From 4b8b6f7210b00c07ed82b6f43a73571e041af70b Mon Sep 17 00:00:00 2001 From: chengkml <45121067+chengkml@users.noreply.github.com> Date: Fri, 1 May 2026 10:05:26 +0800 Subject: [PATCH] fix(api): support configurable users username column mapping --- .env.example | 1 + MEMORY.md | 1 + api/app/core/config.py | 1 + api/app/models/user.py | 10 +++++++++- docker-compose.yml | 3 +++ memory/2026-05-01.md | 25 +++++++++++++++++++++++++ 6 files changed, 40 insertions(+), 1 deletion(-) diff --git a/.env.example b/.env.example index b3c7c42..33f50a1 100644 --- a/.env.example +++ b/.env.example @@ -10,6 +10,7 @@ DB_NAME=postgres DB_SCHEMA=public DB_USERNAME=fquiz DB_PASSWORD=fquiz +USER_USERNAME_COLUMN=username FILE_VFS_ROOT=./data/vfs MINIO_ENABLED=true MINIO_ENDPOINT=http://minio:9000 diff --git a/MEMORY.md b/MEMORY.md index 763c164..1d38bc8 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -226,6 +226,7 @@ - `DB_SCHEMA` 通过 PostgreSQL `search_path` 注入,语义等价 JDBC 的 `currentSchema`。 - API 启动初始化口径:`seed_defaults` 对本地目标执行;为兼容老表状态约束,初始管理员状态写入值统一为 `ENABLED`(不使用 `active`)。 - 用户表兼容口径:用户主键列对齐旧库 `users.user_id`,与用户关联的外键统一引用 `users.user_id`(不再引用 `users.id`)。 +- 用户名列口径:历史环境存在 `users.username` 与 `users.user_name` 双形态;运行时通过 `USER_USERNAME_COLUMN`(`username`/`user_name`)与目标库对齐,避免启动阶段关系预加载触发 `UndefinedColumn`。 ## 发布验收口径(2026-04-26) diff --git a/api/app/core/config.py b/api/app/core/config.py index 30cbd68..3870a14 100644 --- a/api/app/core/config.py +++ b/api/app/core/config.py @@ -23,6 +23,7 @@ class Settings(BaseSettings): db_schema: str = "public" db_username: str = "fquiz" db_password: str = "fquiz" + user_username_column: Literal["username", "user_name"] = "username" file_vfs_root: str = "./data/vfs" minio_enabled: bool = False minio_endpoint: str = "http://minio:9000" diff --git a/api/app/models/user.py b/api/app/models/user.py index 1215319..7ca0b17 100644 --- a/api/app/models/user.py +++ b/api/app/models/user.py @@ -7,6 +7,7 @@ from uuid import uuid4 from sqlalchemy import DateTime, String from sqlalchemy.orm import Mapped, mapped_column, relationship +from ..core.config import get_settings from ..core.database import Base from .base import utcnow @@ -15,6 +16,8 @@ if TYPE_CHECKING: from .audit_log import AuditLog from .rbac import Role +settings = get_settings() + class User(Base): __tablename__ = "users" @@ -26,7 +29,12 @@ class User(Base): default=lambda: uuid4().hex, ) email: Mapped[str] = mapped_column(String(255), unique=True, index=True) - username: Mapped[str] = mapped_column("user_name", String(64), unique=True, index=True) + username: Mapped[str] = mapped_column( + settings.user_username_column, + String(64), + unique=True, + index=True, + ) password_hash: Mapped[str] = mapped_column("password", String(255)) status: Mapped[str] = mapped_column("state", String(32), default="ENABLED", index=True) created_at: Mapped[datetime] = mapped_column( diff --git a/docker-compose.yml b/docker-compose.yml index 2aeb17b..7f3c7f4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -102,6 +102,7 @@ services: DB_SCHEMA: ${DB_SCHEMA:-public} DB_USERNAME: ${DB_USERNAME:-fquiz} DB_PASSWORD: ${DB_PASSWORD:-fquiz} + USER_USERNAME_COLUMN: ${USER_USERNAME_COLUMN:-username} FILE_VFS_ROOT: ${FILE_VFS_ROOT:-./data/vfs} MINIO_ENABLED: ${MINIO_ENABLED:-true} MINIO_ENDPOINT: ${MINIO_ENDPOINT:-http://minio:9000} @@ -175,6 +176,7 @@ services: DB_SCHEMA: ${DB_SCHEMA:-public} DB_USERNAME: ${DB_USERNAME:-fquiz} DB_PASSWORD: ${DB_PASSWORD:-fquiz} + USER_USERNAME_COLUMN: ${USER_USERNAME_COLUMN:-username} CELERY_BROKER_URL: ${CELERY_BROKER_URL:-redis://redis:6379/0} CELERY_RESULT_BACKEND: ${CELERY_RESULT_BACKEND:-redis://redis:6379/1} CELERY_TIMEZONE: ${CELERY_TIMEZONE:-Asia/Shanghai} @@ -211,6 +213,7 @@ services: DB_SCHEMA: ${DB_SCHEMA:-public} DB_USERNAME: ${DB_USERNAME:-fquiz} DB_PASSWORD: ${DB_PASSWORD:-fquiz} + USER_USERNAME_COLUMN: ${USER_USERNAME_COLUMN:-username} CELERY_BROKER_URL: ${CELERY_BROKER_URL:-redis://redis:6379/0} CELERY_RESULT_BACKEND: ${CELERY_RESULT_BACKEND:-redis://redis:6379/1} CELERY_TIMEZONE: ${CELERY_TIMEZONE:-Asia/Shanghai} diff --git a/memory/2026-05-01.md b/memory/2026-05-01.md index 99e90dc..fc0f4be 100644 --- a/memory/2026-05-01.md +++ b/memory/2026-05-01.md @@ -18,3 +18,28 @@ - 风险与影响: - 影响范围:仅 PostgreSQL 且仅触发于“存在 `users.id` 且缺少 `users.user_id`”的历史库。 - 该变更属于启动期一次性 DDL 兼容动作;对已规范为 `users.user_id` 的库无行为变化。 + +## Work Log - 修复 API 启动时报 users.user_name / users.username 列不一致(2026-05-01) + +- 背景: + - API 启动阶段在 `seed_defaults -> _seed_permissions` 查询中触发 ORM 关系预加载,SQL 使用了 `users.user_name`,但当前 PostgreSQL `users` 表实际字段为 `username`,导致 `psycopg.errors.UndefinedColumn`,服务启动失败。 + +- 本次改动(最小闭环): + - 文件:`api/app/core/config.py` + - 新增配置 `user_username_column`(环境变量 `USER_USERNAME_COLUMN`),可选值 `username` / `user_name`,默认 `username`。 + - 文件:`api/app/models/user.py` + - `User.username` 列改为基于 `settings.user_username_column` 动态映射,兼容两类历史库结构。 + - 文件:`docker-compose.yml` + - 为 `api` / `celery-worker` / `celery-beat` 注入 `USER_USERNAME_COLUMN` 环境变量透传。 + - 文件:`.env.example` + - 新增 `USER_USERNAME_COLUMN=username` 示例配置。 + +- 验证: + - `python3 -m py_compile api/app/core/config.py api/app/models/user.py api/app/services/seed_service.py` -> 通过。 + - `USER_USERNAME_COLUMN=user_name POSTGRES_PORT=5434 docker compose up -d --no-deps api` -> 启动成功。 + - `USER_USERNAME_COLUMN=user_name POSTGRES_PORT=5434 docker compose ps api` -> `Up (healthy)`。 + - `curl -fsS http://127.0.0.1:8000/health` -> `{"status":"ok","service":"fquiz-api","version":"0.1.0"}`。 + +- 风险与影响: + - 影响面集中在 `User` 模型用户名字段映射。 + - 运行环境需明确 `USER_USERNAME_COLUMN` 与目标数据库实际字段一致;配置错误会在启动阶段继续抛 `UndefinedColumn`。