feat: 综合平台services多服务支持+SW网络优先缓存修复+适配层/定时任务完善
This commit is contained in:
+53
-10
@@ -8,44 +8,69 @@
|
||||
|
||||
from contextlib import asynccontextmanager
|
||||
from pathlib import Path
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import FileResponse, HTMLResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from starlette.staticfiles import StaticFiles as StarletteStaticFiles
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
from sqlmodel import Session
|
||||
|
||||
from app.core.config import settings
|
||||
from app.core.seed import seed_providers
|
||||
from app.database import assets_engine, init_db
|
||||
from app.routers import agent, assets, monitor, notify, providers, stats, sync
|
||||
from app.routers import agent, assets, monitor, notify, providers, ssl, stats, sync
|
||||
from app.services import cleanup_service
|
||||
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
STATIC_DIR = BASE_DIR / "static"
|
||||
TEMPLATE_DIR = BASE_DIR / "app" / "templates"
|
||||
STATIC_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
def _asset_version() -> str:
|
||||
"""静态资源版本号:取 static 目录内最新文件 mtime,代码更新后自动变更。
|
||||
|
||||
用于前端引用 ?v= 参数,绕开浏览器启发式缓存(旧响应无 Cache-Control
|
||||
时存下的条目会被视为新鲜而不再回源验证)。
|
||||
"""
|
||||
latest = 0
|
||||
for p in STATIC_DIR.rglob("*"):
|
||||
if p.is_file():
|
||||
latest = max(latest, int(p.stat().st_mtime))
|
||||
return str(latest)
|
||||
|
||||
# 全局日志配置:统一格式,便于生产环境排查
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s %(levelname)s [%(name)s] %(message)s",
|
||||
)
|
||||
|
||||
# 直接使用 Jinja2 Environment 渲染(规避 Starlette Jinja2Templates 在 Python 3.14 下的缓存兼容问题)
|
||||
jinja_env = Environment(loader=FileSystemLoader(TEMPLATE_DIR), autoescape=True)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(_: FastAPI):
|
||||
"""应用启动时自动建表并初始化预设平台"""
|
||||
"""应用启动时自动建表、初始化预设平台,并启动监控数据定期清理任务"""
|
||||
init_db()
|
||||
with Session(assets_engine) as session:
|
||||
seed_providers(session)
|
||||
cleanup_task = asyncio.create_task(cleanup_service.cleanup_loop())
|
||||
yield
|
||||
cleanup_task.cancel()
|
||||
|
||||
|
||||
app = FastAPI(title=settings.APP_NAME, version="0.2.0", lifespan=lifespan)
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
allow_origins=[o.strip() for o in settings.CORS_ORIGINS.split(",") if o.strip()],
|
||||
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
||||
allow_headers=["Content-Type", "X-API-Key", "X-Agent-Key"],
|
||||
allow_credentials=True,
|
||||
)
|
||||
|
||||
app.include_router(assets.router)
|
||||
@@ -55,24 +80,42 @@ app.include_router(agent.router)
|
||||
app.include_router(monitor.router)
|
||||
app.include_router(sync.router)
|
||||
app.include_router(notify.router)
|
||||
app.include_router(ssl.router)
|
||||
|
||||
app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
|
||||
# 静态资源:no-cache(每次回源校验 ETag)。避免启发式缓存导致旧版本残留,
|
||||
# 与 SW 的 no-store 回源配合,保证代码更新后立即生效。
|
||||
class NoCacheStaticFiles(StarletteStaticFiles):
|
||||
def file_response(self, *args, **kwargs):
|
||||
resp = super().file_response(*args, **kwargs)
|
||||
resp.headers.setdefault("Cache-Control", "no-cache")
|
||||
return resp
|
||||
|
||||
app.mount("/static", NoCacheStaticFiles(directory=STATIC_DIR), name="static")
|
||||
|
||||
|
||||
@app.get("/", include_in_schema=False)
|
||||
def index() -> HTMLResponse:
|
||||
"""前端 SPA 入口"""
|
||||
html = jinja_env.get_template("index.html").render(app_name=settings.APP_NAME)
|
||||
html = jinja_env.get_template("index.html").render(
|
||||
app_name=settings.APP_NAME, asset_version=_asset_version()
|
||||
)
|
||||
return HTMLResponse(html)
|
||||
|
||||
|
||||
@app.get("/sw.js", include_in_schema=False)
|
||||
def service_worker() -> FileResponse:
|
||||
"""Service Worker(置于根路径以使 scope 覆盖全站)"""
|
||||
"""Service Worker(置于根路径以使 scope 覆盖全站)
|
||||
|
||||
no-cache:保证浏览器每次导航都校验 SW 是否有更新,
|
||||
否则浏览器可能长时间持有旧版 SW(默认更新检查间隔长)。
|
||||
"""
|
||||
return FileResponse(
|
||||
STATIC_DIR / "sw.js",
|
||||
media_type="application/javascript",
|
||||
headers={"Service-Worker-Allowed": "/"},
|
||||
headers={
|
||||
"Service-Worker-Allowed": "/",
|
||||
"Cache-Control": "no-cache",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user