2026-06-24-rating-efficiency-recommend-design.md 9.5 KB

评级时效推荐 + 自动分单 + 报价 设计文档(Design Spec)

项目:RatingRecommend(卡牌评级小程序-推荐服务) 日期:2026-06-24 作者:gengjintao(大侠)/ Claude 协助 状态:已评审,待转实现计划


1. 目标(Goal)

提供一个无状态的计算型 REST 接口:接收一批卡的识别特征(含图片),为每张卡推荐评级时效档位,再按时效将卡聚合为"多个订单建议",并给出每卡单价、订单数量与总计金额。

  • 无状态:全程不写库、不建购物车、不创建真实订单、不结算。
  • 价格能力以"调订单服务 + 预留扩展口子 + 取不到则降级"的方式接入,本期不强依赖订单服务。

2. 范围(Scope)

本期做:

  • 批量卡特征输入 → 逐卡时效推荐(规则表)。
  • 时效精确分组 → 多订单建议。
  • 逐卡报价(时效→价格,来自订单服务,预留口子 + 降级)。
  • 独立 REST 接口(Controller→Service→Manager→Mapper 全链路)。
  • 规则表建表 DDL(标注需评审后手动执行)。

本期不做(YAGNI):

  • 购物车落库、人工校对/重算、结算 checkout。
  • 容差聚类取齐分单(仅做"同时效精确分组",分组逻辑抽象、后续可扩展)。
  • 套餐入参、价格表新建、用户登录强校验。

3. 关键设计决策

# 决策 说明
D1 时效档位直接用 EvaluateEfficiencyEnum.code 普通=1 / 快速=2 / 闪评=3,天然有序,与现有订单口径一致。
D2 交付形态 = 独立 REST 接口(全链路) POST /api/recommend/efficiency
D3 匹配策略 = 全等优先 + 系列主导模糊降级 + 兜底 降级阶梯见 §6.1,命中即止。
D4 分组 = 同时效精确分组 相同最终时效归一个订单建议,按档位升序出 groupNo,最多 3 单;分组逻辑抽象为纯类,容差聚类后续扩展。
D5 价格来源 = 订单服务(order-service),实现预留口子 抽象 EfficiencyPriceProvider,本期可占位/降级;订单服务未接通时单价/总计为 null("待计算"),不阻断推荐。
D6 降级查询实现 = 多次精确 SQL Mapper 按 L1/L2/L3 提供 3 个查询,Service 逐级调用,走索引、DB 友好、逻辑直白。
D7 接口不强制登录 纯计算无需用户上下文,沿用现有放行策略。
D8 规则数据只读 规则表数据由数仓同步 / 人工维护;本功能不产生任何写库语句。

4. 安全与合规(CLAUDE.md 数据库规则)

  • 涉及 1 张新表 DDLt_rating_recommend_rule),标注"⚠️ 需大侠评审确认后手动执行",执行器不主动对数据库运行任何写操作。
  • Mapper 仅含 SELECT,无任何 INSERT/UPDATE/DELETE。
  • 不写 Redis、不发 MQ、不调用第三方变更接口。
  • 价格读取为只读(订单服务查询 / 缓存读取)。

5. 接口契约

5.1 请求 POST /api/recommend/efficiency

RecommendRequest

{
  "cards": [
    {
      "clientCardId": "c1",
      "player": "Jordan",
      "year": "1986",
      "series": "Fleer",
      "cardSet": "Base",
      "frontImageUrl": "http://x/c1f.jpg",
      "backImageUrl": "http://x/c1b.jpg"
    }
  ]
}
  • clientCardId:前端为每张卡生成的临时标识,仅透传、不入库,用于回指卡归属的订单。
  • 四特征:player / year / series / cardSet
  • 图片:frontImageUrl / backImageUrl,仅透传回显。

5.2 响应 AjaxResult.data = RecommendResultVO

{
  "totalCards": 3,
  "orderCount": 2,
  "grandTotal": 360.00,
  "orders": [
    {
      "groupNo": 1,
      "efficiency": 3,
      "efficiencyDesc": "闪评",
      "unitPrice": 120.00,
      "cardCount": 2,
      "totalAmount": 240.00,
      "cards": [
        {
          "clientCardId": "c1",
          "player": "Jordan", "year": "1986", "series": "Fleer", "cardSet": "Base",
          "recommendEfficiency": 3,
          "matchLevel": "EXACT",
          "unitPrice": 120.00,
          "frontImageUrl": "http://x/c1f.jpg",
          "backImageUrl": "http://x/c1b.jpg"
        }
      ]
    }
  ]
}

字段说明:

  • 订单级:groupNoefficiency/efficiencyDesc(订单推荐时效)、unitPrice(该档单价)、cardCount(卡数量)、totalAmount(订单总计 = 单价 × 数量)。
  • 卡级:recommendEfficiencymatchLevel(命中层级 EXACT/L2/L3/DEFAULT)、unitPrice、图片透传。
  • 汇总:grandTotal(所有订单合计)。
  • 降级:价格取不到时,相关 unitPrice/totalAmount/grandTotal = null(前端显示"待计算"),不阻断推荐主体结果。

6. 核心逻辑

6.1 单卡匹配降级(系列主导,命中即止)

