fix(api): support configurable users username column mapping
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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`。
|
||||
|
||||
Reference in New Issue
Block a user