This commit is contained in:
2026-10-27 22:48:36 +08:00
parent cf832f0962
commit 1231251ea4
13 changed files with 946 additions and 0 deletions
+149
View File
@@ -0,0 +1,149 @@
# 文件上传服务接口文档
> 本文件描述文件上传服务的全部接口,包含**后台管理(`/api/admin/files`)**与**小程序端(`/api/ma/files`)**两套入口。两套入口复用同一套业务逻辑(`internal/service/file`)与存储后端,仅鉴权方式不同。公共约定(统一响应、错误码、中间件)见 [admin-api.md](./admin-api.md) 与 [miniapp-api.md](./miniapp-api.md)。
## 1. 概述
- **后台入口基础路径**:`/api/admin/files`
- **小程序入口基础路径**:`/api/ma/files`
- **鉴权**:
- 后台接口需携带 `Authorization: Bearer <JWT>`(由 `AdminAuth` 校验);当前仅要求已登录管理员,**未做细粒度权限码守卫**。
- 小程序接口需携带 `X-App-ID`(`MiniAppAuth` 校验小程序被纳管且启用)与 `Authorization: Bearer <token>` / `X-Session-Token`(由 `MiniUserAuth` 校验登录态)。
- **统一响应**:与全局一致,成功 `code=0`;失败通过 `code` 区分(见 §5)。
- **上传者身份**:由路由分组自动解析——后台为管理员 `admin_id`,小程序为当前登录用户 `user_id`,并写入文件记录(`uploader_type` / `uploader_id`)。未认证调用上传返回 `40100`。
## 2. 配置与存储
- 配置位于 `configs/config.yaml` 的 `upload` 段:`storage_type`(当前 `local`)、`local_path`、`base_url`、`max_size`(字节)、`allowed_ext`(扩展名白名单,空表示不限制)。
- 存储后端抽象为 `internal/pkg/storage.Storage` 接口,当前提供本地磁盘实现(`local`);`minio` 等对象存储**预留**,后续仅需扩展 `storage.New` 的 `case` 分支与对应实现,业务层无感。
- 本地模式下,文件按日期分片落盘:`{local_path}/{yyyy}/{mm}/{dd}/<rand>.<ext>`;返回的 `url` 形如 `{base_url}/{yyyy}/{mm}/{dd}/<rand>.<ext>`,由 `r.Static` 对外提供访问(与 `GET .../raw` 等价)。
- 建表脚本见 `scripts/003_files.sql`(执行顺序在 RBAC、小程序脚本之后)。
## 3. 接口列表
### 3.1 `POST /api/admin/files/upload`(后台上传)
以 `multipart/form-data` 上传单个文件,表单字段名为 `file`。
**请求头**:`Authorization: Bearer <JWT>`;`Content-Type: multipart/form-data`。
**表单字段**
| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `file` | file | 是 | 待上传文件。大小受 `max_size` 限制,扩展名受 `allowed_ext` 限制。 |
**响应**
```json
{
"code": 0,
"msg": "ok",
"data": {
"id": 1,
"url": "/files/static/2026/10/27/3f2a...c1.png",
"filename": "3f2a...c1.png",
"origin_name": "我的图片.png",
"mime_type": "image/png",
"size": 12345
}
}
```
**错误码**:缺文件 `40000`;未认证 `40100`;超限 `41300`;类型不允许 `41500`;落盘/落库失败 `50000`。
**curl 示例**
```bash
curl -X POST http://localhost:8080/api/admin/files/upload \
-H "Authorization: Bearer <JWT>" \
-F "file=@/path/to/我的图片.png"
```
### 3.2 `GET /api/admin/files/:id`(后台查询元数据)
查询指定文件的元数据记录。
**路径参数**:`id`(uint64)。**响应**:返回 `File` 对象(见 §4);不存在返回 `40400`。
### 3.3 `GET /api/admin/files/:id/raw`(后台获取文件流)
以流形式返回文件内容,按 `mime_type` 设置 `Content-Type`,`Content-Disposition: inline`,可用于浏览器预览/下载。
**路径参数**:`id`(uint64)。**响应**:二进制流;不存在返回 `40400`。
> 也可直接通过上传返回的 `url` 访问(本地模式由静态目录服务,等效于本接口)。
### 3.4 `DELETE /api/admin/files/:id`(后台删除)
删除文件:同时清理存储内容与 `files` 表元数据记录。
**路径参数**:`id`(uint64)。**响应**:`{ "code": 0, "msg": "ok", "data": { "id": 1 } }`;不存在返回 `40400`。
### 3.5 `POST /api/ma/files/upload`(小程序上传)
与 3.1 一致,但鉴权为小程序登录态。
**请求头**:`X-App-ID: <AppID>` + `Authorization: Bearer <token>`(或 `X-Session-Token`)。
**curl 示例**
```bash
curl -X POST http://localhost:8080/api/ma/files/upload \
-H "X-App-ID: wx1234567890" \
-H "Authorization: Bearer a1b2c3..." \
-F "file=@/path/to/photo.jpg"
```
### 3.6 `GET /api/ma/files/:id`(小程序查询元数据)
同 3.2,返回 `File` 对象;不存在 `40400`。
### 3.7 `GET /api/ma/files/:id/raw`(小程序获取文件流)
同 3.3,返回文件流;不存在 `40400`。
### 3.8 `DELETE /api/ma/files/:id`(小程序删除)
同 3.4,删除文件内容与元数据;不存在 `40400`。
## 4. 数据模型
### 4.1 File(`files` 表)
| 字段 | 类型 | JSON | 说明 |
| --- | --- | --- | --- |
| `ID` | uint64 | `id` | 主键,自增。 |
| `StorageKey` | string | `storage_key` | 存储后端内的 key,形如 `2026/10/27/<rand>.<ext>`。 |
| `Filename` | string | `filename` | 落盘文件名(含扩展名)。 |
| `OriginName` | string | `origin_name` | 用户上传时的原始文件名。 |
| `MimeType` | string | `mime_type` | 由扩展名推断,未知回退 `application/octet-stream`。 |
| `Ext` | string | `ext` | 小写扩展名,不含点。 |
| `Size` | int64 | `size` | 文件字节数。 |
| `UploaderType` | string | `uploader_type` | 上传者类型:`admin` 或 `ma`。 |
| `UploaderID` | uint64 | `uploader_id` | 上传者主键(管理员 ID 或小程序用户 ID)。 |
| `URL` | string | `url` | 可访问地址(`base_url` 拼接 `storage_key`)。 |
| `CreatedAt` | time | `created_at` | 创建时间(`Y-m-d H:i:s`)。 |
> `GET .../raw` 直接返回二进制流,不返回 `File` JSON;`GET .../:id` 返回上述 `File` 对象。
> 上传成功响应(§3.1)仅返回摘要字段:`id` / `url` / `filename` / `origin_name` / `mime_type` / `size`。
## 5. 错误码
| 业务码 | 常量 | 含义 |
| --- | --- | --- |
| `0` | — | 成功 |
| `40000` | `ErrBadRequest` | 请求错误(如未选择文件 / ID 非法) |
| `40100` | `ErrUnauthorized` | 未认证(上传接口要求已登录身份) |
| `40300` | `ErrForbidden` | 禁止访问 |
| `40400` | `ErrNotFound` | 文件不存在 |
| `41300` | `ErrFileTooLarge` | 文件大小超过 `max_size` 限制 |
| `41500` | `ErrFileTypeForbid` | 扩展名不在 `allowed_ext` 白名单 |
| `50000` | `ErrServer` | 服务内部错误(存储 IO / 落库失败等) |
## 6. 备注
- **批量上传**:当前仅支持单文件(`form` 字段 `file`)。批量能力可后续在 handler 层遍历 `c.MultipartForm.File["file"]` 调用同一 `svc.Upload`,Service 无需改动。
- **对象存储扩展**:接入 minIO/S3 时新增 `internal/pkg/storage/<backend>` 实现 `Storage` 接口,在 `storage.New` 增加对应 `case` 并在 `config.yaml` 设置 `upload.storage_type` 即可,接口与 Service 层保持不变。
- **安全**:存储 key 经 `filepath.Clean` 与根目录二次校验,杜绝目录穿越;文件名以随机值重命名,原始名仅作展示,避免路径注入。
- **静态访问公开性**:本地模式返回的 `url` 由 `r.Static` 公开服务。若文件需鉴权访问,应改走 `.../raw` 接口(受对应入口鉴权保护),或后续切换为对象存储签名 URL。
+1
View File
@@ -342,3 +342,4 @@ curl "http://localhost:8080/api/ma/users?page=1&size=20" \
- **多小程序**:新增 `app2`..`app10` 时,按 `internal/handler/miniapp/app1`、`internal/service/miniapp/app1` 的目录结构各自建包,在 `router.New` 中注册即可,互不耦合。新增小程序后须在本文档补充其接口。
- **会话存储**:默认 `MemoryStore`(单实例),多实例部署应替换为 Redis 实现(复用 `session.Store` 接口)。
- **业务接口**:`/api/ma` 已实现 `login`/`logout`、用户资料查询与修改(`/profile`)、按 ID 查询用户(`/users/:id`)与用户分页列表(`/users`)。新增业务接口时,应在路由中增加基于会话 token 的用户身份中间件(`MiniUserAuth`)后再登记,并同步更新本文档 §3。
- **文件上传**:`/api/ma/files/*`(上传 `POST /upload`、元数据 `GET /:id`、文件流 `GET /:id/raw`、删除 `DELETE /:id`)的小程序端接口见 [file-api.md](./file-api.md);后台端(`/api/admin/files/*`)同见该文档(含配置、存储与错误码)。