docs(review): 平台全量代码 review 报告(认证团队/AI链路/资产计费/Admin/前端/工程安全)+ 清理调试脚本

6 维度并行审查汇总:总评 + P0(上线前必修:密钥[有意保留]/内容审核/审计可查/二次确认/安全基线/数据一致性)
+ P1(Admin 补全:仪表盘/登录日志/导出/批量/审计筛选;团队找回密码/owner转移/多团队/权限细粒度)
+ P2 + 亮点。报告:docs/todo/平台代码review-2026-06-19.md

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
seaislee1209
2026-06-19 23:57:33 +08:00
co-authored by Claude Opus 4.8
parent e702cb7433
commit ad96acfacd
3 changed files with 65 additions and 40 deletions
-21
View File
@@ -1,21 +0,0 @@
import { chromium } from "playwright";
const TOK = process.argv[2];
const b = await chromium.launch();
const ctx = await b.newContext({ viewport: { width: 1440, height: 900 } });
const p = await ctx.newPage();
const errs = [];
p.on("console", m => { if (m.type()==="error") errs.push(m.text().slice(0,120)); });
p.on("pageerror", e => errs.push("PAGEERR: "+e.message.slice(0,150)));
await p.goto("http://127.0.0.1:5173/", { waitUntil: "domcontentloaded" });
await p.evaluate((t) => localStorage.setItem("airshelf_token", t), TOK);
for (const url of ["http://127.0.0.1:5173/model-photo", "http://127.0.0.1:5173/model-photo/demo-a"]) {
errs.length = 0;
await p.goto(url, { waitUntil: "networkidle" });
await p.waitForTimeout(700);
const info = await p.evaluate(() => {
const txt = document.body.innerText || "";
return { isLogin: txt.includes("AUTH/LOGIN") || txt.includes("使用团队邀请邮箱"), firstWords: txt.replace(/\s+/g," ").slice(0,80) };
});
console.log(url.split("5173")[1], JSON.stringify(info), "ERR:", errs.slice(0,3));
}
await b.close();
-19
View File
@@ -1,19 +0,0 @@
import { chromium } from "playwright";
const TOK = process.argv[2];
const b = await chromium.launch();
const ctx = await b.newContext({ viewport:{width:1440,height:900} });
const p = await ctx.newPage();
await p.goto("http://127.0.0.1:5173/", { waitUntil:"domcontentloaded" });
await p.evaluate((t)=>localStorage.setItem("airshelf_token",t), TOK);
// mirror model-photo
await p.goto("http://127.0.0.1:5173/exact/model-photo.html", { waitUntil:"networkidle" });
await p.waitForTimeout(1800);
const mirror = await p.evaluate(()=>{
const items=[...document.querySelectorAll(".mp-ps-item, .ps-item, [class*='ps-item'], .pl-item")].map(e=>e.innerText.replace(/\s+/g,' ').slice(0,30));
return { count: items.length, items: items.slice(0,10) };
});
console.log("MIRROR model-photo product list:", JSON.stringify(mirror));
// real API
const api = await p.evaluate(async (t)=>{ const r=await fetch("/api/products/",{headers:{Authorization:"Token "+t}}); const d=await r.json(); return (d.results||[]).map(x=>x.title); }, TOK);
console.log("REAL /api/products/:", JSON.stringify(api));
await b.close();
@@ -0,0 +1,65 @@
# AirShelf 平台全量代码 Review · 2026-06-19
> 6 个只读代理并行扫了:认证团队 / AI 生成链路 / 资产商品计费 / Admin 后台 / 前端架构 / 工程安全。
> 角度:一个正经的「电商 AI 短视频 + 图片资产生成 + 多团队 + Admin」平台。
## 总评(各维度)
| 维度 | 分 | 一句话 |
|---|---|---|
| 多团队/权限框架 | 7/10 | 框架完整,缺角色细粒度后端校验 + owner 转移/退队/多团队切换 |
| AI 生成链路 | 7/10 | 架构好(可插拔 provider + 额度三态幂等),缺监管层(限流/故障转移/内容审核) |
| 资产/商品/计费数据 | 6/10 | 模型健全,P0 集中在数据一致性(软删除/无图商品/流水对账/预留过期) |
| **Admin 后台** | 7/10 | 核心 CRUD 到位,缺「可视化 + 可控 + 可溯源」三要素 |
| 前端架构 | 黄票 | 逻辑自洽、设计统一,但手写路由 + 巨型 App.tsx 已成扩展瓶颈;0 前端测试 |
| 工程/安全/运维 | 4/10 | **不宜直接上生产**:密钥进库、Token 无期限、无限流、无 CI、无监控 |
---
## P0 — 上生产前必须处理
1. **🔴 密钥泄露在 git 版本库**:`core/backend/.env`(火山 ARK / Tokenssr / MySQL 密码 / TOS 密钥)被 `.gitignore:8``!core/backend/.env` 放行、已 commit。**真修 = 轮换所有密钥 + 从仓库(含历史)移除 + 改用 K8s Secret/Vault**。git 历史里已有,光删 HEAD 不够。
2. **🔴 生成内容审核缺失**:无 NSFW/违规/商标识别就出图出视频上架 — 电商平台的法务红线(services.py 出口 `_store_generated_media` 处加)。
3. **🔴 Admin 审计只写不可查**:`AdminAuditLog` 全程在记,但**没有查看页/端点** — 既是合规硬伤,也是后台本职缺失。
4. **🔴 Admin 危险操作无二次确认**:停用团队 / 删模型 / 改密一点就执行,误点不可回滚。
5. **🔴 安全基线**:Token 无过期(改密后旧 token 仍可用窗口)、login/register 无限流(可暴力破解)、生产无 HTTPS 强制 + HSTS。
6. **🔴 数据一致性**:① 资产软删除多处查询漏 `is_deleted=False` 过滤 → 已删资产会冒出来;② 商品编辑可删光所有图 → 下游图生成落空;③ 流水 `balance_after` 不单调(reserve 记的是未扣前余额)+ 预留过期(`CreditReservation.expires_at`)无清理 → 坏账占额度。
## P1 — 该补才像成熟平台(Admin 为重点)
**Admin 后台缺口**
- **概览仪表盘是假的**:现在只是快捷导航,没有真实 KPI/趋势(活跃团队、今日生成数、消耗额度、成功率、成本异常)。
- **登录/安全日志页**:`LoginSession` 有数据但后台看不了;无异常登录告警。
- **治理体检不能下钻**:只给「17 个无图商品」计数,点不进去看是哪 17 个、不能批量处理。
- **数据导出/报表**:所有列表都没有 CSV/Excel 导出(财务/运营对账要)。
- **批量操作**:只有资产审核能批量,用户/团队/邀请码不能批量启停/撤销。
- **审计高级筛选**:审计查看页(见 P0-3)要支持按 操作人/动作/时间范围 筛。
**团队体系**
- 找回密码(现在只能「联系超管」)、owner 权力转移(否则 owner 丢号团队锁死)、成员主动退队、多团队切换(现在 `get_current_team` 只取第一个)、**角色权限细粒度在后端真正生效**(目前 viewer/member 权限只在前端摆样子,API 层没校验,可越权)、用户停用即时吊销 token。
**生成链路**
- 成本硬上限(QuotaPolicy 已接进 reserve,但手动调额/批量入口没卡)、provider 故障转移(单链路,中转站挂了整条死)、provider 维度限流。
**工程/前端**
- CI/CD 流水线(现在全手工)、Sentry 错误监控、`/api/health/` 要真查 DB/Redis/Celery、Celery 加 `TASK_TIME_LIMIT`;前端加 ErrorBoundary + 统一加载/错误/空态 + 补前端测试(现在 0 条)。
## P2 — 以后再说
- Admin 角色分级(只读运营/客服,现在只有「全有或全无」的超管)、平台系统设置页、全局搜索、群发通知、API 令牌管理。
- 前端:换路由库(TanStack/React-Router)、上状态库(Zustand)、代码分割(构建已 >500KB 警告)、i18n、移动端、沉淀 `<AdminTable>/<AdminForm>` 组件库。
- 业务:商品分类树、项目模板/复制、退款流程、发票/对账报表、孤儿数据清理、API 文档(drf-spectacular)、密钥轮换 + 自动备份。
---
## 值得肯定的(别动,继续保持)
- 可插拔 Provider(换中转站零改代码)、额度 reserve/charge/release 三态 + 行锁幂等、邀请码幂等、平台超管/团队 owner 两层权限隔离、审计 savepoint 隔离(不污染主事务)、Admin 列表已规避 N+1、设计系统(restraint token)全站一致、慢操作全异步化。
## 关于「进场前既有的 6 FAIL+1 ERROR」
都是 `image_edit / base-asset` 的**测试覆盖缺口**(只 mock 了 provider 对象,没真测 image_edit 的参数构造/multipart/返回解析),不是链路 bug,但中转站接口一微调就会掉线 → 建议补端到端 mock 测试(P1)。
---
## 建议的下一步
按「P0 安全 → P0 数据一致性 → P1 Admin 补全」的顺序迭代。其中:
- **P0-1 密钥**:得你来轮换(外部凭证),我可以做 git 侧清理 + .gitignore 修正。
- **P0-3/4 + P1 Admin**(审计查看页 / 二次确认 / 登录日志 / 仪表盘 / 导出 / 批量):纯代码活,可整批用 loop-task 干。
- 其余按优先级排期。