表单文件上传执行(FileUploadController)¶
表单附件/图片上传执行接口:文件上传(三种落盘:系统默认 / 本地路径 / 企业网盘)、文件排序、删除文件、获取历史版本、恢复历史版本、生成历史版本、按用户分页获取关联文件。基址为 /api(继承自 AbstractRuntimeController 的 @RestController,方法返回统一 Resource)。
落盘模式**由上传字段
AbstractUploadField.filePattern(下发属性名fileSaveMode)决定;01/02的目标路径由服务端执行字段 **pathScript(目标路径脚本) 计算,不下发脚本源码到前端。设计说明见仓库docs/superpowers/specs/2026-08-06-upload-field-store-place-design.md。
- 接口类型:REST 资源(
@RestController继承AbstractRuntimeController,方法返回Resource,由 Spring 以 JSON 序列化输出) - 基址:
${myapps.context-path.runtime:}/api - Tag:表单文件上传执行模块
- 源码:
cn.myapps.runtime.dynaform.form.controller.FileUploadController
公共说明¶
- 鉴权(据源码
RuntimeMvcConfig+RestSecurityHandlerInterceptor):本控制器所有方法级路径均位于/api/runtime/**下(类级/api+ 方法级/runtime/...),在RestSecurityHandlerInterceptor覆盖范围内,且不在豁免名单(豁免名单见 login.md「公共说明 · 鉴权」)。所有端点均需 accessToken(或 debugToken),可通过 Cookie / 请求头 / query 参数任一方式传递;执行用户从令牌还原,无userCode参数。控制器内getUser()必须能取到非空WebUser,否则在DesUtil.decryptTextByUserId(applicationId, getUser().getId())处即抛 NPE。 - 路径变量
{applicationId}:经 DES 加密(按当前执行用户密钥),服务端在方法入口处DesUtil.decryptTextByUserId(applicationId, getUser().getId())解密。 - 响应结构:除特别注明外,所有端点返回统一
Resource(见 ../index.md「统一响应结构」)。 - HTTP 状态码:所有端点类级标注
@ResponseStatus(HttpStatus.OK),成功统一返回 200;业务错误由响应体errcode体现。 - 文件上传安全:上传文件名禁止包含
/或\;扩展名须经FileUtil.isLegalFileExt(extName)校验,否则返回error(4001, "上传的文件类型不合法!", null)。 - 落盘意图:有绑定表单字段时,以字段上的
filePattern为准(忽略客户端伪造的fileSaveMode/path用于01/02实际存储);无字段时回退 query 的fileSaveMode(默认00)。
落盘模式(filePattern / fileSaveMode)¶
| 值 | 含义 | 行为 |
|---|---|---|
00 |
系统默认 | 不执行 pathScript;落盘 {storageRoot}/uploads/item/{应用名}/{年}/{月}/...(沿用 ITEM_PATH) |
01 |
本地路径 | 服务端执行 pathScript → 相对路径 rel;物理路径 {storageRoot}/{rel}/...;返回相对 storageRoot 的 filePath |
02 |
企业网盘 | 服务端执行 pathScript → Feign resolve-or-create → Feign /kms/upload;使用 KMS 返回的 id/filePath;无本地实体文件时跳过依赖本地路径的转换任务 |
路径规范化(01/02):统一 /;去首尾空白与多余 /;禁止 .. 段(否则 errcode=4001,路径不允许包含..)。pathScript 未配置或脚本结果为空 → 4001。
1. 文件上传¶
接收 multipart 文件数组,按字段 filePattern(及 pathScript)落盘到系统默认目录、本地自定义相对路径,或经 Feign 归档到企业网盘。返回每个文件的 {fileName, filePath, fileSize, fileType, id} 数组。
- 接口类型:REST 资源(multipart/form-data 文件上传)
- 请求方式:
POST - 请求路径:
/runtime/upload(完整:{runtime-context}/api/runtime/upload) - 鉴权:是(需 accessToken,据源码)
- Tag:表单文件上传执行模块
请求参数¶
| 参数名 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| applicationId | query | string | 是 | 软件 id(DES 加密密文) |
| allowedTypes | query | string | 是 | 允许上传类型(如 image、file) |
| fieldId | query | string | 是 | 表单字段 id(拼接 docId 后用于定位字段名) |
| fileSaveMode | query | string | 是 | 文件保存模式(00/01/02);有字段时以字段 filePattern 为准 |
| path | query | string | 是 | 上传路径占位;00 时参与目录计算,01/02 时真实路径仅由服务端 pathScript 决定 |
| actionType | query | string | 是 | 类型;为 excelImport 时不执行 swf 转换 |
| sourceFileId | query | string | 否 | 源文件 id(用于版本延续,自动递增 V0.x) |
| docId | query | string | 否 | 文档 id(DES 加密密文,用于绑定表单与摘要);01/02 必填(需加载文档以执行脚本) |
| files | body (form-data) | MultipartFile[] | 是 | 多个文件 |
请求示例(00 系统默认)¶
POST /api/runtime/upload?applicationId=__ENC_APPID__&allowedTypes=file&fieldId=__FIELDID__&fileSaveMode=00&path=/uploads/item&actionType=upload&docId=__ENC_DOCID__ HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
Cookie: accessToken=eyJhbGciOiJIUzI1NiJ9...
------WebKitFormBoundary
Content-Disposition: form-data; name="files"; filename="demo.png"
Content-Type: image/png
<二进制内容>
------WebKitFormBoundary--
请求示例(01/02)¶
path 可传空或占位;落盘目录由字段 pathScript 在服务端计算。02 时 Runtime 先调 KMS resolve-or-create,再调 upload(folderId, file)。
POST /api/runtime/upload?applicationId=__ENC_APPID__&allowedTypes=file&fieldId=__DOCID___attachment&fileSaveMode=02&path=&actionType=upload&docId=__ENC_DOCID__ HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
Cookie: accessToken=eyJhbGciOiJIUzI1NiJ9...
------WebKitFormBoundary
Content-Disposition: form-data; name="files"; filename="report.docx"
Content-Type: application/octet-stream
<二进制内容>
------WebKitFormBoundary--
响应¶
结构:统一 Resource(见 ../index.md「统一响应结构」)。
data:JSONArray,每个元素为 { id, fileName, filePath, fileSize, fileType }。
{
"errcode": 0,
"errmsg": "ok",
"data": [
{
"fileName": "demo.png",
"filePath": "/uploads/item/DemoApp/2026/8/demo-xxx.png",
"fileSize": 102400,
"fileType": ".png",
"id": "demo-xxx"
}
],
"errors": null
}
失败示例:
02时 KMS Feign 失败会透传对方errcode/errmsg(如企业盘数量异常为 400)。
2. 文件排序¶
按 JSON 请求体的路径定位 applicationId/formId/docId/itemName/itemValue,调用 DocumentProcess.updateFileSorting 更新指定字段的文件顺序,返回排序后的文件列表。
- 接口类型:REST 资源
- 请求方式:
POST - 请求路径:
/runtime/files/sort(完整:{runtime-context}/api/runtime/files/sort) - 鉴权:是(需 accessToken,据源码)
- Tag:表单文件上传执行模块
请求参数¶
| 参数名 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| content | body | string(JSON) | 是 | 请求包体,含下表字段 |
请求体字段¶
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| applicationId | string | 是 | 软件 id(DES 加密密文) |
| formId | string | 是 | 表单 id |
| docId | string | 是 | 文档 id(DES 加密密文) |
| itemName | string | 是 | 字段名 |
| itemValue | string | 是 | 字段值(含排序后文件清单) |
{
"applicationId": "__ENC_APPID__",
"formId": "__FORMID__",
"docId": "__ENC_DOCID__",
"itemName": "attachment",
"itemValue": "..."
}
请求示例¶
POST /api/runtime/files/sort HTTP/1.1
Content-Type: application/json
Cookie: accessToken=eyJhbGciOiJIUzI1NiJ9...
{ "applicationId": "__ENC_APPID__", "formId": "__FORMID__", "docId": "__ENC_DOCID__", "itemName": "attachment", "itemValue": "..." }
响应¶
结构:统一 Resource。
data:JSONArray,排序后 Document 字段反序列化得到的 datas 数组。
{ "errcode": 0, "errmsg": "ok", "data": [{ "id": "demo-xxx", "fileName": "demo.png" }], "errors": null }
3. 删除文件¶
按 path 定位物理文件并删除,同时移除 UploadVO 元数据。path 中以 _/uploads 分隔前缀作为 fileId,否则按 PATH 列查询。
- 接口类型:REST 资源
- 请求方式:
DELETE - 请求路径:
/runtime/files/delete(完整:{runtime-context}/api/runtime/files/delete) - 鉴权:是(需 accessToken,据源码)
- Tag:表单文件上传执行模块
请求参数¶
| 参数名 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| applicationId | query | string | 是 | 软件 id(DES 加密密文) |
| docId | query | string | 是 | 文档 id(DES 加密密文,源码对该参数未单独 DES 解密,仅作为请求入参) |
| path | query | string | 是 | 文件保存路径(需 URL 编码,服务端 URLDecoder 解码) |
| formId | query | string | 是 | 表单 id |
请求示例¶
DELETE /api/runtime/files/delete?applicationId=__ENC_APPID__&docId=__ENC_DOCID__&path=%2Fuploads%2Fdemo%2Fdemo-xxx.png&formId=__FORMID__ HTTP/1.1
Cookie: accessToken=eyJhbGciOiJIUzI1NiJ9...
响应¶
结构:统一 Resource。
data:string,固定为 "删除成功"(即便 path 为空也返回成功)。
失败示例(文件存在但删除失败抛 OBPMValidateException):
4. 获取文件历史版本信息¶
按 fileId 沿 sourceFileId 链向上回溯,返回该文件的全部历史版本(不含当前版本)。
- 接口类型:REST 资源
- 请求方式:
GET - 请求路径:
/runtime/{applicationId}/files/{fileId}/versions(完整:{runtime-context}/api/runtime/{applicationId}/files/{fileId}/versions) - 鉴权:是(需 accessToken,据源码)
- Tag:表单文件上传执行模块
请求参数¶
| 参数名 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| applicationId | path | string | 是 | 软件 id(DES 加密密文) |
| fileId | path | string | 是 | 文件 id |
请求示例¶
GET /api/runtime/__ENC_APPID__/files/__FILEID__/versions HTTP/1.1
Cookie: accessToken=eyJhbGciOiJIUzI1NiJ9...
响应¶
结构:统一 Resource。
data:List<UploadVO>,沿 sourceFileId 链回溯到的历史版本集合(无历史时为空数组)。
{
"errcode": 0,
"errmsg": "ok",
"data": [
{ "id": "__FILEID_V1__", "name": "demo.png", "versionNo": "V1.0", "sourceFileId": null }
],
"errors": null
}
5. 恢复文件历史版本¶
将 recoverFileId 指定的文件克隆一份新记录(versionNo 在 sourceFileId 的基础上 +0.1),并复制物理文件与对应 swf/pdf 产物,作为「恢复」出的新版本。
- 接口类型:REST 资源
- 请求方式:
POST - 请求路径:
/runtime/{applicationId}/files/{recoverFileId}/versions/recover(完整:{runtime-context}/api/runtime/{applicationId}/files/{recoverFileId}/versions/recover) - 鉴权:是(需 accessToken,据源码)
- Tag:表单文件上传执行模块
请求参数¶
| 参数名 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| applicationId | path | string | 是 | 软件 id(DES 加密密文) |
| recoverFileId | path | string | 是 | 待恢复的目标文件 id |
| sourceFileId | query | string | 是 | 源文件 id(用于取上一版本号) |
请求示例¶
POST /api/runtime/__ENC_APPID__/files/__RECOVERFILEID__/versions/recover?sourceFileId=__SOURCEFILEID__ HTTP/1.1
Cookie: accessToken=eyJhbGciOiJIUzI1NiJ9...
响应¶
结构:统一 Resource。
data:UploadVO,新克隆生成的版本记录(含 id/versionNo/path/sourceFileId 等字段)。
{
"errcode": 0,
"errmsg": "ok",
"data": {
"id": "demo-xxx-new",
"name": "demo.png",
"versionNo": "V1.2",
"sourceFileId": "__SOURCEFILEID__"
},
"errors": null
}
6. 生成文件历史版本¶
将 fileId 指定的当前文件克隆出一份历史版本(新 versionNo +0.1),同时把当前文件的 sourceFileId 指向新版本,复制底层物理文件。
- 接口类型:REST 资源
- 请求方式:
POST - 请求路径:
/runtime/{applicationId}/files/{fileId}/versions/build(完整:{runtime-context}/api/runtime/{applicationId}/files/{fileId}/versions/build) - 鉴权:是(需 accessToken,据源码)
- Tag:表单文件上传执行模块
请求参数¶
| 参数名 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| applicationId | path | string | 是 | 软件 id(DES 加密密文) |
| fileId | path | string | 是 | 文件 id |
请求示例¶
POST /api/runtime/__ENC_APPID__/files/__FILEID__/versions/build HTTP/1.1
Cookie: accessToken=eyJhbGciOiJIUzI1NiJ9...
响应¶
结构:统一 Resource。
data:List<UploadVO>,源码恒为 new ArrayList<>()(即空数组,仅表示操作成功)。
7. 获取用户相关文件信息¶
分页查询当前登录用户上传的文件列表,可按 fileName 与 customizeType 过滤。
- 接口类型:REST 资源
- 请求方式:
GET - 请求路径:
/runtime/{applicationId}/files/relevances(完整:{runtime-context}/api/runtime/{applicationId}/files/relevances) - 鉴权:是(需 accessToken,据源码)
- Tag:表单文件上传执行模块
请求参数¶
| 参数名 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| applicationId | path | string | 是 | 软件 id(DES 加密密文) |
| pageNo | query | int | 否 | 页码,默认 1 |
| linesPerPage | query | int | 否 | 每页行数,默认 10 |
| fileName | query | string | 否 | 文件名模糊过滤 |
| customizeType | query | string | 否 | 文件格式过滤(扩展名) |
请求示例¶
GET /api/runtime/__ENC_APPID__/files/relevances?pageNo=1&linesPerPage=10&fileName=demo&customizeType=.png HTTP/1.1
Cookie: accessToken=eyJhbGciOiJIUzI1NiJ9...
响应¶
结构:统一 Resource。
data:DataPackage<UploadVO>,分页结果(含 datas/pageCount/pageNo/linesPerPage/rowCount)。
{
"errcode": 0,
"errmsg": "ok",
"data": {
"datas": [{ "id": "__FILEID__", "name": "demo.png", "path": "/uploads/demo/demo-xxx.png" }],
"pageNo": 1,
"linesPerPage": 10,
"pageCount": 1,
"rowCount": 1
},
"errors": null
}