跳转至

表单文件上传执行(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
}

失败示例:

{ "errcode": 4001, "errmsg": "上传的文件类型不合法!", "data": null, "errors": null }
{ "errcode": 4001, "errmsg": "数据异常,请重新创建或者保存后重试!", "data": null, "errors": null }
{ "errcode": 4001, "errmsg": "目标路径脚本未配置", "data": null, "errors": null }
{ "errcode": 4001, "errmsg": "目标路径脚本结果为空", "data": null, "errors": null }
{ "errcode": 4001, "errmsg": "路径不允许包含..", "data": null, "errors": null }
{ "errcode": 400, "errmsg": "企业网盘数量异常,期望唯一企业盘", "data": null, "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 为空也返回成功)。

{ "errcode": 0, "errmsg": "ok", "data": "删除成功", "errors": null }

失败示例(文件存在但删除失败抛 OBPMValidateException):

{ "errcode": 500, "errmsg": "File(<realPath>) delete failed", "data": null, "errors": null }


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<>()(即空数组,仅表示操作成功)。

{ "errcode": 0, "errmsg": "ok", "data": [], "errors": null }

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
}