# 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代理待填写_