Files

87 lines
3.0 KiB
Python
Raw Permalink Normal View History

from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from app.config import settings, ensure_dirs, STATIC_DIR
from app.models.database import Base
# 后台推送任务的独立连接写结果可能撞上 SMTP 窗口内持有的写锁,默认 5s
# 忙等上限低于 SMTP 最坏时长,抬到 30s;等待发生在 aiosqlite 工作线程,不阻塞事件循环
engine = create_async_engine(settings.DATABASE_URL, echo=settings.APP_DEBUG,
connect_args={"timeout": 30})
async_session = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
async def _migrate(conn):
result = await conn.execute(text("PRAGMA table_info(conversations)"))
columns = {row[1] for row in result}
if "page_token" not in columns:
await conn.execute(text(
"ALTER TABLE conversations ADD COLUMN page_token VARCHAR(64)"
))
result = await conn.execute(text("PRAGMA table_info(evaluations)"))
columns = {row[1] for row in result}
if "correction_count" not in columns:
await conn.execute(text(
"ALTER TABLE evaluations ADD COLUMN correction_count INTEGER DEFAULT 0"
))
@asynccontextmanager
async def lifespan(app: FastAPI):
ensure_dirs()
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
await _migrate(conn)
# 询盘推送(设计 8.2): 清扫崩溃遗留的 stale pending → failed
# 函数内导入: lead_push_service 顶部 from app.main import async_session,
# 顶层互相导入会循环
from app.services.lead_push_service import sweep_stale_pending
await sweep_stale_pending()
yield
app = FastAPI(title="HESUPIPE AI 客服", lifespan=lifespan)
from app.api.routes.chat import router as chat_router
from app.api.routes.admin import router as admin_router
from app.api.routes.chat_rest import router as chat_rest_router
app.include_router(chat_router)
app.include_router(admin_router)
app.include_router(chat_rest_router)
app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")
@app.middleware("http")
async def static_no_cache(request, call_next):
"""静态资源协商缓存(2026-09-30 线上事件): 无 Cache-Control 时浏览器对
widget.js 走启发式缓存,修复部署后用户端仍跑旧脚本。no-cache=每次用
etag 轻量协商,有更新即时生效,未变则 304 几乎零开销。"""
resp = await call_next(request)
if request.url.path.startswith("/static/"):
resp.headers["Cache-Control"] = "no-cache"
return resp
@app.get("/")
async def index():
from fastapi.responses import FileResponse
return FileResponse(str(STATIC_DIR / "chat.html"))
@app.get("/admin")
async def admin_page():
from fastapi.responses import FileResponse
return FileResponse(str(STATIC_DIR / "admin.html"))
@app.get("/health")
async def health():
return {"status": "ok"}