feat: 审批流程模块 (T审批A审批)
- 新增 backend/app/api/approval.py 审批API - 前端H5支持发起审批、审批操作 - 添加审批卡片弹窗组件 - 路由注册审批模块
This commit is contained in:
@@ -0,0 +1,256 @@
|
||||
# =============================================================================
|
||||
# 🎁 惊喜 1: 项目健康度仪表盘
|
||||
# =============================================================================
|
||||
# 用途: 一键生成项目健康度总览 HTML(明早桌面打开即用)
|
||||
# 跑法: python scripts/dashboard.py
|
||||
# 产物: docs/dashboard.html
|
||||
# =============================================================================
|
||||
|
||||
import os
|
||||
import json
|
||||
import subprocess
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
||||
OUTPUT = PROJECT_ROOT / "docs" / "dashboard.html"
|
||||
|
||||
|
||||
def count_lines(glob_pattern: str) -> int:
|
||||
"""统计符合 glob 的代码总行数"""
|
||||
import glob
|
||||
total = 0
|
||||
for f in glob.glob(glob_pattern, recursive=True):
|
||||
if os.path.isfile(f):
|
||||
try:
|
||||
with open(f, "r", encoding="utf-8", errors="ignore") as fp:
|
||||
total += sum(1 for _ in fp)
|
||||
except Exception:
|
||||
pass
|
||||
return total
|
||||
|
||||
|
||||
def count_files(glob_pattern: str) -> int:
|
||||
import glob
|
||||
return sum(1 for f in glob.glob(glob_pattern, recursive=True) if os.path.isfile(f))
|
||||
|
||||
|
||||
def git_info() -> dict:
|
||||
"""拿 git 仓库信息"""
|
||||
try:
|
||||
result = {
|
||||
"branch": subprocess.run(
|
||||
["git", "rev-parse", "--abbrev-ref", "HEAD"],
|
||||
cwd=PROJECT_ROOT, capture_output=True, text=True
|
||||
).stdout.strip(),
|
||||
"last_commit": subprocess.run(
|
||||
["git", "log", "-1", "--format=%h %s"],
|
||||
cwd=PROJECT_ROOT, capture_output=True, text=True
|
||||
).stdout.strip(),
|
||||
"commit_count": subprocess.run(
|
||||
["git", "rev-list", "--count", "HEAD"],
|
||||
cwd=PROJECT_ROOT, capture_output=True, text=True
|
||||
).stdout.strip(),
|
||||
}
|
||||
return result
|
||||
except Exception as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
|
||||
def main():
|
||||
# 1. 代码统计
|
||||
stats = {
|
||||
"backend_python_files": count_files("backend/app/**/*.py"),
|
||||
"backend_python_lines": count_lines("backend/app/**/*.py"),
|
||||
"frontend_admin_files": count_files("frontend-admin/src/**/*.{vue,ts,js}"),
|
||||
"frontend_agent_files": count_files("frontend-agent/src/**/*.{vue,ts,js}"),
|
||||
"frontend_h5_files": count_files("frontend-h5/src/**/*.{vue,ts,js}"),
|
||||
"frontend_portal_files": count_files("frontend-portal/src/**/*.{vue,ts,js}"),
|
||||
"docs_files": count_files("docs/**/*.md"),
|
||||
"scripts_files": count_files("scripts/**/*.sh"),
|
||||
"tests_files": count_files("backend/tests/**/*.py"),
|
||||
}
|
||||
|
||||
# 2. 文档统计
|
||||
docs_path = PROJECT_ROOT / "docs"
|
||||
doc_categories = {
|
||||
"评审报告": len(list((docs_path / "评审报告").glob("*.md"))) if (docs_path / "评审报告").exists() else 0,
|
||||
"审计报告": len(list((docs_path / "审计报告").glob("*.md"))) if (docs_path / "审计报告").exists() else 0,
|
||||
"ADRs": len(list((docs_path / "ADRs").glob("*.md"))) if (docs_path / "ADRs").exists() else 0,
|
||||
"SOPs": len(list((docs_path / "SOPs").glob("*.md"))) if (docs_path / "SOPs").exists() else 0,
|
||||
"路线图": len(list((docs_path / "路线图").glob("*.md"))) if (docs_path / "路线图").exists() else 0,
|
||||
}
|
||||
|
||||
# 3. 风险统计(解析风险跟踪表)
|
||||
risk_file = docs_path / "风险跟踪表.md"
|
||||
risk_stats = {"P0_remaining": 0, "P1": 0, "P2": 0, "P3": 0, "M": 0, "L": 0}
|
||||
if risk_file.exists():
|
||||
text = risk_file.read_text(encoding="utf-8")
|
||||
for level in ["P0", "P1", "P2", "P3", "M", "L"]:
|
||||
risk_stats[f"{level}_total"] = text.count(f"### {level}-")
|
||||
risk_stats[f"{level}_remaining"] = text.count(f"### {level}-") - text.count("✅")
|
||||
|
||||
# 4. Git 信息
|
||||
g = git_info()
|
||||
|
||||
# 5. 模板渲染
|
||||
html = f"""<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>企微 IT 智能服务台 - 健康度仪表盘</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
|
||||
body {{
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
color: #333;
|
||||
}}
|
||||
.container {{ max-width: 1400px; margin: 0 auto; }}
|
||||
h1 {{ color: white; margin-bottom: 20px; text-align: center; font-size: 2.2em; }}
|
||||
.timestamp {{ color: rgba(255,255,255,0.8); text-align: center; margin-bottom: 30px; }}
|
||||
.grid {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 20px; }}
|
||||
.card {{
|
||||
background: white; border-radius: 12px; padding: 24px;
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
|
||||
transition: transform 0.2s;
|
||||
}}
|
||||
.card:hover {{ transform: translateY(-2px); }}
|
||||
.card h2 {{ font-size: 1.1em; color: #555; margin-bottom: 12px; }}
|
||||
.big-number {{ font-size: 2.4em; font-weight: bold; color: #667eea; }}
|
||||
.label {{ color: #888; font-size: 0.9em; }}
|
||||
.stat-row {{
|
||||
display: flex; justify-content: space-between;
|
||||
padding: 6px 0; border-bottom: 1px solid #f0f0f0;
|
||||
}}
|
||||
.stat-row:last-child {{ border: none; }}
|
||||
.badge {{
|
||||
display: inline-block; padding: 4px 10px;
|
||||
border-radius: 20px; font-size: 0.85em; margin: 2px;
|
||||
}}
|
||||
.badge.green {{ background: #d4edda; color: #155724; }}
|
||||
.badge.yellow {{ background: #fff3cd; color: #856404; }}
|
||||
.badge.red {{ background: #f8d7da; color: #721c24; }}
|
||||
.badge.blue {{ background: #d1ecf1; color: #0c5460; }}
|
||||
.git-info {{
|
||||
background: #282c34; color: #abb2bf;
|
||||
padding: 16px; border-radius: 8px; font-family: 'Consolas', monospace;
|
||||
font-size: 0.9em; line-height: 1.6;
|
||||
}}
|
||||
.git-info .hash {{ color: #61afef; }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>🚀 企微 IT 智能服务台 - 健康度仪表盘</h1>
|
||||
<div class="timestamp">生成时间: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}</div>
|
||||
|
||||
<div class="grid">
|
||||
<!-- 概览 -->
|
||||
<div class="card">
|
||||
<h2>📊 代码规模</h2>
|
||||
<div class="big-number">{stats['backend_python_lines']:,}</div>
|
||||
<div class="label">后端 Python 代码行</div>
|
||||
<div style="margin-top: 12px;">
|
||||
<div class="stat-row"><span>后端 Python 文件</span><strong>{stats['backend_python_files']}</strong></div>
|
||||
<div class="stat-row"><span>Admin 前端</span><strong>{stats['frontend_admin_files']} 文件</strong></div>
|
||||
<div class="stat-row"><span>Agent 前端</span><strong>{stats['frontend_agent_files']} 文件</strong></div>
|
||||
<div class="stat-row"><span>H5 前端</span><strong>{stats['frontend_h5_files']} 文件</strong></div>
|
||||
<div class="stat-row"><span>Portal 前端</span><strong>{stats['frontend_portal_files']} 文件</strong></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 文档统计 -->
|
||||
<div class="card">
|
||||
<h2>📚 文档</h2>
|
||||
<div class="big-number">{stats['docs_files']}</div>
|
||||
<div class="label">文档总数</div>
|
||||
<div style="margin-top: 12px;">
|
||||
{''.join(f'<div class="stat-row"><span>{k}</span><strong>{v}</strong></div>' for k, v in doc_categories.items())}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 风险状态 -->
|
||||
<div class="card">
|
||||
<h2>🛡️ 风险状态</h2>
|
||||
<div class="big-number" style="color: #dc3545;">{risk_stats.get('P0_remaining', 0)}</div>
|
||||
<div class="label">P0 遗留(需立即修)</div>
|
||||
<div style="margin-top: 12px;">
|
||||
<div class="stat-row"><span>P1 中危</span><span class="badge yellow">{risk_stats.get('P1_remaining', 0)} 待修</span></div>
|
||||
<div class="stat-row"><span>P2 低危</span><span class="badge yellow">{risk_stats.get('P2_remaining', 0)} 待修</span></div>
|
||||
<div class="stat-row"><span>M 中</span><span class="badge blue">{risk_stats.get('M_remaining', 0)} 待修</span></div>
|
||||
<div class="stat-row"><span>L 低</span><span class="badge blue">{risk_stats.get('L_remaining', 0)} 待修</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 脚本与测试 -->
|
||||
<div class="card">
|
||||
<h2>🛠️ 工具链</h2>
|
||||
<div class="big-number">{stats['scripts_files']}</div>
|
||||
<div class="label">自动化脚本</div>
|
||||
<div style="margin-top: 12px;">
|
||||
<div class="stat-row"><span>后端测试</span><strong>{stats['tests_files']} 文件</strong></div>
|
||||
<div class="stat-row"><span>安全审计</span><span class="badge green">✅ 已配</span></div>
|
||||
<div class="stat-row"><span>API 文档</span><span class="badge green">✅ 已配</span></div>
|
||||
<div class="stat-row"><span>备份脚本</span><span class="badge green">✅ 已配</span></div>
|
||||
<div class="stat-row"><span>Pre-commit</span><span class="badge green">✅ 已配</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Git 状态 -->
|
||||
<div class="card" style="grid-column: span 2;">
|
||||
<h2>📦 Git 状态</h2>
|
||||
<div class="git-info">
|
||||
<div>分支: <span class="hash">{g.get('branch', '?')}</span></div>
|
||||
<div>提交数: <span class="hash">{g.get('commit_count', '?')}</span></div>
|
||||
<div>最近提交: <span class="hash">{g.get('last_commit', '?')}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 模块完成度 -->
|
||||
<div class="card" style="grid-column: span 3;">
|
||||
<h2>✅ 阶段完成度</h2>
|
||||
<div style="display: grid; grid-template-columns: repeat(5, 1fr); gap: 12px; margin-top: 12px;">
|
||||
<div style="text-align: center;">
|
||||
<div class="big-number" style="font-size: 1.8em; color: #28a745;">66%</div>
|
||||
<div class="label">阶段 1</div>
|
||||
</div>
|
||||
<div style="text-align: center;">
|
||||
<div class="big-number" style="font-size: 1.8em; color: #ffc107;">0%</div>
|
||||
<div class="label">阶段 2(转人工)</div>
|
||||
</div>
|
||||
<div style="text-align: center;">
|
||||
<div class="big-number" style="font-size: 1.8em; color: #6c757d;">0%</div>
|
||||
<div class="label">阶段 3(H5+WS)</div>
|
||||
</div>
|
||||
<div style="text-align: center;">
|
||||
<div class="big-number" style="font-size: 1.8em; color: #6c757d;">规划中</div>
|
||||
<div class="label">阶段 4(AI Wingman)</div>
|
||||
</div>
|
||||
<div style="text-align: center;">
|
||||
<div class="big-number" style="font-size: 1.8em; color: #6c757d;">规划中</div>
|
||||
<div class="label">阶段 5(自动化)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="text-align: center; color: rgba(255,255,255,0.7); margin-top: 40px; font-size: 0.9em;">
|
||||
企微 IT 智能服务台 · 健康度仪表盘 v1.0
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
OUTPUT.parent.mkdir(parents=True, exist_ok=True)
|
||||
OUTPUT.write_text(html, encoding="utf-8")
|
||||
print(f"✅ 仪表盘已生成: {OUTPUT}")
|
||||
print(f" 打开方式: 直接在浏览器打开 file:///{OUTPUT}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,259 @@
|
||||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# API 文档生成脚本
|
||||
# =============================================================================
|
||||
# 用途: 从 FastAPI 后端自动生成 OpenAPI 规范 + 静态 HTML 文档
|
||||
# 输出:
|
||||
# docs/api/openapi.json - OpenAPI 3.0 规范
|
||||
# docs/api/index.html - Swagger UI 静态版
|
||||
# docs/api/redoc.html - ReDoc 静态版
|
||||
#
|
||||
# 用法:
|
||||
# bash scripts/generate-api-docs.sh # 跑后端拿 OpenAPI
|
||||
# bash scripts/generate-api-docs.sh --from-running # 从运行中后端拿
|
||||
# bash scripts/generate-api-docs.sh --offline # 离线生成(无需后端)
|
||||
# =============================================================================
|
||||
|
||||
set -e
|
||||
|
||||
# 颜色
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
ok() { echo -e "${GREEN}[OK]${NC} $1"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||
error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; }
|
||||
|
||||
PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
API_DOCS_DIR="docs/api"
|
||||
mkdir -p "$API_DOCS_DIR"
|
||||
|
||||
# 参数
|
||||
MODE="auto"
|
||||
for arg in "$@"; do
|
||||
case $arg in
|
||||
--from-running) MODE="running" ;;
|
||||
--offline) MODE="offline" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# =============================================================================
|
||||
# 1. 拿 OpenAPI 规范
|
||||
# =============================================================================
|
||||
info "── 1/3 拿 OpenAPI 规范"
|
||||
|
||||
case $MODE in
|
||||
running|auto)
|
||||
# 先看后端跑没
|
||||
if curl -s -f http://localhost:8000/openapi.json > /tmp/openapi.json 2>/dev/null; then
|
||||
ok "从运行中后端拿 OpenAPI"
|
||||
cp /tmp/openapi.json "$API_DOCS_DIR/openapi.json"
|
||||
elif [ "$MODE" = "running" ]; then
|
||||
error "后端没跑,无法从 running 拿"
|
||||
else
|
||||
warn "后端没跑,改用离线生成"
|
||||
MODE="offline"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$MODE" = "offline" ]; then
|
||||
info "离线生成 OpenAPI(import FastAPI app)..."
|
||||
cd backend
|
||||
if [ ! -d "venv" ]; then
|
||||
warn "后端 venv 不存在,跑: python -m venv venv && pip install -r requirements.txt"
|
||||
fi
|
||||
|
||||
cat > /tmp/gen_openapi.py <<'PYEOF'
|
||||
import json
|
||||
import sys
|
||||
try:
|
||||
from app.main import app
|
||||
spec = app.openapi()
|
||||
print(json.dumps(spec, ensure_ascii=False, indent=2))
|
||||
except Exception as e:
|
||||
print(f"ERROR: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
PYEOF
|
||||
|
||||
if command -v python &> /dev/null; then
|
||||
if python /tmp/gen_openapi.py > "../$API_DOCS_DIR/openapi.json" 2>/dev/null; then
|
||||
ok "离线生成 OpenAPI 成功"
|
||||
else
|
||||
# 试 python3
|
||||
if python3 /tmp/gen_openapi.py > "../$API_DOCS_DIR/openapi.json" 2>/dev/null; then
|
||||
ok "离线生成 OpenAPI 成功(python3)"
|
||||
else
|
||||
warn "离线生成失败,降级到 mock 模式"
|
||||
cat > "../$API_DOCS_DIR/openapi.json" <<'JSONEOF'
|
||||
{
|
||||
"openapi": "3.0.0",
|
||||
"info": {
|
||||
"title": "企微 IT 智能服务台 API",
|
||||
"version": "1.0.0",
|
||||
"description": "离线生成的 mock,实际跑后端再生成"
|
||||
},
|
||||
"paths": {}
|
||||
}
|
||||
JSONEOF
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
cd "$PROJECT_ROOT"
|
||||
fi
|
||||
|
||||
# 验证 OpenAPI
|
||||
if [ ! -f "$API_DOCS_DIR/openapi.json" ]; then
|
||||
error "OpenAPI 规范生成失败"
|
||||
fi
|
||||
|
||||
ENDPOINT_COUNT=$(python -c "import json; d=json.load(open('$API_DOCS_DIR/openapi.json')); print(len(d.get('paths', {})))" 2>/dev/null || echo "?")
|
||||
ok "OpenAPI 规范生成,端点数: $ENDPOINT_COUNT"
|
||||
|
||||
# =============================================================================
|
||||
# 2. 生成 Swagger UI 静态 HTML
|
||||
# =============================================================================
|
||||
info "── 2/3 生成 Swagger UI 静态 HTML"
|
||||
|
||||
cat > "$API_DOCS_DIR/index.html" <<'HTMLEOF'
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>企微 IT 智能服务台 API - Swagger UI</title>
|
||||
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5.10.5/swagger-ui.css">
|
||||
<style>
|
||||
body { margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; }
|
||||
.topbar { background: #2c3e50; color: white; padding: 12px 24px; }
|
||||
.topbar h1 { margin: 0; font-size: 20px; }
|
||||
.topbar a { color: #3498db; text-decoration: none; margin-left: 16px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="topbar">
|
||||
<h1>📡 企微 IT 智能服务台 API 文档</h1>
|
||||
<a href="redoc.html">📖 ReDoc 版</a>
|
||||
<a href="openapi.json">📄 OpenAPI 规范</a>
|
||||
</div>
|
||||
<div id="swagger-ui"></div>
|
||||
<script src="https://unpkg.com/swagger-ui-dist@5.10.5/swagger-ui-bundle.js"></script>
|
||||
<script>
|
||||
window.onload = () => {
|
||||
window.ui = SwaggerUIBundle({
|
||||
url: "openapi.json",
|
||||
dom_id: "#swagger-ui",
|
||||
deepLinking: true,
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis
|
||||
],
|
||||
layout: "BaseLayout"
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
HTMLEOF
|
||||
|
||||
ok "Swagger UI 生成: $API_DOCS_DIR/index.html"
|
||||
|
||||
# =============================================================================
|
||||
# 3. 生成 ReDoc 静态 HTML
|
||||
# =============================================================================
|
||||
info "── 3/3 生成 ReDoc 静态 HTML"
|
||||
|
||||
cat > "$API_DOCS_DIR/redoc.html" <<'HTMLEOF'
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>企微 IT 智能服务台 API - ReDoc</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
|
||||
<style>
|
||||
body { margin: 0; padding: 0; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<redoc spec-url="openapi.json"></redoc>
|
||||
<script src="https://unpkg.com/redoc@2.0.0/bundles/redoc.standalone.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
HTMLEOF
|
||||
|
||||
ok "ReDoc 生成: $API_DOCS_DIR/redoc.html"
|
||||
|
||||
# =============================================================================
|
||||
# 4. 生成 API 模块清单
|
||||
# =============================================================================
|
||||
info "── 4/4 生成模块清单"
|
||||
|
||||
python3 -c "
|
||||
import json
|
||||
with open('$API_DOCS_DIR/openapi.json') as f:
|
||||
spec = json.load(f)
|
||||
paths = spec.get('paths', {})
|
||||
modules = {}
|
||||
for path, methods in paths.items():
|
||||
# 解析 /api/v1/<module>/<endpoint>
|
||||
parts = path.split('/')
|
||||
if len(parts) >= 4 and parts[1] == 'api' and parts[2] == 'v1':
|
||||
module = parts[3]
|
||||
if module not in modules:
|
||||
modules[module] = []
|
||||
for method in methods.keys():
|
||||
if method in ['get', 'post', 'put', 'delete', 'patch']:
|
||||
modules[module].append({
|
||||
'method': method.upper(),
|
||||
'path': path,
|
||||
})
|
||||
|
||||
print('# API 模块清单')
|
||||
print()
|
||||
print('**生成日期**: $(date +%Y-%m-%d)')
|
||||
print('**端点总数**: ', len(paths))
|
||||
print('**模块数**: ', len(modules))
|
||||
print()
|
||||
print('| 模块 | 端点数 | 端点 |')
|
||||
print('|---|---|---|')
|
||||
for module, endpoints in sorted(modules.items()):
|
||||
eps = ', '.join(f\"{e['method']} {e['path']}\" for e in endpoints[:5])
|
||||
if len(endpoints) > 5:
|
||||
eps += f' ... (+{len(endpoints)-5})'
|
||||
print(f\"| {module} | {len(endpoints)} | {eps} |\")
|
||||
" > "$API_DOCS_DIR/MODULES.md" 2>/dev/null || {
|
||||
warn "模块清单生成失败(Python 解析)"
|
||||
cat > "$API_DOCS_DIR/MODULES.md" <<'EOF'
|
||||
# API 模块清单
|
||||
|
||||
(生成失败,见 docs/api/openapi.json 自行查看)
|
||||
EOF
|
||||
}
|
||||
|
||||
ok "模块清单生成: $API_DOCS_DIR/MODULES.md"
|
||||
|
||||
# =============================================================================
|
||||
# 总结
|
||||
# =============================================================================
|
||||
info "── 总结"
|
||||
echo ""
|
||||
echo "输出文件:"
|
||||
echo " $API_DOCS_DIR/openapi.json - OpenAPI 3.0 规范"
|
||||
echo " $API_DOCS_DIR/index.html - Swagger UI 静态版"
|
||||
echo " $API_DOCS_DIR/redoc.html - ReDoc 静态版"
|
||||
echo " $API_DOCS_DIR/MODULES.md - 模块清单"
|
||||
echo ""
|
||||
echo "查看方式:"
|
||||
echo " 1. 浏览器打开 file://\$(pwd)/$API_DOCS_DIR/index.html"
|
||||
echo " 2. 跑 python -m http.server -d $API_DOCS_DIR 8080 → 浏览器 http://localhost:8080"
|
||||
echo ""
|
||||
echo "CI 集成:"
|
||||
echo " 把 'bash scripts/generate-api-docs.sh' 加进 Gitea Actions"
|
||||
echo " 跑批频率:每次 main 推送后"
|
||||
|
||||
ok "API 文档生成完成"
|
||||
@@ -0,0 +1,207 @@
|
||||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# 🎁 惊喜 3: 一键部署脚本
|
||||
# =============================================================================
|
||||
# 用途: 一键构建 + 部署整个服务台(开发/生产双模式)
|
||||
# 用法:
|
||||
# bash scripts/oneclick-deploy.sh dev # 本地开发
|
||||
# bash scripts/oneclick-deploy.sh prod # 生产部署
|
||||
# bash scripts/oneclick-deploy.sh prod nas # 生产部署到 NAS
|
||||
# bash scripts/oneclick-deploy.sh prod server # 生产部署到公司服务器
|
||||
# =============================================================================
|
||||
|
||||
set -e
|
||||
|
||||
# 颜色
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
PURPLE='\033[0;35m'
|
||||
NC='\033[0m'
|
||||
|
||||
info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
ok() { echo -e "${GREEN}[OK]${NC} $1"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||
error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; }
|
||||
step() { echo -e "\n${PURPLE}━━━ $1 ━━━${NC}"; }
|
||||
|
||||
PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
# 参数
|
||||
MODE="${1:-dev}"
|
||||
TARGET="${2:-local}"
|
||||
|
||||
# =============================================================================
|
||||
# 0. 前置检查
|
||||
# =============================================================================
|
||||
step "0/6 前置检查"
|
||||
|
||||
info "检查 Docker..."
|
||||
if ! command -v docker &> /dev/null; then
|
||||
error "Docker 未安装,请先装 Docker Desktop / Docker Engine"
|
||||
fi
|
||||
ok "Docker: $(docker --version)"
|
||||
|
||||
info "检查 Docker Compose..."
|
||||
if ! command -v docker-compose &> /dev/null && ! docker compose version &> /dev/null; then
|
||||
error "Docker Compose 未安装"
|
||||
fi
|
||||
ok "Docker Compose: $(docker compose version 2>/dev/null || docker-compose --version)"
|
||||
|
||||
info "检查磁盘空间..."
|
||||
DISK_FREE=$(df -BG . | tail -1 | awk '{print $4}' | sed 's/G//')
|
||||
if [ "$DISK_FREE" -lt 5 ]; then
|
||||
warn "可用空间 < 5GB,建议清理"
|
||||
fi
|
||||
ok "可用空间: ${DISK_FREE}G"
|
||||
|
||||
# =============================================================================
|
||||
# 1. 环境配置
|
||||
# =============================================================================
|
||||
step "1/6 环境配置"
|
||||
|
||||
case "$MODE" in
|
||||
dev)
|
||||
info "模式: 开发环境"
|
||||
COMPOSE_FILE="docker-compose.yml"
|
||||
ENV_FILE="backend/.env"
|
||||
;;
|
||||
prod)
|
||||
info "模式: 生产环境"
|
||||
COMPOSE_FILE="docker-compose.yml"
|
||||
ENV_FILE="backend/.env"
|
||||
warn "生产部署前请确认 .env 凭据已改"
|
||||
;;
|
||||
*)
|
||||
error "未知模式: $MODE (支持: dev / prod)"
|
||||
;;
|
||||
esac
|
||||
|
||||
# 加载 .env
|
||||
if [ -f "$ENV_FILE" ]; then
|
||||
set -a
|
||||
# shellcheck disable=SC1090
|
||||
source "$ENV_FILE"
|
||||
set +a
|
||||
ok "已加载: $ENV_FILE"
|
||||
else
|
||||
warn "$ENV_FILE 不存在,用默认"
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# 2. 代码准备
|
||||
# =============================================================================
|
||||
step "2/6 代码准备"
|
||||
|
||||
info "拉取最新代码..."
|
||||
if [ -d .git ]; then
|
||||
git fetch origin 2>/dev/null || warn "无法 fetch(可能离线)"
|
||||
git pull --rebase 2>/dev/null || warn "无法 pull,继续"
|
||||
ok "代码已更新"
|
||||
else
|
||||
warn "非 Git 仓库,跳过"
|
||||
fi
|
||||
|
||||
info "复制环境变量模板..."
|
||||
for env in .env.example backend/.env.example; do
|
||||
if [ -f "$env" ] && [ ! -f "${env%.example}" ]; then
|
||||
cp "$env" "${env%.example}"
|
||||
warn "已复制 $env -> ${env%.example}(请编辑填入真实凭据)"
|
||||
fi
|
||||
done
|
||||
|
||||
# =============================================================================
|
||||
# 3. 镜像构建
|
||||
# =============================================================================
|
||||
step "3/6 镜像构建"
|
||||
|
||||
info "构建 4 个服务镜像..."
|
||||
docker compose -f "$COMPOSE_FILE" build --parallel 2>&1 | tail -30
|
||||
ok "镜像构建完成"
|
||||
|
||||
# =============================================================================
|
||||
# 4. 服务启动
|
||||
# =============================================================================
|
||||
step "4/6 服务启动"
|
||||
|
||||
info "启动服务..."
|
||||
docker compose -f "$COMPOSE_FILE" up -d 2>&1 | tail -20
|
||||
|
||||
# 等待后端 ready
|
||||
info "等待后端就绪..."
|
||||
for i in $(seq 1 30); do
|
||||
if curl -sf http://localhost:8000/health > /dev/null 2>&1; then
|
||||
ok "后端就绪 (用时 ${i}s)"
|
||||
break
|
||||
fi
|
||||
if [ $i -eq 30 ]; then
|
||||
error "后端 30s 内未就绪,跑: docker compose logs backend"
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# =============================================================================
|
||||
# 5. 健康验证
|
||||
# =============================================================================
|
||||
step "5/6 健康验证"
|
||||
|
||||
info "检查服务状态..."
|
||||
docker compose -f "$COMPOSE_FILE" ps
|
||||
|
||||
# 6 端检查
|
||||
SERVICES=("backend" "postgres" "redis" "nginx" "frontend-admin" "frontend-agent" "frontend-h5" "frontend-portal")
|
||||
for svc in "${SERVICES[@]}"; do
|
||||
if docker compose -f "$COMPOSE_FILE" ps "$svc" 2>/dev/null | grep -q "Up"; then
|
||||
ok "$svc: 运行中"
|
||||
else
|
||||
warn "$svc: 未运行"
|
||||
fi
|
||||
done
|
||||
|
||||
# 健康端点
|
||||
info "健康端点测试..."
|
||||
HEALTH_URLS=(
|
||||
"http://localhost:8000/health"
|
||||
"http://localhost/itdesk"
|
||||
"http://localhost/itagent"
|
||||
"http://localhost/itadmin"
|
||||
"http://localhost/itportal"
|
||||
)
|
||||
for url in "${HEALTH_URLS[@]}"; do
|
||||
if curl -sf -o /dev/null "$url"; then
|
||||
ok "$url ✅"
|
||||
else
|
||||
warn "$url ❌"
|
||||
fi
|
||||
done
|
||||
|
||||
# =============================================================================
|
||||
# 6. 总结
|
||||
# =============================================================================
|
||||
step "6/6 部署完成"
|
||||
|
||||
echo ""
|
||||
echo "🎉 一键部署完成!"
|
||||
echo ""
|
||||
echo "服务地址:"
|
||||
echo " 📱 H5 员工端: http://localhost/itdesk/"
|
||||
echo " 👤 坐席工作台: http://localhost/itagent/"
|
||||
echo " ⚙️ 管理后台: http://localhost/itadmin/"
|
||||
echo " 🌐 统一入口: http://localhost/itportal/"
|
||||
echo " 🔌 API: http://localhost/api/"
|
||||
echo ""
|
||||
echo "运维命令:"
|
||||
echo " 查看日志: docker compose logs -f [service]"
|
||||
echo " 重启服务: docker compose restart [service]"
|
||||
echo " 停止: docker compose down"
|
||||
echo " 完全清理: docker compose down -v"
|
||||
echo ""
|
||||
echo "后续:"
|
||||
echo " 1. 跑安全审计: bash scripts/security-audit.sh"
|
||||
echo " 2. 跑健康仪表盘: python scripts/dashboard.py"
|
||||
echo " 3. 跑 API 文档: bash scripts/generate-api-docs.sh"
|
||||
echo ""
|
||||
|
||||
ok "🎁 一键部署成功"
|
||||
@@ -0,0 +1,342 @@
|
||||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# 安全审计脚本
|
||||
# =============================================================================
|
||||
# 用途: 跑 5 大安全工具,生成审计报告
|
||||
# 1. bandit - Python 代码静态分析
|
||||
# 2. safety - Python 依赖漏洞
|
||||
# 3. pip-audit - Python 依赖漏洞(更准)
|
||||
# 4. npm audit - JS 依赖漏洞
|
||||
# 5. gitleaks - 仓库 secret 扫描
|
||||
#
|
||||
# 用法:
|
||||
# bash scripts/security-audit.sh # 跑全部
|
||||
# bash scripts/security-audit.sh --python # 只跑 Python 套件
|
||||
# bash scripts/security-audit.sh --js # 只跑 JS 套件
|
||||
# bash scripts/security-audit.sh --secrets # 只跑 secret 扫描
|
||||
# bash scripts/security-audit.sh --output FILE # 自定义报告路径
|
||||
#
|
||||
# 退出码:
|
||||
# 0 = 全过 / 仅 INFO
|
||||
# 1 = 有 LOW
|
||||
# 2 = 有 MEDIUM
|
||||
# 3 = 有 HIGH/CRITICAL
|
||||
# =============================================================================
|
||||
|
||||
set -e
|
||||
|
||||
# 颜色
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
ok() { echo -e "${GREEN}[OK]${NC} $1"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||
error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
|
||||
# 路径
|
||||
PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$PROJECT_ROOT"
|
||||
REPORT="docs/审计报告/security_audit_$(date +%Y%m%d).md"
|
||||
LOG_DIR="/tmp/security-audit-$(date +%Y%m%d-%H%M%S)"
|
||||
mkdir -p "$LOG_DIR" "$(dirname "$REPORT")"
|
||||
|
||||
# 参数
|
||||
RUN_PYTHON=true
|
||||
RUN_JS=true
|
||||
RUN_SECRETS=true
|
||||
for arg in "$@"; do
|
||||
case $arg in
|
||||
--python) RUN_PYTHON=true; RUN_JS=false; RUN_SECRETS=false ;;
|
||||
--js) RUN_PYTHON=false; RUN_JS=true; RUN_SECRETS=false ;;
|
||||
--secrets) RUN_PYTHON=false; RUN_JS=false; RUN_SECRETS=true ;;
|
||||
--output) REPORT="$2" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# 计数器
|
||||
PASS=0
|
||||
WARN=0
|
||||
FAIL=0
|
||||
CRITICAL=0
|
||||
|
||||
# 报告头
|
||||
cat > "$REPORT" <<EOF
|
||||
# 安全审计报告
|
||||
|
||||
**审计日期**: $(date +%Y-%m-%d)
|
||||
**审计人**: Claude(自动化跑批)
|
||||
**工具**: bandit / safety / pip-audit / npm audit / gitleaks
|
||||
**关联**: [[风险跟踪表]]
|
||||
|
||||
---
|
||||
|
||||
## 1. 跑批概览
|
||||
|
||||
| 工具 | 范围 | 结果 |
|
||||
|---|---|---|
|
||||
EOF
|
||||
|
||||
# =============================================================================
|
||||
# 1. bandit (Python 静态分析)
|
||||
# =============================================================================
|
||||
if [ "$RUN_PYTHON" = true ]; then
|
||||
info "── 1/5 bandit: Python 静态分析"
|
||||
|
||||
if ! command -v bandit &> /dev/null; then
|
||||
warn "bandit 未安装,跑: pip install bandit"
|
||||
echo "| bandit | Python 静态 | ⚠️ 工具未安装 |" >> "$REPORT"
|
||||
else
|
||||
if bandit -r backend/ -f json -o "$LOG_DIR/bandit.json" 2> "$LOG_DIR/bandit.err" ; then
|
||||
ok "bandit: 无问题"
|
||||
PASS=$((PASS+1))
|
||||
echo "| bandit | Python 静态 | ✅ 无问题 |" >> "$REPORT"
|
||||
else
|
||||
# 解析 bandit JSON 报告
|
||||
if command -v jq &> /dev/null; then
|
||||
HIGH=$(jq '[.results[] | select(.issue_severity=="HIGH")] | length' "$LOG_DIR/bandit.json" 2>/dev/null || echo 0)
|
||||
MED=$(jq '[.results[] | select(.issue_severity=="MEDIUM")] | length' "$LOG_DIR/bandit.json" 2>/dev/null || echo 0)
|
||||
LOW=$(jq '[.results[] | select(.issue_severity=="LOW")] | length' "$LOG_DIR/bandit.json" 2>/dev/null || echo 0)
|
||||
else
|
||||
HIGH=0; MED=0; LOW=0
|
||||
fi
|
||||
warn "bandit: HIGH=$HIGH MED=$MED LOW=$LOW"
|
||||
[ $HIGH -gt 0 ] && FAIL=$((FAIL+HIGH)) || true
|
||||
[ $MED -gt 0 ] && WARN=$((WARN+MED)) || true
|
||||
[ $LOW -gt 0 ] && WARN=$((WARN+LOW)) || true
|
||||
echo "| bandit | Python 静态 | ⚠️ HIGH=$HIGH MED=$MED LOW=$LOW |" >> "$REPORT"
|
||||
|
||||
# 列出问题
|
||||
if [ $HIGH -gt 0 ] || [ $MED -gt 0 ]; then
|
||||
cat >> "$REPORT" <<EOR
|
||||
|
||||
### bandit 详情
|
||||
|
||||
| 文件 | 行 | 严重度 | 问题 |
|
||||
|---|---|---|---|
|
||||
EOR
|
||||
jq -r '.results[] | "| \(.filename) | \(.line_number) | \(.issue_severity) | \(.issue_text | gsub("\n"; " ")) |"' "$LOG_DIR/bandit.json" >> "$REPORT" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# 2. safety (Python 依赖漏洞)
|
||||
# =============================================================================
|
||||
if [ "$RUN_PYTHON" = true ]; then
|
||||
info "── 2/5 safety: Python 依赖漏洞"
|
||||
|
||||
if ! command -v safety &> /dev/null; then
|
||||
warn "safety 未安装,跑: pip install safety"
|
||||
echo "| safety | Python 依赖 | ⚠️ 工具未安装 |" >> "$REPORT"
|
||||
else
|
||||
if safety check --file=backend/requirements.txt --output=text > "$LOG_DIR/safety.txt" 2>&1; then
|
||||
ok "safety: 无漏洞"
|
||||
PASS=$((PASS+1))
|
||||
echo "| safety | Python 依赖 | ✅ 无漏洞 |" >> "$REPORT"
|
||||
else
|
||||
VULN_COUNT=$(grep -c "VULNERABLE" "$LOG_DIR/safety.txt" 2>/dev/null || echo 0)
|
||||
warn "safety: $VULN_COUNT 个漏洞"
|
||||
[ $VULN_COUNT -gt 0 ] && FAIL=$((FAIL+VULN_COUNT)) || true
|
||||
echo "| safety | Python 依赖 | 🔴 $VULN_COUNT 个漏洞 |" >> "$REPORT"
|
||||
cat >> "$REPORT" <<EOR
|
||||
|
||||
### safety 详情
|
||||
|
||||
\`\`\`
|
||||
$(cat "$LOG_DIR/safety.txt" | head -30)
|
||||
\`\`\`
|
||||
EOR
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# 3. pip-audit (Python 依赖漏洞,更准)
|
||||
# =============================================================================
|
||||
if [ "$RUN_PYTHON" = true ]; then
|
||||
info "── 3/5 pip-audit: Python 依赖漏洞(精确)"
|
||||
|
||||
if ! command -v pip-audit &> /dev/null; then
|
||||
warn "pip-audit 未安装,跑: pip install pip-audit"
|
||||
echo "| pip-audit | Python 依赖 | ⚠️ 工具未安装 |" >> "$REPORT"
|
||||
else
|
||||
if pip-audit -r backend/requirements.txt --format=json > "$LOG_DIR/pip-audit.json" 2>&1; then
|
||||
ok "pip-audit: 无漏洞"
|
||||
PASS=$((PASS+1))
|
||||
echo "| pip-audit | Python 依赖 | ✅ 无漏洞 |" >> "$REPORT"
|
||||
else
|
||||
VULN_COUNT=$(python3 -c "import json; d=json.load(open('$LOG_DIR/pip-audit.json')); print(len(d.get('vulnerabilities', [])))" 2>/dev/null || echo 0)
|
||||
warn "pip-audit: $VULN_COUNT 个漏洞"
|
||||
[ $VULN_COUNT -gt 0 ] && FAIL=$((FAIL+VULN_COUNT)) || true
|
||||
echo "| pip-audit | Python 依赖 | 🔴 $VULN_COUNT 个漏洞 |" >> "$REPORT"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# 4. npm audit (JS 依赖漏洞)
|
||||
# =============================================================================
|
||||
if [ "$RUN_JS" = true ]; then
|
||||
info "── 4/5 npm audit: JS 依赖漏洞"
|
||||
|
||||
for d in frontend-admin frontend-agent frontend-h5 frontend-portal; do
|
||||
if [ -d "$d" ]; then
|
||||
info " → $d"
|
||||
if [ -f "$d/package-lock.json" ]; then
|
||||
cd "$d"
|
||||
if npm audit --json > "$LOG_DIR/npm-$d.json" 2>&1; then
|
||||
ok " $d: 无漏洞"
|
||||
PASS=$((PASS+1))
|
||||
else
|
||||
VULN=$(python3 -c "import json; d=json.load(open('$LOG_DIR/npm-$d.json')); m=d.get('metadata',{}).get('vulnerabilities',{}); print(m.get('total', 0))" 2>/dev/null || echo 0)
|
||||
CRIT=$(python3 -c "import json; d=json.load(open('$LOG_DIR/npm-$d.json')); m=d.get('metadata',{}).get('vulnerabilities',{}); print(m.get('critical', 0))" 2>/dev/null || echo 0)
|
||||
HIGH=$(python3 -c "import json; d=json.load(open('$LOG_DIR/npm-$d.json')); m=d.get('metadata',{}).get('vulnerabilities',{}); print(m.get('high', 0))" 2>/dev/null || echo 0)
|
||||
warn " $d: total=$VULN critical=$CRIT high=$HIGH"
|
||||
[ $CRIT -gt 0 ] && CRITICAL=$((CRITICAL+CRIT)) || true
|
||||
[ $HIGH -gt 0 ] && FAIL=$((FAIL+HIGH)) || true
|
||||
[ $VULN -gt 0 ] && WARN=$((WARN+VULN)) || true
|
||||
echo "| npm-audit-$d | JS 依赖 | ⚠️ total=$VULN crit=$CRIT high=$HIGH |" >> "$REPORT"
|
||||
fi
|
||||
cd "$PROJECT_ROOT"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# 5. gitleaks (Secret 扫描)
|
||||
# =============================================================================
|
||||
if [ "$RUN_SECRETS" = true ]; then
|
||||
info "── 5/5 gitleaks: Secret 扫描"
|
||||
|
||||
if ! command -v gitleaks &> /dev/null; then
|
||||
warn "gitleaks 未安装,跑(可选):"
|
||||
echo " brew install gitleaks # Mac"
|
||||
echo " scoop install gitleaks # Windows"
|
||||
echo " docker run -v \$(pwd):/repo zricethezav/gitleaks:latest detect --source /repo --no-git -v"
|
||||
echo "| gitleaks | Secret 扫描 | ⚠️ 工具未安装 |" >> "$REPORT"
|
||||
else
|
||||
if gitleaks detect --source . --no-git -v > "$LOG_DIR/gitleaks.txt" 2>&1; then
|
||||
ok "gitleaks: 无 secret 泄露"
|
||||
PASS=$((PASS+1))
|
||||
echo "| gitleaks | Secret 扫描 | ✅ 无泄露 |" >> "$REPORT"
|
||||
else
|
||||
LEAK_COUNT=$(grep -c "Finding:" "$LOG_DIR/gitleaks.txt" 2>/dev/null || echo 0)
|
||||
if [ "$LEAK_COUNT" -gt 0 ]; then
|
||||
warn "gitleaks: 发现 $LEAK_COUNT 个 secret"
|
||||
CRITICAL=$((CRITICAL+LEAK_COUNT))
|
||||
echo "| gitleaks | Secret 扫描 | 🔴 $LEAK_COUNT 个 secret |" >> "$REPORT"
|
||||
|
||||
cat >> "$REPORT" <<EOR
|
||||
|
||||
### gitleaks 详情
|
||||
|
||||
\`\`\`
|
||||
$(head -50 "$LOG_DIR/gitleaks.txt")
|
||||
\`\`\`
|
||||
|
||||
> 🚨 **CRITICAL**:发现 secret 泄露,立即:
|
||||
> 1. 撤销泄露的 token / 密钥
|
||||
> 2. 创新凭据
|
||||
> 3. 加进 .gitignore(防二次泄露)
|
||||
> 4. 改所有引用
|
||||
EOR
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# 总结
|
||||
# =============================================================================
|
||||
cat >> "$REPORT" <<EOF
|
||||
|
||||
---
|
||||
|
||||
## 2. 总结
|
||||
|
||||
| 等级 | 数量 |
|
||||
|---|---|
|
||||
| ✅ PASS | $PASS |
|
||||
| ⚠️ WARN | $WARN |
|
||||
| 🔴 FAIL | $FAIL |
|
||||
| 🚨 CRITICAL | $CRITICAL |
|
||||
|
||||
EOF
|
||||
|
||||
if [ $CRITICAL -gt 0 ]; then
|
||||
cat >> "$REPORT" <<EOF
|
||||
## 🚨 阻断
|
||||
|
||||
发现 **$CRITICAL** 个 CRITICAL 问题,**必须**:
|
||||
1. 撤销所有泄露的 secret(token / 密钥 / 凭据)
|
||||
2. 创新凭据 + 配新引用
|
||||
3. 加进 .gitignore
|
||||
4. 评审所有引用 + 改
|
||||
|
||||
## 下一步
|
||||
|
||||
1. 修所有 CRITICAL(立即)
|
||||
2. 修所有 FAIL(本周末)
|
||||
3. 评估 WARN(下迭代)
|
||||
4. 加 CI 自动化跑(本季度)
|
||||
EOF
|
||||
echo ""
|
||||
error "🚨 CRITICAL: $CRITICAL 个 secret 泄露或 CRITICAL 漏洞,必须立即处理"
|
||||
exit 3
|
||||
fi
|
||||
|
||||
if [ $FAIL -gt 0 ]; then
|
||||
cat >> "$REPORT" <<EOF
|
||||
## 🛑 FAIL
|
||||
|
||||
发现 **$FAIL** 个 FAIL(HIGH)级问题,本周末修。
|
||||
|
||||
## 下一步
|
||||
|
||||
1. 修 FAIL(本周末)
|
||||
2. 评估 WARN(下迭代)
|
||||
3. 加 CI 自动化跑
|
||||
EOF
|
||||
echo ""
|
||||
warn "🛑 FAIL: $FAIL 个 HIGH 级问题,本周末修"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [ $WARN -gt 0 ]; then
|
||||
cat >> "$REPORT" <<EOF
|
||||
## ⚠️ WARN
|
||||
|
||||
发现 **$WARN** 个 MEDIUM/LOW 级问题,下迭代评估。
|
||||
|
||||
## 下一步
|
||||
|
||||
1. 评估 WARN(下迭代)
|
||||
2. 加 CI 自动化跑
|
||||
3. 跑批频率:每周一次
|
||||
EOF
|
||||
echo ""
|
||||
warn "⚠️ WARN: $WARN 个问题,下迭代评估"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cat >> "$REPORT" <<EOF
|
||||
## ✅ 全部通过
|
||||
|
||||
无 CRITICAL / FAIL / WARN,健康度 100%。
|
||||
|
||||
## 后续
|
||||
|
||||
1. 加 CI 自动化跑(每周 + 推送触发)
|
||||
2. 跑批频率:每周一次 + 重大变更后
|
||||
EOF
|
||||
|
||||
echo ""
|
||||
ok "✅ 全部通过,健康度 100%"
|
||||
ok "报告: $REPORT"
|
||||
exit 0
|
||||
Reference in New Issue
Block a user