From 834a2c434ab2a4b961039c81872bb8f72424b950 Mon Sep 17 00:00:00 2001 From: chenshuobo <1119780489@qq.com> Date: Sun, 7 Jun 2026 21:52:43 +0800 Subject: [PATCH] =?UTF-8?q?=E9=A6=96=E9=A1=B5=20feed=20=E6=99=BA=E8=83=BD?= =?UTF-8?q?=E6=8E=A8=E8=8D=90/=E8=B7=9D=E7=A6=BB=E6=9C=80=E8=BF=91=20?= =?UTF-8?q?=E6=94=B9=E7=94=B1=E5=90=8E=E7=AB=AF=E7=AD=9B=E9=80=89=E6=8E=92?= =?UTF-8?q?=E5=BA=8F(=E6=8A=8A=E5=AE=A2=E6=88=B7=E7=AB=AF=20filter/sort=20?= =?UTF-8?q?=E4=B8=8B=E6=B2=89=E5=88=B0=E5=90=8E=E7=AB=AF)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mentor 指出筛选放前端不合理:前端只能筛"已加载的几十条",不是完整池子。改为 /feed 加 tab 参数, 后端按 tab 处理后返回,前端只渲染: - tab=rec(智能推荐):榜单混合 feed 过滤掉佣金率 < 3%(分页) - tab=distance(距离最近):拉齐全部榜单轮次,在【完整池】上全局按距离由近及远排,一次性返回(has_next=False) - 留空/其它:返回原混合 feed 不筛(老客户端兼容;新 app 会显式传 tab) (销量最高走 /coupons 同城热销,已是后端,不在本次) 实测北京:distance 拉到 118 条且全局升序;rec 第1页佣金率全部 ≥3%。 改动:app/schemas/meituan.py(FeedRequest.tab)、app/api/v1/meituan.py(feed 按 tab 筛选/排序 + _commission_pct)。 Co-Authored-By: Claude Opus 4.8 --- app/api/v1/meituan.py | 38 +++++++++++++++++++++++++++++++++++--- app/schemas/meituan.py | 6 ++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/app/api/v1/meituan.py b/app/api/v1/meituan.py index 7e3e2bc..935755e 100644 --- a/app/api/v1/meituan.py +++ b/app/api/v1/meituan.py @@ -79,13 +79,21 @@ def _interleave(waimai: list[dict], daodian: list[dict]) -> list[CouponCard]: return items -@router.post("/feed", response_model=FeedResponse, summary="混合feed(外卖+到店交叉, 无限流)") +def _commission_pct(card: CouponCard) -> float: + """'1.4%' → 1.4;解析失败按 0(会被智能推荐过滤掉)。""" + try: + return float(card.commission_rate.rstrip("%")) + except (ValueError, AttributeError): + return 0.0 + + +@router.post("/feed", response_model=FeedResponse, summary="混合feed(外卖+到店交叉);tab=rec智能推荐/distance距离最近") def feed(req: FeedRequest) -> FeedResponse: if not settings.mt_cps_configured: return FeedResponse(items=[], has_next=False, page=req.page) - page_idx = req.page - 1 lon, lat = req.longitude, req.latitude - logger.info("[feed] page=%s lon=%.6f lat=%.6f", req.page, lon, lat) + tab = (req.tab or "").strip() + logger.info("[feed] tab=%s page=%s lon=%.6f lat=%.6f", tab or "(default)", req.page, lon, lat) def _fetch_topic(platform: int, biz_line: int | None, topic: int) -> list[dict]: try: @@ -97,6 +105,28 @@ def feed(req: FeedRequest) -> FeedResponse: except MeituanCpsError: return [] + # 距离最近:拉齐全部榜单轮次,合并去重,后端在【完整池子】上全局按距离由近及远排,一次性返回。 + # (距离排序必须在完整池上做、不能逐页排——这正是之前放前端不合理的根因。) + if tab == "distance": + with ThreadPoolExecutor(max_workers=len(_TOPIC_ROUNDS) * 2) as pool: + futs = [] + for wm_topic, dd_topic in _TOPIC_ROUNDS: + futs.append(pool.submit(_fetch_topic, 1, None, wm_topic)) + futs.append(pool.submit(_fetch_topic, 2, 1, dd_topic)) + raws = [f.result() for f in futs] + seen: set[str] = set() + cards: list[CouponCard] = [] + for raw_list in raws: + for it in raw_list: + card = CouponCard.from_raw(it) + if card.product_view_sign and card.product_view_sign not in seen: + seen.add(card.product_view_sign) + cards.append(card) + cards.sort(key=lambda c: c.distance_meters if c.distance_meters is not None else float("inf")) + return FeedResponse(items=cards, has_next=False, page=1) + + # 智能推荐(rec,默认):沿用逐轮分页的混合 feed,后端过滤掉佣金率 < 3%。 + page_idx = req.page - 1 if page_idx >= len(_TOPIC_ROUNDS): return FeedResponse(items=[], has_next=False, page=req.page) @@ -107,6 +137,8 @@ def feed(req: FeedRequest) -> FeedResponse: waimai, daodian = f_wm.result(), f_dd.result() items = _interleave(waimai, daodian) + if tab == "rec": + items = [c for c in items if _commission_pct(c) >= 3.0] has_next = page_idx + 1 < len(_TOPIC_ROUNDS) return FeedResponse(items=items, has_next=has_next, page=req.page) diff --git a/app/schemas/meituan.py b/app/schemas/meituan.py index b8cf1ab..db4d8f2 100644 --- a/app/schemas/meituan.py +++ b/app/schemas/meituan.py @@ -134,6 +134,12 @@ class FeedRequest(BaseModel): latitude: float = Field(..., description="纬度") page: int = Field(1, ge=1) page_size: int = Field(20, ge=1, le=20) + # 筛选/排序口径,后端据此处理后返回(前端不再自己筛/排): + # rec = 智能推荐:榜单混合 feed 去掉佣金率 < 3%(分页) + # distance = 距离最近:拉齐全部轮次后全局按距离由近及远(一次性返回, has_next=False) + # 留空/其它 = 原混合 feed 不筛(老客户端兼容,新 app 会显式传 tab) + # (销量最高 sales 走 /coupons 同城热销,不在本接口) + tab: str = Field("", description="rec 智能推荐 / distance 距离最近 / 空=不筛(兼容)") class FeedResponse(BaseModel):