0860eed92b
store_mapping 的语义是"一次比价(一个 trace)= 一个真实店铺一行", 各目标平台解析出自己的 shopId 就往同一行填自己那几列: 淘宝腿先建行(id_taobao/name_taobao), 将来京东腿解析出 id 再合并进同一行(id_jd/name_jd)。trace_id 唯一约束天然就是这一行的合并键。 原 insert 是"撞 trace 就跳过", 第二条腿来上报会被丢掉, 撑不起渐进填充。改成 upsert: - 不存在 → INSERT(同原行为) - 已存在 → 填空合并(fill-the-blanks): 只写该行当前为 NULL 的列, 绝不覆盖已有非空值。 保证后到平台只填自己的列、动不了先到平台的数据; 共享列(geo/source/溯源)先到先得。 - 并发撞唯一约束 → 回滚后转走同一填空合并路径(不依赖 ON CONFLICT, PG/SQLite 都安全)。 返回值 (created, row_id): created=1 新建 / 0 合并; 端点日志区分"新建/合并"并带上 jd 列。 注: 一行里 id_taobao 与 id_jd 共存只是 name-match 置信度(两腿搜同一源店名各自匹配到某店), 非已核实同一实体; 作 append-only 原始资产留存, 精确匹配下游做。 验证: TestClient 打真实 PG —— 淘宝建行(inserted=1) → 京东同 trace 合并(只 UPDATE id_jd/ name_jd, geohash 不进 SET) → 淘宝重报不同 id 不覆盖(无 UPDATE)。最终一行各列正确, 已清理。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
47 lines
1.7 KiB
Python
47 lines
1.7 KiB
Python
"""平台店铺映射内部上报端点(pricebot → app-server)。
|
|
|
|
pricebot 在淘宝比价拿到 shopId 后,把这一行跨平台店铺映射 POST 到这里落库。
|
|
**不是给客户端的接口**:不走用户 JWT,靠 server 间共享密钥头 `X-Internal-Secret` 校验
|
|
(复用 price.py 的 _check_secret,与 price-observation 同一密钥)。
|
|
|
|
与 price.py 的 /internal/price-observation 平行:那个落价格事实,这个落店铺身份映射。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
|
|
from typing import Annotated
|
|
|
|
from fastapi import APIRouter, Header
|
|
|
|
from app.api.deps import DbSession
|
|
from app.api.internal.price import _check_secret
|
|
from app.repositories import store_mapping as repo
|
|
from app.schemas.store_mapping import StoreMappingIn, StoreMappingOut
|
|
|
|
logger = logging.getLogger("shagua.internal.store")
|
|
|
|
router = APIRouter(prefix="/internal", tags=["internal"])
|
|
|
|
|
|
@router.post(
|
|
"/store-mapping",
|
|
response_model=StoreMappingOut,
|
|
summary="平台店铺映射内部上报(pricebot→app-server,落 store_mapping)",
|
|
)
|
|
def report_store_mapping(
|
|
payload: StoreMappingIn,
|
|
db: DbSession,
|
|
x_internal_secret: Annotated[str | None, Header()] = None,
|
|
) -> StoreMappingOut:
|
|
_check_secret(x_internal_secret)
|
|
created, row_id = repo.upsert(db, payload)
|
|
logger.info(
|
|
"store_mapping trace=%s %s row_id=%s source=%s "
|
|
"taobao=(%s,%s) jd=(%s,%s) device=%s user=%s",
|
|
payload.trace_id, "新建" if created else "合并", row_id, payload.source_platform,
|
|
payload.id_taobao, payload.name_taobao, payload.id_jd, payload.name_jd,
|
|
payload.source_device_id, payload.source_user_id,
|
|
)
|
|
return StoreMappingOut(inserted=created, row_id=row_id)
|