- 删除 docs/plan 下 00–18 的计划、评审、验收、发布记录与升级证据,docs/spikes 与 spikes 验证代码
(保留 spikes/movieclaw-src 本地克隆),以及 4 份一次性审查 / 调研;全部可在 ceb66f8 查到
- MP 对照移到 docs/porting/moviepilot-v3-comparison.md;经验教训改为 docs/plan/lessons.md(补第 25 条)
- docs/plan/README.md:文档放哪、当前状态、待办
- 引用改写:代码注释指向在维护的文档,其余注明已删除与 git 位置;清掉原有 6 个失效链接
- README 开头改为当前状态与入口;CHANGELOG 补齐 0.3.8–0.3.14 与工程变更
Co-Authored-By: Claude Opus 5.5 <[email protected]>
21 KiB
站点中心与仪表盘调整(v0.2.1)
依据:
00-first-version-plan(已删除,见 gitceb66f8) §8(用户 2026-09-28 上午)。状态:待审阅。只读调研 + 规格,不含代码改动。 参考:MoviePilot v3(/opt/moviepilot-plus/upstream)、MovieClaw v0.27.0(spikes/movieclaw-src/repo)。
1. 用户要求(原文要点)
- 资源 → 站点页 = 添加站点 + 凭证管理(账密 / Cookie / API Key,馒头 API Key)+ 卡片/列表(上次登录时间、上传/下载、分享率、用户等级、VIP、账号状态,数据来自
sites/user-parsers的refreshSiteUserData)+ 手动"检测健康"与"用凭证刷新登录状态"。 - 账密登录走 NexusPHP takelogin;验证码 / 二次验证失败 → 待处理,提示改用 Cookie。
- 设置里只留全局默认(限流、快照周期)。
- 仪表盘保留 REQ 六区块,补 MP 有价值的区块(媒体库统计、存储用量、下载器实时状态、最近入库、定时任务状态),砍重复。
2. 参考实现
2.1 MoviePilot v3
| 方面 | 做法 | 位置 |
|---|---|---|
| 卡片 | 名称、URL、左侧状态色条(sitestatistic.lst_state 成功/失败 + seconds 耗时,≥5s 黄)、徽标(限流 / 代理 / 浏览器仿真 / 过滤)、上传 / 下载量;按钮:测试连通、用户数据、更新 Cookie;菜单:编辑、删除;可拖拽排序(优先级) |
components/cards/SiteCard.vue、views/site/SiteCardListView.vue |
| 添加 / 编辑 | 名称、URL、优先级 pri、RSS 地址、Cookie、API Key、Token、UA、代理开关、浏览器仿真、超时、限流(limit_interval 秒内 limit_count 次、limit_seconds 间隔)、下载器、启用 |
dialog/SiteAddEditDialog.vue、db/models/site.py |
| 更新 Cookie | 对话框填用户名 / 密码 / 二步验证码(或 TOTP 密钥)→ POST site/cookie/{id};后端 CookieHelper 用 Playwright 浏览器打开登录页、OCR 识别验证码、按 two_step_code/otp 输入框填 TOTP,拿到 Cookie + UA 写回站点。密码不保存 |
chain/site.py update_cookie、application/security/cookie.py |
| 测试连通 | GET 首页,SiteUtils.is_logged_in 判登录;Cloudflare 挑战单独提示;结果与耗时写 sitestatistic(success/fail/seconds/lst_state/lst_mod_date) |
chain/site.py test/__test |
| 用户数据 | siteuserdata 按天一行:username、userid、user_level、join_at、bonus、upload、download、ratio、seeding、leeching、seeding_size、leeching_size、seeding_info、message_unread(+内容)、err_msg、updated_day/time;对话框看历史曲线 |
db/models/siteuserdata.py、dialog/SiteUserDataDialog.vue |
| 定时 | sitedata_refresh「站点数据刷新」,间隔 sitedata_refresh_interval |
scheduler/catalog.py |
| Cookie 来源 | 手填、账密换取、CookieCloud 同步(设定 → 站点:地址 / 密钥 / 间隔 / 黑名单) | chain/site.py sync_cookies |
2.2 MovieClaw v0.27.0
- 认证方式三种:
cookie/apikey/credential(用户名 + 密码,程序模拟登录并保存)。CredentialAuthProvider:GETlogin.php→ 正则找imagecode.php?...验证码(无识别器 →NEEDS_CAPTCHA返回图片地址)→ 带隐藏字段、imagehash、extra_form_dataPOST 表单 →success_css(a[href*='logout'])判成功,error_css(td.text)取失败原因,验证码错最多重试。 - 账号状态
pending / verifying / active / failed,附last_verified_at、last_checked_at、last_error(归类后的中文);启动时把卡在 verifying 的复位。保存凭证即"保存并验证",卡片有"重新验证"。 - 验证成功时顺手抓
profile:用户名、等级、vip_group(定义里vip_css命中即 VIP)、上传 / 下载字节、分享率(null 与 0 区分)、魔力(无则不显)、做种 / 下载数、fetched_at。 - 卡片另有同步统计(资源数、上次同步 / 成功、下次同步、连续失败、上次错误)和保护 / 刷流开关(不在本期范围)。
3. 现状与缺口
| 项 | 现状 | 缺口 |
|---|---|---|
| 入口 | 站点工作台卡片(启停、健康、账号摘要、快照);详情"账号"页签;设置 #sites 另有站点列表 + 登记抽屉 + 凭证表单(components/settings/sites.tsx,625 行) |
仍是两处可增删改,违背 IA"一个对象一个家" |
| 凭证 | PUT /sites/{id}/credentials(cookie/passkey/api_key/password + username)与 /site-accounts CRUD 并存;密码只存不用 |
无账密登录实现;无 UA 字段;无 TOTP |
| 用户数据 | refreshSiteUserData 与 20 个解析器已移植,服务端没有调用点;site_account.user_info 只存 username |
无数据、无历史、无 VIP 字段 |
| 健康 | adapter 有 healthCheck()(首页 + loginSuccess),无路由;健康只在快照 / Feed 出错时被动更新 |
无手动检测、无检测时间与耗时 |
| 登录态 | site_account.status = unknown/active/invalid/expired,last_verified_at 只在失败时写 |
没有"需验证"态、没有上次登录时间 |
| 删除 | 无删除站点 / 账号接口 | 需要 |
| 全局默认 | settings 无站点默认项;snapshotIntervalSec=null 时不做定时快照 |
需要 sites.* 默认 |
站点定义:23 个里 22 个有 umt.login(path/usernameField/passwordField/errorCss/extraFormData/selectDefaults),馒头只支持 apikey;chdbits、ssd 的 umt.profile.vip_css 已有。
4. 站点页规格(/console/sites,唯一入口)
4.1 页面
- 页头:标题"站点"、视图切换(卡片 / 列表,写 URL
?view=)、刷新、"添加站点"。筛选:全部 / 已启用 / 需处理(账号非正常或健康 failing)。 - 卡片与列表同字段,列表可按分享率、上传、上次登录排序。点名称进详情(整理视图 / 原站视图 / 快照与 Feed / 账号 4 页签不变,"账号"页签改为本页抽屉的完整版,含用户数据历史)。
4.2 卡片字段
| 字段 | 来源 | 显示规则 |
|---|---|---|
| 名称、域名 | site.name、site.base_url 主机名 |
域名可点,新窗口开原站 |
| 启用 | site.enabled |
开关 |
| 账号状态 | site_account.status |
正常(active)/ 已失效(expired、invalid)/ 需验证(challenge)/ 未验证(unknown)/ 未配置(无账号);多账号取最差 |
| 凭证类型 | auth_kind |
Cookie / API Key / 账号密码;另有 passkey 时加"Passkey"小标 |
| 上次登录 | last_login_at |
相对时间;从未成功显示"—" |
| 上传 / 下载 / 分享率 | user_data.upload/download/ratio |
字节格式化;分享率 null 显示"—",download=0 显示"∞" |
| 等级、VIP | user_data.userLevel、user_data.vip |
VIP 为 true 才显徽标 |
| 做种数(体积) | seeding、seedingSize |
|
| 魔力 | bonus |
0 且站点不提供时不显 |
| 未读消息 | messageUnread |
>0 才显 |
| 健康检测 | site.last_health + last_health_at |
"正常 · 320 ms · 5 分钟前" / 失败原因一句 |
| 快照 | last_snapshot_at、周期 |
过期标黄(沿用 snapshotStale) |
| 限流 | site.rate_limit,空则"默认" |
例:"20 次/分" |
用户数据时间戳(user_data_at)显示在数据区角落:"数据 2 小时前"。
4.3 操作
卡片主按钮:检测健康、刷新登录;"更多"菜单:更新 Cookie / 更新 API Key / 更新密码(按凭证类型显示一项)、编辑、快照、停用 / 启用、删除。
| 操作 | 行为 | 端点 |
|---|---|---|
| 添加站点 | 三步对话框:① 选定义(搜索框 + 列表,已登记的置灰;"未适配站点"只能 generic_rss)② 域名覆盖(默认定义域名)+ 凭证(按定义 authSupported 列出可选类型)+ 可选 UA ③ "测试"=保存后立即刷新登录,显示结果(成功带用户名 / 等级),失败可返回修改;"保存"关闭 |
POST /sites → PUT /sites/{id}/credentials → POST /sites/{id}/refresh-login |
| 编辑 | 抽屉:名称、域名、限流(空 = 全局默认)、定时快照开关与周期、UA、使用代理 | PATCH /sites/{id} |
| 检测健康 | 首页连通 + 登录态,同步返回(≤20 s,走站点限流) | POST /sites/{id}/health-check |
| 刷新登录 | 账密:重新登录拿 Cookie;Cookie / API Key:校验;成功后拉用户数据。异步任务,返回 taskId,卡片转圈直到任务结束 | POST /sites/{id}/refresh-login |
| 更新凭证 | 小对话框,只写,不回显;保存即触发刷新登录 | PUT /sites/{id}/credentials |
| 删除 | 确认框写明后果:"删除站点、账号和凭证;已抓到的资源和下载任务保留。不可恢复" | DELETE /sites/{id} |
4.4 账密登录(NexusPHP takelogin)
适配器新增 packages/adapters/src/sites/login/nexusphp.ts,输入定义的 umt.login + 用户名 / 密码 / 可选 TOTP 密钥,输出 { ok, cookie, userAgent } 或 SiteLoginError{ reason }。流程对齐 MovieClaw CredentialAuthProvider:
- 用固定 UA 清空 Cookie GET
login.php(umt.login.path),记下 Set-Cookie。 - 页面匹配
imagecode.php?…→ 验证码。本期不做 OCR:直接失败,reason=captcha。 - 取登录表单(
form[action*=takelogin],缺省takelogin.php)的隐藏字段,填usernameField/passwordField、selectDefaults、extraFormData;有 TOTP 密钥且表单有two_step_code/otp字段时填当前 6 位码。 - POST(不跟随 3xx),再 GET
index.php;loginSuccess命中 → 成功,拼 Cookie。 - 失败分支:
errorCss文本归类 —— 账号或密码错(bad_credentials)、需要二次验证而无 TOTP(two_factor)、验证码(captcha)、账号封禁 / 未确认(account_blocked)、Cloudflare 挑战(challenge)、其它(unknown,原文截 200 字)。
服务端处理:
- 成功:派生 Cookie 存入
session_secret_ref(purposesite_cookie,labelsite_account:<label>:session),status=active,last_login_at=now;之后请求优先用会话 Cookie,遇auth_expired自动重登一次(同账号 10 分钟内最多一次),仍失败再走现有onAuthError。 captcha / two_factor / challenge:status=challenge,开待处理(kind 复用source_unreachable,dedupeKey=site-login:<accountId>),标题"站点「X」登录需要人工验证",摘要"登录页要求验证码 / 二次验证,无法自动登录。改用 Cookie 登录,或填写 TOTP 密钥后重试",选项:改用 Cookie(深链站点页并打开凭证对话框)、重试登录、忽略。bad_credentials / account_blocked:status=invalid,同一待处理,选项去掉"重试登录"。- 表单里提供"只换 Cookie,不保存密码"(MP 做法):登录成功后凭证类型直接落为 Cookie,密码丢弃。
- 馒头及
authSupported不含 password 的站点,对话框不显示账号密码。
4.5 用户数据
- 调用
refreshSiteUserData({ name, url, schema, cookie|apikey, limiter, fetchMessages:false }),做种分页上限由全局sites.userDataMaxSeedingPages(缺省 10;MP 为 50)控制,请求全部过站点令牌桶。 - VIP:
SiteUserInfo增加vip: boolean | null;定义有umt.profile.vip_css时在首页 / 详情页判定,否则按新可选字段umt.vipLevels(等级名列表)匹配,都没有为 null(不显示)。 errMsg非空或userId为空:视为登录态失效,走 4.4 失败处理;数据不覆盖上次成功值。- 首页已显示登录态时顺手更新
last_login_at(Cookie / API Key 校验成功也算"登录")。
4.6 状态机(site_account.status)
unknown →(刷新登录成功)active →(请求遇 auth_expired 且重登失败)expired;任意态 →(验证码 / 二次验证 / 挑战)challenge;→(密码错 / 封禁)invalid;更新凭证 → unknown 并关闭 site-auth:* 与 site-login:* 待处理(现有逻辑扩展)。
5. 数据模型(迁移 0031_site_center.ts)
site_account(沿用 auth_kind 作为凭证类型,不改名;status 即登录状态):
| 列 | 类型 | 说明 |
|---|---|---|
status |
检查约束加 challenge |
contracts SiteAccountStatus 同步 |
login_name |
text null | 账密登录名;从 user_info.username 迁出 |
totp_secret_ref |
uuid null | 可选 TOTP 密钥(purpose 新增 site_totp) |
session_secret_ref |
uuid null | 账密登录得到的会话 Cookie |
user_agent |
text null | 空 = 全局默认 UA;Cookie 与 UA 需成对时填 |
last_login_at |
timestamptz null | 最近一次登录 / 校验成功 |
last_error |
jsonb null | {reason, message, at} |
user_data |
jsonb not null default {} |
最近一次成功的 SiteUserInfo 去掉 raw、messageUnreadContents、seedingInfo |
user_data_at |
timestamptz null |
user_info 保留只读兼容一版,迁移把 username 写入 login_name。
新表 site_user_data(按天一行,MP siteuserdata 对应):id, site_account_id → site_account on delete cascade, day date, upload bigint, download bigint, ratio numeric null, bonus numeric null, seeding int, seeding_size bigint, leeching int, user_level text null, vip boolean null, captured_at timestamptz,唯一 (site_account_id, day),同日 upsert 取最新;保留 400 天(并入现有日志保留清理)。
site:加 last_health jsonb null({ok, status, latencyMs, message})、last_health_at timestamptz null、proxy boolean not null default false。
contracts:SiteAccount 增加上表字段(secret ref 不外露,只给 hasTotp、hasSession 布尔);Site 增加 lastHealth、lastHealthAt、proxy;新增 SiteUserData、SiteHealthResult、SiteCredentialsPut 改为按 kind 的判别联合。
6. 后端端点
| 方法 路径 | scope | 说明 |
|---|---|---|
GET /sites?include=accounts |
read | 列表附带账号摘要(状态、凭证类型、last_login_at、user_data),站点页一次请求 |
POST /sites / PATCH /sites/{id} |
admin | 现有;PATCH 加 proxy |
DELETE /sites/{id} |
admin | 删站点、账号、secret;source_release 保留(外键改 set null 或保留 site_id 只读,实现时确认) |
POST /sites/{id}/health-check |
admin | 同步,返回 SiteHealthResult 并写 site.last_health*、health_status |
POST /sites/{id}/refresh-login |
admin | body {accountId?};入队引擎任务 refreshSiteAccount,返回 {taskId};幂等键按 10 s 取整 |
GET /sites/{id}/user-data |
read | ?accountId&days=30,返回 site_user_data 序列 |
PUT /sites/{id}/credentials |
admin | body 判别联合:{kind:"password", label, username, password, totpSecret?, saveCookieOnly?} / {kind:"cookie", label, cookie, userAgent?} / {kind:"api_key", label, apiKey};passkey 仍用 {kind:"passkey", passkey} 附加到同账号(RSS / 下载用,不参与登录) |
DELETE /site-accounts/{id} |
admin | 删账号与其 secret |
/site-accounts 的 POST / PATCH(凭证部分)标弃用,前端全部改走 PUT /sites/{id}/credentials;Agent 能力表(capabilities.ts)登记 sites.healthCheck、sites.refreshLogin 两个只读风险低的能力。
7. 定时任务
在 sources-identity/index.ts 用现有 job() 登记:
| key | 名称 | 分组 | 默认 | 行为 |
|---|---|---|---|---|
sites.userData |
站点数据刷新 | collect | 每 6 小时,firstRun: after_interval |
对启用站点的启用账号依次入队 refreshSiteAccount(不重登,只有遇失效才重登);跳过 challenge/invalid 账号;只读对账模式跳过 |
另:sites.snapshot 对 snapshotIntervalSec=null 的站点改用全局默认周期。
8. 设置页"站点与账号"改为"站点默认"
SETTINGS_SECTIONS.sites:label 改"站点默认",description"新站点的限流、快照周期、UA 和代理;站点和凭证在站点页管理",resources: [],fields如下(新存储 keysites,contractsSitesSettings):
| 字段 | 缺省 | 说明 |
|---|---|---|
sites.defaultRateLimit |
{requestsPerMinute: 20, concurrency: 1} |
站点 rate_limit 为空时使用 |
sites.defaultSnapshotIntervalSec |
3600 | 站点未设周期时使用 |
sites.userAgent |
固定桌面 Chrome UA | 账号未设 UA 时使用 |
sites.proxyUrl |
空 | 站点开"使用代理"时生效 |
sites.userDataMaxSeedingPages |
10 | 用户数据做种分页上限 |
- 删除
components/settings/sites.tsx里的站点列表、登记抽屉、凭证表单、账号列表;节顶部放一行"站点和凭证在「站点」页管理"+ 跳转链接。 - 迁移:
legacyTabs保留site-credentials,但落点改为/console/sites;settingsPath("sites", "accounts")的调用点全部改为consolePath("sites");冒烟REDIRECT_ROUTES补一条。 - 数据迁移:无(旧设置里没有站点默认值;现有站点的
rate_limit保留)。 console-ia.md§6 设置分区表与 §6.3"站点三处合一"行在实施后更新。
9. 文案(按 glossary)
- 页名"站点"(侧栏"站点工作台"改名);凭证统称"凭证";不写"PT 源""认证信息"。
- 按钮:添加站点、检测健康、刷新登录、更新 Cookie、更新 API Key、更新密码、删除站点。
- 空态:"还没有站点。添加站点并填写凭证后,这里显示账号数据和健康状态"。
- 错误示例:"登录失败:站点要求验证码。改用 Cookie,或稍后在站点页重试"、"Cookie 已失效。更新 Cookie 后重新刷新登录"。
- 账密说明只陈述事实:"密码加密保存,用于 Cookie 失效后自动重新登录。选'只换 Cookie'则不保存密码"。
10. 仪表盘调整
MP 仪表盘组件与数据源:媒体统计(dashboard/statistic:电影 / 剧集 / 集 / 音乐数)、存储(dashboard/storage:总 / 已用)、下载速度(dashboard/downloader:上下行速度、累计、剩余空间)、后台服务(dashboard/schedule + 整理队列)、最近入库(history/transfer)、周整理量(dashboard/transfer)、媒体服务器最新 / 在播 / 媒体库、CPU / 内存 / 网络 / 进程 / 系统信息、快捷操作。
| 区块 | 处理 | 数据来源 | 位置 |
|---|---|---|---|
| 自动化概览 | 保留;加"媒体库"一行:电影 / 剧集 / 集数(MP 媒体统计并入) | GET /dashboard 新增 library{movies, series, episodes, assets}(按 media_entity、content_unit、media_asset 计数) |
第一行左 |
| 当前工作 | 保留;页头加下载器实时速度 ↓/↑ 与活动数(MP 下载速度并入) | /dashboard 新增 downloaders[]{id, name, dlSpeed, upSpeed, freeBytes},来自 qB transferInfo(),服务端 5 s 缓存 |
第一行中 |
| 待处理 | 保留 | 现有 | 第一行右 |
| 缓存水位 → 存储 | 合并:下载缓存卷 + 库根所在卷(MP 存储) | /dashboard.cache 保留;新增 storage[]{rootId, label, totalBytes, usedBytes}(库根 statfs,按设备去重) |
第二行左 |
| 来源健康 | 保留;站点行加账号状态点与分享率,需处理的排前 | /dashboard.sources.sites 增加 accountStatus、ratio、lastLoginAt |
第二行中 |
| 最近产出(= MP 最近入库) | 保留,加海报缩略图 | 现有 recentOutput 加 posterPath |
第二行右 |
| 定时任务 | 新增小块:失败的任务在前,其余显示下次运行 | 前端直接用 GET /schedules(已有) |
第三行,整宽窄条 |
不做:CPU / 内存 / 网络 / 进程 / 系统信息(与媒体流程无关)、媒体服务器在播与最新(v0.3 再评估)、周整理量图(自动化概览已有今日计数)、快捷操作(侧栏与 Agent 已覆盖)、站点统计单独区块(并入来源健康)。
11. 实施拆分
| 路 | 范围 | 主要文件 | 估算 |
|---|---|---|---|
| 后端 B | 迁移 0031;contracts(sources.ts、instances.ts 凭证联合、settings.ts 的 SitesSettings 与分区、organization.ts Dashboard 扩展);NexusPHP 登录模块 + fixture 测试(成功 / 密码错 / 验证码 / 二次验证 / TOTP);SiteUserInfo.vip 与 umt.vipLevels schema;refreshSiteAccount 引擎任务、会话 Cookie 优先与自动重登;4.3 / 6 节端点;sites.userData 定时任务;默认值接入快照与限流;dashboard 聚合(库计数、qB 速度、库根 statfs);Agent 能力登记;vertical 测试补一条"添加站点 → 刷新登录 → 用户数据入库" |
packages/db/migrations/0031_site_center.ts、packages/db/src/types.ts、packages/contracts/src/{sources,instances,settings,organization,api}.ts、packages/adapters/src/sites/{login/*,user-parsers/types.ts,definitions/schema.ts}、apps/server/src/modules/sources-identity/{routes/sources.ts,service/account.ts(新),tasks.ts,index.ts,capabilities.ts}、auth-settings/instances-routes.ts(凭证 PUT)、acquisition-organize/routes/dashboard.ts |
3–3.5 天 |
| 前端 F | 站点页重写(卡片 / 列表切换、筛选、字段、操作菜单);添加站点三步对话框(替换 site-register-dialog.tsx);凭证对话框(三种 kind + 只换 Cookie + TOTP);编辑抽屉;详情"账号"页签加用户数据折线(近 30 天上传 / 分享率);设置分区改"站点默认"并删旧表单;settingsPath("sites") 调用点与重定向;首页区块调整;侧栏改名;冒烟与 nav 测试 |
routes/console/sites.tsx、sites.$siteId.tsx、components/workbench/{site-card.tsx(新),site-list.tsx(新),site-register-dialog.tsx,site-credential-dialog.tsx(新),site-accounts.tsx}、components/settings/sites.tsx、api/sites.ts、api/dashboard.ts、routes/console/home.tsx、components/console/nav.ts、scripts/smoke.mjs |
2.5–3 天 |
顺序:B 先交 contracts 与迁移(半天),F 基于 contracts 用 mock 并行;testkit mock 站点补 login.php/takelogin.php 与用户详情页,两路共用。
12. 待核验 / 风险
- takelogin 表单字段、二次验证字段名、错误文本全部未经真实站点核验(同 README"待真实站点核验")。
- 保存密码是与 MP 不同的取舍(MovieClaw 保存);默认选"保存",可选"只换 Cookie"。
- 用户数据做种分页请求量大,限流默认值需在真实账号上调。
- Cloudflare 站点需要浏览器服务(camofox)登录,本期只识别并转待处理。
DELETE /sites/{id}对source_release.site_id外键的处理需实现时确认,不能级联删资源。