Files
YG_FT_Platform/request/src/utils/doc_generator.py
DESKTOP-72TV0V4\caoxiaozhu bda8f13446 1. 增加了请求框架
2. 增加了删除虚拟环境的脚本
2026-01-12 14:20:44 +08:00

346 lines
8.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
API文档生成器 - 离线生成Swagger文档
"""
import hashlib
import json
import os
from pathlib import Path
from typing import Optional
from ..utils import get_logger
logger = get_logger(__name__)
# 文档缓存目录
STATIC_DIR = Path("static")
CACHE_FILE = STATIC_DIR / ".openapi_cache"
DOC_FILE = STATIC_DIR / "doc.html"
def get_openapi_hash(openapi_spec: dict) -> str:
"""计算OpenAPI规范的哈希值"""
spec_str = json.dumps(openapi_spec, sort_keys=True, ensure_ascii=False)
return hashlib.md5(spec_str.encode()).hexdigest()
def get_cached_hash() -> Optional[str]:
"""获取缓存的哈希值"""
try:
if CACHE_FILE.exists():
return CACHE_FILE.read_text().strip()
except Exception:
pass
return None
def save_cache_hash(hash_value: str):
"""保存哈希值到缓存"""
try:
CACHE_FILE.write_text(hash_value)
except Exception as e:
logger.warning(f"无法保存文档缓存: {e}")
def generate_doc_html(openapi_spec: dict) -> str:
"""生成文档HTML内容"""
# 将OpenAPI规范内嵌到HTML中
spec_json = json.dumps(openapi_spec, ensure_ascii=False, indent=2)
api_version = openapi_spec.get("info", {}).get("version", "1.0.0")
api_title = openapi_spec.get("info", {}).get("title", "API文档")
html_content = f'''<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{api_title} - 接口文档</title>
<!-- 本地Swagger UI资源 -->
<link rel="stylesheet" href="/vendor/swagger-ui.css">
<script src="/vendor/swagger-ui-bundle.js"></script>
<style>
* {{
margin: 0;
padding: 0;
box-sizing: border-box;
}}
body {{
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background: #f8fafc;
}}
.nav {{
background: white;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
height: 64px;
display: flex;
align-items: center;
padding: 0 24px;
position: sticky;
top: 0;
z-index: 100;
}}
.nav-brand {{
display: flex;
align-items: center;
font-size: 20px;
font-weight: 600;
color: #1f2937;
}}
.nav-brand i {{
color: #3b82f6;
margin-right: 8px;
}}
.nav-links {{
display: flex;
margin-left: 24px;
gap: 8px;
}}
.nav-link {{
padding: 8px 12px;
border-radius: 6px;
text-decoration: none;
font-size: 14px;
color: #6b7280;
transition: all 0.2s;
}}
.nav-link:hover {{
background: rgba(59, 130, 246, 0.1);
color: #3b82f6;
}}
.nav-link.active {{
background: rgba(59, 130, 246, 0.1);
color: #3b82f6;
border-bottom: 2px solid #3b82f6;
}}
.nav-right {{
margin-left: auto;
display: flex;
align-items: center;
gap: 16px;
}}
.api-version {{
font-size: 14px;
color: #6b7280;
}}
.api-version span {{
font-weight: 500;
color: #1f2937;
}}
.main {{
padding: 16px 24px;
}}
.doc-container {{
background: white;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
min-height: calc(100vh - 120px);
}}
/* Swagger UI 样式覆盖 */
.swagger-ui .topbar {{
display: none;
}}
.swagger-ui .info {{
margin: 20px 0;
}}
.swagger-ui .scheme-container {{
background: #f8fafc;
padding: 15px;
box-shadow: none;
}}
/* Font Awesome 图标 (内联) */
.fa {{
display: inline-block;
font-style: normal;
}}
.fa-file-text-o:before {{ content: "📄"; }}
.fa-file-text:before {{ content: "📝"; }}
.fa-book:before {{ content: "📖"; }}
.fa-home:before {{ content: "🏠"; }}
</style>
</head>
<body>
<nav class="nav">
<div class="nav-brand">
<i class="fa fa-file-text-o"></i>
X-Request 管理系统
</div>
<div class="nav-links">
<a href="/log.html" class="nav-link">
<i class="fa fa-file-text"></i> 日志管理
</a>
<a href="/doc.html" class="nav-link active">
<i class="fa fa-book"></i> 接口文档
</a>
<a href="/" class="nav-link">
<i class="fa fa-home"></i> 首页
</a>
</div>
<div class="nav-right">
<div class="api-version">
API版本: <span>{api_version}</span>
</div>
</div>
</nav>
<main class="main">
<div class="doc-container">
<div id="swagger-ui"></div>
</div>
</main>
<script>
// 内嵌的OpenAPI规范
const spec = {spec_json};
window.onload = function() {{
SwaggerUIBundle({{
spec: spec,
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIBundle.SwaggerUIStandalonePreset
],
layout: "BaseLayout",
defaultModelsExpandDepth: 1,
defaultModelExpandDepth: 1,
docExpansion: "list",
filter: true,
showExtensions: true,
showCommonExtensions: true
}});
}};
</script>
</body>
</html>'''
return html_content
def generate_loading_html() -> str:
"""生成加载中的HTML页面"""
return '''<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>生成文档中...</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
background: #f8fafc;
}
.loading {
text-align: center;
}
.spinner {
width: 50px;
height: 50px;
border: 4px solid #e5e7eb;
border-top-color: #3b82f6;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 20px;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
h2 {
color: #1f2937;
margin-bottom: 10px;
}
p {
color: #6b7280;
}
</style>
<meta http-equiv="refresh" content="2">
</head>
<body>
<div class="loading">
<div class="spinner"></div>
<h2>正在生成API文档</h2>
<p>请稍候,页面将自动刷新...</p>
</div>
</body>
</html>'''
def should_regenerate(openapi_spec: dict) -> bool:
"""检查是否需要重新生成文档"""
# 检查文档文件是否存在
if not DOC_FILE.exists():
return True
# 检查本地资源是否存在
vendor_dir = STATIC_DIR / "vendor"
if not (vendor_dir / "swagger-ui.css").exists() or not (vendor_dir / "swagger-ui-bundle.js").exists():
return True
# 比较哈希值
current_hash = get_openapi_hash(openapi_spec)
cached_hash = get_cached_hash()
return current_hash != cached_hash
def generate_docs(app) -> bool:
"""
生成API文档
Args:
app: FastAPI应用实例
Returns:
bool: 是否重新生成了文档
"""
try:
# 获取OpenAPI规范
openapi_spec = app.openapi()
# 检查是否需要重新生成
if not should_regenerate(openapi_spec):
logger.info("API文档未变化使用缓存")
return False
logger.info("检测到API变化正在生成文档...")
# 先写入加载页面
DOC_FILE.write_text(generate_loading_html(), encoding='utf-8')
# 生成文档HTML
doc_html = generate_doc_html(openapi_spec)
# 写入文档文件
DOC_FILE.write_text(doc_html, encoding='utf-8')
# 保存哈希值
current_hash = get_openapi_hash(openapi_spec)
save_cache_hash(current_hash)
logger.info("API文档生成完成")
return True
except Exception as e:
logger.error(f"生成API文档失败: {e}")
return False