Sfoglia il codice sorgente

feat: 新增五维基础分计算服务FamilyMemberDimensionBaseScoreService

iwt 1 settimana fa
parent
commit
9964d8247d

+ 140 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/FamilyMemberDimensionBaseScoreService.java

@@ -0,0 +1,140 @@
+package com.etotem.cfc.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.etotem.cfc.entity.FamilyMemberDimensionBaseScore;
+import com.etotem.cfc.mapper.FamilyMemberDimensionBaseScoreMapper;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.*;
+
+@Service
+public class FamilyMemberDimensionBaseScoreService {
+
+    @Resource
+    private FamilyMemberDimensionBaseScoreMapper dimensionBaseScoreMapper;
+
+    private static final String[] DIMENSIONS = {"body", "mind", "wisdom", "action", "wealth"};
+
+    private static final Map<String, String> ZODIAC_TO_DIM = new LinkedHashMap<>();
+    private static final Map<Integer, String> LIFE_NUMBER_TO_DIM = new LinkedHashMap<>();
+
+    static {
+        ZODIAC_TO_DIM.put("白羊座", "mind");
+        ZODIAC_TO_DIM.put("金牛座", "body");
+        ZODIAC_TO_DIM.put("双子座", "action");
+        ZODIAC_TO_DIM.put("巨蟹座", "wealth");
+        ZODIAC_TO_DIM.put("狮子座", "mind");
+        ZODIAC_TO_DIM.put("处女座", "body");
+        ZODIAC_TO_DIM.put("天秤座", "action");
+        ZODIAC_TO_DIM.put("天蝎座", "wealth");
+        ZODIAC_TO_DIM.put("射手座", "mind");
+        ZODIAC_TO_DIM.put("摩羯座", "body");
+        ZODIAC_TO_DIM.put("水瓶座", "action");
+        ZODIAC_TO_DIM.put("双鱼座", "wealth");
+
+        LIFE_NUMBER_TO_DIM.put(1, "action");
+        LIFE_NUMBER_TO_DIM.put(2, "mind");
+        LIFE_NUMBER_TO_DIM.put(3, "wisdom");
+        LIFE_NUMBER_TO_DIM.put(4, "body");
+        LIFE_NUMBER_TO_DIM.put(5, "action");
+        LIFE_NUMBER_TO_DIM.put(6, "mind");
+        LIFE_NUMBER_TO_DIM.put(7, "wisdom");
+        LIFE_NUMBER_TO_DIM.put(8, "wealth");
+        LIFE_NUMBER_TO_DIM.put(9, "mind");
+    }
+
+    public Map<String, Integer> calcBaseScores(Map<String, Integer> wuxingElements,
+                                                String westernSign,
+                                                Integer lifeNumber) {
+        Map<String, Integer> scores = new HashMap<>();
+        for (String dim : DIMENSIONS) {
+            scores.put(dim, 50);
+        }
+
+        if (wuxingElements != null) {
+            scores.put("action", Math.round((float) wuxingElements.getOrDefault("wood", 50) * 0.6f));
+            scores.put("mind",   Math.round((float) wuxingElements.getOrDefault("fire", 50) * 0.6f));
+            scores.put("body",   Math.round((float) wuxingElements.getOrDefault("earth", 50) * 0.6f));
+            scores.put("wisdom", Math.round((float) wuxingElements.getOrDefault("metal", 50) * 0.6f));
+            scores.put("wealth", Math.round((float) wuxingElements.getOrDefault("water", 50) * 0.6f));
+        }
+
+        if (westernSign != null && ZODIAC_TO_DIM.containsKey(westernSign)) {
+            String dim = ZODIAC_TO_DIM.get(westernSign);
+            scores.put(dim, scores.get(dim) + 20);
+        }
+
+        if (lifeNumber != null) {
+            int reduced = reduceMaster(lifeNumber);
+            if (LIFE_NUMBER_TO_DIM.containsKey(reduced)) {
+                String dim = LIFE_NUMBER_TO_DIM.get(reduced);
+                scores.put(dim, scores.get(dim) + 20);
+            }
+        }
+
+        return scores;
+    }
+
+    public void calcAndSave(Long memberId, String memberType,
+                            Map<String, Integer> wuxingElements,
+                            String westernSign, Integer lifeNumber,
+                            String operator) {
+        Map<String, Integer> scores = calcBaseScores(wuxingElements, westernSign, lifeNumber);
+        saveScores(memberId, memberType, scores, operator);
+    }
+
+    private void saveScores(Long memberId, String memberType, Map<String, Integer> scores, String operator) {
+        Date now = new Date();
+        for (Map.Entry<String, Integer> entry : scores.entrySet()) {
+            LambdaQueryWrapper<FamilyMemberDimensionBaseScore> qw = new LambdaQueryWrapper<>();
+            qw.eq(FamilyMemberDimensionBaseScore::getMemberId, memberId)
+              .eq(FamilyMemberDimensionBaseScore::getDimensionCode, entry.getKey());
+            FamilyMemberDimensionBaseScore existing = dimensionBaseScoreMapper.selectOne(qw);
+            if (existing != null) {
+                existing.setBaseScore(entry.getValue());
+                existing.setLastCalculatedAt(now);
+                existing.setCalculatedBy(operator);
+                dimensionBaseScoreMapper.updateById(existing);
+            } else {
+                FamilyMemberDimensionBaseScore entity = new FamilyMemberDimensionBaseScore();
+                entity.setMemberId(memberId);
+                entity.setMemberType(memberType);
+                entity.setDimensionCode(entry.getKey());
+                entity.setBaseScore(entry.getValue());
+                entity.setLastCalculatedAt(now);
+                entity.setCalculatedBy(operator);
+                dimensionBaseScoreMapper.insert(entity);
+            }
+        }
+    }
+
+    public Map<String, Integer> getScores(Long memberId) {
+        Map<String, Integer> scores = new LinkedHashMap<>();
+        for (String dim : DIMENSIONS) {
+            scores.put(dim, 50);
+        }
+        LambdaQueryWrapper<FamilyMemberDimensionBaseScore> qw = new LambdaQueryWrapper<>();
+        qw.eq(FamilyMemberDimensionBaseScore::getMemberId, memberId);
+        List<FamilyMemberDimensionBaseScore> list = dimensionBaseScoreMapper.selectList(qw);
+        for (FamilyMemberDimensionBaseScore item : list) {
+            scores.put(item.getDimensionCode(), item.getBaseScore());
+        }
+        return scores;
+    }
+
+    private int reduceMaster(int n) {
+        if (n == 11) return 2;
+        if (n == 22) return 4;
+        if (n == 33) return 6;
+        while (n >= 10) {
+            int sum = 0;
+            while (n > 0) {
+                sum += n % 10;
+                n /= 10;
+            }
+            n = sum;
+        }
+        return n;
+    }
+}

