Răsfoiți Sursa

feat(recommend): 实现 RecommendService(降级匹配+分组+报价编排)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
jintao.geng 1 lună în urmă
părinte
comite
32ff61e41c

+ 21 - 0
mango-domain/src/main/java/com/mangoo/rating/recommend/service/RecommendService.java

@@ -0,0 +1,21 @@
+package com.mangoo.rating.recommend.service;
+
+import com.mangoo.rating.recommend.AjaxResult;
+import com.mangoo.rating.recommend.request.recommend.RecommendRequest;
+
+/**
+ * 评级时效推荐服务(编排:逐卡降级匹配 → 精确分组 → 报价 → 组装响应)
+ *
+ * @author gengjintao
+ * @date 2026/06/24
+ */
+public interface RecommendService {
+
+    /**
+     * 推荐评级时效并按时效分单
+     *
+     * @param request 批量卡请求
+     * @return AjaxResult.data = RecommendResultVO
+     */
+    AjaxResult recommendEfficiency(RecommendRequest request);
+}

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

@@ -0,0 +1,171 @@
+package com.mangoo.rating.recommend.service.impl;
+
+import com.mangoo.rating.recommend.AjaxResult;
+import com.mangoo.rating.recommend.config.RecommendProperties;
+import com.mangoo.rating.recommend.enums.EvaluateEfficiencyEnum;
+import com.mangoo.rating.recommend.enums.MatchLevelEnum;
+import com.mangoo.rating.recommend.manager.RatingRecommendRuleManager;
+import com.mangoo.rating.recommend.po.RatingRecommendRulePO;
+import com.mangoo.rating.recommend.request.recommend.RecommendCardDTO;
+import com.mangoo.rating.recommend.request.recommend.RecommendRequest;
+import com.mangoo.rating.recommend.response.recommend.OrderSuggestionVO;
+import com.mangoo.rating.recommend.response.recommend.RecommendCardVO;
+import com.mangoo.rating.recommend.response.recommend.RecommendResultVO;
+import com.mangoo.rating.recommend.service.RecommendService;
+import com.mangoo.rating.recommend.service.recommend.EfficiencyPriceProvider;
+import com.mangoo.rating.recommend.service.recommend.OrderGrouper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 评级时效推荐服务实现
+ *
+ * 流程:
+ *   1) 逐卡按"全等→L2→L3→兜底"降级匹配,得到 (recommendEfficiency, matchLevel)
+ *   2) 用纯类 OrderGrouper 按时效精确分组
+ *   3) 加载时效价格(取不到则全部降级"待计算")
+ *   4) 回填单价/总计/合计,组装响应
+ *
+ * @author gengjintao
+ * @date 2026/06/24
+ */
+@Slf4j
+@Service
+public class RecommendServiceImpl implements RecommendService {
+
+    @Resource
+    private RatingRecommendRuleManager ratingRecommendRuleManager;
+
+    @Resource
+    private EfficiencyPriceProvider efficiencyPriceProvider;
+
+    @Resource
+    private RecommendProperties recommendProperties;
+
+    @Override
+    public AjaxResult recommendEfficiency(RecommendRequest request) {
+        // 入参校验
+        if (request == null || request.getCards() == null || request.getCards().isEmpty()) {
+            return AjaxResult.error("卡列表不能为空");
+        }
+        if (request.getCards().size() > recommendProperties.getBatchMax()) {
+            return AjaxResult.error("单次卡数量超过上限:" + recommendProperties.getBatchMax());
+        }
+
+        // 1) 逐卡推荐 → 转 VO
+        List<RecommendCardVO> cardVOs = new ArrayList<>(request.getCards().size());
+        for (RecommendCardDTO dto : request.getCards()) {
+            cardVOs.add(recommendOneCard(dto));
+        }
+
+        // 2) 按时效精确分组
+        List<OrderSuggestionVO> orders = OrderGrouper.group(cardVOs);
+
+        // 3) 报价(取不到则全部 null=待计算,不阻断)
+        Map<Integer, BigDecimal> priceMap = efficiencyPriceProvider.loadEfficiencyPrices();
+        BigDecimal grandTotal = applyPricing(orders, priceMap);
+
+        // 4) 组装响应
+        RecommendResultVO result = new RecommendResultVO();
+        result.setTotalCards(request.getCards().size());
+        result.setOrderCount(orders.size());
+        result.setGrandTotal(grandTotal);
+        result.setOrders(orders);
+        return AjaxResult.success("推荐成功", result);
+    }
+
+    /**
+     * 单卡降级匹配:EXACT → L2 → L3 → DEFAULT
+     */
+    private RecommendCardVO recommendOneCard(RecommendCardDTO dto) {
+        RecommendCardVO vo = new RecommendCardVO();
+        vo.setClientCardId(dto.getClientCardId());
+        vo.setPlayer(dto.getPlayer());
+        vo.setYear(dto.getYear());
+        vo.setSeries(dto.getSeries());
+        vo.setCardSet(dto.getCardSet());
+        vo.setFrontImageUrl(dto.getFrontImageUrl());
+        vo.setBackImageUrl(dto.getBackImageUrl());
+
+        // L1:四字段全等命中
+        RatingRecommendRulePO rule = ratingRecommendRuleManager.selectByExact(
+                dto.getPlayer(), dto.getYear(), dto.getSeries(), dto.getCardSet());
+        if (isLegalRule(rule)) {
+            vo.setRecommendEfficiency(rule.getRecommendEfficiency());
+            vo.setMatchLevel(MatchLevelEnum.EXACT.getCode());
+            return vo;
+        }
+        // L2:series + cardSet + year
+        rule = ratingRecommendRuleManager.selectBySeriesSetYear(dto.getSeries(), dto.getCardSet(), dto.getYear());
+        if (isLegalRule(rule)) {
+            vo.setRecommendEfficiency(rule.getRecommendEfficiency());
+            vo.setMatchLevel(MatchLevelEnum.L2.getCode());
+            return vo;
+        }
+        // L3:series + cardSet
+        rule = ratingRecommendRuleManager.selectBySeriesSet(dto.getSeries(), dto.getCardSet());
+        if (isLegalRule(rule)) {
+            vo.setRecommendEfficiency(rule.getRecommendEfficiency());
+            vo.setMatchLevel(MatchLevelEnum.L3.getCode());
+            return vo;
+        }
+        // 兜底
+        vo.setRecommendEfficiency(recommendProperties.getDefaultEfficiency());
+        vo.setMatchLevel(MatchLevelEnum.DEFAULT.getCode());
+        return vo;
+    }
+
+    /**
+     * 校验规则及其时效值是否合法(非空 + 时效在 EvaluateEfficiencyEnum 范围内)
+     */
+    private boolean isLegalRule(RatingRecommendRulePO rule) {
+        return rule != null
+                && rule.getRecommendEfficiency() != null
+                && EvaluateEfficiencyEnum.getByCode(rule.getRecommendEfficiency()) != null;
+    }
+
+    /**
+     * 给订单列表回填单价/总计,并返回所有订单合计。
+     * 价格 Map 为空或某档无价 → 该订单 unitPrice/totalAmount = null("待计算"),并将 grandTotal 置 null。
+     */
+    private BigDecimal applyPricing(List<OrderSuggestionVO> orders, Map<Integer, BigDecimal> priceMap) {
+        if (orders == null || orders.isEmpty()) {
+            return null;
+        }
+        BigDecimal grand = BigDecimal.ZERO;
+        boolean grandNull = false;
+
+        for (OrderSuggestionVO order : orders) {
+            BigDecimal unit = (priceMap == null) ? null : priceMap.get(order.getEfficiency());
+            if (unit == null) {
+                // 该档价格缺失 → 订单 unitPrice/totalAmount = null,整体合计同步置 null
+                order.setUnitPrice(null);
+                order.setTotalAmount(null);
+                grandNull = true;
+                // 卡级单价同步 null
+                if (order.getCards() != null) {
+                    for (RecommendCardVO c : order.getCards()) {
+                        c.setUnitPrice(null);
+                    }
+                }
+                continue;
+            }
+            BigDecimal total = unit.multiply(BigDecimal.valueOf(order.getCardCount()));
+            order.setUnitPrice(unit);
+            order.setTotalAmount(total);
+            if (order.getCards() != null) {
+                for (RecommendCardVO c : order.getCards()) {
+                    c.setUnitPrice(unit);
+                }
+            }
+            grand = grand.add(total);
+        }
+        return grandNull ? null : grand;
+    }
+}