## Work Log - 修复 API 启动阶段 users 主键列兼容问题(2026-05-01) - 背景: - 启动报错 `psycopg.errors.UndefinedColumn: column users.user_id does not exist`,触发点在 `seed_defaults -> _seed_permissions` 的关系加载 SQL。 - 当前 ORM 与外键约定统一使用 `users.user_id`,但历史库可能残留 `users.id` 主键列命名。 - 本次改动: - `api/app/core/database.py` - 新增 `_ensure_user_pk_column_compatibility()`。 - 在 `init_db()` 执行 `Base.metadata.create_all()` 前,针对 PostgreSQL 做一次兼容检查: - 若 `users` 表存在,且检测到仅有 `id` 而没有 `user_id`,自动执行:`ALTER TABLE users RENAME COLUMN id TO user_id`。 - 其余情况不改动(已是 `user_id` 或两者都不存在时直接跳过)。 - 验证: - 在 `fquiz-api` 容器内执行 `init_db()`:`init_db_ok`。 - 在 `fquiz-api` 容器内执行查询,确认当前数据库 `users` 列包含 `user_id`。 - 风险与影响: - 影响范围:仅 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`。 ## Work Log - GitHub Actions 部署分支切换为 dev(2026-05-01) - 背景: - 当前 workflow 仅监听 `main` push,且 deploy job 条件写死为 `refs/heads/main`,导致 `dev` 推送不触发自动部署。 - 本次改动(最小改动): - 文件:`.github/workflows/main.yml` - `on.push.branches` 从 `main` 改为 `dev`。 - `deploy.if` 从 `github.ref == 'refs/heads/main'` 改为 `github.ref == 'refs/heads/dev'`。 - 预期行为: - `git push origin dev`:自动触发构建,并在构建成功后执行 deploy。 - `main` 分支 push:不再触发该 workflow 自动部署链路。 - 风险与影响: - 生产发布入口从 `main` 切换到 `dev`,需确认团队分支策略已同步。 - `workflow_dispatch` 若在非 `dev` 分支触发,deploy job 会被 `if` 条件跳过。 ## Work Log - 修复 docker db 端口 5433 冲突并改为 5434(2026-05-01) - 背景: - `docker compose` 启动 `db` 报错:`Bind for 0.0.0.0:5433 failed: port is already allocated`。 - 现有默认口径为宿主机映射 `5433->5432`,与本机已占用端口冲突。 - 本次改动(最小闭环): - 文件:`docker-compose.yml` - `db.ports` 默认映射从 `${POSTGRES_PORT:-5433}:5432` 改为 `${POSTGRES_PORT:-5434}:5432`。 - 文件:`.env.example` - `POSTGRES_PORT` 默认值从 `5433` 改为 `5434`。 - 本机直连 `DB_PORT` 默认值从 `5433` 改为 `5434`。 - 文件:`api/app/core/config.py` - `db_port` 默认值从 `5433` 改为 `5434`,与环境模板和 compose 默认保持一致。 - 文件:`README.md` - 本地 PostgreSQL 示例端口从 `localhost:5433` 更新为 `localhost:5434`。 - 文件:`MEMORY.md` - 宿主机默认 `POSTGRES_PORT` 与本机直连 `DB_PORT` 长期口径同步更新为 `5434`。 - 验证: - `POSTGRES_PORT=5434 docker compose up -d db` -> `fquiz-db` 启动成功。 - `docker compose ps -a` -> `fquiz-db` 状态 `Up ... (healthy)`。 - `docker inspect fquiz-db --format '{{json .HostConfig.PortBindings}}'` -> `5432/tcp` 映射 `HostPort=5434`。 - 风险与影响: - 影响范围:本地/部署侧依赖默认 `5433` 的连接配置需同步为 `5434`,否则会出现连接失败。 - 容器内服务间连接不受影响,仍通过 `db:5432` 通信。 ## Work Log - 统一 GitHub 发布默认 PostgreSQL 端口为 5434(2026-05-01) - 背景: - 仓库本地 `docker-compose.yml` 默认端口已是 `5434`,但 GitHub Actions 发布脚本内联的 `docker-compose.prod.yml` 与首次生成 `.env` 仍使用 `5433`,存在口径不一致。 - 本次改动(最小改动): - 文件:`.github/workflows/main.yml` - 将发布脚本中 `db.ports` 默认映射从 `${POSTGRES_PORT:-5433}:5432` 改为 `${POSTGRES_PORT:-5434}:5432`。 - 将首次初始化 `.env` 模板中的 `POSTGRES_PORT=5433` 改为 `POSTGRES_PORT=5434`。 - 验证: - `rg -n "POSTGRES_PORT:-5434|POSTGRES_PORT=5434" .github/workflows/main.yml` 命中两处,未发现 workflow 内 `5433` 残留。 - 风险与影响: - 影响范围:仅 GitHub 发布脚本默认值,不影响已存在且手工维护的服务器 `.env`。 - 若线上环境明确依赖 `5433`,需在服务器 `.env` 显式保留 `POSTGRES_PORT=5433`。 ## Work Log - 修复 API 启动时报 users.password 列不存在(2026-05-01) - 背景: - 启动报错 `psycopg.errors.UndefinedColumn: column users.password does not exist`。 - 触发点在 `seed_defaults -> _seed_permissions` 的查询过程中,SQLAlchemy 关系预加载 `Role.users` 时会查询 `users` 全字段;当前 ORM 将 `password_hash` 绑定到 `users.password`,但目标库字段为 `password_hash`。 - 本次改动(最小闭环): - 文件:`api/app/core/config.py` - 新增配置 `user_password_column`(环境变量 `USER_PASSWORD_COLUMN`),可选值 `password` / `password_hash`,默认 `password_hash`。 - 文件:`api/app/models/user.py` - `User.password_hash` 改为基于 `settings.user_password_column` 动态映射,兼容两类历史库结构。 - 文件:`docker-compose.yml` - 为 `api` / `celery-worker` / `celery-beat` 注入 `USER_PASSWORD_COLUMN` 环境变量透传。 - 文件:`.env.example` - 新增 `USER_PASSWORD_COLUMN=password_hash` 示例配置。 - 验证: - `python3 -m py_compile api/app/core/config.py api/app/models/user.py api/app/services/seed_service.py` -> 通过。 - `rg -n "USER_PASSWORD_COLUMN|user_password_column" ...` 命中配置、模型、compose、env 示例,映射链路完整。 - 风险与影响: - 影响面集中在 `User` 模型密码字段映射。 - 运行环境需确保 `USER_PASSWORD_COLUMN` 与目标数据库实际字段一致;若配置错误,启动阶段仍可能抛 `UndefinedColumn`。 ## Work Log - 修复 API 启动时报 users.state 列不存在(2026-05-01) - 背景: - 启动报错 `psycopg.errors.UndefinedColumn: column users.state does not exist`。 - 触发点同样在 `seed_defaults -> _seed_permissions` 的关系预加载过程中,ORM 查询 `users` 全字段时固定引用了 `state`,但目标库实际字段为 `status`。 - 本次改动(最小闭环): - 文件:`api/app/core/config.py` - 新增配置 `user_status_column`(环境变量 `USER_STATUS_COLUMN`),可选值 `status` / `state`,默认 `status`。 - 文件:`api/app/models/user.py` - `User.status` 列改为基于 `settings.user_status_column` 动态映射,兼容两类历史库结构。 - 文件:`docker-compose.yml` - 为 `api` / `celery-worker` / `celery-beat` 注入 `USER_STATUS_COLUMN` 环境变量透传。 - 文件:`.env.example` - 新增 `USER_STATUS_COLUMN=status` 示例配置。 - 验证: - `rg -n "USER_STATUS_COLUMN|user_status_column"` 命中配置、模型、compose、env 示例,映射链路完整。 - `git diff` 检查仅包含本次预期的 4 个文件改动。 - 风险与影响: - 影响面集中在 `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 锁;对已有规范列名的库无行为变化。 ## Work Log - 修复 /api/v1/admin/roles 在缺失 user_role 表时返回 500(2026-05-01) - 背景: - 复现路径:登录后调用 `GET /api/v1/admin/roles`,接口返回 `500`。 - 根因:`legacy_admin_rbac_service.list_roles` 固定查询 legacy 表 `user_role`,当库仅存在 modern RBAC 表(`roles/user_roles/role_menus`)时触发 `psycopg.errors.UndefinedTable`。 - 本次改动(最小闭环): - 文件:`api/app/services/legacy_admin_rbac_service.py` - 新增 `user_role` 存在性探测:`_legacy_role_table_exists()`。 - `list_roles` 增加双链路读取: - legacy 存在时保持原有 `user_role + role_menu_rela + menu` 查询; - legacy 缺失时自动回退 `roles + role_menus + menus + role_permissions + permissions`。 - `get_role_by_id` 与 `list_role_menu_ids` 同步支持 modern 回退(兼容按 `id` 或 `code` 查角色)。 - 新增辅助函数: - `_load_role_rows(..., role_source=...)` - `_load_role_permission_codes_map(..., role_source=...)` - 扩展辅助函数以支持 modern 回退: - `_load_role_menu_ids_map(..., role_source=...)` - `_load_menus_map(..., role_source=...)` - 验证: - `python3 -m py_compile api/app/services/legacy_admin_rbac_service.py` -> 通过。 - `git push origin HEAD:dev` -> 成功(提交 `2c3ad31`)。 - 风险与影响: - 影响范围限定在后台角色读取接口(`/api/v1/admin/roles*`)查询链路。 - 对 legacy 表完整的数据库行为保持不变;仅在 `user_role` 缺失时触发 modern 回退逻辑。 ## Work Log - 用户管理优化(编辑/检索/分页)(2026-05-01) - 背景: - Issue `FL-121` 要求用户管理支持:用户信息修改、用户检索、用户表格分页。 - 本次改动(最小闭环): - 后端 `users` 列表接口增强(检索 + 分页参数联动): - 文件:`api/app/api/v1/users.py` - `GET /api/v1/users` 新增查询参数: - `keyword`:按 `user_id/email/username` 模糊检索 - `status`:按启用/禁用状态过滤 - 后端用户服务增强: - 文件:`api/app/services/user_service.py` - `list_users(...)` 支持 `keyword/status` 条件查询并对 `total` 同步计数。 - `update_user(...)` 支持修改 `email` 与 `username`: - 统一 trim/归一化 - 重复值冲突校验 - 空值保护 - 用户更新请求模型补齐: - 文件:`api/app/schemas/user.py` - `UserUpdateRequest` 新增可选字段 `email`。 - 前端用户管理页增强: - 文件:`web/src/app/admin/users/page.tsx` - 新增“用户检索”区:关键字输入 + 状态筛选 + 搜索/重置。 - 用户列表请求改为受查询条件驱动(query key 包含分页/筛选参数)。 - 表格接入分页器(页码、每页条数、总数联动后端)。 - 新增“编辑用户”弹窗,支持修改邮箱/用户名/状态。 - 编辑提交仅传变更字段,避免无效更新。 - 验证(未执行编译/构建,遵循当前任务约束): - 代码走读与关键路径自检: - `GET /api/v1/users` -> 支持 `limit/offset/keyword/status`。 - `PATCH /api/v1/users/{id}` -> 支持 `email/username/status` 更新。 - 前端列表查询参数与分页状态联动一致。 - 风险与影响: - 影响范围集中在用户管理模块(`/admin/users` 与 `/api/v1/users*`)。 - 旧调用方不传 `keyword/status` 时行为保持兼容。 - 更新失败错误提示文案仍共用“not found or email/username exists”,后续如需更精确错误码可再拆分。 ## Work Log - 线路管理分布图加入缩放 Slider 与比例显示(2026-05-01) - 背景: - Issue `FL-131` 要求“线路管理页面分布图优化:加入 slider 显示缩放比例”。 - 本次改动(最小闭环): - 文件:`web/src/components/power-line-cesium-map.tsx` - 新增地图缩放 `Slider`(竖向),置于已有 `+/-/居中` 控件上方。 - 新增“缩放比例 xx%”文本展示。 - 新增缩放比例与相机高度的双向映射函数(对数映射),解决不同尺度下线性感知不均问题。 - 监听 `viewer.camera.changed`,实现鼠标滚轮/按钮缩放时比例实时同步。 - 拖动 Slider 时调用相机 `flyTo` 调整高度,保持当前位置不变,仅修改缩放。 - 新增防抖动状态控制(`sliderChangingRef`)与无效 setState 保护,减少高频相机事件导致的重复渲染。 - 验证: - 本次遵循任务约束,未执行编译/安装。 - 通过代码走读确认: - `+/-` 按钮、鼠标滚轮、居中重置都会同步刷新缩放比例。 - Slider 拖动可直接驱动地图缩放。 - 风险与影响: - 影响范围限定在线路管理分布图前端组件,不涉及后端接口与数据结构。 - 缩放比例为相对值(基于当前线路包围球动态计算),不同线路之间 100%/0% 对应的绝对相机高度不同,属于预期行为。 ## Work Log - 线路管理塔杆列表分页(2026-05-01) - 背景: - Issue `FL-132` 要求“线路管理”的塔杆列表表格支持分页。 - 现状是前端固定请求 `limit=500` 并关闭表格分页,数据量大时浏览与定位效率较差。 - 本次改动(最小闭环): - 文件:`web/src/app/admin/power-lines/page.tsx` - 新增塔杆列表分页状态: - `towerPagination.current`(当前页) - `towerPagination.pageSize`(每页条数,默认 20) - 塔杆列表请求参数改为按视图分流: - 表格视图:`limit=pageSize`,`offset=(current-1)*pageSize` - 地图视图:保留 `limit=500`(保证地图仍可展示较完整线路点位) - 表格接入 AntD 分页器: - 使用接口返回 `total` 驱动总数展示 - 支持切换每页条数 - 页码与请求参数联动 - 新增筛选/线路切换时的页码重置: - `selectedLineId / towerKeyword / towerTypeFilter / towerRiskFilter` 变化时自动回到第 1 页,避免落在空页。 - 验证(未执行编译/构建,遵循任务约束): - 代码走读确认: - 后端 `GET /api/v1/lines/{line_id}/towers` 已支持 `limit/offset` 且返回 `total`。 - 前端分页状态、请求参数、表格分页器三者联动一致。 - 风险与影响: - 影响范围:仅前端 `线路管理 -> 塔杆列表` 视图。 - 地图视图继续使用大页请求(500)避免点位显示回归。 ## Work Log - 线路管理分布图移除 Slider(2026-05-01) - 背景: - 用户在 Issue `FL-131` 新评论要求:去掉 slider。 - 本次改动(最小改动): - 文件:`web/src/components/power-line-cesium-map.tsx` - 移除右上角竖向缩放 Slider。 - 移除“缩放比例 xx%”文本展示。 - 清理 slider 配套的缩放百分比状态、相机监听与映射函数。 - 保留原有 `+ / - / 居中重置` 缩放按钮能力不变。 - 验证: - 遵循任务约束,未执行编译检查、未安装依赖。 - 代码走读确认变更范围仅限 slider 相关逻辑。 - 风险与影响: - 影响范围仅为线路管理分布图控件区 UI 与交互;后端接口和数据结构无影响。 ## Work Log - 新增高程数据管理功能(2026-05-01) - 背景: - 目标是支撑线路走向图高程渲染,提供“高程数据集管理 + 杆塔高程回填任务”闭环能力。 - 约束:最小改动优先,不引入重型 GDAL 依赖,先落可运行方案。 - 本次改动(最小闭环): - 后端模型与任务: - 新增 `api/app/models/elevation.py` - `elevation_dataset`:存高程数据集元信息(挂载、文件路径、分辨率、样本统计、bbox、状态)。 - `elevation_apply_job`:存线路回填任务(模式、进度统计、状态、错误信息)。 - 新增 `api/app/tasks/elevation_tasks.py` - Celery 任务 `apply_elevation_for_line_job`,异步执行指定 job。 - `api/app/core/celery_app.py` - Celery include 扩展:加入 `app.tasks.elevation_tasks`。 - 后端 API 与服务: - 新增 `api/app/schemas/elevation.py`(dataset/job 请求与响应模型)。 - 新增 `api/app/services/elevation_service.py`,提供: - 数据集列表/创建/更新/分析; - 回填任务列表/详情/创建; - 回填执行逻辑(最近邻采样 CSV 点,写回 `power_line_tower.altitude_m`)。 - 回填结果写入 `raw_extra_json.elevation`(数据集来源、采样距离、时间)。 - 新增 `api/app/api/v1/elevation.py`: - `GET /api/v1/elevation/datasets` - `POST /api/v1/elevation/datasets` - `PATCH /api/v1/elevation/datasets/{dataset_id}` - `POST /api/v1/elevation/datasets/{dataset_id}/analyze` - `GET /api/v1/elevation/jobs` - `GET /api/v1/elevation/jobs/{job_id}` - `POST /api/v1/elevation/jobs/apply-line` - `api/app/api/router.py` 注册 elevation 路由。 - `api/app/core/database.py` + `api/app/models/__init__.py` 注册新模型,确保 `init_db` 自动建表。 - 权限/菜单/订阅: - `api/app/services/seed_service.py` - 新增权限:`elevation.read` / `elevation.manage`。 - 新增后台菜单:`admin.elevation` -> `/admin/elevation`。 - admin 默认菜单绑定新增 `admin.elevation`。 - `api/app/services/legacy_authz_service.py` - admin 默认权限加入 elevation 权限。 - `MENU_CODE_PERMISSION_MAP` 增加 `admin.elevation`。 - legacy synthetic 菜单补齐 `admin.elevation`。 - `api/app/services/topic_registry.py` - 新增 topic 规则:`admin.elevation`。 - `api/app/services/admin_service.py` / `legacy_admin_rbac_service.py` - 将 `admin.elevation` 设为受保护菜单(不可误删)。 - 前端页面与类型: - 新增 `web/src/app/admin/elevation/page.tsx` - 高程数据集管理(创建、分析、列表)。 - 回填任务管理(创建、进度/结果查看)。 - 支持跳转文件管理上传 CSV(复用现有文件系统)。 - `web/src/types/auth.ts` 增加 elevation 相关类型定义。 - `web/src/app/admin/page.tsx` 新增“高程数据管理”卡片入口。 - `web/src/app/admin/menus/page.tsx` 将 `admin.elevation` 加入前端受保护菜单编码集合。 - 验证: - 后端语法编译: - `python3 -m compileall api/app` -> 通过。 - 前端构建: - `npm run build:web` -> 通过。 - 构建产物中已包含路由:`/admin/elevation`。 - 风险与影响: - 当前实现使用 CSV 点集“最近邻采样”,适合先跑通管理与回填流程;不是严格栅格插值方案。 - 未引入 GDAL/rasterio,部署更稳,但精度依赖 CSV 样本密度与坐标质量。 - 回填默认允许 `overwrite_all`,存在覆盖人工高程风险;前端默认展示“仅填空(推荐)”。 ## Work Log - 高程管理支持 IMG/TIF 导入与回填(2026-05-01) - 背景: - 用户上传的高程数据为 `.img`(栅格),现有实现仅支持 CSV 点集,无法直接用于高程回填任务。 - 本次改动(最小闭环): - 后端服务能力扩展(文件格式识别 + 栅格采样): - 文件:`api/app/services/elevation_service.py` - 数据集创建时按扩展名自动识别并落库 `file_format`(支持 `.csv/.img/.tif/.tiff`)。 - 新增“按格式分流”执行: - `csv`:沿用现有最近邻点集采样逻辑。 - `img/tif/tiff`:新增栅格像元采样逻辑,按杆塔经纬度写回 `power_line_tower.altitude_m`。 - 新增栅格分析逻辑: - 从栅格读取 `width/height/bounds` 回写 `sample_count/bbox`。 - 对非 WGS84 CRS 增加告警,并在回填时自动执行坐标转换(WGS84 -> 栅格 CRS)。 - 回填溯源扩展: - `raw_extra_json.elevation.sample_method` 对栅格标记为 `raster_pixel`。 - 增加 `sample_distance_source` 字段(CSV 为 `computed`,栅格为 `pixel_lookup`)。 - 依赖补齐: - 文件:`api/requirements.txt`,新增 `rasterio==1.4.3`。 - 文件:`api/pyproject.toml`,新增 `rasterio>=1.4.0,<2.0.0`。 - 前端文案更新: - 文件:`web/src/app/admin/elevation/page.tsx` - 页面提示更新为支持 `CSV/IMG/TIF/TIFF`,空态与示例路径同步为栅格可用口径。 - 验证: - `python3 -m compileall api/app` -> 通过。 - `npm run build:web` -> 通过。 - 构建产物包含路由 `/admin/elevation`。 - 风险与影响: - `.img/.tif` 回填依赖 `rasterio`(及底层 GDAL 运行时),部署环境需确保镜像能成功安装该依赖。 - 栅格 bbox 直接来自源栅格 CRS;非经纬度坐标系场景会返回告警,便于识别与后续治理。