fix: 启动时初始化站点库表结构,修复全新部署「有库无表」的缺陷

为什么:全仓没有任何启动期调用 init_db()——web/app.py 的 lifespan 在 yield
之前不做事(只在关停时清连接池),create_app 也只注入音频密钥。后果是全新部署
(宿主机 data/ 是空目录,compose 用 ./data:/app/data 挂载会盖掉镜像内内容)
启动后,第一次查询只是让 sqlite3 建出一个 **0 表空库**,随后所有涉库端点报
`no such table: users`。

实测证据(SITE_DATA_DIR 指向空目录,起真实 uvicorn):
  修复前:POST /auth/login -> 500;site.db 4096 字节 / 0 张表
  修复后:POST /auth/login -> 400(用户名或密码错误,即查询真的执行了);
          site.db 61440 字节 / 9 张表(含 users、settings)

这个故障还极难定位:500 的响应体是「登录失败,请稍后重试」,
把 no such table 整个吞掉了,日志不翻到底看不出来。

改法:在 lifespan 的 yield 之前 `await asyncio.to_thread(auth_system.init_db)`。
init_db 全程 CREATE TABLE IF NOT EXISTS + 幂等迁移,对已有生产库重复执行无副作用
(本次 175 项测试与真实启动冒烟均未触碰 data/,指纹三项一致);初始化失败则让
异常冲出、启动即失败,优于静默带病运行。
This commit is contained in:
snowy-yang committed 2026-09-26 14:38:05 +08:00
1 parent 70ca21974b
commit 3ed2f36b18
1 file changed
+12 -1
+12 -1
View File
@@ -5,12 +5,14 @@
`api_prefix` 用于把整套路由挂到指定前缀下(便于契约测试与反向代理)。 `api_prefix` 用于把整套路由挂到指定前缀下(便于契约测试与反向代理)。
""" """
import asyncio
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from pathlib import Path from pathlib import Path
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
import auth_system
from core.config import APP_VERSION from core.config import APP_VERSION
from core.security import init_secret_key from core.security import init_secret_key
from web.errors import install_error_handlers from web.errors import install_error_handlers
@@ -35,7 +37,16 @@ from web.routers import user as user_router
@asynccontextmanager @asynccontextmanager
async def _lifespan(app: FastAPI): async def _lifespan(app: FastAPI):
"""应用生命周期:关闭时释放 aiohttp 连接池(含按事件循环隔离的 session)。""" """应用生命周期:启动确保站点库表结构就绪,关闭时释放 aiohttp 连接池。
启动必须建表。全新部署时宿主机的 data/ 往往是个空目录(compose 用
`./data:/app/data` 挂载,会盖掉镜像内的任何内容),此时若只等首次查询
让 sqlite3 自动建库,得到的是一张 **0 表空库**——随后所有涉库端点报
`no such table: users`(实测登录端点直接 500,且错误被吞成"请稍后重试",
极难定位)。init_db 全程 `CREATE TABLE IF NOT EXISTS` + 幂等迁移,
对已有库重复执行无副作用,故放启动期最稳妥。
"""
await asyncio.to_thread(auth_system.init_db)
yield yield
try: try:
from core import aiohttp_compat, http_client from core import aiohttp_compat, http_client