分阶段交付明细
阶段 0 — 地基 ✅
- monorepo 骨架(backend/ frontend/ mobile/)
- Go: Gin + GORM + SQLite + slog + 优雅启停
- 核心 6 模型(User/Site/SiteStat/Downloader/ActivationCode/SystemConfig)+ AutoMigrate
- JWT + bcrypt + AES-GCM + 好学 VIP 准入骨架 + HMAC 激活码
- Vue3 + Element Plus + Tailwind + 液态玻璃主题
- Flutter 工程 + GlassCard 主题
- Dockerfile(多阶段)+ docker-compose + Makefile + CI 工作流
阶段 1 — 核心引擎 ✅
- httpclient:cookie jar/重试/UA/Cloudflare 检测/SSRF 全谱防护(单测)
- core/event:广播+链式事件总线(单测)
- core/module:注册表+Run 调度原语 Skip/Continue/Stop(单测)
- chain:ChainBase + RunModule(单测)
- site:types(ISiteMetadata) + selector(goquery 配置驱动) + nologin + registry(YAML 加载)
- site/schemas/nexusphp:HTML 抓取 + Location 头 nologin(httptest 单测)
- cookiecloud:AES-128-CBC 解密 + host 匹配(单测)
- auth/hxpt_vip:接 site 适配器抓用户页判 VIP + 激活 guard
- db/oper/site(CRUD+AES-GCM)+ SiteChain + api/v1/site(CRUD/连接测试/搜索/cookie/inspect)
阶段 2 — 下载器 + 自动化 ✅
- downloader/qb:login(403 重登) + version 感知(5.0 stop/start) + rid 增量同步 SyncState.merge(单测)+ add/pause/resume/delete/stats
- downloader/tr:RPC 409 session 握手 + torrent-get/add/stop/start/remove(单测)
- downloader/manager:多实例 + Invalidate 重建
- scheduler:robfig/cron + Recover
- automation:stats-sync(真实:FetchUserInfo→写 SiteStat)+ checkin(真实:POST attendance.php)+ cookie-keepalive(真实:认证 GET 刷新 session + Set-Cookie 轮换回写 + 失效告警)+ hr-check(真实:抓 H&R 列表→未达标/临期告警)+ registration(真实:探测 signup→关闭开放转换通知)
- notification:bark/telegram/webhook 通道 + Manager 并发扇出(单测)
- api/v1/downloader + api/v1/notification + WebSocket 路由
阶段 3 — 媒体/文件/辅种/备份 ✅
- media:TMDB(search/multi+详情,httptest 单测)+ MusicBrainz + 豆瓣(frodo /api/v2,微信 UA + 公开 apiKey 免签名,国产影视主源,开箱即用)+ PosterCache + NFO(单测)
- filemanager:WebDAV(PROPFIND/PUT/GET/MKCOL,DAV 命名空间解析,单测) + Alist(REST) + 多实例 Manager
- keepseed:名称 token Jaccard + 大小接近度评分(单测)
- backup:自身 JSON 导出/导入 + MoviePilot 站点导入
- models 加 PosterCache/CrossSeed/FileStorage
- api: /media /keepseed /storages /backup
阶段 4 — 收尾 ✅
- 安全加固:ratelimit(全局+登录组) + CORS(可配置源) + X-API-Key + RequestID + 审计日志(单测)
- go:embed:前端 dist 内嵌进后端 → 单镜像完整产品
- CI:ci.yml(三端测试) + docker.yml(多架构镜像) + flutter-release.yml(apk/windows)
- Makefile:dev-*/test/image/up/embed-preview
后续增强 ✅
- 站点校准:好学 hxpt.yaml(源码校准:搜索+VIP=APP专属VIP+torrents,单测钉死)+ 财神 cspt.yaml(实测 cspt.top 验证 no_login)+ 20 站点定义 + EnsureDefaults 自动填充
- 娱乐游戏:好学(九宫格+魔力刮刮乐+勋章合成) + 财神(刮刮乐),真实契约+单测+实测
- 仪表盘真实数据 + 趋势:DashboardChain 聚合 SiteStat → Dashboard.vue 真实指标卡+站点明细表;新增 GET /dashboard/trend 按天聚合(每站每日 MAX 再跨站 SUM,规避累积量重复累加)→ 移动端 Flutter 4 指标卡 + 近 7 天上传趋势柱状图(fl_chart) + 日环比
- 签到自动化:CheckinCapable 接口 + nexusphp Checkin(attendance.php) + automation 真实全站签到
- 保命三件套:cookie 保活(KeepAliveCapable + httpclient.Cookies 读 jar 轮换回写 + 失效 danger 告警)/ H&R 告警(HRCapable + ParseHR/ParseHRDuration/IsHRDangerous,按状态+剩余考核时间判定)/ 注册开放检测(RegistrationWatch 模型 + oper + CRUD API + 关闭→开放转换通知,去重不刷屏)—— 详见 [[保命三件套]]
- MTeam JSON API 适配器:第二个 schema(schema:mteam),走 api.m-team.cc + x-api-key;Search(/api/torrent/search) + FetchUserInfo(/api/member/profile);chain.credential 按 schema 分发凭据(mteam→apiKey/其余→cookie),不改 Adapter 接口;容错数值解析(int/float/string);馒头开箱即用
- 移动端搜索→下载:Flutter 搜索页(跨站聚合 /search → 按站分组 GlassCard 列表 → 选下载器推送 /downloaders/:id/torrents),闭合移动端核心 PT 工作流;addTorrent 补全;馒头站 download_url 空时提示「暂不支持直接下载」
- 实时种子状态(WebSocket 全栈):后端 gorilla/websocket Hub 连接池 + 每 2s 聚合所有下载器种子广播(无连接则跳过;单下载器失败填 error 不中断);
GET /ws?token=<jwt>query token 鉴权 + 握手首帧;移动端 web_socket_channel 单例 + 指数退避重连,任务弹窗实时刷新进度/双速率(↓下载 ↑上传)+ 连接指示灯;Torrent 补 dl_rate - H&R 实时告警推送:复用 WS Hub——hr-check 发现危险种子时除 bark/TG/webhook 通知外,即时广播 hr-alert 给在线连接(Message.Data 改 any 支持多类型消息);移动端仪表盘首屏红色告警卡(站点/标题/状态/剩余考核时间),打开 app 即见
- Cookie 失效实时告警:复用 WS Hub——cookie-keepalive 检测到登录态丢失时除 danger 通知外即时广播 cookie-alert;移动端仪表盘首屏红色告警卡(站点 Chip),与 H&R 共同构成保命实时推送闭环
- 前端 chunk 分包 + element-plus 按需引入:vite manualChunks 分离 element-plus/vue/vendor(index.js 1084KB→5KB,各 vendor 独立缓存);unplugin-auto-import + unplugin-vue-components + ElementPlusResolver 按需引入(CSS 357→135KB 省 222KB;JS 因用到组件覆盖广含 el-table 重组件未明显下降,按需基础设施就位);清理 dead echarts 依赖(src 零引用)
- 前端 typecheck 全绿:Backup.vue onFile 重构(去 template 内联 TS 类型注解 + 假 Event hack);tsconfig include auto-imports.d.ts/components.d.ts(按需引入配套声明);el-table slot row 加 as 断言(FileManager/Sites/Downloaders,DefaultRow → 具体类型);修 downloader.ts 重复 remove key(→ removeTorrents,原删下载器 remove 被覆盖的潜在 bug);vue-tsc exit 0
- 发布前体检:后端 go build/vet/test(19 包 FAIL:0)+ 二进制 ptpatronus.exe 46MB ✅;前端 typecheck exit 0 + build ✅(npm audit 2 个 dev 漏洞 esbuild/vite,仅影响开发期 server,生产构建产物不含 esbuild 运行时,vite 5→8 breaking 暂不盲目升);移动端 analyze + test 23/23 ✅,fl_chart 0.69→1.2 major breaking 暂不升(当前稳定);⚠️ 移动端 release apk 打包需在本地有网络/Android SDK 环境跑(sandbox 内 gradle 首次下载受限)
- 移动端媒体/海报页:Flutter 媒体页(GET /media/search 跨源 TMDB/豆瓣/MusicBrainz → 海报网格卡:来源徽标/标题/类型/年份/评分;Image.network 海报 + 加载失败占位);home_shell 加「媒体」第 5 tab
- 移动端娱乐页:Flutter 娱乐页(站点下拉选择 → GET /entertainment/:id/games 游戏卡片 → POST /entertainment/:id/play 玩一次 → 成功/失败 + 奖项/数值/余额结果);home_shell 加「娱乐」第 6 tab,移动端功能集完整(6 tab:仪表盘/搜索/站点/下载器/媒体/娱乐)
- 上线前安全审查 + 批次 A 加固:6 域并行审查(auth/JWT、crypto、凭据存储、SSRF/出站、注入/路径、WS/CORS/前端)→ 主控逐条源码复核确认。修复上线阻断级 1 + High 4:
- ① SECRET_KEY fail-fast(
config.validateSecrets):release 模式SECRET_KEY/ACTIVATION_SECRET为空或等于公开默认值时拒绝启动——根治「忘配即用源码公开 key → 离线伪造管理员 JWT + 解密全库」的免认证接管(docker-compose 默认空值也命中,故原默认部署即中招)。 - ② SSRF 重定向 + DNS 重绑定(
httpclient自定义DialContext):每调用按ssrf标志解析主机、校验每个 IP 为公网后再直连已校验 IP;首跳与重定向后续跳都走同一拨号路径且复用同一 ctx,一并根除「302→内网/元数据」与「解析后校验、拨号时再解析」两类绕过;保留ssrf=false的内网测试/下载器逃生口。 - ③ WebSocket CSWSH(
WSHandler.checkOrigin):CheckOrigin收敛为CORS_ALLOWED_ORIGINS白名单(原恒true);非浏览器客户端(curl/移动端,无 Origin 头)仍放行。 - ④ 限流伪造 + 内存放大(
router.SetTrustedProxies+ 令牌桶回收):未配代理时不信任任何 XFF(ClientIP取直连对端),防伪造绕过登录限流;令牌桶空闲 2min 定期回收,防海量伪造 key 撑爆内存。 - 同步更新
config.example.yaml/docker-compose.yml/ 部署指南;新增config(release 强校验) /httpclient(ssrf dial 守卫) /middleware(桶回收) 回归测试,后端go build/vet/test全绿。 - 剩余 P2/P3(JWT 失效机制、备份加密、安全响应头、AES 密钥 KDF 域分离、统一出站客户端、文件管理器 BasePath、JWT 存储与有效期)列入批次 B,发布后可跟进。
- ① SECRET_KEY fail-fast(
- 安全加固批次 B(后端自包含子集):挑出低风险、后端闭环、无需跨端/迁移的项先落地:
- ① 安全响应头(
middleware.SecurityHeaders):X-Content-Type-Options:nosniff / X-Frame-Options:SAMEORIGIN / Referrer-Policy:no-referrer / X-XSS-Protection:0;release 模式加 HSTS。CSP 因 SPA + element-plus 含大量内联样式/脚本、需 nonce 化才能上,暂缓(错误的 CSP 会打断 UI)。 - ② 备份导出响应头:
Cache-Control:no-store+nosniff,防浏览器缓存明文凭据文件。 - ③ URL scheme 白名单(
oper.validateURLScheme):FileStorage / Downloader 的 URL 写库前校验仅允许 http/https,拒绝file://、gopher://、ftp://、javascript:——同时覆盖 API 配置与备份导入两条写入路径。 - ④ 文件管理器 BasePath 真正生效(
filemanager.resolvePath):WebDAV/Alist 构造器注入 base_path,每个操作(List/Upload/Download/Delete/Mkdir)入口把用户路径path.Clean并约束在 base 之下,防远端../越界(之前 BasePath 字段持久化了却从不生效);空 base 也消除路径逃逸。 - 补
security_headers/validate/resolvePath单测;后端 build/vet/test 全绿。 - 剩余需跨端 / 数据迁移 / 产品决策的项列入批次 C(待定):JWT 失效+登出+改密(需 DB 字段+前端)、WS 一次性 ticket(需前端+移动端)、refresh token(缩短 JWT 有效期的配套)、AES 密钥 KDF 域分离(会致旧密文无法解密,需迁移脚本)、备份导出 re-auth(需前端密码输入)。
- ① 安全响应头(
- 媒体搜索三连修复(只显音乐/无海报/点击无效):实测定位三处独立根因并逐一修复——
- ① 只显音乐:豆瓣 frodo
/search2024 改版,结果从顶层items→subjects.items、类型字段type→target_type,旧解析器读旧结构致豆瓣返回 0 条;叠加 TMDB 默认无 key 跳过 → 只剩 MusicBrainz(全音乐)。douban.go适配新结构(subjects.items+target_type)并保留旧格式兼容;card_subtitle作 overview;补 canned-frodo 单测。 - ② 无海报:豆瓣
doubanio.com防盗链,浏览器<img>直连只回 1×1 占位图(img9返 418、qnmob3返 12B 占位)。新增GET /media/image?url=反代(白名单*.doubanio.com/image.tmdb.org+ 注入movie.douban.comReferer + 5MB 上限,SSRF 收敛);豆瓣 cover_url 规整成m_ratio_poster(2:3 竖版,原 large 是 h/120 横裁);前端MediaPoster.vue对豆瓣图走 axios+JWT 取 blob 喂<img>(img 不带 token 无法过鉴权),其余 CDN 直连,失败/无图回退占位(音乐 🎵 / 影视 🎬)。 - ②ᵇ JA3 指纹封锁(e2e 实测发现):反代用标准
net/http取豆瓣图时拿到 HTTP 200 +text/html(反爬 JS 挑战页),同机 curl/wget 却能取真图——豆瓣图片 CDN 按 TLS 指纹(JA3)封 Go(H2/H1、换 UA 都无效,纯指纹问题)。解法:引入refraction-networking/utls,反代客户端media_fetch.go用HelloChrome_Auto伪装 Chrome ClientHello + ALPN 仅 http/1.1;e2e 实测/media/image返 102KB 真 JPEG(JFIF 签名)。 - ③ 点击无效:
Media.vue卡片原本无点击。改为点击 → 各源官方详情页(豆瓣条目 / TMDB / MusicBrainz release-group)新标签打开。 - ④ 移动端对齐:海报——Flutter
media_page.dart原Image.network直连豆瓣海报同样防盗链空白,ApiClient加mediaImageUri()(豆瓣图→/media/image反代 URI)+authHeaders,_poster()豆瓣图带 JWT 头经反代加载、其余 CDN 直连;点击——MediaInfo.detailUrl按源映射详情页(豆瓣 subject / TMDB / MusicBrainz),卡片GestureDetector→InAppBrowser应用内打开(复用 flutter_inappwebview,免新依赖)。 - 验证:新增
media_image_test.go(白名单+SSRF 拒绝)+TestDoubanPosterURL;后端 build/vet/test + 前端 typecheck/build + 移动端 analyze/test(24/24) 全绿;端到端实测(本地起后端 sqlite +X-API-Key绕 VIP)/media/search?keyword=霸王别姬返回豆瓣电影(1993) + 海报 URL,/media/image?url=<doubanio>返 102KB 真 JPEG(utls 介入后,此前为 502 JS 挑战页)。新依赖refraction-networking/utls v1.8.2(go.mod 转正)。
- ① 只显音乐:豆瓣 frodo
- 可维护性:qb 拆 3 文件 + entertainment helpers 抽出 + 单元/集成测试全覆盖(19 包 FAIL:0)
Web UI 体验迭代(2026-06-26)
- iOS 26 液态玻璃图标:新增可复用组件
frontend/src/components/GlassIcon.vue(圆角 squircle 渐变色块 + 顶部玻璃高光 + 白色描边字形,对标 iOS 26 Liquid Glass),替换导航栏 / 品牌 Logo / 仪表盘 4 指标卡 / 下载器类型徽标的全部 emoji。每个图标独立from→to配色,经 unplugin-vue-components 自动注册,构建独立分包GlassIcon-*.js。 - 修复「加入站点后仪表盘全 0」:根因是统计仅由
stats-sync定时任务(默认*/30 * * * *)回填,新站最久等 30 分钟才有数据。新增POST /dashboard/sync端点——DashboardChain.SyncAll注入siteCh后立即抓取所有启用站点UserInfo写快照并返回最新总览。前端两处触发:① 仪表盘「刷新统计」按钮(Dashboard.vue);②Sites.vue添加站点成功后后台静默 sync,用户切到仪表盘即见数据。 - 修复「下载器不能编辑」:后端
PUT/DELETE /downloaders/:id本就存在,缺的是前端入口。Downloaders.vue卡片补「编辑 / 删除」按钮,复用同一对话框(editingId区分新建/编辑,密码留空=不修改),并补「默认」开关、下载器类型改用 GlassIcon 徽标。 - 验证:前端
vue-tscexit 0 +npm run build✅;后端go build/go vet ./...✅。
Web UI 体验迭代 · 第二轮(2026-06-26)
针对实测反馈的 6 项修复:
- 图标字形居中:
GlassIcon字形容器改为position:absolute; inset:0确定尺寸 + 子svg用width/height:60%(原先百分比挂在自动尺寸的 inline-flex span 上解析不确定,字形偏位/尺寸漂移)。 - 下载器任务状态中文化:qBittorrent 原始状态码(
stalledUP/pausedUP/downloading…)+ Transmission(stopped/seeding…)→ 中文标签stateLabel,状态列不再显示英文。 - 下载器卡片按钮溢出:
.actions由 flex 改为grid 2 列(4 按钮 2×2 排布),删除按钮不再超出边框;按钮width:100%填满格子。 - 娱乐页空白:根因 host 不匹配——站点 base_url 常带
www(www.hxpt.org)而游戏注册用裸域(hxpt.org),Registry.Games/Game改为后缀匹配(hostMatch:精确相等或子域),www.hxpt.org站点现能匹配到好学游戏。 - 好学统计全 0(根因):
hxpt.yamluser_info.path由userdetails.php(需?id=<uid>,无 id 抓到非资料页→选择器全不匹配→上传/下载/魔力/分享率全 0)改为usercp.php(当前用户个人中心,无需 id,与 cspt.yaml/模板一致)。注:做种仍缺精确选择器(:contains("做种")会误匹配「做种时间」),暂留 0 待/sites/inspect校准。 - 站点可编辑:
Sites.vue补「编辑」按钮 + 编辑态(editingId,cookie 留空=不修改),与下载器一致。 - 验证:前端
vue-tsc+build✅;后端go build/vet/test ./...(含 entertainment/site)✅。
Web UI 体验迭代 · 第三轮:仪表盘数据根治 + 站点明细字段补全(2026-06-26)
针对「仪表盘上传/下载/魔力全 0、站点明细字段太少」的根治性修复(对照 D:\html 三站真实页面 + 参考 MoviePilot/PT-depiler/旧版 PTPatronus 的字段提取范式)。
- 根因:
user_info.fields选择器错配——三站 usercp.php 根本不是td.rowhead表格结构(好学=cute-top-profile、财神=Audiences menu-base-info、蟹黄堡=经典 info_block),旧选择器全部落空;蟹黄堡 yaml 是空壳。统一锚点:NexusPHP 框架常量font.color_*(uploaded/downloaded/ratio/active/bonus)三站共有,命中后取父节点文本 + 正则抽值即可覆盖三种皮肤。 - 选择器引擎增强(
site/selector.go+types.go):ElementQuery新增Selectors []string(回退列表,首个非空胜出)与Parent bool(命中后爬父节点取文本);Filter.regexp支持args带:::N取第 N 捕获组(「当前活动: 27 0」一锚拆做种/下载中)。向后兼容,既有 search/hr 选择器不受影响。 - 字段链路全程加宽:
UserInfo(+leeching/seeding_size/bonus_per_hour) →SiteStat(+level/leeching/seeding_size/bonus_per_hour/join_date) →SiteStatItem+totals→ 前端类型 + 表格列。AutoMigrate 自动加列,旧快照不破坏。 - NexusPHP 多页 best-effort 抓取(
site/schemas/nexusphp.go):usercp.php(基础统计+发现 uid) → userdetails.php?id=uid(level/join_date) → mybonus.php(bonus_per_hour) → getusertorrentlistajax?type=seeding(seeding_size,逐<td>单元格累加)。任一子页失败不致命。 - 三站定义对账重写:
hxpt.yaml(火花/seeding-2 组拆分)、cspt.yaml(元宝/SVG:has(title:contains)菜单)、crabpt.yaml(蟹币值/3 个 color_bonus 取首)。defaults.go的DefaultNexusPHPUserInfo升级为 color_* 锚点版 + 默认 detail_fields,其余 ~17 站无user_info段自动继承。 - 修复
ParseBytes:兼容二进制单位 TiB/GiB(去 i 归一)。 - 前端:明细表新增 等级/做种体积/下载中/时魔/加入时间 列(站点列 fixed 左、横向滚动);新增「总做种体积」卡。
- 测试钉死真实值:
site/testdata/{hxpt,cspt,crabpt}_{usercp,userdetails}.html(截取自真实页面)+parse_fixture_test.go断言三站 uploaded/ratio/bonus/seeding/level/join_date 真实数值(好学 104.564TiB/维护开发员、财神 44.069TiB/总版主、蟹黄堡 1.261TiB/蟹币值1621449.1)+ 引擎单测(Selectors 回退/Parent/:::N);schemas/nexusphp_multifetch_test.gohttptest 验证多页编排与 404 容错。 - 验证:后端
go build/vet/test全绿;前端vue-tsc+build✅。 - 待跟进:① bonus_per_hour/seeding_size 走标准端点 best-effort,部分站(如好学用
getusertorrentlist.php非 ajax)可能取不到,需/sites/inspect实测校准;② 移动端 Flutter dashboard 模型未同步新字段(fromJson 容错不报错,但暂不展示)——✅ 见「移动端:Flutter 仪表盘模型同步新字段」;③ Web UI 全面对齐旧版功能(趋势图/H&R/Cookie 告警卡等移动端已有项)——✅ 见「第四轮」。
Web UI 体验迭代 · 第三轮·续:站点明细操作列 + 发布数 + 侧栏图标居中(2026-06-26)
- 站点明细加列(参考 PT-depiler「我的数据」):发布数
uploads、更新时间updated_at、操作列。操作列两个图标按钮——用户历史数据(蓝色,弹 el-dialog 展示该站近 90 天历史快照表)+ 刷新数据(绿色,仅重新抓取该站、按钮 loading 态)。 - 发布数抓取:userdetails「发布种子」是折叠行(计数靠 AJAX),故走
getusertorrentlistajax?type=uploaded——fetchUploadsCount优先解析响应头<b>N</b>条记录,回退数种子详情行。best-effort。 - 新接口:
POST /dashboard/sync/:id(单站同步,返回刷新后总览)、GET /dashboard/history/:id?days=N(单站历史快照,倒序最多 500)。链路DashboardChain.SyncOne+History。 - 字段链路续扩:
UserInfo/SiteStat/SiteStatItem加uploads,SiteStatItem加updated_at(=SnapAt)。GlassIcon 加refresh/history两字形。 - 侧边栏收起态图标居中:
MainLayout.vue收起时.brand/.nav-item改justify-content:center+ 去左右 padding,图标不再左贴边。 - 测试:
nexusphp_multifetch_test.go增 type=uploaded 路由 +u.Uploads==5断言。后端 build/vet/test 全绿;前端 vue-tsc+build ✅。
Web UI 体验迭代 · 第三轮·续2:做种体积/时魔按 PT-depiler 校准 + 列顺序(2026-06-26)
- 根因(实测对账 PT-depiler 真值):
seedingSize之前逐行累加体积是错的——NexusPHP 的getusertorrentlistajax?type=seeding响应在 table 前有一个汇总 divN | SIZE(见 nexusphp 源码),体积应取该汇总;bonusPerHour之前用全局「每小时」正则会取到魔力总量等干扰数。 - 按 PT-depiler
NexusPHP.ts精确方式重写:fetchSeedingSize优先解析汇总 div「N | SIZE」的体积(正则要求第二段带单位,规避表内 seeders|leechers 误匹配),回退逐<td>累加;fetchBonusPerHour定位div:contains("你当前每小时能获取")(最深)再用能获取锚定取其后数字(避免同元素内魔力总量干扰),多语言/#outer td[rowspan]回退;fetchUploadsCount加汇总 div「N」回退。 - 列顺序:发布数移到做种前面(站点/等级/上传/下载/分享率/发布数/做种/做种体积/…)。
- 测试:multifetch 用真实 NexusPHP 响应格式(汇总 div + 含干扰数的 mybonus div)钉死
SeedingSize==2GiB、BonusPerHour==123.4(不被 999999 干扰)、Uploads==5。后端全绿;前端 ✅。 - 部署要点:改动须重建 Docker 镜像(
make image && make up)或重跑ptpatronus.exe后才生效——运行中的旧镜像/二进制仍是旧行为。
Web UI 体验迭代 · 第四轮:仪表盘对齐移动端(趋势图 + H&R / Cookie 告警卡)(2026-06-26)
收口「第三轮·待跟进 ③ Web UI 全面对齐旧版」——后端 /dashboard/trend + WS hr-alert/cookie-alert 早齐备、移动端已消费,Web 缺的是趋势图与两张实时告警卡。根因:Web 前端无 WS 客户端,且 api/dashboard.ts 未接趋势端点。
- 趋势 API(
api/dashboard.ts):新增DashboardTrendPoint类型 +trend(days=7)(GET /dashboard/trend→data.points),对齐 mobileDashboardTrendPoint。 - WS 客户端(新建
composables/useWs.ts):模块级单例 + Vue 响应式 refs(connected/hrAlerts/cookieAlerts/torrents),镜像mobile/lib/core/ws_client.dart——/api/v1/ws?token=<jwt>(URL 由location.protocol/host推导,同源),按type分发,断线指数退避重连(2…60s 封顶),disconnect()置 stopped 阻止重连。 - WS 生命周期(
MainLayout.vue):onMounted连 /onUnmounted断;登出走auth.logout()+ 跳/login卸载外壳 → 自然断开。 - vite 代理 WS(
vite.config.ts):/api代理补ws: true(开发期转发 WS 升级;生产同源 go:embed 不受影响)。 - 仪表盘渲染(
Dashboard.vue):load()并行overview()+trend(7);渲染顺序对齐移动端——Cookie 失效告警卡(红色,失效站点 chip 列表)→ H&R 告警卡(红色,站点/标题/状态/剩余考核时间,最多 5 条)→ 指标卡墙 → 近 7 天上传趋势(轻量 CSS 柱状图,渐变#6c5ce7→#4285f4,MM-DD 标签,hover title 显完整字节,空态提示)→ 站点明细;总上传/总下载卡加日环比徽标(末天 vs 前一天 ±%)。 - 趋势图方案:用纯 CSS 柱状图(flex 等宽列 + 渐变柱 + height%),未重新引入 echarts——契合上轮清理 echarts 死依赖 + chunk 体积优化;7 根柱场景 echarts 能力溢出。
- GlassIcon:补
alert字形(Lucide triangle-alert),告警卡 danger 徽标用。 - 验证:
vue-tscexit 0 +npm run build✅(新增useWs1.03KB、Dashboard 9.45KB,无新 vendor chunk)。 - 不在本期:WS
torrents已分发但未渲染(下载器实时种子卡属下载器页增强)、WS 连接指示灯、下载量趋势切换、Flutter dashboard 新字段同步(另一 TODO,独立处理)。
移动端:Flutter 仪表盘模型同步新字段 + 站点明细列表(2026-06-26)
收口「第三轮·待跟进 ② 移动端 Flutter dashboard 模型未同步新字段(fromJson 容错不报错,但暂不展示)」。
- 模型同步(
mobile/lib/api/dashboard.dart,对齐后端chain.SiteStatItem/DashboardOverview.totals):SiteStatItem补 7 字段:level/leeching/seedingSize/uploads/bonusPerHour/joinDate/updatedAt(此前 fromJson 容错丢弃)。DashboardOverview.totals补 3 字段:leeching/seedingSize/bonusPerHour。DashboardTrendPoint已与后端TrendPoint对齐,不动。
- 站点明细列表(
dashboard_page.dart):新增「站点明细(N)」区——每站一张GlassCard:站点名 + 等级 chip + 彩色分享率 + 三行统计网格(上传/下载/做种 · 做种体积/发布/下载中 · 魔力/时魔·h)+ 加入/更新时间;空态提示「暂无数据,等待统计同步」。整体_body由Column改ListView可滚动,趋势图由Expanded改固定高 190。 - 测试:
dashboard_model_test.dart补新字段断言(好学 level=维护开发员/seedingSize/uploads/bonusPerHour/joinDate/updatedAt 真值 + 馒头缺字段回落默认值);顺手修widget_test.dart陈旧断言SegmentedButton<bool>→<int>(登录重设计后登录方式 2→3 种,段类型改 int,冒烟测试未跟进——与本期改动无关的历史遗留)。 - 验证:
flutter analyzeNo issues found! +flutter test23/23 ✅。
仪表盘趋势图升级:全指标多系列 + 区间选择(2026-06-28)
「第四轮」趋势图仅上传单系列 CSS 柱图、无分享率、不可切指标。本轮升级为可切指标/可叠加/带 hover tooltip 的真趋势图,覆盖上传/下载/做种/做种体积/魔力/分享率全指标。
- 后端(chain/dashboard.go):
TrendPoint补seeding_size(SQL 内层MAX+ 外层SUM,与既有累积量防重复累加同构)与ratio(Scan 后由uploaded/downloaded补算,保持 SQL 跨方言稳定;值为当日跨站聚合总分享率)。新增 dashboard_trend_test.go:同日双快照验证「MAX-再-SUM」+ ratio 断言。 - Web:新建零依赖 TrendChart.vue(
viewBoxSVG,legend 点按切换/叠加系列,上传=柱、下载/做种/魔力=线、做种体积=面积、分享率走右轴;每列热区 hover tooltip 显当日各指标;复用utils/format.ts的fmtBytes)。Dashboard.vue 替换旧 CSS 柱图为该组件 + 7/30/90 天区间分段(切换仅重拉趋势),删trendBarscomputed 与旧柱图 CSS。 - 方案延续:仍未重新引入 echarts(项目刻意精简依赖,分享图走 Canvas、趋势图走手绘 SVG),延续「第四轮」与 chunk 优化决策。
- 移动端(dashboard_page.dart):
DashboardTrendPoint补seedingSize/ratio;趋势卡加 ChoiceChips 指标选择器(6 指标全可选)+ 动态标题「近 7 天 {指标} 趋势」,复用既有_TrendChart(fl_chart BarChart) 喂选中指标 data。注:fl_chart 单 Y 轴,移动端为「单指标切换」而非 Web 的多系列叠加。 - 验证:后端
go test ./internal/chain/...✅;Webvue-tsc+npm run build✅(Dashboard 18.57KB、vendor 160KB 无 echarts);移动端flutter analyzeNo issues found!。
仪表盘趋势图板块重设计:sparkline 内嵌指标卡(2026-06-28)
用户反馈独立趋势大卡「不好看且占地方」。改用 sparkline 内嵌指标卡方案:移除独立趋势卡,趋势融进已有指标卡墙,零额外占地。
- 移除:独立
.trend-card+ 全指标多系列 SVGcomponents/dashboard/TrendChart.vue(已删,上方 2026-06-28 段落里的该组件链接随之失效)+ 7/30/90 区间选择器 +trendDaysref/watch。 - 新增 Sparkline.vue:零依赖迷你折线,viewBox 100×36 +
preserveAspectRatio="none"横向自适应卡片宽度 +vector-effect="non-scaling-stroke"保 2px 描边不随拉伸变粗,下方渐变面积,HTML 百分比定位列热区 + hover tooltip(日期 + 该指标当日值)。该模式下独立端点圆会变椭圆,故用 area 填充 + round line-cap 替代。 - 嵌入 Dashboard.vue:5 张指标卡(上传/下载/做种/做种体积/魔力)底部各嵌一条,颜色沿用原 TrendChart SERIES 配色(#6c5ce7/#4285f4/#38ef7d/#43cea2/#f7971e)保持视觉连续。
- 数据:trend 请求从跟随
trendDays(7/30/90) 改为固定 14 天(sparkline 形态比 7 点丰富);日环比仍取末两天;导出图片slice(-7)保持「近 7 天」语义不变。 - 方案延续:仍未引入 echarts(sparkline 手绘 SVG,延续零依赖画图哲学)。
- 验证:Web
vue-tsc+npm run build✅(Dashboard 15.79KB)。仅 Web 改动,移动端(fl_chart ChoiceChips 单指标切换)本次未动。
仪表盘导出图片重做:参考 PT-depiler 时间轴海报(2026-06-28)
原导出图较简陋(标题 + 5 指标块 + 7 天柱图 + 页脚)。参考 PT-depiler UserDataTimeline(Konva 海报式分享图)重做为「个人 PT 成就海报」,沿用项目零依赖纯 Canvas 2D 哲学(不引 Konva/ECharts/html2canvas)。
- 重写 dashboardImage.ts:扩展
DashboardShareData(去trend,加sites: DashboardShareSite[]+username+totals对象);exportDashboardImage改async,先Promise.all并行预载每站 favicon(base_url + /favicon.ico,crossOrigin='anonymous'防 canvas taint,3s 超时/失败回退),再 Canvas 绘制 5 分区:①深色渐变背景 ②顶部用户名 + 生成时间 ③基础信息(总站点 / PT 龄 / 6 指标两列网格 / 上传量冠亚军 金#C9B037银#B4B4B4)④每站两列明细(等级 + 上传/下载/分享率 + 做种/体积/发布 + 魔力/时魔 + 加入年,共 10+ 字段;favicon 圆 clip,失败 → 站名首字 + 哈希 hue 色块)⑤页脚。布局用单一数据源layout()返回各区高度,画布高 =HEADER + 摘要 + 站点区(ceil(N/2)*70) + 页脚动态算。 - 改 Dashboard.vue
exportImage改 async:拉siteApi.list()按 id joinbase_url(SiteStatItem不带),sites 按 uploaded 降序,username 取useAuthStore().username。 - 去掉 原「近 7 天上传趋势柱图」(纯成就海报,忠实 PT-depiler)。
- 关键坑:Canvas
drawImage跨域未授权图会 taint 致toBlob抛错——用crossOrigin='anonymous',无 CORS 头则onerror回退色块(不会 taint)。奖牌用纯几何圆 + 序号(无 emoji 字体依赖)。 - 验证:Web
vue-tsc+npm run build✅(Dashboard 15.79KB)。
仪表盘导出图片配置化扩展:主题/脱敏/背景图/字段开关/7 类冠军(2026-06-28)
对照重构前旧版(Kotlin DashboardExporter)补全:导出从「一键直出」升级为配置驱动,弹配置对话框,配置持久化到 localStorage。
- 新增 dashboardExportConfig.ts:
DashboardExportConfig(总览 10 字段开关 / showRank + 7 类 rankMetrics / showSites + 站点白名单 + 数量 / 3 主题 / 双脱敏 / 背景图 + 模糊 + 遮罩)+loadConfig/saveConfig(localStorage,merge 默认值容错,配额超限返回 false)。 - 新增 DashboardExportDialog.vue:配置弹窗(el-dialog)。背景图
<input file>→ FileReader → canvas 压缩(max 1600 / jpeg 0.85) 再存(控 localStorage 体积);模糊/遮罩滑块;字段/冠军 checkbox;站点白名单 el-select multiple + 数量 el-input-number;脱敏开关。watch deep 自动存盘,超限提示。 - 重写 dashboardImage.ts:
exportDashboardImage(d, config)。主题色系统themeColors(theme, hasBg)(dark 深紫渐变 / light 浅色 / glass=有背景图→白字 + 暗遮罩);drawBackground背景图 cover +ctx.filter='blur'(try/catch 降级) + dim 遮罩;脱敏mask(站点名/用户名→***);总览补 总发布数 / 总时魔·h / 最早入站+站点;冠军扩 7 类(RANK_DEFS 各指标冠军站点,替换旧上传冠亚军);layout 改绝对坐标返回各区 y(总览字段数/冠军数/站点数全动态)。 - 改 Dashboard.vue:导出按钮 → 打开 dialog(
openExport首次拉siteApi.list()缓存 base_url);exportDatacomputed 组装全量 sites(含 site_id,排序/筛选交 config)。 - 关键坑:① 背景图本地上传(dataURL)同源不 taint;favicon 跨域仍需 crossOrigin。②
ctx.filterblur 兼容性包 try/catch。③ localStorage ~5MB → 背景图必压缩。④ EP 实际装 2.14.2,el-radio/checkbox 取值用value,同时设label兼容旧版 a11y。⑤ layout 绝对坐标避免动态字段数下绘制与画布高漂移。 - 验证:Web
vue-tsc+npm run build✅。
实测娱乐页除九宫格外全失败。对照迁移前 Kotlin 项目(F:\PT-admin\PTPatronus,adapter/lottery/*)逐游戏核对,根因是 Go 重写时游戏契约写错了(非简单缺头):
- 魔力刮刮乐(
invalid character '<'):站点buy返回 HTML(内嵌currentScratchId=N),Kotlin 版用正则抠 id;Go 版误当 JSON 解析 → 炸。 - 勋章合成(安全验证失败):站点需 csrf_token——GET 页面取 token + 首个
synthesis_id,POSTaction=draw+params={"synthesis_id":N}+csrf_token并带X-CSRF-TOKEN头,token 失效要刷新重试;Go 版只发action=draw(无 token)→ 被拦。 - 财神刮刮乐(401 未授权):财神 API 走 Bearer token,需 POST
/api/login(用户名+密码)换 token;Go 版只带 cookie → 401。
修复(按 Kotlin 契约重写):
httpclient:新增PostFormAjaxWith(带额外头,供 csrfX-CSRF-TOKEN)+GetWithHeaders;既有PostFormAjax统一带X-Requested-With+Referer(具体游戏页 URL,非站点根)。main.godebug 环境改用 debug 日志级别;4 游戏加logResp(slog.Debug 打站点原始响应 status+body,诊断用)。MagicScratchGame重写:buy→正则抠currentScratchId、scratch→JSON、中奖→settle。MedalSynthesisGame重写:GET 页面取 csrf+synthID→POST draw(X-CSRF-TOKEN)→msg 含「安全验证/csrf/刷新页面」则刷新 token 重试一次;「玩一次」=抽首个合成目标 1 片。- 测试:重写
TestMagicScratchGamePlay(HTML buy + settle 校验)、medal_test三例(正常 / 普通失败不重试 / CSRF 失败重试);go build/vet/test全绿(22 包 FAIL:0)。 - 待定 · 财神刮刮乐:需站点用户名+密码(API 登录换 Bearer token)。现
Site模型只有 cookie,无 username/password。要支持须:Site 加Username+PasswordEnc字段(AutoMigrate)+ oper 加解密 + cspt 游戏加/api/login换 token 流程 + Web/移动端加站点凭据录入 UI。属跨端功能,待确认是否做。
娱乐:财神刮刮乐功能补全 + 富 UI(对齐原站)(2026-06-26)
接上文「待定 · 财神」——落地完整凭据 + 富 UI(用户给出原站 UI 参考:古风金玉主题、奖品展示网格、扇形刮卡、自动刮卡、购卡档位 1/10/50/100、抽奖记录、游戏规则)。
- 凭据层:
Site模型加Username+PasswordEnc(AES-GCM,AutoMigrate 自动加列);oper.SiteOper增Password()解密 +SiteInput.Username/Password;entertainment.Creds{Cookie,Username,Password}贯穿,LotteryGame.Play签名改为收Creds(4 游戏全改)。 - 财神 API 登录:
cspt.go重写——csptLogin(POST/api/loginJSON 换 Bearer) +csptRequest(登录→带 token POST→401/未授权重登重试);每次操作重登(游戏单例无法跨站缓存 token)。 - 能力接口(对齐 Kotlin
LotteryCardShop等):Statable/Purchasable/Prizeable/Historable可选接口 + 结果类型(StatsResult/BuyOption/PrizeDef/GameRecord);cspt 实现全部(stats=余额/剩余卡、buy=1/10/50/100 档位、prizes=奖品展示、history=抽奖记录)。 - 新端点:
GET /entertainment/:id/games/:game/{stats,buy-options,prizes,history}+POST .../buy;chain 按resolve()取站点+游戏+凭据+baseURL 后按能力类型断言分发。 - Web 富 UI:新增
components/ScratchCardGame.vue——古风金玉主题,含统计栏(剩余刮刮卡/金元宝余额)、奖品展示、刮卡视觉(刮奖区→揭奖动画)、单抽 + 自动刮卡(循环至卡耗尽)、购卡档位(买N送M + 价格)、抽奖记录;Entertainment.vue改为「站点选择 + 玩法选择 + 按玩法分发」(cspt_scratch 走富组件,其余走通用卡)。 - 站点表单:
Sites.vue+api/site.ts加「账号/密码」录入(密码 AES-GCM 加密入库,编辑留空=不修改)。 - 验证:后端
go build/vet/test22 包全绿(含 cspt 登录/重登/无凭据/重试单测);前端vue-tsc+build✅。 - 待跟进:① 移动端站点表单 + 移动端富娱乐 UI;② 九宫格/魔力刮刮乐/勋章合成的富 UI(当前 Web 走通用卡,可照 ScratchCardGame 范式扩);③ 真机实测各站真实响应(debug 日志已就位:
entertainment raw response)。
娱乐:九宫格富 UI(3×3 抽奖动画)(2026-06-26)
对照迁移前 Kotlin JggGameUi.kt + 站点源码(jgg/magic_grid_config.php)。
- 后端:
JggGame实现Statable(action=get_stats→ 今日次数/上限/单次消耗);PlayResult加PrizeID;修正JggGame.Play奖品在嵌套prize对象({id,name,icon,color,type,value})里的解析(此前误读扁平prize_name, PrizeID 缺失导致动画无法定位),兼容扁平回退。 - Web 富 UI:新增
components/JggGameUi.vue——固定 9 奖品(id1-8 外圈顺时针、id9 归 VIP1天格);3×3 网格(外圈 8 格 + 中心「开始抽奖」按钮);中奖后高亮顺时针循环减速 3 圈落到中奖格(对齐 Kotlin 节奏:70ms 起、+8/步、封顶 280ms)+ 落定 pop 动画 + 结果展示;统计栏(今日次数/火花余额)。Entertainment.vue分发:jgg → JggGameUi。 - 测试:新增
TestJggGamePlayNested(钉死嵌套 prize 的 PrizeID/Prize/Balance 真值);后端go build/vet/test22 包全绿;前端vue-tsc+build✅。
娱乐:魔力刮刮乐 + 勋章合成富 UI(Web 四玩法齐)(2026-06-26)
四个玩法 Web 端均有独立富组件,Entertainment.vue 按 gameKey 分发(通用卡兜底)。
- 魔力刮刮乐(
MagicScratchGame.vue,琥珀/火花主题):后端MagicScratchGame实现Statable(GET magic_scratch.php HTML 抠「当前余额」+「今日剩余 N/M」,对齐 Kotlin loadStats);UI 统计栏(火花余额/今日次数)+ 刮卡视觉(刮奖区→揭奖)+「刮一张」。 - 勋章合成(
MedalSynthesisGame.vue,紫/勋章主题):抽取一片 → 把 Play 返回的碎片汇总(「碎片X×2、碎片Y×1」)拆成碎片卡逐张揭奖动画。注:当前抽取首个合成目标;完整目标选择/碎片库存/合成面板为后续增强(需站点页深度解析)。 - 验证:后端 22 包全绿;前端
vue-tsc+build✅。 - 待跟进:① 移动端富娱乐 UI;② 勋章合成完整面板(目标列表/碎片库存/合成);③ 真机实测各站真实响应校准(debug 日志
entertainment raw response已就位)。
卡牌合成完整面板 + 真实源码对账(2026-06-26)
用户提供好学站点源码备份 F:\www.hxpt.org\public(迁移前 Kotlin 注释即引用此处),据此把卡牌合成(gameKey=medal_synthesis,站点 UI 名「卡牌合成」)做成完整面板并逐行核对契约。
- 正名:站点 UI 实为「卡牌合成」(Kotlin
title="卡牌合成"),后端MedalSynthesisGame.Name()由「勋章合成」改为「卡牌合成」。 - 后端能力(源码
medal_synthesis.php核对):Synthesizable接口 +SynthesisTarget/SynthFragment类型;MedalSynthesisGame实现Syntheses/Draw(count 1/10/100/→draw/draw_ten/draw_hundred/draw_custom)/Synthesize。- 页面解析移植 Kotlin
parseSyntheses(深度配对<div>块):synthesis-card/data-synthesis-id/fragment-item/span.current|required/drawFragment(id,count,price)/btn-synthesize/pity-count——正则经源码逐行核对全部对上,TestParseSyntheses钉死。 - csrf 对
$_COOKIE['synthesis_csrf_token'](cookie jar 从 GET 带到 POST);Historable:action=get_history params={synthesis_id,page}→data.logs:[{fragment_name,synthesis_name,cost,created_at}]。 Stats(GET 页面抠userBonus/当前火花);端点syntheses/draw/synthesize/stats/history。
- Web 面板(
MedalSynthesisGame.vue,浅粉/紫/绿对齐原站):顶部栏(卡牌合成 + 当前火花)→ 目标卡(图/名/进度/保底)+ 碎片库存网格(每片 current/required,集齐绿框)→ 单抽/十连/百连/合成/记录 → 上次抽取碎片揭示 → 记录区(碎片/消耗/时间)。 - 验证:后端 22 包全绿(含
TestParseSyntheses);前端vue-tsc+build✅。
移动端富娱乐 UI(Flutter 四玩法)(2026-06-26)
对齐 Web,移动端娱乐页从「卡片+玩一次」升级为按玩法分发的富 UI。
- API(
entertainment.dart):加GameStats/BuyOption/GameRecord/SynthFragment/SynthesisTarget类型 +stats/buyOptions/buy/history/syntheses/draw/synthesize方法;PlayResult加prizeId。 - 页重构(
entertainment_page.dart):站点选择 + 玩法选择 + 按 gameKey 分发;通用卡兜底。 - 4 个富组件(
features/entertainment/):jgg_game_view.dart:3×3(外圈 8 格顺时针 + 中心按钮),中奖后高亮循环减速 3 圈落定(节奏对齐 Web/Kotlin)。cspt_scratch_view.dart(古风金玉):stats(剩余刮卡/金元宝)+ 刮卡 + 单抽/自动刮卡 + 购卡档位。magic_scratch_view.dart(琥珀):stats(火花/今日次数)+ 刮卡 + 刮一张。card_synth_view.dart(粉/紫):目标卡 + 碎片库存网格 + 单抽/十连/百连/合成/记录。
- 验证:
flutter analyzeNo issues found! +flutter test23/23 ✅。 - 待跟进:① 真机实测各站真实响应(卡牌合成解析、财神登录 token、stats 正则等)校准;② 娱乐页「任务/赠送碎片/万能卡」等次要功能(源码有
get_tasks/gift_fragment/use_universal等接口)按需补。
Web 跨站搜索页(对齐移动端 + 后端搜索并发化)(2026-06-27)
收口「Web 缺跨站搜索页」——后端 POST /search 聚合端点 + 移动端 search_page.dart 早齐备,Web 前端既无 client 也无视图/路由。同时修掉后端一处隐患:SiteChain.SearchSites 注释写「并发」但实现是串行 for、且无超时。
- 后端并发化(
chain/site.goSearchSites):sync.WaitGroup扇出 + 预分配results[i]各 goroutine 写独立下标(无锁)+chan struct{}信号量封顶 8 并发 + 每站context.WithTimeout(15s);保留单站失败写 Error 不影响其它的容错语义。多站搜索从 N×RTT 降到 ~1×RTT。 - 后端测试(
chain/site_search_test.go,复用fakeAdapter补零值安全err/delay+newChainDB缝):TestSiteChainSearchSitesConcurrent(5×100ms 并发耗时 <400ms 证非串行 + 单站 err 隔离不影响其它)、TestSiteChainSearchSitesTimeout(hang 站被 ctx 取消记 error 不阻塞整体)。go vet/test 全绿;-race需本地 gcc(当前环境无,但各 goroutine 写独立下标、无共享写,设计上无数据竞争)。 - 前端 API(新建
api/search.ts):SiteTorrent/SiteSearchResult(snake_case 对齐site.Torrent/chain.SiteResult,size 为站点已格式化串)+searchApi.all(keyword)(POST /search空 body + query keyword)。 - 前端视图(新建
views/Search.vue):扁平可排序表(站点 chip / 标题+副标题 / 大小 / 做种↑ / 吸血 / Free / 操作,默认按做种降序)+ 失败站点折叠提示(聚合容错)+ 三态(顶层请求失败 ⚠+重试;空态区分「未搜」与「搜了无果」)+ 点「下载」弹下载器选择 →downloaderApi.add推送(对齐移动端_download闭环;download_url 空提示「该站点暂不支持直接下载」)+ 「详情」外链新标签(桌面端增值)。 - 接线:
router/index.ts加search路由;MainLayout.vuenavItems 站点后插「搜索」;GlassIcon.vue加search放大镜字形。 - 验证:后端
go vet ./...+go test ./...全绿;前端vue-tscexit 0 +npm run build✅(新增Search-*.js5.55KB)。
移动端备份页(对齐 Web)(2026-06-27)
收口「移动端缺备份」——后端 /backup/{export,import,import-moviepilot} + Web Backup.vue 早齐备,移动端零备份代码。本次完整移植(导出+导入+MoviePilot 导入)。
- 契约纠偏(实测 backup.go
readBody=ShouldBindJSON(&json.RawMessage)):导入是 raw JSON body,不是 multipart——Web 也是「读文件→JSON.parse→POST JSON」。移动端走 JSON body,禁用postMultipart。 - ApiClient 补两方法(api_client.dart):①
getBytes(path)→Uint8List(导出用,跳过 jsonDecode、401 复刻清 token);②postJson(path, Object body)(导入用,body 类型放宽为Object使 List(MoviePilot 数组)也能进 JSON body)。 - 新建 BackupApi(api/backup.dart):
export()/importOwn(Map)→ImportResult{sites,downloaders}/importMoviePilot(List)→int。 - 新建备份页(features/backup/backup_page.dart):三 GlassCard(导出/导入/MP 导入,文案对齐 Web);导出→
Share.shareXFiles([XFile.fromData(bytes,…)])系统分享存盘;导入→FilePicker.pickFiles(json, withData:true)→utf8.decode→jsonDecode→按 own(Map)/mp(List) 分发;MissingPluginException兜底「请用 Web 端」(鸿蒙等无原生实现);SnackBar 反馈、_busy禁用按钮。 - 入口:account_page.dart 「登出」上方加「备份」按钮 →
Navigator.push(BackupPage)(底部 7 tab 已满,二级 push 同 sites_page→WebViewLogin 惯例)。 - 依赖:pubspec.yaml 加
share_plus ^10.0.0(系统分享,重导出XFile;无需单独 cross_file)。 - 验证:
flutter pub get✅(share_plus 10.1.4)、flutter analyzeNo issues found!、flutter test24/24 ✅。
移动端文件管理页(对齐 Web)(2026-06-27)
收口「移动端缺文件管理」——后端 /storages/*(list/upload/download/mkdir + storage CRUD)+ Web FileManager.vue 早齐备,移动端零文件管理代码。本次移植:浏览已有存储后端的目录 + 下载 + 上传 + 新建文件夹。无新依赖(复用备份加的 share_plus、file_picker、getBytes/postMultipart)。
- 后端小修(filemanager.go
Download):出错分支由200+JSON改500——原软失败会让客户端把错误 JSON 当成文件字节;改后getBytes自然抛ApiException,Web<a download>也从「下坏 JSON」变「显错误」。仅 download 一处。 - 新建 FileStorageApi + 模型(api/file_storage.dart):
FileStorage/FileEntry(snake_case 对齐后端)+list/files/upload/download/mkdir。软失败感知:files 读error字段、upload/mkdir 检查ok转Exception(后端软失败返 200+字段,仅 >=400 才触发 ApiException)。 - 新建文件管理页(features/file_manage/file_manage_page.dart):Scaffold+AppBar(建目录/刷新)+ 存储下拉切换 +
Wrap+ActionChip面包屑(点段截断、上级、根)+GlassCard列表行(目录前置、点目录进入、文件显fmtBytes)+ FAB 上传 + 文件行下载按钮。下载走getBytes+Share.shareXFiles(抄备份模式,MissingPluginException兜底);上传走FilePicker→MultipartFile(抄 downloaders,path 进 multipartfields);建目录走_prompt对话框。 - 入口:account_page.dart 「备份」下加「文件管理」按钮 → 二级 push(不改 home_shell)。
- 验证:后端
go vet/test全绿;移动端flutter analyzeNo issues found! +flutter test24/24 ✅。 - 非目标:
不做删除文件(✅ 见下「文件删除补全」);不做存储 CRUD(请在 Web 端配置,无存储时空态提示);上传无进度条。
移动端 NAS 监控(含摄像头实时画面,后端 HLS 改造)(2026-06-27)
收口跨端最后一项——后端 NAS 监控(设备/指标/告警/探活)+ Web NasServer.vue 齐备,移动端零监控代码。难点:摄像头实时画面被后端 jessibuca 专有格式(裸 H264 annexB + 4 字节 LE 时间戳 over WS,rtsp_session.go frameMessage)阻塞,标准播放器吃不下。用户选定「含实时画面(需后端改造)」→ 后端加 HLS 转封装出口。
- 后端 HLS(方案 B,无新外部 Go 包):新建 hls_session.go——
hlsSink用 mediacommonpkg/formats/mpegts的mpegts.Writer把已解出的 H264 AVCC 转 MPEG-TS;IDR 作分段边界,每段新建 Writer +WriteTables()写段头 PAT/PMT(段可独立解码),DTS 用h264.DTSExtractor,ring 留最近 5 段 + live m3u8。mediacommon/v2 + go-astits 本是 gortsplib 间接依赖,go mod tidy升 direct。 - 后端接入:rtsp_session.go OnPacketRTP 回调
broadcast(...)重构为dispatch(ts, nalus)(同时喂 ws jessibuca 帧 + hls sink),缓存原始 SPS/PPS;HLS 观众走租约定时器(每次请求续约 hlsLease,超时释放 sink;与 ws 观众共纳入 viewerKeepWindow 保活与全局上限 8)。rtsp_proxy.go 加ServePlaylist/ServeSegment+authHLS(复用?token=<jwt>,与 stream 同构,media_kit HTTP 不能设 header)。monitor_stream.go + router.go 加GET /monitor/cameras/:id/hls/playlist.m3u8+segment/:seq(guarded 外,token 鉴权)。Web 的 WS/jessibuca 链路完全不动。app.go 无需改(复用现有 token)。H.265 仍报错(与 Web 一致)。 - 后端验证:新建 hls_session_test.go(SPS/PPS/IDR 取自 mediacommon 官方测试向量)——断言合法 H264 → 正确 m3u8(EXTM3U/MEDIA-SEQUENCE/EXTINF)+ TS 段 188 对齐/sync 0x47/首包 PID=0(PAT)+ ring 淘汰。
go vet/test ./...全绿。e2e(真实摄像头 H264 码流)待真实环境——可用 ffmpeg+mediamtx 起本地 RTSP 源 + ffprobe 验证 ts 可解码。 - 移动端:pubspec.yaml 加 media_kit 族(+ android/ios/windows 原生 lib);main.dart
MediaKit.ensureInitialized()(try-catch 鸿蒙跳过);AndroidManifest 补 INTERNET 权限、iOS Info.plist 加 NSLocalNetworkUsageDescription。 - 移动端 API/页面:新建 api/monitor.dart(MonitorTarget/Metrics/MetricSnapshot/MonitorAlert 模型 + MonitorApi +
streamURL(id)→HLS 地址;Metrics 二次 JSON 解析);monitor_page.dart(按 host/nas/camera 分段 + 在线状态点 + 未读告警条 + 增删改入口);monitor_detail_page.dart(探活/编辑/删除 + 指标卡(CPU/内存/磁盘/温度) + fl_chart LineChart 趋势 + 磁盘/温度/网速/负载/运行时长 + 摄像头 media_kit Video 预览(16:9, dispose 释放, 鸿蒙 try-catch 降级 WebView 跳 Web 版) + 设备信息 + 该设备告警);device_form_sheet.dart(type 驱动 protocol/vendor、凭据编辑留空不改)。告警走 HTTP 轮询(对齐 Web 15s;后端无 nas-alert WS)。入口:account_page.dart 加「设备监控」按钮 push。 - 验证:
flutter analyzeNo issues found! +flutter test24/24 ✅。 - 风险/待真实环境:① HLS muxer 单元层验证 TS 结构正确,真实摄像头码流(PTS/IDR 间隔/H265)待实测;② 5-10s 延迟(监控可接受,UI 已标注);③ 鸿蒙无 media_kit 原生 → 降级 WebView;④ H.265 摄像头报错提示切 H.264。
文件删除补全(后端补路由 + Web/mobile 接入)(2026-06-27)
补全文件管理缺的删除:后端 chain.Delete 早已就绪,缺的只是 HTTP 路由 + 前端入口。
- 后端:filemanager.go 加
DeleteFilehandler(path 走 query、soft-fail{ok},与 Mkdir 同构);router.go 加POST /storages/:id/delete。 - Web:filemanager.ts 加
removeFile(id,path)(soft-fail 检查ok转 Error);FileManager.vue 操作列加「删除」按钮(ElMessageBox 二次确认,文件/目录均可删,删除后刷新)。 - 移动端:file_storage.dart 加
remove(id,path);file_manage_page.dart 行内加删除 IconButton(AlertDialog 二次确认 + 刷新)。 - 验证:后端
go vet/test全绿;前端vue-tsc+build✅;移动端flutter analyzeNo issues +flutter test24/24 ✅。
安全加固 P3:SSH hostkey TOFU + WS/RTSP 一次性 ticket(2026-06-27)
收口安全审查遗留 P3 两项:
A. SSH hostkey TOFU(ssh.go 原全后端唯一 InsecureIgnoreHostKey):监控 SSH 采集无 host key 校验,可被中间人。改为 TOFU——首次连接记录 key、后续校验、不符即拒绝(连接失败 → 设备显离线 + LastError 含 mismatch,复用现有 offline→告警链)。
- 新建
SSHHostKey模型(models.go,host+port 复合索引、Key 存 marshaled 公钥不敏感)+ oper/ssh_hostkey.go(Lookup/Record/Delete)+ AutoMigrate。 - monitor 包
HostKeyStore接口 +makeHostKeyCallback(注入避免 monitor→oper 反向依赖);NewSSHCollector(keyStore);app.go 注入 oper(结构化满足接口)。 - AcceptHostKey 端点(合法换 key 一键重置防 footgun):
POST /monitors/:id/accept-host-key→ 删该 host:port 的 key → 下次连接重新 TOFU。 - 测试 ssh_hostkey_test.go:ed25519 真公钥 + fake store 断言三态(首次记录/同 key 放行/不同 key 拒绝)+ nil store 降级。
B. WS/RTSP 一次性 ticket(原 ?token=<jwt> 落 URL + 跳过 TokenVersion 校验、登出不踢 WS):JWT 不再落 URL,顺带修登出洞。
- 新建 auth/ticket.go
TicketStore(map[ticket]{uid,exp},crypto/rand 32B、30s TTL、Consume 单次删除、惰性 sweep——复用 ratelimit/token_version 的 TTL map 范式)+ ticket_test.go(单次消费/过期)。 - 签发端点
POST /auth/ws-ticket(放 guarded 内→走 JWTAuth+TokenVersion,登出后无法再签);WS ws.go + RTSP rtsp_proxy.go HandleWS 改?ticket=+Consume。 - HLS 保持
?token=<jwt>(media_kit 多段拉取+不能设 header,单次 ticket 不适用;仅移动端内部播放器无 referer 面)。 - 跨端接 ticket:前端 useWs.ts/RtspPlayer.vue/monitor.ts streamURL + auth.ts wsTicket;移动端 ws_client.dart(HLS streamURL 不动)。
- 测试 ws_test.go 更新(?ticket=garbage→401)。
- 验证:后端
go vet/test ./...全绿;前端vue-tsc+build✅;移动端flutter analyzeNo issues +flutter test24/24 ✅。 - 风险/边界:TOFU 首次盲区(首次连接的中间人仍被记录,TOFU 固有);ticket 单实例(内存 map,与 TokenVersionCache 同约束);登出后旧 WS 长连接仍活到自然断(长连接固有,ticket 只防新连接拿 JWT);HLS 仍 query token(留作后续)。
下载器健壮性加固(qb 401/rid;TR ids 核实排除)(2026-06-27)
逐条核实体验测试遗留的「P2 下载器健壮性」三项(非盲改):
- TR ids → 核实排除(假阳性):tr/client.go 的
rpc不设请求 tag(同步请求/响应、无错配);Pause/Resume/Delete把 hashString 放进ids是 Transmission 官方支持形式;401=凭证错(Basic Auth 每请求带、无会话过期);无状态→重启即恢复。无真实缺陷,不改(同「限速单位」「NFO」核实排除)。 - qb 401 自愈(client.go:135):
doBody会话失效重试由status==403扩为403||401(qB 默认 403,反向代理/鉴权变更等可能 401)。httptest 验证首次 401→重登→200 成功。 - qb rid 重启丢弃(client.go Sync、sync.go:50):qB 重启 rid 回退、若返非 full 增量会被
r.Rid<s.Rid永久丢弃、卡死旧数据。Sync 加回归检测(r.Rid>0 && r.Rid<reqRid && !FullUpdate)→ 重置状态 + 以 rid=0 全量重拉一次(单次递归、无死循环)。httptest 验证重启后自动全量重拉。 - 验证:
go vet ./...+go test ./internal/downloader/...全绿(新增 401 自愈 + rid 回退两 httptest 用例,既有不回归)。 - 真实实例(bonus):连真 qB 重启后看自动全量重拉;401 场景难复现(qB 默认 403),靠 httptest 覆盖。
原项目功能移植 · 第一批:站免 + 公告 + PT监护室(2026-06-27)
移植原 Kotlin 项目缺失的 10 个功能的第一批(全 10 个按 4 批排期,逐批确认;本批三端全对齐)。三功能均归入「站点信息聚合」。
- 站免(Freeleech):检测每站首页全站免费/促销横幅(正在/即将/暂无/失败)。后端走站点 capability 范式(套用 H&R 全链路):
FreeleechDef/FreeleechCapable(adapter.go)+ParseFreeleech(freeleech.go,候选选择器 + 双关键词 AND + isUpcoming + findInlineBackground)+(*NexusPHP).FetchFreeleech+SiteChain.Freeleech/FreeleechAll(site_info.go,per-site 缓存 30min)+runFreeleechcron(WS 推送正在站免)。APIGET /freeleech+POST /freeleech/sync/:id。 - 公告(Announcements):抓每站首页公告,结构化标题/日期/正文。
ParseAnnouncements5 级联策略(li.index-news-item 现代 / [id^=ka] 经典 / div#announcement / rowhead 公告行 / 最大文本块兜底)。per-site 缓存 60s。APIGET /announcements+POST /announcements/sync/:id。 - PT监护室(Savept):聚合第三方 savept.icu 站点健康(正常/病危/已关站)+ 病危/恢复/站庆公告 + 抢救时长,与本地站点 4 级匹配(URL→域名→名称精确→名称模糊)。独立
internal/savept/包(不依赖站点登录),全局缓存 6h,本地匹配每请求实时算。APIGET /savept。cron 每 2h 刷新 + 病危通知。 - 关键决策:站点为全局共享配置 → per-site 缓存按 siteID 分桶(非 per-user);导出仪表盘图走客户端出图(第四批);savept/savept.icu 抓取 SSRF 守卫已启用。
- 三端:Web 加 3 视图(
Freeleech/Announcements/Savept.vue)+ 3 api 客户端 + 路由 + 导航(GlassIcon.vue 加 freeleech/announcement/savept 字形);Mobile 加 3 feature 页 + 3 api + 账户页「更多功能」入口。 - 验证:
go build/vet/test ./internal/site/... ./internal/savept/...全绿(新增站免/公告/savept fixture 钉值测试);npm run build通过;flutter analyzeNo issues。 - 文档:新增 09-站点信息聚合.md,更新 00-总览.md。
- 待跟进:① 真实 NexusPHP 站首页站免/公告横幅实测校准(fixture 现为代表性样例,按选择器校准工作流逐站钉值);② savept.icu 真实页结构校准(断网时用 fixture);③ MTeam 站免/公告(MTeam 走 JSON API,本批 NexusPHP 覆盖,MTeam 暂未实现 capability,graceful 跳过)。
原项目功能移植 · 第二批:消息 + 考核 + 后宫(2026-06-27)
第一批(站免/公告/监护室)三端全绿后,移植原项目三个最大的站点抓取功能。复用第一批的 capability 范式,引入两个新架构模式:
- 后宫(Harem):聚合各站邀请库存(剩余永/临邀请 + 可购买)+ 被邀请人花名册。
HaremCapable(adapter.go)+ParseInviteStatus/ParseInvitees(harem.go)+ cookie userId 提取(cookie.go,c_secure_passbase64→JSON)+SiteChain.HaremAll(Semaphore(5)+per-site Mutex+30min 缓存)+ 药单导出。MTeam/api/member/profile+/api/invite/getUserInviteHistory。 - 考核(Assessment):新建
internal/site/assessment/per-platform 子包 + Registry(NexusPHP HTML 跨行聚合 / MTeam/api/examine/getMyActiveList/ TNode 不支持)。DB 持久化AssessmentTask表(UNIQUE(site_id,task_name))+ 3 级缓存(内存30min→DB6h→网络)。convertTaskToCardUI 状态阈值按指标计数整数除法。assessment-refreshcron。 - 消息(Messages):
MessageCapable多方法接口(第一批只读,本批扩展写操作:FetchMessages/Content/Reply/Delete/MarkRead)。CSRF 可选(修正原版默认 reply 必失败的 bug:空 csrfField=不带 token 直接 POST)。多 mailbox(inbox/sent/system/cheater/reports/staff,STAFF 走 staffbox.php)。MTeam 只读(通知 API + 统计伪消息)。 - 关键决策落地:消息写操作 CSRF 可选化;考核 DB 持久化(进度历史趋势,第四批导出图复用);MTeam 三个功能均走 JSON API 复用
mtRequest(baseURL 已是 api.m-team.cc,无需子域改写);考核状态阈值按指标计数整数除法(非 progress%)。 - 三端:Web 加 3 视图(Harem 药单表+花名册弹窗/Assessment 进度卡网格/Messages mailbox tab+详情抽屉+回复删除)+ 3 api + 路由 + 导航(GlassIcon 加 harem/assessment/message 字形);Mobile 加 3 feature + 3 api + 账户页入口。
- 验证:
go build/vet/test ./...全绿(新增后宫/考核/消息 fixture + 考核跨行聚合 + MTeam 拼写错误字段测试);npm run build通过;flutter analyzeNo issues。 - 文档:新增 10-站点用户管理.md,更新 00-总览.md。
- 待校准(真实环境):① 后宫 cookie userId 提取 + invitee 禁用属性检测;② 考核 NexusPHP userdetails 跨行聚合 + MTeam examine 字段;③ 消息列表/正文选择器 + 回复 CSRF 字段名(各站不同,需站点 YAML
messages.csrf_field配置)。
原项目功能移植 · 第三批:种子浏览(详情页) + 推荐 + 探索(2026-06-27)
第二批(消息/考核/后宫)三端全绿后,移植最后三个最大的站点/媒体功能。两个架构决策(与用户确认锁定):① 按需联动——推荐/探索展示媒体元数据,「在站点查找」复用既有 POST /search(标题当 keyword),不预过滤、无 N×M、零后端改动;② 信息流 vs 搜索——推荐=多源热门信息流,探索=关键词跨源搜索,共享联动基础设施。
- 种子浏览(详情页):跨站搜索 → 点击单种看详情页。
TorrentDetailCapable(adapter.go,type-assert)+DetailDef/TorrentDetail(types.go)+DefaultNexusPHPDetail(defaults.go,details.php 选择器)+ParseTorrentDetail/extractMulti(seedetail.go,复用 Extract/ApplyFilters/ResolveURL)。NexusPHP(直接 GET 绝对 URL + loggedOut)+ MTeam(/api/torrent/detail {sn},schemas/seedetail.go)。顺手修 MTeam bug:parseMTTorrent补 DetailURL(原遗漏)。SiteChain.TorrentDetail(sameHost SSRF 守卫比对最后两段注册域 + 10min 缓存)。 - 推荐:新增
TrendingCapable可选接口(media.go,不破坏 base MediaSource,MusicBrainz 跳过)+ 各源Trending(豆瓣/movie/hot·/coming·/tv/hot/ TMDB/trending/{movie,tv}/week·/movie/popular/ Bangumisort=heat)+RecommendChain(recommend.go,仅内存缓存 30min 无 DB,并发扇出 semaphore 4 + 容错合并 + force 绕缓存)。APIGET /media/recommend。 - 探索:
MediaHandler.Discover直接复用既有MediaChain.Search(跨源合并 + PosterCache),source返回后过滤——零新 chain。APIGET /media/discover。 - Bangumi 客户端(bangumi.go):v0 API(可选 Bearer token),Search(
filter.type [2,6]动漫+三次元)/ Detail / Trending。无需扩MediaInfo(既有 Source/Type 够用)。配置BANGUMI_TOKEN;图片反代mediaImageHosts加lain.bgm.tv/bgm.tv。 - 三端:Web 加 3 视图(SeedBrowse 跨站搜索表+详情抽屉 / Recommend 卡片网格+源/分类 chip+在站点查找 / Discover 搜索框+媒体卡+在站点查找)+ 3 api + 路由 + 导航(GlassIcon 加 seedbrowse/recommend/discover 字形);Mobile 加 3 feature 页 + 3 api + 账户页入口(7 底部 tab 满,二级 push)+ 共享 widget
media_poster.dart/find_on_sites_sheet.dart。 - 验证:
go build/vet/test ./...全绿(新增详情 fixture + MTeam sn 抽取 + parseMTTorrent DetailURL 回归 + Bangumi search/detail/年容差 + 三源 Trending + RecommendChain 合并/缓存/force/过滤);npm run build✅;flutter analyzeNo issues。 - 文档:新增 11-种子浏览与媒体发现.md,更新 00-总览.md。
- 待校准(真实环境):① 种子详情 NexusPHP
Detail.Fields各站选择器(/sites/inspect校准);② MTeam/api/torrent/detail端点/sn/字段名;③ Bangumirating形态;④ 豆瓣 frodo 热门路径与响应结构;⑤ 各源 Trending 字段。
原项目功能移植 · 第四批:导出总仪表盘图片(2026-06-27)
收口最后一批——导出仪表盘为可分享图片。架构决策(早前锁定):走客户端出图,无后端改动(仪表盘数据既有 /dashboard,导出纯前端)。捕获范围 = 总览摘要(指标 + 近 7 天趋势),非滚动站点明细表(表格不适合出图)。零新增依赖。
- Web(utils/dashboardImage.ts):零依赖 Canvas 2D 绘制深色品牌分享卡(紫蓝渐变背景 + 标题时间戳 + 5 指标块[色条+自适应字号] + 近 7 天柱状图[渐变+值标签+日期] + 页脚),@2x 渲染清晰;
exportDashboardImage()→canvas.toBlob触发 PNG 下载。不引入 html2canvas(项目刻意保持依赖精简,echarts 已移除)。Dashboard.vue page-head 加「导出图片」按钮(download字形)。 - Mobile(dashboard_page.dart):RepaintBoundary.toImage 截分享卡 widget → PNG → 复用既有
share_plus(XFile.fromData,同备份导出范式)分享/保存。点「导出图片」弹预览底栏(_shareCard在RepaintBoundary内,已 layout+paint)+「分享」按钮 → toImage(pixelRatio:3)。分享卡用手动柱状图(不依赖 fl_chart,截图可靠)。MissingPluginException兜底「请用 Web 端」(鸿蒙等)。 - 验证:前端
vue-tsc+npm run build✅;移动端flutter analyzeNo issues。 - 非目标:不导出滚动站点明细表(适合 CSV,不适合图);Web 端为下载、移动端为系统分享(各自平台惯用法)。
| 包 | 测试 |
|---|---|
| httpclient | SSRF IP + retry + CF + cookie + ssrf dial 守卫(激活拦截私网/未激活降级) |
| core/event | 广播扇出 + 链式顺序 + panic 隔离 |
| core/module | Skip/Continue/Stop + 取消 |
| core/security | AES 往返 + bcrypt + JWT 往返/篡改/过期 |
| core/config | release 强校验(缺/默认值拒绝 + 强密钥通过 + dev 回退) |
| auth | 激活码生成/校验/激活/过期/吊销/复用 |
| chain | RunModule 三语义 + SiteChain 调度 + ErrNoAdapter + SearchSites 并发(耗时证非串行+err 隔离)+每站超时(ctx 取消 hang 站) + RecommendChain(多源 Trending 合并 + 二次命中缓存源不重复调 + force 绕缓存 + source 过滤 + 无 Trending 能力源跳过) |
| site | selector 提取/过滤器/ParseBytes(含 TiB/GiB) + Selectors 回退/Parent/:::N 取组 + search fixture + nologin + IsVIP + hxpt 校准(2) + HR 解析(ParseHR/时长/危险判定) + 三站真实页面 fixture 钉死(hxpt/cspt/crabpt usercp+userdetails:color_* 锚点 + rowhead level/join + uid 发现) + 站免(ParseFreeleech 双关键词 AND) + 公告(ParseAnnouncements 5 级联) + 后宫(ParseInviteStatus 4 策略 + ParseInvitees 流量/分享率/禁用属性 + IsDead/IsLowRatio 计算字段 + cookie userId base64 提取) + 消息(ParseMessages 多 mailbox + 日期探测 + unread + SynthesizeMsgID + ParseMessageContent 4 策略) + 种子详情(ParseTorrentDetail fixture:简介/截图[]/IMDBID/DoubanID/DownloadURL 绝对化 + DefaultNexusPHPDetail + EnsureDefaults 填充 + 多值 extractMulti) |
| site/schemas | nexusphp httptest(搜索+登录检测) + keepalive(Set-Cookie 轮换+失效) + hr(抓取+登出) + mteam(canned JSON 搜索/profile + 401/业务错误) + nexusphp 多页 FetchUserInfo(usercp→uid→userdetails/mybonus/getusertorrentlistajax 聚合 + 子页 404 容错 + seeding_size 逐单元格累加) + 后宫(invite.php 翻页≤20 + MTeam profile/invitee JSON) + 消息(多 mailbox + 正文 + 写操作 CSRF 可选 + MTeam 通知/统计伪消息) + 种子详情(NexusPHP 直接 GET 绝对 URL + MTeam /api/torrent/detail JSON 映射 + mtSNFromDetailURL 末段/query 抽 id + parseMTTorrent 补 DetailURL 回归) |
| site/assessment | per-platform Registry(NoOp 兜底) + NexusPHP HTML 跨行聚合(normalizeHTML <td>→换行 + mergeAssessmentLines + header A/B + 指标 Phase1 单行/Phase2 逐行 + 结果 isResultCompleted 排除「未达标」+ 单位归一 size→GB/time→小时 + endTime 不可解析→UNKNOWN) + MTeam examine(拼写错误字段 uploaed/downloaed/bonuses) + TNode 不支持 |
| db/oper | site AES-GCM + registration CRUD/状态流转 + URL scheme 白名单 + assessment BatchUpsert(事务删后插)/CleanupExpired/LatestFetchedAt |
| chain(registration) | signup 开放/关闭判定 + 转换去重 |
| downloader/qb | rid merge(全量/增量/过期) + httptest(登录/版本/同步/添加/分支) |
| downloader/tr | 409 session 握手 + torrent-get |
| cookiecloud | AES 往返 + host 匹配 |
| media | TMDB httptest + NFO + ErrNotConfigured + 豆瓣(canned frodo 搜索/详情 + 微信 UA/apiKey 注入 + 年份过滤 + subjects.items 改版适配) + posterURL(m_ratio_poster 规整) + Bangumi(search/detail/年容差 + Trending heat) + 三源 Trending(豆瓣 hot/coming/tv + TMDB trending/popular + 未配置 ErrNotConfigured) |
| api/v1 | media 图片反代白名单(mediaImageReferer:doubanio/tmdb/bgm 放行 + 后缀伪装/SSRF 拒绝) |
| filemanager | WebDAV httptest(列目录/上传/下载) + BasePath resolvePath(越界约束) |
| keepseed | 评分(相同/悬殊/无关/空) |
| notification | 扇出隔离 + webhook httptest |
| mobile | 搜索 + 仪表盘 + WS 推送 + 媒体 + 娱乐模型 fromJson(/search、/dashboard、/dashboard/trend、ws 种子快照+H&R+cookie、/media/search、/entertainment 含缺字段容错) + MediaInfo.detailUrl(豆瓣/TMDB/MB 映射+缺 id 容错) + 站免/公告/savept 模型 fromJson(状态/匹配枚举容错) + 种子详情/推荐/探索模型 fromJson(TorrentDetail + MediaInfo + 缺字段容错) + 辅种 reseed(ReseedTask 模型 + reseedApi) + 冒烟测试 |
| savept | parseSavept(卡片 data-* 抽取 + 状态归一 + 抢救时长回填 + 公告切片) + matchCards(4 级匹配优先级 + 已绑定回填) + summarize + normalizeStatus 关键词 |
| ws | HRAlert/Cookie/Freeleech 编码(hr-alert/cookie-alert/freeleech-alert) + torrents 消息(any Data 兼容) |
| entertainment | 4 游戏 × 成功/失败 + Registry |
| api/middleware | ratelimit burst + per-key + 桶空闲回收 + 安全响应头 |
| api | 端到端用户旅程(httptest: health→setup→login→建站→下载器→通知→备份→VIP→SPA) + WS 鉴权(无/坏 token→401) |
IYUU 精确辅种(infohash 跨站匹配 + 一键添加)(2026-06-28)
既有 keepseed 仅名称模糊匹配(keepseed.Score Jaccard+大小),包注释明写「对接 IYUU 精确 infoHash 匹配作为后续增强」。本轮实现精确辅种闭环。协议参考 Go 实现 goreseed + api.iyuu.cn(已核实):sign 字段即 IYUU token(无额外签名);客户端按站点目录 + torrent_id + 本地 passkey 拼 download.php?id=&passkey= 直链。
- IYUU 客户端(新建 iyuu/iyuu.go):
HashIndex(token, hashes)POSTApp.Api.Hash(multipart,sign=token)→ 每 hash 的[{sid, torrent_id, info_hash}];Sites(token)POSTApp.Api.Sites→ 站点目录;BuildDownloadURL移植 goreseed 平台分支(默认 NexusPHP / m-team&https=1/ ttg / ccfbits)。复用共享httpclient(SSRF 守卫对公网 api.iyuu.cn 放行)。httptest 测试钉死响应 + URL 拼装各分支。 - Chain(keepseed.go):
Reseed(dlID)取下载器做种 hash(需 save_path 复用路径)→ IYUU 匹配 → 按 base_url host 匹配本地 Site → 解密PasskeyEnc(新增siteOp.DecryptedPasskey)→ 拼 URL → 落CrossSeedTask{status=pending}(同 hash+候选站去重);Resolve(taskID, add|skip):add 走AddTorrent(URL, SavePath, Paused)。token 存SystemConfig(新建SystemConfigOper,对齐「runtime settings in DB」)。 - 可测性:chain 下载器依赖抽成窄接口
dlProvider/reseedClient(仅 ListTorrents+AddTorrent)+ManagerProvider适配真实 manager——测试注入 2 方法 fake,不必实现完整 Downloader 接口。 - API(keepseed.go):
GET/PUT /keepseed/config(token 掩码读/明文写)、POST /keepseed/reseed、GET /keepseed/tasks?status=、POST /keepseed/tasks/:id/resolve;保留POST /keepseed/find。 - Web:新建 Reseed.vue + api/reseed.ts + 路由/侧栏「辅种」项——配置 token、选下载器开始辅种、候选任务表(状态筛选 + 添加辅种/跳过)。
- 模型:
CrossSeedTask加downloader_id/save_path。 - 移动端收尾:新建 api/reseed.dart(ReseedTask 模型 + ReseedApi)+ reseed_page.dart(token 配置 + 下载器下拉 + 开始辅种 + 任务卡列表[状态筛选 + 添加辅种/跳过]),account_page 加「辅种」入口。复用后端
/keepseed/*API,对齐 Web Reseed.vue。flutter analyze全绿。 - 定时自动辅种 cron(2026-06-28 补):
CrossSeedSetting加AutoReseed/AutoReseedAdd(per-downloader);chain/keepseed.go加Setting/SaveSetting/ListAutoReseedSettings;新建 automation/keepseed.go 的runKeepSeed——遍历auto_reseed=true的下载器重跑Reseed刷新 pending,AutoReseedAdd开则对 created 逐个Resolve("add")(默认非破坏:无启用即零动作、auto-add 默认关)。automation.Service依赖收窄为keepSeedRunner接口(测试注入 fake)。配置KEEPSEED_CRON(默认0 3 * * *)。APIGET/PUT /keepseed/setting;Web/Mobile「开始辅种」卡各加「定时自动辅种/自动添加」两开关。GORM 坑:PauseAfterAdd的default:true使 bool false=零值落库被改写成 true →SaveSetting新建走 map-basedUpdates强制写零值。chain/automation 测试覆盖 scan-only/auto-add/零启用 三路径。 - MTeam 走 API genDlToken 下载(2026-06-28 补):MTeam 禁 passkey 直链,原辅种对 MTeam fails。mteam.go 加
GenDlToken(multipart/form-dataid+x-api-keyPOST/api/torrent/genDlToken,envelopedata= 签名 URL 字符串;契约源 pt_mate + MoviePilot)。URL 有时效 → 添加时(Resolve)现取:CrossSeedTask加SiteID;Reseed 对local.Platform=="mteam"存哨兵mteam:<id>(复用现有siteOp.APIKey()解密判凭据),Resolve 对哨兵按 SiteID 取站 → genDlToken → AddTorrent;其它站 passkey 流程不变。chain 依赖收窄mtTokenGen接口(测试注入 fake)。mteam_test + keepseed_test 全绿。 - 仍不在本期:无——IYUU 辅种 deferred 项全部完成,剩真实环境联调(用户 MTeam api_key + iyuu.cn 绑站 + 有做种下载器)。
- 验证:
go test ./internal/...全绿(iyuu httptest + chain Reseed/Resolve 链路 fake downloader);Webvue-tsc+npm run build✅。离线仅验证协议拼装与链路;真实 IYUU 匹配需用户 token + 已绑定站点 + 有做种的下载器实测。
娱乐:NovaHD 三娱乐适配(魔力农场 / 刮刮乐 / 幸运抽奖)(2026-06-29)
NovaHD(pt.novahd.top,NexusPHP)此前无娱乐源。用户提供站点源码备份(F:\新建文件夹 (3)\好学备份\pt.novahd.top\nexusphp)+ 页面 HTML(D:\html),据此精确适配三个娱乐功能。三个均纯 Cookie 鉴权,货币 = users.seedbonus(魔力值),注册 host=novahd.top(后缀匹配 pt.novahd.top)。
- 魔力农场(gameKey=
novahd_farm,magic_fram.php):站点为 GET + 整页 HTML(无 JSON API),故新增Farmable能力接口(Farm/Plant/Harvest/HarvestAll/Sell,section=crop→plant / animal→breed)。配置源码写死(Go 端常量化):小麦4h/500、玉米8h/1000、花生12h/1500、土豆24h/2000(crop expire 2 天);鸡24h/1000、猪48h/2000、羊72h/5000、牛96h/10000(animal expire 5 天)。状态由页面链接判定(含 harvest 链接=ready / plant·breed=idle / 否则 growing);余额抠.points-display、提示抠.message、仓库抠warehouse-table、菜市场抠两张market-table(第一张 crop 其后 animal)。 - 魔力值刮刮乐(
novahd_scratch,magic_scratch.php):buy(POST→HTML 抠currentScratchId)→scratch(POST→JSON{success,prize,prize_level,scratch_id})→settle(GET)。6 档常量化:未中奖40%/五等500/四等1000/三等3000/二等5000/一等100000,cost=5000 daily_limit=5。复用LotteryGame+Statable+Prizeable+Historable(与 hxptMagicScratchGame同构)。 - 幸运抽奖(
novahd_lucky,fortune-wheel-spin.php):fortune-wheel-plugin 是 Laravel 商业授权插件(license.dat + 域名/机器码校验;PTPatronus 代理请求站点自身端点不受影响)。POSTcount=1|10|20|50→ JSON{success, results:[{status:awarded|nothing, type:bonus|attendance_card|upload|nothing, value, unit}]};顶层PlayResult.Amount仅累加 type=bonus(补签卡/上传 value 是其他单位)。 - 三端:后端 6 处同步(entertainment.go 接口+DTO → novahd.go → chain 分发+spendGuard → handler → router → app.go Register)+ novahd_test.go(httptest mock);Web 3 富组件(
NovaHDFarmGame.vue/NovaHDScratchGame.vue/NovaHDLuckyGame.vue)+ Entertainment.vue 分发;Flutter 3 View + entertainment_page.dart 分发。 - 验证:后端
go test ./internal/entertainment/...+go build ./...全绿;Webnpm run build(vue-tsc)✅;Flutterflutter analyzeNo issues found!。 - 待真实页面校准:① 农场余额/仓库/菜市场 HTML 抠取正则(对照
D:\html真实页面);② 刮刮乐Historable(记录区)、Stats(余额/今日剩余);③ 抽奖Stats(消耗/今日,渲染在 fortune-wheel-plugin/resources/views/wheel.php)、Prizes(grades)、Historable——目前宽松抠取或返回空,待联调校准。
娱乐:好学农场接入(魔力农场通用化)(2026-06-29)
好学(hxpt)也有"魔力农场",源码 F:\www.hxpt.org\public\magic_fram.php(1009 行)与 NovaHD(872 行)对比:同一套插件的不同版本——name/grow/price 配置、操作 action(plant/breed/harvest/harvest_all/sell)、渲染骨架(farm-item/harvest-btn/plant-btn/breed-btn)完全一致;差异仅货币名(好学"火花"/NovaHD"魔力值",余额抠 .points-display 数字不依赖文本)、农作物有效期(好学5天/NovaHD2天)、好学无 img 字段(用 pic/farm/{name}.png)、市场规则文本(好学±50%每日6刷/NovaHD±20%每日1刷)。
- 通用化:
NovaHDFarmGame→MagicFarmGame(gameKeynovahd_farm→magic_farm),注册 hxpt.org + novahd.top 两 host 共用一个 Game + 一个富组件。 - expire 通用化:农作物/动物有效期两站不同,改为从页面 section 标题抠(
magicFarmExpireDays:「农作物有效期N天」/「动物有效期N天」),不再写死常量;magicFarmItemDef去掉 ExpireDays 字段。 - 三端改名:Web
NovaHDFarmGame.vue→MagicFarmGame.vue、Flutternovahd_farm_view→magic_farm_view(类MagicFarmView)、Entertainment.vue/entertainment_page.dart分发 key 改magic_farm;测试加 expire 抠取断言。 - 验证:
go test ./internal/entertainment/...+go build/ Webnpm run build/ Flutterflutter analyze三端全绿。
娱乐:好学九宫格 连抽(10/20/50/100)+ 抽奖历史(2026-06-30)
九宫格(jgg,hxpt)此前仅单抽。站点源码 F:\www.hxpt.org\public\jgg\magic_grid_bulk.php + jgg.php(get_history/get_stats)证实站点原生支持连抽与历史,对照 Kotlin 旧版 HxptJggGame.kt 适配。
- 连抽(
JggGame实现MultiPlayable):POST /jgg/magic_grid_bulk.phpformbulk_count=N,站点服务端白名单仅[10,20,50,100](Go 端同步拦截非法值)。响应{success, results:[{prize,message}], prize_stats:{name:count}, total_cost, new_balance};每次prize聚合进PlayResult.Prizes(前端按prize_name分组统计,等价站点prize_stats)。奖品value按类型换算(jggPrizeValue:upload/download 字节→GB、vip/noad 秒→天、invite→个),对齐站点formatPrizeValue。 - 历史(
JggGame实现Historable):POST /jgg.php action=get_history→{history:[{prize_name,prize_type,prize_value,cost,created_at}]}。prize_value是 PHP serialize 标量(如i:10737418240;=10GB),新增phpSerializedNum(正则^[id]:(-?[0-9.]+);)还原后再走jggPrizeValue换算。 - 零接线:chain/API/router 的
play-multi+history早就是按能力接口分发的通用路由,本次只给JggGame加两个方法即自动暴露(resolve()+类型断言,spendGuard 防双击)。 - 三端 UI:Web
JggGameUi.vue+ Flutterjgg_game_view.dart加「🎀10/💖20/✨50/🌸100 连抽」按钮(2×2)+ 连抽结果面板(分组统计 × 消耗/余额)+ 📜抽奖历史区(prize_name/-cost/时间,最多 20 条)。连抽无单一中奖格,仅做装饰性外圈流转反馈(区别于单抽落到中奖格)。 - 验证:后端
go test ./internal/entertainment/ ./internal/chain/(含TestJggGamePlayMulti/TestJggGameHistory/非法次数拦截)+go build ./...全绿;Webvue-tsc --noEmit✅;Flutterflutter analyzeNo issues found!。
MoviePilot 第三方插件生态移植(2026-07-12 起,7 批进行中)
盘点 59 个 MoviePilot 第三方插件仓库,对照已实现功能识别出 17 个「值得移植」的缺口功能,按 P0–P3 全做、三端对齐、验证=编译+单测+mock(真实联调留用户)。完整计划见会话 plan。每批 1 commit 不 push。
- 批次1(commit
61ac5f9/abaadd1/5c5437a,PT 核心):刷流引擎(BrushFlowTask/Seed + chain 选种/扫删/cron,tagbrushflow:<id>识别规避跨系统 infohash 匹配)+ 未辅种检查(KeepSeedChain.FindMissing 复用 iyuu 查漏,与辅种互补)+ 保种契约(ContractRule per-site 下限 + evalBreaches 纯函数 + UserInfo 现场比对 + 当天去重通知 + 每日 9 点 cron)。三端 + glyph brushflow/contract。 - 批次2(媒体服务器基础,2026-07-12):
- 新包
internal/mediaserver/:统一Client接口 +embyJellyfinClient(共源 REST,X-Emby-Token头鉴权,/Library/MediaFolders列库、POST /Items/{id}/Refresh?Recursive=true扫库、POST /Notifications/Admin通知、/Items?ParentId=取近期条目 +/Items/{id}/Images/Primary海报)+plexClient(/library/sections+X-Plex-Token、GET/library/sections/{id}/refresh扫描、无通知端点返明确错误、/library/metadata/{rk}/poster海报)+ 海报墙封面GenerateCover:area-average 降采样拼网格 JPEG,纯标准库image无第三方依赖(缺图跳过留深色背景)。 MediaServermodel(多实例 CRUD,同 Downloader 范式;token 加密往返;事件开关 per-serverRefreshOnDownload/NotifyOnPlay)+ oper(类型白名单emby|jellyfin|plex,包ErrInvalidInput供 handler 判 400)+ AutoMigrate。MediaServerChain:CRUD 透传 + 按需构建 client(解密凭据,内网AllowPrivateHTTP 客户端)+ 库扫描/刷新/通知/封面 +OnDownloadCompleted(下载完成→扫所有RefreshOnDownload=true服务器,best-effort 单失败不阻断)+ReceiveWebhook(Emby/JF/Plex 播放回调解析标题→通知中心)。- API
/mediaserver/*(CRUD +/test+/libraries+/scan+/refresh+/notify+/cover)+ 公开/mediaserver/:id/webhook(媒体服务器无法带 JWT)。 - 下载完成→扫库接线:
ws.Broadcaster加SetDownloadDoneHook(同SetNotice注入范式,detectDone内持锁调用),app.go接线mediaServerCh.OnDownloadCompleted(非 bus 事件,避免改 broadcaster 构造签名)。 - 三端:Web
MediaServer.vue(卡片 + CRUD + 连接测试 + 库管理 sheet + 封面 blob 预览)+ Fluttermedia_server_page(卡片 + 表单 + 库 sheet +Image.memory封面)+ glyphmediaserver两端 1:1 同步。 - 测试全绿:mediaserver(Emby httptest 全流程 + Plex section id + Plex 通知不支持 + GenerateCover 端到端尺寸/JPEG/坏图跳过 + resizeImage 纯色保真);chain(parseMediaWebhook 4 形态 + oper CRUD token 往返 + OnDownloadCompleted 触发 mock 扫库)。
- 新包
- 批次3(云盘 + STRM,2026-07-12):
- 新包
internal/cloud/:统一Client接口 + 三家原生 HTTP 客户端 ——client115(cookie 鉴权,按cid列目录webapi.115.com/files,无 sha1=目录)+clientQuark(cookie 鉴权,按pdir_fid列目录drive-pc.quark.cn,file_type=0=目录)+client123(123云盘官方 API,Authorization: Bearertoken 鉴权,按parentFileId列目录,code=0成功)。三家网盘均为公网站点 → SSRF 守卫客户端(AllowPrivate=false,Do(ssrf=true));按「路径」语义列目录(内部resolvePath从根 id 逐级按名解析 + 翻页)。字段名/端点基于社区逆向契约,单测用 httptest mock 覆盖,真实联调留用户(代码标TODO 联调)。 - STRM 生成器
cloud.Generate:遍历云盘目录树(递归可选)→ 按扩展名识别媒体文件(mkv/mp4/ts/iso 等)→ 按模板生成本地.strm(默认{mount}{cloudpath},支持{mount}/{cloudpath}/{relpath}/{filename})→ 本地幂等(已存在且内容相同跳过)+ 软上限防失控 + 单文件失败不阻断。 CloudDrive/StrmRule/StrmRecord三 model(多实例 CRUD,同 MediaServer 范式;cookie/token 加密往返;StrmRecorddrive_id+cloud_path唯一索引去重)+ oper(类型白名单115|quark|123,级联删规则+记录)+ AutoMigrate +CloudStrmCron(默认每 6 小时)。CloudChain:CRUD 透传 + 按需构建 client(解密凭据)+ 浏览/配额/探活 +GenerateStrm/GenerateAll(规则驱动,落库 StrmRecord)+ 订阅追更SyncSubscriptions(复用 SubscribeChain 列订阅,按文件名归一化匹配订阅标题→链接记录 + 通知中心;归一化剥离年份/分辨率/编码噪声)。- filemanager 云盘后端:
cloudBackend适配器把云盘 client 暴露为StorageBackend(只读List,写操作返ErrCloudReadOnly),buildBackend加115/quark/123case,验证层放行(网盘为固定公网端点,无自定义 URL)。文件管理器可浏览云盘资源。 - API
/cloud/*(子分组/drives/rules/records避免:id与静态段冲突):CRUD +/test+/quota+/list(浏览)+ 规则/generate+/generate-all+/sync-subscriptions。 - cron:
cloud-strm(每 6 小时 → GenerateAll + SyncSubscriptions,单规则失败不阻断)。 - 三端:Web
Cloud.vue(三 Tab:网盘卡片+CRUD+测试+容量+浏览 / STRM 规则表+CRUD+生成 / STRM 记录表,浏览弹窗选目录)+ Fluttercloud_page(TabController 三 Tab + 卡片/列表 +showAdaptiveSheet表单+浏览 + 追更/生成全部)+ glyphcloud(云朵)两端 1:1 同步。导航「基础设施」组,蓝青渐变#4cc9f0→#4361ee。 - 测试全绿:cloud(cleanCloudPath/splitSegments/IsMediaFile/RenderTemplate 纯函数 + 115/夸克/123 httptest 列目录+路径解析+Bearer 头 + STRM 生成器 fakeClient 端到端写入/幂等/非递归/去扩展名);chain(CloudDrive CRUD+token 往返+白名单 + StrmRule/Record CRUD+Upsert + normalizeName/matchAnySubscription + 删盘级联)。go build ./... + go test ./... / vue-tsc + npm build / dart analyze 全绿。
- 新包
- 批次4(字幕 + AI字幕,2026-07-12):
- 新包
internal/subtitle/:统一Provider接口 + 四源 ——assrtProvider(ASSRT 官方 API,token 鉴权,/v1/sub/search+/v1/sub/detail取 zip)+opensubtitlesProvider(官方 REST,Api-Key头,/api/v1/subtitles搜 +/api/v1/download换直链)+subhdProvider/zimukuProvider(goquery 抓取,社区逆向选择器,标 TODO 联调)。四源均公网站点走 SSRF 守卫客户端。 - 下载管线
subtitle.Extract(zip 魔数检测 +archive/zip取字幕文件,RAR/7z 返明确错误)+DecodeUTF8(GBK/GB18030→UTF-8,国内站常见)+Convert(OpenCC 繁简,github.com/longbridgeapp/opencc纯 Go 内嵌字典,t2s/s2t 缓存实例)。仅对文本字幕(srt/ass/ssa/vtt)+ 源语言与目标不同时转换。 SubtitleSourceProvider(SystemConfig JSON blobsubtitle.*键 + env 默认ASSRT_TOKEN/OPENSUBTITLES_KEY,ApplySettings 即时 rebuild,同 MediaSourceProvider 范式)+SubtitleChain(并发跨源搜索 singleflight-like 聚合 + 单源失败不阻断 + 按 Score 降序 + 语言过滤;Download 解 zip→UTF-8→繁简)。- API
/subtitle/search({results:[...]})+/subtitle/download(POST 字幕标识 → 附件返回字节)+/subtitle/configGET/PUT(secret 脱敏 +_configured+ 空字段保留)。 - 三端:Web
Subtitle.vue(搜索栏+语言筛选+结果列表+下载触发浏览器另存 + 源设置弹窗)+ Fluttersubtitle_page(搜索+结果列表+postBytes下载经 path_provider+share_plus 分享 + 源设置 sheet)+ glyphsubtitle(对话气泡)两端 1:1。ApiClient 加postBytes(getBytes 的 POST 对偶,文件流下载)。导航「基础设施」组,蓝紫渐变#4361ee→#7209b7。 - AI字幕市场插件
market-plugins/ptp-ai-subtitle/:external-http Python 插件,faster-whisper 转录生成 .srt + 可选 OpenAI 兼容接口翻译(双语/单语);actions transcribe/scan/status + Schedule;pluginctl package签名(Ed25519,复用 examples/plugin-market/keys)+ 入市场索引(4 官方插件);TestRealMarketFetchAndInstall验全 4 真安装链。 - 测试全绿:subtitle(normalizeLang 双语优先/zh-tw→cht + IsSubtitleExt + Extract zip/直传 + DecodeUTF8 GBK 往返 + Convert t2s/s2t/noop);providers(ASSRT/OpenSubtitles httptest + Api-Key 头 + ErrNotConfigured + SubHD/Zimuku mock HTML 去重);chain(并发聚合+失败源跳过+Score 排序+语言过滤 + Download 繁转简端到端 + shouldConvert 表)。go build + test / vue-tsc + npm build / dart analyze + flutter build windows 全绿。
- 新包
- 批次5(Emby 进阶 + 多评分,commit
99e722e,2026-07-12):- Emby 进阶:mediaserver.Client 扩展
Users/PlayedItems/LibraryItems(Emby/JFIsPlayed过滤 + ProviderIds + PlayCount/LastPlayedDate;Plex 历史需 Pass 标 TODO)→ 观影报告 PlaybackReport(枚举用户→各取最近 N→聚合用户/热门/最近/总播放,days 窗口)+ 重复检测 DetectDuplicates(TMDB>IMDb ID 优先,回退 normalizeTitle+年份,Count>=2 组)+ 302 直链反代GET /mediaserver/:id/direct/*path(公开静态改写 DirectLinkBase 不泄露 Token,对标 MP Emby 直链省本机带宽)。 - 多评分:MediaInfo 加
VoteCount(TMDB/豆瓣/Bangumi 三源)+ 新增 OMDb IMDb 源(?t最佳匹配 +parseVotes/parseFloat解析"500,000"/N/A",OMDB_API_KEY空=跳过)→MediaChain.AggregatedRating(并发跨源按标题+年份取每源投票最高 match,投票数加权综合,6h 内存缓存避订阅击穿)+ 三道闸门evaluateGates(min_score/min_votes/min_release_days)+ 订阅低分保护(SubscriptionEnableRatingGuard,全局 MediaQualityConfig 与单订阅开关取并集,查询失败 fail-open)。API/media/qualityGET/PUT +/media/rating预览 +/mediaserver/:id/playback-report|duplicates。三端 glyphmediaquality(星形)红橙渐变。 - 测试全绿(EmbyAdvanced httptest + DetectDuplicates 归并 + OMDb 三源 + evaluateGates 三关 + AggregatedRating 加权 + subscribe ratingGate 四态)。
- Emby 进阶:mediaserver.Client 扩展
- 批次6(通知渠道补全 + 论坛签到扩展,commit
9e2c2c6,2026-07-12):- 通知渠道补全:
internal/notification/新增 11 通道(PushPlus/PushDeer/ntfy/gotify/Apprise/WorkWechat/WxPusher/Lark/Discord/MQTT/Server酱);MQTT 纯标准库自实现 3.1.1 报文(CONNECT/PUBLISH QoS0/DISCONNECT,不引 paho);NotificationSettings 扩 11 子结构 + provider rebuild(秘密字段空串=保留)+ API GetConfig/SetConfig(maskSecret +*_configured);三端设置页 11 el-input 卡片。 - 论坛签到扩展:新子包
internal/plugins/forumsignin/(6 站 handler:NodeSeek JSON + 蜂巢/飞牛/柠檬/国语视界/SSDForum 共享 nexusSignin 基类 + classifySigninBody 分类)+ Engine(逐站签到+composeReport 通知+失败重试 KV+time.AfterFunc)+forum_signin.go插件入口(ConfigSchema + Schedule 每日 8 点)+ manager 注册ptp-forum-signin。凭据由插件配置承载(key|base|cookie[|path]每行)。 - 测试全绿(channels_extra httptest 10 通道 + MQTT 本地 broker done channel 避竞态 + forumsignin NodeSeek/nexusSignin/Engine 端到端)。
- 通知渠道补全:
- 批次7(Plex 套件 + MCP + Trakt + OIDC + 自定义命令,本批):
- Plex 套件(内置插件
ptp-plex-suite):新子包internal/plugins/plexsuite/(自包含 Plex REST 客户端,X-Plex-Token头,内网AllowPrivate)实现 4 特性——库本地化(PUT /:/prefs?Language=)+ 跳片头分析触发(GET /library/sections/{id}/analyze,需 Plex Pass 标 TODO)+ 自动音频/字幕语言(/:/prefs?AudioStreamLanguage=&SubtitleStreamLanguage=)+ IP 优选(并发探测候选地址取最快写回 server_url);凭据由插件配置承载不耦合 mediaserver 表;Schedule 每日 4 点 + actions apply/test/ip_optimize。特性 pref 键名标 TODO 联调(httptest mock 覆盖探测/排序)。 - MCP Server(
internal/mcp/+ 插件ptp-mcp-server):JSON-RPC 2.0 over streamable HTTP 核心子集(initialize/ping/tools/list/tools/call/notifications/initialized),纯标准库;公开路由/api/v1/mcp(GET/POST,由 Server 自身校验enabled+ Bearer token);注册 5 只读宿主工具(system.info/sites.list/subscriptions.list/downloaders.list/torrents.search,密钥字段 modeljson:"-"不泄露);配置(启用/令牌/基地址)由插件承载,Server 经SettingsGetter每请求读插件配置(单一真源热改)。工具错误以isError=truecontent 返回不破坏 JSON-RPC 信封;notifications 返 202 不回包。 - Trakt 同步(chain,需 SubscribeChain):新包
internal/trakt/(Device OAuth:/oauth/device/code+/oauth/device/token轮询 +authorization_pending/slow_downDeviceTokenError 不按 400 失败 + RefreshToken +/users/me/watchlist/{movies,shows}解析 tmdb/imdb/trakt ids)+TraktChain(StartDeviceAuth→PollDeviceStatus→refreshTokenIfNeeded→SyncWatchlist 映射为 Subscription,subCh.Create幂等去重;settings 走traktStore接口便无 DB 单测);API/trakt/*(config GET/PUT 脱敏 + device-code/device-status/sync/history)+ 公开/trakt/deviceHTML 引导页(JS 自动轮询);cron 每小时同步。三端 Trakt 页(设备授权引导 + 同步状态 + 配置)+ glyphtrakt(播放列表)粉红渐变两端 1:1。 - OIDC/SSO(
internal/auth/oidc.go+ 路由):通用 OIDC Authorization Code + PKCE(S256) 兼容 Authelia/Keycloak/Casdoor;discovery/.well-known/openid-configuration10min 缓存;公开/auth/oidc/start(state+verifier cookie + 302)+/auth/oidc/callback(state 校验 + 换令牌 + userinfo/id_token fallback + upsert Useroidc:<sub>+ 签 JWT + 302 回前端?oidc_token=)+ 管理员/auth/oidc/configGET/PUT;/auth/status加oidc_enabled/oidc_display_name驱动登录页入口。用SettingsStore接口破 auth→oper→monitor→auth 循环(同 notice.Store);OIDC 由管理员启用视为可信身份(VipLevel=1 放行 VIP 准入,首个 OIDC 用户为 admin)。三端登录页加 OIDC 按钮(Web 整页跳转 + 回调读?oidc_token存 token;Flutter url_launcher 开浏览器,deep-link 取回令牌留 TODO)+ Web 设置页 OIDC 配置 Tab。 - 自定义命令(内置插件
ptp-custom-command):定时在宿主机执行一组 shell 命令并按退出码推送(name|command[|timeout]每行 + 全局 cron + 成功/失败推送策略 +sh -c+ 超时 kill);与工作流 ssh_command(agent 远程)互补,本插件面向宿主机本机;actions run/test + Schedule 每 6 小时。单测覆盖 parseCmdItems/atoiOr + runOne 成功/失败/超时 + 推送策略。 - 验证全绿:go build + vet + test(mcp initialize/tools/error/notif + 401/404 门禁;trakt device code/pending(400)/success/watchlist httptest;plexsuite 探测排序;custom_command 超时/失败;trakt chain settings 往返)+ vue-tsc + npm build(dist 重建嵌入)+ dart analyze No issues + flutter build windows ✓。真实 Plex pref 键名 / Trakt Device 页 / OIDC Provider 标 TODO 联调(mock 覆盖)。
- Plex 套件(内置插件
17 功能 7 批全部完成 ✅(P0–P3 全做、三端对齐、编译+单测+mock 验证;真实 PT/Emby/Plex/网盘/OIDC Provider 联调留用户)。
Web 导航微调:搜索移出侧栏 → 顶栏图标(2026-07-22)
主菜单「内容获取」组移除「搜索」项;搜索改为顶栏常驻图标(放大镜,位于折叠导航按钮右侧),点击直达 /search,命中当前页时与侧栏选中态一致高亮(.icon-btn.router-link-active 紫蓝渐变)。
- 动机:搜索是横向跳转能力而非具体任务域,放顶栏更贴合「主菜单只回答我今天做什么」,侧栏瘦身。设计稿 menu-redesign.md §3 裁决同步由「A + 顶栏 双入口」收敛为「仅顶栏」(顶部加 2026-07-22 修订记录、§4 定稿菜单树删除搜索行)。
- 改动集中 MainLayout.vue:
navGroups「内容获取」组删 search 项 + importSearch图标 + topbar 折叠导航按钮后插<router-link :to="{ name: 'search' }">+ 加.icon-btn.router-link-active高亮。 - 验证:
Search为@element-plus/icons-vue标准导出(dist/types/components/index.d.ts:225实查),search路由早已存在(router/index.ts:25);本次仅模板 / CSS / 标准图标 import,未改逻辑与路由表。