93ba41ed79
- 新增 backend/app/api/approval.py 审批API - 前端H5支持发起审批、审批操作 - 添加审批卡片弹窗组件 - 路由注册审批模块
260 lines
8.4 KiB
Bash
260 lines
8.4 KiB
Bash
#!/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 文档生成完成"
|