|
|
@@ -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代理在审查完成后填写*
|