From 79d57da7694f4d757457b1229a0c3f3226b56638 Mon Sep 17 00:00:00 2001 From: stefanfeng Date: Wed, 8 Apr 2026 11:49:57 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BB=8A=E6=97=A5=E6=96=87=E7=AB=A0?= =?UTF-8?q?=E4=B8=8D=E8=B6=B3=E6=97=B6=E6=B7=B7=E5=85=A5=E5=8E=86=E5=8F=B2?= =?UTF-8?q?=E6=96=87=E7=AB=A0=EF=BC=8C=E9=81=BF=E5=85=8D=E6=89=80=E6=9C=89?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E6=89=8E=E5=A0=86=E5=90=8C=E4=B8=80=E7=AF=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题:今日只有1篇文章时,所有虚拟用户都只互动这同一篇 原因:Phase 1 找到今日文章后直接返回,不管数量多少 修复逻辑: - 今日有效文章 → 每用户最多取 1 篇(count//3,最少1篇) - 剩余名额(count - today_quota)从历史文章补充 - 历史文章:按当前小时对应页拉取(与Phase 2相同),随机打散 - 历史文章排除今日文章ID和当天发布的文章,保证内容不重复 - 最终返回:今日N篇 + 历史M篇,总量接近 count 效果:今日1篇文章时 → 用户取1篇今日 + 4篇历史,互动多样性恢复 --- backend/app/services/news_service.py | 101 ++++++++++++++++++++++----- 1 file changed, 85 insertions(+), 16 deletions(-) diff --git a/backend/app/services/news_service.py b/backend/app/services/news_service.py index bba85b0..bd1b835 100755 --- a/backend/app/services/news_service.py +++ b/backend/app/services/news_service.py @@ -508,7 +508,7 @@ class NewsPlatformService: logger.warning(f"[广场新闻-今日] {user.account} 请求异常: {e}") if today_articles: - # 今日文章:从新到旧排序(createTime 降序) + # 今日文章:从新到旧排序 today_articles.sort( key=lambda x: x.get("publishTime") or x.get("createTime") or x.get("pushTime") or "", reverse=True @@ -520,24 +520,93 @@ class NewsPlatformService: if aid and aid not in seen: seen.add(aid) unique.append(a) - logger.info( - f"[广场新闻] {user.account} 今日新文章 {len(unique)} 篇," - f"顺序轮询(最多取 {count} 篇)" - ) - candidates = unique[:max(count * 2, 20)] # 取候选池,稍多于 count 以备校验失败 - # 有效性校验后返回 - valid = [] - for a in candidates: - if len(valid) >= count: - break + # 有效性校验今日文章 + today_valid = [] + for a in unique: aid = str(a.get("recordId") or a.get("id", "")) if await self.validate_article(db, user, aid): - valid.append(a) - if valid and not force_history: - return valid - if not valid: - logger.info(f"[广场新闻] {user.account} 今日文章校验后全部无效,转历史") + today_valid.append(a) + + if today_valid: + # ── 混合策略:今日文章不足时,补充历史文章,避免所有用户扎堆同一篇 ── + # 每个用户最多分配 1 篇今日文章,其余名额从历史文章随机补充 + today_quota = min(len(today_valid), max(1, count // 3)) + today_pick = today_valid[:today_quota] + today_ids = {str(a.get("recordId") or a.get("id","")) for a in today_pick} + hist_needed = count - today_quota + + logger.info( + f"[广场新闻] {user.account} 今日有效 {len(today_valid)} 篇," + f"取 {today_quota} 篇今日 + {hist_needed} 篇历史" + ) + + hist_valid = [] + if hist_needed > 0: + import math as _math2 + # 拉历史页(与 Phase 2 相同的小时分页逻辑) + total_pages_h = 1 + page1_items_h = [] + try: + async with httpx.AsyncClient(timeout=10) as _hc: + _hr = await _hc.get( + f"{biz}/business/square/list", + headers=self._bearer(token), + params=_build(1), + ) + _hd = _hr.json() + if _hd.get("code") in [0, 200]: + _hnd = _hd.get("data", {}) + total_pages_h = max(1, _math2.ceil(_hnd.get("totalSize", 0) / 50)) + page1_items_h = _filter( + _hnd.get("data") or _hnd.get("list") or _hnd.get("records") or [] + ) + except Exception: + pass + + max_pages_h = min(total_pages_h, 10) + hour_page_h = (_dt.now().hour % max_pages_h) + 1 + hist_pool = page1_items_h if hour_page_h == 1 else [] + + if hour_page_h != 1: + try: + async with httpx.AsyncClient(timeout=12) as _hc2: + _hr2 = await _hc2.get( + f"{biz}/business/square/list", + headers=self._bearer(token), + params=_build(hour_page_h), + ) + if _hr2.status_code == 200: + _hd2 = _hr2.json() + if _hd2.get("code") in [0, 200]: + _hnd2 = _hd2.get("data", {}) + hist_pool = _filter( + _hnd2.get("data") or _hnd2.get("list") or _hnd2.get("records") or [] + ) + except Exception: + hist_pool = page1_items_h # fallback to page 1 + + # 排除今日文章,避免重复 + hist_pool = [x for x in hist_pool + if (x.get("recordId") or x.get("id","")) not in today_ids + and not _is_today(x)] + _rand.shuffle(hist_pool) + + for a in hist_pool: + if len(hist_valid) >= hist_needed: + break + aid = str(a.get("recordId") or a.get("id", "")) + if await self.validate_article(db, user, aid): + hist_valid.append(a) + + result = today_pick + hist_valid + logger.info( + f"[广场新闻] {user.account} 返回 {len(result)} 篇" + f"(今日 {len(today_pick)} + 历史 {len(hist_valid)})" + ) + return result + + logger.info(f"[广场新闻] {user.account} 今日文章校验后全部无效,转历史") # ── Phase 2: 无今日新文章 → 从最新(第1页)开始往旧顺序遍历 ──── # 规则:始终从第1页(最新)开始,按页顺序 1→2→3...往旧方向走