2026-09-10 15:55:54 +08:00
|
|
|
|
from sqlalchemy.engine import make_url
|
2026-09-09 21:55:37 +08:00
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
2026-09-10 09:23:22 +08:00
|
|
|
|
from sqlalchemy.pool import NullPool
|
2026-09-09 21:55:37 +08:00
|
|
|
|
|
|
|
|
|
|
from app.core.config import get_settings
|
|
|
|
|
|
|
2026-09-10 15:55:54 +08:00
|
|
|
|
# 存储层统一 UTC:`init_command` 是 asyncmy(driver 层)在**每次建立连接**时执行的
|
|
|
|
|
|
# 初始化 SQL,SQLAlchemy 的 asyncmy 方言会把 DSN 上的 query 参数原样透传给
|
|
|
|
|
|
# `asyncmy.connect`,因此写在 DSN 上即可生效,无需事件监听。
|
|
|
|
|
|
#
|
|
|
|
|
|
# 为什么不挂 `connect` 事件:`connect` 事件在 asyncmy 这套 asyncio 方言上**不会**
|
|
|
|
|
|
# 等待 async 监听器——实测注册后 `@@session.time_zone` 仍是 `SYSTEM`,同时抛
|
|
|
|
|
|
# `RuntimeWarning: coroutine ... was never awaited`。也就是说那种写法不报错、不生效,
|
|
|
|
|
|
# 是"看着修好了、其实照旧差 8 小时"的典型陷阱。
|
|
|
|
|
|
#
|
|
|
|
|
|
# 背景:建库时未显式指定时区,MySQL 会话继承系统时区(本机 Asia/Shanghai),
|
|
|
|
|
|
# 表上 `created_at DATETIME(6) DEFAULT CURRENT_TIMESTAMP(6)` 因此写入**北京时间**,
|
|
|
|
|
|
# 而应用代码统一写 `datetime.now(UTC)`:同一行里 DB 默认值与应用写入值相差 8 小时。
|
|
|
|
|
|
# 更严重的真实故障是给 `sys_user_role.assigned_at` 用 MySQL `NOW()` 会超前 UTC 8 小时,
|
|
|
|
|
|
# 被 RBAC 的 `assigned_at <= now` 判为"尚未生效",接口直接 403。
|
|
|
|
|
|
#
|
|
|
|
|
|
# `.env` 的 `TIMEZONE=Asia/Shanghai` 只作用于展示/日志,改它**不会**影响 MySQL 会话时区,
|
|
|
|
|
|
# 所以必须在连接层设置。改这里即可:新增的连接一律 UTC,DB 默认值与应用 UTC 语义一致。
|
|
|
|
|
|
_SESSION_UTC_INIT_COMMAND = "SET time_zone = '+00:00'"
|
|
|
|
|
|
|
|
|
|
|
|
settings = get_settings()
|
|
|
|
|
|
_url = make_url(settings.mysql_dsn)
|
|
|
|
|
|
if "init_command" not in _url.query:
|
|
|
|
|
|
_url = _url.update_query_dict({"init_command": _SESSION_UTC_INIT_COMMAND})
|
|
|
|
|
|
|
2026-09-10 09:23:22 +08:00
|
|
|
|
engine = create_async_engine(
|
2026-09-10 17:26:52 +08:00
|
|
|
|
_url,
|
2026-09-10 09:23:22 +08:00
|
|
|
|
pool_pre_ping=settings.mysql_pool_pre_ping,
|
|
|
|
|
|
poolclass=NullPool,
|
|
|
|
|
|
)
|
2026-09-09 21:55:37 +08:00
|
|
|
|
SessionFactory = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
|