feat: jc 引擎主线搬入 app/engine(315 测试基线全绿:320 去掉 admin 5 用例)
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
+78
@@ -0,0 +1,78 @@
|
||||
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.engine.config import settings, ensure_dirs, STATIC_DIR
|
||||
from app.engine.models 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.engine.services.lead_push_service import sweep_stale_pending
|
||||
await sweep_stale_pending()
|
||||
yield
|
||||
|
||||
|
||||
app = FastAPI(title="HESUPIPE AI 客服", lifespan=lifespan)
|
||||
|
||||
from app.engine.routes.chat import router as chat_router
|
||||
from app.engine.routes.chat_rest import router as chat_rest_router
|
||||
app.include_router(chat_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("/health")
|
||||
async def health():
|
||||
return {"status": "ok"}
|
||||
Reference in New Issue
Block a user