增加swagger的离线模式

This commit is contained in:
zhangxunhui 2025-12-10 10:25:52 +08:00
parent 3eaf290b6d
commit 2b70de0e92
4 changed files with 201 additions and 3 deletions

4
.gitignore vendored
View File

@ -127,3 +127,7 @@ models/
# docker export
docker-images-export/
# Swagger UI static assets (keep these for offline use)
# Uncomment the line below if you don't want to commit downloaded assets
# static/swagger-ui/

View File

@ -43,7 +43,11 @@ RUN pip install --upgrade pip && \
COPY . /app/
# 创建必要的目录
RUN mkdir -p /app/chroma_db /app/logs
RUN mkdir -p /app/chroma_db /app/logs /app/static/swagger-ui
# 可选:如果在构建时有网络连接,可以下载 Swagger UI 资源
# 取消下面的注释以在构建时自动下载资源(需要网络连接)
# RUN python download_swagger_assets.py || echo "Warning: Failed to download Swagger UI assets. Run download_swagger_assets.py manually if needed."
# 暴露端口(注意:使用 host 网络模式时EXPOSE 仅作文档说明)
EXPOSE 8001

View File

@ -10,7 +10,9 @@ warnings.filterwarnings("ignore", message=".*pkg_resources is deprecated.*", cat
from contextlib import asynccontextmanager
from pathlib import Path
from fastapi import FastAPI, HTTPException, UploadFile, File, Form
from fastapi.responses import StreamingResponse
from fastapi.responses import StreamingResponse, Response
from fastapi.staticfiles import StaticFiles
from fastapi.openapi.docs import get_swagger_ui_html, get_redoc_html
from pydantic import BaseModel, Field
from typing import Optional, List
from loguru import logger
@ -170,14 +172,105 @@ async def lifespan(app: FastAPI):
logger.info("RAG services shut down")
# Check if static assets are available for offline use
STATIC_DIR = Path(__file__).parent.parent / "static" / "swagger-ui"
SWAGGER_UI_BUNDLE = STATIC_DIR / "swagger-ui-bundle.js"
SWAGGER_UI_CSS = STATIC_DIR / "swagger-ui.css"
REDOC_BUNDLE = STATIC_DIR / "redoc.standalone.js"
# Determine if we should use local assets or CDN
USE_LOCAL_ASSETS = (
SWAGGER_UI_BUNDLE.exists() and
SWAGGER_UI_CSS.exists() and
REDOC_BUNDLE.exists()
)
if USE_LOCAL_ASSETS:
logger.info("Using local Swagger UI assets for offline mode")
else:
logger.warning(
"Local Swagger UI assets not found. Swagger UI will use CDN resources. "
"For offline use, run: python download_swagger_assets.py"
)
# Initialize FastAPI app
app = FastAPI(
title=settings.API_TITLE,
version=settings.API_VERSION,
description="RAG API for local knowledge base retrieval and generation",
lifespan=lifespan
lifespan=lifespan,
swagger_ui_parameters={
"persistAuthorization": True,
},
)
# Mount static files directory if it exists
if STATIC_DIR.exists():
app.mount("/static/swagger-ui", StaticFiles(directory=str(STATIC_DIR)), name="swagger-ui-static")
# Add favicon route to prevent 404 errors
@app.get("/favicon.ico", include_in_schema=False)
async def favicon():
"""Return empty favicon to prevent 404 errors"""
# Return a minimal 1x1 transparent PNG
# This prevents browser from requesting favicon and getting 404
return Response(
content=b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1f\x15\xc4\x89\x00\x00\x00\nIDATx\x9cc\x00\x01\x00\x00\x05\x00\x01\r\n-\xdb\x00\x00\x00\x00IEND\xaeB`\x82',
media_type="image/png"
)
# Override Swagger UI to use local assets when available
@app.get("/docs", include_in_schema=False)
async def custom_swagger_ui_html():
"""Custom Swagger UI that uses local assets in offline mode"""
if USE_LOCAL_ASSETS:
# Use local static files
return get_swagger_ui_html(
openapi_url=app.openapi_url,
title=app.title + " - Swagger UI",
swagger_js_url="/static/swagger-ui/swagger-ui-bundle.js",
swagger_css_url="/static/swagger-ui/swagger-ui.css",
swagger_favicon_url="/favicon.ico",
oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url,
init_oauth=app.swagger_ui_init_oauth,
swagger_ui_parameters=app.swagger_ui_parameters,
)
else:
# Fallback to default (CDN)
return get_swagger_ui_html(
openapi_url=app.openapi_url,
title=app.title + " - Swagger UI",
swagger_favicon_url="/favicon.ico",
oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url,
init_oauth=app.swagger_ui_init_oauth,
swagger_ui_parameters=app.swagger_ui_parameters,
)
# Override ReDoc to use local assets when available
@app.get("/redoc", include_in_schema=False)
async def custom_redoc_html():
"""Custom ReDoc that uses local assets in offline mode"""
if USE_LOCAL_ASSETS:
# Use local static files
return get_redoc_html(
openapi_url=app.openapi_url,
title=app.title + " - ReDoc",
redoc_js_url="/static/swagger-ui/redoc.standalone.js",
redoc_favicon_url="/favicon.ico",
with_google_fonts=False, # Disable Google Fonts for offline use
)
else:
# Fallback to default (CDN)
return get_redoc_html(
openapi_url=app.openapi_url,
title=app.title + " - ReDoc",
redoc_favicon_url="/favicon.ico",
with_google_fonts=True,
)
# Request/Response models
class QueryRequest(BaseModel):

97
download_swagger_assets.py Executable file
View File

@ -0,0 +1,97 @@
#!/usr/bin/env python3
"""
Download Swagger UI and ReDoc static assets for offline use
Run this script once in an online environment to download necessary assets
"""
import os
import urllib.request
import json
from pathlib import Path
# Swagger UI version and assets
SWAGGER_UI_VERSION = "5.9.0"
SWAGGER_UI_ASSETS = {
"swagger-ui-bundle.js": f"https://cdn.jsdelivr.net/npm/swagger-ui-dist@{SWAGGER_UI_VERSION}/swagger-ui-bundle.js",
"swagger-ui-standalone-preset.js": f"https://cdn.jsdelivr.net/npm/swagger-ui-dist@{SWAGGER_UI_VERSION}/swagger-ui-standalone-preset.js",
"swagger-ui.css": f"https://cdn.jsdelivr.net/npm/swagger-ui-dist@{SWAGGER_UI_VERSION}/swagger-ui.css",
}
# ReDoc version and assets
REDOC_VERSION = "2.1.3"
REDOC_ASSETS = {
"redoc.standalone.js": f"https://cdn.jsdelivr.net/npm/redoc@{REDOC_VERSION}/bundles/redoc.standalone.js",
}
# Output directory
STATIC_DIR = Path(__file__).parent / "static" / "swagger-ui"
STATIC_DIR.mkdir(parents=True, exist_ok=True)
def download_file(url: str, filepath: Path):
"""Download a file from URL to filepath"""
print(f"Downloading {filepath.name}...")
try:
urllib.request.urlretrieve(url, filepath)
print(f"✓ Downloaded {filepath.name}")
return True
except Exception as e:
print(f"✗ Failed to download {filepath.name}: {e}")
return False
def main():
"""Download all Swagger UI and ReDoc assets"""
print("=" * 60)
print("Downloading Swagger UI and ReDoc assets for offline use")
print("=" * 60)
print()
success_count = 0
total_count = len(SWAGGER_UI_ASSETS) + len(REDOC_ASSETS)
# Download Swagger UI assets
print("Downloading Swagger UI assets...")
for filename, url in SWAGGER_UI_ASSETS.items():
filepath = STATIC_DIR / filename
if download_file(url, filepath):
success_count += 1
print()
# Download ReDoc assets
print("Downloading ReDoc assets...")
for filename, url in REDOC_ASSETS.items():
filepath = STATIC_DIR / filename
if download_file(url, filepath):
success_count += 1
print()
# Save version info
version_info = {
"swagger_ui_version": SWAGGER_UI_VERSION,
"redoc_version": REDOC_VERSION,
"assets": {
**{f"swagger-ui/{k}": k for k in SWAGGER_UI_ASSETS.keys()},
**{f"swagger-ui/{k}": k for k in REDOC_ASSETS.keys()},
}
}
version_file = STATIC_DIR / "version.json"
with open(version_file, "w") as f:
json.dump(version_info, f, indent=2)
print(f"✓ Saved version info to {version_file}")
print()
print("=" * 60)
if success_count == total_count:
print(f"✓ Successfully downloaded all {total_count} assets")
print(f"Assets saved to: {STATIC_DIR}")
print()
print("You can now use the API in offline mode!")
else:
print(f"⚠️ Downloaded {success_count}/{total_count} assets")
print("Some assets failed to download. Please check your internet connection.")
print("=" * 60)
if __name__ == "__main__":
main()