From c051c2dbf86cb271604d03e71d694f1b4ca086f9 Mon Sep 17 00:00:00 2001 From: chengkml Date: Fri, 1 May 2026 12:25:16 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20users=20=E5=AE=A1=E8=AE=A1?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E7=BC=BA=E5=A4=B1=E5=AF=BC=E8=87=B4=20API=20?= =?UTF-8?q?=E5=90=AF=E5=8A=A8=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: multica-agent --- MEMORY.md | 9 +++++ api/app/core/database.py | 82 ++++++++++++++++++++++++++++++++++++++++ memory/2026-05-01.md | 24 ++++++++++++ 3 files changed, 115 insertions(+) diff --git a/MEMORY.md b/MEMORY.md index 68804a7..d86b10a 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -949,6 +949,15 @@ - 若检测到 `users` 表存在且仅有 `id`、缺少 `user_id`,自动执行 `ALTER TABLE users RENAME COLUMN id TO user_id`,再继续 `create_all/seed`。 - 对已对齐 `users.user_id` 的库,该逻辑不产生任何改动。 +## users 审计列兼容口径(2026-05-01) + +- 用户审计字段工程约定为 `users.create_user`、`users.update_user`。 +- 为兼容历史库并避免启动 seed 阶段出现 `UndefinedColumn: users.create_user/update_user`,`init_db()` 在 PostgreSQL 下新增启动期兼容逻辑: + - 若存在 `create_by/created_by`,自动重命名为 `create_user`。 + - 若存在 `update_by/updated_by`,自动重命名为 `update_user`。 + - 若目标列仍缺失,自动补齐 nullable 列:`create_user VARCHAR(64)`、`update_user VARCHAR(64)`。 +- 对已对齐审计字段的库,该逻辑不产生任何改动。 + ## GitHub Actions 发布分支口径(2026-05-01) - `.github/workflows/main.yml` 的自动发布触发分支已切换为 `dev`: diff --git a/api/app/core/database.py b/api/app/core/database.py index 7768d78..e8da541 100644 --- a/api/app/core/database.py +++ b/api/app/core/database.py @@ -102,6 +102,87 @@ def _ensure_user_timestamp_column_compatibility() -> None: "Detected legacy users.update_date; renamed to users.updated_at for schema compatibility.", ) +def _rename_user_column_if_needed( + connection: Any, + *, + column_names: set[str], + target_column: str, + legacy_candidates: tuple[str, ...], +) -> set[str]: + if target_column in column_names: + return column_names + + legacy_column = next( + (candidate for candidate in legacy_candidates if candidate in column_names), + None, + ) + if not legacy_column: + return column_names + + connection.execute( + text(f"ALTER TABLE users RENAME COLUMN {legacy_column} TO {target_column}"), + ) + logger.warning( + "Detected legacy users.%s; renamed to users.%s for schema compatibility.", + legacy_column, + target_column, + ) + column_names.remove(legacy_column) + column_names.add(target_column) + return column_names + + +def _ensure_user_audit_column_compatibility() -> None: + """ + Keep `users` audit columns aligned with the current ORM mapping. + + Some legacy deployments use `create_by` / `created_by` and + `update_by` / `updated_by`, or may miss these nullable columns. + """ + if not database_url.startswith("postgresql"): + return + + schema = settings.resolved_db_schema + with engine.begin() as connection: + db_inspector = inspect(connection) + if not db_inspector.has_table("users", schema=schema): + return + + column_names = { + column["name"] + for column in db_inspector.get_columns("users", schema=schema) + } + + column_names = _rename_user_column_if_needed( + connection, + column_names=column_names, + target_column="create_user", + legacy_candidates=("create_by", "created_by"), + ) + column_names = _rename_user_column_if_needed( + connection, + column_names=column_names, + target_column="update_user", + legacy_candidates=("update_by", "updated_by"), + ) + + if "create_user" not in column_names: + connection.execute( + text("ALTER TABLE users ADD COLUMN IF NOT EXISTS create_user VARCHAR(64)"), + ) + logger.warning( + "Detected missing users.create_user; added nullable create_user column for schema compatibility.", + ) + column_names.add("create_user") + + if "update_user" not in column_names: + connection.execute( + text("ALTER TABLE users ADD COLUMN IF NOT EXISTS update_user VARCHAR(64)"), + ) + logger.warning( + "Detected missing users.update_user; added nullable update_user column for schema compatibility.", + ) + def get_db() -> Generator[Session, None, None]: db = SessionLocal() @@ -141,6 +222,7 @@ def init_db() -> None: _ensure_user_pk_column_compatibility() _ensure_user_timestamp_column_compatibility() + _ensure_user_audit_column_compatibility() Base.metadata.create_all(bind=engine) with SessionLocal() as db: local_hosts = {"db", "localhost", "127.0.0.1", "::1"} diff --git a/memory/2026-05-01.md b/memory/2026-05-01.md index 2ad52a1..7038721 100644 --- a/memory/2026-05-01.md +++ b/memory/2026-05-01.md @@ -154,3 +154,27 @@ - 风险与影响: - 影响面集中在 `User` 模型状态字段映射。 - 运行环境需确保 `USER_STATUS_COLUMN` 与目标数据库实际字段一致;若配置错误,启动阶段仍可能抛 `UndefinedColumn`。 + +## Work Log - 修复 API 启动时报 users.create_user / users.update_user 列不存在(2026-05-01) + +- 背景: + - 发布部署日志报错 `psycopg.errors.UndefinedColumn: column users.create_user does not exist`,容器持续重启。 + - 触发点在 `init_db -> seed_defaults`,`Role.users` 关系预加载会查询 `users` 全字段;当历史库缺失 `create_user/update_user` 时,启动阶段直接失败。 + +- 本次改动(最小闭环): + - 文件:`api/app/core/database.py` + - 新增 `_ensure_user_audit_column_compatibility()` 并在 `init_db()` 中接入(位于 `Base.metadata.create_all()` 前执行)。 + - 兼容策略: + - 若存在历史列 `create_by/created_by`,自动重命名为 `create_user`。 + - 若存在历史列 `update_by/updated_by`,自动重命名为 `update_user`。 + - 若目标列仍缺失,自动补齐 nullable 列: + - `ALTER TABLE users ADD COLUMN IF NOT EXISTS create_user VARCHAR(64)` + - `ALTER TABLE users ADD COLUMN IF NOT EXISTS update_user VARCHAR(64)` + +- 验证: + - `git diff` 检查改动仅落在 `api/app/core/database.py` 与记忆文档。 + - 启动期兼容逻辑为 PostgreSQL 定向执行;仅在 `users` 表存在且列不一致时触发 DDL,已对齐环境不会产生变更。 + +- 风险与影响: + - 影响面限定在 PostgreSQL 的 `users` 表审计字段兼容处理。 + - 该兼容动作为启动期一次性 DDL,可能短暂持有 `users` 表 DDL 锁;对已有规范列名的库无行为变化。