Files
shaguabijia-app-server/app/api/internal/store.py
T
marco 848f23aaea feat(store_mapping): 加 GET /internal/store-mapping/lookup 缓存查询(按源店名+geo 反查目标店铺 id)
比价前 pricebot 调它反查各目标平台已沉淀的店铺 id, 命中则目标腿直接 deeplink 跳店内搜索。
- lookup_nearest: 按"源平台对应 name 列 == 源店名"匹配; 每个目标**分别**取"含该目标 id 的同名候选
  里最近一条"(haversine, 淘宝 id / 京东 id 可能在不同行); 排除源平台自己; 有入参 geo 取最近、否则
  取 created_at 最新。返 {target: {店铺id..., deeplink, row_id, dist_km}}。
- 端点日志打源/店名/geo/命中行/距离/deeplink, 与 pricebot 侧对账。
- 同次提交带上 upsert 首写改逐列灌 _MERGE_COLUMNS(任意平台首写不丢列)+ 美团/京东合并列。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-14 00:44:55 +08:00

80 lines
2.8 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.get(
"/store-mapping/lookup",
summary="比价前按源平台店名反查各目标平台已沉淀的店铺 id(命中→pricebot 直接 deeplink)",
)
def lookup_store_mapping(
source_platform: str,
name: str,
db: DbSession,
lat: float | None = None,
lng: float | None = None,
x_internal_secret: Annotated[str | None, Header()] = None,
) -> dict:
_check_secret(x_internal_secret)
result = repo.lookup_nearest(db, source_platform, name, lat, lng)
if result:
hits = ", ".join(
f"{t}:row{v['row_id']}"
f"{'(' + str(v['dist_km']) + 'km)' if 'dist_km' in v else ''}"
f"{v.get('deeplink') or '(无deeplink)'}"
for t, v in result.items()
)
logger.info(
"store_mapping lookup source=%s name=%r geo=(%s,%s) → 命中 %s",
source_platform, name, lat, lng, hits,
)
else:
logger.info(
"store_mapping lookup source=%s name=%r geo=(%s,%s) → MISS",
source_platform, name, lat, lng,
)
return result
@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)