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

feat(backend): 家庭挑战结算发放能量点奖励并标记成员完成

Xiaogang Liao 1 месяц назад
Родитель
Сommit
93aa93bf7c

+ 32 - 4
cfc-backend/src/main/java/com/etotem/cfc/service/FamilyChallengeService.java

@@ -1,6 +1,7 @@
 package com.etotem.cfc.service;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
 import com.etotem.cfc.common.Result;
 import com.etotem.cfc.entity.FamilyChallenge;
 import com.etotem.cfc.entity.FamilyChallengeProgress;
@@ -32,6 +33,9 @@ public class FamilyChallengeService {
     @Resource
     private HealthScoreService healthScoreService;
 
+    @Resource
+    private PointsService pointsService;
+
     public List<FamilyChallenge> getActiveChallenges(Long familyId) {
         if (familyId == null) return new ArrayList<>();
 
@@ -210,10 +214,34 @@ public class FamilyChallengeService {
         }
 
         if (isCompleted || challenge.getEndDate() == null || new Date().after(challenge.getEndDate())) {
-            challenge.setStatus("completed");
-            familyChallengeMapper.updateById(challenge);
-            log.info("挑战已结算: id={}, type={}, targetMode={}, targetValue={}, completed={}",
-                    challenge.getId(), challenge.getChallengeType(), targetMode, targetValue, isCompleted);
+            // 幂等结算:仅当挑战仍是 active 时原子跃迁为 completed(条件更新 WHERE id=? AND status='active')。
+            // 并发打卡/重复触发结算时,只有一个请求能更新成功(影响行数=1),其余请求影响行数=0 直接跳过,
+            // 从根源杜绝重复发奖。
+            UpdateWrapper<FamilyChallenge> uw = new UpdateWrapper<FamilyChallenge>()
+                    .eq("id", challenge.getId())
+                    .eq("status", "active")
+                    .set("status", "completed");
+            int updated = familyChallengeMapper.update(null, uw);
+            if (updated > 0) {
+                if (isCompleted) {
+                    // 发放奖励:能量点 + 标记成员完成(仅成功结算的调用执行,不会重复)
+                    int points = challenge.getRewardPoints() != null ? challenge.getRewardPoints() : 0;
+                    for (FamilyChallengeProgress p : progresses) {
+                        if (!Boolean.TRUE.equals(p.getCompleted())) {
+                            p.setCompleted(true);
+                            p.setUpdatedAt(new Date());
+                            familyChallengeProgressMapper.updateById(p);
+                        }
+                        if (points > 0) {
+                            // 对接积分服务发放奖励(PointsService.awardSystemPoints,按 memberId 发放)
+                            pointsService.awardSystemPoints(p.getChildId(), points,
+                                    "家庭挑战完成奖励: " + challenge.getTitle());
+                        }
+                    }
+                }
+                log.info("挑战已结算: id={}, type={}, targetMode={}, targetValue={}, completed={}",
+                        challenge.getId(), challenge.getChallengeType(), targetMode, targetValue, isCompleted);
+            }
         }
     }
 

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

@@ -16,6 +16,9 @@ import java.util.Date;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -78,4 +81,33 @@ public class FamilyChallengeServiceTest {
         verify(familyChallengeProgressMapper, never()).insert(any());
         verify(familyChallengeProgressMapper, never()).updateById(any());
     }
+
+    @Test
+    void checkAndSettle_shouldGrantPointsWhenCompleted() {
+        FamilyChallenge challenge = new FamilyChallenge();
+        challenge.setId(1L);
+        challenge.setFamilyId(10L);
+        challenge.setStatus("active");
+        challenge.setTargetMode("aggregate");
+        challenge.setTargetValue(10);
+        challenge.setRewardPoints(50);
+        challenge.setEndDate(new Date(System.currentTimeMillis() - 86400000L));
+
+        FamilyChallengeProgress p = new FamilyChallengeProgress();
+        p.setChallengeId(1L);
+        p.setChildId(100L);
+        p.setProgressValue(12);
+        p.setCompleted(false);
+
+        when(familyChallengeMapper.selectById(1L)).thenReturn(challenge);
+        when(familyChallengeProgressMapper.selectList(any())).thenReturn(java.util.Arrays.asList(p));
+        when(familyMemberMapper.selectList(any())).thenReturn(java.util.Collections.emptyList());
+        when(familyChallengeMapper.update(any(), any())).thenReturn(1);
+
+        service.settleChallenge(1L);
+
+        verify(familyChallengeMapper).update(any(), any());
+        verify(familyChallengeProgressMapper).updateById(argThat(p2 -> Boolean.TRUE.equals(p2.getCompleted())));
+        verify(pointsService).awardSystemPoints(eq(100L), eq(50), anyString());
+    }
 }