--- title: 附件 description: 文件上传(本地 / OSS 直传)、记录落库、删除与静态访问 pageClass: g3-api --- # 附件 文件接口,前缀 `/file/**`。有两种存储模式,由 `erp.file.storage-type` 决定(`local` / `aliyun-oss`)。 | 模式 | 上传链路 | |---|---| | `local` | 前端直接 POST 文件 → 后端写盘 + 落库 | | `aliyun-oss` | 取凭证 → 前端直传 OSS → 回调后端落库 | > **第一步先问模式**:调 `/file/config` 拿到当前模式,再决定走哪条链路。前端组件不要写死。 ## `POST` `/file/config` 返回当前存储模式。 ```json { "code": 0, "message": "操作成功", "data": { "storageType": "local" } } ``` ## `POST` `/file/upload` **本地上传**(`storage-type=local` 时使用)。支持多文件,**是 `multipart/form-data`,不是 JSON**。 | 字段 | 类型 | 必填 | 说明 | |---|---|---|---| | `files` | file[] | 是 | 文件,key 固定为 `files`,可重复 | | `father` | string | 是 | 宿主业务主键(存宿主 `b_id`) | | `moduleId` | string | 是 | 模块编码 | | `cateId` | string | | 分类 ID | ```bash curl -X POST http://localhost:8088/api/file/upload \ -H 'Authorization: Bearer ' \ -F 'files=@report.pdf' \ -F 'father=1234567890' \ -F 'moduleId=v_b_othercompany' \ -F 'cateId=987654321' ``` ```json { "code": 0, "message": "操作成功", "data": [ { "subid": "1234567890123", "mx_filename": "report.pdf", "mx_filesize": "20480", "mx_fileext": "pdf", "mx_mapfilename": "/api/file/static/G3HD/2026/10/13/abc.pdf", "father": "1234567890", "mx_moduleid": "v_b_othercompany", "mx_cate_id": "987654321" } ] } ``` - `mx_mapfilename` 就是**访问地址**,预览 / 下载直接用它。 - `subid` 是雪花 ID,**本地模式由后端自己取号**后写入 `bf_files`(前端不用管)。 ## `POST` `/file/upload-credential` **取 OSS 直传凭证**(`storage-type=aliyun-oss` 时使用)。无请求字段。 后端转发到 g3oss,`bucket` 即当前机构码。 ```json { "code": 0, "message": "操作成功", "data": { "accessid": "LTAI...", "policy": "eyJleHBpcmF0aW9uIjoi...", "signature": "abc123==", "dir": "G3HD/", "host": "https://g3soft.oss-cn-hangzhou.aliyuncs.com", "expire": 1700000000 } } ``` > **字段以 g3oss 实际返回为准**:后端只做 `readValue(...).Map` 的**原样透传**,不加工也不改名。 > 上面是 OSS 直传的常见字段形状,接入时请以实际返回为准;凭证有有效期,取到后应尽快直传。 ## `POST` `/file/save-record` **OSS 直传完成后回调落库**(`storage-type=aliyun-oss` 时使用)。本地模式不需要单独调它。 | 字段 | 类型 | 必填 | 说明 | |---|---|---|---| | `subid` | number | 是 | 上传前取的雪花 ID | | `father` | string | 是 | 宿主业务主键 | | `mx_moduleid` | string | 是 | 模块编码 | | `mx_cate_id` | string | | 分类 ID | | `mx_filename` | string | 是 | 原始文件名 | | `mx_filesize` | string | 是 | 文件大小(字节) | | `mx_fileext` | string | 是 | 扩展名 | | `mx_mapfilename` | string | 是 | OSS 返回的完整 URL | ```json { "code": 0, "message": "操作成功", "data": { "subid": "1234567890123", "message": "记录已保存" } } ``` > **`subid` 在 OSS 链路必须由前端在上传前取号**(调 `/data/nextid`),因为凭证直传时后端不在链路上,无法代为取号。 > 本地链路则相反:**后端自己取号**,前端不用传。两条链路都不能依赖数据库自增。 ## `POST` `/file/delete` 删除文件:**先删存储、后删 DB 行**。 | 字段 | 类型 | 必填 | 说明 | |---|---|---|---| | `subid` | string | 是 | 文件记录 ID | ```json { "code": 0, "message": "操作成功", "data": null } ``` 外部对象操作**不属于数据库事务**: - 删存储失败 → **不删数据库行**(保持可重试)。 - 数据库删除失败 → 保留可重试的清理记录。 记录不存在时静默返回成功(幂等)。 ## `GET` `/file/static/{fileName}` **本地模式**下的文件访问入口。不是 JSON 接口,直接返回文件流。 ```text GET /api/file/static/G3HD/2026/10/13/abc.pdf ``` - 路径形如 `{orgId}/{yyyy}/{MM}/{dd}/{file}`,与 `mx_mapfilename` 去掉前缀后的部分对应。 - 带**路径穿越防护**:解析后必须仍在存储根目录内,否则 404。 - 响应头带 `Content-Disposition: inline`,浏览器可直接预览。 ## 相关数据表 | 表 | 说明 | |---|---| | `bf_files` | 文件记录(主键 `subid` 雪花 ID,`father` 存宿主主键) | | `bf_files_category` | 文件分类(`b_module_id` 为空表示跨模块通用分类) | ## 常见错误 | 场景 | 表现 | |---|---| | 本地上传时缺少 `files` | `1000` 参数错误 | | 文件静态访问越界或不存在 | **HTTP 404**(这条路径不走统一响应格式) | | OSS 凭证获取失败 | `1000`「获取 OSS 上传凭证失败: {状态码}」 | | 存储模式不匹配 | 例如 `local` 模式下调 `/file/save-record` 会落库成功但文件并不存在——**要先查 `/file/config`** |