新增 admin 模块接口文档(auth login/me、admins CRUD、audit-logs、feedback 处理、stats 概览、user coins/detail/status、wallet 流水、withdraw 对账/刷新/列表) + admin_user/admin_audit_log 两张表文档,更新 api/database README 索引。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: OuYingJun1024 <1034284404@qq.com> Reviewed-on: #12 Co-authored-by: ouzhou <ouzhou@wonderable.ai> Co-committed-by: ouzhou <ouzhou@wonderable.ai>
4.4 KiB
POST /api/v1/compare/record — 上报一次比价结果(幂等)
所属:比价记录组(前缀
/api/v1/compare) | 鉴权:Bearer | ← 返回 API 索引
比价 done 帧后,客户端用带 JWT 的通道上报一条比价结果,落 comparison_record 表,作为「我的比价记录」数据源 + 用户级行为画像。
⚠️ 与不鉴权的透传端点
/api/v1/price/step不同:那是转发壳,本接口按用户维度落库,必须鉴权。 本轮只做 server 端;客户端在 done 帧后调本接口的改动另起一轮(见 待办与技术债.md P1)。
入参(JSON body)
字段刻意对齐 pricebot 协议(02_api_protocol.md 的 calibration + done.params),客户端把 Phase1 的 calibration 和 done 帧 params 零翻译映射上来即可。
| 字段 | 类型 | 必填 | 默认 | 说明 |
|---|---|---|---|---|
trace_id |
string | ✅ | — | pricebot 侧 trace_id。幂等键:同用户同 trace_id 重复上报覆盖、返回同一 id |
business_type |
string | ❌ | food |
food(外卖,当前唯一接通) / ecom(电商) / coupon(领券) |
device_id |
string | null | ❌ | null | 设备号 |
store_name |
string | null | ❌ | null | 店铺名(外卖,来自 calibration.result) |
source_platform_id |
string | null | ❌ | null | 源平台代号,如 taobao_flash |
source_platform_name |
string | null | ❌ | null | 源平台中文名 |
source_package |
string | null | ❌ | null | 源平台 Android 包名 |
source_price |
float | null | ❌ | null | 源平台到手价(元) |
items |
Item[] | ❌ | [] | 下单菜品,{name, qty, specs?} |
comparison_results |
Result[] | ❌ | [] | 逐平台对比,见下表(price 单位元) |
skipped_dish_count |
int | null | ❌ | null | 目标平台未找到、跳过的菜品数 |
skipped_dish_names |
string[] | ❌ | [] | 被跳过的菜名 |
total_dish_count |
int | null | ❌ | null | 原始菜品总数 |
information |
string | null | ❌ | null | done 帧文案(成功摘要 / 失败具体原因),持久化为列并在列表/详情返回,前端失败时当原因展示 |
status |
string | null | ❌ | null | success / failed。不传则服务端派生 |
Item:{ name: string, qty: int=1, specs: string[]\|null }
Result(comparison_results 元素):{ platform_id, platform_name, package, price(元,float\|null), is_source(bool), rank(int\|null), coupon_saved(元,float\|null), coupon_name(string\|null) }
coupon_saved:该平台本单平台主优惠额(元)——美团红包 / 淘宝平台红包 / 京东优惠券·百亿补贴,只取那一笔,不含配送费减免/共减总额。仅外卖目标平台带值,源平台/没用为null,前端记录页据此展示**「已优惠 ¥X」**(null 不展示)。coupon_name:优惠来源名(展示用,best-effort)——美团「外卖大额神券」/ 京东「百亿补贴」/ 淘宝「平台红包」。仅在该行有coupon_saved时带;null时前端走通用「红包」。非用户选中的具体券名(选中态不可读)。
服务端派生(客户端不用算)
从 comparison_results 派生并落结构化列:
best_*:取最便宜的一家(按rank升序、再按price),写best_platform_id/name/best_price_centssource_price_cents:优先用source_price,缺失则取comparison_results里is_source=true行的价saved_amount_cents=源价 − 最优价(可为 0 / 负:源平台本来就最便宜)is_source_best:最便宜的一家是否为源平台(= 这次没省到)status(未显式传时):存在「非源且有价」的结果 →success,否则 →failed- 金额一律
round(元 × 100)存分
comparison_results / items / skipped_dish_names 原样存 JSON;完整上报 body 另存 raw_payload(详情接口可取回)。
出参
响应 200:{ "id": int }(写入或已存在记录的 id)
错误
401未鉴权422缺trace_id或字段类型不符
说明
失败的比价(done 帧只有源/空 comparison_results)也会落一条 status=failed——「越详细越好」,是否在 UI 展示由前端过滤。