Эх сурвалжийг харах

fix(backend): 家庭挑战打卡每日限次 + memberId 归属校验

Xiaogang Liao 1 сар өмнө
parent
commit
1bd0f884f9

+ 38 - 2
cfc-backend/src/main/java/com/etotem/cfc/service/FamilyChallengeService.java

@@ -96,11 +96,47 @@ public class FamilyChallengeService {
             return Result.error("参数错误");
         }
 
+        // 1. 挑战必须存在
+        FamilyChallenge challenge = familyChallengeMapper.selectById(challengeId);
+        if (challenge == null) {
+            return Result.error("挑战不存在");
+        }
+
+        // 2. 挑战必须 active 且未到期(已结算/已取消/已过期的挑战不允许打卡)
+        if (!"active".equals(challenge.getStatus())) {
+            return Result.error("挑战已结束");
+        }
+        if (challenge.getEndDate() != null && !new Date().before(challenge.getEndDate())) {
+            return Result.error("挑战已结束");
+        }
+
+        // 3. memberId 归属校验:childId 对应的 family_member 必须属于挑战所属家庭(防刷他人进度)
+        LambdaQueryWrapper<FamilyMember> memberCheck = new LambdaQueryWrapper<FamilyMember>()
+                .eq(FamilyMember::getId, childId)
+                .eq(FamilyMember::getFamilyId, challenge.getFamilyId());
+        if (familyMemberMapper.selectCount(memberCheck) == 0) {
+            return Result.error("无权为该成员打卡");
+        }
+
+        // 4. 查该成员在本挑战的进度
         LambdaQueryWrapper<FamilyChallengeProgress> wrapper = new LambdaQueryWrapper<FamilyChallengeProgress>()
                 .eq(FamilyChallengeProgress::getChallengeId, challengeId)
                 .eq(FamilyChallengeProgress::getChildId, childId);
         FamilyChallengeProgress progress = familyChallengeProgressMapper.selectOne(wrapper);
 
+        // 5. 每日限次:同一 challengeId+childId 当天只能打卡一次(按 updatedAt 判断)
+        if (progress != null && progress.getUpdatedAt() != null) {
+            java.util.Calendar today = java.util.Calendar.getInstance();
+            today.setTime(new Date());
+            java.util.Calendar upd = java.util.Calendar.getInstance();
+            upd.setTime(progress.getUpdatedAt());
+            if (upd.get(java.util.Calendar.YEAR) == today.get(java.util.Calendar.YEAR)
+                    && upd.get(java.util.Calendar.DAY_OF_YEAR) == today.get(java.util.Calendar.DAY_OF_YEAR)) {
+                return Result.error("今日已打卡,明天再来吧");
+            }
+        }
+
+        // 6. 累加进度(原有逻辑)
         if (progress == null) {
             progress = new FamilyChallengeProgress();
             progress.setChallengeId(challengeId);
@@ -116,8 +152,8 @@ public class FamilyChallengeService {
             familyChallengeProgressMapper.updateById(progress);
         }
 
-        FamilyChallenge challenge = familyChallengeMapper.selectById(challengeId);
-        if (challenge != null && "active".equals(challenge.getStatus())) {
+        // 7. 结算判定(原有逻辑,复用已查出的 challenge,不再重复 selectById)
+        if ("active".equals(challenge.getStatus())) {
             checkAndSettle(challenge);
         }
 

+ 81 - 0
cfc-backend/src/test/java/com/etotem/cfc/service/FamilyChallengeServiceTest.java

@@ -0,0 +1,81 @@
+package com.etotem.cfc.service;
+
+import com.etotem.cfc.common.Result;
+import com.etotem.cfc.entity.FamilyChallenge;
+import com.etotem.cfc.entity.FamilyChallengeProgress;
+import com.etotem.cfc.mapper.FamilyChallengeMapper;
+import com.etotem.cfc.mapper.FamilyChallengeProgressMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Date;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class FamilyChallengeServiceTest {
+
+    @Mock private FamilyChallengeMapper familyChallengeMapper;
+    @Mock private FamilyChallengeProgressMapper familyChallengeProgressMapper;
+    @Mock private FamilyMemberMapper familyMemberMapper;
+    @Mock private HealthScoreService healthScoreService;
+    @Mock private PointsService pointsService;
+    @InjectMocks private FamilyChallengeService service;
+
+    @BeforeEach
+    void setUp() {
+        MockitoAnnotations.openMocks(this);
+    }
+
+    @Test
+    void updateProgress_shouldRejectDuplicateCheckinSameDay() {
+        FamilyChallenge challenge = new FamilyChallenge();
+        challenge.setId(1L);
+        challenge.setFamilyId(10L);
+        challenge.setStatus("active");
+        challenge.setTargetMode("aggregate");
+        challenge.setTargetValue(300);
+        challenge.setEndDate(new Date(System.currentTimeMillis() + 86400000L * 7));
+
+        FamilyChallengeProgress existing = new FamilyChallengeProgress();
+        existing.setChallengeId(1L);
+        existing.setChildId(100L);
+        existing.setProgressValue(5);
+        existing.setUpdatedAt(new Date());
+
+        when(familyChallengeMapper.selectById(1L)).thenReturn(challenge);
+        when(familyChallengeProgressMapper.selectOne(any())).thenReturn(existing);
+        when(familyMemberMapper.selectCount(any())).thenReturn(1L);
+
+        Result<Void> result = service.updateProgress(1L, 100L, 1);
+
+        assertEquals(Integer.valueOf(500), result.getCode());
+        verify(familyChallengeProgressMapper, never()).updateById(any());
+    }
+
+    @Test
+    void updateProgress_shouldRejectWhenChallengeNotActive() {
+        FamilyChallenge challenge = new FamilyChallenge();
+        challenge.setId(1L);
+        challenge.setFamilyId(10L);
+        challenge.setStatus("completed");
+        challenge.setTargetMode("aggregate");
+        challenge.setTargetValue(300);
+        challenge.setEndDate(new Date(System.currentTimeMillis() + 86400000L * 7));
+
+        when(familyChallengeMapper.selectById(1L)).thenReturn(challenge);
+
+        Result<Void> result = service.updateProgress(1L, 100L, 1);
+
+        assertEquals(Integer.valueOf(500), result.getCode());
+        verify(familyChallengeProgressMapper, never()).insert(any());
+        verify(familyChallengeProgressMapper, never()).updateById(any());
+    }
+}