家庭成员角色系统重构方案.md 22 KB

家庭成员角色系统重构方案

目标:废除 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

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 表重命名字段

ALTER TABLE relationship_types
  CHANGE COLUMN capability_role default_role VARCHAR(20) DEFAULT NULL 
  COMMENT '默认角色模板: parent/child';

2.3 新建 system_config 表

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 数据迁移脚本

-- ============================================================
-- 迁移 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

@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

public interface SystemConfigMapper extends BaseMapper<SystemConfig> {}

SystemConfigService.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 字段变更

// 删除
- 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 字段变更

// 删除
- private String capabilityRole;

// 新增
+ private String defaultRole;  // 默认角色模板: parent/child

3.6 FamilyMemberVO.java 字段变更

// 删除
- private String capabilityRole;

// 新增
+ private String effectiveRole;   // 计算后的实际角色(前端展示用)
+ private String roleOverride;     // 管理员设置的覆盖值

3.7 SwitchMemberVO.java 字段变更

// 删除
- private String capabilityRole;

// 新增
+ private String effectiveRole;

3.8 AddFamilyMemberDTO.java 新增字段

+ private String roleOverride;  // auto/parent/child/elderly

3.9 FamilyMemberService.java 核心改动

3.9.1 新增 computeEffectiveRole 方法

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)

// 改前
member.setCapabilityRole(relType.getCapabilityRole());

// 改后
member.setRoleOverride("auto");

3.9.3 children 表同步逻辑(约 line 116)

// 改前
if ("child".equals(relType.getCapabilityRole())) {
    Child child = new Child();
    // ... childMapper.insert(child)
}

// 改后
// 无需同步到 children 表,数据已存在 family_members 中
// 如需 child 专属逻辑(游戏/任务),通过 computeEffectiveRole 判断即可

3.9.4 listMembers() 方法(约 line 188)

// 改前
vo.setCapabilityRole(fm.getCapabilityRole());

// 改后
vo.setEffectiveRole(computeEffectiveRole(fm));
vo.setRoleOverride(fm.getRoleOverride());

3.9.5 switchToMember() 方法(约 line 236)

// 改前
vo.setCapabilityRole(member.getCapabilityRole());

// 改后
vo.setEffectiveRole(computeEffectiveRole(member));

3.9.6 kickMember() 方法(约 line 287)

// 改前
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)

// 改前
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)

// 改前
existing.setCapabilityRole(type.getCapabilityRole());

// 改后
existing.setDefaultRole(type.getDefaultRole());

3.9.9 toFamilyMemberVO() 方法(约 line 454)

// 改前
vo.setCapabilityRole(member.getCapabilityRole());

// 改后
vo.setEffectiveRole(computeEffectiveRole(member));
vo.setRoleOverride(member.getRoleOverride());

3.10 所有引用 childMapper 的文件替换清单

Service 层(约 25 个)

文件 替换方式
UserService.java childMapper.selectListfamilyMemberMapper.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 同上

通用替换模式

// 改前
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

  • 字段标签:能力角色默认角色模板
  • 字段名:capabilityRoledefaultRole
  • 下拉选项说明:不指定则按年龄自动判断

新增家庭成员 roleOverride 下拉

  • 位置:家庭成员管理页(新建或修改成员时)
  • 字段:roleOverride 下拉(auto / parent / child / elderly)
  • 权限:仅家庭管理员可见

4.2 cfc-小程序

文件 改动
store/modules/family.js capabilityRoleeffectiveRole
家庭切换组件 使用 effectiveRole 决定家长端/孩子端界面
相关 API 响应处理 capabilityRoleeffectiveRole

五、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 的并发写入需要加事务保护。