整理内容: - 删除 60+ 临时测试输出文件 (*.txt) - 移动二进制文件到 bin/ 目录 - 移动 Shell 脚本到 scripts/ 目录 - scripts/dev/: check_gitea.sh, check_sub2api.sh, run_tests.sh - scripts/deploy/: deploy_*.sh, simple_deploy.sh - scripts/ops/: fix_nginx.sh, fix_ssl.sh, install_docker.sh - scripts/test/: test_*.sh, test_*.bat - 移动批处理文件到 scripts/ - 移动 Python 脚本到 tools/ - 清理临时日志文件 保留根目录必要文件: - go.mod, go.sum, go.work - Makefile, docker-compose.yml - .env.example, .gitignore - README.md, AGENTS.md, DEPLOY_GUIDE.md 验证: go build ./... && go test ./... 通过
289 lines
14 KiB
Markdown
289 lines
14 KiB
Markdown
# 前后端一致性 + 架构性能专项审查报告
|
||
|
||
**审查日期**: 2026-04-02
|
||
**审查范围**: 前后端一致性 + 架构性能执行
|
||
**审查方法**: 多智能体并行审查(一致性审查、性能审查)
|
||
|
||
---
|
||
|
||
## 一、执行摘要
|
||
|
||
### 综合评分
|
||
|
||
| 维度 | 得分 | 说明 |
|
||
|------|------|------|
|
||
| 前后端一致性 | 2.0/10 | 存在根本性协议层不匹配,72 个端点路由正确但请求/响应格式全面错位 |
|
||
| 架构性能执行 | 5.5/10 | 架构基础合理,但 SQLite、N+1 查询、无界导出等严重制约扩展性 |
|
||
| **综合评分** | **3.8/10** | 这是当前项目最薄弱的两个环节,必须优先修复 |
|
||
|
||
### 问题统计
|
||
|
||
| 严重级别 | 一致性 | 性能 | 总计 |
|
||
|----------|--------|------|------|
|
||
| 🔴 严重 | 18 | 7 | 25 |
|
||
| 🟡 重要 | 14 | 17 | 31 |
|
||
| 💭 轻微 | 8 | 8 | 16 |
|
||
| **总计** | **40** | **32** | **72** |
|
||
|
||
---
|
||
|
||
## 二、前后端一致性审查
|
||
|
||
### 2.1 根本性问题:响应格式协议不匹配
|
||
|
||
**这是整个项目最严重的一致性问题。**
|
||
|
||
- **前端期望**: 所有 API 响应格式为 `{code: number, data: T, message: string}`
|
||
- **后端实际**: 直接返回裸 JSON,如 `{users: [...], total: 100}` 或 `{error: "..."}`
|
||
- **影响**: 前端 `client.ts` 检查 `result.code !== 0` 时,`result.code` 为 `undefined`,导致**每个 API 调用都会抛出错误**。
|
||
- **结论**: 如果这不是在隔离测试环境中运行(测试环境可能走了不同的代码路径),整个应用将无法正常工作。
|
||
|
||
### 2.2 一致性问题分类
|
||
|
||
#### 🔴 严重问题(18 个)
|
||
|
||
| ID | 类别 | 前端 | 后端 | 问题 |
|
||
|----|------|------|------|------|
|
||
| CONSISTENCY-01 | 全局 | `client.ts:240-245` | ALL handlers | 响应格式不匹配:前端期望 `{code, data, message}`,后端返回裸 JSON |
|
||
| CONSISTENCY-02 | 用户列表 | `users.ts:23-24` | `user_handler.go:76-81` | 响应 key: `items` vs `users`,分页: `page/page_size` vs `offset/limit` |
|
||
| CONSISTENCY-03 | 角色列表 | `roles.ts:11-15` | `role_handler.go:52-55` | 响应 key: `items` vs `roles`,缺 `page/page_size` |
|
||
| CONSISTENCY-04 | 角色权限 | `roles.ts:38-40` | `role_handler.go:160` | 前端期望数组,后端返回 `{permissions: [...]}` |
|
||
| CONSISTENCY-05 | 权限列表 | `permissions.ts:22-23` | `permission_handler.go:52-55` | 前端期望数组,后端返回 `{permissions, total}` |
|
||
| CONSISTENCY-06 | 权限树 | `permissions.ts:14-15` | `permission_handler.go:153` | 前端期望数组,后端返回 `{permissions: tree}` |
|
||
| CONSISTENCY-07 | 设备列表 | `devices.ts:10-14` | `device_handler.go:63-68` | 响应 key: `items` vs `devices` |
|
||
| CONSISTENCY-08 | 管理员设备 | `devices.ts:18-22` | `device_handler.go:198-203` | 响应 key: `items` vs `devices` |
|
||
| CONSISTENCY-09 | Webhook 列表 | `webhooks.ts:35-47` | `webhook_handler.go:26` | 响应 key: `data` vs `webhooks` |
|
||
| CONSISTENCY-10 | 登录日志 | `login-logs.ts:12-22` | `log_handler.go:43-48` | 响应 key: `list` vs `logs`,`size` vs `page_size` |
|
||
| CONSISTENCY-11 | 操作日志 | `operation-logs.ts:12-22` | `log_handler.go:52` | 响应 key: `list` vs `logs` |
|
||
| CONSISTENCY-12 | 下线设备 | `devices.ts:58-59` | `device_handler.go:308-314` | 前端发送 body `current_device_id`,后端读 header `X-Device-ID` |
|
||
| CONSISTENCY-13 | 修改密码 | `profile.ts:52-53` | `user_handler.go:160-162` | 前端发送 `current_password`,后端期望 `old_password` |
|
||
| CONSISTENCY-14 | TOTP 状态 | `auth.ts:129-130` | `totp_handler.go:38` | 前端期望 `totp_enabled`,后端返回 `enabled` |
|
||
| CONSISTENCY-15 | Capabilities | `auth.ts:34-36` | `auth_handler.go:136-141` | 字段完全错位:前端期望 `password/email_activation/...`,后端返回 `register/login/...` |
|
||
| CONSISTENCY-16 | Bootstrap | `types/auth.ts:80-84` | `auth_handler.go:243-247` | 前端 `email` 可选,后端必填;前端发 `nickname`,后端不收 |
|
||
| CONSISTENCY-17 | 注册 | `types/auth.ts:71-78` | `auth_handler.go:22-28` | 前端发 `phone_code`,后端不收 |
|
||
| CONSISTENCY-18 | 重置密码 | `types/auth.ts:114-118` | `password_reset_handler.go:65-68` | 前端发 `confirm_password`,后端不收 |
|
||
|
||
#### 🟡 重要问题(14 个)
|
||
|
||
| ID | 类别 | 问题 |
|
||
|----|------|------|
|
||
| CONSISTENCY-19 | 用户状态 | 前端发送数字 `0|1|2|3`,后端期望字符串 `"active"/"inactive"/...` |
|
||
| CONSISTENCY-20 | 角色状态 | 前端发送数字 `0|1`,后端期望字符串 `"enabled"/"disabled"` |
|
||
| CONSISTENCY-21 | 权限状态 | 前端发送数字 `0|1`,后端期望字符串 `"enabled"/"disabled"` |
|
||
| CONSISTENCY-22 | 设备状态 | 前端发送数字 `0|1`,后端期望字符串 `"active"/"inactive"` |
|
||
| CONSISTENCY-23 | 分页参数 | 前端发送 `page/page_size`,后端读取 `offset/limit` |
|
||
| CONSISTENCY-24 | 用户更新 | 前端发 7 个字段,后端只收 2 个(email, nickname) |
|
||
| CONSISTENCY-25 | 登录方式 | 前端只支持 username,后端支持 account/email/phone |
|
||
| CONSISTENCY-26 | CSRF Token | 响应未包装,`result.code` 为 undefined |
|
||
| CONSISTENCY-27 | Token 重试 | 401 重试所有方法(含 POST/PUT/DELETE),可能导致重复操作 |
|
||
| CONSISTENCY-28 | OAuth 授权 | 后端返回格式不匹配 |
|
||
| CONSISTENCY-29 | OAuth 交换 | 后端返回格式不匹配 |
|
||
| CONSISTENCY-30 | 用户角色 | 后端返回空 stub |
|
||
| CONSISTENCY-31 | 分配角色 | 后端返回 stub 但状态码 200 |
|
||
| CONSISTENCY-32 | 统计接口 | 后端返回 stub |
|
||
|
||
#### 💭 轻微问题(8 个)
|
||
|
||
| ID | 类别 | 问题 |
|
||
|----|------|------|
|
||
| CONSISTENCY-33 | OAuth | 前端发送 `return_to` 参数,后端不读取 |
|
||
| CONSISTENCY-34 | 短信验证码 | 前端期望 void,后端返回对象 |
|
||
| CONSISTENCY-35 | 头像上传 | 前端期望对象,后端返回 stub |
|
||
| CONSISTENCY-36 | 导出字段 | 前端发送逗号分隔字符串 |
|
||
| CONSISTENCY-37 | 日志导出格式 | 格式参数传递方式需确认 |
|
||
| CONSISTENCY-38 | TOTP 验证 | 前端期望 void,后端返回 `{verified: true}` |
|
||
| CONSISTENCY-39 | 社交账号 | 前端期望数组,后端返回包装对象 |
|
||
| CONSISTENCY-40 | 设备指纹 | 前端无持久化设备标识 |
|
||
|
||
### 2.3 正确对齐的 API(72 个端点)
|
||
|
||
✅ 所有 72 个端点的 URL 路径和 HTTP 方法都正确匹配。问题完全在于请求/响应载荷格式,不在于路由。
|
||
|
||
### 2.4 修复建议(按优先级)
|
||
|
||
#### P0:修复响应协议(阻塞所有功能)
|
||
|
||
**方案 A(推荐):添加 Gin 响应包装中间件**
|
||
```go
|
||
// 拦截所有 c.JSON() 调用,自动包装为 {code: 0, data: <original>, message: ""}
|
||
func ResponseWrapper() gin.HandlerFunc {
|
||
return func(c *gin.Context) {
|
||
// 包装成功响应
|
||
// 错误响应包装为 {code: <http_status>, data: null, message: <error>}
|
||
}
|
||
}
|
||
```
|
||
|
||
**方案 B:重写前端 client.ts**
|
||
移除 `result.code !== 0` 检查,直接返回 `response.json()`。
|
||
|
||
#### P1:标准化响应 Key
|
||
|
||
- 所有列表端点统一返回 `{items, total, page, page_size}`
|
||
- 所有直接数组端点(权限树、角色权限)直接返回数组
|
||
|
||
#### P2:修复关键字段错位
|
||
|
||
| 端点 | 修复 |
|
||
|------|------|
|
||
| `POST /devices/me/logout-others` | 前端改为发送 `X-Device-ID` header |
|
||
| `PUT /users/:id/password` | 前端改为发送 `old_password` |
|
||
| `GET /auth/2fa/status` | 后端改为返回 `totp_enabled` |
|
||
| `GET /auth/capabilities` | 后端重写响应字段名 |
|
||
| `GET /auth/csrf-token` | 包装响应或前端直接读取 |
|
||
|
||
#### P3:标准化状态类型
|
||
|
||
- 所有状态端点统一接受数字值(0, 1, 2, 3)
|
||
- 后端 switch 语句改为处理 `int` 而非 `string`
|
||
|
||
#### P4:修复分页
|
||
|
||
- `GET /users`: 接受 `page/page_size` 参数,内部转换为 `offset/limit`
|
||
- 所有分页端点统一返回 `page` 和 `page_size`
|
||
|
||
---
|
||
|
||
## 三、架构性能执行审查
|
||
|
||
### 3.1 性能问题分类
|
||
|
||
#### 🔴 严重问题(7 个)
|
||
|
||
| ID | 类别 | 文件 | 问题 | 影响 |
|
||
|----|------|------|------|------|
|
||
| PERF-01 | 数据库 | `middleware/auth.go:131-197` | 认证中间件 N+1 查询:每个请求 7-8 次 DB 查询 | 1000 并发用户 = 7000-8000 DB 查询/秒 |
|
||
| PERF-02 | 数据库 | `middleware/auth.go:210-221` | isUserActive 每次请求都执行 SELECT * | 缓存命中也无法避免 |
|
||
| PERF-03 | 数据库 | `login_log.go:118-139` | 导出/无分页查询加载全表到内存 | 百万级日志表 OOM |
|
||
| PERF-14 | 并发 | `auth.go:482-487` | 无界 goroutine + context.Background() | DB 降级时 goroutine 泄漏 → 连接池耗尽 |
|
||
| PERF-28 | 架构 | `V1__init.sql` | SQLite 作为生产数据库 | 写入串行化,吞吐量上限 50-100 writes/sec |
|
||
| PERF-29 | 架构 | `middleware/auth.go:38-47` | L1 缓存每进程独立,无法水平扩展 | 多实例部署权限变更 30 分钟传播延迟 |
|
||
| C03 | 前端 | `client.ts:210-221` | Token 刷新重试非幂等请求 | 可能导致重复创建用户等操作 |
|
||
|
||
#### 🟡 重要问题(17 个)
|
||
|
||
| ID | 类别 | 问题 |
|
||
|----|------|------|
|
||
| PERF-04 | 数据库 | List() 总是 COUNT + SELECT(2 次查询),即使只需要 count |
|
||
| PERF-05 | 数据库 | Dashboard stats 8+ 次顺序查询 |
|
||
| PERF-06 | 数据库 | GetAncestorIDs 顺序单行查询(最多 5 层) |
|
||
| PERF-07 | 数据库 | BatchSet 事务内 N 次顺序查询 |
|
||
| PERF-08 | 数据库 | LIKE '%keyword%' 4 列无全文索引 |
|
||
| PERF-09 | 数据库 | GetActiveDevices/GetTrustedDevices 无分页限制 |
|
||
| PERF-11 | 内存 | L1Cache updateAccessOrder 使用 O(n) 切片操作 |
|
||
| PERF-12 | 内存 | BatchDelete 未预分配切片容量 |
|
||
| PERF-15 | 并发 | L1Cache Get 使用写锁(Lock)而非读锁(RLock) |
|
||
| PERF-17 | HTTP | 无响应压缩中间件 |
|
||
| PERF-18 | HTTP | 大多数路由无请求体大小限制 |
|
||
| PERF-19 | HTTP | 操作日志中间件为每个写请求分配 4KB 缓冲 |
|
||
| PERF-21 | Bundle | 无代码分割配置(antd + react 打包在一起) |
|
||
| PERF-22 | Runtime | ProfileSecurityPage 946 行 mega-component |
|
||
| PERF-23 | Runtime | WebhooksPage 客户端过滤 + 分页 |
|
||
| PERF-26 | Network | ProfileSecurityPage 挂载时 6 个并行 API 调用,无请求去重 |
|
||
| PERF-30 | 架构 | 无会话管理扩展性(多实例无法强制登出) |
|
||
|
||
#### 💭 轻微问题(8 个)
|
||
|
||
| ID | 类别 | 问题 |
|
||
|----|------|------|
|
||
| PERF-10 | 数据库 | UpdateLastLogin 使用 map[string]interface{} |
|
||
| PERF-13 | 内存 | generateUniqueUsername 最多 1001 次顺序 DB 查询 |
|
||
| PERF-16 | 并发 | 祖先 ID 收集未并行化 |
|
||
| PERF-20 | HTTP | 全局 30s 超时对所有请求统一应用 |
|
||
| PERF-24 | Runtime | UsersPage columns 未 useMemo |
|
||
| PERF-25 | Runtime | PermissionsPage buildTreeData 每次渲染递归 |
|
||
| PERF-27 | Network | 401 重试未检查 body 是否可流式传输 |
|
||
| PERF-31 | 架构 | Webhook 事件通过无重试 goroutine 发布 |
|
||
|
||
### 3.2 前 5 大性能瓶颈
|
||
|
||
| 排名 | 问题 | 影响 |
|
||
|------|------|------|
|
||
| 1 | **SQLite 作为生产数据库** | 写入串行化,登录风暴时级联超时 |
|
||
| 2 | **认证中间件 N+1 查询** | 每个请求 7-8 次 DB 查询,冷启动时查询风暴 |
|
||
| 3 | **无界导出查询** | 导出端点加载全表到内存,百万级数据 OOM |
|
||
| 4 | **Dashboard stats 顺序查询** | 8 次顺序查询,冷加载 200-500ms |
|
||
| 5 | **泄漏的无界 goroutine** | DB 降级时 goroutine 堆积 → 连接池耗尽 → 全面宕机 |
|
||
|
||
### 3.3 架构扩展性评估
|
||
|
||
| 用户规模 | 状态 | 说明 |
|
||
|----------|------|------|
|
||
| 100 用户 | ✅ 就绪 | SQLite 可处理轻量并发 |
|
||
| 1,000 用户 | ⚠️ 有风险 | 登录突发(>50/sec)会导致 SQLite 写入争用 |
|
||
| 10,000 用户 | ❌ 不可用 | SQLite 写入串行化成为硬瓶颈,认证中间件查询量不可持续 |
|
||
|
||
**10,000 用户前必须完成的变更**:
|
||
1. 迁移到 PostgreSQL
|
||
2. 合并认证中间件查询为 1-2 次缓存查找
|
||
3. 添加 Redis 作为共享缓存层
|
||
4. 流式导出替代内存加载
|
||
5. 添加自动化日志清理 cron
|
||
|
||
---
|
||
|
||
## 四、综合建议
|
||
|
||
### P0:立即修复(阻塞生产部署)
|
||
|
||
1. **修复响应格式协议不匹配**(CONSISTENCY-01)
|
||
- 添加 Gin 响应包装中间件
|
||
- 或重写前端 client.ts 接受裸响应
|
||
|
||
2. **修复关键字段错位**(CONSISTENCY-12, 13, 14, 15)
|
||
- 设备下线:body → header
|
||
- 修改密码:current_password → old_password
|
||
- TOTP 状态:enabled → totp_enabled
|
||
- Capabilities:重写后端响应字段
|
||
|
||
3. **修复认证中间件 N+1 查询**(PERF-01, 02)
|
||
- 合并为单次 JOIN 查询
|
||
- 将 user.Status 纳入缓存条目
|
||
|
||
4. **修复导出无界查询**(PERF-03)
|
||
- 添加 LIMIT(如 100K 上限)
|
||
- 或实现游标分页流式导出
|
||
|
||
### P1:当前迭代解决
|
||
|
||
5. **标准化响应 Key**(CONSISTENCY-02 到 11)
|
||
- 所有列表端点统一 `{items, total, page, page_size}`
|
||
|
||
6. **标准化状态类型**(CONSISTENCY-19 到 22)
|
||
- 后端改为接受数字值
|
||
|
||
7. **修复分页参数**(CONSISTENCY-23)
|
||
- 后端接受 `page/page_size`,内部转换
|
||
|
||
8. **修复 Dashboard stats 查询**(PERF-05)
|
||
- 合并为单次 GROUP BY 查询
|
||
|
||
9. **修复 L1Cache 并发**(PERF-15)
|
||
- Get 使用 RLock
|
||
|
||
10. **修复 goroutine 泄漏**(PERF-14)
|
||
- 添加 context.WithTimeout
|
||
|
||
### P2:下一轮优化
|
||
|
||
11. **迁移到 PostgreSQL**(PERF-28)
|
||
12. **添加 Redis 共享缓存**(PERF-29, 30)
|
||
13. **前端代码分割**(PERF-21)
|
||
14. **ProfileSecurityPage 拆分**(PERF-22)
|
||
15. **WebhooksPage 服务端过滤**(PERF-23)
|
||
16. **添加响应压缩**(PERF-17)
|
||
17. **实现 Stub 端点**(CONSISTENCY-30, 31, 32)
|
||
|
||
---
|
||
|
||
## 五、审查方法说明
|
||
|
||
本次审查采用多智能体并行模式:
|
||
- **一致性审查智能体**: 交叉比对每个前端服务调用与后端 handler,检查 URL、方法、请求体、响应格式、错误处理、数据模型
|
||
- **性能审查智能体**: 审查数据库查询、内存使用、并发模式、HTTP 配置、前端 bundle、运行时渲染、网络请求、架构扩展性
|
||
|
||
审查覆盖:
|
||
- 前端: 13 个服务文件 + HTTP 客户端 + 类型定义
|
||
- 后端: 所有 handler + repository + service + middleware + cache + 配置
|
||
- 架构: 数据库选择、缓存策略、水平扩展能力
|