Просмотр исходного кода

feat(family-member): 实现成员切换/退出/回收核心逻辑

Sisyphus 1 месяц назад
Родитель
Сommit
cf54be15dd
1 измененных файлов с 313 добавлено и 19 удалено
  1. 313 19
      cfc-backend/src/main/java/com/etotem/cfc/service/FamilyMemberService.java

+ 313 - 19
cfc-backend/src/main/java/com/etotem/cfc/service/FamilyMemberService.java

@@ -10,12 +10,16 @@ import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.entity.FamilyRelationship;
 import com.etotem.cfc.entity.FamilyMemberAttributes;
 import com.etotem.cfc.entity.FamilyMemberLog;
+import com.etotem.cfc.entity.FiveDimensionScore;
+import com.etotem.cfc.entity.HealthReport;
 import com.etotem.cfc.entity.User;
 import com.etotem.cfc.enums.GenerationLevel;
 import com.etotem.cfc.mapper.FamilyMapper;
 import com.etotem.cfc.mapper.FamilyMemberAttributesMapper;
 import com.etotem.cfc.mapper.FamilyMemberLogMapper;
 import com.etotem.cfc.mapper.FamilyMemberMapper;
+import com.etotem.cfc.mapper.FiveDimensionScoreMapper;
+import com.etotem.cfc.mapper.HealthReportMapper;
 import com.etotem.cfc.mapper.UserMapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -63,6 +67,12 @@ public class FamilyMemberService {
     @Resource
     private CouponService couponService;
 
+    @Resource
+    private FiveDimensionScoreMapper fiveDimensionScoreMapper;
+
+    @Resource
+    private HealthReportMapper healthReportMapper;
+
     /**
      * 添加家庭成员
      */
@@ -211,9 +221,11 @@ public class FamilyMemberService {
         Long creatorId = (family != null) ? family.getCreatorId() : null;
         boolean isAdmin = creatorId != null && creatorId.equals(userId);
 
-        // 查询 family_members 表
+        // 查询 family_members 表(排除回收箱成员)
         List<FamilyMember> familyMembers = familyMemberMapper.selectList(
-                new LambdaQueryWrapper<FamilyMember>().eq(FamilyMember::getFamilyId, familyId));
+                new LambdaQueryWrapper<FamilyMember>()
+                        .eq(FamilyMember::getFamilyId, familyId)
+                        .ne(FamilyMember::getStatus, "recycled"));
 
         // 查找当前查看者在 family_members 中的记录(用于计算视角标签)
         FamilyMember viewerMember = null;
@@ -365,7 +377,13 @@ public class FamilyMemberService {
     }
 
     /**
-     * 踢出家庭成员
+     * 踢出家庭成员(管理员操作)
+     * 规则:
+     * - 仅管理员可踢出
+     * - 被踢成员 CF 值(system_points)随成员带走
+     * - 收回该家庭发放给该成员的人口券(POPULATION)
+     * - 微信登录用户:迁移资产到其初始家庭(无则创建新家庭)
+     * - 非微信登录用户:进入回收箱(status=recycled),资产保留,可后续回收
      */
     @Transactional(rollbackFor = Exception.class)
     public void kickMember(Long userId, Long memberId) {
@@ -384,30 +402,304 @@ public class FamilyMemberService {
 
         // 从 family_members 踢出
         FamilyMember member = familyMemberMapper.selectById(memberId);
-        if (member != null && member.getFamilyId().equals(operator.getFamilyId())) {
-            // 先清理关系记录
-            familyRelationshipService.deleteRelationsForMember(memberId);
-            familyMemberMapper.deleteById(memberId);
-            logMemberAction(operator.getFamilyId(), memberId, "kick", member.getNickname(), null, userId);
-            // 如果关联了 User:优先恢复到其自己创建的家庭,否则清除 familyId
-            if (member.getUserId() != null) {
-                User targetUser = userMapper.selectById(member.getUserId());
-                if (targetUser != null && operator.getFamilyId().equals(targetUser.getFamilyId())) {
-                    Long ownFamilyId = findOwnFamilyId(targetUser.getId());
+        if (member == null || !member.getFamilyId().equals(operator.getFamilyId())) {
+            throw new RuntimeException("成员不存在");
+        }
+        // 不能踢出家庭创建者
+        if (member.getUserId() != null && Long.valueOf(member.getUserId()).equals(family.getCreatorId())) {
+            throw new RuntimeException("不能踢出家庭创建者,请先转让管理员权限");
+        }
+
+        Long oldFamilyId = member.getFamilyId();
+        Long targetUserId = member.getUserId();
+
+        // 收回该家庭发放给该成员的人口券
+        try {
+            couponService.revokeFamilyPopulationCoupons(oldFamilyId, memberId);
+        } catch (Exception e) {
+            log.warn("收回人口券异常: familyId={}, memberId={}", oldFamilyId, memberId, e);
+        }
+
+        // 清理关系记录
+        familyRelationshipService.deleteRelationsForMember(memberId);
+
+        if (targetUserId != null) {
+            User targetUser = userMapper.selectById(targetUserId);
+            if (targetUser != null && oldFamilyId.equals(targetUser.getFamilyId())) {
+                boolean isWechatUser = isWechatUser(targetUser);
+                if (isWechatUser) {
+                    // 微信用户:迁移资产到初始家庭,CF 带走
+                    Long ownFamilyId = findOwnFamilyId(targetUserId);
+                    if (ownFamilyId == null) {
+                        ownFamilyId = createFamilyForUser(targetUser);
+                    }
+                    migrateMemberAssets(member, oldFamilyId, ownFamilyId);
+                    member.setFamilyId(ownFamilyId);
+                    member.setStatus("active");
+                    member.setUpdatedAt(new Date());
+                    familyMemberMapper.updateById(member);
                     targetUser.setFamilyId(ownFamilyId);
                     targetUser.setUpdatedAt(new Date());
                     userMapper.updateById(targetUser);
+                } else {
+                    // 非微信用户:进入回收箱,资产保留在原家庭记录上
+                    member.setStatus("recycled");
+                    member.setUpdatedAt(new Date());
+                    familyMemberMapper.updateById(member);
+                    targetUser.setFamilyId(null);
+                    targetUser.setUpdatedAt(new Date());
+                    userMapper.updateById(targetUser);
                 }
             }
-            // children 表已废除,无需额外清理
-            // 如果成员有效角色为 child,可能需要清理其他关联数据(按需扩展)
-            if ("child".equals(computeEffectiveRole(member))) {
-                // child 专属数据(如游戏记录等)后续按需清理
+        } else {
+            // 无关联用户(纯手动添加的成员):直接删除记录
+            familyMemberMapper.deleteById(memberId);
+        }
+
+        logMemberAction(oldFamilyId, memberId, "kick", member.getNickname(), null, userId);
+    }
+
+    /**
+     * 主动退出家庭(微信登录用户)
+     * 规则:
+     * - 家庭创建者禁止退出,需先转让管理员
+     * - CF 值(system_points)清零丢弃(留在原家庭)
+     * - 迁移资产到初始家庭(无则创建新家庭)
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public void leaveFamily(Long userId) {
+        User user = userMapper.selectById(userId);
+        if (user == null || user.getFamilyId() == null) {
+            throw new RuntimeException("用户未加入家庭");
+        }
+        Long oldFamilyId = user.getFamilyId();
+        Family family = familyMapper.selectById(oldFamilyId);
+        if (family == null) {
+            throw new RuntimeException("家庭不存在");
+        }
+        // 家庭创建者禁止退出
+        if (Long.valueOf(userId).equals(family.getCreatorId())) {
+            throw new RuntimeException("您是家庭创建者,请先转让管理员权限后再退出");
+        }
+        // 非微信用户禁止自主退出
+        if (!isWechatUser(user)) {
+            throw new RuntimeException("非微信登录用户不能自主退出家庭");
+        }
+
+        // 查找该用户在 family_members 中的记录
+        FamilyMember member = familyMemberMapper.selectOne(
+                new LambdaQueryWrapper<FamilyMember>()
+                        .eq(FamilyMember::getFamilyId, oldFamilyId)
+                        .eq(FamilyMember::getUserId, userId)
+                        .last("LIMIT 1"));
+        if (member == null) {
+            throw new RuntimeException("未找到该用户的家庭成员记录");
+        }
+
+        // CF 值清零丢弃(留在原家庭)
+        member.setSystemPoints(0);
+        member.setUpdatedAt(new Date());
+
+        // 清理关系记录
+        familyRelationshipService.deleteRelationsForMember(member.getId());
+
+        // 迁移资产到初始家庭
+        Long ownFamilyId = findOwnFamilyId(userId);
+        if (ownFamilyId == null) {
+            ownFamilyId = createFamilyForUser(user);
+        }
+        migrateMemberAssets(member, oldFamilyId, ownFamilyId);
+        member.setFamilyId(ownFamilyId);
+        member.setStatus("active");
+        familyMemberMapper.updateById(member);
+
+        user.setFamilyId(ownFamilyId);
+        user.setUpdatedAt(new Date());
+        userMapper.updateById(user);
+
+        logMemberAction(oldFamilyId, member.getId(), "leave", member.getNickname(), null, userId);
+    }
+
+    /**
+     * 从回收箱回收成员(管理员操作):将回收箱中的非微信用户重新加入指定家庭
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public void recycleMember(Long operatorUserId, Long memberId, Long targetFamilyId) {
+        User operator = userMapper.selectById(operatorUserId);
+        if (operator == null || operator.getFamilyId() == null) {
+            throw new RuntimeException("用户未加入家庭");
+        }
+        Family targetFamily = familyMapper.selectById(targetFamilyId);
+        if (targetFamily == null) {
+            throw new RuntimeException("目标家庭不存在");
+        }
+        // 仅管理员可回收
+        if (!Long.valueOf(operatorUserId).equals(targetFamily.getCreatorId())) {
+            throw new RuntimeException("只有目标家庭管理员才能回收成员");
+        }
+
+        FamilyMember member = familyMemberMapper.selectById(memberId);
+        if (member == null) {
+            throw new RuntimeException("成员不存在");
+        }
+        if (!"recycled".equals(member.getStatus())) {
+            throw new RuntimeException("该成员不在回收箱中");
+        }
+
+        // 迁移资产到目标家庭
+        Long oldFamilyId = member.getFamilyId();
+        migrateMemberAssets(member, oldFamilyId, targetFamilyId);
+        member.setFamilyId(targetFamilyId);
+        member.setStatus("active");
+        member.setUpdatedAt(new Date());
+        familyMemberMapper.updateById(member);
+
+        // 关联用户重新归属目标家庭
+        if (member.getUserId() != null) {
+            User targetUser = userMapper.selectById(member.getUserId());
+            if (targetUser != null) {
+                targetUser.setFamilyId(targetFamilyId);
+                targetUser.setUpdatedAt(new Date());
+                userMapper.updateById(targetUser);
             }
+        }
+
+        logMemberAction(targetFamilyId, memberId, "recycle", null, member.getNickname(), operatorUserId);
+    }
+
+    /**
+     * 切换成员到目标家庭(被邀请走/加入新家庭场景)
+     * 规则:
+     * - 非微信用户禁止自主切换
+     * - 家庭创建者禁止切换(需先转让管理员)
+     * - CF 值(system_points)清零丢弃(留在原家庭)
+     * - 迁移资产到目标家庭
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public void switchMemberToFamily(Long userId, Long targetFamilyId) {
+        User user = userMapper.selectById(userId);
+        if (user == null || user.getFamilyId() == null) {
+            throw new RuntimeException("用户未加入家庭");
+        }
+        Long oldFamilyId = user.getFamilyId();
+        if (oldFamilyId.equals(targetFamilyId)) {
             return;
         }
+        Family oldFamily = familyMapper.selectById(oldFamilyId);
+        if (oldFamily != null && Long.valueOf(userId).equals(oldFamily.getCreatorId())) {
+            throw new RuntimeException("您是家庭创建者,请先转让管理员权限后再切换家庭");
+        }
+        if (!isWechatUser(user)) {
+            throw new RuntimeException("非微信登录用户不能自主切换家庭");
+        }
+
+        FamilyMember member = familyMemberMapper.selectOne(
+                new LambdaQueryWrapper<FamilyMember>()
+                        .eq(FamilyMember::getFamilyId, oldFamilyId)
+                        .eq(FamilyMember::getUserId, userId)
+                        .last("LIMIT 1"));
+        if (member == null) {
+            throw new RuntimeException("未找到该用户的家庭成员记录");
+        }
+
+        // CF 值清零丢弃(留在原家庭)
+        member.setSystemPoints(0);
+        member.setUpdatedAt(new Date());
+
+        // 清理关系记录
+        familyRelationshipService.deleteRelationsForMember(member.getId());
+
+        // 迁移资产到目标家庭
+        migrateMemberAssets(member, oldFamilyId, targetFamilyId);
+        member.setFamilyId(targetFamilyId);
+        member.setStatus("active");
+        familyMemberMapper.updateById(member);
+
+        user.setFamilyId(targetFamilyId);
+        user.setUpdatedAt(new Date());
+        userMapper.updateById(user);
 
-        throw new RuntimeException("成员不存在");
+        logMemberAction(oldFamilyId, member.getId(), "switch", member.getNickname(), null, userId);
+    }
+
+    /**
+     * 迁移成员资产到目标家庭:
+     * - five_dimension_scores(能量值):改 family_id
+     * - health_reports(健康报告):改 family_id
+     * - dan_assessment_results(测评):以 familyMemberId 关联,改 family_members.family_id 后自动跟随,无需迁移
+     * - family_member_attributes(属性):以 memberId 关联,自动跟随
+     * - family_relationships(关系):删旧建新(新家庭关系需重新计算)
+     */
+    private void migrateMemberAssets(FamilyMember member, Long oldFamilyId, Long targetFamilyId) {
+        if (oldFamilyId.equals(targetFamilyId)) {
+            return;
+        }
+        Long memberId = member.getId();
+        // 能量值
+        try {
+            List<FiveDimensionScore> scores = fiveDimensionScoreMapper.selectList(
+                    new LambdaQueryWrapper<FiveDimensionScore>()
+                            .eq(FiveDimensionScore::getFamilyId, oldFamilyId)
+                            .eq(FiveDimensionScore::getMemberId, memberId));
+            for (FiveDimensionScore s : scores) {
+                s.setFamilyId(targetFamilyId);
+                fiveDimensionScoreMapper.updateById(s);
+            }
+        } catch (Exception e) {
+            log.warn("迁移能量值异常: memberId={}", memberId, e);
+        }
+        // 健康报告(按 userId 或 subjectId 关联)
+        try {
+            if (member.getUserId() != null) {
+                List<HealthReport> reports = healthReportMapper.selectList(
+                        new LambdaQueryWrapper<HealthReport>()
+                                .eq(HealthReport::getFamilyId, oldFamilyId)
+                                .and(w -> w.eq(HealthReport::getUserId, member.getUserId())
+                                        .or().eq(HealthReport::getSubjectId, member.getUserId())));
+                for (HealthReport r : reports) {
+                    r.setFamilyId(targetFamilyId);
+                    healthReportMapper.updateById(r);
+                }
+            }
+        } catch (Exception e) {
+            log.warn("迁移健康报告异常: memberId={}", memberId, e);
+        }
+    }
+
+    /**
+     * 判断用户是否为微信登录用户(openid 为真实微信 openid,而非 "phone:" 前缀)
+     */
+    private boolean isWechatUser(User user) {
+        if (user == null || user.getOpenid() == null) {
+            return false;
+        }
+        return !user.getOpenid().startsWith("phone:");
+    }
+
+    /**
+     * 为用户创建新家庭(不含成员记录——调用方随后会迁移原 family_members 记录过来,避免重复)
+     */
+    private Long createFamilyForUser(User user) {
+        Family family = new Family();
+        String familyName = (user.getNickname() != null && !user.getNickname().isEmpty())
+                ? user.getNickname() + "的家庭" : "我的家庭";
+        family.setName(familyName);
+        family.setInviteCode(generateInviteCode());
+        family.setCreatorId(user.getId());
+        family.setCreatedAt(new Date());
+        family.setUpdatedAt(new Date());
+        familyMapper.insert(family);
+        return family.getId();
+    }
+
+    private String generateInviteCode() {
+        String chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
+        StringBuilder sb = new StringBuilder();
+        java.util.Random random = new java.util.Random();
+        for (int i = 0; i < 6; i++) {
+            sb.append(chars.charAt(random.nextInt(chars.length())));
+        }
+        return sb.toString();
     }
 
     /**
@@ -763,7 +1055,9 @@ public class FamilyMemberService {
         result.put("parents", userMapper.selectList(
                 new LambdaQueryWrapper<User>().eq(User::getFamilyId, familyId).eq(User::getRole, "parent")));
         result.put("children", familyMemberMapper.selectList(
-                new LambdaQueryWrapper<FamilyMember>().eq(FamilyMember::getFamilyId, familyId)));
+                new LambdaQueryWrapper<FamilyMember>()
+                        .eq(FamilyMember::getFamilyId, familyId)
+                        .ne(FamilyMember::getStatus, "recycled")));
         return result;
     }
 }