topfans/docs/frontend-api-consistency-audit.md
2026-06-09 12:38:12 +08:00

228 lines
9.2 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 接口格式统一性审查报告
> 审查时间:2026-06-09
> 范围:`frontend/` 下所有源码(`unpackage/dist` 等编译产物已排除)
> 核心封装器:`frontend/utils/api.js` 的 `request(options)`
---
## 0. TL;DR
整体架构是统一的:所有 JSON 接口走同一 `request()` 入口,token 注入、401 跳转、响应解析、Mock 短路都集中在 `utils/api.js`
**主要问题不在封装本身,而在一批"绕过封装"的散点**:
- **OSS 上传/下载**没有沉淀到 `api.js`,导致 5 个页面 + 3 个 utils 各自实现一遍
- **20+ 处业务侧**重复判断 `code === 200`(封装器已保证)
- **2 处历史遗留** URL 仍走旧版 `/api/user/*`(无 `/v1`)
- **3 个模块** 自己造了 `request` 风格的 `uni.request` 轮子
优先级 P0:把 OSS 直传抽到 `utils/api.js`,可一次性消掉 30+ 处重复代码。
---
## 1. 已统一的良好部分 ✅
### 1.1 入口封装 — 95% 的 JSON 接口走 `request()`
- 唯一封装器:`frontend/utils/api.js` 的 `request(options)`(L53L134)
- `frontend/utils/api.js` 内 60+ 个 API + `frontend/utils/task-api.js` 的 11 个 API 全部走 `request()`
- 全项目 **46 个文件** 直接 import `@/utils/api``@/utils/task-api`,**无相对路径污染**
### 1.2 Token 注入
- 自动从 `uni.getStorageSync('access_token')` 读取
- Header 格式统一为 `Authorization: Bearer <token>`
- 白名单(不注入 token):`/api/v1/auth/login|register|send-code|verify-code`
### 1.3 业务响应结构
后端约定:`{ code, message, data }`,封装器在 `request()` 内统一处理:
| 输入 | 行为 |
|---|---|
| `HTTP 200/202` + `code === 200` | resolve 整体响应 |
| `HTTP 200/202` + `code === 401/400/403` | 清 token + 跳登录 + reject |
| `HTTP 200/202` + 其他 `code` | reject(`Error(message)`) |
| `HTTP 200/202` + 无 `code` 字段 | resolve(`res.data`) ← 兜底 |
| `HTTP 401` | 清 token + 跳登录 + reject |
| 其他 HTTP 状态码 | reject(`Error(message || '请求失败 (statusCode)')`) |
### 1.4 环境与 Mock 开关
- `VITE_API_BASE_URL` / `VITE_USE_MOCK_API` 集中读取
- `IS_MOCK_API` 暴露给业务做短路
- `getWebSocketBaseUrl` / `getSegmentApiBaseUrl` / `getLaserApiBaseUrl` 三个 baseURL 工厂统一
### 1.5 Dashboard 模块有专门封装
`dashboardApi` 对象 + `dashboardRequest()` 工厂(`utils/api.js` L999L1007),统一了 mock 短路与 baseURL 前缀。
### 1.6 WebSocket 入口统一
`utils/socket/SocketManager.js` 通过 `getWebSocketBaseUrl()` 工厂函数拿到 baseURL(L48L49),token 拼在 query 上(`?token=Bearer_<token>`),鉴权方式与 HTTP 通道保持一致。
---
## 2. 不一致点(按严重度排序)
### 🔴 严重:页面层直连 `uni.request` / `uni.uploadFile` / `fetch`
下列文件**绕过了 `request()` 封装**,需要各自维护 token 注入、超时、错误处理、401 跳转。表中行号定位到调用点。
| 文件 | 行号 | 用途 | 风险点 |
|---|---|---|---|
| `pages/discover/discover.vue` | L101, L134 | OSS PostObject | 重复实现 fetch + uploadFile 分支 |
| `pages/discover/generation-result.vue` | L316, L418, L495, L653 | OSS + 上传 | 4 处独立直传,逻辑高度相似 |
| `pages/castlove/index.vue` | L261, L409 | 图片下载/上传 | 自行处理 token |
| `pages/castlove/create.vue` | L430, L527, L540, L569, L614 | 全链路直传 | 5 处,逻辑重复 |
| `pages/castlove/lenticular/lenticular-create.vue` | L331, L427, L440, L468, L512 | 全链路直传 | 5 处 |
| `pages/castlove/lenticular/lenticular-result.vue` | L217, L319, L396, L554 | 全链路直传 | 4 处 |
| `pages/square/components/WaterfallGrid.vue` | L712, L735, L777, L832 | 业务请求 | 4 处 `res.code === 200` |
| `pages/square/components/HotCategoryBlock.vue` | L193 | 业务请求 | `code === 200` 重复判断 |
| `pages/profile/profile.vue` | L1187 | 头像上传 | 自处理 statusCode |
| `pages/profile/setNickname.vue` | L183 | 头像上传 | 自处理 statusCode,**无 token 注入** |
| `pages/starbook/items.vue` | L136 | 业务请求 | `response.code === 200` |
### 🟡 中等:业务侧重复判断 `code === 200`
`request()` 已经对 `code === 200` resolve 整体,业务侧**不应**再判断。下列位置违反封装契约:
```
utils/progress-manager.js:180
utils/activity-config.js:46, 69, 87, 105, 126
utils/craftMintSubmit.js:481, 510, 627, 690
pages/square/components/WaterfallGrid.vue:712, 735, 777, 832
pages/square/components/HotCategoryBlock.vue:193
pages/starbook/items.vue:136
pages/discover/discover.vue:195, 223
pages/discover/generation-result.vue:284
composables/useLaserMint.js:42
```
### 🟡 中等:部分模块自己造 `request` 风格轮子
| 文件 | 位置 | 说明 |
|---|---|---|
| `composables/useLaserDifyGenerate.js` | L19L47 | 独立 `laserRequest()`,自行注入 token、超时、状态码判断,文件内注释自承 "返回格式与 api.js 的 request() 一致" |
| `utils/laser-card/segmentationCloud.js` | L21L39 | `uniRequest()` 重复实现,仅做状态码判断 |
| `utils/laser-card/aliyunPortraitUni.js` | L273 | `uni.request` 走 OSS PostObject,因 OSS 协议需要单独处理 `x-oss-date` 等特殊头 |
| `utils/craftMintSubmit.js` | L21, L57, L70, L399, L407, L423, L451 | 5 处 `uni.uploadFile`/`fetch` 混用,H5/App 分支自管 |
> 这些位置**有合理理由**绕过 `request()`(OSS 直传需自定义头、需 `multipart/form-data`),但应**抽到 `utils/api.js`** 而不是散布在业务模块。
### 🟡 中等:URL 风格不一致
虽然都带 `/api/v1` 前缀,但**功能模块 URL 风格混乱**:
- 资源 URL:`/api/v1/mygalleries`(无 `/me` 也没有 `/users`)vs `/api/v1/galleries/:uid` vs `/api/v1/galleries/random`(`utils/api.js` L431L452)
- 历史遗留:`updateUserInfoApi` L215 仍走 `/api/user/update`(无 `/v1`),`deleteAccountApi` L276 走 `/api/user/delete-account`
- 单复数、是否用 `/me` 表示当前用户不统一:
- `/api/v1/auth/me`
- `/api/v1/me/nickname`
- `/api/v1/me/avatar`
- `/api/v1/me/liked-assets`
- `/api/v1/me/exhibited-assets`
- `/api/v1/users/:uid/liked-assets`
### 🟢 轻微:拼装 URL 的方式不统一
- 多数用模板字符串拼在 `url` 内(`/api/v1/social/users?page=${page}&page_size=${pageSize}`,`utils/api.js` L226)
- 也有用 `data` 字段传 query 的(GET 请求,如 `task-api.js` L15 `getDailyTasks``star_id``data` 字段,uni.request 在 GET 下会拼到 query)
- 统一规范缺失,新人很容易混用
### 🟢 轻微:`request` 默认 timeout 与 `uni.uploadFile` 超时不一致
- `request()` 默认 60000ms(L78)
- `segmentPortraitApi` 用 120000ms(L749)
- `downloadToLocal` 用 120000ms(`composables/useLaserSegment.js` L29)
- 没有集中常量
---
## 3. 统计摘要
| 指标 | 数值 |
|---|---|
| 直接 import `@/utils/api` 的文件 | 46 |
| 仍走 `uni.request` 直连的文件 | 7 |
| 仍走 `uni.uploadFile` 直连的文件 | 8 |
| 仍走 `fetch()` 直连的文件 | 5 |
| 业务侧 `code === 200` 二次判断处 | 20+ |
| URL 路径里残留 `/api/user/*` 旧接口 | 2 |
| 重复实现的 `request` 风格封装 | 3(`laserRequest`、`uniRequest`、`laserRequest`) |
---
## 4. 修复建议(按 ROI 排序)
### P0 — 把"绕过封装"的 OSS 直传抽到 `api.js`
新增一组 OSS 专用工具到 `utils/api.js`:
```js
// 上传 OSS (H5: fetch + FormData / App: uni.uploadFile)
export function uploadToOss({ host, dir, filePath, policy, signature, ... }) { ... }
// 下载 OSS (H5: fetch + blob URL / App: uni.downloadFile)
export function downloadFromOss(signedUrl) { ... }
```
**收益**:消掉 5 个页面、3 个 utils 中重复的 30+ 处直传代码;统一 token 注入;统一超时;统一错误处理。
### P1 — 修复 `/api/user/*` 旧路径
- `updateUserInfoApi`(L215):`/api/user/update` → 对应的 v1 路径
- `deleteAccountApi`(L276):`/api/user/delete-account` → `/api/v1/me/account` 之类
### P1 — 业务侧禁用 `code === 200` 二次判断
封装器已经处理,业务层应只 try/catch。可在 `request()` 里加注释:
```js
// 业务侧请勿重复判断 code === 200,封装器已保证 resolve 时 code === 200
```
### P2 — URL 风格规范化
建议对齐:
- 当前用户 → `/me`(已用):`/api/v1/me/liked-assets`、`/api/v1/me/exhibited-assets` 等等
- 指定用户 → `/users/:uid`:`/api/v1/users/:uid/liked-assets`
- 收藏/资源 → 单数主语,操作作子路径:`/api/v1/galleries/...` 而不是 `/api/v1/mygalleries`
### P2 — 提取 `api.js` 默认 timeout 常量
```js
const DEFAULT_TIMEOUT = 60000
const UPLOAD_TIMEOUT = 120000
export const API_TIMEOUT = {
default: DEFAULT_TIMEOUT,
upload: UPLOAD_TIMEOUT,
download: UPLOAD_TIMEOUT,
}
```
### P3 — 拼装 query 的工具函数
```js
export function buildQuery(params) {
return '?' + new URLSearchParams(params).toString()
}
```
让所有 `getXxxApi` 走同一方式,而非分散在 `url`/`data` 两处。
---
## 5. 后续 Action Items
| 优先级 | 任务 | 涉及文件数 | 预估工时 |
|---|---|---|---|
| P0 | 把 OSS 上传/下载抽到 `utils/api.js` | 8 | 4h |
| P1 | 修复 `/api/user/*` 旧路径 | 2 | 0.5h |
| P1 | 业务侧清除 20+ 处 `code === 200` 二次判断 | 10+ | 1h |
| P2 | URL 风格规范化 + `/me` 对齐 | 20+ | 2h |
| P2 | 提取 `API_TIMEOUT` 常量 | 3 | 0.5h |
| P3 | 引入 `buildQuery` 工具函数 | 30+ | 1h |