1. 增加了请求框架

2. 增加了删除虚拟环境的脚本
This commit is contained in:
2026-01-12 14:20:44 +08:00
parent 45276a7787
commit bda8f13446
68 changed files with 9067 additions and 1 deletions

View File

@@ -0,0 +1,345 @@
"""
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