fix: 历史文章改为从新到旧顺序翻页,而非随机
修复原因: - 需求要求从最新文章开始往旧的方向依次互动 - 原实现随机选页,导致每次跳到不同页,无法体现「从新到旧」 - 列表API返回的文章已按 createTime 降序排列(第1页最新) 新Phase 2逻辑: - 计算总页数(最多10页) - 用当前小时 % 总页数 决定起始页(同小时内分散到不同页) - 若起始页为空,顺序往后再折回,直到找到有文章的页 - 在同一页内随机打散,保证同一时段不同用户不总是抢相同文章 - validate_article 校验,不够则从剩余补充 Phase 1(今日新文章)逻辑不变: - 仍从第1页抓取当天文章,按 createTime 降序(新→旧)排列后校验返回
This commit is contained in:
@@ -488,8 +488,12 @@ class NewsPlatformService:
|
|||||||
return valid
|
return valid
|
||||||
logger.info(f"[广场新闻] {user.account} 今日文章校验后全部无效,转历史")
|
logger.info(f"[广场新闻] {user.account} 今日文章校验后全部无效,转历史")
|
||||||
|
|
||||||
# ── Phase 2: 无今日新文章 → 历史随机翻页(热度+新鲜度加权)────────
|
# ── Phase 2: 无今日新文章 → 从最新到旧顺序翻页(随机从某页开始)────
|
||||||
logger.info(f"[广场新闻] {user.account} 无今日新文章,随机历史翻页")
|
# 规则:从第1页(最新)开始依次往后,轮到哪页由当前小时决定,
|
||||||
|
# 保证不同时段覆盖不同页,模拟"从新到旧"逐步互动
|
||||||
|
logger.info(f"[广场新闻] {user.account} 无今日新文章,从最新向旧顺序翻页")
|
||||||
|
|
||||||
|
# 获取总页数
|
||||||
total_pages = 1
|
total_pages = 1
|
||||||
try:
|
try:
|
||||||
async with httpx.AsyncClient(timeout=10) as _c:
|
async with httpx.AsyncClient(timeout=10) as _c:
|
||||||
@@ -505,58 +509,48 @@ class NewsPlatformService:
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
rand_page = _rand.randint(1, min(total_pages, 10))
|
max_pages = min(total_pages, 10) # 最多翻前10页
|
||||||
items = []
|
# 用小时数取模决定起始页,保证同一小时内不同用户分散在不同页
|
||||||
try:
|
# 从最新(page=1)开始往旧的方向走
|
||||||
async with httpx.AsyncClient(timeout=15) as c:
|
hour_slot = _dt.now().hour % max_pages
|
||||||
r = await c.get(
|
start_page = hour_slot + 1 # 1-based
|
||||||
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}")
|
|
||||||
|
|
||||||
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:
|
if not items:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# 热度 + 新鲜度加权采样
|
# 在该页内随机打散(同一页内不需要排序,保持自然顺序即可)
|
||||||
def _hot_weight(a):
|
_rand.shuffle(items)
|
||||||
hot = (int(a.get("commentNum") or 0) * 3 +
|
selected = items[:min(count * 2, len(items))]
|
||||||
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)
|
|
||||||
|
|
||||||
# 有效性校验
|
# 有效性校验
|
||||||
valid = []
|
valid = []
|
||||||
remaining = [i for i in pool_idx]
|
|
||||||
for a in selected:
|
for a in selected:
|
||||||
if len(valid) >= count:
|
if len(valid) >= count:
|
||||||
break
|
break
|
||||||
@@ -564,11 +558,10 @@ class NewsPlatformService:
|
|||||||
if await self.validate_article(db, user, aid):
|
if await self.validate_article(db, user, aid):
|
||||||
valid.append(a)
|
valid.append(a)
|
||||||
|
|
||||||
# 不够则从剩余池补充
|
# 不够则从剩余补充
|
||||||
for ri in remaining:
|
for a in items[len(selected):]:
|
||||||
if len(valid) >= count:
|
if len(valid) >= count:
|
||||||
break
|
break
|
||||||
a = items[ri]
|
|
||||||
aid = str(a.get("recordId") or a.get("id", ""))
|
aid = str(a.get("recordId") or a.get("id", ""))
|
||||||
if await self.validate_article(db, user, aid):
|
if await self.validate_article(db, user, aid):
|
||||||
valid.append(a)
|
valid.append(a)
|
||||||
|
|||||||
Reference in New Issue
Block a user