目标:废除 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 → 模板值 → 年龄计算 |
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 '专注次数重置日期';
ALTER TABLE relationship_types
CHANGE COLUMN capability_role default_role VARCHAR(20) DEFAULT NULL
COMMENT '默认角色模板: parent/child';
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', '老年年龄分界线(岁)');
-- ============================================================
-- 迁移 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;
| 分类 | 新建 | 修改 | 删除 |
|---|---|---|---|
| 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 |
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;
}
}
cfc-backend/src/main/java/com/etotem/cfc/entity/Child.javacfc-backend/src/main/java/com/etotem/cfc/mapper/ChildMapper.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;
// 删除
- private String capabilityRole;
// 新增
+ private String defaultRole; // 默认角色模板: parent/child
// 删除
- private String capabilityRole;
// 新增
+ private String effectiveRole; // 计算后的实际角色(前端展示用)
+ private String roleOverride; // 管理员设置的覆盖值
// 删除
- private String capabilityRole;
// 新增
+ private String effectiveRole;
+ private String roleOverride; // auto/parent/child/elderly
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";
}
// 改前
member.setCapabilityRole(relType.getCapabilityRole());
// 改后
member.setRoleOverride("auto");
// 改前
if ("child".equals(relType.getCapabilityRole())) {
Child child = new Child();
// ... childMapper.insert(child)
}
// 改后
// 无需同步到 children 表,数据已存在 family_members 中
// 如需 child 专属逻辑(游戏/任务),通过 computeEffectiveRole 判断即可
// 改前
vo.setCapabilityRole(fm.getCapabilityRole());
// 改后
vo.setEffectiveRole(computeEffectiveRole(fm));
vo.setRoleOverride(fm.getRoleOverride());
// 改前
vo.setCapabilityRole(member.getCapabilityRole());
// 改后
vo.setEffectiveRole(computeEffectiveRole(member));
// 改前
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);
}
// 改前
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());
}
// 改前
existing.setCapabilityRole(type.getCapabilityRole());
// 改后
existing.setDefaultRole(type.getDefaultRole());
// 改前
vo.setCapabilityRole(member.getCapabilityRole());
// 改后
vo.setEffectiveRole(computeEffectiveRole(member));
vo.setRoleOverride(member.getRoleOverride());
| 文件 | 替换方式 |
|---|---|
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 |
同上 |
通用替换模式:
// 改前
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);
| 文件 | 改动 |
|---|---|
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 表同步逻辑 |
| 文件 | 改动 |
|---|---|
GuideFamilyTaskControllerTest.java |
mock Child 相关数据 → FamilyMember |
GuideRolePermissionTest.java |
同上 |
PointsControllerTest.java |
同上 |
StatsControllerTest.java |
同上 |
DailyCheckinStreakFlowTest.java |
同上 |
WishExchangeFlowTest.java |
同上 |
TaskServiceMinigameTest.java |
同上 |
GuideHierarchyServiceTest.java |
同上 |
| 文件 | 改动 |
|---|---|
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 |
无 |
能力角色 → 默认角色模板capabilityRole → defaultRole不指定则按年龄自动判断roleOverride 下拉(auto / parent / child / elderly)| 文件 | 改动 |
|---|---|
store/modules/family.js |
capabilityRole → effectiveRole |
| 家庭切换组件 | 使用 effectiveRole 决定家长端/孩子端界面 |
| 相关 API 响应处理 | capabilityRole → effectiveRole |
需同步更新以下内容:
family_members 表:role_override 字段替换 capability_role,新增 10 个 child 字段relationship_types 表:default_role 字段替换 capability_rolesystem_config 表定义children 表定义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 前端启动验证
| 文件 | 操作 |
|---|---|
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/家庭切换组件(路径待确认) |
修改 |
tasks.child_id 外键:tasks 表的 child_id 字段原指向 children.id,合并后该字段需要业务确认处理方式。建议保留字段但通过 (family_id, nickname) 匹配到新的 family_members.id。
ID 空间冲突:children 表和 family_members 表的 ID 自增序列独立,迁移后不会冲突(匹配行复用 family_members 的 ID,不匹配行 INSERT 时会分配新的 ID)。
40+ 文件改动:全部改完后建议用 mvn clean compile 全量验证,逐个 Service 测试太慢。
测试覆盖:测试类中的 mock 数据结构需要同步更新为 FamilyMember 结构。
数据一致性:迁移过程中对 children 和 family_members 的并发写入需要加事务保护。