287 lines
14 KiB
HTML
287 lines
14 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Code Review Report</title>
|
||
<style>
|
||
:root {
|
||
--bg: #1a1b1e;
|
||
--surface: #25262b;
|
||
--border: #373a40;
|
||
--text: #c1c2c5;
|
||
--text-muted: #909296;
|
||
--accent: #5c7cfa;
|
||
--success: #51cf66;
|
||
--warning: #fcc419;
|
||
--danger: #ff6b6b;
|
||
--info: #339af0;
|
||
}
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||
background: var(--bg);
|
||
color: var(--text);
|
||
padding: 32px;
|
||
line-height: 1.6;
|
||
}
|
||
.container { max-width: 960px; margin: 0 auto; }
|
||
.header {
|
||
background: var(--surface);
|
||
border: 1px solid var(--border);
|
||
border-radius: 12px;
|
||
padding: 24px 32px;
|
||
margin-bottom: 24px;
|
||
}
|
||
.header h1 {
|
||
font-size: 22px;
|
||
color: #fff;
|
||
margin-bottom: 16px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
.meta-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||
gap: 8px;
|
||
font-size: 14px;
|
||
}
|
||
.meta-item { display: flex; gap: 8px; }
|
||
.meta-label { color: var(--text-muted); min-width: 80px; }
|
||
.meta-value { color: #fff; }
|
||
.verdict { font-weight: 700; font-size: 18px; }
|
||
.verdict.pass { color: var(--success); }
|
||
.verdict.fail { color: var(--danger); }
|
||
.section {
|
||
background: var(--surface);
|
||
border: 1px solid var(--border);
|
||
border-radius: 12px;
|
||
padding: 24px 32px;
|
||
margin-bottom: 20px;
|
||
}
|
||
.section h2 {
|
||
font-size: 16px;
|
||
color: #fff;
|
||
margin-bottom: 16px;
|
||
padding-bottom: 8px;
|
||
border-bottom: 1px solid var(--border);
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.5px;
|
||
}
|
||
.layer-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 8px; }
|
||
.layer-item { display: flex; justify-content: space-between; padding: 6px 0; font-size: 14px; }
|
||
.layer-item .status { font-weight: 600; }
|
||
.status-clean { color: var(--success); }
|
||
.status-issues { color: var(--warning); }
|
||
.status-na { color: var(--text-muted); }
|
||
table { width: 100%; border-collapse: collapse; font-size: 14px; }
|
||
th, td { text-align: left; padding: 8px 12px; border-bottom: 1px solid var(--border); }
|
||
th { color: var(--text-muted); font-weight: 500; text-transform: uppercase; font-size: 12px; letter-spacing: 0.5px; }
|
||
td { color: var(--text); }
|
||
.metric-warn { color: var(--warning); font-weight: 600; }
|
||
.metric-good { color: var(--success); }
|
||
.summary-row { display: flex; gap: 16px; flex-wrap: wrap; }
|
||
.summary-card {
|
||
flex: 1; min-width: 140px; padding: 16px; border-radius: 8px; text-align: center;
|
||
border: 1px solid var(--border);
|
||
}
|
||
.summary-card .num { font-size: 28px; font-weight: 700; }
|
||
.summary-card .label { font-size: 12px; color: var(--text-muted); margin-top: 4px; }
|
||
.card-ready .num { color: var(--success); }
|
||
.card-needsfix .num { color: var(--warning); }
|
||
.card-unusable .num { color: var(--danger); }
|
||
.severity-row { display: flex; gap: 16px; flex-wrap: wrap; margin-bottom: 16px; }
|
||
.severity-badge {
|
||
display: inline-flex; align-items: center; gap: 6px;
|
||
padding: 4px 12px; border-radius: 20px; font-size: 13px; font-weight: 600;
|
||
}
|
||
.sev-blocker { background: rgba(255,107,107,0.15); color: var(--danger); }
|
||
.sev-major { background: rgba(252,196,25,0.15); color: var(--warning); }
|
||
.sev-minor { background: rgba(52,154,240,0.15); color: var(--info); }
|
||
.issue {
|
||
padding: 12px 16px; margin-bottom: 8px; border-radius: 8px;
|
||
border-left: 4px solid; font-size: 14px;
|
||
}
|
||
.issue-blocker { background: rgba(255,107,107,0.08); border-left-color: var(--danger); }
|
||
.issue-major { background: rgba(252,196,25,0.08); border-left-color: var(--warning); }
|
||
.issue-minor { background: rgba(52,154,240,0.08); border-left-color: var(--info); }
|
||
.issue .tag {
|
||
display: inline-block; padding: 1px 8px; border-radius: 4px;
|
||
font-size: 11px; font-weight: 600; margin-right: 8px;
|
||
}
|
||
.issue .tag-blocker { background: rgba(255,107,107,0.2); color: var(--danger); }
|
||
.issue .tag-major { background: rgba(252,196,25,0.2); color: var(--warning); }
|
||
.issue .tag-minor { background: rgba(52,154,240,0.2); color: var(--info); }
|
||
.issue .category { color: var(--accent); font-weight: 500; }
|
||
.issue .location { color: var(--text-muted); font-size: 12px; }
|
||
.issue .fix { margin-top: 4px; padding: 6px 10px; background: rgba(92,124,250,0.1); border-radius: 4px; font-size: 13px; color: #dde; }
|
||
.manual-item { padding: 8px 0; font-size: 14px; }
|
||
.manual-item::before { content: '→ '; color: var(--warning); }
|
||
.accum-item { padding: 6px 0; font-size: 14px; }
|
||
.accum-item::before { content: '• '; color: var(--accent); }
|
||
.footer { text-align: center; color: var(--text-muted); font-size: 12px; margin-top: 32px; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
|
||
<!-- Header -->
|
||
<div class="header">
|
||
<h1>🔍 Backend Code Review Report</h1>
|
||
<div class="meta-grid">
|
||
<div class="meta-item"><span class="meta-label">Scope</span><span class="meta-value">chain-level(评审管线→路由→导出→前端,覆盖本轮全部改动)</span></div>
|
||
<div class="meta-item"><span class="meta-label">Tier</span><span class="meta-value">standard</span></div>
|
||
<div class="meta-item"><span class="meta-label">Time</span><span class="meta-value">2026-08-05</span></div>
|
||
<div class="meta-item"><span class="meta-label">Files</span><span class="meta-value">review.service.ts, review-constants.ts, entries.ts, projects.ts, standards.ts, config.ts, auth.ts, db.ts, path-security.ts, pdf.service.ts, ProjectView.tsx, Sidebar.tsx, api.ts</span></div>
|
||
<div class="meta-item"><span class="meta-label">Baseline</span><span class="meta-value">ai-review-wiki.md(无独立需求文档,按既有基线 + 通用标准评审)</span></div>
|
||
<div class="meta-item">
|
||
<span class="meta-label">Verdict</span>
|
||
<span class="meta-value verdict pass">✅ PASS(无 🔴,3 🟡 需修)</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Layer 1 -->
|
||
<div class="section">
|
||
<h2>Layer 1 · Chain Decomposition</h2>
|
||
<div class="layer-grid">
|
||
<div class="layer-item"><span>Interface</span><span class="status status-issues">⚠️ Issues Found</span></div>
|
||
<div class="layer-item"><span>Business</span><span class="status status-issues">⚠️ Issues Found</span></div>
|
||
<div class="layer-item"><span>Data</span><span class="status status-clean">✅ Clean</span></div>
|
||
<div class="layer-item"><span>Utility</span><span class="status status-clean">✅ Clean</span></div>
|
||
<div class="layer-item"><span>Error Handling</span><span class="status status-clean">✅ Clean</span></div>
|
||
<div class="layer-item"><span>Security</span><span class="status status-issues">⚠️ Issues Found</span></div>
|
||
<div class="layer-item"><span>Performance</span><span class="status status-issues">⚠️ Issues Found</span></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Layer 2 -->
|
||
<div class="section">
|
||
<h2>Layer 2 · Quantitative Metrics</h2>
|
||
<table>
|
||
<thead><tr><th>Metric</th><th>Value</th></tr></thead>
|
||
<tbody>
|
||
<tr><td>Requirement Coverage</td><td class="metric-good">95%</td></tr>
|
||
<tr><td>Logic Alignment</td><td>92%</td></tr>
|
||
<tr><td>Exception Branch Coverage</td><td class="metric-warn">75%(153 后端 + 6 前端测试)</td></tr>
|
||
<tr><td>SQL Performance Risk</td><td class="metric-good">Low(参数化 + 索引)</td></tr>
|
||
<tr><td>Code Redundancy Rate</td><td>~10%(dimGuidelines 重复段、PDF/前端 SVG 双份、deliverables 三处)</td></tr>
|
||
<tr><td>Vulnerability Risk Rate</td><td class="metric-good">Low(H2/H3/M4 已修,残留 2 项 🟡)</td></tr>
|
||
<tr><td>High-Risk Scenario Coverage</td><td class="metric-warn">⚠️ 队列并发缺陷(第 4+ 条目卡 queued)</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<!-- Classification -->
|
||
<div class="section">
|
||
<h2>Classification</h2>
|
||
<div class="summary-row">
|
||
<div class="summary-card card-ready"><div class="num">2</div><div class="label">Ready</div></div>
|
||
<div class="summary-card card-needsfix"><div class="num">3</div><div class="label">Needs Fix</div></div>
|
||
<div class="summary-card card-unusable"><div class="num">0</div><div class="label">Unusable</div></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Severity Summary -->
|
||
<div class="section">
|
||
<h2>Severity Summary</h2>
|
||
<div class="severity-row">
|
||
<span class="severity-badge sev-blocker">🔴 Blocker: 0</span>
|
||
<span class="severity-badge sev-major">🟡 Major: 3</span>
|
||
<span class="severity-badge sev-minor">🔵 Minor: 8</span>
|
||
</div>
|
||
|
||
<div class="issue issue-major">
|
||
<span class="tag tag-major">🟡</span>
|
||
<span class="category">[Business]</span> 排队队列死锁 — 第 4+ 个并发条目永久卡在 queued
|
||
<div class="location">→ review.service.ts:26(queue 数组从未 push)、:32-36(并发满分支)、:56-58(processQueue 无可消费)</div>
|
||
<div class="fix">Fix: 并发满分支 `queue.push(entryId)`,使 processQueue 能消费 DB 中的 queued 条目</div>
|
||
</div>
|
||
<div class="issue issue-major">
|
||
<span class="tag tag-major">🟡</span>
|
||
<span class="category">[Interface]</span> GET entries `limit` 无上限 — 可一次拉全表
|
||
<div class="location">→ entries.ts:50(parseInt(limit) 未 clamp)</div>
|
||
<div class="fix">Fix: `Math.min(parseInt(limit, 10) || 50, 500)`,offset 非负校验</div>
|
||
</div>
|
||
<div class="issue issue-major">
|
||
<span class="tag tag-major">🟡</span>
|
||
<span class="category">[Security]</span> 前端 token 存 localStorage + 无严格 CSP
|
||
<div class="location">→ web/src/services/api.ts(token 注入);index.css 大量内联样式使 CSP 受限</div>
|
||
<div class="fix">Fix: 换 httpOnly cookie 认证,或加严格 CSP(需与内联样式权衡);至少评审侧注入面已由 H3 封堵</div>
|
||
</div>
|
||
<div class="issue issue-minor">
|
||
<span class="tag tag-minor">🔵</span>
|
||
<span class="category">[Business]</span> 并发计数双源(DB `active.cnt` + 内存 `activeCount`),重启/多实例漂移
|
||
<div class="location">→ review.service.ts:32 / :43</div>
|
||
<div class="fix">Fix: 以 DB 为准或引入进程内调度器;单实例当前可接受</div>
|
||
</div>
|
||
<div class="issue issue-minor">
|
||
<span class="tag tag-minor">🔵</span>
|
||
<span class="category">[Data]</span> 评审收尾"写 ai_report + 插 review_snapshots + 删 clone 目录"多步非原子
|
||
<div class="location">→ review.service.ts(executeReview 尾部)</div>
|
||
<div class="fix">Fix: 包事务(写库两步),目录清理失败不影响评分一致性</div>
|
||
</div>
|
||
<div class="issue issue-minor">
|
||
<span class="tag tag-minor">🔵</span>
|
||
<span class="category">[Performance]</span> GET /projects 每项目一条统计 SQL(N+1)
|
||
<div class="location">→ projects.ts GET /</div>
|
||
<div class="fix">Fix: 单条 GROUP BY 查询;规模小可接受</div>
|
||
</div>
|
||
<div class="issue issue-minor">
|
||
<span class="tag tag-minor">🔵</span>
|
||
<span class="category">[Redundancy]</span> dimGuidelines「规模、功能点、技术难度」与「规模与功能点与技术难度」两段正文完全重复
|
||
<div class="location">→ review.service.ts dimGuidelines</div>
|
||
<div class="fix">Fix: 共用同一段指南文本</div>
|
||
</div>
|
||
<div class="issue issue-minor">
|
||
<span class="tag tag-minor">🔵</span>
|
||
<span class="category">[Redundancy]</span> PDF 与前端 SVG 雷达/条形图逻辑双份实现
|
||
<div class="location">→ pdf.service.ts / ProjectView.tsx(RadarChart/BarChart)</div>
|
||
<div class="fix">Fix: 可选,抽出共享 SVG 生成器(跨运行时,成本高,可保留)</div>
|
||
</div>
|
||
<div class="issue issue-minor">
|
||
<span class="tag tag-minor">🔵</span>
|
||
<span class="category">[Redundancy]</span> DEFAULT_DELIVERABLES 三处重复定义
|
||
<div class="location">→ ProjectView.tsx(DeliverablesView + DetailPanel)、entries.ts(init + export)</div>
|
||
<div class="fix">Fix: 收敛为共享常量</div>
|
||
</div>
|
||
<div class="issue issue-minor">
|
||
<span class="tag tag-minor">🔵</span>
|
||
<span class="category">[Interface]</span> 成功响应格式不一({success:true} vs 整条记录)
|
||
<div class="location">→ 各路由</div>
|
||
<div class="fix">Fix: 约定统一包装;前端已兼容,非阻塞</div>
|
||
</div>
|
||
<div class="issue issue-minor">
|
||
<span class="tag tag-minor">🔵</span>
|
||
<span class="category">[Security]</span> 改密码不使既有 JWT 失效(authSecret 未轮换);登录限流 Map 重启清零
|
||
<div class="location">→ auth.ts</div>
|
||
<div class="fix">Fix: 如需强制登出则轮换 AUTH_SECRET;限流可持久化(单管理员可接受)</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Manual Review Required -->
|
||
<div class="section">
|
||
<h2>Manual Review Required</h2>
|
||
<div class="manual-item">权限模块:单管理员 token 24h、无刷新/撤销/轮换、localStorage 存储(单管理员工具可接受,需确认)</div>
|
||
<div class="manual-item">数据迁移:ALTER TABLE try/catch 幂等、无显式回滚计划(本规模可接受)</div>
|
||
<div class="manual-item">评审引擎执行参赛者仓库代码(npm install/build/start、spawn cmd)— 未信任代码执行,需沙箱(doc §8.2)</div>
|
||
</div>
|
||
|
||
<!-- Knowledge Accumulation -->
|
||
<div class="section">
|
||
<h2>Knowledge Accumulation Suggestions</h2>
|
||
<div class="accum-item">历史坑位 — 并发排队只写 DB 状态、未入内存队列(review.service.ts queue[] 从未 push)</div>
|
||
<div class="accum-item">业务规则 — max_score_cap / final_level 需在「评审管线 + 人工修正」双路径保持一致</div>
|
||
<div class="accum-item">架构约束 — 评审引擎需在隔离/沙箱环境运行,禁止裸机直跑参赛代码</div>
|
||
</div>
|
||
|
||
<!-- Footer -->
|
||
<div class="footer">
|
||
Generated by code-review + gstack-review skills · 2026-08-05
|
||
</div>
|
||
|
||
</div>
|
||
</body>
|
||
</html>
|