Browse Source

feat: ButlerService新增管家浏览/查询/自助绑定方法

iwt 2 months ago
parent
commit
1af197a4f5
1 changed files with 169 additions and 0 deletions
  1. 169 0
      cfc-backend/src/main/java/com/etotem/cfc/service/ButlerService.java

+ 169 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/ButlerService.java

@@ -18,6 +18,11 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import com.etotem.cfc.util.SortUtil;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.etotem.cfc.entity.ButlerAssignment;
+import com.etotem.cfc.mapper.ButlerAssignmentMapper;
+import com.etotem.cfc.service.MemberSubscriptionService;
+import com.etotem.cfc.entity.MemberSubscription;
 
 @Service
 public class ButlerService {
@@ -33,6 +38,12 @@ public class ButlerService {
     @Resource
     private UserService userService;
 
+    @Resource
+    private ButlerAssignmentMapper butlerAssignmentMapper;
+
+    @Resource
+    private MemberSubscriptionService memberSubscriptionService;
+
     /**
      * 申请成为管家
      * @param userId 申请人用户 ID
@@ -325,4 +336,162 @@ public class ButlerService {
 
         return butler.getUserId();
     }
+
+    // ==================== 家庭端自助选择 ====================
+
+    /**
+     * 可选管家列表(浏览开放,不校验会员等级)
+     * 条件:approved + tier=L2 + accepting=1 + 有余量;返回脱敏档案
+     */
+    public Result<List<Map<String, Object>>> getAvailableButlers() {
+        LambdaQueryWrapper<ButlerProfile> query = new LambdaQueryWrapper<>();
+        query.eq(ButlerProfile::getStatus, "approved")
+             .eq(ButlerProfile::getTier, "L2")
+             .eq(ButlerProfile::getAccepting, 1)
+             .apply("member_count < max_members")
+             .orderByAsc(ButlerProfile::getCreatedAt);
+        SortUtil.applySort(query);
+        List<ButlerProfile> profiles = butlerProfileMapper.selectList(query);
+
+        List<Long> userIds = new java.util.ArrayList<>();
+        for (ButlerProfile p : profiles) {
+            userIds.add(p.getUserId());
+        }
+        Map<Long, User> userMap = new HashMap<>();
+        if (!userIds.isEmpty()) {
+            for (User u : userMapper.selectBatchIds(userIds)) {
+                userMap.put(u.getId(), u);
+            }
+        }
+
+        List<Map<String, Object>> result = new java.util.ArrayList<>();
+        for (ButlerProfile p : profiles) {
+            User u = userMap.get(p.getUserId());
+            Map<String, Object> item = new HashMap<>();
+            item.put("butlerUserId", p.getUserId());
+            item.put("nickname", u != null ? u.getNickname() : "管家");
+            item.put("avatar", u != null ? u.getAvatar() : "");
+            item.put("description", p.getDescription());
+            item.put("certificates", p.getCertificates());
+            item.put("remainingSlots", (p.getMaxMembers() == null ? 0 : p.getMaxMembers())
+                    - (p.getMemberCount() == null ? 0 : p.getMemberCount()));
+            result.add(item);
+        }
+        return Result.success(result);
+    }
+
+    /**
+     * 当前家庭的管家绑定信息(无绑定时 data 为 null)
+     */
+    public Result<Map<String, Object>> getMyButler(Long userId) {
+        Long familyId = requireFamilyId(userId);
+        if (familyId == null) {
+            return Result.error("未找到家庭信息");
+        }
+        ButlerAssignment assignment = butlerAssignmentMapper.selectOne(
+                new LambdaQueryWrapper<ButlerAssignment>()
+                        .eq(ButlerAssignment::getFamilyId, familyId)
+                        .last("LIMIT 1"));
+        if (assignment == null) {
+            return Result.success(null);
+        }
+        return Result.success(buildButlerSummary(assignment));
+    }
+
+    /**
+     * 自助绑定/更换管家:L2 校验 + 条件扣容 + INSERT/UPDATE 双路径
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public Result<Map<String, Object>> selectButler(Long userId, Long butlerUserId) {
+        Long familyId = requireFamilyId(userId);
+        if (familyId == null) {
+            return Result.error("未找到家庭信息");
+        }
+        // ① L2 有效订阅校验
+        MemberSubscription sub = memberSubscriptionService.getActiveSubscription(familyId);
+        boolean active = sub != null && sub.getExpireTime() != null
+                && sub.getExpireTime().after(new Date());
+        if (!active || !"L2".equals(sub.getLevel())) {
+            return Result.error("该功能面向久久一生(L2)会员家庭");
+        }
+        // ② 目标管家校验
+        ButlerProfile butler = butlerProfileMapper.selectOne(
+                new LambdaQueryWrapper<ButlerProfile>()
+                        .eq(ButlerProfile::getUserId, butlerUserId)
+                        .eq(ButlerProfile::getStatus, "approved")
+                        .eq(ButlerProfile::getTier, "L2")
+                        .eq(ButlerProfile::getAccepting, 1)
+                        .last("LIMIT 1"));
+        if (butler == null) {
+            return Result.error("该管家不可选择");
+        }
+        // ③ 条件原子扣容(防并发超卖)
+        LambdaUpdateWrapper<ButlerProfile> incr = new LambdaUpdateWrapper<>();
+        incr.eq(ButlerProfile::getId, butler.getId())
+            .apply("member_count < max_members")
+            .setSql("member_count = member_count + 1");
+        int affected = butlerProfileMapper.update(null, incr);
+        if (affected == 0) {
+            return Result.error("该管家名额已满");
+        }
+        // ④ assignments INSERT/UPDATE 双路径(激活自动分配只加计数不建行)
+        ButlerAssignment existing = butlerAssignmentMapper.selectOne(
+                new LambdaQueryWrapper<ButlerAssignment>()
+                        .eq(ButlerAssignment::getFamilyId, familyId)
+                        .last("LIMIT 1"));
+        if (existing != null) {
+            // 旧管家计数回退
+            LambdaUpdateWrapper<ButlerProfile> decr = new LambdaUpdateWrapper<>();
+            decr.eq(ButlerProfile::getUserId, existing.getButlerUserId())
+                .setSql("member_count = GREATEST(member_count - 1, 0)");
+            butlerProfileMapper.update(null, decr);
+
+            existing.setButlerUserId(butlerUserId);
+            existing.setLevel(sub.getLevel());
+            existing.setAssignedAt(new Date());
+            butlerAssignmentMapper.updateById(existing);
+        } else {
+            ButlerAssignment na = new ButlerAssignment();
+            na.setFamilyId(familyId);
+            na.setButlerUserId(butlerUserId);
+            na.setSubscriptionId(sub.getId());
+            na.setLevel(sub.getLevel());
+            na.setAssignedAt(new Date());
+            butlerAssignmentMapper.insert(na);
+        }
+        log.info("Family {} selected butler {}, level={}", familyId, butlerUserId, sub.getLevel());
+
+        ButlerAssignment saved = butlerAssignmentMapper.selectOne(
+                new LambdaQueryWrapper<ButlerAssignment>()
+                        .eq(ButlerAssignment::getFamilyId, familyId)
+                        .last("LIMIT 1"));
+        return Result.success(buildButlerSummary(saved));
+    }
+
+    /**
+     * userId -> familyId(与 MembershipController 同源:users.family_id)
+     */
+    private Long requireFamilyId(Long userId) {
+        User user = userMapper.selectById(userId);
+        return user != null ? user.getFamilyId() : null;
+    }
+
+    /**
+     * 组装绑定摘要(脱敏:不含联系方式)
+     */
+    private Map<String, Object> buildButlerSummary(ButlerAssignment assignment) {
+        Map<String, Object> summary = new HashMap<>();
+        summary.put("butlerUserId", assignment.getButlerUserId());
+        summary.put("level", assignment.getLevel());
+        summary.put("assignedAt", assignment.getAssignedAt());
+        User bu = userMapper.selectById(assignment.getButlerUserId());
+        summary.put("nickname", bu != null ? bu.getNickname() : "管家");
+        summary.put("avatar", bu != null ? bu.getAvatar() : "");
+        ButlerProfile bp = butlerProfileMapper.selectOne(
+                new LambdaQueryWrapper<ButlerProfile>()
+                        .eq(ButlerProfile::getUserId, assignment.getButlerUserId())
+                        .last("LIMIT 1"));
+        summary.put("description", bp != null ? bp.getDescription() : "");
+        return summary;
+    }
 }