204 lines
8.6 KiB
Markdown
204 lines
8.6 KiB
Markdown
# 文件上传服务接口文档
|
||
|
||
> 本文件描述文件上传服务的全部接口,包含**后台管理(`/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。
|