Files
portal-mini-go/.codebuddy/docs/file-api.md
T
2026-10-27 22:55:36 +08:00

204 lines
8.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 文件上传服务接口文档
> 本文件描述文件上传服务的全部接口,包含**后台管理(`/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 `GET /api/admin/files`(后台文件列表)
分页列出文件,支持按上传者类型/ID 与关键字过滤,供后台「文件管理」栏目使用。
**请求头**:`Authorization: Bearer <JWT>`。
**查询参数**
| 参数 | 类型 | 必填 | 默认值 | 说明 |
| --- | --- | --- | --- | --- |
| `page` | int | 否 | `1` | 页码,从 1 开始。 |
| `size` | int | 否 | `20` | 每页条数。 |
| `keyword` | string | 否 | 空 | 模糊匹配 `origin_name` 或 `filename`。 |
| `uploader_type` | string | 否 | 空 | 按上传者类型过滤:`admin` / `ma`;空表示不限。 |
| `uploader_id` | uint64 | 否 | `0` | 按上传者主键过滤;`0` 表示不限。 |
**响应**
```json
{
"code": 0,
"msg": "ok",
"data": {
"list": [
{
"id": 1,
"storage_key": "2026/10/27/3f2a...c1.png",
"filename": "3f2a...c1.png",
"origin_name": "我的图片.png",
"mime_type": "image/png",
"ext": "png",
"size": 12345,
"uploader_type": "admin",
"uploader_id": 1,
"url": "/files/static/2026/10/27/3f2a...c1.png",
"created_at": "2026-10-27 22:00:00"
}
],
"total": 1,
"page": 1,
"size": 20
}
}
```
**错误码**:未认证 `40100`;查询失败 `50000`。
**curl 示例**
```bash
curl "http://localhost:8080/api/admin/files?keyword=png&page=1&size=20" \
-H "Authorization: Bearer <JWT>"
```
### 3.2 `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.3 `GET /api/admin/files/:id`(后台查询元数据)
查询指定文件的元数据记录。
**路径参数**:`id`(uint64)。**响应**:返回 `File` 对象(见 §4);不存在返回 `40400`。
### 3.4 `GET /api/admin/files/:id/raw`(后台获取文件流)
以流形式返回文件内容,按 `mime_type` 设置 `Content-Type`,`Content-Disposition: inline`,可用于浏览器预览/下载。
**路径参数**:`id`(uint64)。**响应**:二进制流;不存在返回 `40400`。
> 也可直接通过上传返回的 `url` 访问(本地模式由静态目录服务,等效于本接口)。
### 3.5 `DELETE /api/admin/files/:id`(后台删除)
删除文件:同时清理存储内容与 `files` 表元数据记录。
**路径参数**:`id`(uint64)。**响应**:`{ "code": 0, "msg": "ok", "data": { "id": 1 } }`;不存在返回 `40400`。
### 3.6 `POST /api/ma/files/upload`(小程序上传)
与 3.2 一致,但鉴权为小程序登录态。
**请求头**:`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.7 `GET /api/ma/files/:id`(小程序查询元数据)
同 3.3,返回 `File` 对象;不存在 `40400`。
### 3.8 `GET /api/ma/files/:id/raw`(小程序获取文件流)
同 3.4,返回文件流;不存在 `40400`。
### 3.9 `DELETE /api/ma/files/:id`(小程序删除)
同 3.5,删除文件内容与元数据;不存在 `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.2)仅返回摘要字段:`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。