From 8cad83e8f58e94a775669932354e8f0da7dc9998 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 28 Sep 2026 14:32:42 +0800 Subject: [PATCH] docs(design): site center spec (sites page as single entry, credentials, user data, dashboard) Co-Authored-By: Claude Fable 5.1 --- docs/design/site-center.md | 222 +++++++++++++++++++++++++++++++++++++ 1 file changed, 222 insertions(+) create mode 100644 docs/design/site-center.md diff --git a/docs/design/site-center.md b/docs/design/site-center.md new file mode 100644 index 00000000..af63796e --- /dev/null +++ b/docs/design/site-center.md @@ -0,0 +1,222 @@ +# 站点中心与仪表盘调整(v0.2.1) + +> 依据:`docs/plan/00-first-version-plan.md` §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`:GET `login.php` → 正则找 `imagecode.php?...` 验证码(无识别器 → `NEEDS_CAPTCHA` 返回图片地址)→ 带隐藏字段、`imagehash`、`extra_form_data` POST 表单 → `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`: + +1. 用固定 UA 清空 Cookie GET `login.php`(`umt.login.path`),记下 Set-Cookie。 +2. 页面匹配 `imagecode.php?…` → 验证码。本期不做 OCR:直接失败,`reason=captcha`。 +3. 取登录表单(`form[action*=takelogin]`,缺省 `takelogin.php`)的隐藏字段,填 `usernameField` / `passwordField`、`selectDefaults`、`extraFormData`;有 TOTP 密钥且表单有 `two_step_code`/`otp` 字段时填当前 6 位码。 +4. POST(不跟随 3xx),再 GET `index.php`;`loginSuccess` 命中 → 成功,拼 Cookie。 +5. 失败分支:`errorCss` 文本归类 —— 账号或密码错(`bad_credentials`)、需要二次验证而无 TOTP(`two_factor`)、验证码(`captcha`)、账号封禁 / 未确认(`account_blocked`)、Cloudflare 挑战(`challenge`)、其它(`unknown`,原文截 200 字)。 + +服务端处理: + +- 成功:派生 Cookie 存入 `session_secret_ref`(purpose `site_cookie`,label `site_account: