Просмотр исходного кода

✨ feat(stories): 创建故事 010.001 - 统一广告后端模块

创建统一广告后端模块 (unified-advertisements-module):
- 移除 tenant_id 字段,实现统一广告数据管理
- 管理员路由使用 tenantAuthMiddleware(超级管理员专用)
- 用户展示路由使用 authMiddleware,返回统一数据
- API 100% 兼容原模块,小程序端无感知

🤖 Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
yourname 2 недель назад
Родитель
Сommit
544f15dda2
1 измененных файлов с 348 добавлено и 0 удалено
  1. 348 0
      docs/stories/010.001.story.md

+ 348 - 0
docs/stories/010.001.story.md

@@ -0,0 +1,348 @@
+# Story 010.001: 创建统一广告后端模块
+
+## Status
+Approved
+
+## Story
+
+**As a** 超级管理员,
+**I want** 一个统一的后端广告模块(无租户隔离),
+**so that** 可以在租户管理后台统一管理所有广告,所有租户用户端看到相同的广告数据。
+
+## Acceptance Criteria
+
+1. 创建 `packages/unified-advertisements-module` 包,包含完整的Entity、Service、Schema、Routes结构
+2. Entity定义不包含 `tenant_id` 字段,与原 `advertisements-module-mt` 的Entity结构相同但移除租户隔离
+3. 实现管理员路由(使用 `tenantAuthMiddleware`),只有超级管理员(ID=1)可访问
+4. 实现用户展示路由(使用 `authMiddleware`),返回统一的广告数据给所有租户
+5. API路由路径、请求参数、响应结构与原模块保持100%兼容,确保小程序端无感知
+6. 关联文件模块(`@d8d/core-module-mt/file-module-mt`),使用 `FileMt` 实体
+7. 包含完整的单元测试和集成测试
+
+## Tasks / Subtasks
+
+- [ ] **任务1: 创建包结构和配置文件** (AC: 1)
+  - [ ] 创建 `packages/unified-advertisements-module` 目录
+  - [ ] 创建 `package.json`,配置包名为 `@d8d/unified-advertisements-module`
+  - [ ] 创建 `tsconfig.json`
+  - [ ] 创建 `vitest.config.ts`(设置 `fileParallelism: false`)
+  - [ ] 创建 `src/` 子目录:`entities/`, `services/`, `routes/`, `schemas/`
+  - [ ] 创建 `tests/` 子目录:`integration/`, `utils/`
+
+- [ ] **任务2: 定义Entity(无tenant_id字段)** (AC: 2)
+  - [ ] 创建 `src/entities/unified-advertisement.entity.ts`,参考 `advertisements-module-mt` 但移除 `tenant_id` 字段
+  - [ ] 创建 `src/entities/unified-advertisement-type.entity.ts`
+  - [ ] 配置Entity关联:`@ManyToOne` 关联 `FileMt`(使用核心包路径)和 `AdvertisementType`
+  - [ ] 创建 `src/entities/index.ts` 导出所有Entity
+
+- [ ] **任务3: 实现Service层** (AC: 2, 7)
+  - [ ] 创建 `src/services/unified-advertisement.service.ts`,继承 `GenericCrudService`
+  - [ ] 创建 `src/services/unified-advertisement-type.service.ts`
+  - [ ] 覆盖 `create`、`update`、`delete` 方法(使用 `override` 关键字)
+  - [ ] 实现软删除逻辑(设置 `status=0`)
+  - [ ] 创建 `src/services/index.ts` 导出所有Service
+
+- [ ] **任务4: 定义Schema** (AC: 5)
+  - [ ] 创建 `src/schemas/unified-advertisement.schema.ts`,使用 Zod + OpenAPI装饰器
+  - [ ] 创建 `src/schemas/unified-advertisement-type.schema.ts`
+  - [ ] 使用 `z.coerce.date<Date>()` 和 `z.coerce.number<number>()` 泛型语法
+  - [ ] 定义 `Create*Dto`、`Update*Dto`、`*ListResponseSchema`
+  - [ ] 不导出推断类型(`z.infer`),类型由RPC自动推断
+  - [ ] 创建 `src/schemas/index.ts` 导出所有Schema
+
+- [ ] **任务5: 实现管理员路由(超级管理员专用)** (AC: 3)
+  - [ ] 创建 `src/routes/admin/unified-advertisements.admin.routes.ts`
+  - [ ] 使用 `OpenAPIHono` 和 `AuthContext` 泛型
+  - [ ] 使用 `createRoute` 定义路由,包含请求/响应Schema
+  - [ ] 应用 `tenantAuthMiddleware` 中间件(来自 `@d8d/tenant-module-mt`,独立包)
+  - [ ] 自定义路由使用 `parseWithAwait` 验证响应数据
+  - [ ] 使用 `createZodErrorResponse` 处理Zod错误
+  - [ ] 400响应使用 `ZodErrorSchema`,其他错误使用 `ErrorSchema`
+
+- [ ] **任务6: 实现用户展示路由(与原模块保持一致)** (AC: 4, 5)
+  - [ ] 创建 `src/routes/unified-advertisements.routes.ts`
+  - [ ] 使用 `authMiddleware` 中间件(来自 `@d8d/core-module-mt/auth-module-mt`)
+  - [ ] 路由结构与原模块完全一致:`GET /api/v1/advertisements`、`GET /api/v1/advertisements/:id`
+  - [ ] Schema响应结构与原 `advertisements-module-mt` 一致
+  - [ ] 不使用 `tenantOptions`,返回统一数据给所有租户
+
+- [ ] **任务7: 创建包导出入口** (AC: 1)
+  - [ ] 创建 `src/index.ts`,导出Entities、Services、Routes、Schemas
+  - [ ] 配置 `package.json` 的 `exports` 字段,支持子路径导出
+
+- [ ] **任务8: 编写单元测试** (AC: 7)
+  - [ ] 创建 `tests/utils/test-data-factory.ts`
+  - [ ] 创建Service层单元测试
+  - [ ] 创建Schema验证测试
+  - [ ] 使用时间戳保证测试数据唯一性
+
+- [ ] **任务9: 编写集成测试** (AC: 7)
+  - [ ] 创建 `tests/integration/unified-advertisements.integration.test.ts`
+  - [ ] 测试管理员CRUD操作(验证 `tenantAuthMiddleware` 权限)
+  - [ ] 测试用户展示接口(验证返回统一数据)
+  - [ ] 测试API响应结构与原模块一致
+
+- [ ] **任务10: 代码质量检查** (AC: 1, 7)
+  - [ ] 运行 `pnpm typecheck` 确保无TypeScript错误
+  - [ ] 运行 `pnpm lint` 确保代码符合规范
+  - [ ] 运行 `pnpm test` 确保所有测试通过
+  - [ ] 运行 `pnpm test:coverage` 确保覆盖率达标
+
+## Dev Notes
+
+### 项目结构信息
+
+**新包位置**:
+```
+packages/unified-advertisements-module/
+├── package.json
+├── tsconfig.json
+├── vitest.config.ts
+├── src/
+│   ├── entities/
+│   │   ├── unified-advertisement.entity.ts
+│   │   ├── unified-advertisement-type.entity.ts
+│   │   └── index.ts
+│   ├── services/
+│   │   ├── unified-advertisement.service.ts
+│   │   ├── unified-advertisement-type.service.ts
+│   │   └── index.ts
+│   ├── routes/
+│   │   ├── admin/
+│   │   │   └── unified-advertisements.admin.routes.ts
+│   │   ├── unified-advertisements.routes.ts
+│   │   ├── unified-advertisement-types.routes.ts
+│   │   └── index.ts
+│   ├── schemas/
+│   │   ├── unified-advertisement.schema.ts
+│   │   ├── unified-advertisement-type.schema.ts
+│   │   └── index.ts
+│   └── index.ts
+└── tests/
+    ├── integration/
+    │   └── unified-advertisements.integration.test.ts
+    └── utils/
+        └── test-data-factory.ts
+```
+
+**参考模块**:
+- 原多租户广告模块: `packages/advertisements-module-mt`
+- 认证模块: `@d8d/core-module-mt/auth-module-mt`
+- 租户模块: `@d8d/core-module-mt/tenant-module-mt`
+- 文件模块: `@d8d/core-module-mt/file-module-mt`
+
+### Entity设计规范
+
+**统一广告Entity** (`unified-advertisement.entity.ts`):
+- 继承自原 `advertisements-module-mt` 的Entity结构
+- **关键区别**: 无 `tenant_id` 字段
+- 使用 `@ManyToOne` 关联 `FileMt`(从核心包路径引用)
+- 使用 `@ManyToOne` 关联 `UnifiedAdvertisementType`
+- 字段包括:`id`, `title`, `typeId`, `code`, `url`, `imageFileId`, `sort`, `status`, `actionType`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy`
+
+**核心包引用规范** [Source: docs/architecture/backend-module-package-standards.md#核心包引用规范]:
+```typescript
+// ✅ 正确:从核心包路径引用
+import { FileMt } from '@d8d/core-module-mt/file-module-mt';
+
+// ❌ 错误:直接从桥接包引用
+import { FileMt } from '@d8d/file-module-mt';
+```
+
+### 路由设计规范
+
+**管理员接口** (新增):
+```
+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   # 删除广告类型
+```
+
+**用户展示接口** (保持不变 - 小程序端使用):
+```
+GET    /api/v1/advertisements          # 获取有效广告列表(不变)
+GET    /api/v1/advertisements/:id      # 获取单个广告详情(不变)
+GET    /api/v1/advertisement-types     # 获取广告类型列表(不变)
+```
+
+**关键设计**: 小程序端无需任何改动,API契约100%兼容。只是后端数据源从多租户切换到统一模块。
+
+### 中间件使用规范
+
+**管理员路由**:
+- 使用 `tenantAuthMiddleware`(来自 `@d8d/tenant-module-mt`,独立包)
+- 只有超级管理员(ID=1)可访问
+- 代码引用:`import { tenantAuthMiddleware } from '@d8d/tenant-module-mt';`
+- 参考: `packages/tenant-module-mt/src/middleware/`
+
+**用户展示路由**:
+- 使用 `authMiddleware`(来自 `@d8d/core-module-mt/auth-module-mt`)
+- 进行多租户认证,但返回统一数据(无tenant_id过滤)
+- 代码引用:`import { authMiddleware } from '@d8d/core-module-mt/auth-module-mt';`
+
+### Schema规范
+
+**Zod 4.0 coerce使用** [Source: docs/architecture/backend-module-package-standards.md#Schema规范]:
+```typescript
+// ✅ 正确:Zod 4.0 - 使用泛型指定类型
+z.coerce.date<Date>()       // 转换为Date类型
+z.coerce.number<number>()    // 转换为number类型
+
+// ❌ 错误:不指定泛型(Zod 4.0中类型推断可能不准确)
+z.coerce.date()
+z.coerce.number()
+```
+
+**不导出推断类型** [Source: docs/architecture/backend-module-package-standards.md#类型使用说明]:
+- Schema只用于请求参数验证和响应定义
+- **不需要导出推断的TypeScript类型**(`z.infer<typeof Schema>`)
+- UI包通过RPC直接从API路由推断类型
+
+### 测试规范
+
+**测试配置** [Source: docs/architecture/backend-module-package-standards.md#测试配置]:
+```typescript
+// vitest.config.ts
+export default defineConfig({
+  test: {
+    globals: true,
+    environment: 'node',
+    include: ['tests/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
+    fileParallelism: false // 避免数据库连接冲突
+  }
+});
+```
+
+**测试数据工厂** [Source: docs/architecture/backend-module-package-standards.md#测试数据工厂]:
+- 使用时间戳保证数据唯一性
+- 提供 `createTestData(overrides)` 静态方法
+- 提供 `createTestRecord(dataSource, overrides)` 异步方法
+
+### API兼容性要求
+
+**关键**: 与原 `advertisements-module-mt` 保持100%API兼容
+- 路由路径完全相同
+- 请求参数结构相同
+- 响应Schema结构相同
+- 小程序端无需任何改动
+
+### 数据库类型映射
+
+[Source: docs/architecture/backend-module-package-standards.md#数据库类型规范]
+
+| 数据库类型 | TypeORM类型 | 备注 |
+|------------|-------------|------|
+| `int unsigned` | `int` + `unsigned: true` | 主键常用 |
+| `varchar(n)` | `varchar` + `length: n` | 字符串 |
+| `timestamp` | `timestamp` | 时间戳 |
+| `int` (状态) | `int` | 状态枚举 |
+
+### package.json配置参考
+
+[Source: docs/architecture/backend-module-package-standards.md#包配置规范]
+
+```json
+{
+  "name": "@d8d/unified-advertisements-module",
+  "version": "1.0.0",
+  "type": "module",
+  "main": "src/index.ts",
+  "types": "src/index.ts",
+  "scripts": {
+    "test": "vitest run",
+    "test:coverage": "vitest run --coverage",
+    "typecheck": "tsc --noEmit"
+  },
+  "dependencies": {
+    "@d8d/shared-crud": "workspace:*",
+    "@d8d/shared-types": "workspace:*",
+    "@d8d/shared-utils": "workspace:*",
+    "@d8d/core-module-mt": "workspace:*",
+    "@d8d/tenant-module-mt": "workspace:*",
+    "@hono/zod-openapi": "^1.0.2",
+    "typeorm": "^0.3.20",
+    "zod": "^4.1.12"
+  }
+}
+```
+
+**重要说明**:
+- **`@d8d/core-module-mt`**: 核心包聚合了 auth-module-mt、file-module-mt、user-module-mt 等基础模块
+  - 代码引用子模块:`import { FileMt } from '@d8d/core-module-mt/file-module-mt';`
+  - 代码引用子模块:`import { authMiddleware } from '@d8d/core-module-mt/auth-module-mt';`
+- **`@d8d/tenant-module-mt`**: 独立的租户管理模块包(不在core-module-mt中)
+  - 代码引用:`import { tenantAuthMiddleware } from '@d8d/tenant-module-mt';`
+
+## Testing
+
+### 测试文件位置
+- 单元测试: `tests/unit/`(与源码文件对应)
+- 集成测试: `tests/integration/`
+- 测试工具: `tests/utils/`
+
+### 测试框架
+- **Vitest**: 主要测试运行器
+- **hono/testing**: API路由测试
+- **TypeORM**: 数据库测试
+
+### 测试标准
+[Source: docs/architecture/testing-strategy.md]
+
+| 测试类型 | 最低要求 | 目标要求 |
+|----------|----------|----------|
+| 单元测试 | 70% | 80% |
+| 集成测试 | 50% | 60% |
+
+### 关键测试要求
+1. **API兼容性测试**: 验证响应结构与原模块完全一致
+2. **权限测试**: 验证管理员路由只有超级管理员可访问
+3. **统一数据测试**: 验证不同租户用户获取到相同的广告数据
+4. **软删除测试**: 验证删除操作设置 `status=0` 而非物理删除
+5. **关联测试**: 验证 `FileMt` 和 `AdvertisementType` 关联正确
+
+### 测试执行命令
+```bash
+# 进入模块目录
+cd packages/unified-advertisements-module
+
+# 运行所有测试
+pnpm test
+
+# 运行集成测试
+pnpm test:integration
+
+# 生成覆盖率报告
+pnpm test:coverage
+
+# 类型检查
+pnpm typecheck
+```
+
+## Change Log
+
+| Date | Version | Description | Author |
+|------|---------|-------------|--------|
+| 2026-01-02 | 1.0 | 初始故事创建 | Bob (Scrum Master) |
+
+## Dev Agent Record
+
+### Agent Model Used
+_待开发代理填写_
+
+### Debug Log References
+_待开发代理填写_
+
+### Completion Notes List
+_待开发代理填写_
+
+### File List
+_待开发代理填写_
+
+## QA Results
+_待QA代理填写_