Explorar o código

feat: 运势算法接入真实八字/五行数据

- FortuneServiceImpl.calculateFamilyElements(): 从 family_members 查询成员,
  聚合 family_member_attributes.wuxingElements JSON,按成员数取平均,
  替代原来的硬编码 {木:20, 火:20, 土:20, 金:20, 水:20}
- FortuneServiceImpl.getChineseDate(): 用与 FamilyMemberAttributeService
  一致的干支算法计算当日干支(2000-01-01 为甲子日参考),替换原硬编码 '甲子'
- FortuneServiceImpl.generatePlaceholderFortune(): dominantElement 优先使用
  家庭真实最强五行,而非哈希随机值
- memberType 查询改为先查 child 再查 parent(与 TianpanService 一致)
- lambda 捕获 validCount 改为 final memberCount 避免编译错误
Sisyphus Agent hai 1 mes
pai
achega
77fabab2b6

+ 126 - 18
cfc-backend/src/main/java/com/etotem/cfc/service/impl/FortuneServiceImpl.java

@@ -1,7 +1,12 @@
 package com.etotem.cfc.service.impl;
 package com.etotem.cfc.service.impl;
 
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.etotem.cfc.entity.FamilyFortune;
 import com.etotem.cfc.entity.FamilyFortune;
+import com.etotem.cfc.entity.FamilyMember;
+import com.etotem.cfc.entity.FamilyMemberAttributes;
 import com.etotem.cfc.mapper.FamilyFortuneMapper;
 import com.etotem.cfc.mapper.FamilyFortuneMapper;
+import com.etotem.cfc.mapper.FamilyMemberAttributesMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.service.FortuneService;
 import com.etotem.cfc.service.FortuneService;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.transaction.annotation.Transactional;
