"""平台店铺映射内部上报端点(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, StoreMappingInvalidateIn, StoreMappingInvalidateOut, 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) @router.post( "/store-mapping/invalidate", response_model=StoreMappingInvalidateOut, summary="标记某平台 shopId 的缓存 deeplink 失效(pricebot 撞错误页回退时上报,lookup 不再返回)", ) def invalidate_store_mapping( payload: StoreMappingInvalidateIn, db: DbSession, x_internal_secret: Annotated[str | None, Header()] = None, ) -> StoreMappingInvalidateOut: _check_secret(x_internal_secret) if payload.platform == "taobao": affected = repo.mark_taobao_deeplink_invalid(db, payload.shop_id) elif payload.platform == "jd": affected = repo.mark_jd_deeplink_invalid(db, payload.shop_id) else: # 当前只接淘宝/京东; 其它平台先 no-op(affected=0), 不报错 — 向后兼容 pricebot 将来扩展。 logger.info("store_mapping invalidate 跳过: platform=%s 暂不支持", payload.platform) return StoreMappingInvalidateOut(ok=True, affected=0) logger.info( "store_mapping invalidate platform=%s shop_id=%s → 标记失效 %d 行", payload.platform, payload.shop_id, affected, ) return StoreMappingInvalidateOut(ok=True, affected=affected)