文件下载(FileDownloadController)¶
提供 KMS 知识管理模块「文件下载域」的核心能力:按文件 Id / checksum 下载(支持服务端叠加水印)、批量打包下载(ZIP)、按文件 Id 取预览对象,以及外部分享链接的校验与下载。下载类端点直接写 HTTP 响应流(二进制 application/force-download 或 application/octet-stream),不返回统一 Resource JSON 封装;其余端点按统一 Resource 或直接对象序列化。
- 接口类型:REST 资源(
@RestController,继承AbstractBaseController) - 基址:
${myapps.context-path.kms:}/api(类级@RequestMapping仅声明/api前缀,比常见/api/kms少一层;方法级路径自带/kms/...或/files/...段) - Tag:kms文件下载模块
公共说明¶
- 鉴权(据源码
KmsMvcConfig+KmsSecurityFilter):KmsMvcConfig注册全局 Servlet 过滤器KmsSecurityFilter(URL 模式/*,context-path 为/时为/kms/*)。本控制器路径/api/kms/download/**、/api/kms/downloads、/api/kms/disks/files/{fileId}/preview、/api/files/outsideshare/{shareObjId}/verify、/api/files/outsideshare/{shareObjId}/download均不在KmsSecurityFilter.isExcludeURI的豁免名单内(豁免仅覆盖/login.*、/admin、/domain.*、/tray/service、/authtime、service/OfficeServer、.*outsideshare/.*/preview、静态资源后缀、actuator/health等;本控制器outsideshare端点路径以/verify、/download结尾,不匹配/preview正则)。过滤器调用Security.getUserIdFromToken(request),取不到用户则返回 HTTP401(无响应体)。因此所有端点均需 accessToken,可通过以下任一方式传递(据Security.getUserIdFromToken):query 参数accessToken、query 参数access_token(移动端)、请求头accessToken、CookieaccessToken、请求头Authorization: Bearer <token>。完整鉴权机制见 index.md「鉴权说明」。 - 外部分享端点的过滤器旁路(据源码):
KmsSecurityFilter.isExcludeURI内额外判断——当请求同时携带 query 参数id(分享对象 Id)与code(提取码),且fileService.getShareObjById(id).code == code、duration未过期时,将请求标记为预览豁免(preview=true)直接放行。本控制器 #5outSideVerify/ #6outSideDownload的形参shareObjId为**路径变量**、code为 query 参数,本身不读 query 参数id;若调用方在请求路径之外**额外**附带?id=<shareObjId>&code=<有效提取码>且分享未过期,过滤器将放行该请求,跳过 accessToken 校验。无此 query 组合时仍按上述规则要求 accessToken。 - 执行用户:控制器内
getUser()(继承自AbstractBaseController)调用Security.getUserIdFromToken(request)还原当前用户 id,再经 Feign(UserAPI.getUserById)装载KmsUser。无userCode参数。 - 响应结构:本控制器**多数下载端点直接写 HTTP 响应流**(
Content-Type: application/force-download、Content-Disposition: attachment;filename=<编码后文件名>、HTTP 200),不走统一Resource封装;错误时(如权限不足)由全局异常处理器返回统一Resource(见 ../index.md「统一响应结构」),字段为errcode/errmsg/data/errors。其中 #4getFilePreviewByFileId直接以@ResponseBody返回FileObject(非Resource封装),#5outSideVerify返回统一Resource。 - HTTP 状态码与错误码(据源码
AbstractBaseController全局异常处理):成功默认 HTTP 200;InvalidRequestException→ HTTP 400 / errcode=400;UnauthorizedException→ HTTP 403 / errcode=403;ForbiddenException(权限不足,如缺少CODE_DOWNLOAD)→ HTTP 403 / errcode=403;ResourceNotFoundException→ HTTP 404 / errcode=404;其他Exception→ HTTP 500 / errcode=500。 - 下载权限校验(据源码
isPrivilege):基于grantService.isPrivilegedByResourceIdAndUserIdAndOperation(resourceId, GrantOperation.CODE_DOWNLOAD, getUser())校验当前用户对每个folderId/fileId是否拥有CODE_DOWNLOAD操作权限。 - 文件名编码(据源码
getFileNameCoding):Edge 浏览器(User-Agent含EDGE)走URLEncoder.encode(name, UTF-8),其他浏览器走new String(name.getBytes(UTF-8), ISO-8859-1),再写入Content-Disposition头。 - 路径变量:
fileId、checksum、shareObjId均为 KMS 内部主键(明文 id),非 DES 加密密文。
1. 下载文件¶
根据文件 Id 下载文件。若请求带 referer 头且 preview=false,会先校验当前用户对该文件是否拥有 CODE_DOWNLOAD 权限,无权时抛 ForbiddenException(HTTP 403);据源码注释,HiApp 编辑场景调用下载以**跳过权限验证**。支持服务端叠加水印:传入 watermarkString 时,图片类型生成水印图后下载,非图片类型生成带水印的 PDF 后下载(文件名后缀替换为 .pdf)。
- 接口类型:REST 资源(直接写 HTTP 响应流)
- 请求方式:
@RequestMapping(未限定 method,匹配所有 HTTP 方法;按惯例以GET/POST调用) - 请求路径:
/kms/download/{fileId}(完整:{kms-context}/api/kms/download/{fileId}) - 鉴权:是(需 accessToken,据源码)
- Tag:kms文件下载模块
请求参数¶
| 参数名 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| fileId | path | string | 是 | 文件Id |
| watermarkString | query | string | 否 | 水印内容(无注解,Spring 默认按形参名绑定为 query 参数;非空时触发水印处理) |
| preview | query | boolean | 否 | 是否预览模式(required=false;为 true 时跳过 referer 触发的权限校验) |
请求示例¶
响应¶
结构:二进制文件流(非统一 Resource)。
- Content-Type: application/force-download
- Content-Disposition: attachment;filename=<编码后文件名>(带水印且非图片时为 <原名>.pdf)
- HTTP 200:文件字节流直写 response.getOutputStream()
文件不存在或内部异常时,据源码 catch 内 e.printStackTrace() 静默吞掉(不写响应体);权限不足时由全局异常处理器返回:
2. 根据 checksum 下载文件¶
根据文件校验码(checksum)查找匹配文件并下载。服务端会遍历所有匹配的 FileEntity,逐一校验 CODE_DOWNLOAD 权限(无权则 continue),首个有权限的文件即下载;若全部无权或无匹配,抛 ForbiddenException(HTTP 403)。水印内容优先取调用方传入的 watermarkString,否则取服务端按当前用户、网盘、部门等动态生成的水印(watermarkService.getWatermarkContent,场景 download)。图片类型生成水印图,非图片类型生成水印 PDF。
- 接口类型:REST 资源(直接写 HTTP 响应流)
- 请求方式:
@RequestMapping(未限定 method,匹配所有 HTTP 方法;按惯例以GET/POST调用) - 请求路径:
/kms/download-by-checksum/{checksum}(完整:{kms-context}/api/kms/download-by-checksum/{checksum}) - 鉴权:是(需 accessToken,据源码)
- Tag:kms文件下载模块
请求参数¶
| 参数名 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| checksum | path | string | 是 | 文件校验码 |
| watermarkString | query | string | 否 | 水印内容(Swagger 标注 required=false;非空时覆盖服务端动态生成的水印) |
请求示例¶
响应¶
结构:二进制文件流(非统一 Resource)。
- Content-Type: application/force-download
- Content-Disposition: attachment;fileName=<编码后文件名>
- HTTP 200:首个有权限文件的字节流
失败示例(无匹配/无权限):
3. 批量下载文件¶
根据文件夹 Id 数组与文件 Id 数组批量下载,打包为 ZIP。先统一校验所有 folderIds、fileIds 的 CODE_DOWNLOAD 权限,无权即抛 ForbiddenException(HTTP 403)。特殊情形:当仅传 1 个 fileId 且无 folderIds 时,内部转调 download(单文件下载,跳过权限二次校验)。
- 接口类型:REST 资源(直接写 HTTP 响应流)
- 请求方式:
@RequestMapping(未限定 method,匹配所有 HTTP 方法;按惯例以GET/POST调用) - 请求路径:
/kms/downloads(完整:{kms-context}/api/kms/downloads) - 鉴权:是(需 accessToken,据源码)
- Tag:kms文件下载模块
请求参数¶
| 参数名 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| folderIds | query | array(string) | 是 | 文件夹Id数组(无文件夹时传空数组) |
| fileIds | query | array(string) | 是 | 文件Id数组(无文件时传空数组) |
请求示例¶
GET /api/kms/downloads?folderIds=__FOLDERID1__&folderIds=__FOLDERID2__&fileIds=__FILEID1__&accessToken=__TOKEN__ HTTP/1.1
响应¶
结构:二进制 ZIP 流(非统一 Resource)。
- 多文件/含文件夹时:Content-Type: application/force-download,Content-Disposition: attachment;fileName=<时间戳yyyyMMddHHmmss>.zip,HTTP 200
- 单文件(folderIds 空 + fileIds 长度 1)时:走 #1 download 的响应规则
失败示例(权限不足):
4. 获取文件预览对象¶
根据文件 Id 取 FileObject(文件对象,含文件物理信息)。直接以 @ResponseBody 返回 FileObject,不走统一 Resource 封装。
- 接口类型:REST 资源
- 请求方式:
GET - 请求路径:
/kms/disks/files/{fileId}/preview(完整:{kms-context}/api/kms/disks/files/{fileId}/preview) - 鉴权:是(需 accessToken,据源码)
- Tag:kms文件下载模块
请求参数¶
| 参数名 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| fileId | path | string | 是 | 文件Id(实际为 FileObject Id,由 fileService.findByFileObjectId 加载) |
请求示例¶
响应¶
结构:直接序列化的 FileObject(produces=application/json,非统一 Resource),含文件名、类型、大小、路径、是否文件夹等字段。文件未找到时返回 null。
与 file.md #21
FileController的/disks/{diskId}/files/{fileId}/preview(路径含diskId、返回Resource包装的 JSON 字符串)不同:本端点路径**不含diskId,且返回**裸FileObject。
5. 校验外部链接可用性¶
校验外部分享链接的提取码与有效期。若 code 与服务端存储的提取码不一致返回 errcode=400,duration 已过期返回 errcode=400,校验通过返回 errcode=0。注意:本端点为外部链接访问场景设计,但据 KmsSecurityFilter 规则,路径以 /verify 结尾不在豁免名单;调用方需附带 accessToken,或额外以 query 参数 id=<shareObjId>&code=<有效提取码> 触发过滤器的分享预览豁免(详见上文「公共说明」)。
- 接口类型:REST 资源
- 请求方式:
GET - 请求路径:
/files/outsideshare/{shareObjId}/verify(完整:{kms-context}/api/files/outsideshare/{shareObjId}/verify) - 鉴权:是(需 accessToken,据源码;或调用方额外附
?id=<shareObjId>&code=<有效提取码>触发过滤器分享豁免) - Tag:kms文件下载模块
请求参数¶
| 参数名 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| shareObjId | path | string | 是 | 分享对象Id |
| code | query | string | 否 | 提取码(无注解,Spring 默认按形参名绑定为 query 参数;建议传入) |
请求示例¶
响应¶
结构:统一 Resource(见 ../index.md「统一响应结构」)。
data:null(仅以 errcode/errmsg 表达校验结果)。
成功示例:
失败示例(提取码错误/已失效):
6. 外部链接文件下载¶
按外部链接下载分享对象对应的文件或文件夹。先校验 code 与有效期(不匹配/已失效时直接 response.getWriter().write(...) 写错误文本,不抛异常),通过后按对象类型分流:文件夹打包为 ZIP 下载,单文件直接下载(不叠加水印)。注意:与 #5 同理,路径以 /download 结尾不在 KmsSecurityFilter 豁免名单;调用方需 accessToken,或额外以 query 参数 id=<shareObjId>&code=<有效提取码> 触发豁免。
- 接口类型:REST 资源(直接写 HTTP 响应流)
- 请求方式:
GET - 请求路径:
/files/outsideshare/{shareObjId}/download(完整:{kms-context}/api/files/outsideshare/{shareObjId}/download) - 鉴权:是(需 accessToken,据源码;或调用方额外附
?id=<shareObjId>&code=<有效提取码>触发过滤器分享豁免) - Tag:kms文件下载模块
请求参数¶
| 参数名 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| shareObjId | path | string | 是 | 分享对象Id |
| code | query | string | 否 | 提取码(建议传入;不匹配时响应体写错误文本) |
请求示例¶
响应¶
结构:二进制文件流(非统一 Resource)。
- 文件夹:Content-Type: application/force-download,Content-Disposition: attachment;fileName=<时间戳yyyyMMddHHmmss>.zip,HTTP 200
- 单文件:Content-Type: application/force-download,Content-Disposition: attachment;fileName=<编码后文件名>,HTTP 200
失败(提取码错误/已失效/内部异常):以纯文本写入响应体(Content-Type 默认),如 提取码错误!、分享链接已失效!、提取错误:<异常消息>。