- 根因①:页面硬编码 http://127.0.0.1:8000/api,从 192.168.1.160:3000 访问时浏览器请求自身/跨域失败 - 根因②(500):全市场数据同步(长写事务)持有 SQLite 锁导致读接口 database is locked - 前端:next.config.ts rewrites /api/* → http://127.0.0.1:8000/api(BACKEND_API_URL 可覆盖); lib/api.ts 默认相对 /api 同源(NEXT_PUBLIC_API_BASE 保留为直连覆盖项);.env.local.example 说明两种方式 - 后端:CORS 开发期 allow_origins=["*"](无凭据,上线前收紧);SQLite engine busy_timeout=30s - USAGE FAQ 同步;验证:192.168.1.160:3000/api/health 与 /api/factors 经代理 200,CORS 头 access-control-allow-origin: *
56 lines
1.7 KiB
Python
56 lines
1.7 KiB
Python
"""SQLAlchemy engine / Session 工厂与 FastAPI 依赖。
|
|
|
|
注意:本文件属于 Infrastructure 层,是唯一允许出现 engine 创建的地方之一。
|
|
业务 Service / Domain 禁止直接使用 Session —— 一律通过 Repository。
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Generator
|
|
|
|
from sqlalchemy import create_engine, event
|
|
from sqlalchemy.orm import Session, sessionmaker
|
|
|
|
from app.core.config import get_settings
|
|
|
|
_settings = get_settings()
|
|
|
|
# SQLite 写锁等待加固:全市场同步等长写事务进行时,读接口等待而非立刻
|
|
# "database is locked";另在连接上设置 busy_timeout。WAL 迁移留作后续专项
|
|
# (需在无活跃写事务时统一切换,避免混合 journal 模式)。
|
|
_connect_args: dict = {}
|
|
if _settings.database_url.startswith("sqlite"):
|
|
_connect_args["timeout"] = 30
|
|
|
|
engine = create_engine(
|
|
_settings.database_url,
|
|
echo=_settings.sqlalchemy_echo,
|
|
future=True,
|
|
connect_args=_connect_args,
|
|
)
|
|
|
|
|
|
@event.listens_for(engine, "connect")
|
|
def _set_sqlite_pragmas(dbapi_conn, _record) -> None: # pragma: no cover —— 仅 SQLite 路径
|
|
if _settings.database_url.startswith("sqlite"):
|
|
cur = dbapi_conn.cursor()
|
|
cur.execute("PRAGMA busy_timeout = 30000")
|
|
cur.close()
|
|
|
|
|
|
SessionLocal = sessionmaker(
|
|
bind=engine,
|
|
autoflush=False,
|
|
expire_on_commit=False,
|
|
class_=Session,
|
|
)
|
|
|
|
|
|
def get_session() -> Generator[Session, None, None]:
|
|
"""FastAPI 依赖:每个请求一个 Session,请求结束自动关闭。
|
|
|
|
供 Repository 实现内部使用;业务层通过 Repository 间接访问。
|
|
"""
|
|
with SessionLocal() as session:
|
|
yield session
|