# 家庭成员角色系统重构方案 > 目标:废除 children 表,合并到 family_members;用 roleOverride 替代 capabilityRole,支持管理员手动设置 + 年龄自动判断。 --- ## 一、核心变更目标 | 目标 | 说明 | |------|------| | 废除 children 表 | 合并到 family_members,删除 Child.java / ChildMapper | | capabilityRole → roleOverride | family_members 表字段,管理员可手动覆盖 | | capabilityRole → defaultRole | relationship_types 表字段,关系类型的默认模板值 | | 可配置年龄阈值 | system_config 表存储 child/elderly 年龄分界 | | effectiveRole 计算 | 优先 roleOverride → 模板值 → 年龄计算 | --- ## 二、数据库变更 ### 2.1 family_members 表扩字段 + capability_role → role_override ```sql ALTER TABLE family_members CHANGE COLUMN capability_role role_override VARCHAR(20) DEFAULT 'auto' COMMENT '角色覆盖: auto/parent/child/elderly,空=auto'; ALTER TABLE family_members ADD COLUMN id_card VARCHAR(18) DEFAULT NULL COMMENT '身份证号(孩子)'; ALTER TABLE family_members ADD COLUMN penalty_enabled TINYINT DEFAULT 1 COMMENT '是否扣减积分(0否1是)'; ALTER TABLE family_members ADD COLUMN theme VARCHAR(32) DEFAULT 'default' COMMENT '主题'; ALTER TABLE family_members ADD COLUMN system_points INT DEFAULT 0 COMMENT '系统积分(干预/平台奖励)'; ALTER TABLE family_members ADD COLUMN streak_days INT DEFAULT 0 COMMENT '连续打卡天数'; ALTER TABLE family_members ADD COLUMN last_task_date DATE COMMENT '上次任务日期'; ALTER TABLE family_members ADD COLUMN focus_max_daily TINYINT DEFAULT 3 COMMENT '每日专注上限'; ALTER TABLE family_members ADD COLUMN focus_remaining TINYINT DEFAULT 3 COMMENT '今日剩余专注次数'; ALTER TABLE family_members ADD COLUMN focus_reset_date DATE COMMENT '专注次数重置日期'; ``` ### 2.2 relationship_types 表重命名字段 ```sql ALTER TABLE relationship_types CHANGE COLUMN capability_role default_role VARCHAR(20) DEFAULT NULL COMMENT '默认角色模板: parent/child'; ``` ### 2.3 新建 system_config 表 ```sql CREATE TABLE IF NOT EXISTS system_config ( id BIGINT AUTO_INCREMENT PRIMARY KEY, config_key VARCHAR(100) NOT NULL UNIQUE, config_value VARCHAR(500), description VARCHAR(255), created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; INSERT INTO system_config (config_key, config_value, description) VALUES ('child_age_threshold', '18', '未成年年龄分界线(岁)'), ('elderly_age_threshold', '65', '老年年龄分界线(岁)'); ``` ### 2.4 数据迁移脚本 ```sql -- ============================================================ -- 迁移 children 数据到 family_members -- ============================================================ -- Step 1: 匹配更新 — family_members 中已存在 (family_id, nickname) 相同的记录 UPDATE family_members fm INNER JOIN children c ON fm.family_id = c.family_id AND fm.nickname = c.nickname SET fm.id_card = c.id_card, fm.penalty_enabled = c.penalty_enabled, fm.theme = c.theme, fm.system_points = c.system_points, fm.streak_days = c.streak_days, fm.last_task_date = c.last_task_date, fm.focus_max_daily = c.focus_max_daily, fm.focus_remaining = c.focus_remaining, fm.focus_reset_date = c.focus_reset_date, fm.role_override = 'child'; -- Step 2: 不匹配 — 插入为新的 family_members 记录 INSERT INTO family_members ( family_id, user_id, nickname, birthday, gender, phone, id_card, penalty_enabled, theme, system_points, streak_days, last_task_date, focus_max_daily, focus_remaining, focus_reset_date, show_to_family, total_points, role_override, created_at, updated_at ) SELECT c.family_id, c.user_id, c.nickname, c.birthday, c.gender, c.phone, c.id_card, c.penalty_enabled, c.theme, c.system_points, c.streak_days, c.last_task_date, c.focus_max_daily, c.focus_remaining, c.focus_reset_date, c.show_to_family, c.total_points, 'child', c.created_at, c.updated_at FROM children c WHERE NOT EXISTS ( SELECT 1 FROM family_members fm2 WHERE fm2.family_id = c.family_id AND fm2.nickname = c.nickname ); -- Step 3: tasks.child_id 处理 -- 旧方案:tasks.child_id → family_members.id 映射(需创建 mapping 表或重建关联逻辑) -- 本次方案:保留 tasks.child_id,但业务层通过 (family_id, nickname) 匹配到新的 family_members.id -- 后续迭代中重构为 family_member_id 统一关联 -- Step 4: 删除 children 表 DROP TABLE IF EXISTS children; ``` --- ## 三、后端 Java 变更 ### 3.1 文件变更总览 | 分类 | 新建 | 修改 | 删除 | |------|:----:|:----:|:----:| | Entity | 1 | 2 | 1 | | Mapper | 1 | 0 | 1 | | Service | 2 | ~25 | 0 | | Controller | 0 | ~10 | 0 | | DTO/VO | 0 | 4 | 0 | | 测试类 | 0 | ~8 | 0 | | 配置类 | 0 | 2 | 0 | ### 3.2 新建文件 **SystemConfig.java** ```java @Data @TableName("system_config") public class SystemConfig { @TableId(type = IdType.AUTO) private Long id; private String configKey; private String configValue; private String description; private Date createdAt; private Date updatedAt; } ``` **SystemConfigMapper.java** ```java public interface SystemConfigMapper extends BaseMapper {} ``` **SystemConfigService.java** ```java @Service public class SystemConfigService { @Resource private SystemConfigMapper systemConfigMapper; private static final int DEFAULT_CHILD_AGE = 18; private static final int DEFAULT_ELDERLY_AGE = 65; public int getChildAgeThreshold() { SystemConfig cfg = systemConfigMapper.selectOne( new LambdaQueryWrapper() .eq(SystemConfig::getConfigKey, "child_age_threshold")); return cfg != null ? Integer.parseInt(cfg.getConfigValue()) : DEFAULT_CHILD_AGE; } public int getElderlyAgeThreshold() { SystemConfig cfg = systemConfigMapper.selectOne( new LambdaQueryWrapper() .eq(SystemConfig::getConfigKey, "elderly_age_threshold")); return cfg != null ? Integer.parseInt(cfg.getConfigValue()) : DEFAULT_ELDERLY_AGE; } } ``` ### 3.3 删除文件 - `cfc-backend/src/main/java/com/etotem/cfc/entity/Child.java` - `cfc-backend/src/main/java/com/etotem/cfc/mapper/ChildMapper.java` ### 3.4 FamilyMember.java 字段变更 ```java // 删除 - private String capabilityRole; // 新增 + private String roleOverride; // auto/parent/child/elderly,默认 auto + private String idCard; + private Integer penaltyEnabled; + private String theme; + private Integer systemPoints; + private Integer streakDays; + private Date lastTaskDate; + private Integer focusMaxDaily; + private Integer focusRemaining; + private Date focusResetDate; ``` ### 3.5 RelationshipType.java 字段变更 ```java // 删除 - private String capabilityRole; // 新增 + private String defaultRole; // 默认角色模板: parent/child ``` ### 3.6 FamilyMemberVO.java 字段变更 ```java // 删除 - private String capabilityRole; // 新增 + private String effectiveRole; // 计算后的实际角色(前端展示用) + private String roleOverride; // 管理员设置的覆盖值 ``` ### 3.7 SwitchMemberVO.java 字段变更 ```java // 删除 - private String capabilityRole; // 新增 + private String effectiveRole; ``` ### 3.8 AddFamilyMemberDTO.java 新增字段 ```java + private String roleOverride; // auto/parent/child/elderly ``` ### 3.9 FamilyMemberService.java 核心改动 #### 3.9.1 新增 computeEffectiveRole 方法 ```java public String computeEffectiveRole(FamilyMember member) { // 1. roleOverride 非 auto → 直接返回 if (member.getRoleOverride() != null && !"auto".equals(member.getRoleOverride())) { return member.getRoleOverride(); } // 2. 取关系类型模板值 RelationshipType relType = relationshipTypeMapper.selectOne( new LambdaQueryWrapper() .eq(RelationshipType::getTypeKey, member.getRelationshipType())); if (relType != null && relType.getDefaultRole() != null) { return relType.getDefaultRole(); } // 3. 按年龄计算 if (member.getBirthday() != null) { int age = calculateAge(member.getBirthday()); int threshold = systemConfigService.getChildAgeThreshold(); if (age < threshold) return "child"; if (threshold >= 65 && age >= threshold) return "elderly"; } return "parent"; } ``` #### 3.9.2 addMember() 方法(约 line 92) ```java // 改前 member.setCapabilityRole(relType.getCapabilityRole()); // 改后 member.setRoleOverride("auto"); ``` #### 3.9.3 children 表同步逻辑(约 line 116) ```java // 改前 if ("child".equals(relType.getCapabilityRole())) { Child child = new Child(); // ... childMapper.insert(child) } // 改后 // 无需同步到 children 表,数据已存在 family_members 中 // 如需 child 专属逻辑(游戏/任务),通过 computeEffectiveRole 判断即可 ``` #### 3.9.4 listMembers() 方法(约 line 188) ```java // 改前 vo.setCapabilityRole(fm.getCapabilityRole()); // 改后 vo.setEffectiveRole(computeEffectiveRole(fm)); vo.setRoleOverride(fm.getRoleOverride()); ``` #### 3.9.5 switchToMember() 方法(约 line 236) ```java // 改前 vo.setCapabilityRole(member.getCapabilityRole()); // 改后 vo.setEffectiveRole(computeEffectiveRole(member)); ``` #### 3.9.6 kickMember() 方法(约 line 287) ```java // 改前 if ("child".equals(member.getCapabilityRole())) { List children = childMapper.selectList(...); for (Child c : children) { childMapper.deleteById(c.getId()); } } // 改后 // children 表已废除,删除 family_members 记录即可 // 若 roleOverride 为 child,需清理 family_members 中对应行 if ("child".equals(computeEffectiveRole(member))) { familyMemberMapper.deleteById(memberId); } ``` #### 3.9.7 updateMember() 方法(约 line 349) ```java // 改前 if ("child".equals(member.getCapabilityRole())) { List children = childMapper.selectList(...); for (Child c : children) { /* update child */ } } // 改后 if ("child".equals(computeEffectiveRole(member))) { // 直接更新 family_members 中的 child 专属字段即可 FamilyMember toUpdate = familyMemberMapper.selectById(member.getId()); // ... update fields } // 另:支持 roleOverride 更新 if (dto.getRoleOverride() != null) { member.setRoleOverride(dto.getRoleOverride()); } ``` #### 3.9.8 saveRelationshipType() 方法(约 line 418) ```java // 改前 existing.setCapabilityRole(type.getCapabilityRole()); // 改后 existing.setDefaultRole(type.getDefaultRole()); ``` #### 3.9.9 toFamilyMemberVO() 方法(约 line 454) ```java // 改前 vo.setCapabilityRole(member.getCapabilityRole()); // 改后 vo.setEffectiveRole(computeEffectiveRole(member)); vo.setRoleOverride(member.getRoleOverride()); ``` ### 3.10 所有引用 childMapper 的文件替换清单 #### Service 层(约 25 个) | 文件 | 替换方式 | |------|---------| | `UserService.java` | `childMapper.selectList` → `familyMemberMapper.selectList` + computeEffectiveRole 过滤 | | `StreakService.java` | 同上,streak_days 在 family_members 中 | | `MiniGameService.java` | 同上 | | `TaskService.java` | 同上 | | `PointsService.java` | 同上 | | `WishService.java` | 同上 | | `RewardService.java` | 同上 | | `EnergyService.java` | 同上 | | `OnboardingService.java` | 同上 | | `TaskStatsService.java` | 同上 | | `GrowthTaskService.java` | 同上 | | `InviteCardService.java` | 同上 | | `InviteMilestoneService.java` | 同上 | | `DataMigrationService.java` | 同上 | | `EmotionAlertService.java` | 同上 | | `EmotionCheckinService.java` | 同上 | | `FinanceCheckinService.java` | 同上 | | `HealthCheckinService.java` | 同上 | | `HealthReportService.java` | 同上 | | `ArticleService.java` | 同上 | | `ActivityService.java` | 同上 | | `CompatibilityService.java` | 同上 | | `AIService.java` | 同上 | | `ShareService.java` | 同上 | | `PointsExchangeService.java` | 同上 | | `TeacherService.java` | 同上 | | `MentalHealthScreenService.java` | 同上 | | `CognitiveService.java` | 同上 | | `TrainingPlanService.java` | 同上 | | `ChildNutritionProfileService.java` | 同上 | | `CognitiveTrainingConfigService.java` | 同上 | | `ProductCategoryService.java` | 同上 | | `ProductOrderService.java` | 同上 | | `ProductDimensionConfigService.java` | 同上 | **通用替换模式**: ```java // 改前 List children = childMapper.selectList( new LambdaQueryWrapper() .eq(Child::getFamilyId, familyId)); // 改后 List members = familyMemberMapper.selectList( new LambdaQueryWrapper() .eq(FamilyMember::getFamilyId, familyId)); List children = members.stream() .filter(m -> "child".equals(computeEffectiveRole(m))) .collect(Collectors.toList()); // 或通过方法(性能更优,由 FamilyMemberService 提供工具方法) List children = familyMemberService.listChildrenByFamilyId(familyId); ``` #### Controller 层(约 10 个) | 文件 | 改动 | |------|------| | `StreakController.java` | childMapper → familyMemberMapper | | `UserController.java` | 同上 | | `AdminController.java` | 同上 | | `FamilyUserController.java` | 同上 | | `InviteCardController.java` | 同上 | | `ReportStatsController.java` | 同上 | | `ParentAssessmentController.java` | 同上 | | `GuideFamilyTaskController.java` | 同上 | | `EmotionAlertController.java` | 同上 | | `StatsController.java` | 同上 | #### 配置/初始化类 | 文件 | 改动 | |------|------| | `DatabaseInitializer.java` | 移除 child 初始化逻辑 | | `SchemaSyncHandler.java` | 移除 children 表同步逻辑 | #### 测试类(约 8 个) | 文件 | 改动 | |------|------| | `GuideFamilyTaskControllerTest.java` | mock Child 相关数据 → FamilyMember | | `GuideRolePermissionTest.java` | 同上 | | `PointsControllerTest.java` | 同上 | | `StatsControllerTest.java` | 同上 | | `DailyCheckinStreakFlowTest.java` | 同上 | | `WishExchangeFlowTest.java` | 同上 | | `TaskServiceMinigameTest.java` | 同上 | | `GuideHierarchyServiceTest.java` | 同上 | ### 3.11 其他 DTO/Entity 改动 | 文件 | 改动 | |------|------| | `CreateTaskDTO.java` | 移除 childId 相关,改用 familyMemberId | | `DirectRegisterDTO.java` | 移除 child 相关字段 | | `MemberEnergyDTO.java` | capabilityRole → effectiveRole | | `PhoneLoginDTO.java` | 移除 child 相关 | | `RegisterWithInviteCodeDTO.java` | 移除 child 相关 | | `Task.java` entity | childId 字段保留(待后续重构),关联逻辑改为 familyMemberId | | `User.java` entity | 暂无 children 引用 | | `Wish.java` entity | 无 | | `Article.java` entity | 无 | --- ## 四、前端变更 ### 4.1 cfc-web(Web管理端) #### RelationshipTypes.vue - 字段标签:`能力角色` → `默认角色模板` - 字段名:`capabilityRole` → `defaultRole` - 下拉选项说明:`不指定则按年龄自动判断` #### 新增家庭成员 roleOverride 下拉 - 位置:家庭成员管理页(新建或修改成员时) - 字段:`roleOverride` 下拉(auto / parent / child / elderly) - 权限:仅家庭管理员可见 ### 4.2 cfc-小程序 | 文件 | 改动 | |------|------| | `store/modules/family.js` | `capabilityRole` → `effectiveRole` | | 家庭切换组件 | 使用 `effectiveRole` 决定家长端/孩子端界面 | | 相关 API 响应处理 | `capabilityRole` → `effectiveRole` | --- ## 五、schema.sql 同步更新 需同步更新以下内容: 1. `family_members` 表:`role_override` 字段替换 `capability_role`,新增 10 个 child 字段 2. `relationship_types` 表:`default_role` 字段替换 `capability_role` 3. 新增 `system_config` 表定义 4. 删除 `children` 表定义 5. 附注迁移 SQL(2.4 节) --- ## 六、实施顺序 ``` Phase 1 — 数据库层 [ ] 1.1 修改 schema.sql(DDL) [ ] 1.2 执行 ALTER TABLE [ ] 1.3 验证表结构 Phase 2 — 基础设施(SystemConfig) [ ] 2.1 新建 SystemConfig.java + SystemConfigMapper.java [ ] 2.2 新建 SystemConfigService.java [ ] 2.3 mvn compile 验证 Phase 3 — Entity 核心 [ ] 3.1 FamilyMember.java:字段变更 + 10 个新字段 [ ] 3.2 RelationshipType.java:字段重命名 [ ] 3.3 删除 Child.java + ChildMapper.java [ ] 3.4 FamilyMemberVO.java:字段变更 [ ] 3.5 SwitchMemberVO.java:字段变更 [ ] 3.6 AddFamilyMemberDTO.java:新增 roleOverride [ ] 3.7 mvn compile 验证 Phase 4 — Service 层(FamilyMemberService 核心逻辑) [ ] 4.1 实现 computeEffectiveRole() 方法 [ ] 4.2 重构 addMember() [ ] 4.3 重构 listMembers() [ ] 4.4 重构 switchToMember() [ ] 4.5 重构 kickMember() [ ] 4.6 重构 updateMember() [ ] 4.7 重构 saveRelationshipType() [ ] 4.8 重构 toFamilyMemberVO() [ ] 4.9 mvn compile 验证 Phase 5 — Service 层(批量替换 childMapper) [ ] 5.1 UserService.java [ ] 5.2 StreakService.java [ ] 5.3 MiniGameService.java [ ] 5.4 TaskService.java [ ] 5.5 PointsService.java [ ] 5.6 WishService.java [ ] 5.7 RewardService.java [ ] 5.8 EnergyService.java [ ] 5.9 其余 20+ 个 Service [ ] 5.10 mvn compile 验证 Phase 6 — Controller 层 [ ] 6.1 FamilyMembersController.java [ ] 6.2 RelationshipTypeAdminController.java [ ] 6.3 其余 ~10 个 Controller [ ] 6.4 mvn compile 验证 Phase 7 — 测试类 [ ] 7.1 更新 ~8 个测试类 [ ] 7.2 mvn test 验证 Phase 8 — 前端 [ ] 8.1 cfc-web RelationshipTypes.vue 字段更新 [ ] 8.2 cfc-web 家庭成员管理页新增 roleOverride 下拉 [ ] 8.3 cfc-小程序 family store + 组件适配 [ ] 8.4 npm run build 验证 Phase 9 — 数据迁移 [ ] 9.1 备份数据库 [ ] 9.2 执行迁移 SQL(2.4 节) [ ] 9.3 验证数据完整性 [ ] 9.4 删除 children 表 Phase 10 — 最终验证 [ ] 10.1 mvn clean compile [ ] 10.2 mvn test [ ] 10.3 后端启动验证 [ ] 10.4 前端启动验证 ``` --- ## 七、涉及文件清单(共约 93 个) ### 后端 Java | 文件 | 操作 | |------|------| | `entity/Child.java` | **删除** | | `mapper/ChildMapper.java` | **删除** | | `entity/FamilyMember.java` | 修改 | | `entity/RelationshipType.java` | 修改 | | `entity/SystemConfig.java` | **新建** | | `mapper/SystemConfigMapper.java` | **新建** | | `service/SystemConfigService.java` | **新建** | | `service/FamilyMemberService.java` | 修改(核心) | | `service/UserService.java` | 修改 | | `service/StreakService.java` | 修改 | | `service/MiniGameService.java` | 修改 | | `service/TaskService.java` | 修改 | | `service/PointsService.java` | 修改 | | `service/WishService.java` | 修改 | | `service/RewardService.java` | 修改 | | `service/EnergyService.java` | 修改 | | `service/OnboardingService.java` | 修改 | | `service/TaskStatsService.java` | 修改 | | `service/GrowthTaskService.java` | 修改 | | `service/InviteCardService.java` | 修改 | | `service/InviteMilestoneService.java` | 修改 | | `service/DataMigrationService.java` | 修改 | | `service/EmotionAlertService.java` | 修改 | | `service/EmotionCheckinService.java` | 修改 | | `service/FinanceCheckinService.java` | 修改 | | `service/HealthCheckinService.java` | 修改 | | `service/HealthReportService.java` | 修改 | | `service/ArticleService.java` | 修改 | | `service/ActivityService.java` | 修改 | | `service/CompatibilityService.java` | 修改 | | `service/AIService.java` | 修改 | | `service/ShareService.java` | 修改 | | `service/PointsExchangeService.java` | 修改 | | `service/TeacherService.java` | 修改 | | `service/MentalHealthScreenService.java` | 修改 | | `service/CognitiveService.java` | 修改 | | `service/TrainingPlanService.java` | 修改 | | `service/ChildNutritionProfileService.java` | 修改 | | `service/CognitiveTrainingConfigService.java` | 修改 | | `service/ProductCategoryService.java` | 修改 | | `service/ProductOrderService.java` | 修改 | | `service/ProductDimensionConfigService.java` | 修改 | | `controller/StreakController.java` | 修改 | | `controller/UserController.java` | 修改 | | `controller/AdminController.java` | 修改 | | `controller/FamilyUserController.java` | 修改 | | `controller/InviteCardController.java` | 修改 | | `controller/ReportStatsController.java` | 修改 | | `controller/ParentAssessmentController.java` | 修改 | | `controller/GuideFamilyTaskController.java` | 修改 | | `controller/EmotionAlertController.java` | 修改 | | `controller/StatsController.java` | 修改 | | `controller/FamilyMembersController.java` | 修改 | | `controller/RelationshipTypeAdminController.java` | 修改 | | `config/DatabaseInitializer.java` | 修改 | | `config/SchemaSyncHandler.java` | 修改 | | `dto/FamilyMemberVO.java` | 修改 | | `dto/SwitchMemberVO.java` | 修改 | | `dto/AddFamilyMemberDTO.java` | 修改 | | `dto/CreateTaskDTO.java` | 修改 | | `dto/DirectRegisterDTO.java` | 修改 | | `dto/MemberEnergyDTO.java` | 修改 | | `dto/PhoneLoginDTO.java` | 修改 | | `dto/RegisterWithInviteCodeDTO.java` | 修改 | | `entity/Task.java` | 修改 | | `test/GuideFamilyTaskControllerTest.java` | 修改 | | `test/GuideRolePermissionTest.java` | 修改 | | `test/PointsControllerTest.java` | 修改 | | `test/StatsControllerTest.java` | 修改 | | `test/DailyCheckinStreakFlowTest.java` | 修改 | | `test/WishExchangeFlowTest.java` | 修改 | | `test/TaskServiceMinigameTest.java` | 修改 | | `test/GuideHierarchyServiceTest.java` | 修改 | ### 前端 | 文件 | 操作 | |------|------| | `cfc-web/views/admin/RelationshipTypes.vue` | 修改 | | `cfc-web/家庭成员管理页`(路径待确认) | 修改/新建 | | `cfc-frontend/store/modules/family.js` | 修改 | | `cfc-frontend/家庭切换组件`(路径待确认) | 修改 | --- ## 八、风险提示 1. **tasks.child_id 外键**:`tasks` 表的 `child_id` 字段原指向 `children.id`,合并后该字段需要业务确认处理方式。建议保留字段但通过 (family_id, nickname) 匹配到新的 family_members.id。 2. **ID 空间冲突**:children 表和 family_members 表的 ID 自增序列独立,迁移后不会冲突(匹配行复用 family_members 的 ID,不匹配行 INSERT 时会分配新的 ID)。 3. **40+ 文件改动**:全部改完后建议用 `mvn clean compile` 全量验证,逐个 Service 测试太慢。 4. **测试覆盖**:测试类中的 mock 数据结构需要同步更新为 FamilyMember 结构。 5. **数据一致性**:迁移过程中对 children 和 family_members 的并发写入需要加事务保护。