This commit is contained in:
oneao committed 2026-10-13 22:32:50 +08:00
1 parent f3936cec61
commit 15e0ae5b67
115 files changed
+11399 -2741

No files matched your search

+203
View File
@@ -0,0 +1,203 @@
---
title: 附件
description: 文件上传(本地 / OSS 直传)、记录落库、删除与静态访问
pageClass: g3-api
---
# 附件
文件接口,前缀 `/file/**`。有两种存储模式,由 `erp.file.storage-type` 决定(`local` / `aliyun-oss`)。
| 模式 | 上传链路 |
|---|---|
| `local` | 前端直接 POST 文件 → 后端写盘 + 落库 |
| `aliyun-oss` | 取凭证 → 前端直传 OSS → 回调后端落库 |
> **第一步先问模式**:调 `/file/config` 拿到当前模式,再决定走哪条链路。前端组件不要写死。
## `POST` `/file/config`
返回当前存储模式。
<G3ApiPayload title="响应" :code="0">
```json
{
"code": 0,
"message": "操作成功",
"data": { "storageType": "local" }
}
```
</G3ApiPayload>
## `POST` `/file/upload`
**本地上传**(`storage-type=local` 时使用)。支持多文件,**是 `multipart/form-data`,不是 JSON**。
<G3ApiParams title="表单字段">
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `files` | file[] | 是 | 文件,key 固定为 `files`,可重复 |
| `father` | string | 是 | 宿主业务主键(存宿主 `b_id`) |
| `moduleId` | string | 是 | 模块编码 |
| `cateId` | string | | 分类 ID |
</G3ApiParams>
<G3ApiPayload title="请求">
```bash
curl -X POST http://localhost:8088/api/file/upload \
-H 'Authorization: Bearer <token>' \
-F 'files=@report.pdf' \
-F 'father=1234567890' \
-F 'moduleId=v_b_othercompany' \
-F 'cateId=987654321'
```
</G3ApiPayload>
<G3ApiPayload title="响应" :code="0">
```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"
}
]
}
```
</G3ApiPayload>
- `mx_mapfilename` 就是**访问地址**,预览 / 下载直接用它。
- `subid` 是雪花 ID,**本地模式由后端自己取号**后写入 `bf_files`(前端不用管)。
## `POST` `/file/upload-credential`
**取 OSS 直传凭证**(`storage-type=aliyun-oss` 时使用)。无请求字段。
后端转发到 g3oss,`bucket` 即当前机构码。
<G3ApiPayload title="响应" :code="0">
```json
{
"code": 0,
"message": "操作成功",
"data": {
"accessid": "LTAI...",
"policy": "eyJleHBpcmF0aW9uIjoi...",
"signature": "abc123==",
"dir": "G3HD/",
"host": "https://g3soft.oss-cn-hangzhou.aliyuncs.com",
"expire": 1700000000
}
}
```
</G3ApiPayload>
> **字段以 g3oss 实际返回为准**:后端只做 `readValue(...).Map` 的**原样透传**,不加工也不改名。
> 上面是 OSS 直传的常见字段形状,接入时请以实际返回为准;凭证有有效期,取到后应尽快直传。
## `POST` `/file/save-record`
**OSS 直传完成后回调落库**(`storage-type=aliyun-oss` 时使用)。本地模式不需要单独调它。
<G3ApiParams title="请求字段">
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `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 |
</G3ApiParams>
<G3ApiPayload title="响应" :code="0">
```json
{
"code": 0,
"message": "操作成功",
"data": { "subid": "1234567890123", "message": "记录已保存" }
}
```
</G3ApiPayload>
> **`subid` 在 OSS 链路必须由前端在上传前取号**(调 `/data/nextid`),因为凭证直传时后端不在链路上,无法代为取号。
> 本地链路则相反:**后端自己取号**,前端不用传。两条链路都不能依赖数据库自增。
## `POST` `/file/delete`
删除文件:**先删存储、后删 DB 行**。
<G3ApiParams title="请求字段">
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `subid` | string | 是 | 文件记录 ID |
</G3ApiParams>
<G3ApiPayload title="响应" :code="0">
```json
{ "code": 0, "message": "操作成功", "data": null }
```
</G3ApiPayload>
外部对象操作**不属于数据库事务**:
- 删存储失败 → **不删数据库行**(保持可重试)。
- 数据库删除失败 → 保留可重试的清理记录。
记录不存在时静默返回成功(幂等)。
## `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`** |