fix(auth): 登录 500——asyncpg 拒绝 aware datetime 写入 naive TIMESTAMP 列

批次5(64dc85b)datetime 弃用清零把 utcnow() 换成 aware 的
now(timezone.utc),而 users.last_login 列是 TIMESTAMP WITHOUT TIME
ZONE(sa.DateTime()),asyncpg 编码时抛 DataError → 未捕获 →
Starlette 纯文本 500。症状:密码错误正常 401,密码正确反而 500
(错误密码在 commit 前已 return)。

已在生产库实测确认(事务回滚零写入):aware 写入 REJECTED、
naive ACCEPTED、过去 1 小时 last_login 零记录。

- auth_service: last_login 改存 naive UTC(.replace(tzinfo=None)),
  JWT exp 不受影响仍用 aware
- experience_feedback_service: expires_at 同为 naive 列,提交/查询
  两处 aware now 一并修掉(线上尚无此表,属前瞻性修复)

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
2026-09-26 22:29:17 +08:00
parent 9e52c95400
commit f1d6a78f8a
2 changed files with 10 additions and 3 deletions
@@ -223,7 +223,9 @@ class ExperienceFeedbackService:
role_code = "user"
# 6. 写 ExperienceFeedback
now = datetime.now(timezone.utc)
# expires_at 列是 naive TIMESTAMP(sa.DateTime()),asyncpg 拒绝 aware datetime
# (DataError → 提交反馈 500);存库/比较统一 naive UTC(同 auth last_login 修复)
now = datetime.now(timezone.utc).replace(tzinfo=None)
new_ttl = now + timedelta(days=FEEDBACK_TTL_DAYS)
feedback = ExperienceFeedback(
processing_task_id=processing_task.id,
@@ -274,7 +276,8 @@ class ExperienceFeedbackService:
排除 expires_at < now() 的过期反馈;按 material_family + is_foam 锚定;
按 scheme_axis 聚合(adopted/rejected/adjust 计数 + 加权 confidence)。
"""
now = datetime.now(timezone.utc)
# 同上:expires_at 是 naive TIMESTAMP,SQL 参数也不能传 aware datetime
now = datetime.now(timezone.utc).replace(tzinfo=None)
is_foam_str = "true" if is_foam else "false"
mat_lower = (material_name or "").lower()
if "al" in mat_lower and "si" in mat_lower:
+5 -1
View File
@@ -142,7 +142,11 @@ async def authenticate_user(db_session: AsyncSession, username: str, password: s
if not verify_password(password, user.hashed_password):
return None
user.last_login = datetime.now(timezone.utc)
# last_login 列是 TIMESTAMP WITHOUT TIME ZONE(sa.DateTime()),asyncpg 拒绝写入
# 带 tzinfo 的 datetime(DataError → 登录 500):批次5 弃用清理把 utcnow() 换成
# aware 时间后,任何一次成功登录都会在 commit 处炸掉。存库统一 naive UTC,
# JWT exp(上方 create_access_token)不受影响,仍用 aware。
user.last_login = datetime.now(timezone.utc).replace(tzinfo=None)
await db_session.commit()
return user