From f1d6a78f8a92fb4f1482026738e7927e1cb7a03f Mon Sep 17 00:00:00 2001 From: chenjw28 <792430652@qq.com> Date: Sat, 26 Sep 2026 22:29:17 +0800 Subject: [PATCH] =?UTF-8?q?fix(auth):=20=E7=99=BB=E5=BD=95=20500=E2=80=94?= =?UTF-8?q?=E2=80=94asyncpg=20=E6=8B=92=E7=BB=9D=20aware=20datetime=20?= =?UTF-8?q?=E5=86=99=E5=85=A5=20naive=20TIMESTAMP=20=E5=88=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 批次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 --- src/moldinsight/services/experience_feedback_service.py | 7 +++++-- src/shared/services/auth_service.py | 6 +++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/moldinsight/services/experience_feedback_service.py b/src/moldinsight/services/experience_feedback_service.py index dbd48c4..c12138c 100644 --- a/src/moldinsight/services/experience_feedback_service.py +++ b/src/moldinsight/services/experience_feedback_service.py @@ -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: diff --git a/src/shared/services/auth_service.py b/src/shared/services/auth_service.py index f652c30..ed02ae8 100644 --- a/src/shared/services/auth_service.py +++ b/src/shared/services/auth_service.py @@ -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