EXACT : player + year + series + cardSet 全等
L2    : series + cardSet + year
L3    : series + cardSet
DEFAULT: 兜底默认档(可配置,默认 普通=1)
  • 某一层参与字段存在空值,则跳过该层(如 series 为空,则 L2/L3 均无法命中 → 直接兜底)。
  • 命中但规则值非法(不在 1/2/3)→ 兜底。
  • 输出 (recommendEfficiency, matchLevel)

6.2 分组(纯类 OrderGrouper

  • 按每卡最终推荐时效精确分组,相同档归一组。
  • 按档位升序输出 groupNo(普通→快速→闪评),最多 3 组。
  • 纯函数、无 Spring 依赖;TDD 覆盖:空输入 / 单卡 / 全同档 / 多档乱序。

6.3 报价口子(EfficiencyPriceProvider

  • 抽象方法:Map<Integer, BigDecimal> loadEfficiencyPrices()(时效 code → 单价)。
  • 本期实现:预留调订单服务(order-service)的 Feign 口子,取全部服务等级(ProductServiceLevelCacheDTO:含 timeLimit 字符串 + price),按"timeLimit 字符串 → EvaluateEfficiencyEnum.code"映射(映射规则配置化)。
  • 订单服务未接通 / 取不到 → 返回空 Map → 上层降级为"待计算"。
  • 设计意图:口子预留,后续替换实现对上层零改动

7. 分层与文件结构(包名 com.mangoo.rating.recommend.*

mango-common

  • request/recommend/RecommendRequest.javarequest/recommend/RecommendCardDTO.java
  • response/recommend/RecommendResultVO.javaresponse/recommend/OrderSuggestionVO.javaresponse/recommend/RecommendCardVO.java
  • po/RatingRecommendRulePO.java
  • enums/MatchLevelEnum.java(EXACT/L2/L3/DEFAULT)
  • 复用 enums/EvaluateEfficiencyEnum.java

mango-infrastructure

  • mapper/RatingRecommendRuleMapper.java + resources/mapper/RatingRecommendRuleMapper.xml(仅 SELECT)

mango-manager

  • manager/RatingRecommendRuleManager.java + manager/impl/RatingRecommendRuleManagerImpl.java

mango-domain

  • service/RecommendService.java + service/impl/RecommendServiceImpl.java(编排)
  • service/recommend/OrderGrouper.java(纯类,分组)
  • service/recommend/EfficiencyPriceProvider.java + 实现(价格口子 + 降级)

mango-application

  • app/controller/RecommendController.java
  • config/RecommendProperties.java(默认兜底档 + 时效字符串映射配置)
  • 测试:OrderGrouperTestRecommendServiceImplTest(Mockito)

DDL

  • docs/superpowers/specs/ddl/2026-06-24-rating-recommend-rule.sql(待评审执行)

8. 规则表 DDL(PostgreSQL,待评审)

CREATE TABLE IF NOT EXISTS t_rating_recommend_rule (
    id                   BIGSERIAL PRIMARY KEY,
    player               VARCHAR(255) NOT NULL,
    year                 VARCHAR(64)  NOT NULL,
    series               VARCHAR(255) NOT NULL,
    card_set             VARCHAR(255) NOT NULL,
    recommend_efficiency SMALLINT     NOT NULL,
    effective_time       TIMESTAMP,
    create_time          TIMESTAMP    NOT NULL DEFAULT now(),
    update_time          TIMESTAMP    NOT NULL DEFAULT now(),
    del_flag             SMALLINT     NOT NULL DEFAULT 0
);
COMMENT ON TABLE t_rating_recommend_rule IS '评级时效推荐规则表(数仓沉淀,只读查询)';
-- 支撑系列主导降级查询
CREATE INDEX IF NOT EXISTS idx_rrr_series_set_year ON t_rating_recommend_rule (series, card_set, year);
-- 支撑全等命中查询
CREATE INDEX IF NOT EXISTS idx_rrr_feature ON t_rating_recommend_rule (player, year, series, card_set);

9. 前置改造(必须)

当前启动类 RatingRecommendApplicationexclude = {DataSourceAutoConfiguration.class}数据源未启用。本功能需查询规则表,实现计划第一步须先启用数据源、接通 PostgreSQL(移除排除项并校验既有配置)。

10. 测试策略

  • 纯算法 OrderGrouper:纯 JUnit5,TDD 先行,覆盖空/单卡/全同/多档乱序。
  • 降级匹配 / 报价映射:Mockito 单测(mock Manager / PriceProvider / 配置)。
  • 编排 / 落库查询 / Controller:编译通过 + @SpringBootTest 冒烟 + 手动接口验证;不写无断言的假测试。

11. 配置项(recommend.*

recommend:
  default-efficiency: 1          # 兜底默认档(EvaluateEfficiencyEnum.code)
  # 服务等级时效字符串 → 时效 code 映射(报价用),示例:
  time-limit-mapping:
    "普通": 1
    "快速": 2
    "闪评": 3

12. 开放/已知简化项

  • 报价依赖订单服务,本期为预留口子;未接通时整单"待计算"。
  • 模糊匹配为"系列主导"三级阶梯,更复杂的相似度/热度权重留后续。
  • 分组为同时效精确分组,容差聚类取齐留后续(OrderGrouper 已抽象,便于扩展)。