# Story 010.006: Web集成和Server模块替换 ## Status Ready for Review ## Story **As a** 超级管理员, **I want** 将统一广告管理集成到租户后台,并从各租户admin后台移除广告管理功能,Server包切换到统一广告模块, **so that** 可以在租户后台统一管理所有租户的广告,所有租户用户端展示相同的广告数据,且小程序端无需任何修改。 ## Acceptance Criteria 1. 租户后台(超级管理员专用)添加广告管理菜单项和路由 2. 租户后台API客户端初始化,连接到统一广告模块的管理员API 3. Admin后台移除广告管理和广告类型管理菜单项和路由 4. **Server包替换模块**: 将 `@d8d/advertisements-module-mt` 替换为 `@d8d/unified-advertisements-module` 5. **保持API兼容性**: `/api/v1/advertisements` 和 `/api/v1/advertisement-types` 路由保持不变 6. **数据源切换**: 注册 `UnifiedAdvertisement` 和 `UnifiedAdvertisementType` 实体 7. **E2E测试验证**: 验证租户后台可管理广告,验证小程序端API兼容性100% 8. 权限控制正确(只有超级管理员ID=1可管理统一广告) ## Tasks / Subtasks - [x] **任务1: 租户后台集成统一广告管理UI** (AC: 1, 2, 8) - [x] 在 `web/src/client/tenant/routes.tsx` 添加广告管理路由 - [x] 在 `web/src/client/tenant/layouts/MainLayout.tsx` 添加菜单项(广告管理、广告类型管理) - [x] 确认 `web/src/client/tenant/api_init.ts` 中API客户端正确配置 - [x] 测试租户后台广告管理功能可访问 - [x] **任务2: Admin后台移除广告管理功能** (AC: 3) - [x] 从 `web/src/client/admin/routes.tsx` 移除广告管理路由 - [x] 从 `web/src/client/admin/layouts/MainLayout.tsx` 移除菜单项 - [x] 移除 `@d8d/advertisement-management-ui-mt` 和 `@d8d/advertisement-type-management-ui-mt` 的导入 - [x] 验证Admin后台不再显示广告管理入口 - [x] **任务3: Server包替换模块引用** (AC: 4, 5, 6) - [x] 在 `packages/server/src/index.ts` 中: - [x] 替换导入:`@d8d/advertisements-module-mt` → `@d8d/unified-advertisements-module` - [x] 替换实体:`Advertisement, AdvertisementType` → `UnifiedAdvertisement, UnifiedAdvertisementType` - [x] 替换路由:`advertisementRoutes, advertisementTypeRoutes` → `unifiedAdvertisementRoutes, unifiedAdvertisementTypeRoutes` - [x] 添加管理员路由导入:`unifiedAdvertisementAdminRoutes, unifiedAdvertisementTypeAdminRoutes` - [x] 在 `initializeDataSource` 中注册新实体 - [x] 在 `packages/server/src/data-source.ts` 中: - [x] 替换实体导入和注册 - [x] 在 `packages/server/package.json` 中: - [x] 替换依赖:`@d8d/advertisements-module-mt` → `@d8d/unified-advertisements-module` - [x] 验证类型检查通过:`cd packages/server && pnpm typecheck` - [x] **任务4: Server包注册统一广告管理员路由** (AC: 1, 5, 8) - [x] 在 `packages/server/src/index.ts` 添加管理员路由注册: - [x] `/api/v1/admin/unified-advertisements` → `unifiedAdvertisementAdminRoutes` - [x] `/api/v1/admin/unified-advertisement-types` → `unifiedAdvertisementTypeAdminRoutes` - [x] 验证管理员路由使用 `tenantAuthMiddleware`(仅超级管理员可访问) - [x] **任务5: E2E测试验证API兼容性** (AC: 5, 7) - [x] 创建 `web/tests/e2e/unified-advertisement-api.spec.ts` 测试文件 - [x] 测试 `/api/v1/advertisements` 端点返回统一广告数据 - [x] 测试 `/api/v1/advertisement-types` 端点返回统一广告类型数据 - [x] 验证响应结构与原模块完全一致(字段名、类型、格式) - [x] **任务6: 集成测试验证管理员权限** (AC: 8) - [x] 创建 `packages/server/tests/integration/unified-advertisement-auth.integration.test.ts` - [x] 测试普通租户用户无法访问管理员API(应返回403) - [x] 测试超级管理员(ID=1)可以访问管理员API - [x] 测试认证用户可以访问用户端API(获取统一广告数据) - [x] **任务7: 更新史诗010文档** (AC: 完成) - [x] 在 `docs/prd/epic-010-unified-ad-management.md` 中标记故事010.006为完成 - [x] 记录模块切换的详细信息和验证结果 - [x] 更新兼容性验证结果(小程序端API 100%兼容) ## Dev Notes ### 前一故事关键要点(来自 010.005) **测试成果**: - 创建了 51 个通过的集成测试,覆盖率87.33% statements - 统一广告管理UI包已完成完整的功能测试 - 所有测试使用 `data-testid` 进行可靠的元素选择 **技术要点**: - **RPC客户端**: UI包使用 `@hono/zod-openapi` 的类型推断,通过 `hc.rpc()` 获取类型安全的客户端 - **API路径**: 统一广告模块的管理员API端点为 `/api/v1/admin/unified-advertisements` - **权限中间件**: 管理员路由使用 `tenantAuthMiddleware`,只有超级管理员(ID=1)可访问 ### 项目结构映射 **Server包集成**: ``` packages/server/src/ ├── index.ts # 主入口 - 需要修改 │ ├── 导入模块: @d8d/advertisements-module-mt → @d8d/unified-advertisements-module │ ├── 实体注册: Advertisement → UnifiedAdvertisement │ └── 路由注册: advertisementRoutes → unifiedAdvertisementRoutes └── data-source.ts # 数据源配置 - 需要修改 └── 实体列表: Advertisement → UnifiedAdvertisement ``` **租户后台集成**: ``` web/src/client/tenant/ ├── routes.tsx # 路由配置 - 需要添加广告管理路由 └── layouts/MainLayout.tsx # 布局组件 - 需要添加菜单项 ``` **Admin后台移除**: ``` web/src/client/admin/ ├── routes.tsx # 路由配置 - 需要移除广告管理路由 └── layouts/MainLayout.tsx # 布局组件 - 需要移除菜单项 ``` ### API端点映射(保持兼容) **用户端API(小程序使用,保持不变)**: ```typescript // 当前: advertisements-module-mt // 切换后: unified-advertisements-module // 路由路径和响应结构100%兼容 GET /api/v1/advertisements // 获取广告列表 GET /api/v1/advertisements/:id // 获取广告详情 GET /api/v1/advertisement-types // 获取广告类型列表 ``` **管理员API(新增,租户后台使用)**: ```typescript // 使用 tenantAuthMiddleware,仅超级管理员ID=1可访问 GET /api/v1/admin/unified-advertisements // 广告列表 POST /api/v1/admin/unified-advertisements // 创建广告 PUT /api/v1/admin/unified-advertisements/:id // 更新广告 DELETE /api/v1/admin/unified-advertisements/:id // 删除广告 GET /api/v1/admin/unified-advertisement-types // 广告类型列表 POST /api/v1/admin/unified-advertisement-types // 创建广告类型 PUT /api/v1/admin/unified-advertisement-types/:id // 更新广告类型 DELETE /api/v1/admin/unified-advertisement-types/:id // 删除广告类型 ``` ### 模块导入路径 **统一广告模块导出** [Source: packages/unified-advertisements-module/src/index.ts]: ```typescript // 实体 export { UnifiedAdvertisement } from './entities/unified-advertisement.entity'; export { UnifiedAdvertisementType } from './entities/unified-advertisement-type.entity'; // 管理员路由(新增,租户后台使用) export { default as unifiedAdvertisementAdminRoutes } from './routes/admin/unified-advertisements.admin.routes'; export { default as unifiedAdvertisementTypeAdminRoutes } from './routes/admin/unified-advertisement-types.admin.routes'; // 用户路由(与原模块API兼容) export { default as unifiedAdvertisementRoutes } from './routes/unified-advertisements.routes'; export { default as unifiedAdvertisementTypeRoutes } from './routes/unified-advertisement-types.routes'; ``` **Server包修改示例**: ```typescript // packages/server/src/index.ts // ===== 旧代码(删除)===== import { Advertisement, AdvertisementType } from '@d8d/advertisements-module-mt'; import { advertisementRoutes, advertisementTypeRoutes } from '@d8d/advertisements-module-mt'; // ===== 新代码(使用)===== import { UnifiedAdvertisement, UnifiedAdvertisementType } from '@d8d/unified-advertisements-module'; import { unifiedAdvertisementRoutes, unifiedAdvertisementTypeRoutes, unifiedAdvertisementAdminRoutes, // 新增:管理员路由 unifiedAdvertisementTypeAdminRoutes // 新增:管理员类型路由 } from '@d8d/unified-advertisements-module'; // ===== 数据源注册 ===== initializeDataSource([ // ...其他实体 // 旧: Advertisement, AdvertisementType, 新: UnifiedAdvertisement, UnifiedAdvertisementType, ]); // ===== 路由注册 - 用户端(保持路径不变)===== export const advertisementApiRoutes = api.route('/api/v1/advertisements', unifiedAdvertisementRoutes); export const advertisementTypeApiRoutes = api.route('/api/v1/advertisement-types', unifiedAdvertisementTypeRoutes); // ===== 路由注册 - 管理员端(新增)===== export const adminUnifiedAdApiRoutes = api.route('/api/v1/admin/unified-advertisements', unifiedAdvertisementAdminRoutes); export const adminUnifiedAdTypeApiRoutes = api.route('/api/v1/admin/unified-advertisement-types', unifiedAdvertisementTypeAdminRoutes); ``` ### 租户后台集成示例 **路由配置** [Source: web/src/client/tenant/routes.tsx]: ```typescript // 添加广告管理路由 import { UnifiedAdvertisementManagement } from '@d8d/unified-advertisement-management-ui'; export const router = createBrowserRouter([ // ... { path: '/tenant', element: , children: [ // ... { path: 'unified-advertisements', element: , errorElement: }, // ... ], }, ]); ``` ### Admin后台移除示例 **路由配置** [Source: web/src/client/admin/routes.tsx]: ```typescript // 移除以下导入 // import { AdvertisementManagement } from '@d8d/advertisement-management-ui-mt'; // import { AdvertisementTypeManagement } from '@d8d/advertisement-type-management-ui-mt'; // 移除以下路由配置 // { // path: 'advertisements', // element: , // }, // { // path: 'advertisement-types', // element: , // }, ``` ### 权限控制验证 **tenantAuthMiddleware** [Source: docs/prd/epic-010-unified-ad-management.md]: - 只有超级管理员(tenantId=1, userId=1)可以访问管理员API - 管理员路由必须在server包中正确注册中间件 - 用户端路由使用 `authMiddleware` 进行多租户认证,但返回统一的广告数据(无tenant_id过滤) ### 测试策略 **E2E测试重点** [Source: docs/architecture/testing-strategy.md]: - 验证API端点路径保持不变 - 验证响应结构100%兼容 - 验证小程序端无需任何修改 **集成测试重点** [Source: docs/architecture/web-server-testing-standards.md]: - 验证管理员权限控制 - 验证数据源切换正确 - 验证新实体可以正常操作 ### 关键注意事项 1. **API兼容性优先**: 小程序端使用的API端点必须保持100%兼容,路由路径、请求参数、响应结构都不能变化 2. **权限控制严格**: 管理员API必须使用 `tenantAuthMiddleware`,确保只有超级管理员可访问 3. **数据源切换**: 确保TypeORM实体正确注册,避免运行时错误 4. **回滚准备**: 保留 `@d8d/advertisements-module-mt` 包不动,便于回滚 5. **测试验证**: E2E测试必须验证小程序端API兼容性 ### 参考 - [后端模块包开发规范](../architecture/backend-module-package-standards.md) - [UI包开发规范](../architecture/ui-package-standards.md) - [Web Server测试规范](../architecture/web-server-testing-standards.md) - [测试策略概述](../architecture/testing-strategy.md) - [API设计和集成](../architecture/api-design-integration.md) - [源码树和文件组织](../architecture/source-tree.md) ## Testing ### 测试文件位置 - E2E测试: `web/tests/e2e/unified-advertisement-api.spec.ts` - 集成测试: `packages/server/tests/integration/unified-advertisement-auth.integration.test.ts` ### 测试框架 - **E2E**: Playwright (chromium) - **集成测试**: Vitest + hono/testing ### 测试标准 | 测试类型 | 覆盖率要求 | 重点验证 | |----------|------------|----------| | E2E测试 | 关键流程100% | API兼容性 | | 集成测试 | ≥60% | 权限控制、数据源 | ### 测试执行命令 ```bash # E2E测试(验证API兼容性) cd web && pnpm test:e2e:chromium # 集成测试(验证权限控制) cd packages/server && pnpm test # 类型检查 cd packages/server && pnpm typecheck ``` ## Change Log | Date | Version | Description | Author | |------|---------|-------------|--------| | 2026-01-03 | 1.0 | 初始故事创建 | James (Claude Code) | | 2026-01-03 | 1.1 | 故事完成 - Ready for Review | James (Claude Code) | | 2026-01-03 | 1.2 | 修复集成测试 - testClient调用方式修正 | James (Claude Code) | | 2026-01-03 | 1.3 | 修复集成测试 - 全部17个测试通过 | James (Claude Code) | | 2026-01-03 | 1.4 | E2E测试全部通过(50 passed, 5 skipped),更新测试策略文档 | James (Claude Code) | ## Dev Agent Record ### Agent Model Used claude-opus-4-5-20251101 (d8d-model) ### Debug Log References - **集成测试修复 (第一轮)**: 修复 `testClient` 调用方式 - 问题:直接从模块导入路由导致数据源未初始化 - 解决:从 server 包的 `../../src/api` 导入路由 - 问题:headers 作为第一个参数传递不符合 API 规范 - 解决:将 headers 移到第二个参数 - **集成测试修复 (第二轮)**: 修复API响应格式和数据清理 - 问题:API返回 `{ code, message, data: { list, total } }` 而不是直接的数组 - 解决:更新测试断言检查 `data.data.list` 而不是直接检查数组 - 问题:创建广告时缺少必填字段 `typeId` 和 `code` - 解决:添加必填字段并在测试中先创建广告类型 - 问题:数据清理使用 `delete({})` 和 `clear()` 方法有外键约束问题 - 解决:使用 repository 的 `find()` + `remove()` 方法逐个删除 - **集成测试修复 (第三轮)**: 修复测试生命周期和用户ID设置 - 问题:`afterEach` 销毁数据源导致后续测试表不存在 - 解决:使用 `beforeAll` 和 `afterAll` 而不是 `beforeEach` 和 `afterEach` - 问题:用户ID设置在 `save()` 后没有正确应用到数据库 - 解决:使用 query builder 直接更新用户ID为1 ### Completion Notes List 1. **租户后台集成完成**: 添加了广告管理和广告类型管理的路由、菜单项和API客户端初始化 2. **Admin后台移除完成**: 从admin后台移除了广告管理相关的路由和菜单项 3. **Server包模块替换完成**: 成功将 `@d8d/advertisements-module-mt` 替换为 `@d8d/unified-advertisements-module` 4. **API兼容性保持**: 用户端API路径 `/api/v1/advertisements` 和 `/api/v1/advertisement-types` 保持不变 5. **管理员路由新增**: 添加了 `/api/v1/admin/unified-advertisements` 和 `/api/v1/admin/unified-advertisement-types` 路由 6. **测试文件创建**: 创建了E2E测试和集成测试文件 7. **类型检查通过**: server包类型检查通过,无新增错误 8. **集成测试全部通过**: 17个测试用例全部通过 - 权限控制测试全部通过 - API路径兼容性验证通过 - CRUD操作测试通过 9. **E2E测试全部通过**: 50个测试通过,5个跳过 - 修复了Playwright配置(testDir从'./specs'改为'.') - 添加了JWT认证逻辑到E2E测试 - 修复了API响应格式解析(result.data.list) - 创建了测试租户和测试用户数据 10. **测试文档更新**: - 创建了E2E测试规范文档 `docs/architecture/e2e-testing-standards.md` - 更新了测试策略文档 `docs/architecture/testing-strategy.md` (v3.1→v3.2) ### File List **修改的文件**: - `web/src/client/tenant/routes.tsx` - 添加广告管理路由 - `web/src/client/tenant/menu.tsx` - 添加广告管理菜单项 - `web/src/client/tenant/api_init.ts` - 初始化API客户端 - `web/src/client/admin/routes.tsx` - 移除广告管理路由 - `web/src/client/admin/menu.tsx` - 移除广告管理菜单项 - `packages/server/src/index.ts` - 替换模块导入和路由注册 - `packages/server/src/data-source.ts` - 替换实体注册 - `packages/server/package.json` - 替换依赖包 - `docs/prd/epic-010-unified-ad-management.md` - 更新史诗文档 - `web/tests/e2e/playwright.config.ts` - 修复testDir配置 - `web/tests/e2e/unified-advertisement-api.spec.ts` - 添加认证和修复响应解析 - `docs/architecture/testing-strategy.md` - 添加E2E测试规范引用 **新增的文件**: - `packages/server/tests/integration/unified-advertisement-auth.integration.test.ts` - 管理员权限集成测试 - `docs/architecture/e2e-testing-standards.md` - E2E测试规范文档 ## QA Results _QA代理待填写_