修复 users 审计字段缺失导致 API 启动失败
Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
@@ -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`:
|
||||
|
||||
@@ -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"}
|
||||
|
||||
@@ -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 锁;对已有规范列名的库无行为变化。
|
||||
|
||||
Reference in New Issue
Block a user