Bladeren bron

重构推荐服务实现逻辑

jintao.geng 2 weken geleden
bovenliggende
commit
ed5d7b113b

+ 3 - 0
mango-common/src/main/java/com/mangoo/rating/recommend/request/recommend/RecommendCardDTO.java

@@ -42,6 +42,9 @@ public class RecommendCardDTO {
     @ApiModelProperty(value = "套餐价格")
     private BigDecimal packagePrice;
 
+    @ApiModelProperty(value = "前端选择的服务时效ID")
+    private Long evaluateEfficiencyId;
+
     /**
      * 运动项目
      */

+ 1 - 0
mango-domain/src/main/java/com/mangoo/rating/recommend/client/OrderApiClient.java

@@ -35,6 +35,7 @@ public class OrderApiClient {
     public List<ProductServiceLevelDTO> serviceList(String levelName) {
         try {
             ProductServiceLevelRequest request = new ProductServiceLevelRequest();
+            request.setLevelName(levelName);
             log.info("获取产品服务时效列表参数:{}", request);
             JSONObject result = orderServiceClient.serviceList(request);
             if (result != null && result.getInteger("code") == 0) {

+ 3 - 0
mango-domain/src/main/java/com/mangoo/rating/recommend/client/feign/dto/ProductServiceLevelRequest.java

@@ -21,4 +21,7 @@ public class ProductServiceLevelRequest implements Serializable {
     @ApiModelProperty("规格等级名称")
     private String levelName;
 
+    @ApiModelProperty("套餐ID")
+    private Long packageId;
+
 }

+ 4 - 7
mango-domain/src/main/java/com/mangoo/rating/recommend/service/RecommendService.java

@@ -4,18 +4,15 @@ import com.mangoo.rating.recommend.AjaxResult;
 import com.mangoo.rating.recommend.request.recommend.RecommendRequest;
 
 /**
- * 评级时效推荐服务(编排:逐卡降级匹配 → 精确分组 → 报价 → 组装响应)
- *
- * @author gengjintao
- * @date 2026/06/24
+ * Rating efficiency grouping service.
  */
 public interface RecommendService {
 
     /**
-     * 推荐评级时效并按时效分单
+     * Group cards by the efficiency id selected by the frontend.
      *
-     * @param request 批量卡请求
-     * @return AjaxResult.data = RecommendResultVO
+     * @param request batch card request
+     * @return ajax result
      */
     AjaxResult recommendEfficiency(RecommendRequest request);
 }

+ 12 - 134
mango-domain/src/main/java/com/mangoo/rating/recommend/service/impl/RecommendServiceImpl.java

@@ -2,16 +2,9 @@ package com.mangoo.rating.recommend.service.impl;
 
 import com.mangoo.rating.recommend.AjaxResult;
 import com.mangoo.rating.recommend.client.PreOrderApiClient;
-import com.mangoo.rating.recommend.client.feign.PreOrderFeignClient;
 import com.mangoo.rating.recommend.config.RecommendProperties;
 import com.mangoo.rating.recommend.dto.recommend.EfficiencyDictItem;
-import com.mangoo.rating.recommend.dto.recommend.SeriesCardSetKey;
-import com.mangoo.rating.recommend.dto.recommend.SeriesRoleKey;
 import com.mangoo.rating.recommend.enums.EvaluateEfficiencyEnum;
-import com.mangoo.rating.recommend.enums.MatchLevelEnum;
-import com.mangoo.rating.recommend.manager.ActiveBatchManager;
-import com.mangoo.rating.recommend.manager.RatingRecommendRuleManager;
-import com.mangoo.rating.recommend.po.RatingRecommendRulePO;
 import com.mangoo.rating.recommend.request.recommend.PreOrderCardDTO;
 import com.mangoo.rating.recommend.request.recommend.PreOrderGroupDTO;
 import com.mangoo.rating.recommend.request.recommend.PreOrderSaveDTO;
@@ -23,7 +16,6 @@ import com.mangoo.rating.recommend.service.RecommendService;
 import com.mangoo.rating.recommend.service.recommend.EfficiencyPriceProvider;
 import com.mangoo.rating.recommend.service.recommend.LoginUserProvider;
 import com.mangoo.rating.recommend.service.recommend.OrderGrouper;
-import com.mangoo.rating.recommend.service.recommend.RuleMatcher;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 
@@ -32,7 +24,6 @@ import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.stream.Collectors;
 
 /**
@@ -53,22 +44,12 @@ import java.util.stream.Collectors;
 @Service
 public class RecommendServiceImpl implements RecommendService {
 
-    @Resource
-    private RatingRecommendRuleManager ratingRecommendRuleManager;
-
     @Resource
     private EfficiencyPriceProvider efficiencyPriceProvider;
 
     @Resource
     private RecommendProperties recommendProperties;
 
-    @Resource
-    private ActiveBatchManager activeBatchManager;
-
-    @Resource
-    private PreOrderFeignClient preOrderFeignClient;
-
-
     @Resource
     private PreOrderApiClient preOrderApiClient;
 
@@ -84,70 +65,36 @@ public class RecommendServiceImpl implements RecommendService {
             return AjaxResult.error("单次卡数量超过上限:" + recommendProperties.getBatchMax());
         }
 
-        // 0) 当前生效批次号(为空说明尚未同步过规则数据 → 全部走兜底)
-        String activeBatchNo = activeBatchManager.selectActiveBatchNo();
-
-        // 1) 批量捞规则:按 cardType 分派收窄键 —— 宝可梦(cardType=1)按 (series,role)、其它(球星卡/未知)按 (series,cardSet)
-        List<SeriesCardSetKey> cardSetKeys = request.getCards().stream()
-                .filter(c -> !isPokemon(c.getCardType()))
-                .filter(c -> c.getSeries() != null && c.getCardSet() != null)
-                .map(c -> new SeriesCardSetKey(c.getSeries(), c.getCardSet()))
-                .distinct()
-                .collect(Collectors.toList());
-        List<SeriesRoleKey> roleKeys = request.getCards().stream()
-                .filter(c -> isPokemon(c.getCardType()))
-                .filter(c -> c.getSeries() != null && c.getRole() != null)
-                .map(c -> new SeriesRoleKey(c.getSeries(), c.getRole()))
-                .distinct()
+        List<RecommendCardVO> cardVOs = request.getCards().stream()
+                .map(this::toCardVO)
                 .collect(Collectors.toList());
-        List<RatingRecommendRulePO> pool = new ArrayList<>();
-        if (!cardSetKeys.isEmpty()) {
-            pool.addAll(ratingRecommendRuleManager.selectByBatchAndSeriesSets(activeBatchNo, cardSetKeys));
-        }
-        if (!roleKeys.isEmpty()) {
-            pool.addAll(ratingRecommendRuleManager.selectByBatchAndSeriesRoles(activeBatchNo, roleKeys));
-        }
-
-        // 2) 逐卡内存匹配(RuleMatcher 做前提过滤 + L0~L3 降级 + 取舍),未命中按 cardType 兜底
-        List<RecommendCardVO> cardVOs = new ArrayList<>(request.getCards().size());
-        for (RecommendCardDTO dto : request.getCards()) {
-            cardVOs.add(recommendOneCard(dto, pool));
-        }
 
-        // 3) 按时效精确分组 → 加载时效字典(名称+价,一次拉取共享)→ 回填组名 + 报价
         List<OrderSuggestionVO> orders = OrderGrouper.group(cardVOs);
         Map<Long, EfficiencyDictItem> dict = efficiencyPriceProvider.loadEfficiencyDict();
         fillGroupNames(orders, dict);
         BigDecimal grandTotal = applyPricing(orders, dict);
 
-        // 4) 取当前用户(走 Provider 而非直接调 UserUtils 静态方法,便于单测 mock)
         Long userId = loginUserProvider.currentUserId();
         if (userId == null) {
             return AjaxResult.error("用户未登录,无法生成预订单");
         }
 
-        // 5) 组装落库包并 Feign 远程落库(推荐服务不写库,交由 ratingApp)
-        PreOrderSaveDTO saveDTO = buildSaveDTO(request, orders, grandTotal, activeBatchNo, userId);
+        PreOrderSaveDTO saveDTO = buildSaveDTO(request, orders, grandTotal, userId);
         String preOrderNo = preOrderApiClient.savePreOrder(loginUserProvider.currentUserHeader(), saveDTO);
         if (preOrderNo == null || preOrderNo.isEmpty()) {
             return AjaxResult.error("预订单生成失败,请稍后重试");
         }
-        // 显式走双参 success(msg,data):单参 success(String) 会被重载解析成 success(msg),导致 preOrderNo 落进 msg 而 data=null。
         return AjaxResult.success("操作成功", preOrderNo);
     }
 
-    /**
-     * 组装预订单落库整包(主表汇总 + 分组 + 卡片明细)。
-     */
     private PreOrderSaveDTO buildSaveDTO(RecommendRequest request, List<OrderSuggestionVO> orders,
-                                         BigDecimal grandTotal, String activeBatchNo, Long userId) {
+                                         BigDecimal grandTotal, Long userId) {
         PreOrderSaveDTO dto = new PreOrderSaveDTO();
         dto.setUserId(userId);
         dto.setTotalCards(request.getCards().size());
         dto.setOrderCount(orders == null ? 0 : orders.size());
         dto.setGrandTotal(grandTotal);
-        dto.setActiveBatchNo(activeBatchNo);
-        List<PreOrderGroupDTO> groups = new ArrayList<>();
+        dto.setGroups(new ArrayList<>());
         if (orders != null) {
             for (OrderSuggestionVO o : orders) {
                 PreOrderGroupDTO g = new PreOrderGroupDTO();
@@ -160,16 +107,12 @@ public class RecommendServiceImpl implements RecommendService {
                 g.setTotalAmount(o.getTotalAmount());
                 g.setCards(o.getCards() == null ? new ArrayList<>()
                         : o.getCards().stream().map(this::toCardDTO).collect(Collectors.toList()));
-                groups.add(g);
+                dto.getGroups().add(g);
             }
         }
-        dto.setGroups(groups);
         return dto;
     }
 
-    /**
-     * RecommendCardVO -> PreOrderCardDTO(含全部 18 识别字段 + 计算字段)。
-     */
     private PreOrderCardDTO toCardDTO(RecommendCardVO vo) {
         PreOrderCardDTO c = new PreOrderCardDTO();
         c.setPlayer(vo.getPlayer());
@@ -197,10 +140,7 @@ public class RecommendServiceImpl implements RecommendService {
         return c;
     }
 
-    /**
-     * 单卡匹配:先透传全部识别字段到 VO,再在候选池内做降级匹配;未命中按 cardType 兜底。
-     */
-    private RecommendCardVO recommendOneCard(RecommendCardDTO dto, List<RatingRecommendRulePO> pool) {
+    private RecommendCardVO toCardVO(RecommendCardDTO dto) {
         RecommendCardVO vo = new RecommendCardVO();
         vo.setPlayer(dto.getPlayer());
         vo.setYear(dto.getYear());
@@ -210,7 +150,6 @@ public class RecommendServiceImpl implements RecommendService {
         vo.setBackImageUrl(dto.getBackImageUrl());
         vo.setPackageId(dto.getPackageId());
         vo.setPackagePrice(dto.getPackagePrice());
-        // 透传全部识别字段(用于落库存全量)
         vo.setSportEvent(dto.getSportEvent());
         vo.setCardType(dto.getCardType());
         vo.setRole(dto.getRole());
@@ -221,56 +160,10 @@ public class RecommendServiceImpl implements RecommendService {
         vo.setRoleName(dto.getRule());
         vo.setRarity(dto.getRarity());
         vo.setMaterial(dto.getMaterial());
-
-        RuleMatcher.MatchResult res = RuleMatcher.match(dto, pool);
-        // 命中且规则档位不为脏:正常填充;否则统一走兜底,避免"命中了但档位为 null"的语义错乱
-        if (res != null && res.getRule().getRecommendEfficiencyId() != null) {
-            fillFromRule(vo, res.getRule(), res.getLevel());
-            return vo;
-        }
-        // 未命中 或 命中规则档位脏:按 cardType 兜底,取不到退回全局默认
-        vo.setEvaluateEfficiencyId(resolveDefaultEfficiencyId(dto.getCardType()));
-        vo.setMatchLevel(MatchLevelEnum.DEFAULT.getCode());
+        vo.setEvaluateEfficiencyId(dto.getEvaluateEfficiencyId());
         return vo;
     }
 
-    /**
-     * 是否为宝可梦卡(cardType==1)。用于分派 (series,role) vs (series,cardSet) 收窄链。
-     */
-    private static boolean isPokemon(Integer cardType) {
-        return cardType != null && cardType == 1;
-    }
-
-    /**
-     * 兜底默认档:优先按 cardType 分档,取不到退回全局 defaultEfficiencyId。
-     */
-    private Long resolveDefaultEfficiencyId(Integer cardType) {
-        Map<Integer, Long> byType = recommendProperties.getDefaultEfficiencyByCardType();
-        if (cardType != null && byType != null) {
-            Long v = byType.get(cardType);
-            if (v != null) {
-                return v;
-            }
-        }
-        return recommendProperties.getDefaultEfficiencyId();
-    }
-
-    /**
-     * 命中规则后填充:时效、命中层级、卡片ID(POP 补查已下线,形参 activeBatchNo 一并移除)
-     */
-    private void fillFromRule(RecommendCardVO vo, RatingRecommendRulePO rule, MatchLevelEnum level) {
-        // 语义档 code(Integer) 装入 VO 的 evaluateEfficiencyId(Long)
-        vo.setEvaluateEfficiencyId(Objects.isNull(rule.getRecommendEfficiencyId())
-                ? null : rule.getRecommendEfficiencyId());
-        vo.setMatchLevel(level.getCode());
-        vo.setCardId(rule.getCardId());
-        // 规则带出的数仓卡号不再覆盖入参识别编号 cardNo(数仓 ID 由 cardId 承载)
-    }
-
-    /**
-     * 用时效字典回填每个订单的时效名称(替代 OrderGrouper 旧硬编码)。
-     * 字典无该档时,退回枚举描述;再取不到置空字符串。
-     */
     private void fillGroupNames(List<OrderSuggestionVO> orders, Map<Long, EfficiencyDictItem> dict) {
         if (orders == null) {
             return;
@@ -281,21 +174,11 @@ public class RecommendServiceImpl implements RecommendService {
             if (item != null && item.getName() != null) {
                 o.setEvaluateEfficiencyName(item.getName());
             } else {
-                // 字典缺失兜底:用枚举描述(普通/快速/闪评)
-                o.setEvaluateEfficiencyName(code == null ? ""
-                        : EvaluateEfficiencyEnum.getDescByCode(code.intValue()));
+                o.setEvaluateEfficiencyName(code == null ? "" : EvaluateEfficiencyEnum.getDescByCode(code.intValue()));
             }
         }
     }
 
-    /**
-     * 回填单价/总计并返回合计。
-     * 算价口径(大侠口径 2026-06-30):
-     *   订单 packageTotal = Σ(各卡 packagePrice),null 当 0
-     *   订单 totalAmount  = packageTotal × 卡数 + 时效单价
-     *   时效价某档无价 → 订单 unitPrice/totalAmount = null("待计算"),grandTotal 也为 null
-     *   套餐价 packagePrice 始终透传到卡级,无需降级
-     */
     private BigDecimal applyPricing(List<OrderSuggestionVO> orders, Map<Long, EfficiencyDictItem> dict) {
         if (orders == null || orders.isEmpty()) {
             return null;
@@ -303,11 +186,9 @@ public class RecommendServiceImpl implements RecommendService {
         BigDecimal grand = BigDecimal.ZERO;
         boolean grandNull = false;
         for (OrderSuggestionVO order : orders) {
-            // 1) 先汇总套餐价(与时效价是否取到无关,packageTotal 始终能算出)
             BigDecimal packageTotal = sumPackagePrice(order.getCards());
             order.setPackageTotal(packageTotal);
 
-            // 2) 再算时效价;缺失则降级"待计算"
             EfficiencyDictItem item = (dict == null) ? null : dict.get(order.getEvaluateEfficiencyId());
             BigDecimal unit = (item == null) ? null : item.getPrice();
             if (unit == null) {
@@ -321,9 +202,9 @@ public class RecommendServiceImpl implements RecommendService {
                 }
                 continue;
             }
-            // 3) 订单总计 = 套餐价合计 × 卡数 + 时效单价
-            BigDecimal packageSubtotal = packageTotal.multiply(BigDecimal.valueOf(order.getCardCount()));
-            BigDecimal total = packageSubtotal.add(unit);
+
+            BigDecimal serviceSubtotal = unit.multiply(BigDecimal.valueOf(order.getCardCount()));
+            BigDecimal total = packageTotal.add(serviceSubtotal);
             order.setEfficiencyPrice(unit);
             order.setTotalAmount(total);
             if (order.getCards() != null) {
@@ -336,9 +217,6 @@ public class RecommendServiceImpl implements RecommendService {
         return grandNull ? null : grand;
     }
 
-    /**
-     * 汇总订单内各卡的套餐价(packagePrice 为 null 当 0 处理)
-     */
     private BigDecimal sumPackagePrice(List<RecommendCardVO> cards) {
         BigDecimal sum = BigDecimal.ZERO;
         if (cards == null) {

+ 9 - 9
mango-manager/src/main/java/com/mangoo/rating/recommend/manager/impl/RatingRecommendRuleManagerImpl.java

@@ -37,7 +37,7 @@ public class RatingRecommendRuleManagerImpl implements RatingRecommendRuleManage
     }
 
     @Override
-    public int batchInsert(java.util.List<RatingRecommendRulePO> list) {
+    public int batchInsert(List<RatingRecommendRulePO> list) {
         if (list == null || list.isEmpty()) {
             return 0;
         }
@@ -53,21 +53,21 @@ public class RatingRecommendRuleManagerImpl implements RatingRecommendRuleManage
     }
 
     @Override
-    public java.util.List<String> selectDistinctBatchNos() {
-        java.util.List<String> list = ratingRecommendRuleMapper.selectDistinctBatchNos();
-        return list == null ? java.util.Collections.emptyList() : list;
+    public List<String> selectDistinctBatchNos() {
+        List<String> list = ratingRecommendRuleMapper.selectDistinctBatchNos();
+        return list == null ? Collections.emptyList() : list;
     }
 
     @Override
-    public java.util.List<RatingRecommendRulePO> selectByBatchAndSeriesSets(
-            String batchNo, java.util.List<com.mangoo.rating.recommend.dto.recommend.SeriesCardSetKey> keys) {
+    public List<RatingRecommendRulePO> selectByBatchAndSeriesSets(
+            String batchNo, List<com.mangoo.rating.recommend.dto.recommend.SeriesCardSetKey> keys) {
         // 无生效批次或无查询键 → 空列表(上层将对全部卡走兜底)
         if (isBlank(batchNo) || keys == null || keys.isEmpty()) {
-            return java.util.Collections.emptyList();
+            return Collections.emptyList();
         }
-        java.util.List<RatingRecommendRulePO> list =
+        List<RatingRecommendRulePO> list =
                 ratingRecommendRuleMapper.selectByBatchAndSeriesSets(batchNo, keys);
-        return list == null ? java.util.Collections.emptyList() : list;
+        return list == null ? Collections.emptyList() : list;
     }
 
     @Override