010.006.story.md 17 KB

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. 数据源切换: 注册 UnifiedAdvertisementUnifiedAdvertisementType 实体
  7. E2E测试验证: 验证租户后台可管理广告,验证小程序端API兼容性100%
  8. 权限控制正确(只有超级管理员ID=1可管理统一广告)

Tasks / Subtasks

  • [x] 任务1: 租户后台集成统一广告管理UI (AC: 1, 2, 8)

    • web/src/client/tenant/routes.tsx 添加广告管理路由
    • web/src/client/tenant/layouts/MainLayout.tsx 添加菜单项(广告管理、广告类型管理)
    • 确认 web/src/client/tenant/api_init.ts 中API客户端正确配置
    • 测试租户后台广告管理功能可访问
  • [x] 任务2: Admin后台移除广告管理功能 (AC: 3)

    • web/src/client/admin/routes.tsx 移除广告管理路由
    • web/src/client/admin/layouts/MainLayout.tsx 移除菜单项
    • 移除 @d8d/advertisement-management-ui-mt@d8d/advertisement-type-management-ui-mt 的导入
    • 验证Admin后台不再显示广告管理入口
  • [x] 任务3: Server包替换模块引用 (AC: 4, 5, 6)

    • packages/server/src/index.ts 中:
    • 替换导入:@d8d/advertisements-module-mt@d8d/unified-advertisements-module
    • 替换实体:Advertisement, AdvertisementTypeUnifiedAdvertisement, UnifiedAdvertisementType
    • 替换路由:advertisementRoutes, advertisementTypeRoutesunifiedAdvertisementRoutes, unifiedAdvertisementTypeRoutes
    • 添加管理员路由导入:unifiedAdvertisementAdminRoutes, unifiedAdvertisementTypeAdminRoutes
    • initializeDataSource 中注册新实体
    • packages/server/src/data-source.ts 中:
    • 替换实体导入和注册
    • packages/server/package.json 中:
    • 替换依赖:@d8d/advertisements-module-mt@d8d/unified-advertisements-module
    • 验证类型检查通过:cd packages/server && pnpm typecheck
  • [x] 任务4: Server包注册统一广告管理员路由 (AC: 1, 5, 8)

    • packages/server/src/index.ts 添加管理员路由注册:
    • /api/v1/admin/unified-advertisementsunifiedAdvertisementAdminRoutes
    • /api/v1/admin/unified-advertisement-typesunifiedAdvertisementTypeAdminRoutes
    • 验证管理员路由使用 tenantAuthMiddleware(仅超级管理员可访问)
  • [x] 任务5: E2E测试验证API兼容性 (AC: 5, 7)

    • 创建 web/tests/e2e/unified-advertisement-api.spec.ts 测试文件
    • 测试 /api/v1/advertisements 端点返回统一广告数据
    • 测试 /api/v1/advertisement-types 端点返回统一广告类型数据
    • 验证响应结构与原模块完全一致(字段名、类型、格式)
  • [x] 任务6: 集成测试验证管理员权限 (AC: 8)

    • 创建 packages/server/tests/integration/unified-advertisement-auth.integration.test.ts
    • 测试普通租户用户无法访问管理员API(应返回403)
    • 测试超级管理员(ID=1)可以访问管理员API
    • 测试认证用户可以访问用户端API(获取统一广告数据)
  • [x] 任务7: 更新史诗010文档 (AC: 完成)

    • docs/prd/epic-010-unified-ad-management.md 中标记故事010.006为完成
    • 记录模块切换的详细信息和验证结果
    • 更新兼容性验证结果(小程序端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(小程序使用,保持不变):

// 当前: advertisements-module-mt
// 切换后: unified-advertisements-module
// 路由路径和响应结构100%兼容

GET    /api/v1/advertisements          // 获取广告列表
GET    /api/v1/advertisements/:id      // 获取广告详情
GET    /api/v1/advertisement-types     // 获取广告类型列表

管理员API(新增,租户后台使用):

// 使用 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]:

// 实体
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包修改示例:

// 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]:

// 添加广告管理路由
import { UnifiedAdvertisementManagement } from '@d8d/unified-advertisement-management-ui';

export const router = createBrowserRouter([
  // ...
  {
    path: '/tenant',
    element: <ProtectedRoute><MainLayout /></ProtectedRoute>,
    children: [
      // ...
      {
        path: 'unified-advertisements',
        element: <UnifiedAdvertisementManagement />,
        errorElement: <ErrorPage />
      },
      // ...
    ],
  },
]);

Admin后台移除示例

路由配置 [Source: web/src/client/admin/routes.tsx]:

// 移除以下导入
// import { AdvertisementManagement } from '@d8d/advertisement-management-ui-mt';
// import { AdvertisementTypeManagement } from '@d8d/advertisement-type-management-ui-mt';

// 移除以下路由配置
// {
//   path: 'advertisements',
//   element: <AdvertisementManagement />,
// },
// {
//   path: 'advertisement-types',
//   element: <AdvertisementTypeManagement />,
// },

权限控制验证

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兼容性

参考

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% 权限控制、数据源

测试执行命令

# 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 而不是直接检查数组
    • 问题:创建广告时缺少必填字段 typeIdcode
    • 解决:添加必填字段并在测试中先创建广告类型
    • 问题:数据清理使用 delete({})clear() 方法有外键约束问题
    • 解决:使用 repository 的 find() + remove() 方法逐个删除
  • 集成测试修复 (第三轮): 修复测试生命周期和用户ID设置

    • 问题:afterEach 销毁数据源导致后续测试表不存在
    • 解决:使用 beforeAllafterAll 而不是 beforeEachafterEach
    • 问题:用户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代理待填写