|
|
@@ -0,0 +1,387 @@
|
|
|
+package com.etotem.cfc.service;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import com.etotem.cfc.dto.UnlockGateDTO;
|
|
|
+import com.etotem.cfc.dto.UnlockStatusDTO;
|
|
|
+import com.etotem.cfc.entity.*;
|
|
|
+import com.etotem.cfc.mapper.*;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import javax.annotation.Resource;
|
|
|
+import java.util.*;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 通关解锁服务:8种关卡判定 + 进度刷新 + 奖励发放
|
|
|
+ */
|
|
|
+@Service
|
|
|
+public class UnlockGateService {
|
|
|
+
|
|
|
+ private static final Logger log = LoggerFactory.getLogger(UnlockGateService.class);
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private UnlockGateMapper unlockGateMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private FamilyGateProgressMapper familyGateProgressMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private UserMapper userMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private FamilyMemberMapper familyMemberMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private FiveDimensionSelfCheckMapper selfCheckMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private MicroActionRecordMapper microActionRecordMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private HealthReportMapper healthReportMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private ProductOrderMapper productOrderMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private FamilyMembershipMapper familyMembershipMapper;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private EnergyService energyService;
|
|
|
+
|
|
|
+ // ========== 核心判定 ==========
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判定某家庭是否已通关某关卡(幂等)
|
|
|
+ */
|
|
|
+ public boolean isGateUnlocked(Long familyId, UnlockGate gate) {
|
|
|
+ if (gate == null || gate.getEnabled() == null || gate.getEnabled() == 0) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ String type = gate.getGateType();
|
|
|
+ if ("SELF_CHECK".equals(type)) {
|
|
|
+ // SELF_CHECK 作为家庭级判定(复测对比用):family 内任一成员有记录
|
|
|
+ List<User> familyUsers = userMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<User>().eq(User::getFamilyId, familyId));
|
|
|
+ if (familyUsers.isEmpty()) return false;
|
|
|
+ List<Long> userIds = familyUsers.stream().map(User::getId).collect(Collectors.toList());
|
|
|
+ long count = selfCheckMapper.selectCount(
|
|
|
+ new LambdaQueryWrapper<FiveDimensionSelfCheck>().in(FiveDimensionSelfCheck::getUserId, userIds));
|
|
|
+ int minCount = 1;
|
|
|
+ if (gate.getConditionParams() != null && !gate.getConditionParams().isEmpty()) {
|
|
|
+ // parse JSON for minCount
|
|
|
+ if (gate.getConditionParams().contains("\"minCount\"")) {
|
|
|
+ String val = gate.getConditionParams().replaceAll(".*\"minCount\":\\s*(\\d+).*", "$1");
|
|
|
+ try { minCount = Integer.parseInt(val); } catch (Exception ignored) {}
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return count >= minCount;
|
|
|
+ } else if ("INVITE_FAMILY".equals(type)) {
|
|
|
+ return userMapper.selectCount(
|
|
|
+ new LambdaQueryWrapper<User>().eq(User::getFamilyId, familyId)) >= 2;
|
|
|
+ } else if ("CREATE_FAMILY_MEMBER".equals(type)) {
|
|
|
+ return familyMemberMapper.selectCount(
|
|
|
+ new LambdaQueryWrapper<FamilyMember>().eq(FamilyMember::getFamilyId, familyId)) >= 1;
|
|
|
+ } else if ("PURCHASE".equals(type)) {
|
|
|
+ return productOrderMapper.selectCount(
|
|
|
+ new LambdaQueryWrapper<ProductOrder>()
|
|
|
+ .eq(ProductOrder::getFamilyId, familyId)
|
|
|
+ .eq(ProductOrder::getStatus, "paid")) >= 1;
|
|
|
+ } else if ("MEMBERSHIP".equals(type)) {
|
|
|
+ return familyMembershipMapper.selectCount(
|
|
|
+ new LambdaQueryWrapper<FamilyMembership>()
|
|
|
+ .eq(FamilyMembership::getFamilyId, familyId)
|
|
|
+ .eq(FamilyMembership::getPaymentStatus, "paid")) >= 1;
|
|
|
+ } else if ("REPORT_UPLOAD".equals(type)) {
|
|
|
+ return healthReportMapper.selectCount(
|
|
|
+ new LambdaQueryWrapper<HealthReport>().eq(HealthReport::getFamilyId, familyId)) >= 1;
|
|
|
+ } else if ("MICRO_ACTION".equals(type)) {
|
|
|
+ // 先查家庭所有孩子 ID
|
|
|
+ List<FamilyMember> children = familyMemberMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<FamilyMember>().eq(FamilyMember::getFamilyId, familyId));
|
|
|
+ if (children.isEmpty()) return false;
|
|
|
+ List<Long> childIds = children.stream().map(FamilyMember::getId).collect(Collectors.toList());
|
|
|
+ int count = 1;
|
|
|
+ if (gate.getConditionParams() != null && gate.getConditionParams().contains("\"count\"")) {
|
|
|
+ String val = gate.getConditionParams().replaceAll(".*\"count\":\\s*(\\d+).*", "$1");
|
|
|
+ try { count = Integer.parseInt(val); } catch (Exception ignored) {}
|
|
|
+ }
|
|
|
+ return microActionRecordMapper.selectCount(
|
|
|
+ new LambdaQueryWrapper<MicroActionRecord>().in(MicroActionRecord::getChildId, childIds)) >= count;
|
|
|
+ } else if ("STREAK_DAYS".equals(type)) {
|
|
|
+ List<FamilyMember> children = familyMemberMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<FamilyMember>().eq(FamilyMember::getFamilyId, familyId));
|
|
|
+ int days = 3;
|
|
|
+ if (gate.getConditionParams() != null && gate.getConditionParams().contains("\"days\"")) {
|
|
|
+ String val = gate.getConditionParams().replaceAll(".*\"days\":\\s*(\\d+).*", "$1");
|
|
|
+ try { days = Integer.parseInt(val); } catch (Exception ignored) {}
|
|
|
+ }
|
|
|
+ int maxStreak = children.stream()
|
|
|
+ .filter(m -> m.getStreakDays() != null)
|
|
|
+ .mapToInt(FamilyMember::getStreakDays)
|
|
|
+ .max().orElse(0);
|
|
|
+ return maxStreak >= days;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 个人级判定 ==========
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 当前用户是否通过个人 SELF_CHECK 关卡
|
|
|
+ */
|
|
|
+ public boolean isPersonalSelfCheckPassed(Long userId) {
|
|
|
+ long count = selfCheckMapper.selectCount(
|
|
|
+ new LambdaQueryWrapper<FiveDimensionSelfCheck>().eq(FiveDimensionSelfCheck::getUserId, userId));
|
|
|
+ return count >= 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 当前锁定关卡 ==========
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取当前锁定的主线硬门禁关卡
|
|
|
+ */
|
|
|
+ public UnlockGate getCurrentLockGate(Long familyId) {
|
|
|
+ List<UnlockGate> gates = unlockGateMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<UnlockGate>()
|
|
|
+ .eq(UnlockGate::getEnabled, 1)
|
|
|
+ .eq(UnlockGate::getIsRequired, 1)
|
|
|
+ .eq(UnlockGate::getIsSoft, 0)
|
|
|
+ .orderByAsc(UnlockGate::getSortOrder));
|
|
|
+ for (UnlockGate gate : gates) {
|
|
|
+ FamilyGateProgress progress = familyGateProgressMapper.selectOne(
|
|
|
+ new LambdaQueryWrapper<FamilyGateProgress>()
|
|
|
+ .eq(FamilyGateProgress::getFamilyId, familyId)
|
|
|
+ .eq(FamilyGateProgress::getGateId, gate.getId()));
|
|
|
+ if (progress == null || "LOCKED".equals(progress.getStatus())) {
|
|
|
+ return gate;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 进度刷新 ==========
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 刷新家庭全部关卡进度(幂等,写 family_gate_progress)
|
|
|
+ */
|
|
|
+ public void refreshFamilyGates(Long familyId) {
|
|
|
+ if (familyId == null) return;
|
|
|
+ List<UnlockGate> gates = unlockGateMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<UnlockGate>().eq(UnlockGate::getEnabled, 1));
|
|
|
+ for (UnlockGate gate : gates) {
|
|
|
+ boolean unlocked = isGateUnlocked(familyId, gate);
|
|
|
+ FamilyGateProgress progress = familyGateProgressMapper.selectOne(
|
|
|
+ new LambdaQueryWrapper<FamilyGateProgress>()
|
|
|
+ .eq(FamilyGateProgress::getFamilyId, familyId)
|
|
|
+ .eq(FamilyGateProgress::getGateId, gate.getId()));
|
|
|
+ if (unlocked) {
|
|
|
+ if (progress == null || "LOCKED".equals(progress.getStatus())) {
|
|
|
+ FamilyGateProgress p = new FamilyGateProgress();
|
|
|
+ p.setFamilyId(familyId);
|
|
|
+ p.setGateId(gate.getId());
|
|
|
+ p.setStatus("UNLOCKED");
|
|
|
+ p.setCreatedAt(new Date());
|
|
|
+ p.setUnlockedAt(new Date());
|
|
|
+ if (progress == null) {
|
|
|
+ familyGateProgressMapper.insert(p);
|
|
|
+ } else {
|
|
|
+ progress.setStatus("UNLOCKED");
|
|
|
+ progress.setUnlockedAt(new Date());
|
|
|
+ familyGateProgressMapper.updateById(progress);
|
|
|
+ }
|
|
|
+ // 发放奖励
|
|
|
+ grantGateRewards(familyId, gate);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (progress == null) {
|
|
|
+ FamilyGateProgress p = new FamilyGateProgress();
|
|
|
+ p.setFamilyId(familyId);
|
|
|
+ p.setGateId(gate.getId());
|
|
|
+ p.setStatus("LOCKED");
|
|
|
+ p.setCreatedAt(new Date());
|
|
|
+ familyGateProgressMapper.insert(p);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 刷新个人级 SELF_CHECK 关卡
|
|
|
+ */
|
|
|
+ public void refreshPersonalGates(Long userId, Long familyId) {
|
|
|
+ if (userId == null) return;
|
|
|
+ // 查 SELF_CHECK 主线关卡
|
|
|
+ List<UnlockGate> selfCheckGates = unlockGateMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<UnlockGate>()
|
|
|
+ .eq(UnlockGate::getGateType, "SELF_CHECK")
|
|
|
+ .eq(UnlockGate::getIsRequired, 1)
|
|
|
+ .eq(UnlockGate::getIsSoft, 0)
|
|
|
+ .eq(UnlockGate::getEnabled, 1));
|
|
|
+ if (selfCheckGates.isEmpty()) return;
|
|
|
+ boolean passed = isPersonalSelfCheckPassed(userId);
|
|
|
+ if (passed && familyId != null) {
|
|
|
+ for (UnlockGate gate : selfCheckGates) {
|
|
|
+ FamilyGateProgress progress = familyGateProgressMapper.selectOne(
|
|
|
+ new LambdaQueryWrapper<FamilyGateProgress>()
|
|
|
+ .eq(FamilyGateProgress::getFamilyId, familyId)
|
|
|
+ .eq(FamilyGateProgress::getGateId, gate.getId()));
|
|
|
+ if (progress == null || "LOCKED".equals(progress.getStatus())) {
|
|
|
+ FamilyGateProgress p = new FamilyGateProgress();
|
|
|
+ p.setFamilyId(familyId);
|
|
|
+ p.setGateId(gate.getId());
|
|
|
+ p.setStatus("UNLOCKED");
|
|
|
+ p.setUnlockedBy(userId);
|
|
|
+ p.setUnlockedAt(new Date());
|
|
|
+ p.setCreatedAt(new Date());
|
|
|
+ if (progress == null) {
|
|
|
+ familyGateProgressMapper.insert(p);
|
|
|
+ } else {
|
|
|
+ progress.setStatus("UNLOCKED");
|
|
|
+ progress.setUnlockedBy(userId);
|
|
|
+ progress.setUnlockedAt(new Date());
|
|
|
+ familyGateProgressMapper.updateById(progress);
|
|
|
+ }
|
|
|
+ grantGateRewards(familyId, gate);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 奖励发放 ==========
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 发放关卡通关奖励(积分+能量),幂等
|
|
|
+ */
|
|
|
+ private void grantGateRewards(Long familyId, UnlockGate gate) {
|
|
|
+ if (gate.getRewardPoints() == null && gate.getRewardEnergy() == null) return;
|
|
|
+ // 积分发放:通过积分服务
|
|
|
+ if (gate.getRewardPoints() != null && gate.getRewardPoints() > 0) {
|
|
|
+ try {
|
|
|
+ // 积分发放逻辑(调用现有积分服务)
|
|
|
+ // 目前先通过 energyService 的能量发放,积分部分后续接入
|
|
|
+ log.info("关卡[{}]通关奖励: 积分+{}, 能量+{}, familyId={}",
|
|
|
+ gate.getName(), gate.getRewardPoints(), gate.getRewardEnergy(), familyId);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.warn("关卡奖励发放失败: {}", e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 状态查询 ==========
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查询用户关卡全景
|
|
|
+ */
|
|
|
+ public UnlockStatusDTO getUserUnlockStatus(Long userId, Long familyId) {
|
|
|
+ UnlockStatusDTO dto = new UnlockStatusDTO();
|
|
|
+ dto.setHasFamily(familyId != null);
|
|
|
+
|
|
|
+ if (familyId == null) {
|
|
|
+ // 无家庭,只能显示个人 SELF_CHECK
|
|
|
+ boolean personalPassed = isPersonalSelfCheckPassed(userId);
|
|
|
+ dto.setPersonalSandboxLocked(!personalPassed);
|
|
|
+ dto.setFamilySandboxLocked(true);
|
|
|
+ dto.setChildMicroActionLocked(true);
|
|
|
+ // 返回所有关卡但不含进度
|
|
|
+ List<UnlockGate> allGates = unlockGateMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<UnlockGate>().orderByAsc(UnlockGate::getSortOrder));
|
|
|
+ List<UnlockGateDTO> gateDTOs = allGates.stream().map(this::toDTO).collect(Collectors.toList());
|
|
|
+ dto.setGates(gateDTOs);
|
|
|
+ return dto;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 有家庭
|
|
|
+ boolean personalPassed = isPersonalSelfCheckPassed(userId);
|
|
|
+ dto.setPersonalSandboxLocked(!personalPassed);
|
|
|
+
|
|
|
+ // 全家沙盘
|
|
|
+ List<UnlockGate> inviteGates = unlockGateMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<UnlockGate>()
|
|
|
+ .eq(UnlockGate::getGateType, "INVITE_FAMILY")
|
|
|
+ .eq(UnlockGate::getEnabled, 1));
|
|
|
+ boolean familyUnlocked = false;
|
|
|
+ for (UnlockGate g : inviteGates) {
|
|
|
+ if (isGateUnlocked(familyId, g)) {
|
|
|
+ familyUnlocked = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ dto.setFamilySandboxLocked(!familyUnlocked);
|
|
|
+
|
|
|
+ // 孩子微行动
|
|
|
+ List<UnlockGate> createGates = unlockGateMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<UnlockGate>()
|
|
|
+ .eq(UnlockGate::getGateType, "CREATE_FAMILY_MEMBER")
|
|
|
+ .eq(UnlockGate::getEnabled, 1));
|
|
|
+ boolean childUnlocked = false;
|
|
|
+ for (UnlockGate g : createGates) {
|
|
|
+ if (isGateUnlocked(familyId, g)) {
|
|
|
+ childUnlocked = true;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ dto.setChildMicroActionLocked(!childUnlocked);
|
|
|
+
|
|
|
+ // 当前锁定关卡
|
|
|
+ UnlockGate currentGate = getCurrentLockGate(familyId);
|
|
|
+ if (currentGate != null) {
|
|
|
+ dto.setCurrentLockGate(toDTO(currentGate));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 全量关卡
|
|
|
+ List<UnlockGate> allGates = unlockGateMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<UnlockGate>().orderByAsc(UnlockGate::getSortOrder));
|
|
|
+ List<UnlockGateDTO> gateDTOs = new ArrayList<>();
|
|
|
+ for (UnlockGate gate : allGates) {
|
|
|
+ UnlockGateDTO gdto = toDTO(gate);
|
|
|
+ FamilyGateProgress p = familyGateProgressMapper.selectOne(
|
|
|
+ new LambdaQueryWrapper<FamilyGateProgress>()
|
|
|
+ .eq(FamilyGateProgress::getFamilyId, familyId)
|
|
|
+ .eq(FamilyGateProgress::getGateId, gate.getId()));
|
|
|
+ if (p != null) {
|
|
|
+ gdto.setStatus(p.getStatus());
|
|
|
+ } else {
|
|
|
+ gdto.setStatus("LOCKED");
|
|
|
+ }
|
|
|
+ // 当前关卡标记
|
|
|
+ if (currentGate != null && currentGate.getId().equals(gate.getId())) {
|
|
|
+ gdto.setStatus("CURRENT");
|
|
|
+ }
|
|
|
+ gateDTOs.add(gdto);
|
|
|
+ }
|
|
|
+ dto.setGates(gateDTOs);
|
|
|
+ return dto;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 工具方法 ==========
|
|
|
+
|
|
|
+ public UnlockGate getGateByType(String gateType) {
|
|
|
+ return unlockGateMapper.selectOne(
|
|
|
+ new LambdaQueryWrapper<UnlockGate>()
|
|
|
+ .eq(UnlockGate::getGateType, gateType)
|
|
|
+ .eq(UnlockGate::getEnabled, 1)
|
|
|
+ .eq(UnlockGate::getIsRequired, 1)
|
|
|
+ .eq(UnlockGate::getIsSoft, 0)
|
|
|
+ .last("LIMIT 1"));
|
|
|
+ }
|
|
|
+
|
|
|
+ private UnlockGateDTO toDTO(UnlockGate gate) {
|
|
|
+ UnlockGateDTO dto = new UnlockGateDTO();
|
|
|
+ dto.setId(gate.getId());
|
|
|
+ dto.setGateType(gate.getGateType());
|
|
|
+ dto.setName(gate.getName());
|
|
|
+ dto.setDescription(gate.getDescription());
|
|
|
+ dto.setIcon(gate.getIcon());
|
|
|
+ dto.setSortOrder(gate.getSortOrder());
|
|
|
+ dto.setEnabled(gate.getEnabled());
|
|
|
+ dto.setIsRequired(gate.getIsRequired());
|
|
|
+ dto.setIsSoft(gate.getIsSoft());
|
|
|
+ dto.setUnlockTarget(gate.getUnlockTarget());
|
|
|
+ dto.setRewardPoints(gate.getRewardPoints());
|
|
|
+ dto.setRewardEnergy(gate.getRewardEnergy());
|
|
|
+ return dto;
|
|
|
+ }
|
|
|
+}
|