# 2026-07-05 项目全面 Review 报告(真实复核版) > 生成时间:2026-07-05T20:59:15+08:00 > 审查对象:`/home/long/project/gaokao-volunteer-system` > 当前 HEAD:`4059f144c49398aec2fcc3040e639e1312061086` > 审查方式:真实读取代码/CI/脚本/现有报告 + 本地门禁执行 + 静态扫描;未把历史报告当作当前事实。 > 重要边界:本报告是代码库与本地门禁 review,不等同于线上真实支付、真实域名、真实用户流量验收。 > 复验更新:已在报告初稿后补装前端依赖并复跑 `pnpm typecheck/lint/test/build`,结果已回写到 §1.2 / M0。 --- ## 0. 总结结论 **结论:REQUEST_CHANGES / 不能宣称生产级完成。** 本轮 review 发现的当前有效问题(含异步三线补充后): | 严重级别 | 数量 | 摘要 | |---|---:|---| | HIGH | 11 | Admin 导航断链、React Admin mock 登录未接真实 JWT、前端 API client 未注入 Authorization、Python mypy 失败、JWT query token、payment_id 换 Portal token、公共错误泄漏、公共下单缺限流、SQLite migration 无版本、Poster Docker 构建失败、compose healthcheck 端口漂移 | | MEDIUM | 8 | Chromatic token/LHCI/100-case smoke gate 语义、前端依赖准备、Portal token 吊销、附件 magic bytes、Alipay notify body limit、订单删除审计、Node/pnpm/turbo 环境口径 | | LOW | 2 | 文档中仍大量保留 mock/sandbox 历史语境,旧截图报告目录仍在仓库中易误导;部分静态扫描命中需后续清理白名单 | 已确认的正向事实: - `main / origin / gitea / tksea` 在上一轮已同步到 `4059f14`,本轮 review 起点工作区为 `## main`。 - `apps/web/src/types/api-generated.d.ts` 与 `apps/web/src/schemas/api-generated.ts` 当前 `any_count=0`。 - `/health` dev readiness 改动已进入 HEAD,`settings_valid` 在 dev 环境允许占位密钥,但 prod fail-closed 仍由配置加载策略覆盖。 --- ## 1. Gate 结果(当前真实输出) ### 1.1 Python / 后端总门禁 命令来源:`/tmp/gaokao-review-gates-20260705.log`,由本轮执行: ```bash bash scripts/dev-verify.sh ``` 结果:**FAIL**。关键输出: ```text admin/routes/sprint3_api.py:292: error: Incompatible types in assignment (expression has type "str", variable has type "Literal['pending', 'in_progress', 'approved', 'rejected', 'changes_requested']") [assignment] data/cli_compat_share.py:43: error: Item "None" of "_ArgumentGroup | None" has no attribute "_group_actions" [union-attr] data/cli_compat_share.py:44: error: Item "Action" of "Action | Any" has no attribute "add_parser" [union-attr] data/share/poster.py:123: error: Argument "candidate_name" to "PosterPayload" has incompatible type "str | None"; expected "str" [arg-type] data/share/poster.py:273: error: Incompatible return value type (got "FreeTypeFont", expected "ImageFont") [return-value] data/share/poster.py:274: error: Incompatible return value type (got "FreeTypeFont | ImageFont", expected "ImageFont") [return-value] data/share/poster.py:319: error: Incompatible return value type (got "float", expected "int") [return-value] data/share/poster.py:324: error: Incompatible return value type (got "float", expected "int") [return-value] data/share/poster.py:329: error: Library stubs not installed for "qrcode" [import-untyped] Found 9 errors in 3 files (checked 268 source files) DEV_VERIFY_EXIT=1 ``` 影响: - 这不是单测功能失败,而是类型门禁失败;按项目质量标准不能用“pytest 局部通过”覆盖。 - 失败集中在 poster / report 相关类型与缺失 third-party stubs,说明分享海报链路仍存在类型质量债。 ### 1.2 前端本地门禁 第一轮执行结果:**BLOCKED**,原因是本地缺少 `node_modules` / `turbo`,未进入真实业务门禁。随后已补执行: ```bash pnpm install --frozen-lockfile pnpm typecheck pnpm lint pnpm test pnpm build ``` 复验结果:**PASS**。fresh evidence 来自 `/tmp/gaokao-frontend-gates-20260705.log`: ```text === FRONTEND FRESH GATES START === --- pnpm install --- root node_modules exists apps/web/node_modules exists --- web typecheck --- --- web lint --- --- web test --- --- web build --- Tasks: 2 successful, 2 total Tasks: 1 successful, 1 total @gaokao/web:build: TOTAL │ 1.36 MB │ 393.60 KB │ ✅ @gaokao/web:build: ✅ T-B-25 验证通过 === FRONTEND FRESH GATES END === ``` 更新判断:前端本地 typecheck/lint/test/build 当前已有 fresh gate 证据;原 H5 从 HIGH blocker 降级为 MEDIUM 环境/可复现性问题。仍需注意:这不自动证明 Playwright e2e、Chromatic、LHCI 或真实浏览器视觉验收已全部闭环。 --- ## 2. HIGH Findings ### H1 · 后台导航 `/admin/review` 真实断链 **证据:** `apps/web/src/layouts/AdminLayout.tsx` 注册了 `/admin/review`: - L12: `const adminNavItems = [` - L13: ` { to: '/admin', labelKey: 'admin.nav.dashboard', icon: Home },` - L14: ` { to: '/admin/orders', labelKey: 'admin.nav.orders', icon: FileText },` - L15: ` { to: '/admin/cases', labelKey: 'admin.nav.cases', icon: Users },` - L16: ` { to: '/admin/share-links', labelKey: 'admin.nav.shareLinks', icon: Link2 },` - L17: ` { to: '/admin/score-lines', labelKey: 'admin.nav.scoreLines', icon: Database },` - L18: ` { to: '/admin/rank-estimator', labelKey: 'admin.nav.rankEstimator', icon: TrendingUp },` - L19: ` { to: '/admin/majors', labelKey: 'admin.nav.majors', icon: BarChart3 },` - L20: ` { to: '/admin/schools', labelKey: 'admin.nav.schools', icon: School },` - L21: ` { to: '/admin/review', labelKey: 'admin.nav.review', icon: ShieldCheck },` - L22: ` { to: '/admin/posters', labelKey: 'admin.nav.posters', icon: BarChart3 },` - L23: `] as const;` `apps/web/src/router.tsx` 的 `/admin` 子路由没有 `review`: - L64: ` { path: '/admin/login', element: },` - L65: ` { path: '/403', element: },` - L66: ` {` - L67: ` path: '/admin',` - L68: ` element: (` - L69: ` ` - L70: ` ` - L71: ` ` - L72: ` ),` - L73: ` children: [` - L74: ` { index: true, element: },` - L75: ` { path: 'orders', element: },` - L76: ` { path: 'orders/:orderId', element: },` - L77: ` { path: 'cases', element: },` - L78: ` { path: 'cases/:caseId', element: },` - L79: ` { path: 'share-links', element: },` - L80: ` { path: 'share-links/:code', element: },` - L81: ` { path: 'posters', element: },` - L82: ` { path: 'score-lines', element: },` - L83: ` { path: 'rank-estimator', element: },` - L84: ` { path: 'majors', element: },` - L85: ` { path: 'schools', element: },` - L86: ` { path: 'error', element: },` - L87: ` { path: '*', element: },` - L88: ` ],` 静态路由对照结果: ```text nav=['/admin', '/admin/orders', '/admin/cases', '/admin/share-links', '/admin/score-lines', '/admin/rank-estimator', '/admin/majors', '/admin/schools', '/admin/review', '/admin/posters'] admin_routes=['/admin/*', '/admin/cases', '/admin/cases/:caseId', '/admin/error', '/admin/majors', '/admin/orders', '/admin/orders/:orderId', '/admin/posters', '/admin/rank-estimator', '/admin/schools', '/admin/score-lines', '/admin/share-links', '/admin/share-links/:code'] missing=['/admin/review'] ``` **影响:** 后台用户点击“复核 / Review”会进入 NotFound,属于真实用户路径断裂。 **建议修复:** 新增 `/admin/review` 子路由并接入已有 Review 页面或移除导航项;新增 e2e 遍历所有 admin nav links,断言不落入 NotFound。 --- ### H2 · React Admin 登录仍是前端 mock,未接 `/api/auth/login` **证据:** `apps/web/src/pages/admin/LoginPage.tsx` 只校验固定验证码 `123456`,直接写 user store: - L53: ` const onSubmit = async (values: LoginFormValues): Promise => {` - L54: ` setSubmitError(null);` - L55: ` await new Promise((resolve) => window.setTimeout(resolve, 80));` - L56: `` - L57: ` if (values.code !== '123456') {` - L58: ` setSubmitError(intl.formatMessage({ id: 'admin.login.mockCodeError' }));` - L59: ` return;` - L60: ` }` - L61: `` - L62: ` setUser({` - L63: ` id: `admin-${values.phone.slice(-4)}`,` - L64: ` name: intl.formatMessage({ id: 'admin.login.mockAdminName' }, { suffix: values.phone.slice(-4) }),` - L65: ` phone: values.phone,` - L66: ` role: 'admin',` - L67: ` });` - L68: ` toast.success(intl.formatMessage({ id: 'admin.login.toastSuccess' }), {` - L69: ` description: intl.formatMessage({ id: 'admin.login.toastSuccessDescription' }),` - L70: ` });` - L71: ` void navigate(from, { replace: true });` - L72: ` };` - L102: `

` - L103: ` ` - L104: `

` - L105: `

` - L106: ` ` - L107: `

` - L132: `
` - L133: `