|
|
@@ -0,0 +1,695 @@
|
|
|
+# 家庭成员角色系统重构方案
|
|
|
+
|
|
|
+> 目标:废除 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<SystemConfig> {}
|
|
|
+```
|
|
|
+
|
|
|
+**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<SystemConfig>()
|
|
|
+ .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<SystemConfig>()
|
|
|
+ .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<RelationshipType>()
|
|
|
+ .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<Child> 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<Child> 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<Child> children = childMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<Child>()
|
|
|
+ .eq(Child::getFamilyId, familyId));
|
|
|
+
|
|
|
+// 改后
|
|
|
+List<FamilyMember> members = familyMemberMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<FamilyMember>()
|
|
|
+ .eq(FamilyMember::getFamilyId, familyId));
|
|
|
+List<FamilyMember> children = members.stream()
|
|
|
+ .filter(m -> "child".equals(computeEffectiveRole(m)))
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+// 或通过方法(性能更优,由 FamilyMemberService 提供工具方法)
|
|
|
+List<FamilyMember> 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 的并发写入需要加事务保护。
|