# 数仓同步 + 卡片趋势接口 + 推荐增强 设计文档(Design Spec v2) > 项目:RatingRecommend | 日期:2026-06-25 > 前置:本文是 `2026-06-24-rating-efficiency-recommend-design.md` 的扩展 > 状态:已评审,待放行写库后实施 --- ## 1. 目标 1. **数仓同步**:数仓每日 ETL 完成后,通过 HTTP 主动推送卡片规则数据(含 POP report、价值、价格历史)到本服务并落库。 2. **卡片趋势/详情查询接口**:给前端提供对齐图片 UI 的卡片详情(身份 + POP + 价值 + 价格走势曲线)。 3. **推荐响应增强**:推荐接口命中后,透传 POP/价值数据。 ## 2. 关键决策(本轮新增确认) | # | 决策 | 说明 | |---|---|---| | E1 | POP/价值/趋势对推荐**仅展示透传** | recommend_efficiency 仍由数仓直接给,推荐降级匹配逻辑不变 | | E2 | 卡片唯一键 = 数仓 `card_id` | 去重根基 | | E3 | POP report 按**三机构各一套**(PSA/BGS/Nexphostis)| 用明细行存表 | | E4 | 数仓主动推(HTTP)+ T+1 每日批量 | 本服务被动接收 | | E5 | 规则/POP/当前价:**全量批次替换**(batch_no + 指针切换,查询零中断)| 当前快照 | | E6 | 价格历史:**增量追加**(每日追加当天价,永久累积,不被全量覆盖)| 趋势数据源 | | E7 | 数据量小(万级),单次全量推送 + 单事务切换 | | ## 3. 表结构 ### 3.1 `t_rating_recommend_rule`(规则主表,加字段) 新增:`card_id`、`card_no`、`current_value`、`value_change_pct`、`value_min`、`value_max`、`batch_no` 唯一约束:`(batch_no, card_id)`;查询索引:`(batch_no, series, card_set, year)` ### 3.2 `t_rating_card_pop`(POP 明细,新表) `id, card_id, agency, grade_all, grade_10, grade_9, grade_8, grade_7, batch_no` 索引:`(batch_no, card_id)` ### 3.3 `t_rating_card_value_history`(价格历史,新表,增量累积) `id, card_id, price, record_date, create_time` 唯一约束:`(card_id, record_date)`(同卡同日只一条,幂等) ### 3.4 `t_rating_recommend_active_batch`(生效批次指针,新表,单行) `id, active_batch_no, update_time` ## 4. 数仓同步接口 `POST /api/recommend/rule/sync`(内部鉴权:Token/签名/IP 白名单) 请求体: ```json { "batchNo": "2026062501", "cards": [ { "cardId":"...", "cardNo":"066/080", "player":"AIPOM", "year":"2026", "series":"pokemon.M2.JNP", "cardSet":"BASE", "recommendEfficiency":2, "currentValue":15, "valueChangePct":7.05, "valueMin":10, "valueMax":45, "recordDate":"2026-06-25", "todayPrice":15, "pops":[ {"agency":"PSA","all":660,"g10":25,"g9":152,"g8":220,"g7":458}, ... ] } ] } ``` 处理(单事务): 1. 校验:batchNo 合法、cards 非空(空拒绝,防全表清空)、batchNo 必须比当前生效批次新(防回灌) 2. 按 cardId 去重(同批次重复 cardId 取最新/报错) 3. **全量替换部分**:DELETE 该 batchNo 残留(规则表 + POP 表)→ 批量 INSERT 规则表 + POP 表 4. **增量追加部分**:价格历史表按 `(card_id, record_date)` UPSERT 当天价(同日幂等) 5. UPDATE 指针表 active_batch_no = batchNo 6. 提交事务(查询原子切到新批次;历史表已追加) 7. 事务后异步 DELETE 更早批次(规则表 + POP 表保留最近 1~2 批) ## 5. 卡片趋势/详情查询接口 `GET /api/recommend/card/detail?cardId={cardId}&period={7d|30d|90d|all}`(period 默认 30d) 响应 `data` = `CardDetailVO`: ```json { "cardId":"...", "cardNo":"066/080", "name":"AIPOM", "year":"2026", "series":"pokemon.M2.JNP", "cardSet":"BASE", "imageUrl":"...", "pops":[ {"agency":"PSA","all":660,"g10":25,"g9":152,"g8":220,"g7":458}, ... ], "value":{"current":15,"changePct":7.05,"min":10,"max":45}, "trend":[ {"date":"2026-06-01","price":12.0}, {"date":"2026-06-25","price":15.0} ] } ``` - 身份 + value:查规则表当前生效批次(按 card_id) - pops:查 POP 明细表(card_id + 生效批次) - trend:查价格历史表(card_id + period 时间窗,按日期升序) > 注:imageUrl 若数仓未提供则为 null(图片字段本期可选) ## 6. 推荐响应增强 - `RecommendCardVO` 增字段:`cardId、cardNo、currentValue、valueChangePct、valueMin、valueMax、pops[]` - `RecommendServiceImpl`:三层降级命中规则后,带出 card_id/价值,并按 card_id 补查 POP 明细放入 VO - 规则查询统一加 `batch_no = 当前生效批次` 过滤(读指针表,可缓存 30s) ## 7. 去重 & 历史 - 唯一键 `card_id`;同批次按 card_id 去重;规则/POP 跨批次全量替换 → 无脏历史 - 价格历史按 `(card_id, record_date)` 幂等累积,永久保留 ## 8. ⚠️ 写库清单(CLAUDE.md 红线,需大侠放行) | 写操作 | 位置 | |---|---| | 4 张表 DDL(规则表加列 + POP/历史/指针 新表)| 由大侠**手动执行** | | 规则表/POP 表 DELETE + INSERT(全量替换)| 同步 Service/Mapper | | 价格历史表 UPSERT(增量)| 同步 Service/Mapper | | 指针表 UPDATE(切批次)| 同步 Service/Mapper | | 旧批次异步 DELETE | 同步 Service/Mapper | > 推荐查询、趋势查询、POP 查询均为只读 SELECT,不在红线内。 ## 9. 测试策略 - 同步去重/防回灌/空保护:Service 单测(Mockito) - 趋势 period 时间窗过滤:Mapper/Service 验证 - 推荐增强:扩展现有 RecommendServiceImplTest - 全链路:编译 + 单测 + 手动接口(需 DDL 执行 + 数据) ## 10. 跨团队对接事项 数仓侧需对齐:`cardId` 稳定性、`batchNo` 生成规则、`pops` 三机构口径、每日推送的 `recordDate/todayPrice` 字段、鉴权方式。