Files
fquiz/memory/2026-04-23.md
T
2026-04-24 15:50:52 +08:00

789 lines
43 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## Work Log - 日记管理按 quiz 表与逻辑重构(2026-04-23
- 背景:按“用老工程表,参考老工程逻辑改造当前工程日记管理功能”要求,将 `fquiz``/admin/diary` 从系统日志复用页切换为独立 Diary 模块。
- 本次改动(最小闭环):
- 后端模型与接口落地(对齐 quiz 口径):
- `api/app/models/diary.py`
- 新增主表 `diary`,字段:
- `id/title/content/diary_date/mood/weather/archived/create_date/create_user/update_date/update_user`
- 索引:`idx_diary_create_user/idx_diary_diary_date/idx_diary_mood/idx_diary_archived`
- `api/app/schemas/diary.py`
- 新增 `DiaryMood` 枚举:`HAPPY/CALM/SAD/ANGRY/TIRED/EXCITED`
- 新增 `DiaryQueryRequest/DiaryCreateRequest/DiaryUpdateRequest/DiarySummary/DiaryPageResponse`
- `api/app/services/diary_service.py`
- 新增 create/search/get/update/delete/archive 逻辑。
- 查询逻辑对齐 quiz:按 `create_user` 隔离数据,支持 `title/mood/diary_date_start/diary_date_end/archived` 过滤,排序 `diary_date DESC, create_date DESC`
- 归档逻辑对齐 quiz`archive` 仅修改 `archived` 状态,保留原记录。
- `api/app/api/v1/diary.py`
- 新增接口:
- `POST /api/v1/diary/search`
- `GET /api/v1/diary/get/{id}`
- `POST /api/v1/diary/create`
- `PUT /api/v1/diary/update`
- `DELETE /api/v1/diary/delete/{id}`
- `POST /api/v1/diary/{id}/archive?archived=...`
- 权限口径:
- 读:`menu.read | menu.manage`
- 写:`menu.manage`
- 路由与建表注册:
- `api/app/api/router.py` 挂载 `diary` 路由。
- `api/app/models/__init__.py``api/app/core/database.py` 注册 `diary` 模型,确保 `create_all` 生效。
- 前端页面改造(替换 syslog 复用):
- `web/src/app/admin/diary/page.tsx`
- 改为独立 Diary 页面:
- 列表分页
- 条件查询(标题、心情、日期范围、归档状态)
- 新增 / 编辑 / 删除
- 归档 / 取消归档
- 详情弹窗
- API 调整到新后端接口 `/api/v1/diary/*`
- `web/src/types/auth.ts`
- 新增 `DiaryMood/DiarySummary/DiaryListResponse` 类型。
- `web/src/app/admin/page.tsx`
- 首页卡片文案从“上帝视角”调整为“日记管理”。
- `api/app/services/seed_service.py`
- 菜单 `admin.diary` 名称更新为“日记管理”(路径保持 `/admin/diary`)。
- 验证:
- `python3 -m py_compile api/app/models/diary.py api/app/schemas/diary.py api/app/services/diary_service.py api/app/api/v1/diary.py api/app/api/router.py api/app/models/__init__.py api/app/core/database.py api/app/services/seed_service.py` -> 通过。
- `python3 -m compileall api/app` -> 通过。
- 前端 ESLint 未完成(环境依赖缺失):
- `npm run lint` 提示 `eslint: not found`
- 直接执行 eslint 后提示缺少 `typescript` 依赖(`Cannot find module 'typescript'`)。
- 风险与影响:
- `admin.diary` 保留 `menu.read/menu.manage` 权限体系(未引入独立 `diary.read/diary.manage`),属于兼容方案;后续若需要更细权限边界,可单独拆分权限码。
- 本次仅对齐了老工程 diary 主链路,不包含分组/tag 维度扩展(老工程该模块本身也未强依赖)。
## Work Log - 登录链路切换为 quiz 老表与老逻辑兼容(2026-04-23
- 背景:按“改用老工程 `/root/.openclaw/workspace/quiz` 中的表,参照老工程登录逻辑改造”要求,当前轮以“登录可用 + 会话鉴权可用 + 菜单可拉取”为最小闭环。
- 本次改动(最小闭环):
- 登录请求契约切换:
- `api/app/schemas/auth.py`
- `LoginRequest``email + password` 改为 `user_id + password`(密码最小长度放宽为 1,兼容老库短密码)。
- `web/src/components/auth-provider.tsx`
- `login(...)` 传参改为 `user_id + password`,请求体同步改为 `{ user_id, password }`
- `web/src/app/page.tsx`
- 登录表单输入从 `Email` 改为 `User ID`,记住密码缓存字段同步改为 `userId`
- 密码与状态兼容:
- `api/app/core/security.py`
- `verify_password` 增加 `BCrypt` 兼容分支(老工程口令),保留现有 `Argon2` 支持。
- `api/app/services/legacy_authz_service.py`(新增)
- 新增用户状态归一:`ENABLED/ACTIVE/1/TRUE -> active``DISABLED/... -> disabled`
- `api/app/models/user.py`
- 用户默认状态改为 `ENABLED`;默认主键改为 `uuid4().hex`32 位,兼容老表长度习惯)。
- 旧表角色/权限装配兼容层(核心):
- `api/app/services/legacy_authz_service.py`(新增)
- 从老表链路读取授权信息:
- `user_role_rela -> user_role` 计算 `role_codes`
- `role_menu_rela -> menu` 映射 `permission_codes`
- 管理员识别兼容:`admin/sys_mgr/administrator` 或角色名含“管理员”,统一附加 `admin` 角色别名。
- 提供 `build_legacy_menu_tree(...)`,按老 `menu` 表生成 `/admin/me/menus` 所需菜单树。
- 后端鉴权链路接入兼容层:
- `api/app/services/auth_service.py`
- 登录改为按 `user_id` 查询。
- Access Token 的 `role_codes/permission_codes` 改为通过兼容层计算(不再依赖新 RBAC 表)。
- `api/app/core/dependencies.py`
- `get_current_user` 改为用户基础信息 + 兼容层授权计算。
- `api/app/api/v1/ws.py`
- WS 鉴权改为复用兼容层 `role_codes/permission_codes`
- `api/app/services/jwt_generator_service.py`
- JWT 生成与用户列表角色展示改为兼容层口径,状态过滤兼容 `ENABLED`
- `api/app/services/user_service.py`
- `serialize_user/queue_user_auth_refresh` 改为兼容层计算授权信息。
- 用户状态写入统一落库存储值 `ENABLED/DISABLED`
- 用户角色更新改为直连老表 `user_role_rela` 写入(`rela_id + user_id + role_id`)。
- `api/app/services/admin_service.py`
- `build_menu_tree` 优先走老表菜单树构建,失败时回退现有实现。
- 验证:
- 后端编译:
- `python3 -m py_compile api/app/services/legacy_authz_service.py api/app/core/security.py api/app/schemas/auth.py api/app/schemas/user.py api/app/services/auth_service.py api/app/core/dependencies.py api/app/services/user_service.py api/app/api/v1/ws.py api/app/services/jwt_generator_service.py api/app/services/admin_service.py api/app/models/user.py` -> 通过。
- `python3 -m compileall api/app` -> 通过。
- 前端构建:
- `npm run build:web` -> 失败(既有类型问题,非本轮新增):
- `web/src/app/admin/layout.tsx:155``Card` 组件 `children` 类型不匹配(`ui-antd` 兼容层类型定义问题)。
- 风险与影响:
- 本轮优先闭环“登录 + 鉴权 + 菜单树”,未对 `admin/roles``admin/menus` 全量 CRUD 全面切换到老表语义;这些接口仍存在新旧口径混用风险。
- 旧表无独立 `permissions` 表,当前 `permission_codes` 为“角色菜单到权限码”的兼容映射;若老库菜单编码与映射不一致,可能出现权限显隐偏差。
- 注册接口当前保留并做了缺省兜底,但老库角色初始化策略(如默认 `user` 角色)依赖目标库实际数据,需联调确认。
## Work Log - 第二轮:角色/菜单管理切换 quiz 老表口径(2026-04-23
- 背景:按“继续做第二轮”要求,将后台角色/菜单管理从现有 RBAC ORM 表切换为 quiz 老表链路,并同步前后端 ID 类型契约。
- 本次改动(最小闭环):
- 后端接口与服务切换:
- `api/app/services/legacy_admin_rbac_service.py`
- 完成并启用老表 CRUD
- 角色:`user_role``user_role_rela``role_menu_rela`
- 菜单:`menu``role_menu_rela`
- 支持:`list/get/create/update/delete role``list/get/create/update/delete menu``list/replace role menus``list permissions`
- 保护项兼容:保留受保护角色与菜单编码删除拦截。
- 修复更新边界:`menu.parent_id` 支持显式清空;角色/菜单名称做 `strip` 空值校验。
- `api/app/api/v1/admin.py`
- 角色/菜单相关路由改为调用 `legacy_admin_rbac_service`
- 角色/菜单 path 参数改为字符串:`role_id: str``menu_id: str`
- `GET /admin/roles/{role_id}/menus` 返回改为 `list[str]`
- `api/app/schemas/admin.py`
- 角色/菜单契约改为字符串 ID
- `RolePublic.id``RolePublic.menu_ids`
- `RoleCreateRequest.menu_ids``RoleUpdateRequest.menu_ids`
- `MenuPublic.id``MenuPublic.parent_id`
- `MenuCreateRequest.parent_id``MenuUpdateRequest.parent_id`
- `RoleMenuUpdateRequest.menu_ids`
- 鉴权菜单树 ID 口径对齐:
- `api/app/services/legacy_authz_service.py`
- `build_legacy_menu_tree` 改为直接使用老表 `menu.menu_id/parent_id` 作为菜单树 ID,不再生成临时递增 ID。
- 前端类型与页面对齐:
- `web/src/types/auth.ts`
- `RoleItem.id/menu_ids``MenuItem.id/parent_id` 改为字符串类型。
- `web/src/app/admin/roles/page.tsx`
- 角色编辑状态与 `menu_ids` 改为字符串口径。
- `web/src/app/admin/menus/page.tsx`
- 菜单编辑状态与 `parent_id` 改为字符串口径;
- ID 排序改为兼容字符串(纯数字优先数值比较,否则字典序)。
- `web/src/app/admin/layout.tsx`
- 菜单路径项 `id` 改为字符串。
- `web/src/app/admin/users/page.tsx`
- 角色兜底项 `id` 改为字符串(`fallback-*`)。
- 验证:
- 后端编译:
- `python3 -m py_compile api/app/schemas/admin.py api/app/api/v1/admin.py api/app/services/legacy_admin_rbac_service.py api/app/services/legacy_authz_service.py` -> 通过。
- `python3 -m compileall api/app` -> 通过。
- 前端构建:
- `npm run build:web` -> 失败,仍为既有问题:
- `web/src/app/admin/layout.tsx:155``Card` 组件 `children` 类型不匹配(`ui-antd` 兼容层类型定义问题,非本轮新增)。
- 风险与影响:
- 老表 `menu` 字段语义与当前前端展示字段并非一一对应(如 `component/cacheable`),当前仍为兼容映射;如需完全对齐 quiz UI 语义,后续需补充映射规则。
- `permissions` 仍是基于菜单编码映射的兼容推导;若线上老表菜单编码偏差,权限显示与鉴权可能出现边缘不一致,需要联调验证。
## Work Log - 第三轮:前端构建门禁打通(2026-04-23)
- 背景:第二轮后 `npm run build:web` 仍被前端类型问题阻断,影响发布门禁。
- 本次改动(最小闭环):
- `antd Card` 类型兼容修复:
- `web/src/components/ui-antd.tsx`
- `Card` 包装器改用 `antd` 官方 `CardProps` 类型,不再依赖 `ComponentProps<typeof AntCard>` 推导。
-`AntCard` 做显式可渲染组件收敛(`AntCardComponent`),避免 `JSX element type 'AntCard' does not have construct or call signatures`
- 页面层统一复用兼容层 `Card`
- `web/src/app/admin/mermaid-mgr/_components/mermaid-editor.tsx`
- `web/src/app/admin/mindmap/_components/mindmap-editor.tsx`
-`antd` 直引 `Card` 改为 `@/components/ui-antd``Card`
- 严格 TS 隐式 any 清理(与现有 strict 配置对齐):
- `web/src/app/admin/mermaid-mgr/_components/mermaid-editor.tsx`
- `web/src/app/admin/mermaid-mgr/page.tsx`
- `web/src/app/admin/mindmap/_components/mindmap-editor.tsx`
- `web/src/app/admin/schedule/page.tsx`
- `web/src/app/admin/todos/page.tsx`
- 补全 `onChange/onClick/onFinish/showTotal/footer` 等回调参数类型标注,消除 `noImplicitAny` 阻断。
- 验证:
- `npm run build:web` -> 通过(TypeScript 检查通过 + 54 个页面静态生成完成)。
- 风险与影响:
- 本轮仅做类型与组件兼容修复,未改业务逻辑与接口契约。
- 后续若继续在 `web/src/app/admin/**` 新增 AntD 回调,仍需显式参数类型以避免 `strict` 下再次触发 `implicit any`
## Work Log - 第四轮:类型门禁巡检与基线固化(2026-04-23)
- 背景:第三轮已打通 `build:web`,第四轮目标是做“全量巡检”防止同类问题回归。
- 本次动作:
- 代码扫描:
- `web/src``import { Card } from "antd"`(避免回到不稳定类型入口);
- `onFinish/onChange/onClick/onPressEnter` 中常见未标注参数模式(`event/value/values/total/_`)。
- 类型基线检查:
- `npm --workspace web exec tsc --noEmit --pretty false`
- 结果:
- 扫描未发现新的高风险命中。
- `tsc --noEmit` 全量通过,`web` 当前类型门禁基线稳定。
- 影响:
- 第四轮未新增业务逻辑改动,属于稳定性巡检与基线确认。
## Work Log - 第五轮:后端联调冒烟与运行时修复(2026-04-23)
- 背景:按“继续第五轮”要求,对登录与后台角色/菜单主链路做联调冒烟,验证第二轮老表改造在运行容器中的真实表现。
- 本次动作与发现:
- 容器代码加载校验:
- 初次冒烟发现运行中的 `api` 仍走旧登录契约(`email + password`),因此执行 `docker compose up --build -d api` 重新加载最新代码。
- 运行时阻断修复 1(启动失败):
- 重建后 `api` 启动报错:`ModuleNotFoundError: No module named 'bcrypt'`
- 处理:`api/requirements.txt` 新增 `bcrypt==4.2.1`,重建 `api` 后恢复健康。
- 运行时阻断修复 2`/admin/me/menus` 500):
- 冒烟命中 `500`,日志定位到 `legacy_authz_service.build_legacy_menu_tree` 使用了未定义变量 `node_id`
- 处理:`api/app/services/legacy_authz_service.py``nodes[node_id]` 修正为 `nodes[legacy_id]`,重建后恢复。
- 冒烟验证(只读,不写库):
- 登录契约校验:
- `POST /api/v1/auth/login``{user_id, password}` -> 401(凭证错误,说明契约已生效);
-`{email, password}` -> 422(缺少 `user_id`)。
- 管理后台主链路:
- `GET /api/v1/admin/roles` -> 200`RolePublic.id/menu_ids` 为字符串;
- `GET /api/v1/admin/menus` -> 200`MenuPublic.id/parent_id` 为字符串口径;
- `GET /api/v1/admin/me/menus` -> 200,菜单树根节点 `id` 为字符串;
- `GET /api/v1/admin/roles/sys_mgr/menus` -> 200`menu_ids` 为字符串数组;
- `GET /api/v1/admin/permissions` -> 200。
- 相关文件:
- `api/requirements.txt`
- `api/app/services/legacy_authz_service.py`
- 风险与影响:
- 本轮未执行角色/菜单写接口(create/update/delete),避免对外部数据库产生变更;当前验证覆盖“登录契约 + 权限读取 + 菜单读取”读路径。
## Work Log - 第六轮:修复日程页 Modal.footer 类型不兼容(2026-04-23
- 背景:`docker compose build web``web/src/app/admin/schedule/page.tsx:747` 失败,报错为 `Modal.footer` 回调签名不匹配(`OkBtn/CancelBtn` 被错误标注为 `() => ReactElement`)。
- 本次改动(最小闭环):
- `web/src/app/admin/schedule/page.tsx`
- 补充 React 类型导入:`FC``ReactNode`
- `Modal.footer` 回调类型从:
- `_origin: unknown`
- `{ OkBtn: () => ReactElement; CancelBtn: () => ReactElement }`
调整为:
- `_origin: ReactNode`
- `{ OkBtn: FC; CancelBtn: FC }`
- 保持现有按钮渲染与删除/保存逻辑不变,仅修复类型契约。
- 验证:
- `npm run build:web` -> 通过(Next.js 编译、TypeScript 检查、静态页面生成全部完成)。
- 风险与影响:
- 仅影响前端类型声明,不涉及接口契约、请求参数、业务分支和数据写入行为。
## Work Log - 第七轮:后台系统菜单改用 AntD Menu 组件(2026-04-23
- 背景:当前后台左侧“系统菜单”是递归 `Button + Link`,移动端是 `DropdownMenu`。按要求切换为 Ant Design `Menu` 组件承载菜单树。
- 本次改动(最小闭环):
- `web/src/app/admin/layout.tsx`
- 新增 `Menu as AntMenu``MenuProps` 引入。
- 移除递归渲染函数 `renderMenuNodes` 与移动端 `flattenMenuPaths` 下拉菜单构建逻辑。
- 新增 `buildMenuItems`:把后端返回的 `MenuTreeItem[]` 转为 `AntMenu``items` 结构(支持嵌套)。
- 新增 `collectSubmenuKeys`:收集所有带子节点菜单并展开,保持与旧实现一致的“默认全展开”体验。
- 新增 `findActiveMenuState`:基于 `pathname` 计算 `selectedKeys/openKeys`,保证当前路由高亮准确。
- 桌面端左侧“系统菜单”改为 `AntMenu mode=\"inline\"` 渲染。
- 移动端菜单入口改为在卡片内直接渲染 `AntMenu mode=\"inline\"`(不再使用下拉菜单承载)。
- 顶部“账号”菜单保持原 `DropdownMenu` 方案不变。
- 验证:
- `npm --workspace web exec tsc --noEmit --pretty false` -> 通过。
- `npm run build:web` -> 通过(Next.js 编译、TypeScript、静态页面生成全部完成)。
- 风险与影响:
- 移动端菜单交互由“点击按钮弹出”改为“卡片内直接展示”,视觉高度会增加,但菜单可见性更高。
- 本次只调整菜单承载组件,不涉及菜单接口、权限判断和路由结构变更。
## Work Log - 第八轮:菜单导航切回左侧布局(2026-04-23)
- 背景:收到“menu 导航放到左侧”的要求,当前后台壳层为顶部横向 `Menu`,与期望不一致。
- 本次改动(最小闭环):
- `web/src/app/admin/layout.tsx`
- 移除顶部横向 `AntMenu(mode=\"horizontal\")` 导航区。
- 新增左侧栏布局(`md` 及以上):`AntMenu(mode=\"inline\")` 承载后台菜单树。
- 移动端菜单改为左侧 `Drawer``placement=\"left\"`)承载 `AntMenu`,顶部仅保留“菜单”按钮触发抽屉。
- 保留现有菜单数据来源(`/api/v1/admin/me/menus`)、路由高亮逻辑(`selectedKeys`)与账号区交互不变。
- 菜单展开状态统一为 `menuOpenKeys`,桌面侧栏与移动抽屉共用,保证一致性。
- 验证:
- `npm --workspace web exec tsc --noEmit --pretty false` -> 通过。
- `npm run build:web` -> 通过(Next.js 编译、TypeScript、静态页面生成全部完成)。
- 风险与影响:
- 布局从“顶部导航”改为“左侧导航”,横向空间分配会变化,但仅影响后台壳层,不涉及页面业务逻辑与接口契约。
## Work Log - 第八轮:后台壳层改为顶部固定导航(2026-04-23)
- 背景:按“参考 Ant Design 文档站顶部固定导航(左侧 Logo + 主导航)”要求,重构当前后台壳层布局。
- 本次改动(最小闭环):
- `web/src/app/admin/layout.tsx`
- 布局从“左侧固定菜单 + 内容头卡片”调整为“顶部固定导航 + 内容区”。
- 顶栏结构改为:左侧品牌 Logo`Q + fquiz`+ 中部主导航(`AntMenu mode=horizontal`+ 右侧账号区。
- 保留现有菜单数据链路:继续通过 `/api/v1/admin/me/menus` 拉取并渲染树形菜单。
- 保留路由高亮逻辑,并补充 `openKeys` 计算用于移动端内嵌菜单展开。
- 新增移动端菜单折叠入口(`菜单/收起菜单`),避免小屏导航不可达。
- 保留 `menuError` 提示、登录态判断与账号退出逻辑,不改接口契约。
- 验证:
- `npm run build:web` -> 通过(Next.js 编译、TypeScript 检查、静态页面生成全部完成)。
- 风险与影响:
- 桌面端导航入口从左侧改为顶部,用户需要适应新的操作路径。
- 当顶级菜单数量较多时,水平菜单会进入 AntD 的溢出折叠(`...`)交互,属于组件默认行为。
## Work Log - 第九轮:84 库用户/角色/菜单数据导出并初始化本地库(2026-04-23)
- 背景:按“查询 84 的用户表、角色表、菜单表、菜单角色关系数据,然后初始化本地库”要求执行。
- 本次动作(最小闭环):
- 远端 84 库(`223.109.142.84:5432/postgres`)连通与数据核验:
- 表存在:`users / user_role / menu / role_menu_rela`
- 行数:`users=5``user_role=6``menu=64``role_menu_rela=123`
- 数据导出留档:
- CSV 导出文件:
- `tmp/84-export/users_84.csv`
- `tmp/84-export/user_role_84.csv`
- `tmp/84-export/menu_84.csv`
- `tmp/84-export/role_menu_rela_84.csv`
- 初始化脚本导出:
- `tmp/84-export/legacy_auth_schema.sql`
- `tmp/84-export/legacy_auth_data.sql`
- `tmp/84-export/legacy_auth_data_wrapped.sql`
- 本地库初始化:
- 目标库:本地 `fquiz-db``postgres` 数据库(用户 `fquiz`)。
-`DROP TABLE IF EXISTS public.role_menu_rela, public.menu, public.user_role, public.users CASCADE`
- 回放 schema + data 完成建表和数据导入。
- 由于 `menu.parent_id` 自关联外键,导入数据时在同一会话启用 `session_replication_role=replica` 后回放,导入成功。
- 验证:
- 远端与本地(postgres)按表对比 `count + md5(signature)` 全量一致:
- `users`: `5` / `a50fedde66f156b0442d792b42c355b7`
- `user_role`: `6` / `7fb7d2520f44efe25e8be15762a3bd3d`
- `menu`: `64` / `094b533fe91b853868a0f9e4356da49a`
- `role_menu_rela`: `123` / `3c752bb95375e80d835b198831d44535`
- 风险与影响:
- 本次初始化目标是本地 `postgres` 库,不影响本地 `fquiz` 库现有表数据。
- `users` 表为老工程结构(`user_id/user_name/password/state...`),与新表结构不同;后续若切回新 RBAC 表需区分数据库与表口径。
## Work Log - 第十轮:数据库默认目标切换到本地并修复启动兼容(2026-04-23)
- 背景:按“现在切到本地库,不用84了”要求,将默认数据库目标从 `223.109.142.84` 切到本地 `fquiz-db`
- 本次改动(最小闭环):
- `docker-compose.yml`
- `db` 服务移除 `local-db` profile,改为默认启动。
- `api` 新增 `depends_on: db (service_healthy)`
- `api` 的默认 DB 参数切到本地:
- `DB_HOST=db`
- `DB_PORT=5432`
- `DB_NAME=postgres`
- `DB_USERNAME=fquiz`
- `DB_PASSWORD=fquiz`
- `api/app/core/config.py`
- 非 Docker 默认 DB 参数改为本机本地库:
- `db_host=127.0.0.1`
- `db_port=5433`
- `db_name=postgres`
- `db_username=fquiz`
- `db_password=fquiz`
- `.env.example`
- 同步改为本机本地库默认值(不再出现 84 默认地址)。
- `api/app/services/seed_service.py`
- `_seed_initial_admin` 中初始管理员状态由 `active` 调整为 `ENABLED`,兼容老 `users.state` 约束,解决本地库启动时 `users_state_check` 报错。
- 验证:
- `python3 -m py_compile api/app/services/seed_service.py` -> 通过。
- `docker compose up -d --build api` -> 通过,`fquiz-api` 正常启动。
- `docker compose ps` -> `api/db/web` 全部 `healthy/up`
- `GET /health` -> 200。
- `POST /api/v1/auth/login`(错误密码探测)-> 401(说明 API 可用且查询链路正常)。
- 风险与影响:
- 默认目标已改为本地库;若后续要临时连外部库,需显式设置 `DATABASE_URL` 或覆盖 `DB_HOST` 等环境变量。
## Work Log - 第十一轮:后台菜单改为左侧内嵌并支持右上角显隐(2026-04-23)
- 背景:按“改成内嵌菜单,放在页面左侧,通过右上角菜单按钮隐藏/显示”要求调整后台壳层交互。
- 本次改动(最小闭环):
- `web/src/app/admin/layout.tsx`
- 移除移动端 `Drawer` 侧滑菜单方案,统一为页面内嵌左侧菜单。
- 新增状态 `menuVisible`,默认 `true`,控制左侧菜单区域显示/隐藏。
- 顶栏右上角“菜单”按钮改为全端可见,并切换文案:
- 菜单显示时:`隐藏菜单`
- 菜单隐藏时:`显示菜单`
- 内容区网格列根据 `menuVisible` 动态切换:
- 显示:`md:grid-cols-[280px_minmax(0,1fr)]`
- 隐藏:`md:grid-cols-1`
- 左侧菜单保留 `AntMenu inline` + `openKeys/selectedKeys` 逻辑,保持当前路由高亮与展开行为。
- 验证:
- `npm --workspace web exec tsc --noEmit --pretty false` -> 通过。
- `next build` 多次在当前环境命中 `.next` 产物文件 `ENOENT`(非本次业务逻辑错误):
- Turbopack 路径:`_buildManifest.js.tmp` 丢失;
- Webpack 路径:`edge-runtime-webpack.js` copyfile 丢失。
- 风险与影响:
- 本次仅改后台壳层菜单交互,不涉及 API 契约和业务数据写入。
- 当前构建环境存在 `.next` 产物落盘异常,影响 `build:web` 门禁稳定性(与菜单逻辑无直接耦合)。
## Work Log - 第十二轮:支持主题色切换并持久化(2026-04-23)
- 背景:按“支持主题色切换”要求,增加可视化主题色切换入口,并要求刷新后保持。
- 本次改动(最小闭环):
- `web/src/components/ui-antd.tsx`
- 新增主题外观上下文 `ThemeAppearanceContext`
- 新增 `useThemeAppearance()`,对外暴露:
- `accentColor`
- `setAccentColor(...)`
- 新增 `THEME_ACCENT_OPTIONS`(靛蓝/蓝色/青色/绿色/橙色/红色/粉色/紫色)。
- `Theme` 组件增加主题色状态与持久化:
- 启动时读取 `localStorage["fquiz:theme:accent-color"]`
- 切换后写回 localStorage
- 切换即更新 AntD `ConfigProvider``colorPrimary`,全局生效。
- `web/src/app/admin/layout.tsx`
- 顶栏右侧新增主题色选择器(`Select`),使用 `THEME_ACCENT_OPTIONS` 渲染。
- 选择器值绑定 `useThemeAppearance().accentColor`,变更时调用 `setAccentColor`
- 验证:
- `npm --workspace web exec tsc --noEmit --pretty false` -> 通过。
- `npm run build:web` -> 通过(Next.js 编译、TypeScript、静态页面生成全部完成)。
- 风险与影响:
- 主题色选择属于前端 UI 层改动,不影响后端接口与数据模型。
## Work Log - 登录页怪兽主视觉改为“毛怪 + 大眼仔”双角色(2026-04-23
- 背景:按“把登录页面的怪兽换成毛怪和大眼仔”要求,保留登录/注册链路不变,仅重做首页登录视觉。
- 本次改动(最小闭环):
- `web/src/app/page.tsx`
- 保留 `useAuth` 登录/注册/退出与表单提交流程不变。
- 将单怪兽舞台替换为双角色舞台:`sulley + mike`
- 保留“眼睛跟随鼠标”交互,并新增大眼仔眼球偏移参数(`MIKE_GAZE_MAX_X/Y`)。
- 保留“密码输入时挪开视线”交互:毛怪转头避视、大眼仔轻微眯眼。
- 替换旧 `.monster-*` 样式为 `.duo-* / .sulley-* / .mike-*` 样式,维持响应式布局与背景动效。
- 验证:
- `npm --workspace web exec tsc --noEmit --pretty false` -> 通过。
- `npm run build:web` -> 通过(Next.js 编译、TypeScript、静态页面生成全部完成)。
- 风险与影响:
- 本次为前端视觉层改动,不涉及 API 契约、鉴权逻辑或后端数据结构。
- 角色形象为页面内 CSS 卡通实现,不依赖外部图片资源。
## Work Log - 第九轮:按 AntD 配色收口全局变量(2026-04-23
- 背景:当前前端虽然已切到 AntD 组件栈,但大量页面仍使用历史 `--gray-* / --accent-*` 变量与 Tailwind 类,导致视觉不完全跟随 AntD 主题色。
- 本次改动(最小闭环):
- `web/src/components/ui-antd.tsx`
-`Theme` 内新增 `ThemeCssVarsScope`,通过 `antdTheme.useToken()` 把旧语义变量映射到 AntD token。
- 覆盖变量包括:
- AntD 直连变量:`--ant-color-primary``--ant-color-bg-layout``--ant-color-border-secondary``--ant-color-text-secondary``--ant-color-text`
- 旧语义变量:`--gray-*``--accent-*``--red-*``--green-*``--orange-*``--indigo-*``--color-panel-solid``--border`
- 通过 `display: contents` 避免新增布局层级影响页面结构。
- `web/src/app/globals.css`
- 新增上述变量的静态兜底值,避免主题注入前变量缺失。
- `body` 背景与文字颜色改为走 AntD 变量(`--ant-color-bg-layout``--ant-color-text`)。
- 验证:
- `npm --workspace web exec tsc --noEmit --pretty false` -> 通过。
- `npm run build:web` -> 通过(Next.js 编译、TypeScript、静态页面生成全部完成)。
- 风险与影响:
- 本次不改接口与业务逻辑,仅调整前端主题变量层与全局视觉基线。
- 旧页面里保留的 `var(--gray/* --accent/*)` 写法将统一跟随 AntD token,后续可按需渐进式替换为原生 AntD token class/style。
## Work Log - 第十轮:去掉前端 URL 的 `/admin` 前缀(2026-04-23
- 背景:希望用户侧访问地址不再出现 `/admin`,但保留现有 `app/admin/**` 页面实现与权限链路。
- 本次改动(最小闭环):
- `web/src/middleware.ts`(新增)
- 新地址(不含 `/admin`)统一 rewrite 到现有 `/admin/**` 页面路由。
- 旧地址 `/admin/**` 自动 redirect 到无前缀地址,兼容历史链接与书签。
- 根路径 `/``/api``/_next`、常见静态文件请求走 bypass,不参与改写。
- 约定:原 `/admin` 首页映射为 `/dashboard`
- `web/src/app/admin/layout.tsx`
- 菜单数据加载后统一把 `path``/admin/**` 规范化为无前缀地址(`/admin` -> `/dashboard`)。
- 顶部 Logo 跳转从 `/admin` 改为 `/dashboard`
- `web/src/app/page.tsx`
- 首页快捷入口改为无前缀地址:`/dashboard``/users``/requirements`
- 验证:
- `npm --workspace web exec tsc --noEmit --pretty false` -> 通过。
- `npm run build:web` -> 通过(含 Proxy/Middleware 生效,编译与静态生成完成)。
- 风险与影响:
- 现有硬编码 `/admin/**` 链接仍可用,但会发生一次 30x 重定向到无前缀地址。
- Next.js 16 对 `middleware` 命名提示迁移到 `proxy`,当前功能正常;后续可按官方建议改名以消除提示。
## Work Log - 第十轮:下线 8 个后台功能(2026-04-23
- 背景:按“删除生命倒计时、密钥管理、价格监控、历史答卷、诗词本、日记管理、家庭作业、试题管理功能”要求,执行最小闭环下线。
- 本次改动(最小闭环):
- 菜单与权限口径收敛:
- `api/app/services/seed_service.py`
- 移除默认菜单:
- `admin.life_countdown`
- `admin.password`
- `admin.token_usage`
- `admin.history`
- `admin.vocabulary`
- `admin.diary`
- `admin.homework`
- `admin.question_bank`
- `ROLE_MENU_BINDINGS["admin"]` 同步移除上述菜单。
- 移除默认权限 `life_countdown.read/manage`
- `api/app/services/legacy_authz_service.py`
- 将上述 8 个菜单加入 `DISABLED_MENU_CODES`,保证老库已有记录也不会再出现在 `/api/v1/admin/me/menus`
- 权限映射移除对应菜单码;补充 `admin.job_mgr -> question_bank.read/manage`,确保“作业监控”权限链路不受影响。
- `api/app/services/legacy_admin_rbac_service.py`
- 增加 `REMOVED_MENU_CODES` 过滤:菜单列表、角色菜单返回、权限推导、菜单可用性校验均排除已下线菜单。
- `api/app/services/admin_service.py`
- 受保护菜单集合同步移除上述 8 个菜单码(兼容回退路径)。
- `web/src/app/admin/menus/page.tsx`
- 前端受保护菜单集合同步移除上述 8 个菜单码。
- 后端接口下线:
- `api/app/api/router.py`
- 不再挂载 `diary_router``life_countdown_router`
- `api/app/api/v1/admin.py`
- 删除密钥管理专用接口:
- `GET /api/v1/admin/password/models`
- `GET /api/v1/admin/password/models/{model_id}/keys`
- `POST /api/v1/admin/password/models/{model_id}/rotate-key`
- 前端页面下线:
- 删除路由页面:
- `web/src/app/admin/life-countdown/page.tsx`
- `web/src/app/admin/password/page.tsx`
- `web/src/app/admin/price-monitor/page.tsx`
- `web/src/app/admin/token-usage/page.tsx`
- `web/src/app/admin/history/page.tsx`
- `web/src/app/admin/poetry/page.tsx`
- `web/src/app/admin/diary/page.tsx`
- `web/src/app/admin/homework/page.tsx`
- `web/src/app/admin/question-bank/page.tsx`
- `web/src/app/admin/vocabulary/page.tsx`
- `web/src/app/admin/page.tsx` 移除上述功能卡片入口。
- `web/src/app/admin/vocabulary-proficiency/page.tsx` 移除“进入诗词本”跳转按钮。
- 保留“作业监控”功能:
- 将原 `question-bank` 页面实现迁移到 `web/src/app/admin/job/_components/job-question-bank-page.tsx`
- `web/src/app/admin/job/page.tsx` 改为引用该实现。
- 验证:
- 后端语法校验:
- `python3 -m py_compile api/app/services/seed_service.py api/app/services/legacy_authz_service.py api/app/services/legacy_admin_rbac_service.py api/app/services/admin_service.py api/app/api/v1/admin.py api/app/api/router.py` -> 通过。
- `python3 -m compileall api/app` -> 通过。
- 前端构建:
- `npm run build:web` -> 通过。
- 输出路由确认已不再生成:
- `/admin/life-countdown``/admin/password``/admin/price-monitor``/admin/token-usage``/admin/history``/admin/poetry``/admin/diary``/admin/homework``/admin/question-bank``/admin/vocabulary`
- 风险与影响:
- 本次下线为“菜单 + 路由 + 部分接口”闭环;`question_bank``vocabulary` 底层 API 仍保留,供“作业监控/分组管理/知识点管理/单词统计”等保留模块复用。
- 若线上老库仍有已下线菜单记录,当前会被服务层过滤,不再对外展示。
## Work Log - 下线 8 个后台功能模块(2026-04-23
- 背景:按要求删除系统中的以下功能:
- 微信小程序(`admin.wxapp`
- MD解析(`admin.mdresolve`
- 数据查询(`admin.data_query`
- 热搜(`admin.hot_search`
- 文件识别(`admin.filedetector`
- 百度网盘(`admin.baidu_pan`
- 分组管理(`admin.tag`
- 知识点管理(`admin.knowledge_point_mgr`
- 本次改动(最小闭环):
- 前端:删除 8 个页面路由文件(访问即 404):
- `web/src/app/admin/wxapp/page.tsx`
- `web/src/app/admin/mdresolve/page.tsx`
- `web/src/app/admin/data-query/page.tsx`
- `web/src/app/admin/hot-search/page.tsx`
- `web/src/app/admin/filedetector/page.tsx`
- `web/src/app/admin/baidu-pan/page.tsx`
- `web/src/app/admin/group/page.tsx`
- `web/src/app/admin/knowledge/page.tsx`
- 前端:移除后台首页入口卡片:
- `web/src/app/admin/page.tsx`
- 前端:菜单管理页取消上述 8 个菜单码的“受保护菜单”限制(允许删除历史残留菜单):
- `web/src/app/admin/menus/page.tsx`
- 后端:菜单树硬过滤(即使老库仍有菜单记录,也不会下发给前端导航):
- `api/app/services/legacy_authz_service.py`
- 新增 `DISABLED_MENU_CODES` 并在菜单树构建、权限推导时过滤。
- `api/app/services/admin_service.py`
- 新增 `REMOVED_MENU_CODES` 并在 `list_menus/get_menu_by_id/get_menu_by_code/create_menu/build_menu_tree/_load_menus_by_ids` 过滤。
- `api/app/services/legacy_admin_rbac_service.py`
- 将 8 个菜单码加入 `REMOVED_MENU_CODES`,统一影响菜单列表、角色菜单关联、权限推导。
- 后端:停用对应接口挂载与默认种子:
- `api/app/api/router.py`
- 移除 `hot_search_router``mdresolve_router` 挂载。
- `api/app/services/topic_registry.py`
- 移除 `admin.hot_search``admin.hot_search.follow_topics` topic 规则。
- `api/app/services/seed_service.py`
- 移除 8 个菜单定义与 admin 默认菜单绑定。
- 移除 `seed_hot_search_defaults` 导入与调用。
- 验证:
- `python3 -m py_compile api/app/services/legacy_authz_service.py api/app/services/seed_service.py api/app/api/router.py api/app/services/topic_registry.py api/app/services/legacy_admin_rbac_service.py api/app/services/admin_service.py` -> 通过。
- `python3 -m compileall api/app` -> 通过。
- `npm --workspace web exec tsc --noEmit --pretty false` -> 通过。
- `npm run build:web` -> 通过;后台静态路由从 54 个下降到 36 个,已不再包含上述 8 个页面。
- 风险与影响:
- 本次不做数据库物理删行迁移;若库内仍有历史菜单记录,将被后端过滤而不可见。
- `api/app/api/v1/hot_search.py``api/app/api/v1/mdresolve.py` 文件仍保留在仓库,但未挂载到 `api_router`,外部不可访问。
## Work Log - 下线功能补充收口(2026-04-23
- 补充改动:
- 删除历史别名页面 `web/src/app/admin/tag/page.tsx`,彻底下线“分组管理”的旧路由入口。
- 验证补充:
- 因删除页面后 `.next` 残留类型缓存导致 `tsc` 引用旧路径,执行 `rm -rf web/.next` 后重跑:
- `npm --workspace web exec tsc --noEmit --pretty false` -> 通过
- `npm run build:web` -> 通过
- 产物路由确认不再包含 `/admin/tag` 及本轮下线的 8 个功能页面。
## Work Log - 下线 4 个后台功能模块(2026-04-23
- 背景:按要求删除以下功能:
- 脚本管理(`admin.cron_task_mgr`
- 待办管理(`admin.todos`
- 作业监控(`admin.job_mgr`
- JWT 生成器(`admin.jwt_generator`
- 本次改动(最小闭环):
- 前端下线:
- 删除路由页面:
- `web/src/app/admin/cron/page.tsx`
- `web/src/app/admin/todos/page.tsx`
- `web/src/app/admin/job/page.tsx`
- `web/src/app/admin/jwt-generator/page.tsx`
- 后台首页移除 4 个入口卡片:
- `web/src/app/admin/page.tsx`
- 菜单管理页移除 4 个菜单码受保护限制(允许清理历史残留):
- `web/src/app/admin/menus/page.tsx`
- 为保留“队列管理”能力,将原待办页面实现迁移到:
- `web/src/app/admin/jobqueue/_components/jobqueue-todo-page.tsx`
- `web/src/app/admin/jobqueue/page.tsx` 改为引用新组件。
- 后端菜单/权限链路下线:
- `api/app/services/seed_service.py`
- 移除 4 个菜单定义及 admin 默认菜单绑定。
- 移除 `jwt_generator.read/jwt_generator.manage` 默认权限与 admin 角色默认绑定。
- `api/app/services/legacy_authz_service.py`
- 将 4 个菜单码加入 `DISABLED_MENU_CODES`,并移除对应菜单权限映射。
- 移除 `DEFAULT_ADMIN_PERMISSION_CODES``jwt_generator.*`
- `api/app/services/legacy_admin_rbac_service.py`
- 将 4 个菜单码加入 `REMOVED_MENU_CODES`
- `api/app/services/admin_service.py`
- 将 4 个菜单码加入 `REMOVED_MENU_CODES`,并从受保护删除集合中移除。
- 后端接口挂载下线:
- `api/app/api/router.py`
- 移除 `jwt_generator_router` import 与 `include_router`,JWT 生成器 API 不再对外挂载。
- 验证:
- `python3 -m py_compile api/app/services/seed_service.py api/app/services/legacy_authz_service.py api/app/services/legacy_admin_rbac_service.py api/app/services/admin_service.py api/app/api/router.py` -> 通过。
- `python3 -m compileall api/app` -> 通过。
- `npm --workspace web exec tsc --noEmit --pretty false` -> 通过。
- `npm --workspace web exec next build --webpack` -> 通过;产物路由确认不再包含:
- `/admin/cron`
- `/admin/todos`
- `/admin/job`
- `/admin/jwt-generator`
- 风险与影响:
- `api/app/api/v1/jwt_generator.py` 文件仍保留在仓库,但已从路由汇总中移除,不可外部访问。
- 当前环境下 Next 构建偶发 `.next` 产物拷贝 `ENOENT` 警告(非本次改动引入,存在环境不稳定性);本轮最终构建已成功产出路由清单。
## Work Log - 修复 `/users` 首屏卡在 Loading2026-04-23
- 背景:访问 `http://localhost:3000/users` 时,后台壳层页面一直停留在 `Loading admin workspace...`
- 根因:
- `web/src/app/admin/layout.tsx``loadMenus` 仅处理了接口返回 `!ok` 场景;
-`fetchWithAuth("/api/v1/admin/me/menus")` 发生网络异常(例如 API 未启动、连接失败)抛出异常时,没有兜底 `catch/finally`,导致 `loadingMenus` 一直为 `true`
- 本次改动(最小闭环):
- `web/src/app/admin/layout.tsx`
- `loadMenus` 增加 `try/catch/finally`
- 异常场景下会:
- 清空 `menuTree`
- 设置 `menuError`(显示可见错误)
-`finally` 中统一 `setLoadingMenus(false)`,避免页面卡死在 loading。
- 无用户场景补充 `setMenuError("")`,避免遗留错误文案。
- 验证:
- `npm --workspace web exec tsc --noEmit --pretty false` -> 通过。
- 风险与影响:
- 仅涉及后台壳层菜单加载状态管理,不改接口契约与业务数据。
## Work Log - 默认 admin 视为全权限(2026-04-23
- 背景:要求“默认 admin 有全部权限”。
- 现状定位:
- 后端接口鉴权已具备 `admin` 角色兜底(`require_permission/require_any_permission``if "admin" in role_codes: allow`)。
- 前端权限判定 `hasPermission` 仅判断 `permission_codes.includes(...)`,未对 `admin` 角色做兜底,可能导致 admin 页面按钮/入口被误隐藏。
- 本次改动(最小闭环):
- `web/src/components/auth-provider.tsx`
- `hasPermission` 调整为:
- 若用户角色包含 `admin`,直接返回 `true`
- 否则按原逻辑判断 `permission_codes`
- 验证:
- 先清理 Next 类型缓存:`rm -rf web/.next`
- `npm --workspace web exec tsc --noEmit --pretty false` -> 通过。
- 风险与影响:
- 仅影响前端可见性与交互开关判定,不改后端鉴权规则。
- 后端仍保持真实权限校验,前端放行不会绕过服务端安全边界。
## Work Log - 去掉登录后首页,直接进入后台(2026-04-23)
- 背景:要求“去掉首页,登录完成直接进入后台”。
- 本次改动(最小闭环):
- `web/src/app/page.tsx`
- 移除已登录态的首页欢迎面板(欢迎文案 / Ping / 快捷入口)。
- 新增登录态跳转:当 `!initializing && user` 时执行 `router.replace("/dashboard")`
- 已登录时仅短暂显示“正在进入后台...”,避免闪现旧首页内容。
- `web/src/app/admin/layout.tsx`
- 未登录访问后台时按钮文案从“返回首页”调整为“前往登录”(`/`)。
- 账号下拉中的“返回首页”调整为“后台首页”(`/dashboard`)。
- 验证:
- `npm --workspace web exec tsc --noEmit --pretty false` -> 通过。
- 风险与影响:
- 仅涉及前端路由与文案,不改后端接口与鉴权逻辑。
## Work Log - 右上角账号入口改为 Avatar2026-04-23
- 背景:要求“右上角账号改成 avatar 组件”。
- 本次改动(最小闭环):
- `web/src/app/admin/layout.tsx`
- 顶部账号下拉触发器由“账号”按钮改为 AntD `Avatar`
- 头像文案取当前用户名首字母(大写),空值兜底 `U`
- 保持原下拉菜单项不变(账号信息、后台首页、退出登录)。
- 验证:
- `npm --workspace web exec tsc --noEmit --pretty false` -> 通过。
- 风险与影响:
- 仅涉及前端展示与交互入口样式,不改业务逻辑与接口调用。