@@ -16,6 +21,16 @@ public class FortuneServiceImpl implements FortuneService {
     
     
     @Resource
     @Resource
     private FamilyFortuneMapper familyFortuneMapper;
     private FamilyFortuneMapper familyFortuneMapper;
+
+    @Resource
+    private FamilyMemberMapper familyMemberMapper;
+
+    @Resource
+    private FamilyMemberAttributesMapper familyMemberAttributesMapper;
+
+    // 天干地支常量(与 FamilyMemberAttributeService 保持一致)
+    private static final String[] HEAVENLY_STEMS = {"甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬", "癸"};
+    private static final String[] EARTHLY_BRANCHES = {"子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥"};
     
     
     @Override
     @Override
     public FamilyFortune getFamilyFortune(Long familyId, LocalDate date) {
     public FamilyFortune getFamilyFortune(Long familyId, LocalDate date) {
@@ -75,36 +90,131 @@ public class FortuneServiceImpl implements FortuneService {
         return result;
         return result;
     }
     }
 
 
-    /**
-     * 获取日期的农历信息(简化实现)
+/**
+     * 获取日期的农历信息(与 FamilyMemberAttributeService 保持一致的干支算法)
      */
      */
     private ChineseDate getChineseDate(LocalDate date) {
     private ChineseDate getChineseDate(LocalDate date) {
-        // TODO: 实际项目中可以集成农历库或数据库表
-        // 这里简化实现:仅基于月份
         ChineseDate cd = new ChineseDate();
         ChineseDate cd = new ChineseDate();
         cd.year = date.getYear();
         cd.year = date.getYear();
         cd.month = date.getMonthValue();
         cd.month = date.getMonthValue();
         cd.day = date.getDayOfMonth();
         cd.day = date.getDayOfMonth();
         cd.ganzhiYear = getGanzhiYear(date.getYear());
         cd.ganzhiYear = getGanzhiYear(date.getYear());
         cd.ganzhiMonth = getGanzhiMonth(date.getMonthValue(), date.getYear());
         cd.ganzhiMonth = getGanzhiMonth(date.getMonthValue(), date.getYear());
-        cd.ganzhiDay = "甲子"; // 简化
+
+        // 计算日柱(参考 FamilyMemberAttributeService.autoCalculate 的算法)
+        // 参考日:2000-01-01 视为甲子日(stemIdx=0, branchIdx=0)
+        long epochDay = date.toEpochDay();
+        long refEpochDay = LocalDate.of(2000, 1, 1).toEpochDay();
+        long diffDays = epochDay - refEpochDay;
+
+        int dayStemIdx = (int) ((diffDays) % 10);
+        if (dayStemIdx < 0) dayStemIdx += 10;
+        int dayBranchIdx = (int) ((diffDays) % 12);
+        if (dayBranchIdx < 0) dayBranchIdx += 12;
+
+        String dayStem = HEAVENLY_STEMS[dayStemIdx];
+        String dayBranch = EARTHLY_BRANCHES[dayBranchIdx];
+        cd.ganzhiDay = dayStem + dayBranch;
+
         return cd;
         return cd;
     }
     }
     
     
-    /**
-     * 计算家庭成员的五行得分
+/**
+     * 计算家庭成员的五行得分(聚合所有成员的 wuxingElements)
      */
      */
     private Map<String, Integer> calculateFamilyElements(Long familyId) {
     private Map<String, Integer> calculateFamilyElements(Long familyId) {
-        // TODO: 实际项目中需要从数据库读取家庭成员的出生信息(八字)
-        // 这里简化实现:均衡分配
         Map<String, Integer> elements = new HashMap<>();
         Map<String, Integer> elements = new HashMap<>();
-        elements.put("木", 20);
-        elements.put("火", 20);
-        elements.put("土", 20);
-        elements.put("金", 20);
-        elements.put("水", 20);
+        elements.put("木", 0);
+        elements.put("火", 0);
+        elements.put("土", 0);
+        elements.put("金", 0);
+        elements.put("水", 0);
+
+        // 查询家庭成员
+        List<FamilyMember> members = familyMemberMapper.selectList(
+                new LambdaQueryWrapper<FamilyMember>()
+                        .eq(FamilyMember::getFamilyId, familyId)
+                        .eq(FamilyMember::getStatus, "active"));
+        if (members == null || members.isEmpty()) {
+            return elements;
+        }
+
+        // key 映射:英文 -> 中文
+        Map<String, String> elementMap = new HashMap<>();
+        elementMap.put("wood", "木");
+        elementMap.put("fire", "火");
+        elementMap.put("earth", "土");
+        elementMap.put("metal", "金");
+        elementMap.put("water", "水");
+
+        int validCount = 0;
+        for (FamilyMember member : members) {
+            // memberType: 先查 child(不可登录成员),再查 parent(可登录成员)
+            FamilyMemberAttributes attrs = familyMemberAttributesMapper.selectOne(
+                    new LambdaQueryWrapper<FamilyMemberAttributes>()
+                            .eq(FamilyMemberAttributes::getMemberId, member.getId())
+                            .eq(FamilyMemberAttributes::getMemberType, "child"));
+            if (attrs == null) {
+                attrs = familyMemberAttributesMapper.selectOne(
+                        new LambdaQueryWrapper<FamilyMemberAttributes>()
+                                .eq(FamilyMemberAttributes::getMemberId, member.getId())
+                                .eq(FamilyMemberAttributes::getMemberType, "parent"));
+            }
+            if (attrs != null && attrs.getWuxingElements() != null) {
+                Map<String, Object> wuxingMap = fromJson(attrs.getWuxingElements());
+                for (Map.Entry<String, Object> entry : wuxingMap.entrySet()) {
+                    String cnKey = elementMap.get(entry.getKey());
+                    if (cnKey != null) {
+                        Long val = (entry.getValue() instanceof Number)
+                                ? ((Number) entry.getValue()).longValue()
+                                : 0L;
+                        elements.put(cnKey, elements.get(cnKey) + val.intValue());
+                    }
+                }
+                validCount++;
+            }
+        }
+
+        // 取平均值(保留整数)
+        // 取平均值(保留整数)
+        final int memberCount = validCount;
+        if (memberCount > 0) {
+            elements.replaceAll((k, v) -> v / memberCount);
+        }
         return elements;
         return elements;
     }
     }
+
+    /**
+     * Simple JSON parser for Map<String, Object> (reuse from FamilyMemberAttributeService)
+     */
+    @SuppressWarnings("unchecked")
+    private Map<String, Object> fromJson(String json) {
+        Map<String, Object> map = new LinkedHashMap<>();
+        if (json == null || json.isEmpty() || !json.startsWith("{")) {
+            return map;
+        }
+        String content = json.substring(1, json.length() - 1).trim();
+        if (content.isEmpty()) return map;
+        String[] pairs = content.split(",");
+        for (String pair : pairs) {
+            String[] kv = pair.split(":", 2);
+            if (kv.length == 2) {
+                String key = kv[0].trim().replace("\"", "");
+                String value = kv[1].trim().replace("\"", "");
+                try {
+                    if (value.contains(".")) {
+                        String intStr = value.replace(".", "").replaceAll("0+$", "");
+                        map.put(key, intStr.isEmpty() ? 0L : Long.parseLong(intStr));
+                    } else {
+                        map.put(key, Long.parseLong(value));
+                    }
+                } catch (NumberFormatException e) {
+                    map.put(key, value);
+                }
+            }
+        }
+        return map;
+    }
     
     
     /**
     /**
      * 根据农历信息和家庭五行确定吉位
      * 根据农历信息和家庭五行确定吉位
@@ -292,11 +402,9 @@ public class FortuneServiceImpl implements FortuneService {
         fortune.setFortuneDate(date);
         fortune.setFortuneDate(date);
         
         
         // 占位算法:五行三合
         // 占位算法:五行三合
-        String[] elements = {"木", "火", "土", "金", "水"};
-        // 注意:原写法因运算符优先级问题(+ 高于 >>> 且 ^ 低于两者)会导致移位越界,可能造成 ArrayIndexOutOfBoundsException
+        String dominant = getStrongestElement(calculateFamilyElements(familyId));
+        fortune.setDominantElement(dominant);
         int hash = (int)(familyId.longValue() ^ (familyId.longValue() >>> 32) ^ date.toEpochDay());
         int hash = (int)(familyId.longValue() ^ (familyId.longValue() >>> 32) ^ date.toEpochDay());
-        int idx = Math.floorMod(Math.abs(hash), elements.length);
-        fortune.setDominantElement(elements[idx]);
         
         
         // 吉位
         // 吉位
         fortune.setLuckyDirection(calculateLuckyDirection(familyId, date));
         fortune.setLuckyDirection(calculateLuckyDirection(familyId, date));