704 lines
23 KiB
HTML
704 lines
23 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>代码审查报告 - UI 预览</title>
|
|
<style>
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
background: #0d1117;
|
|
color: #c9d1d9;
|
|
font-size: 13px;
|
|
line-height: 1.5;
|
|
max-width: 960px;
|
|
margin: 0 auto;
|
|
min-height: 100vh;
|
|
padding: 0;
|
|
}
|
|
|
|
/* ─── Header ─── */
|
|
.header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 16px 16px 12px;
|
|
border-bottom: 1px solid #21262d;
|
|
}
|
|
.header h2 {
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
color: #e6edf3;
|
|
}
|
|
.header .meta {
|
|
color: #8b949e;
|
|
font-size: 12px;
|
|
margin-top: 2px;
|
|
}
|
|
|
|
/* ─── Buttons ─── */
|
|
.btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
padding: 5px 12px;
|
|
border: 1px solid #30363d;
|
|
background: #21262d;
|
|
color: #c9d1d9;
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
font-size: 12px;
|
|
transition: background .15s, border-color .15s;
|
|
}
|
|
.btn:hover { background: #30363d; }
|
|
.btn-primary {
|
|
background: #238636;
|
|
color: #fff;
|
|
border-color: rgba(240,246,252,0.1);
|
|
}
|
|
.btn-primary:hover { background: #2ea043; }
|
|
|
|
/* ─── Banner ─── */
|
|
.banner {
|
|
margin: 12px 16px 0;
|
|
padding: 8px 12px;
|
|
border-radius: 6px;
|
|
font-size: 12px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
}
|
|
.banner-warning {
|
|
background: rgba(200, 160, 0, 0.15);
|
|
border: 1px solid rgba(200, 160, 0, 0.35);
|
|
color: #cca700;
|
|
}
|
|
.banner-error {
|
|
background: rgba(248, 81, 73, 0.15);
|
|
border: 1px solid rgba(248, 81, 73, 0.35);
|
|
color: #f48771;
|
|
}
|
|
|
|
/* ─── Errors box ─── */
|
|
.errors-box {
|
|
margin: 12px 16px 0;
|
|
padding: 10px 12px;
|
|
background: rgba(248, 81, 73, 0.1);
|
|
border: 1px solid rgba(248, 81, 73, 0.3);
|
|
border-radius: 6px;
|
|
}
|
|
.errors-box-title {
|
|
font-weight: 600;
|
|
color: #f48771;
|
|
font-size: 12px;
|
|
margin-bottom: 4px;
|
|
}
|
|
.errors-box-item {
|
|
font-size: 12px;
|
|
color: #f48771;
|
|
padding: 2px 0;
|
|
}
|
|
|
|
/* ─── Summary Stats ─── */
|
|
.summary {
|
|
display: flex;
|
|
gap: 8px;
|
|
padding: 12px 16px 0;
|
|
flex-wrap: wrap;
|
|
}
|
|
.stat-card {
|
|
flex: 1;
|
|
min-width: 100px;
|
|
padding: 10px 12px;
|
|
border: 1px solid #30363d;
|
|
border-radius: 8px;
|
|
background: #161b22;
|
|
text-align: center;
|
|
}
|
|
.stat-card .num {
|
|
font-size: 24px;
|
|
font-weight: 700;
|
|
line-height: 1.2;
|
|
}
|
|
.stat-card .label {
|
|
font-size: 11px;
|
|
color: #8b949e;
|
|
margin-top: 1px;
|
|
}
|
|
.stat-error .num { color: #f48771; }
|
|
.stat-warning .num { color: #d29922; }
|
|
.stat-info .num { color: #58a6ff; }
|
|
.stat-total .num { color: #e6edf3; }
|
|
|
|
/* ─── Tab Bar ─── */
|
|
.tab-bar {
|
|
display: flex;
|
|
align-items: stretch;
|
|
margin: 16px 16px 0;
|
|
border-bottom: 1px solid #21262d;
|
|
}
|
|
.tab {
|
|
position: relative;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
padding: 8px 16px;
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
color: #8b949e;
|
|
cursor: pointer;
|
|
border-bottom: 2px solid transparent;
|
|
transition: color .15s, border-color .15s;
|
|
user-select: none;
|
|
white-space: nowrap;
|
|
}
|
|
.tab:hover { color: #e6edf3; }
|
|
.tab.active {
|
|
color: #e6edf3;
|
|
border-bottom-color: #f78166;
|
|
}
|
|
.tab-count {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-width: 18px;
|
|
height: 18px;
|
|
padding: 0 5px;
|
|
border-radius: 9px;
|
|
font-size: 11px;
|
|
font-weight: 500;
|
|
line-height: 1;
|
|
}
|
|
.tab-count-error { background: rgba(248, 81, 73, 0.2); color: #f48771; }
|
|
.tab-count-warning { background: rgba(200, 160, 0, 0.2); color: #cca700; }
|
|
.tab-count-info { background: rgba(88, 166, 255, 0.2); color: #58a6ff; }
|
|
|
|
/* ─── Tab Content ─── */
|
|
.tab-content { display: none; padding: 8px 16px 20px; }
|
|
.tab-content.active { display: block; }
|
|
|
|
/* ─── Section header ─── */
|
|
.section-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-bottom: 8px;
|
|
padding-top: 12px;
|
|
}
|
|
.section-header:first-child { padding-top: 0; }
|
|
.section-header-title {
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
color: #8b949e;
|
|
text-transform: uppercase;
|
|
letter-spacing: .03em;
|
|
}
|
|
|
|
/* ─── Item ─── */
|
|
.item {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 10px;
|
|
padding: 10px 12px;
|
|
margin-top: 6px;
|
|
border: 1px solid #21262d;
|
|
border-radius: 8px;
|
|
background: #161b22;
|
|
cursor: pointer;
|
|
transition: border-color .15s, background .15s;
|
|
}
|
|
.item:first-child { margin-top: 0; }
|
|
.item:hover {
|
|
border-color: #58a6ff;
|
|
background: #1c2128;
|
|
}
|
|
.item.expanded {
|
|
border-color: #58a6ff;
|
|
}
|
|
|
|
.item-severity {
|
|
flex-shrink: 0;
|
|
width: 5px;
|
|
align-self: stretch;
|
|
border-radius: 3px;
|
|
margin: -10px 0 -10px -12px;
|
|
border-top-left-radius: 8px;
|
|
border-bottom-left-radius: 8px;
|
|
}
|
|
.item-severity-error { background: #f48771; }
|
|
.item-severity-warning { background: #cca700; }
|
|
.item-severity-info { background: #58a6ff; }
|
|
|
|
.item-body { flex: 1; min-width: 0; }
|
|
.item-row1 {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
flex-wrap: nowrap;
|
|
}
|
|
.item-icon {
|
|
flex-shrink: 0;
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
display: inline-block;
|
|
}
|
|
.icon-error { background: #f48771; }
|
|
.icon-warning { background: #cca700; }
|
|
.icon-info { background: #58a6ff; }
|
|
|
|
.item-badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
padding: 0 7px;
|
|
height: 20px;
|
|
border-radius: 5px;
|
|
font-size: 10px;
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
letter-spacing: .03em;
|
|
flex-shrink: 0;
|
|
line-height: 1;
|
|
}
|
|
.badge-linter { background: rgba(139, 148, 158, 0.15); color: #8b949e; }
|
|
.badge-custom { background: rgba(191, 133, 255, 0.15); color: #ce93d8; }
|
|
.badge-ai { background: rgba(79, 195, 247, 0.15); color: #4dd0e1; }
|
|
|
|
.item-message {
|
|
flex: 1;
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
color: #e6edf3;
|
|
font-size: 14px;
|
|
}
|
|
.item-line {
|
|
flex-shrink: 0;
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
color: #8b949e;
|
|
font-family: 'SF Mono', Consolas, 'Liberation Mono', Menlo, monospace;
|
|
background: rgba(139, 148, 158, 0.08);
|
|
padding: 1px 6px;
|
|
border-radius: 4px;
|
|
line-height: 20px;
|
|
}
|
|
.item-rule {
|
|
flex-shrink: 0;
|
|
font-size: 12px;
|
|
color: #8b949e;
|
|
font-family: 'SF Mono', Consolas, 'Liberation Mono', Menlo, monospace;
|
|
max-width: 180px;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
.item-fix {
|
|
flex-shrink: 0;
|
|
padding: 2px 8px;
|
|
border: 1px solid #30363d;
|
|
background: #21262d;
|
|
color: #c9d1d9;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-size: 11px;
|
|
transition: background .15s;
|
|
line-height: 18px;
|
|
}
|
|
.item-fix:hover { background: #30363d; }
|
|
.item-fix:disabled { opacity: .4; cursor: not-allowed; }
|
|
|
|
/* ─── Detail / Expanded ─── */
|
|
.item-detail { display: none; margin-top: 8px; padding-top: 8px; border-top: 1px solid #21262d; }
|
|
.item.expanded .item-detail { display: block; animation: fadeSlideIn .2s ease; }
|
|
@keyframes fadeSlideIn {
|
|
from { opacity: 0; transform: translateY(-4px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
.detail-text { color: #8b949e; font-size: 13px; line-height: 1.7; }
|
|
.detail-text code { font-family: 'SF Mono', Consolas, 'Liberation Mono', Menlo, monospace; font-size: 13px; }
|
|
.detail-suggestion {
|
|
margin-top: 8px;
|
|
padding: 8px 12px;
|
|
background: rgba(88, 166, 255, 0.08);
|
|
border: 1px solid rgba(88, 166, 255, 0.2);
|
|
border-radius: 6px;
|
|
font-size: 13px;
|
|
color: #79c0ff;
|
|
}
|
|
.detail-diff {
|
|
margin-top: 8px;
|
|
padding: 10px 12px;
|
|
background: #0d1117;
|
|
border: 1px solid #30363d;
|
|
border-radius: 6px;
|
|
font-family: 'SF Mono', Consolas, 'Liberation Mono', Menlo, monospace;
|
|
font-size: 13px;
|
|
line-height: 1.6;
|
|
overflow-x: auto;
|
|
white-space: pre;
|
|
}
|
|
.detail-diff del {
|
|
color: #f48771;
|
|
background: rgba(248, 81, 73, 0.15);
|
|
display: block;
|
|
text-decoration: none;
|
|
}
|
|
.detail-diff ins {
|
|
color: #3fb950;
|
|
background: rgba(63, 185, 80, 0.15);
|
|
display: block;
|
|
text-decoration: none;
|
|
}
|
|
.detail-original {
|
|
margin-top: 6px;
|
|
font-size: 12px;
|
|
color: #8b949e;
|
|
font-style: italic;
|
|
}
|
|
.detail-category {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
padding: 2px 8px;
|
|
border-radius: 4px;
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
background: rgba(139,148,158,0.1);
|
|
color: #8b949e;
|
|
margin-top: 6px;
|
|
}
|
|
|
|
/* ─── Empty state ─── */
|
|
.empty {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 48px 20px;
|
|
color: #8b949e;
|
|
text-align: center;
|
|
font-style: italic;
|
|
font-size: 13px;
|
|
}
|
|
|
|
/* ─── Actions bar ─── */
|
|
.actions {
|
|
display: flex;
|
|
gap: 8px;
|
|
padding: 16px 16px 20px;
|
|
border-top: 1px solid #21262d;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div class="header">
|
|
<div>
|
|
<h2>
|
|
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#58a6ff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M9 5H7a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-2"/>
|
|
<rect x="9" y="3" width="6" height="4" rx="1"/>
|
|
<path d="M9 14l2 2 4-4"/>
|
|
</svg>
|
|
代码审查报告
|
|
</h2>
|
|
<div class="meta">
|
|
src/UserService.java · Java · 8.3s
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="banner banner-warning">⚠ AI 审查未完成,报告仅包含静态分析结果</div>
|
|
|
|
<div class="errors-box">
|
|
<div class="errors-box-title">✖ 执行错误</div>
|
|
<div class="errors-box-item">未配置 sql 语言的 linter,请在设置中添加 vscode-code-reviewer.linters.sql</div>
|
|
<div class="errors-box-item">AI 审查失败: API 请求超时</div>
|
|
</div>
|
|
|
|
<div class="summary">
|
|
<div class="stat-card stat-total"><div class="num">12</div><div class="label">总计问题</div></div>
|
|
<div class="stat-card stat-error"><div class="num">3</div><div class="label">错误</div></div>
|
|
<div class="stat-card stat-warning"><div class="num">7</div><div class="label">警告</div></div>
|
|
<div class="stat-card stat-info"><div class="num">2</div><div class="label">建议</div></div>
|
|
</div>
|
|
|
|
<div class="tab-bar">
|
|
<div class="tab active" data-tab="linter" onclick="switchTab('linter')">
|
|
🔧 ESLint + PMD
|
|
<span class="tab-count tab-count-error">2</span>
|
|
<span class="tab-count tab-count-warning">4</span>
|
|
<span class="tab-count tab-count-info">1</span>
|
|
</div>
|
|
<div class="tab" data-tab="custom" onclick="switchTab('custom')">
|
|
📋 自定义规则
|
|
<span class="tab-count tab-count-error">1</span>
|
|
<span class="tab-count tab-count-warning">1</span>
|
|
</div>
|
|
<div class="tab" data-tab="ai" onclick="switchTab('ai')">
|
|
🤖 AI 审查
|
|
<span class="tab-count tab-count-warning">2</span>
|
|
<span class="tab-count tab-count-info">1</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tab-content active" id="tab-linter">
|
|
<div class="section-header">
|
|
<span class="section-header-title">ESLint + PMD · 7 个问题</span>
|
|
<button class="btn">🔧 全部修复</button>
|
|
</div>
|
|
|
|
<div class="item static-item" onclick="toggleItem(this)">
|
|
<div class="item-severity item-severity-error"></div>
|
|
<div class="item-body">
|
|
<div class="item-row1">
|
|
<span class="item-icon icon-error"></span>
|
|
<span class="item-badge badge-linter">Linter</span>
|
|
<span class="item-rule">pmd:AvoidUsingHardCodedPassword</span>
|
|
<span class="item-message">硬编码密码:密码不应硬编码在代码中</span>
|
|
<span class="item-line">L13</span>
|
|
<button class="item-fix" onclick="event.stopPropagation(); this.disabled=true; this.textContent='⏳...';">🔧 修复</button>
|
|
</div>
|
|
<div class="item-detail">
|
|
<div class="detail-original">原文: Hard-coded password detected (DB_PASS) in field definition</div>
|
|
<div class="detail-suggestion">将密码移至环境变量或配置中心,通过 system.getenv() 读取,避免硬编码在源码中</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="item static-item expanded" onclick="toggleItem(this)">
|
|
<div class="item-severity item-severity-error"></div>
|
|
<div class="item-body">
|
|
<div class="item-row1">
|
|
<span class="item-icon icon-error"></span>
|
|
<span class="item-badge badge-linter">Linter</span>
|
|
<span class="item-rule">pmd:SQLInjection</span>
|
|
<span class="item-message">SQL 注入:直接拼接用户输入可能导致 SQL 注入攻击</span>
|
|
<span class="item-line">L18</span>
|
|
<button class="item-fix" onclick="event.stopPropagation(); this.disabled=true; this.textContent='⏳...';">🔧 修复</button>
|
|
</div>
|
|
<div class="item-detail">
|
|
<div class="detail-original">原文: SQL injection detected: string concatenation in query</div>
|
|
<div class="detail-suggestion">改用 PreparedStatement 参数化查询,避免字符串拼接</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="item static-item" onclick="toggleItem(this)">
|
|
<div class="item-severity item-severity-warning"></div>
|
|
<div class="item-body">
|
|
<div class="item-row1">
|
|
<span class="item-icon icon-warning"></span>
|
|
<span class="item-badge badge-linter">Linter</span>
|
|
<span class="item-rule">pmd:CloseResource</span>
|
|
<span class="item-message">资源未关闭:Statement/ResultSet 未在 finally 中关闭</span>
|
|
<span class="item-line">L19</span>
|
|
<button class="item-fix" onclick="event.stopPropagation(); this.disabled=true; this.textContent='⏳...';">🔧 修复</button>
|
|
</div>
|
|
<div class="item-detail">
|
|
<div class="detail-original">原文: Ensure that resources like Statement and ResultSet are closed</div>
|
|
<div class="detail-suggestion">使用 try-with-resources 自动关闭 Statement 和 ResultSet,或确保在 finally 块中关闭它们</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="item static-item" onclick="toggleItem(this)">
|
|
<div class="item-severity item-severity-warning"></div>
|
|
<div class="item-body">
|
|
<div class="item-row1">
|
|
<span class="item-icon icon-warning"></span>
|
|
<span class="item-badge badge-linter">Linter</span>
|
|
<span class="item-rule">pmd:MethodNamingConventions</span>
|
|
<span class="item-message">方法命名不规范:FetchUserById 应以小写字母开头</span>
|
|
<span class="item-line">L37</span>
|
|
</div>
|
|
<div class="item-detail">
|
|
<div class="detail-original">原文: Method name 'FetchUserById' does not follow camelCase naming convention</div>
|
|
<div class="detail-suggestion">将方法名改为 fetchUserById,遵循 Java camelCase 命名规范</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="item static-item" onclick="toggleItem(this)">
|
|
<div class="item-severity item-severity-warning"></div>
|
|
<div class="item-body">
|
|
<div class="item-row1">
|
|
<span class="item-icon icon-warning"></span>
|
|
<span class="item-badge badge-linter">Linter</span>
|
|
<span class="item-rule">pmd:FieldNamingConventions</span>
|
|
<span class="item-message">字段命名:api_key 应使用大写常量命名</span>
|
|
<span class="item-line">L44</span>
|
|
</div>
|
|
<div class="item-detail">
|
|
<div class="detail-original">原文: Field name 'api_key' does not follow naming convention</div>
|
|
<div class="detail-suggestion">对于 static final 常量,应将名称改为 API_KEY 并使用下划线分隔</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="item static-item" onclick="toggleItem(this)">
|
|
<div class="item-severity item-severity-warning"></div>
|
|
<div class="item-body">
|
|
<div class="item-row1">
|
|
<span class="item-icon icon-warning"></span>
|
|
<span class="item-badge badge-linter">Linter</span>
|
|
<span class="item-rule">eslint:no-console</span>
|
|
<span class="item-message">生产代码不应保留 console 语句</span>
|
|
<span class="item-line">L57</span>
|
|
</div>
|
|
<div class="item-detail">
|
|
<div class="detail-original">原文: Unexpected console statement (no-console)</div>
|
|
<div class="detail-suggestion">移除 console.log 调试语句,或使用专门的日志框架替代</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="item static-item" onclick="toggleItem(this)">
|
|
<div class="item-severity item-severity-info"></div>
|
|
<div class="item-body">
|
|
<div class="item-row1">
|
|
<span class="item-icon icon-info"></span>
|
|
<span class="item-badge badge-linter">Linter</span>
|
|
<span class="item-rule">pmd:UnusedLocalVariable</span>
|
|
<span class="item-message">未使用的局部变量:unusedVar 已声明但未使用</span>
|
|
<span class="item-line">L56</span>
|
|
</div>
|
|
<div class="item-detail">
|
|
<div class="detail-original">原文: Avoid unused local variables such as 'unusedVar'</div>
|
|
<div class="detail-suggestion">删除未使用的变量声明,或确认是否需要该变量</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tab-content" id="tab-custom">
|
|
<div class="section-header">
|
|
<span class="section-header-title">自定义规则 · 2 个问题</span>
|
|
</div>
|
|
|
|
<div class="item static-item expanded" onclick="toggleItem(this)">
|
|
<div class="item-severity item-severity-error"></div>
|
|
<div class="item-body">
|
|
<div class="item-row1">
|
|
<span class="item-icon icon-error"></span>
|
|
<span class="item-badge badge-custom">自定义</span>
|
|
<span class="item-rule">no-hardcoded-credentials</span>
|
|
<span class="item-message">禁止在代码中硬编码数据库密码和 API Key</span>
|
|
<span class="item-line">L13</span>
|
|
</div>
|
|
<div class="item-detail">
|
|
<div class="detail-text">规则 no-hardcoded-credentials 触发: 禁止在代码中硬编码数据库密码和 API Key</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="item static-item" onclick="toggleItem(this)">
|
|
<div class="item-severity item-severity-warning"></div>
|
|
<div class="item-body">
|
|
<div class="item-row1">
|
|
<span class="item-icon icon-warning"></span>
|
|
<span class="item-badge badge-custom">自定义</span>
|
|
<span class="item-rule">avoid-console-log</span>
|
|
<span class="item-message">生产代码不应使用 console.log 调试</span>
|
|
<span class="item-line">L57</span>
|
|
</div>
|
|
<div class="item-detail">
|
|
<div class="detail-text">规则 avoid-console-log 触发: 生产代码不应使用 console.log 调试</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tab-content" id="tab-ai">
|
|
<div class="section-header">
|
|
<span class="section-header-title">AI 审查建议 · 3 条</span>
|
|
</div>
|
|
|
|
<div class="item expanded" onclick="toggleItem(this)">
|
|
<div class="item-severity item-severity-warning"></div>
|
|
<div class="item-body">
|
|
<div class="item-row1">
|
|
<span class="item-icon icon-warning"></span>
|
|
<span class="item-badge badge-ai">AI</span>
|
|
<span class="item-rule">sql-injection</span>
|
|
<span class="item-message">SQL注入风险:用户状态参数直接拼接 SQL 查询</span>
|
|
<span class="item-line">L18</span>
|
|
</div>
|
|
<div class="item-detail">
|
|
<div class="detail-text">getActiveUsers 方法中,status 参数直接拼接到 SQL 查询字符串中,攻击者可传入恶意值导致 SQL 注入。同时缺少对 Connection 为 null 的防御性检查。</div>
|
|
<span class="detail-category">🎯 安全漏洞</span>
|
|
<div class="detail-suggestion">使用 PreparedStatement 参数化查询;添加 Connection 非空校验;使用 try-with-resources 管理资源</div>
|
|
<div class="detail-diff"><del>- String query = "SELECT username FROM users WHERE status = '" + status + "'";</del>
|
|
<ins>+ String query = "SELECT username FROM users WHERE status = ?";</ins>
|
|
<ins>+ PreparedStatement ps = conn.prepareStatement(query);</ins>
|
|
<ins>+ ps.setString(1, status);</ins>
|
|
<ins>+ ResultSet rs = ps.executeQuery();</ins></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="item" onclick="toggleItem(this)">
|
|
<div class="item-severity item-severity-warning"></div>
|
|
<div class="item-body">
|
|
<div class="item-row1">
|
|
<span class="item-icon icon-warning"></span>
|
|
<span class="item-badge badge-ai">AI</span>
|
|
<span class="item-rule">hardcoded-secret</span>
|
|
<span class="item-message">硬编码密钥:数据库密码和 API Key 直接写在源码中</span>
|
|
<span class="item-line">L13</span>
|
|
</div>
|
|
<div class="item-detail">
|
|
<div class="detail-text">DB_PASS 和 api_key 都是敏感凭据,硬编码在类中会导致密钥泄露。任何有权访问代码的人(包括通过反编译 .class 文件)都可获取这些凭据。</div>
|
|
<span class="detail-category">🎯 安全漏洞</span>
|
|
<div class="detail-suggestion">使用环境变量或密钥管理服务(如 Vault)存储敏感信息,在运行时注入</div>
|
|
<div class="detail-diff"><del>- private static final String DB_PASS = "admin123";</del>
|
|
<ins>+ private static final String DB_PASS = System.getenv("DB_PASS");</ins></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="item" onclick="toggleItem(this)">
|
|
<div class="item-severity item-severity-info"></div>
|
|
<div class="item-body">
|
|
<div class="item-row1">
|
|
<span class="item-icon icon-info"></span>
|
|
<span class="item-badge badge-ai">AI</span>
|
|
<span class="item-rule">unused-method</span>
|
|
<span class="item-message">未使用的公共方法:unusedMethod 疑似死代码</span>
|
|
<span class="item-line">L55</span>
|
|
</div>
|
|
<div class="item-detail">
|
|
<div class="detail-text">unusedMethod 方法仅在 main 中未被调用,且其内部存在 DEBUG 打印和未使用的局部变量,可能是开发阶段遗留的测试代码,建议确认后删除。</div>
|
|
<span class="detail-category">🎯 代码设计</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="actions">
|
|
<button class="btn btn-primary">🔄 重新审查</button>
|
|
<button class="btn">📄 导出报告</button>
|
|
<button class="btn">⚙️ 设置</button>
|
|
</div>
|
|
|
|
<script>
|
|
function switchTab(tabId) {
|
|
document.querySelectorAll('.tab').forEach(function(t) { t.classList.remove('active'); });
|
|
document.querySelectorAll('.tab-content').forEach(function(tc) { tc.classList.remove('active'); });
|
|
document.querySelector('.tab[data-tab="' + tabId + '"]').classList.add('active');
|
|
document.getElementById('tab-' + tabId).classList.add('active');
|
|
}
|
|
function toggleItem(el) {
|
|
if (event.target.closest('button')) return;
|
|
el.classList.toggle('expanded');
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|