fix(monitor): 没有作品的博主在作品栏里也要看得见
Deploy VitePress site to Pages / build (push) Canceled after 0s
Deploy VitePress site to Pages / Deploy (push) Canceled after 0s

分组是从作品推出来的(按作品的 creator_hash 归组),于是没有作品的博主根本
不进列表:目标加了、资料采到了、粉丝数就躺在库里,界面上什么都看不见。

而这类博主恰恰是最该看见的 —— 还在涨粉,只是最近没发东西。藏起来正好藏反了。
线上就有一个:5 个目标里 3 个没作品,那 3 个连同已采到的粉丝数一起消失了。

改成 **账号快照 ∪ 作品** 两个来源:

* 有快照没作品 → 一个 0 篇的组,备注和粉丝数照常显示,组里写「暂无作品」;
* 有作品没快照 → 一个没有账号指标的组(小红书那条路不产生快照)。

/notes 因此多返回一份 `creators`,而不是让前端从作品里推 —— 作品推不出上面
第一类人。组头改读它,`MonitorNote` 上那几个字段降级成「顺着作品问作者」用。
This commit is contained in:
2026-10-10 20:57:07 +08:00
parent 3c4daae0ac
commit e0581682e1
7 changed files with 342 additions and 44 deletions
+102
View File
@@ -218,3 +218,105 @@ class TestCreatorStats:
assert note["creator_works"] is None
# 快照本身是有的(有采集时间),只是值不知道 —— 前端要能分开这两件事。
assert note["creator_stats_at"] is not None
async def _seed_task(platform: str = "xhs", task_name: str = "空任务") -> int:
"""只有任务,**一条作品都没有**。"""
async with monitor_db.get_session() as session:
task = MonitorTask(
name=task_name, platform=platform, mode=MODE_CREATOR, enabled=True,
interval_minutes=60, max_notes_count=20, enable_comments=False,
max_comments_count=50, run_timeout_seconds=3600,
notify_enabled=False, created_at=0, updated_at=0,
)
session.add(task)
await session.flush()
return task.id
async def _creators(client, platform: str = "xhs"):
return (await client.get("/api/monitor/notes", params={"platform": platform})).json()["creators"]
class TestCreatorList:
"""作品栏要显示谁 —— **包括一条作品都没有的博主**。
组原先是从作品推出来的,于是「目标加了、资料采到了、粉丝数在库里,界面上什么都
没有」。而这类博主恰恰最该看见:还在涨粉,只是最近没发东西。
"""
@pytest.mark.asyncio
async def test_a_creator_with_no_works_still_shows_up(self, client):
"""**这条就是这个改动的全部理由。**"""
task_id = await _seed_task()
await _add_stat(task_id, run_id=1, fans=12000)
creators = await _creators(client)
assert len(creators) == 1
assert creators[0]["creator_hash"] == CREATOR_HASH
assert creators[0]["creator_name"] == NICKNAME # 只剩快照这一个来源
assert creators[0]["note_count"] == 0
assert creators[0]["creator_fans"] == 12000
@pytest.mark.asyncio
async def test_a_creator_with_works_carries_their_count(self, client):
task_id = await _seed()
await _add_stat(task_id, run_id=1, fans=12000)
creators = await _creators(client)
assert len(creators) == 1
assert creators[0]["note_count"] == 1
assert creators[0]["creator_name"] == NICKNAME
assert creators[0]["creator_fans"] == 12000
@pytest.mark.asyncio
async def test_a_creator_with_neither_works_nor_a_snapshot_is_absent(self, client):
"""两个来源都没有 = 我们对他一无所知,不该凭空造一个组出来。"""
await _seed_task()
assert await _creators(client) == []
@pytest.mark.asyncio
async def test_a_work_without_a_snapshot_still_lists_its_creator(self, client):
"""小红书那条路不产生账号快照 —— 那边只能靠作品认出人来。"""
await _seed()
creators = await _creators(client)
assert len(creators) == 1
assert creators[0]["note_count"] == 1
assert creators[0]["creator_fans"] is None
@pytest.mark.asyncio
async def test_the_creator_remark_comes_along(self, client):
"""没有作品的博主也要能起备注 —— 否则「这是谁」在最需要的时候认不出来。"""
task_id = await _seed_task()
await _add_stat(task_id, run_id=1, fans=12000)
await _set_alias(client, "竞品A")
assert (await _creators(client))[0]["creator_alias"] == "竞品A"
@pytest.mark.asyncio
async def test_platforms_stay_apart(self, client):
dy_task = await _seed_task(platform="dy")
await _add_stat(dy_task, run_id=1, fans=999)
assert await _creators(client, "xhs") == []
assert (await _creators(client, "dy"))[0]["creator_fans"] == 999
@pytest.mark.asyncio
async def test_the_same_creator_under_two_tasks_is_two_rows_the_ui_merges(self, client):
"""服务端按 任务×博主 给(快照就是那么存的),合并交给界面 —— 因为备注跨任务
是同一条,合并之后的组才是人眼里的「一个博主」。"""
first = await _seed(task_name="任务甲")
second = await _seed(task_name="任务乙")
await _add_stat(first, run_id=1, fans=12000)
creators = await _creators(client)
assert len(creators) == 2
assert {row["creator_hash"] for row in creators} == {CREATOR_HASH}
assert sum(row["note_count"] for row in creators) == 2