From 169e7987188acb59a165d2fd8730d6f03ea7a856 Mon Sep 17 00:00:00 2001 From: stefanfeng Date: Tue, 7 Apr 2026 13:24:42 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=8E=86=E5=8F=B2=E6=96=87=E7=AB=A0?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=E4=BB=8E=E6=96=B0=E5=88=B0=E6=97=A7=E9=A1=BA?= =?UTF-8?q?=E5=BA=8F=E7=BF=BB=E9=A1=B5=EF=BC=8C=E8=80=8C=E9=9D=9E=E9=9A=8F?= =?UTF-8?q?=E6=9C=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复原因: - 需求要求从最新文章开始往旧的方向依次互动 - 原实现随机选页,导致每次跳到不同页,无法体现「从新到旧」 - 列表API返回的文章已按 createTime 降序排列(第1页最新) 新Phase 2逻辑: - 计算总页数(最多10页) - 用当前小时 % 总页数 决定起始页(同小时内分散到不同页) - 若起始页为空,顺序往后再折回,直到找到有文章的页 - 在同一页内随机打散,保证同一时段不同用户不总是抢相同文章 - validate_article 校验,不够则从剩余补充 Phase 1(今日新文章)逻辑不变: - 仍从第1页抓取当天文章,按 createTime 降序(新→旧)排列后校验返回 --- backend/app/services/news_service.py | 91 +++++++++++++--------------- 1 file changed, 42 insertions(+), 49 deletions(-) diff --git a/backend/app/services/news_service.py b/backend/app/services/news_service.py index f815007..9bb1027 100755 --- a/backend/app/services/news_service.py +++ b/backend/app/services/news_service.py @@ -488,8 +488,12 @@ class NewsPlatformService: return valid logger.info(f"[广场新闻] {user.account} 今日文章校验后全部无效,转历史") - # ── Phase 2: 无今日新文章 → 历史随机翻页(热度+新鲜度加权)──────── - logger.info(f"[广场新闻] {user.account} 无今日新文章,随机历史翻页") + # ── Phase 2: 无今日新文章 → 从最新到旧顺序翻页(随机从某页开始)──── + # 规则:从第1页(最新)开始依次往后,轮到哪页由当前小时决定, + # 保证不同时段覆盖不同页,模拟"从新到旧"逐步互动 + logger.info(f"[广场新闻] {user.account} 无今日新文章,从最新向旧顺序翻页") + + # 获取总页数 total_pages = 1 try: async with httpx.AsyncClient(timeout=10) as _c: @@ -505,58 +509,48 @@ class NewsPlatformService: except Exception: pass - rand_page = _rand.randint(1, min(total_pages, 10)) - items = [] - try: - async with httpx.AsyncClient(timeout=15) as c: - r = await c.get( - f"{biz}/business/member/square/list", - headers=self._bearer(token), - params=_build(rand_page), - ) - if r.status_code == 200: - d = r.json() - if d.get("code") in [0, 200]: - nd = d.get("data", {}) - items = nd.get("data") or nd.get("list") or nd.get("records") or [] - items = _filter(items) - except Exception as e: - logger.error(f"[广场新闻-历史] {user.account}: {e}") + max_pages = min(total_pages, 10) # 最多翻前10页 + # 用小时数取模决定起始页,保证同一小时内不同用户分散在不同页 + # 从最新(page=1)开始往旧的方向走 + hour_slot = _dt.now().hour % max_pages + start_page = hour_slot + 1 # 1-based - logger.info(f"[广场新闻] {user.account} 历史第{rand_page}页获取到 {len(items)} 条") + items = [] + # 尝试从 start_page 开始,若该页为空则顺序往后再往前找 + pages_to_try = list(range(start_page, max_pages + 1)) + list(range(1, start_page)) + tried_page = start_page + for page in pages_to_try: + try: + async with httpx.AsyncClient(timeout=15) as c: + r = await c.get( + f"{biz}/business/member/square/list", + headers=self._bearer(token), + params=_build(page), + ) + if r.status_code == 200: + d = r.json() + if d.get("code") in [0, 200]: + nd = d.get("data", {}) + _items = nd.get("data") or nd.get("list") or nd.get("records") or [] + _items = _filter(_items) + if _items: + items = _items + tried_page = page + break + except Exception as e: + logger.error(f"[广场新闻-历史] {user.account} page={page}: {e}") + + logger.info(f"[广场新闻] {user.account} 历史第{tried_page}页获取到 {len(items)} 条") if not items: return [] - # 热度 + 新鲜度加权采样 - def _hot_weight(a): - hot = (int(a.get("commentNum") or 0) * 3 + - int(a.get("praiseNum") or 0) * 2 + - int(a.get("readNum") or 0)) - freshness = 1.0 - t = a.get("createTime") or a.get("publishTime") or "" - if t: - try: - pub = _dt.strptime(t[:19], "%Y-%m-%d %H:%M:%S") - h = (_dt.now() - pub).total_seconds() / 3600 - freshness = max(1.0, 3.0 - h / 36.0) - except Exception: - pass - return max(1.0, (hot + 1) * freshness) - - weights = [_hot_weight(a) for a in items] - pool_idx = list(range(len(items))) - selected = [] - for _ in range(min(count * 2, len(items))): - if not pool_idx: - break - ci = _rand.choices(pool_idx, weights=[weights[i] for i in pool_idx], k=1)[0] - selected.append(items[ci]) - pool_idx.remove(ci) + # 在该页内随机打散(同一页内不需要排序,保持自然顺序即可) + _rand.shuffle(items) + selected = items[:min(count * 2, len(items))] # 有效性校验 valid = [] - remaining = [i for i in pool_idx] for a in selected: if len(valid) >= count: break @@ -564,11 +558,10 @@ class NewsPlatformService: if await self.validate_article(db, user, aid): valid.append(a) - # 不够则从剩余池补充 - for ri in remaining: + # 不够则从剩余补充 + for a in items[len(selected):]: if len(valid) >= count: break - a = items[ri] aid = str(a.get("recordId") or a.get("id", "")) if await self.validate_article(db, user, aid): valid.append(a)