From 60157507adfbb68f6ae65e73f5b1da87d8551ac3 Mon Sep 17 00:00:00 2001 From: chenshuobo <1119780489@qq.com> Date: Sun, 7 Jun 2026 23:18:06 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20/top-sales=20=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3:=E9=94=80=E9=87=8F=E6=9C=80=E9=AB=98=E4=BB=8E?= =?UTF-8?q?=E7=A6=BB=E7=BA=BF=E5=BA=93=20meituan=5Fcoupon=20=E5=8F=96(?= =?UTF-8?q?=E4=B8=8D=E5=86=8D=E5=AE=9E=E6=97=B6=E6=89=93=E7=BE=8E=E5=9B=A2?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 「销量最高」改为从本地离线库按销量降序返回,替代实时打美团同城热销(外卖只有~20条还波动): - POST /api/v1/meituan/top-sales:WHERE sale_volume_num IS NOT NULL,按 sale_volume_num 降序、 同销量再按 commission_percent 降序;用 dedup_key 跨源去重;用 raw(整条原始返回)重建 CouponCard, 字段与实时接口完全一致,前端无需改渲染。 - schemas 加 TopSalesRequest(page / page_size / 可选 platform) 离线库由定时 ETL(scripts/pull_meituan_coupons.py)灌入(见本分支前序提交)。 Co-Authored-By: Claude Opus 4.8 --- app/api/v1/meituan.py | 41 ++++++++++++++++++++++++++++++++++++++++- app/schemas/meituan.py | 7 +++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/app/api/v1/meituan.py b/app/api/v1/meituan.py index 935755e..9d7510e 100644 --- a/app/api/v1/meituan.py +++ b/app/api/v1/meituan.py @@ -7,10 +7,14 @@ from __future__ import annotations import logging from concurrent.futures import ThreadPoolExecutor, as_completed -from fastapi import APIRouter, HTTPException +from fastapi import APIRouter, Depends, HTTPException +from sqlalchemy import select +from sqlalchemy.orm import Session from app.core.config import settings +from app.db.session import get_db from app.integrations.meituan import MeituanCpsError, get_referral_link, query_coupon +from app.models.meituan_coupon import MeituanCoupon from app.schemas.meituan import ( CouponCard, CouponListRequest, @@ -19,6 +23,7 @@ from app.schemas.meituan import ( FeedResponse, ReferralLinkRequest, ReferralLinkResponse, + TopSalesRequest, ) logger = logging.getLogger("shagua.meituan") @@ -162,3 +167,37 @@ def referral_link(req: ReferralLinkRequest) -> ReferralLinkResponse: link_map = raw.get("referralLinkMap") or {} link = raw.get("data") or link_map.get("1") or link_map.get("3") or next(iter(link_map.values()), "") return ReferralLinkResponse(link=link, link_map=link_map) + + +@router.post("/top-sales", response_model=CouponListResponse, + summary="销量最高(从离线库 meituan_coupon 按销量降序 + 跨源去重,不实时打美团)") +def top_sales(req: TopSalesRequest, db: Session = Depends(get_db)) -> CouponListResponse: + # 只取有销量档的(美团很多券没销量,排不了);按销量降序、同销量再按佣金降序 + stmt = select(MeituanCoupon).where(MeituanCoupon.sale_volume_num.isnot(None)) + if req.platform is not None: + stmt = stmt.where(MeituanCoupon.platform == req.platform) + stmt = stmt.order_by( + MeituanCoupon.sale_volume_num.desc(), + MeituanCoupon.commission_percent.desc(), + ) + rows = db.execute(stmt).scalars().all() + + # 跨源去重(dedup_key = 品牌|名|价):按销量降序遍历,每个 dedup_key 只留第一条(=销量最高那条)。 + # 用 raw(整条原始返回)重建 CouponCard,字段与实时接口完全一致,前端无需改渲染。 + seen: set[str] = set() + cards: list[CouponCard] = [] + for row in rows: + if row.dedup_key in seen: + continue + seen.add(row.dedup_key) + try: + card = CouponCard.from_raw(row.raw or {}) + except Exception: # noqa: BLE001 + continue + if card.product_view_sign: + cards.append(card) + + start = (req.page - 1) * req.page_size + page_items = cards[start:start + req.page_size] + has_next = start + req.page_size < len(cards) + return CouponListResponse(items=page_items, has_next=has_next, search_id=None) diff --git a/app/schemas/meituan.py b/app/schemas/meituan.py index db4d8f2..67cf36c 100644 --- a/app/schemas/meituan.py +++ b/app/schemas/meituan.py @@ -148,6 +148,13 @@ class FeedResponse(BaseModel): page: int = 1 +class TopSalesRequest(BaseModel): + """销量最高 tab:从离线库 meituan_coupon 按销量降序取(不实时打美团)。""" + page: int = Field(1, ge=1) + page_size: int = Field(20, ge=1, le=50) + platform: int | None = Field(None, description="可选: 1只外卖 / 2只到店; 不填=全部(全城销量)") + + # ───────────────── 换链 请求 / 响应 ───────────────── class ReferralLinkRequest(BaseModel):