docs: 清理过时文档并更新管理端名称

删除home-api.md、team-building-api.md等废弃文档
统一替换所有文档中的`WonderQ-Admin-UI`为`WonderQ-Admin-UI-Vue`
更新README.md与联调文档的内容与路径
修正各API文档的过时描述,移除废弃的迁移说明与本地mock依赖
整理docs/README.md的文档索引,优化阅读路径
This commit is contained in:
duanshuwen committed 2026-08-26 19:41:15 +08:00
1 parent 8de6ea01d0
commit e8eb8614f0
17 files changed
+143 -1754

No files matched your search

@@ -1,468 +0,0 @@
# WonderQ Admin Vue 正式化实施计划
> **For agentic workers:** REQUIRED SUB-SKILL: Use `subagent-driven-development` or `executing-plans` to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 在不删除 React 过渡端的前提下,将四组首页 CRUD 接入 Vue,补齐管理员踢下线、显式 403、五级数据范围和 `/admin`、`/admin-legacy` 正式代理,并完成可重复的三端验收。
**Architecture:** 保持 FastAPI + PostgreSQL + Redis 的现有模块化单体。后端继续以既有 Admin API 和 `{code,msg,data}` 契约为边界;Vue 通过独立的 API client、页面组件和静态路由注册实现新管理端;React 继续由独立 legacy 静态入口提供回滚能力。Redis 只保存会话记录、用户会话索引和权限缓存,不新增会话数据库表。
**Tech Stack:** Python 3、FastAPI、SQLAlchemy 2、Alembic、PostgreSQL、Redis、JWT、Vue 3、Element Plus、TypeScript、Pinia、Vue Router 4、React、Vite、Nginx、Docker Compose、pytest、Vitest。
---
## 文件边界与责任
### 后端
- 修改 `WonderQ-Admin/app/redis_session.py`:扩展会话存储协议,实现按用户索引、列出和批量撤销会话;不改变 Token 明文存储策略。
- 修改 `WonderQ-Admin/app/auth.py`:在登录创建和刷新轮换时维护用户会话索引;保留旧登录响应字段。
- 修改 `WonderQ-Admin/app/routers/system.py`:新增踢下线接口,执行权限、数据范围、当前会话保护和审计。
- 修改 `WonderQ-Admin/app/routers/admin.py`:为四组首页资源接入现有权限码和数据范围检查,保持既有路径与返回字段。
- 修改 `WonderQ-Admin/app/rbac.py`:固定数据范围和权限缓存故障的安全行为,确保资源列表与单条写入使用同一范围规则。
- 修改 `WonderQ-Admin/tests/test_home.py`、`tests/test_admin_rbac.py`:补充首页权限、范围和契约回归。
- 新建 `WonderQ-Admin/tests/test_admin_sessions.py`:覆盖 Redis 会话索引、刷新轮换、批量撤销和故障行为。
- 新建 `WonderQ-Admin/tests/test_data_scope_matrix.py`:覆盖五种数据范围的跨部门矩阵。
### Vue 管理端
- 修改 `WonderQ-Admin-UI-Vue/src/types.ts`:增加四组首页资源类型、403 错误类型、会话撤销结果类型。
- 修改 `WonderQ-Admin-UI-Vue/src/api/client.ts`:增加首页 CRUD、踢下线请求和显式 403 错误;不改变 API base、Cookie 或 Access Token 键。
- 新建 `WonderQ-Admin-UI-Vue/src/api/client-home.test.ts`:验证四组首页 API 方法、HTTP 方法和路径。
- 修改 `WonderQ-Admin-UI-Vue/src/api/client-system.test.ts`:验证踢下线 API。
- 新建 `WonderQ-Admin-UI-Vue/src/components/home/HomeCrudEditor.vue`:共享首页卡片表单、校验、图片 URL/状态字段和提交态。
- 新建 `WonderQ-Admin-UI-Vue/src/components/home/HomeCrudTable.vue`:共享列表、空态、错误态、重试、排序和权限按钮。
- 新建 `WonderQ-Admin-UI-Vue/src/lib/home-content.ts`:定义四组资源的字段元数据、默认值和 payload 归一化。
- 新建 `WonderQ-Admin-UI-Vue/src/pages/HomeContentPage.vue`:体验、团建、野奢三组 CRUD 页面。
- 新建 `WonderQ-Admin-UI-Vue/src/pages/HomeWanfaRecommendationPage.vue`:玩法推荐关联 CRUD 页面。
- 新建 `WonderQ-Admin-UI-Vue/src/pages/ForbiddenPage.vue`:登录态下的 403 页面。
- 修改 `WonderQ-Admin-UI-Vue/src/pages/HomePage.vue`、`src/router/index.ts`:把首页站点配置与四组业务入口拆开,并注册 403 和首页业务路由。
- 修改 `WonderQ-Admin-UI-Vue/src/pages/SystemResourcePage.vue`:在用户列表增加踢下线操作。
- 新建 `WonderQ-Admin-UI-Vue/src/router/router.test.ts`:验证 401、403、无权限页面和未知路径的分流。
### 部署与契约
- 修改 `docs/home-api.md`:补充 Vue 对接字段、统一错误和四组后台验收矩阵。
- 修改 `docs/admin-api-requirements.md`:补充踢下线、403、数据范围和 Redis 故障契约。
- 修改 `docs/integration-workflow.md`:补充双管理端、正式代理和回滚命令。
- 修改 `docs/admin-business-function-matrix.md`:记录每个切片的实际测试和人工验收结果。
- 新建 `WonderQ-Admin-UI-Vue/Dockerfile`、`WonderQ-Admin-UI-Vue/nginx.conf`:构建并提供 `/admin/` 静态资源。
- 修改 `WonderQ-Admin-UI/vite.config.ts`、`WonderQ-Admin-UI/nginx.conf`、`WonderQ-Admin-UI/Dockerfile`:支持 `/admin-legacy/` base 和 legacy 静态入口。
- 新建 `deploy/nginx/wonderq-admin.conf`:统一代理 `/admin/`、`/admin-legacy/` 和 `/api/`;不写入任何真实环境变量。
- 新建 `scripts/verify-admin-proxy.ps1`:检查两个构建产物和统一 Nginx 配置的入口、深链与 API 隔离。
- 修改 `docker-compose.yml`(仅在现有 Compose 确认包含前端服务时):加入两个前端构建入口和健康检查;若现有 Compose 不负责前端,则只增加部署文档,不扩大 Compose 责任。
## Task 1: 建立契约基线与失败测试入口
**Files:**
- Modify: `docs/home-api.md`
- Modify: `docs/admin-api-requirements.md`
- Modify: `docs/integration-workflow.md`
- Test: `WonderQ-Admin/tests/test_home.py`
- Test: `WonderQ-Admin-UI-Vue/src/api/client-home.test.ts`
- [ ] **Step 1: 记录四组首页的现有字段与接口响应**
从 `WonderQ-Admin/app/schemas.py`、`WonderQ-Admin/app/routers/admin.py`、`WonderQ-Admin-UI/src/api.ts` 和 `docs/home-api.md` 对照四组资源。契约表必须明确 `GET/POST/PATCH/DELETE/PATCH reorder`、成功状态码、空数组、排序完整 ID 列表和 403 响应,不改现有公共字段名。
- [ ] **Step 2: 先写 Vue API 失败测试**
在 `client-home.test.ts` 中为四组资源各调用一次列表、创建、更新、删除、排序方法,断言 `fetch` 收到以下请求:
```ts
expect(fetchMock.mock.calls.map(([url, options]) => [url, options?.method])).toEqual([
["/api/admin/home/experiences", undefined],
["/api/admin/home/experiences", "POST"],
["/api/admin/home/experiences/experience-1", "PATCH"],
["/api/admin/home/experiences/experience-1", "DELETE"],
["/api/admin/home/experiences/reorder", "PATCH"],
["/api/admin/home/team-buildings", undefined],
["/api/admin/home/team-buildings", "POST"],
["/api/admin/home/team-buildings/team-1", "PATCH"],
["/api/admin/home/team-buildings/team-1", "DELETE"],
["/api/admin/home/team-buildings/reorder", "PATCH"],
["/api/admin/home/wild-archives", undefined],
["/api/admin/home/wild-archives", "POST"],
["/api/admin/home/wild-archives/archive-1", "PATCH"],
["/api/admin/home/wild-archives/archive-1", "DELETE"],
["/api/admin/home/wild-archives/reorder", "PATCH"],
["/api/admin/home/play-recommendations", undefined],
["/api/admin/home/play-recommendations", "POST"],
["/api/admin/home/play-recommendations/recommendation-1", "PATCH"],
["/api/admin/home/play-recommendations/recommendation-1", "DELETE"],
["/api/admin/home/play-recommendations/reorder", "PATCH"],
]);
```
- [ ] **Step 3: 运行失败测试,确认缺口真实存在**
Run: `Set-Location WonderQ-Admin-UI-Vue; yarn test --run src/api/client-home.test.ts`
Expected: FAIL because the four Vue client methods and corresponding types do not exist yet.
- [ ] **Step 4: 同步契约文档并检查差异**
Run: `git diff --check -- docs/home-api.md docs/admin-api-requirements.md docs/integration-workflow.md`
Expected: no whitespace errors;文档中没有真实 Token、密码、OSS 凭证或生产地址。
## Task 2: 接入四组首页 API 与后端权限/范围
**Files:**
- Modify: `WonderQ-Admin-UI-Vue/src/types.ts`
- Modify: `WonderQ-Admin-UI-Vue/src/api/client.ts`
- Modify: `WonderQ-Admin/app/routers/admin.py`
- Modify: `WonderQ-Admin/tests/test_home.py`
- Modify: `WonderQ-Admin-UI-Vue/src/api/client-home.test.ts`
- [ ] **Step 1: 写后端首页鉴权和范围失败测试**
为四组列表和至少一个更新接口增加测试:普通管理员没有 `admin:site-config` 时返回 403;有该权限时只能读取或更新 `is_within_data_scope` 为真的记录;完整排序请求缺 ID 或重复 ID 时返回 400。测试继续使用现有依赖覆盖方式,不创建真实生产数据。
- [ ] **Step 2: 运行后端失败测试**
Run: `Set-Location WonderQ-Admin; .\.venv\Scripts\python.exe -m pytest tests/test_home.py -q`
Expected: new permission/scope assertions fail while existing home CRUD compatibility tests remain visible in the output。
- [ ] **Step 3: 增加 TypeScript 资源类型和 API 方法**
在 `src/types.ts` 中以 React 已有类型和后端 schema 为准定义 `HomeExperience`、`HomeTeamBuilding`、`HomeWildArchive`、`HomeWanfaRecommendation` 及对应 Create/Patch 类型;在 `src/api/client.ts` 中提供以下函数:
```ts
getHomeExperiences(); createHomeExperience(input); updateHomeExperience(id, input);
deleteHomeExperience(id); reorderHomeExperiences(itemIds);
getHomeTeamBuildings(); createHomeTeamBuilding(input); updateHomeTeamBuilding(id, input);
deleteHomeTeamBuilding(id); reorderHomeTeamBuildings(itemIds);
getHomeWildArchives(); createHomeWildArchive(input); updateHomeWildArchive(id, input);
deleteHomeWildArchive(id); reorderHomeWildArchives(itemIds);
getHomePlayRecommendations(); createHomePlayRecommendation(input);
updateHomePlayRecommendation(id, input); deleteHomePlayRecommendation(id);
reorderHomePlayRecommendations(itemIds);
```
每个方法只调用既有 `/api/admin/home/...` 路径,并由 `request<T>` 统一解包响应。
- [ ] **Step 4: 将后端四组路由切换到已有权限依赖并复用数据范围**
把当前四组首页路由的 `Depends(require_admin)` 替换为 `Depends(require_admin_permission("admin:site-config"))`,列表通过 `apply_data_scope`,单条更新、删除和排序前通过 `is_within_data_scope`;保留超级管理员兼容行为、既有审计事件和返回结构。写入记录继续使用现有创建人/部门回填逻辑。
- [ ] **Step 5: 运行后端与 Vue API 测试**
Run: `Set-Location WonderQ-Admin; .\.venv\Scripts\python.exe -m pytest tests/test_home.py -q`
Run: `Set-Location ..\WonderQ-Admin-UI-Vue; yarn test --run src/api/client-home.test.ts`
Expected: new assertions PASS;如全量后端仍出现已知 Alembic 兼容失败,记录其具体测试名,不将其归因于首页切片。
## Task 3: 迁移首页三组内容 CRUD 页面
**Files:**
- Create: `WonderQ-Admin-UI-Vue/src/lib/home-content.ts`
- Create: `WonderQ-Admin-UI-Vue/src/components/home/HomeCrudEditor.vue`
- Create: `WonderQ-Admin-UI-Vue/src/components/home/HomeCrudTable.vue`
- Create: `WonderQ-Admin-UI-Vue/src/pages/HomeContentPage.vue`
- Modify: `WonderQ-Admin-UI-Vue/src/router/index.ts`
- Modify: `WonderQ-Admin-UI-Vue/src/pages/HomePage.vue`
- Test: `WonderQ-Admin-UI-Vue/src/lib/home-content.test.ts`
- [ ] **Step 1: 写字段元数据和表单归一化失败测试**
测试必须分别检查三组资源的必填字段、空字符串裁剪、数字字段转换、`sortOrder` 默认值、`isActive` 默认值和错误文案。测试输入只使用测试文本和本地图片 URL。
- [ ] **Step 2: 运行失败测试**
Run: `Set-Location WonderQ-Admin-UI-Vue; yarn test --run src/lib/home-content.test.ts`
Expected: FAIL because the metadata module does not exist。
- [ ] **Step 3: 实现元数据模块**
`home-content.ts` 导出资源联合类型、资源定义、`createEmptyHomeDraft`、`toHomePayload` 和 `validateHomePayload`。所有字段名必须与 `docs/home-api.md` 和后端 Pydantic schema 一致;不把 base64 文件内容写入 CRUD payload,只保存媒体接口返回的 URL。
- [ ] **Step 4: 实现共享表格与编辑器**
`HomeCrudTable.vue` 接收 `items/loading/error/permission`,提供空态、错误重试、编辑、删除、上下移和提交中禁用;`HomeCrudEditor.vue` 接收资源定义和 draft,提供 Element Plus 表单校验、取消、提交中状态和 `save` 事件。按钮使用 `v-permission="'admin:site-config'"`,后端 403 仍由页面捕获显示错误。
- [ ] **Step 5: 实现三组 Vue 页面**
`HomeContentPage.vue` 根据路由参数选择 `experiences`、`team-buildings` 或 `wild-archives`,加载、创建、更新、删除和排序均调用 Task 2 的 API 方法。列表、编辑器和请求状态按资源隔离,避免修改一组数据时污染另外两组。页面提供加载骨架、空态、错误重试和窄屏表格滚动。
- [ ] **Step 6: 将首页入口与新页面注册到静态路由**
保留 `/admin/home` 作为站点配置页,在该页增加三个明确入口;在 `router/index.ts` 增加 `/home/content/:resource` 静态路由并绑定 `HomeContentPage`,权限使用 `admin:site-config`。组件仍必须来自本地预注册表,不接受后端任意组件路径。
- [ ] **Step 7: 运行 Vue 单元、类型和构建验证**
Run: `Set-Location WonderQ-Admin-UI-Vue; yarn test --run src/lib/home-content.test.ts src/api/client-home.test.ts`
Run: `yarn build`
Expected: tests and TypeScript build PASS;Vite 已有 chunk 大小或 `__dirname` warning 可记录,但不得出现编译错误。
## Task 4: 迁移玩法推荐关联 CRUD
**Files:**
- Create: `WonderQ-Admin-UI-Vue/src/pages/HomeWanfaRecommendationPage.vue`
- Modify: `WonderQ-Admin-UI-Vue/src/router/index.ts`
- Modify: `WonderQ-Admin-UI-Vue/src/pages/HomePage.vue`
- Test: `WonderQ-Admin-UI-Vue/src/api/client-home.test.ts`
- Test: `WonderQ-Admin/tests/test_home.py`
- [ ] **Step 1: 写玩法推荐列表、关联和排序失败测试**
后端测试验证关联不存在的玩法分类返回 404/400、删除关联不删除分类、状态更新只改变关联、排序必须包含当前全部关联 ID。Vue API 测试验证 `play-recommendations` 的五个方法路径。
- [ ] **Step 2: 运行失败测试**
Run: `Set-Location WonderQ-Admin; .\.venv\Scripts\python.exe -m pytest tests/test_home.py -q`
Run: `Set-Location ..\WonderQ-Admin-UI-Vue; yarn test --run src/api/client-home.test.ts`
Expected: new Vue methods and missing edge-case assertions fail。
- [ ] **Step 3: 实现 Vue 玩法推荐页面**
页面加载现有玩法分类作为选择项,单独显示已关联推荐;创建和编辑只提交后端允许的分类 ID、状态和排序字段;删除操作明确提示“只移除首页关联,不删除玩法分类”;复用 `HomeCrudTable` 的空态、错误态、排序和权限按钮。
- [ ] **Step 4: 运行玩法推荐切片验收**
Run: `Set-Location WonderQ-Admin; .\.venv\Scripts\python.exe -m pytest tests/test_home.py -q`
Run: `Set-Location ..\WonderQ-Admin-UI-Vue; yarn test --run; yarn build`
Expected: 后端首页测试、Vue 现有 25 项基线测试和构建全部通过;失败项记录到业务矩阵后再进入下一阶段。
## Task 5: 实现管理员踢下线
**Files:**
- Modify: `WonderQ-Admin/app/redis_session.py`
- Modify: `WonderQ-Admin/app/auth.py`
- Modify: `WonderQ-Admin/app/routers/system.py`
- Modify: `WonderQ-Admin/tests/test_admin_rbac.py`
- Create: `WonderQ-Admin/tests/test_admin_sessions.py`
- Modify: `WonderQ-Admin-UI-Vue/src/types.ts`
- Modify: `WonderQ-Admin-UI-Vue/src/api/client.ts`
- Modify: `WonderQ-Admin-UI-Vue/src/api/client-system.test.ts`
- Modify: `WonderQ-Admin-UI-Vue/src/pages/SystemResourcePage.vue`
- [ ] **Step 1: 写会话索引失败测试**
在 `test_admin_sessions.py` 验证:
```python
store = InMemoryAdminSessionStore()
store.create(session_id="s-1", user_id="u-1", access_jti="a-1", refresh_hash="r-1", access_expires_at=future, refresh_expires_at=future)
store.create(session_id="s-2", user_id="u-1", access_jti="a-2", refresh_hash="r-2", access_expires_at=future, refresh_expires_at=future)
assert store.revoke_user_sessions("u-1", except_session_id="s-1") == 1
assert store.is_access_active("s-1", "a-1")
assert not store.is_access_active("s-2", "a-2")
```
同时验证 rotate 保留同一会话索引、revoke 删除索引、重复批量撤销返回 0、Redis 异常抛出 `RedisUnavailableError`。
- [ ] **Step 2: 运行会话失败测试**
Run: `Set-Location WonderQ-Admin; .\.venv\Scripts\python.exe -m pytest tests/test_admin_sessions.py -q`
Expected: FAIL because the protocol and stores do not expose `revoke_user_sessions`。
- [ ] **Step 3: 扩展协议和两种存储实现**
在 `AdminSessionStore` 增加 `list_user_sessions(user_id)` 和 `revoke_user_sessions(user_id, except_session_id=None)`。InMemory 使用 `dict[user_id, set[session_id]]` 或从现有记录派生并在 create/rotate/revoke 中保持一致;Redis 使用 `wonderq:admin:user-sessions:{user_id}` Set,成员仅为 session ID,批量删除 session key、refresh key 和 Set 成员。所有 Redis 客户端异常统一为 `RedisUnavailableError`。
- [ ] **Step 4: 运行会话存储测试并修复轮换回归**
Run: `Set-Location WonderQ-Admin; .\.venv\Scripts\python.exe -m pytest tests/test_admin_sessions.py tests/test_admin_rbac.py -q`
Expected: session index, JWT rotation, logout and existing legacy login fields all PASS。
- [ ] **Step 5: 写踢下线路由失败测试**
使用 FastAPI `TestClient` 和依赖覆盖验证:没有 `system:user:update` 返回 403;目标用户不存在返回 404;目标为当前用户返回 400;成功时排除当前 session、返回撤销数量并写入审计记录;Redis 不可用返回服务错误而不是继续放行。
- [ ] **Step 6: 实现 system 路由与前端 API**
新增 `POST /api/admin/system/users/{user_id}/sessions/revoke`,依赖 `require_permission("system:user:update")`,使用当前 JWT 的 `sid` 作为排除项,调用 `revoke_user_sessions`,审计内容只记录操作者、目标用户和数量。Vue 增加 `revokeSystemUserSessions(userId)`,系统用户行增加“踢下线”确认按钮、提交中禁用和结果提示。
- [ ] **Step 7: 运行踢下线全链路测试**
Run: `Set-Location WonderQ-Admin; .\.venv\Scripts\python.exe -m pytest tests/test_admin_sessions.py tests/test_admin_rbac.py -q`
Run: `Set-Location ..\WonderQ-Admin-UI-Vue; yarn test --run src/api/client-system.test.ts`
Expected: backend and API client tests PASS;浏览器验收时用两个登录会话确认被踢会话的下一次请求收到 401 并回到登录页。
## Task 6: 增加显式 403 页面与错误分流
**Files:**
- Modify: `WonderQ-Admin-UI-Vue/src/api/client.ts`
- Create: `WonderQ-Admin-UI-Vue/src/pages/ForbiddenPage.vue`
- Modify: `WonderQ-Admin-UI-Vue/src/router/index.ts`
- Create: `WonderQ-Admin-UI-Vue/src/router/router.test.ts`
- [ ] **Step 1: 写 403 与 401 分流失败测试**
测试 `request` 收到 403 时抛出 `PermissionDeniedError`,不调用 `/auth/refresh`;收到 401 时仍只刷新一次;路由守卫对已登录无权限页面返回 `forbidden`,未登录返回 `login`,未知路径返回 `not-found`。
- [ ] **Step 2: 运行失败测试**
Run: `Set-Location WonderQ-Admin-UI-Vue; yarn test --run src/router/router.test.ts`
Expected: FAIL because 403 error class、forbidden route and explicit guard branch do not exist。
- [ ] **Step 3: 实现错误类与 403 页面**
新增 `PermissionDeniedError extends ApiRequestError`,状态码固定为 403;`request` 保持 403 原样解包并抛出该错误。新增 `/forbidden` 公共渲染页面,显示无权限、返回上一页和回到首页操作,不清除 Pinia 登录态。
- [ ] **Step 4: 修改路由守卫**
把当前 `return { name: "not-found" }` 的权限分支改为 `return { name: "forbidden", query: { from: to.fullPath } }`;保留动态路由只从 `componentRegistry` 注册。系统资源路由仍根据用户、角色、菜单、部门分别检查权限码。
- [ ] **Step 5: 运行 403、现有测试和构建**
Run: `Set-Location WonderQ-Admin-UI-Vue; yarn test --run; yarn build`
Expected: 403 不触发刷新、不误导为 404,现有测试和构建 PASS。
## Task 7: 完成五级数据范围自动化矩阵
**Files:**
- Modify: `WonderQ-Admin/app/rbac.py`
- Modify: `WonderQ-Admin/app/routers/admin.py`
- Modify: `WonderQ-Admin/tests/test_data_scope_matrix.py`
- Modify: `WonderQ-Admin/tests/test_admin_rbac.py`
- Modify: `docs/admin-api-requirements.md`
- [ ] **Step 1: 建立隔离测试数据**
测试 fixture 创建部门 `dept-a`、子部门 `dept-a-child`、`dept-b`,管理员 `user-a`、`user-b`,并创建带 `deptId` 和 `createdById` 的线索、玩法详情、管家、媒体和首页资源。fixture 每次测试独立回滚,不使用开发数据库真实业务数据。
- [ ] **Step 2: 写五种范围失败测试**
分别为 `all`、`dept`、`dept_and_children`、`custom_dept`、`self` 配置角色,断言列表只返回预期 ID;同时调用详情、更新、删除或状态写入,确认跨范围资源返回 404/403,不能只在前端隐藏。
- [ ] **Step 3: 运行范围矩阵失败测试**
Run: `Set-Location WonderQ-Admin; .\.venv\Scripts\python.exe -m pytest tests/test_data_scope_matrix.py -q`
Expected: 对未覆盖资源或错误的异常状态出现失败,输出具体资源和范围编码。
- [ ] **Step 4: 统一范围过滤与写入检查**
确保 `apply_data_scope` 对目录列表使用部门/创建人 OR 规则;`is_within_data_scope` 用于详情、更新、删除、状态和排序;缺少归属字段的旧记录由既有回填规则处理,不把无归属资源错误暴露给非超级管理员。保留 `admin/super_admin` 的全量兼容行为。
- [ ] **Step 5: 固定 Redis 权限缓存故障语义**
权限缓存读取或写入失败时,允许数据库重新计算权限但不得把异常转换成放行;需要 Redis 会话校验的接口继续拒绝。为 `RedisUnavailableError` 增加测试,确认不能因 Redis 断开返回全量菜单或全量业务数据。
- [ ] **Step 6: 运行范围和后端回归**
Run: `Set-Location WonderQ-Admin; .\.venv\Scripts\python.exe -m pytest tests/test_data_scope_matrix.py tests/test_admin_rbac.py tests/test_home.py -q`
Expected: five-level matrix and affected business tests PASS;与本切片无关的既有 Alembic bootstrap 失败单独记录,不跳过新测试。
## Task 8: 建立 Docker 与正式双入口代理
**Files:**
- Create: `WonderQ-Admin-UI-Vue/Dockerfile`
- Create: `WonderQ-Admin-UI-Vue/nginx.conf`
- Modify: `WonderQ-Admin-UI/Dockerfile`
- Modify: `WonderQ-Admin-UI/nginx.conf`
- Modify: `WonderQ-Admin-UI/vite.config.ts`
- Create: `deploy/nginx/wonderq-admin.conf`
- Modify: `docs/integration-workflow.md`
- [ ] **Step 1: 写代理静态检查**
新建 `scripts/verify-admin-proxy.ps1`,断言 Vue 构建产物引用 `/admin/`,React legacy 构建产物引用 `/admin-legacy/`,Nginx 配置包含 `/api/` proxy、两个 SPA fallback 和独立 `/healthz`,且不存在把 `/api/` fallback 到前端 index 的规则。
- [ ] **Step 2: 运行代理失败检查**
Run: `rg -n "admin-legacy|/admin/|proxy_pass|try_files|location /api" WonderQ-Admin-UI WonderQ-Admin-UI-Vue deploy/nginx`
Expected: 当前 React 入口和 Vue Docker 入口缺少至少一项,检查失败并列出缺失路径。
- [ ] **Step 3: 配置 Vue 静态镜像**
Vue Dockerfile 使用 Node 构建 `yarn build`,Nginx 配置把 `/` 的容器根目录作为 Vue `/admin/` 资源根,并对 `/admin/` history fallback 到 `/admin/index.html`。健康检查使用 `/healthz`,不暴露环境变量。
- [ ] **Step 4: 配置 React legacy base 和镜像**
在 React Vite 配置中读取 `VITE_BASE_PATH`,默认开发仍为 `/`,正式 legacy 构建传入 `/admin-legacy/`;React Nginx 资源根、history fallback 和静态资源链接与该 base 一致。原有 API proxy 开发行为保持不变。
- [ ] **Step 5: 编写统一部署 Nginx 配置**
统一配置明确以下映射:
```nginx
location /api/ { proxy_pass http://wonderq-api:4000; }
location /admin/ { alias /srv/wonderq-vue/; try_files $uri $uri/ /admin/index.html; }
location /admin-legacy/ { alias /srv/wonderq-react/; try_files $uri $uri/ /admin-legacy/index.html; }
```
实际文件路径按镜像挂载目录调整,但必须保留三条逻辑边界,且 `/api/` 位于 SPA fallback 之前并单独转发。
- [ ] **Step 6: 构建并验证容器**
Run: `docker compose config`
Run: `docker build -t wonderq-admin-ui-vue:acceptance .\WonderQ-Admin-UI-Vue`
Run: `docker build --build-arg VITE_BASE_PATH=/admin-legacy/ -t wonderq-admin-ui-react-legacy:acceptance .\WonderQ-Admin-UI`
Run: `docker compose up -d postgres redis`
Run: `Set-Location WonderQ-Admin; .\.venv\Scripts\python.exe -m alembic upgrade head`
Expected: 两个前端镜像、PostgreSQL、Redis 构建/启动成功;`http://localhost:4000/health` 和两个静态入口可访问。若当前 Compose 不包含 API 服务,只执行现有 API 启动命令并记录边界。
## Task 9: 三端回归、人工验收与 React 删除闸门
**Files:**
- Modify: `docs/admin-business-function-matrix.md`
- Modify: `docs/integration-workflow.md`
- Read-only verification: `WonderQ-MiniAPP/src/lib/api.ts`, `WonderQ-Admin-UI/src/api.ts`
- [ ] **Step 1: 运行后端完整回归**
Run: `Set-Location WonderQ-Admin; .\.venv\Scripts\python.exe -m pytest -q`
Expected: 新增切片全部通过;既有 Alembic 兼容失败必须保留完整测试名和原因,不能用“部分通过”掩盖。
- [ ] **Step 2: 运行两套管理端回归**
Run: `Set-Location ..\WonderQ-Admin-UI-Vue; yarn test --run; yarn build`
Run: `Set-Location ..\WonderQ-Admin-UI; yarn build`
Expected: Vue 测试/构建和 React 构建通过;旧 React API 路径、登录响应字段和刷新逻辑没有被删改。
- [ ] **Step 3: 做 MiniAPP Public API 兼容检查**
确认 `WonderQ-MiniAPP/src/lib/api.ts` 仍只访问 `/api/public/...`,首页 `experiences`、团队共创和极境视界字段与 `docs/public-api.md` 一致。只有 Public API 或字段发生变化时才运行 MiniAPP 类型检查和测试。
- [ ] **Step 4: 人工验收顺序**
使用独立测试账号逐项完成:
1. 四组首页新建、编辑、删除、排序、启停和错误重试;
2. 两个登录会话验证踢下线;
3. 无页面权限 403、无按钮权限和直接 API 403;
4. 五种数据范围跨部门列表与写入;
5. Redis 异常、Docker、`/admin/`、`/admin-legacy/` 和深链刷新;
6. Public API/MiniAPP 回归;
7. 最后进行 OSS/媒体上传验收。
每项将实际 URL、操作结果和失败证据写入 `docs/admin-business-function-matrix.md`,不记录账号密码、Token 或密钥。
- [ ] **Step 5: 判断 React 删除闸门**
只有 Vue 功能矩阵、Public/Admin API 回归、RBAC 五级范围、会话/Redis、Docker/代理、OSS/媒体和人工验收全部通过,并收到用户明确“删除旧 React”确认,才另开删除计划。当前计划结束时保留 React 源码、依赖、构建入口和 `/admin-legacy/`。
## 验证与交付规则
- 每个 Task 完成后先运行其局部测试,再运行受影响端的构建;失败时先定位根因,不以扩大改动规避测试。
- 不执行 `git reset`、`git checkout --`、批量删除或覆盖用户现有改动。
- 不修改 `.env`、`.env.local`、真实 Docker secrets、OSS 凭证、生产数据库连接或生产部署变量。
- 不新增 Alembic 迁移;会话索引只使用 Redis。若实现过程中发现确需数据库结构变化,暂停并单独征求确认。
- 不自动提交或删除 React;阶段结果以 `git diff --check`、测试输出和业务矩阵记录作为检查点。