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

docs(story): 创建故事015.003 - 个人信息管理API

为人才小程序提供个人信息查询功能,包括:
- 个人信息查询API (基本信息)
- 银行卡信息查询API (卡号脱敏)
- 证件照片查询API (类型过滤)

技术要点:
- 遵循只读设计原则
- 使用talentAuthMiddleware进行权限验证
- 关联查询disabled_bank_card、disabled_photo、bank_name、file表
- 卡号脱敏逻辑 (前4位+****+后4位)

验收标准:
- 个人信息查询接口返回正确的人才基本信息
- 银行卡信息查询接口返回银行卡信息(卡号脱敏处理)
- 证件照片查询接口返回证件照片信息
- 所有接口验证用户权限,确保用户只能查询自己的数据
- 所有接口通过单元测试和集成测试

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

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

+ 402 - 0
docs/stories/015.003.story.md

@@ -0,0 +1,402 @@
+# Story 015.003: 个人信息管理API
+
+## Status
+Approved
+
+## Story
+**作为** 人才用户,
+**我希望** 能够查看我的个人信息,包括基本信息、银行卡信息和证件照片,
+**以便** 我能够了解和管理我的个人数据。
+
+## Acceptance Criteria
+1. 个人信息查询接口返回正确的人才基本信息
+2. 银行卡信息查询接口返回银行卡信息(卡号脱敏处理)
+3. 证件照片查询接口返回证件照片信息
+4. 所有接口验证用户权限,确保用户只能查询自己的数据
+5. 所有接口通过单元测试和集成测试
+
+## Tasks / Subtasks
+
+- [ ] 任务1: 创建人才个人信息查询API (AC: 1, 4)
+  - [ ] 在disability-module创建人才专用路由文件 `talent-personal-info.routes.ts`
+  - [ ] 创建人才个人信息查询Schema (个人信息响应Schema)
+  - [ ] 实现GET `/personal/info` 接口 - 查询人才基本信息
+  - [ ] 基于users2表的person_id和JWT token验证人才用户身份
+  - [ ] 确保用户只能查询自己的个人信息
+  - [ ] 添加OpenAPI文档元数据
+
+- [ ] 任务2: 创建银行卡信息查询API (AC: 2, 4)
+  - [ ] 创建银行卡信息查询Schema (银行卡响应Schema,卡号脱敏)
+  - [ ] 实现GET `/personal/bank-cards` 接口 - 查询银行卡列表
+  - [ ] 基于person_id关联查询disabled_bank_card表
+  - [ ] 实现卡号脱敏逻辑 (显示前4位和后4位,中间用****代替)
+  - [ ] 关联查询bank_name表获取银行名称
+  - [ ] 添加OpenAPI文档元数据
+
+- [ ] 任务3: 创建证件照片查询API (AC: 3, 4)
+  - [ ] 创建证件照片查询Schema (证件照片响应Schema)
+  - [ ] 实现GET `/personal/photos` 接口 - 查询证件照片列表
+  - [ ] 基于person_id关联查询disabled_photo表
+  - [ ] 支持按photo_type过滤照片类型
+  - [ ] 关联file表获取文件URL和文件信息
+  - [ ] 添加OpenAPI文档元数据
+
+- [ ] 任务4: 扩展DisabledPersonService添加人才专用查询方法
+  - [ ] 添加 `getPersonalInfo(personId: number)` 方法
+  - [ ] 添加 `getBankCardsByPersonId(personId: number)` 方法
+  - [ ] 添加 `getPhotosByPersonId(personId: number, photoType?: string)` 方法
+  - [ ] 实现卡号脱敏工具函数
+
+- [ ] 任务5: 创建人才认证中间件 (如需要)
+  - [ ] 创建 `talentAuthMiddleware` 验证人才用户身份
+  - [ ] 从JWT token中提取person_id
+  - [ ] 验证用户类型为talent
+  - [ ] 将person_id注入到上下文中
+
+- [ ] 任务6: 在server包注册人才个人信息路由
+  - [ ] 在server包中导入 `talent-personal-info.routes.ts`
+  - [ ] 添加 `/api/v1/rencai` 前缀
+  - [ ] 确保路由正确注册到主应用
+
+- [ ] 任务7: 编写单元测试和集成测试 (AC: 5)
+  - [ ] 测试个人信息查询成功场景
+  - [ ] 测试银行卡信息查询成功场景和卡号脱敏
+  - [ ] 测试证件照片查询成功场景和类型过滤
+  - [ ] 测试权限验证 - 用户只能查询自己的数据
+  - [ ] 测试认证失败场景 (未登录、非人才用户)
+  - [ ] 测试用户不存在场景
+  - [ ] 集成测试验证完整查询流程
+
+- [ ] 任务8: 更新模块导出和文档
+  - [ ] 在disability-module的index.ts中导出新路由
+  - [ ] 更新README文档 (如果需要)
+  - [ ] 确保类型正确导出供前端使用
+
+## Dev Notes
+
+### 先前故事见解
+- **故事015.001**: 数据库schema扩展完成,users2表支持talent类型和person_id字段 [Source: docs/stories/015.001.story.md#L130-L141]
+- **故事015.002**: 人才用户认证API完成,建立了talentAuthMiddleware中间件模式 [Source: docs/stories/015.002.story.md#L325]
+- **认证模式**: 使用talentAuthMiddleware中间件验证人才用户身份,从JWT token中提取person_id [Source: docs/stories/015.002.story.md#L325]
+- **路由前缀约定**: 人才小程序API使用 `/api/v1/rencai` 前缀,模块内路由不包含前缀 [Source: docs/prd/epic-015-talent-mini-program-api-support.md#L53-L61]
+- **只读设计原则**: 人才小程序API以查询功能为主,数据修改由管理员在后台处理 [Source: docs/prd/epic-015-talent-mini-program-api-support.md#L50-L52]
+
+### 技术栈要求
+- **后端框架**: Hono 4.8.5 [Source: docs/architecture/tech-stack.md#L12]
+- **数据库**: PostgreSQL 17 [Source: docs/architecture/tech-stack.md#L15]
+- **ORM**: TypeORM 0.3.20 [Source: docs/architecture/tech-stack.md#L16]
+- **测试框架**: Vitest 2.x [Source: docs/architecture/testing-strategy.md#L45-L46]
+- **认证**: JWT 9.0.2 [Source: docs/architecture/tech-stack.md#L19]
+
+### 数据模型规范
+
+**DisabledPerson实体关键字段** [Source: allin-packages/disability-module/src/entities/disabled-person.entity.ts#L8-L238]:
+- `id`: person_id (主键)
+- `name`: 姓名
+- `gender`: 性别 (男/女)
+- `idCard`: 身份证号 (唯一索引)
+- `disabilityId`: 残疾证号 (唯一索引)
+- `disabilityType`: 残疾类型
+- `disabilityLevel`: 残疾等级
+- `phone`: 联系方式
+- `province`: 省级
+- `city`: 市级
+- `district`: 区县级
+- `detailedAddress`: 详细地址
+- `birthDate`: 出生日期
+- `idAddress`: 身份证地址
+- `idValidDate`: 身份证有效期
+- `disabilityValidDate`: 残疾证有效期
+- `canDirectContact`: 是否可直接联系
+- `isMarried`: 是否已婚
+- `nation`: 民族
+- `isInBlackList`: 是否在黑名单中
+- `jobStatus`: 在职状态
+- `specificDisability`: 具体残疾部位和情况
+- 关系: `bankCards` (一对多), `photos` (一对多)
+
+**DisabledBankCard实体关键字段** [Source: allin-packages/disability-module/src/entities/disabled-bank-card.entity.ts]:
+- `id`: card_id (主键)
+- `personId`: 残疾人ID (外键)
+- `subBankName`: 发卡支行
+- `bankNameId`: 银行名称ID (外键引用bank_name表)
+- `cardNumber`: 卡号 (需要脱敏)
+- `cardholderName`: 持卡人姓名
+- `cardType`: 银行卡类型 (一类卡/二类卡)
+- `fileId`: 文件ID (外键引用files表)
+- `isDefault`: 是否默认
+- 关系: `person` (多对一), `file` (多对一), `bankName` (多对一)
+
+**DisabledPhoto实体关键字段** [Source: allin-packages/disability-module/src/entities/disabled-photo.entity.ts]:
+- `id`: photo_id (主键)
+- `personId`: 残疾人ID (外键)
+- `photoType`: 照片类型 (身份证、残疾证、体检报告、征信报告等)
+- `fileId`: 文件ID (外键引用files表)
+- `uploadTime`: 上传时间
+- `canDownload`: 是否可下载
+- 关系: `person` (多对一), `file` (多对一)
+
+**UserEntity关键字段** (参考故事015.002):
+- `id`: 用户ID (主键)
+- `username`: 用户名
+- `userType`: 用户类型 (admin/employer/talent)
+- `personId`: 残疾人ID (外键,可为空) [Source: docs/stories/015.001.story.md#L132-L133]
+
+### API路径约定
+- **人才小程序API前缀**: `/api/v1/rencai` [Source: docs/prd/epic-015-talent-mini-program-api-support.md#L53-L61]
+- **模块包内路由定义**: 不应包含API前缀,前缀在server包注册时统一添加 [Source: docs/prd/epic-015-talent-mini-program-api-support.md#L335-L337]
+
+**具体接口路径**:
+- 个人信息查询: `GET /api/v1/rencai/personal/info`
+- 银行卡信息查询: `GET /api/v1/rencai/personal/bank-cards`
+- 证件照片查询: `GET /api/v1/rencai/personal/photos`
+
+### 项目结构指南
+**模块位置**:
+- disability-module: `allin-packages/disability-module/` [Source: docs/architecture/source-tree.md]
+- auth-module: `packages/core-module/auth-module/` (认证中间件) [Source: docs/architecture/source-tree.md#L109-L129]
+- server包: `packages/server/` (路由注册) [Source: docs/architecture/source-tree.md#L56-L61]
+
+**disability-module路由文件结构** (参考现有模式):
+```
+allin-packages/disability-module/src/routes/
+├── talent-personal-info.routes.ts  # 新增: 人才个人信息路由
+├── person-extension.route.ts        # 参考: 企业用户扩展路由模式
+├── disabled-person.routes.ts        # 主路由
+└── index.ts                         # 路由导出
+```
+
+**disability-module服务文件结构**:
+```
+allin-packages/disability-module/src/services/
+├── disabled-person.service.ts       # 现有服务,需要扩展
+└── index.ts                         # 服务导出
+```
+
+### Schema验证规范
+**个人信息响应Schema** (参考):
+```typescript
+const PersonalInfoResponseSchema = z.object({
+  name: z.string(),
+  gender: z.string(),
+  idCard: z.string(),
+  disabilityId: z.string(),
+  disabilityType: z.string(),
+  disabilityLevel: z.string(),
+  phone: z.string(),
+  province: z.string(),
+  city: z.string(),
+  district: z.string().nullable(),
+  detailedAddress: z.string().nullable(),
+  birthDate: z.string().nullable(), // ISO日期字符串
+  idAddress: z.string(),
+  idValidDate: z.string().nullable(),
+  disabilityValidDate: z.string().nullable(),
+  canDirectContact: z.number(),
+  isMarried: z.number().nullable(),
+  nation: z.string().nullable(),
+  jobStatus: z.number(),
+});
+```
+
+**银行卡信息响应Schema** (包含卡号脱敏):
+```typescript
+const BankCardInfoSchema = z.object({
+  id: z.number(),
+  subBankName: z.string(),
+  bankName: z.string().nullable(), // 关联查询
+  cardNumber: z.string(), // 脱敏: 1234****5678
+  cardholderName: z.string(),
+  cardType: z.string().nullable(),
+  isDefault: z.number(),
+  fileUrl: z.string().nullable(), // 关联file表
+});
+
+const BankCardsResponseSchema = z.object({
+  data: z.array(BankCardInfoSchema),
+  total: z.number(),
+});
+```
+
+**证件照片响应Schema**:
+```typescript
+const PhotoInfoSchema = z.object({
+  id: z.number(),
+  photoType: z.string(),
+  fileUrl: z.string().nullable(), // 关联file表
+  fileName: z.string().nullable(),
+  uploadTime: z.string(), // ISO时间字符串
+  canDownload: z.number(),
+});
+
+const PhotosResponseSchema = z.object({
+  data: z.array(PhotoInfoSchema),
+  total: z.number(),
+});
+```
+
+**查询参数Schema** (证件照片过滤):
+```typescript
+const PhotosQuerySchema = z.object({
+  photoType: z.string().optional(), // 可选: 按类型过滤
+  skip: z.coerce.number().int().min(0).default(0),
+  take: z.coerce.number().int().min(1).max(100).default(10),
+});
+```
+
+### 卡号脱敏逻辑
+**卡号脱敏规则**: 保留前4位和后4位,中间用****代替
+- 示例: `6222021234567890123` → `6222****0123`
+- 实现方式: 在Service层或响应转换中处理
+
+### 认证和权限验证
+**使用talentAuthMiddleware**:
+- 从故事015.002中复用 `talentAuthMiddleware` [Source: docs/stories/015.002.story.md#L325]
+- 中间件验证JWT token和用户类型为talent
+- 从token中提取person_id并注入到上下文
+
+**权限验证逻辑**:
+1. talentAuthMiddleware验证用户身份
+2. 从上下文获取person_id
+3. 使用person_id查询数据,确保用户只能访问自己的信息
+4. 如果person_id不匹配,返回403错误
+
+### 只读设计原则
+**查询专用接口**:
+- 所有接口均为GET请求,不提供POST/PUT/DELETE
+- 个人信息修改由管理员在管理后台处理
+- 人才用户只能查看数据,不能修改
+
+### 文件模块集成
+**File模块集成** (参考现有模式):
+- 银行卡和证件照片都关联到file_id
+- 需要关联查询File实体获取文件URL
+- File实体位置: `packages/file-module/src/entities/file.entity.ts` [Source: docs/architecture/source-tree.md#L130-L149]
+- 使用FileService获取文件下载URL (如需要) [Source: allin-packages/disability-module/src/services/disabled-person.service.ts#L8]
+
+### 错误处理规范
+**HTTP状态码** [Source: docs/architecture/backend-module-package-standards.md#L442-L449]:
+- `200 OK`: 查询成功
+- `400 Bad Request`: 请求参数错误
+- `401 Unauthorized`: 未授权或token无效
+- `403 Forbidden`: 权限不足 (尝试访问他人数据)
+- `404 Not Found`: 资源不存在 (用户不存在)
+
+**错误响应格式** [Source: docs/architecture/backend-module-package-standards.md#L450-L458]:
+```typescript
+{
+  success: false,
+  code: 403,
+  message: "权限不足,无法访问该数据",
+  timestamp: "2025-12-25T10:30:00Z"
+}
+```
+
+### 关联查询优化
+**TypeORM关系配置** (参考现有模式):
+- DisabledPerson已配置 `@OneToMany` 关系: `bankCards`, `photos` [Source: allin-packages/disability-module/src/entities/disabled-person.entity.ts#L224-L228]
+- DisabledBankCard已配置 `@ManyToOne` 关系: `bankName`, `file` [Source: allin-packages/disability-module/src/entities/disabled-bank-card.entity.ts#L84-L97]
+- DisabledPhoto已配置 `@ManyToOne` 关系: `file` [Source: allin-packages/disability-module/src/entities/disabled-photo.entity.ts#L58-L65]
+- 使用 `relations` 选项预加载关联数据
+
+### 参考实现
+- **企业用户扩展路由**: 参考 `person-extension.route.ts` 的路由定义模式 [Source: allin-packages/disability-module/src/routes/person-extension.route.ts]
+- **DisabledPersonService**: 参考现有的服务方法模式 [Source: allin-packages/disability-module/src/services/disabled-person.service.ts]
+- **认证中间件**: 参考 `talentAuthMiddleware` [Source: docs/stories/015.002.story.md#L325]
+
+### 文件位置
+**需要创建/修改的文件**:
+- `allin-packages/disability-module/src/routes/talent-personal-info.routes.ts` - 新增: 人才个人信息路由
+- `allin-packages/disability-module/src/routes/index.ts` - 修改: 导出新路由
+- `allin-packages/disability-module/src/schemas/talent-personal-info.schema.ts` - 新增: 人才个人信息Schema
+- `allin-packages/disability-module/src/schemas/index.ts` - 修改: 导出新Schema
+- `allin-packages/disability-module/src/services/disabled-person.service.ts` - 修改: 添加人才专用查询方法
+- `packages/server/src/index.ts` - 修改: 注册人才个人信息路由 (添加`/api/v1/rencai`前缀)
+- `allin-packages/disability-module/tests/integration/talent-personal-info.integration.test.ts` - 新增: 集成测试
+
+### 技术约束
+- **包命名**: 使用现有的 `@d8d/allin-disability-person-management` 包 [Source: docs/architecture/backend-module-package-standards.md#L39-L42]
+- **循环依赖处理**: 使用ID引用或TypeORM的relations避免循环依赖 [Source: docs/architecture/backend-module-package-standards.md#L314-L328]
+- **模块导出**: 确保新路由和Schema正确导出供server包使用
+
+### Testing
+
+#### 测试文件位置
+- 集成测试: `allin-packages/disability-module/tests/integration/talent-personal-info.integration.test.ts` [Source: docs/architecture/testing-strategy.md#L53]
+
+#### 测试框架
+- Vitest 2.x [Source: docs/architecture/testing-strategy.md#L45-L46]
+- hono/testing用于API测试 [Source: docs/architecture/tech-stack.md#L26]
+
+#### 测试覆盖率要求
+- 核心业务逻辑 > 80% [Source: docs/architecture/coding-standards.md#L18]
+- API端点覆盖 ≥ 60% [Source: docs/architecture/testing-strategy.md#L169-L173]
+
+#### 测试策略
+**单元测试** (可选):
+- 测试卡号脱敏函数
+- 测试Service层的查询方法
+
+**集成测试** (必须):
+- 测试完整查询流程 (从路由到数据库)
+- 测试认证和权限验证
+- 测试关联查询正确性
+- 测试错误场景
+
+**测试场景**:
+1. **个人信息查询成功场景**:
+   - 人才用户登录后成功查询个人信息
+   - 返回完整的个人信息字段
+   - 数据格式正确
+
+2. **银行卡信息查询成功场景**:
+   - 成功查询银行卡列表
+   - 卡号正确脱敏 (前4位+****+后4位)
+   - 银行名称正确关联
+   - 文件URL正确返回
+
+3. **证件照片查询成功场景**:
+   - 成功查询证件照片列表
+   - 支持按photoType过滤
+   - 文件URL正确返回
+   - 分页功能正常
+
+4. **权限验证场景**:
+   - 未登录用户返回401
+   - 非talent用户返回401
+   - 用户尝试查询他人数据返回403
+   - person_id不匹配返回403
+
+5. **错误场景**:
+   - 用户不存在返回404
+   - 无效的photoType参数返回400
+   - 数据库错误返回500
+
+**测试数据工厂** (参考现有模式):
+- 使用测试数据工厂创建DisabledPerson测试数据
+- 创建关联的DisabledBankCard和DisabledPhoto测试数据
+- 创建关联的File测试数据 (如需要)
+- 确保测试数据完整,包含所有必填字段
+
+## Change Log
+| Date | Version | Description | Author |
+|------|---------|-------------|--------|
+| 2025-12-25 | 1.0 | 初始故事创建 | Scrum Master |
+
+## Dev Agent Record
+*此部分由开发代理在实施过程中填写*
+
+### Agent Model Used
+待填写
+
+### Debug Log References
+待填写
+
+### Completion Notes List
+待填写
+
+### File List
+待填写
+
+## QA Results
+*此部分由QA代理在审查完成后填写*