"""价格观测内部上报端点(pricebot → app-server)。 pricebot 在比价 done 帧把整批价格事实 POST 到这里落库。**不是给客户端的接口**: 不走用户 JWT,靠 server 间共享密钥头 `X-Internal-Secret` 校验(== settings.INTERNAL_API_SECRET)。 密钥未配置(默认空)时直接 503,避免裸奔的写端点。 与不鉴权的比价透传(compare.py / coupon.py)无关:那两个是客户端→server 的透传壳, 这个是 server→server 的内部写库。 """ from __future__ import annotations import hmac import logging from typing import Annotated from fastapi import APIRouter, Header, HTTPException, status from app.api.deps import DbSession from app.core.config import settings from app.repositories import price_observation as repo from app.schemas.price_observation import ( PriceObservationBatchIn, PriceObservationBatchOut, ) logger = logging.getLogger("shagua.internal.price") router = APIRouter(prefix="/internal", tags=["internal"]) def _check_secret(x_internal_secret: str | None) -> None: """共享密钥校验。未配置 → 503(挡住裸奔写端点);不匹配 → 401(常量时间比较)。""" configured = settings.INTERNAL_API_SECRET if not configured: raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="internal api not configured", ) if not x_internal_secret or not hmac.compare_digest(x_internal_secret, configured): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="invalid internal secret", ) @router.post( "/price-observation", response_model=PriceObservationBatchOut, summary="价格观测内部上报(pricebot→app-server,落 price_observation)", ) def report_price_observation( payload: PriceObservationBatchIn, db: DbSession, x_internal_secret: Annotated[str | None, Header()] = None, ) -> PriceObservationBatchOut: _check_secret(x_internal_secret) inserted, skipped = repo.insert_batch(db, payload) logger.info( "price_observation trace=%s inserted=%d skipped=%d device=%s user=%s", payload.trace_id, inserted, skipped, payload.source_device_id, payload.source_user_id, ) return PriceObservationBatchOut(inserted=inserted, skipped=skipped)