+ 130 - 0
cfc-backend/src/test/java/com/etotem/cfc/unit/FamilyMemberDimensionBaseScoreServiceTest.java

@@ -0,0 +1,130 @@
+package com.etotem.cfc.unit;
+
+import com.etotem.cfc.service.FamilyMemberDimensionBaseScoreService;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+@ExtendWith(MockitoExtension.class)
+class FamilyMemberDimensionBaseScoreServiceTest {
+
+    @InjectMocks
+    private FamilyMemberDimensionBaseScoreService service;
+
+    private Map<String, Integer> wuxing(int wood, int fire, int earth, int metal, int water) {
+        Map<String, Integer> m = new HashMap<>();
+        m.put("wood", wood);
+        m.put("fire", fire);
+        m.put("earth", earth);
+        m.put("metal", metal);
+        m.put("water", water);
+        return m;
+    }
+
+    @Test
+    void calcBaseScores_fullData_returnsCorrectScores() {
+        // 八字五行 {wood:40, fire:60, earth:50, metal:30, water:20}
+        // 星座狮子座(火象→心)
+        // 灵数7(→智)
+        Map<String, Integer> result = service.calcBaseScores(wuxing(40, 60, 50, 30, 20), "狮子座", 7);
+
+        assertEquals(30, result.get("body"));      // 50×0.6=30
+        assertEquals(56, result.get("mind"));      // 60×0.6+20=56
+        assertEquals(38, result.get("wisdom"));    // 30×0.6+20=38
+        assertEquals(24, result.get("action"));    // 40×0.6=24
+        assertEquals(12, result.get("wealth"));    // 20×0.6=12
+    }
+
+    @Test
+    void calcBaseScores_nullWuxing_returnsNeutral50() {
+        Map<String, Integer> result = service.calcBaseScores(null, null, null);
+
+        assertEquals(50, result.get("body"));
+        assertEquals(50, result.get("mind"));
+        assertEquals(50, result.get("wisdom"));
+        assertEquals(50, result.get("action"));
+        assertEquals(50, result.get("wealth"));
+    }
+
+    @Test
+    void calcBaseScores_masterNumber_11MapsTo2() {
+        // 灵数11→化简为2→映射到心(mind),星座null
+        Map<String, Integer> result = service.calcBaseScores(wuxing(50, 50, 50, 50, 50), null, 11);
+
+        // fire=50, 50×0.6=30, 灵数11→2→心(mind) → 30+20=50
+        assertEquals(50, result.get("mind"));
+    }
+
+    @Test
+    void calcBaseScores_masterNumber_22MapsTo4() {
+        Map<String, Integer> result = service.calcBaseScores(wuxing(50, 50, 50, 50, 50), null, 22);
+        // 22→4→身(body=earth)
+        // earth=50, 50×0.6=30, 30+20=50
+        assertEquals(50, result.get("body"));
+    }
+
+    @Test
+    void calcBaseScores_allZodiacSigns_coverage() {
+        // 使用非均匀五行值,使命中维度 > 中性线
+        Map<String, Integer> w = wuxing(60, 80, 60, 60, 60);
+        // 各维度基础分: action=36, mind=48, body=36, wisdom=36, wealth=36
+
+        // 火象 → 心(mind): 48+20=68 > 50 ✓
+        Map<String, Integer> fireResult = service.calcBaseScores(w, "白羊座", null);
+        assertTrue(fireResult.get("mind") > 50);
+
+        // 土象 → 身(body): 36+20=56 > 50 ✓
+        Map<String, Integer> earthResult = service.calcBaseScores(w, "金牛座", null);
+        assertTrue(earthResult.get("body") > 50);
+
+        // 风象 → 行(action): 36+20=56 > 50 ✓
+        Map<String, Integer> airResult = service.calcBaseScores(w, "双子座", null);
+        assertTrue(airResult.get("action") > 50);
+
+        // 水象 → 富(wealth): 36+20=56 > 50 ✓
+        Map<String, Integer> waterResult = service.calcBaseScores(w, "巨蟹座", null);
+        assertTrue(waterResult.get("wealth") > 50);
+    }
+
+    @Test
+    void calcBaseScores_all12ZodiacSigns() {
+        Map<String, Integer> w = wuxing(60, 80, 60, 60, 60);
+
+        // 火象 3个
+        service.calcBaseScores(w, "狮子座", null);
+        service.calcBaseScores(w, "射手座", null);
+
+        // 土象 3个
+        service.calcBaseScores(w, "处女座", null);
+        service.calcBaseScores(w, "摩羯座", null);
+
+        // 风象 3个
+        service.calcBaseScores(w, "天秤座", null);
+        service.calcBaseScores(w, "水瓶座", null);
+
+        // 水象 3个
+        service.calcBaseScores(w, "天蝎座", null);
+        service.calcBaseScores(w, "双鱼座", null);
+
+        // 所有12个星座不应抛异常;白羊座(火→心): 48+20=68
+        Map<String, Integer> result = service.calcBaseScores(w, "白羊座", null);
+        assertEquals(68, result.get("mind"));
+    }
+
+    @Test
+    void calcBaseScores_unknownZodiacNoBonus() {
+        // wuxing全null时,各维度默认50;未知星座不应加分
+        Map<String, Integer> result = service.calcBaseScores(null, "未知星座", null);
+        assertEquals(50, result.get("body"));
+        assertEquals(50, result.get("mind"));
+        assertEquals(50, result.get("wisdom"));
+        assertEquals(50, result.get("action"));
+        assertEquals(50, result.get("wealth"));
+    }
+}