架构说明
res-downloader 是一个基于 Wails 的跨平台桌面应用。Vue 前端负责交互与状态展示,Go 后端负责本地代理、证书与系统集成、资源识别、插件运行和下载任务。应用通过版本化插件协议扩展站点能力,避免将频繁变化的网站逻辑直接写入宿主。
本文介绍模块边界、运行时依赖和主要数据流。插件字段、权限和运行时 API 的完整定义请查看插件开发指南。
总体架构
浏览器 / 手机 / 桌面应用
│ HTTP / HTTPS 代理流量
▼
┌──────────────────────────────────────────────────────────────┐
│ server.Gateway:统一监听 Host:Port │
│ │
│ 本地 /api 请求 ──► httpapi.Server │
│ 其他请求 ──► proxy.Engine(goproxy) │
└──────────┬───────────────────────────────┬───────────────────┘
│ │
│ 配置、资源、任务、插件管理 │ 请求/响应观察结果
▼ ▼
┌──────────────────┐ ┌─────────────────────────────┐
│ Runtime 业务服务 │ │ plugin.PluginManager │
│ │ │ - 内置通用识别器 │
│ resource.Resource│◄────────────│ - 应用内嵌官方插件 │
│ download.Scheduler │ - 用户安装插件 │
│ media.Engine │ └──────────────┬──────────────┘
│ system.Setup │ │ ResourceCandidate
└─────────┬────────┘ ▼
│ 资源目录、关联与持久化
│ HTTP 响应 / Wails 事件 │
▼ │
┌───────────────────────────────────────────────┴──────────────┐
│ Vue + Pinia 前端:资源列表、任务中心、插件管理、系统设置 │
└──────────────────────────────────────────────────────────────┘internal/app.Runtime 是应用的组合根。构造阶段只准备应用信息、配置、日志和事件对象;业务资源在 Wails 单实例检查之后初始化。
CLI / MCP 通过独立的本机控制入口调用现有 HTTP 业务处理器:Agent / Shell → internal/automation → internal/control → httpapi.ControlHandler → 资源与下载服务。客户端不构造桌面 Runtime、不打开业务数据库,也不启动下载调度器。
运行时组成
| 模块 | 主要职责 | 关键依赖或输出 |
|---|---|---|
| Wails | 加载内嵌前端、提供窗口生命周期和少量 Bind 方法 | frontend/dist、app.Bind |
app.Runtime | 创建模块、连接依赖、管理启动与关闭顺序 | 配置、事件、代理、HTTP、插件、资源、下载 |
server.Gateway | 管理抓取代理监听器,并在本地 API 与代理请求之间分流 | httpapi.Server、proxy.Engine |
control.Server | 管理本机自动化监听器、独立凭据及连接发现文件 | 仅监听回环地址,复用资源与下载 HTTP 处理器 |
automation | 将 CLI 命令与 MCP stdio 工具转换为本机控制请求 | 每次调用读取当前连接信息,不启动桌面 Runtime |
proxy.Engine | 处理 HTTP 代理和 HTTPS MITM,生成请求/响应观察结果 | 拦截规则、设备证书、插件管理器 |
plugin.PluginManager | 加载插件、执行观察钩子、关联资源、刷新地址和生成下载计划 | 内置、内嵌和用户插件 |
operation.Service | 操作发现、会话选择、单次/批量、调度、取消、历史、结果和产物 | 独立 operations.db、插件页面桥、资源与下载适配 |
resource.Resource | 维护资源目录、持久化候选资源、执行资源操作和下载计划 | resources.db、捕获缓存、媒体引擎 |
download.Scheduler | 持久化任务、管理工作协程和任务状态,处理暂停、恢复与重试 | tasks.db、插件下载计划 |
download.PlanRunner | 执行一个下载计划中的输入获取、处理和输出安装 | HTTP、HLS、捕获文件、FFmpeg、WASM |
capture.Store | 暂存代理、页面脚本或插件钩子写入的文件字节,供下载计划读取 | capture-cache/ |
media.Engine | 调用用户配置的 FFmpeg / FFprobe 完成媒体处理 | 转封装、合并、录制等能力 |
system.Setup | 管理设备证书、系统代理和平台相关操作 | Windows、macOS、Linux 实现 |
events.Emitter | 把资源和任务变化编码后发送给 Wails 前端 | 单一 event 事件通道 |
启动和关闭生命周期
构造阶段
NewRuntime 准备应用目录、日志、配置和事件对象,供窗口尺寸、Bind 和中间件配置使用,不打开业务数据库或加载插件。
启动阶段
桌面入口使用 Wails SingleInstanceLock;重复启动会唤起已有窗口。OnDomReady 异步调用 Runtime.Start,在单实例检查之后依次初始化设备证书、捕获缓存、资源数据库、插件、任务数据库、代理和 HTTP 服务。前端静态资源可立即加载,应用信息和 API 请求等待后端就绪;初始化失败会释放已创建的业务资源。退出时取消后续启动阶段,当前同步初始化操作完成后再安全清理。
配置应用后,运行时会按需更新上游代理传输、下载工作协程数量和 HTTPS 拦截规则,不需要重新创建整个应用。
Runtime.Start 依次初始化代理处理器、启动统一 HTTP 网关,再启动下载调度器。下载调度器会恢复持久化任务:仍处于等待状态的任务会重新排队,先前正在解析、下载或处理的任务则标记为已中断,等待用户决定是否恢复或重试。
随后启动 control.Server,监听 127.0.0.1 的动态端口,写入受当前用户访问权限保护的 control/session.json。该入口使用独立随机 Token 和路由白名单,不受抓取代理 Host / Port 设置影响。自动化服务启动失败时记录日志,桌面抓取和下载继续运行。
关闭阶段
App 统一管理退出、重启、重置和更新交接;Runtime.Close 只释放业务资源:关闭自动化入口、移除当前实例的连接文件、关闭应用管理的系统代理、停止 HTTP 网关和下载调度器,再关闭资源数据库与捕获缓存。清理成功后,App 按需执行后续动作,最后关闭日志;清理失败则取消更新,不重置或启动新进程。资源清理和后续动作均避免重复执行。
改端口或重置后的新进程会等待旧进程退出,再进入单实例检查。更新助手和 CLI / MCP 不进入桌面单实例流程。
资源捕获流程
- 用户开启系统代理,或手动让其他设备和应用连接到配置的监听地址。
server.Gateway接收连接。已识别的本地/api请求交给 HTTP API,其他请求交给proxy.Engine。- HTTPS CONNECT 请求先由
rules.Set根据域名策略决定进行 MITM 还是直接透传。该阶段只有主机信息,资源类型和 MIME 匹配在后续观察阶段处理。 - 代理把请求或响应转换成版本化的
Observation。只有匹配规则和权限需要时,才读取受bodyLimit限制的 Body。 plugin.PluginManager按优先级调用已启用插件,包括内置通用识别器、应用内嵌官方插件和用户安装插件;声明的业务操作由统一操作服务投递给明确选定的桥接页面。- 插件可以输出资源候选、关联多次请求、请求受控的响应修改或页面脚本,并通过响应捕获或
api.capture.save把文件字节写入capture.Store。 - 插件输出的
ResourceCandidate由资源服务规范化、关联并保存到内存目录和resources.db。 - 资源变化通过 Wails 事件推送到前端,前端再按筛选条件更新资源列表。
代理对请求 Body 的读取会把已读取前缀重新接回原始流;响应观察也会保留完整响应供客户端继续消费。插件观察不应改变普通代理传输,除非插件显式申请并返回了受校验的修改结果。
下载执行流程
- 前端调用本地 API 创建下载任务,
download.Scheduler生成任务记录并写入tasks.db。 - 调度器根据配置的并发数从队列取出任务。集合资源会拆分为多个子项,但仍保留一个父任务状态。
- 如果资源已过期或标记为需要刷新,调度器先请求原插件刷新资源;无法刷新时返回需要重新捕获的明确状态。
- 插件为资源生成
DownloadPlan。计划描述一个或多个输入、所需执行器、处理步骤和最终输出,而不是直接控制宿主对象。 resource.Resource根据文件名模板和重名策略计算最终路径,再把计划交给download.PlanRunner。- PlanRunner 根据计划使用普通 HTTP、HLS、捕获文件或 FFmpeg 等执行器获取输入,并按需执行宿主处理或插件 WASM 处理器。
- 临时结果完成后原子化安装到最终路径;进度和状态同时写入任务数据库并通过事件发送给前端。
调度器持有每次任务执行的取消上下文,并通过父子上下文传递给 PlanRunner 和输入执行器。资源服务只执行计划、处理命名与输出安装,不持有调度器或维护下载实例索引。执行进度和处理阶段通过绑定任务 ID 与执行上下文的回调上报,已取消执行的迟到回调会被忽略。集合任务按完成子项数统计进度,子项字节进度不会更新同一资源的独立下载任务。资源列表和任务中心共享 downloadTaskUpdated 事件。
普通 HTTP 文件和捕获文件计划可以保留断点状态;直播录制、HLS 或媒体处理是否支持暂停恢复由具体执行器决定。应用退出时未完成的任务会记录为可恢复或已中断状态,而不是继续持有后台进程。
前后端通信
前端不会直接引用 Go 内部模块,主要通过三条通道通信:
- Wails Bind:提供应用信息、配置快照、API Session Token 和重置入口等少量启动能力。
- 本地 HTTP API:处理资源、预览、下载任务、插件管理、证书和设置请求。除少数公开的证书下载或 HLS 预览路径外,请求需要本次启动随机生成的 Bearer Token,并校验来源和请求方法。
- Wails 事件:后端将不同业务事件封装为
{type, data},统一通过event通道发送;Pinia 事件 Store 再分发给业务 Store 和页面。
下载任务由共享的 Pinia Store 统一加载,并处理 downloadTaskUpdated 和 downloadTaskRemoved 事件。任务中心按任务 ID 展示,资源列表按资源 ID 查询最新关联任务,不在资源行维护另一份可写下载状态。列表加载期间收到的更新和删除会保留,较旧的任务快照不会覆盖较新的状态。
Wails AssetServer 使用同一套 HTTP API 中间件,使内嵌前端可以访问 /api。独立监听器上的网关则让代理、手机证书下载和本地预览复用配置的 Host 与 Port。
CLI 与 MCP 另经本机控制监听器访问资源查询及下载任务操作。控制入口拒绝浏览器 Origin、非本机 Host 和白名单之外的路由;控制 Token 不能直接用于桌面 API。客户端不使用环境代理、不跟随重定向,也不自动重试写操作。MCP 使用官方 Go SDK 处理 stdio 协议,标准输出仅写协议消息;业务进度通过工具查询获取。使用方法见 CLI 与 MCP。
插件系统边界
插件接收结构化观察结果并返回结构化候选资源或下载计划,不直接持有 Go 对象,也不能任意访问文件系统、Shell 或宿主任意网络接口。宿主在加载和执行阶段校验 Manifest、域名、capability、Body 上限、页面脚本、资源操作、下载计划和处理器声明。业务操作必须声明输入输出 Schema、页面脚本和 effects;资源按钮的输入由宿主从已保存资源中取得并校验归属。网站私有接口保留在插件内。
插件来源分为三类:
- 内置插件:由 Go 实现,例如通用资源识别器。
- 内嵌官方插件:源码快照位于
internal/plugin/bundled/,随应用发布并安装到用户插件目录。 - 用户插件:安装在用户数据目录的
plugins/下,可以来自扩展商店或本地 ZIP。
站点专用的接口识别、数据关联、签名计算、地址刷新和非 DRM 字节转换应保留在插件中。只有多个插件都可能复用、且当前协议无法安全表达的能力,才应扩展宿主 API;扩展时需要同步 Go 模型、运行时校验、Schema、TypeScript 声明、示例和文档。
更多细节请查看插件开发指南、插件 SDK v1和扩展商店发布说明。
数据与状态
应用状态位于操作系统为 res-downloader 分配的用户配置目录。主要内容如下:
| 路径 | 内容 | 生命周期 |
|---|---|---|
config.json | 界面、监听地址、代理、下载、命名和媒体工具设置 | 设置保存时原子更新 |
logs/app.log 及带时间戳的备份 | 正式版应用日志和已开启的插件调试日志 | 单文件 10 MiB,最多 5 个备份;打开和轮转时清理超过 7 天的备份,详见日志说明 |
mitm-ca.crt、mitm-ca.key | 当前设备生成的 HTTPS 拦截证书和私钥 | 重置应用时清理 |
resources.db | 已发现资源的持久化目录 | 清空资源或重置时更新 |
operations.db | 业务操作摘要、允许保存的结果、关联、设置和幂等记录 | 默认摘要 7 天、结果 2 小时,支持调整保留时间和清理;重置应用时删除 |
tasks.db | 下载任务、子项、进度和恢复状态 | 任务变化时更新 |
control/session.json | 本次启动的自动化地址和独立 Token | 自动化服务启动时写入,正常关闭时删除,异常退出后由下次启动替换 |
capture-cache/ | 代理响应和页面媒体片段的临时字节缓存 | 有有效期,并在重置时清理 |
plugins/ | 当前安装的外部和内嵌插件副本 | 安装、升级、回滚或卸载时更新 |
plugin-state.json | 插件启用状态 | 插件设置变化时更新 |
plugin-settings.json | 各插件的用户配置 | 保存插件设置时更新 |
plugin-removed.json | 用户主动移除的内嵌插件记录 | 防止升级时意外恢复 |
plugin-sources.json | 已安装插件的来源信息 | 安装和升级时更新 |
plugin-backups/ | 外部插件升级时保留的一次回滚副本 | 升级、回滚和卸载时更新 |
资源库或任务库无法打开时,相应模块会记录错误并退化为内存状态;这能让应用继续启动,但本次状态无法跨重启保存。操作库采用独立失败策略:不可用时拒绝操作,不降级为易失成功。
主要目录
| 目录 | 职责 |
|---|---|
frontend/ | Vue、Pinia、Naive UI 前端及 Wails 生成的桥接代码 |
internal/app/ | 应用组合根、Wails Bind 和各后端模块的适配层 |
internal/server/ | 统一监听器及 API / 代理分流,不包含业务处理 |
internal/control/ | 本机控制监听器、连接发现及跨平台凭据文件权限 |
internal/automation/ | CLI 参数、MCP 工具和共用的本机 API 客户端 |
internal/httpapi/ | 本地 API、鉴权、预览和桌面操作入口 |
internal/proxy/ | HTTP 代理、HTTPS MITM、观察结果和页面脚本注入 |
internal/rules/ | CONNECT 阶段的域名拦截与透传策略 |
internal/plugin/ | 插件加载、权限校验、运行时、商店、安装和开发 CLI |
internal/operation/ | 统一业务操作服务及独立 BoltDB 存储 |
internal/resource/ | 资源目录、关联、持久化、资源操作和下载计划入口 |
internal/download/ | 任务调度、状态持久化、下载执行器和计划运行器 |
internal/capture/ | 可按范围写入或追加片段的临时响应缓存 |
internal/media/ | FFmpeg / FFprobe 能力检测与进程执行 |
internal/system/ | 证书、系统代理和平台差异实现 |
internal/config/ | 默认配置、校验、快照和持久化 |
internal/events/ | Go 到 Wails 前端的事件发送 |
internal/model/ | 跨模块共享的资源、插件和下载协议模型 |
internal/naming/ | 下载文件名模板和重名策略 |
internal/logging/ | 应用日志封装 |
examples/plugins/ | 可提交的插件协议示例和脱敏 fixture |
plugins/ | 独立站点插件的本地开发工作区,默认不随宿主提交或打包 |
cmd/githubctl/ | 生成正式版更新信息和扩展商店索引的脚本 |
cmd/resdctl/ | 可独立构建的 CLI / MCP 控制台客户端,仍依赖运行中的桌面应用 |
build/ | Wails 平台配置、图标和安装包构建资源 |
docs/ | 用户指南、插件 SDK 和开发文档 |
修改边界
- 新增或修复站点适配时,优先修改独立插件,不要把网站私有逻辑加入
internal/proxy/或internal/resource/。 - 扩展插件宿主能力时,先定义通用协议和权限边界,再同步运行时、校验、SDK 文件、示例和文档。
- 修改本地 API 时,同时检查来源、鉴权、方法、Body 上限和前端类型。
- 修改下载状态或资源模型时,同时检查 BoltDB 恢复逻辑、事件负载和前端状态映射。
- 修改启动或关闭流程时,保持构造函数无监听器和后台任务副作用,并确保失败路径释放已经创建的资源。
- 平台相关的证书与抓取代理设置应留在
internal/system/,更新请求的代理解析应留在internal/netproxy/,公共业务层通过接口或适配器调用。
参与代码开发前请阅读参与贡献。涉及插件时,还应阅读插件开发指南及对应 SDK 文档。
业务操作生命周期
Runtime 在资源、插件和下载服务就绪后创建操作服务,再提供桌面与控制 API。operation.Service 通过后端接口发现插件/页面、发送调用并整理资源与下载结果,不包含网站逻辑。页面 handler 执行异步业务;Goja 的同步钩子仅整理结果。启动将未完成记录标记 interrupted,不重放页面行为;关闭操作服务后再释放它依赖的插件、任务和资源。存储不可用时明确拒绝操作。
页面桥上报携带执行领取时的 revision,不能用当前页面 revision 替代。页面状态变化后,旧执行仅可确认 handler 已结束并释放占用,不能提交结果或覆盖终态。普通页面消息与操作就绪上报共享页面上下文,通过页面会话锁读写。
资源行通过桌面专用 /api/operations/resources 按资源 ID 查询状态,每次最多 1000 个;同一资源优先返回活动执行,再按创建时间选最新记录,不返回结果正文。该查询只覆盖当前筛选后的资源及其子项,独立于历史分页;前端离开资源页或窗口隐藏时停止轮询。历史列表仅深拷贝当前分页的摘要,避免复制全部结果。
操作页按当前内容刷新:历史页刷新历史,运行页刷新页面会话,打开的执行详情和活动批次刷新状态;操作定义与服务设置降低刷新频率。窗口隐藏时不发送轮询请求。
保留清理复用最近一次扫描,直到记录写入、设置变化、手动清理或最近的到期时间;最长间隔一分钟。结果和产物仍在到期后立即失去读取权限。重置应用时一并删除 operations.db,避免留下引用已清空资源和任务的历史、设置或幂等记录。详细状态、限额和保留规则见插件业务操作。
