feat(monitor): 给博主起备注 —— 作品栏里才认得出「这是谁」
Deploy VitePress site to Pages / build (push) Canceled after 0s
Deploy VitePress site to Pages / Deploy (push) Canceled after 0s

作品栏按 creator_hash 把作品归到博主的组里,可那是个哈希;creator_name 是平台昵称,
粉丝少的号常常认不出。两样都没法把账号对上人。

* 新表 monitor_creator_alias,键取 **(platform, creator_hash)** 而不是按任务:
  哈希对同一个 uid 是稳定的,所以同一个博主出现在多个任务里时,备注只填一次。
* list_notes 带上 creator_alias(整体查一次再在内存里取,不是每条作品查一次)。
* `PUT /monitor/creators/{creator_hash}`,空串即清掉那条备注。
* 作品栏的博主组头:**优先显示备注**,起过备注之后平台昵称降成副标题(它仍是有用的
  对照);组头上一个铅笔按钮就地编辑,回车保存、失焦保存、Esc 取消。

Esc 那条要单独处理:取消之后紧接着的失焦会把刚放弃的内容存进去,所以用一个标记让那次
失焦闭嘴。

测试 +6:没起过时是空串、起了会跟着作品返回、**跨任务共用一条**、**不串到别的平台**、
空串清掉、前后空格会被去掉。
This commit is contained in:
2026-10-10 18:00:13 +08:00
parent 5552e2a2b8
commit 0a88474c92
9 changed files with 341 additions and 12 deletions
+60
View File
@@ -34,6 +34,7 @@ from .models import (
MODE_CREATOR,
MODE_NOTE,
MonitorComment,
MonitorCreatorAlias,
MonitorEvent,
MonitorNote,
MonitorNoteMetric,
@@ -328,6 +329,48 @@ def trigger_manual_run(task_id: int) -> None:
# Dashboard queries
# ---------------------------------------------------------------------------
async def _creator_alias_map(session: AsyncSession) -> Dict[tuple, str]:
"""``(platform, creator_hash) -> 备注``。
整体读一次再在内存里取,而不是每条作品查一次 —— 作品列表动辄几十条。备注本身很少,
表不会大。
"""
rows = (await session.scalars(select(MonitorCreatorAlias))).all()
return {(row.platform, row.creator_hash): row.alias for row in rows if row.alias}
async def set_creator_alias(
session: AsyncSession, platform: str, creator_hash: str, alias: str
) -> None:
"""给博主起备注;传空串就是删掉这条备注(界面上的"清空")。"""
alias = (alias or "").strip()[:128]
existing = await session.scalar(
select(MonitorCreatorAlias).where(
MonitorCreatorAlias.platform == platform,
MonitorCreatorAlias.creator_hash == creator_hash,
)
)
if not alias:
if existing is not None:
await session.delete(existing)
return
if existing is None:
session.add(
MonitorCreatorAlias(
platform=platform,
creator_hash=creator_hash,
alias=alias,
updated_at=get_current_timestamp(),
)
)
return
existing.alias = alias
existing.updated_at = get_current_timestamp()
async def _latest_successful_run_id(session: AsyncSession, task_id: int) -> Optional[int]:
return await session.scalar(
select(MonitorRun.id)
@@ -387,6 +430,19 @@ async def list_notes(
latest_run_ids: Dict[int, Optional[int]] = {}
result: List[Dict[str, Any]] = []
# 博主的备注。键是 (platform, creator_hash) —— 作品的 platform 挂在它的任务上。
task_platform = {
row.id: row.platform
for row in (
await session.execute(
select(MonitorTask.id, MonitorTask.platform).where(
MonitorTask.id.in_({note.task_id for note in notes})
)
)
).all()
}
aliases = await _creator_alias_map(session)
for note in notes:
series = by_note.get(note.note_id, [])
current = series[0] if series else None
@@ -414,6 +470,10 @@ async def list_notes(
# (见 config.MASK_NICKNAME),所以就是原文。
"creator_hash": note.creator_hash,
"creator_name": note.creator_name,
# 人自己起的备注,界面上优先显示它 —— 昵称认不出是谁,哈希更认不出。
"creator_alias": aliases.get(
(task_platform.get(note.task_id, ""), note.creator_hash), ""
),
# 优先给本地缓存地址:远程地址带签名、会过期(实测隔天即 403),
# 本地那份不会。没有缓存时才退回远程,至少让图先显示出来。
"cover": covers.cover_url(note.note_id, note.cover),