|
|
@@ -0,0 +1,141 @@
|
|
|
+# 家庭加入流程:被邀请人必填真实姓名·出生日期·性别(准入校验)
|
|
|
+
|
|
|
+- 状态:已裁决(2026-11-07,主理人需求)
|
|
|
+- 类型:需求变更(基于 `2026-11-07-family-join-dynamic-identity.md` 的进一步约束)
|
|
|
+- 溯源场景:`tests/user-scenarios/应用场景总览.md` → SC-X-04(家庭邀请闭环)、SC-G-04(邀请码注册加入家庭)、SC-P-02(邀请成员)
|
|
|
+- 关联规格:`docs/superpowers/specs/2026-11-07-family-join-dynamic-identity.md`(身份由邀请人指定·视角动态称谓)
|
|
|
+
|
|
|
+## 1. 变更背景
|
|
|
+
|
|
|
+上一轮已把「家庭身份」从申请人的自填字段改为由邀请人在审批时指定(辈分关系 + 配偶/兄弟姐妹)。但加入流程仍存在一个准入漏洞:**被邀请人在提交加入申请时,系统并不强制其填写基础身份信息**。当前 `join-form.vue` 只要求真实姓名,出生日期与性别完全不收集;后端 `FamilyJoinRequestService.createJoinRequest` 在创建待审批请求前,对 `User.realName` / `User.birthday` / `User.gender` 三项**零校验**。
|
|
|
+
|
|
|
+这带来两个问题:
|
|
|
+
|
|
|
+1. **数据完整性缺失**:家庭关系是五维主动健康(含保险缺口、能量分布、成员画像)的计算基础。成员生日、性别为空,会导致保险缺口/年龄分层/能量评估等下游字段失真或空值。
|
|
|
+2. **合规与可信度**:邀请人审批时无法确认被邀请人的真实身份,家庭作为「以家庭为单位的健康编排」单元,成员身份可信度不足。
|
|
|
+
|
|
|
+因此新增约束:**被邀请人必须填写真实姓名、出生日期和性别三项必填信息且校验通过后才能进入家庭;信息缺失或不符合规则则不允许加入。**
|
|
|
+
|
|
|
+## 2. 影响范围
|
|
|
+
|
|
|
+| 模块 | 文件 | 改动类型 | 说明 |
|
|
|
+|---|---|---|---|
|
|
|
+| 前端·加入表单 | `cfc-frontend/pages/invite/join-form.vue` | 修改 | 新增「出生日期」「性别」两个必填项;提交前校验;`updateUserInfo` 调用补 `birthday`/`gender` |
|
|
|
+| 前端·接口封装 | `cfc-frontend/utils/api.js` | 无改动 | `updateUserInfo` 已支持 `realName`/`gender`/`birthday` 透传,`requestJoinByCode` 维持仅传 `inviteCode` |
|
|
|
+| 后端·加入服务 | `cfc-backend/.../service/FamilyJoinRequestService.java`(`createJoinRequest`) | 修改 | 创建请求前校验三项;缺失/非法直接抛错、不写库 |
|
|
|
+| 后端·用户实体 | `cfc-backend/.../entity/User.java` | 无改动 | `realName`/`birthday`/`gender` 字段已存在 |
|
|
|
+| 数据库 | `users` 表 | 无迁移 | `real_name`(VARCHAR50)、`birthday`(DATE)、`gender`(ENUM('male','female')) 列已存在(schema.sql:55-58) |
|
|
|
+| 数据库 | `family_join_requests` 表 | 无迁移 | 校验基于 `User` 记录,申请表不新增列 |
|
|
|
+| 后端·资料更新 | `cfc-backend/.../service/UserService.java` | 无改动(可复用) | 生日 `yyyy-MM-dd` 解析、性别 `male/female` 二值判定逻辑已存在(:492-505),门禁校验可对齐其规则 |
|
|
|
+| 前端·审批卡片(可选增强) | `cfc-frontend/pages/profile-extra/family-members.vue` + `FamilyJoinRequestDTO` | 可选 | 待审批卡片展示申请人出生日期/性别,便于邀请人确认 |
|
|
|
+
|
|
|
+**影响边界**:仅约束「新申请加入家庭」的被邀请人;不影响已存在家庭成员、不影响审批/辈分关系建立等既有流程。
|
|
|
+
|
|
|
+## 3. 验收标准(需求级)
|
|
|
+
|
|
|
+- **AC-R1(前端可见且必填)**:被邀请人进入加入申请表单,必须能看到并填写真实姓名、出生日期、性别三项;出生日期、性别为必填(带 `*` 标记)。
|
|
|
+- **AC-R2(双重门禁)**:三项任一缺失,前端拦截提交并 toast 提示;即便绕过前端,后端 `POST /api/family/invite/request-join-by-code` 也拒绝并返回明确错误,**不在 `family_join_requests` 产生任何记录**。
|
|
|
+- **AC-R3(校验规则)**:
|
|
|
+ - 真实姓名:trim 后非空,长度 1–20 字;
|
|
|
+ - 出生日期:非空、`yyyy-MM-dd` 可解析、为合法日期、**不晚于今天、不早于 1900-01-01**;
|
|
|
+ - 性别:仅限 `male` / `female` 二值(大小写敏感)。
|
|
|
+- **AC-R4(正常通路)**:三项齐全且合法,申请正常创建并进入「待审批」,后续审批/关系建立流程与上一轮规格一致、不受影响。
|
|
|
+- **AC-R5(无回归)**:已有家庭成员、历史加入请求、审批通过流程均不受影响;`mvn -q compile` 通过。
|
|
|
+
|
|
|
+## 4. 实现要点(契约)
|
|
|
+
|
|
|
+### 4.1 后端门禁(核心)
|
|
|
+
|
|
|
+位置:`FamilyJoinRequestService.createJoinRequest`,在 `FamilyJoinRequest request = new FamilyJoinRequest();` 之前、`User currentUser = userMapper.selectById(userId);` 之后插入校验块:
|
|
|
+
|
|
|
+```java
|
|
|
+// 准入校验:被邀请人必须已完善真实姓名/出生日期/性别(需求变更 2026-11-07-family-join-required-info)
|
|
|
+validateJoinRequiredInfo(currentUser);
|
|
|
+```
|
|
|
+
|
|
|
+`validateJoinRequiredInfo(User u)` 规则:
|
|
|
+- `u.getRealName()` 为 null 或 trim 后空或长度 >20 → 抛 `RuntimeException("请先完善真实姓名、出生日期与性别后再申请加入家庭")`
|
|
|
+- `u.getBirthday()` 为 null → 同上
|
|
|
+- `u.getGender()` 非 `"male"` 且非 `"female"`(含 null/空/其他)→ 同上
|
|
|
+- 生日范围:`u.getBirthday()` 晚于今天 → 抛 `RuntimeException("出生日期不能晚于今天")`
|
|
|
+- 生日范围:`u.getBirthday()` 早于 1900-01-01 → 抛 `RuntimeException("出生日期不合法")`
|
|
|
+- (可选)年龄 >120 视为非法
|
|
|
+
|
|
|
+> 与 `UserService` 现有解析保持口径一致:性别 `male/female`、生日 `yyyy-MM-dd`(`UserService.java:492-505`)。门禁只做「存在且合法」判定,不做写入。
|
|
|
+
|
|
|
+### 4.2 前端表单
|
|
|
+
|
|
|
+`join-form.vue`:
|
|
|
+- `data().form` 增加 `birthday: ''`、`gender: ''`(gender 存 `male`/`female`)。
|
|
|
+- 模板新增:
|
|
|
+ - 出生日期:`<picker mode="date" :value="form.birthday" @change="onBirthdayChange">`(uni 默认返回 `yyyy-MM-dd`)
|
|
|
+ - 性别:`<picker :range="['男','女']" @change="onGenderChange">` → 男→`male`、女→`female`
|
|
|
+- `prefill()`:从 `uni.getStorageSync('userInfo')` 带出 `birthday`/`gender`(与 `realName` 同处理)。
|
|
|
+- `submit()`:先校验三项非空 + 日期/性别格式;通过后再 `updateUserInfo({ realName, gender, birthday, nickname, avatar })`,随后 `requestJoinByCode(code)`。
|
|
|
+
|
|
|
+### 4.3 小程序端约束
|
|
|
+
|
|
|
+禁用可选链 `?.`、禁用 CSS Grid、禁用 `:key` 表达式、禁止直接 `new Date(string)`(日期比较用字符串或 `parseDate` 封装)。
|
|
|
+
|
|
|
+## 5. 开发任务拆分(工作项 + 验收条件)
|
|
|
+
|
|
|
+### WI-1 前端:加入申请表单补充出生日期与性别必填项
|
|
|
+- **文件**:`cfc-frontend/pages/invite/join-form.vue`
|
|
|
+- **工作**:新增出生日期(date picker)、性别(picker 男/女映射 male/female)两个必填项;`form` 建模 + `prefill` 带出 + `submit` 校验 + `updateUserInfo` 补 `birthday`/`gender` 字段。
|
|
|
+- **验收条件**:
|
|
|
+ - AC1.1 表单含真实姓名、出生日期、性别三项,出生日期与性别带 `*` 必填标记。
|
|
|
+ - AC1.2 出生日期未选 / 性别未选 / 真实姓名空 → 提交被拦截,toast 提示对应缺失项;不调用 `requestJoinByCode`。
|
|
|
+ - AC1.3 提交时 `birthday` 以 `yyyy-MM-dd` 字符串、`gender` 为 `male`/`female` 写入 `updateUserInfo`。
|
|
|
+ - AC1.4 进入页面时若 `userInfo` 已有 `birthday`/`gender`/`realName` 则预填。
|
|
|
+ - AC1.5 小程序/H5 编译无报错(uni build 通过)。
|
|
|
+
|
|
|
+### WI-2 后端:加入请求创建前校验三必填项(门禁)
|
|
|
+- **文件**:`cfc-backend/.../service/FamilyJoinRequestService.java`(`createJoinRequest`)
|
|
|
+- **工作**:读取 `currentUser` 后、创建请求前,校验 `realName`/`birthday`/`gender` 是否存在且合法;不合法直接抛错、不创建 `FamilyJoinRequest`。
|
|
|
+- **验收条件**:
|
|
|
+ - AC2.1 `realName` 空 → `request-join-by-code` 返回错误「请先完善真实姓名、出生日期与性别后再申请加入家庭」,DB 无新 `family_join_requests` 行。
|
|
|
+ - AC2.2 `birthday` 为 null → 同上拒绝。
|
|
|
+ - AC2.3 `gender` 非 `male`/`female`(含 null/空/其他值)→ 同上拒绝。
|
|
|
+ - AC2.4 三项齐全合法 → 正常创建请求,既有逻辑不受影响。
|
|
|
+ - AC2.5 `cfc-backend` 下 `mvn -q compile` 通过。
|
|
|
+
|
|
|
+### WI-3 后端:校验规则细化(日期范围 / 性别枚举 / 姓名合规)
|
|
|
+- **文件**:同 WI-2 校验块(与 WI-2 同一修改内实现,拆项仅为测试/验收分离)
|
|
|
+- **工作**:生日范围约束(不晚于今天、不早于 1900-01-01)、性别严格二值、真实姓名非空且 ≤20 字;错误信息语义清晰可直接前端展示。
|
|
|
+- **验收条件**:
|
|
|
+ - AC3.1 `birthday` = 未来日期(如 2099-01-01)→ 拒绝,提示「出生日期不能晚于今天」。
|
|
|
+ - AC3.2 `birthday` = 极早日期(如 1800-01-01)或无法解析串 → 拒绝,提示「出生日期不合法」。
|
|
|
+ - AC3.3 `realName` 超 20 字 → 拒绝。
|
|
|
+ - AC3.4 `gender` 大小写混用(如 `Male`)、其他值 → 拒绝。
|
|
|
+ - AC3.5 错误文案可被前端 `e.message` 直接 toast 展示。
|
|
|
+
|
|
|
+### WI-4 审批卡片展示三项信息(可选增强)
|
|
|
+- **文件**:`cfc-frontend/pages/profile-extra/family-members.vue` + `FamilyJoinRequestDTO`
|
|
|
+- **工作**:待审批卡片在真实姓名旁展示出生日期、性别,便于邀请人确认身份。
|
|
|
+- **验收条件(可选)**:
|
|
|
+ - AC4.1 审批卡片显示申请人真实姓名、出生日期、性别。
|
|
|
+ - AC4.2 任一字段缺失时优雅回退(不报错、不显示乱码)。
|
|
|
+
|
|
|
+### WI-5 测试:三类场景端到端验证
|
|
|
+- **文件**:后端单测(`FamilyJoinRequestService` 校验块)+ 可选端到端
|
|
|
+- **验收条件**:
|
|
|
+ - AC5.1 缺失任一项 → 拒绝(断言无 DB 插入 + 错误码/文案)。
|
|
|
+ - AC5.2 日期非法/未来/极早 → 拒绝。
|
|
|
+ - AC5.3 三项齐全合法 → 通过(既有审批流程不受影响)。
|
|
|
+ - AC5.4 前端必填校验拦截(手工或 e2e)。
|
|
|
+
|
|
|
+## 6. 任务登记(TDD 账本)
|
|
|
+
|
|
|
+| 工作项 | 能力键 | requirement 锚点 | design 锚点 | 阶段 |
|
|
|
+|---|---|---|---|---|
|
|
|
+| WI-1 前端表单 | `family.invite.joinRequiredInfoFrontend` | 本规格 §4.2 / §5 WI-1 | `join-form.vue` + `api.js updateUserInfo` | UNTRACKED |
|
|
|
+| WI-2 后端门禁 | `family.invite.joinValidationGate` | 本规格 §3 AC-R2 / §5 WI-2 | `FamilyJoinRequestService.createJoinRequest` + `User` 实体 | UNTRACKED |
|
|
|
+| WI-3 后端规则 | `family.invite.joinValidationRules` | 本规格 §3 AC-R3 / §5 WI-3 | `UserService.java:492-505`(gender/birthday 解析口径) | UNTRACKED |
|
|
|
+| WI-4 审批展示 | `family.invite.joinRequestCardInfo` | 本规格 §5 WI-4 | `family-members.vue` + `FamilyJoinRequestDTO` | UNTRACKED |
|
|
|
+| WI-5 测试 | `family.invite.joinRequiredInfoTest` | 本规格 §5 WI-5 | 上述 WI-1~WI-3 实现 | UNTRACKED |
|
|
|
+
|
|
|
+## 7. 遗留与待决
|
|
|
+
|
|
|
+- **年龄上限**:是否设年龄上限(如 ≤120)需主理人确认;当前默认仅约束「不晚于今天、不早于 1900」。
|
|
|
+- **生日与身份证联动**:`UserService` 已支持 `idCard` 反填生日;本需求不要求,但若后续接入,门禁应优先取 `idCard` 派生生日。
|
|
|
+- **WI-4 是否本轮做**:属增强非强制,建议本轮一并做(成本低、提升审批可信度),由主理人定。
|