feat: 双层并发 + 全局限流 + 整轮扫尾 + 画廊目录清单
原实现只能「多画廊并行,每个画廊串行下」,单张大画廊下得很慢。 按用户多年使用习惯(5 画廊 × 每画廊 8 张)改造为双层并发。 - 双层并发:画廊并发(默认5) × 页并发(默认8) = 最多 40 张同时下载, 两个值都在设置面板可调(1-20)。页级 worker 池 + mutex 保护计数。 - 全局限流:对齐参考实现的 nextRequestTime 算法,限所有请求 (含 /s/ 解析与图片本体),默认 0 秒关闭。 - 整轮扫尾:跑完后自动重扫失败页 1 轮(同样走完整 4 次退避重试), 仍失败则停下并标记 partial,换线路后手动点开始即可续传。 - 画廊目录清单 .ehdl.json:记录 gid/token/总页数/已完成页, 即使 state.json 丢失也能算出「应该多少张、还差多少张」。 顺带修复: - Start() 双重加锁导致死锁(引擎完全无法启动) - clampInt 越界时返回默认值而非边界值(设 30 会静默变 8) - 直链正则写死 hath.network,CDN 换域名会全军覆没 -> 加图片 URL 兜底 - ReconcileTask 会误报「有改动」 测试从 37 增至 62,其中并发/限流/扫尾用 httptest 模拟 EH 实测: - 页并发峰值 = 8、画廊并发峰值 = 5(完全生效) - 12 次请求限流 80ms 实测 966ms(期望 880ms) - 断点续传 4 张已存在时只发 5 次请求
This commit is contained in:
@@ -66,14 +66,18 @@ eh-downloader-wails/
|
||||
├── internal/
|
||||
│ ├── eh/ ★ 协议核心(可独立测试,无 UI 依赖)
|
||||
│ │ ├── eh.go ①画廊页分页 → ②/s/ → ③hath直链 → 下载
|
||||
│ │ ├── limiter.go 全局限流器
|
||||
│ │ ├── eh_test.go 单元测试
|
||||
│ │ ├── limiter_test.go 限流测试(含并发穿透)
|
||||
│ │ └── e2e_test.go 端到端链路测试(httptest 模拟 EH)
|
||||
│ └── core/
|
||||
│ ├── engine.go 任务编排、并发池、退避重试、断点续传
|
||||
│ ├── engine.go 任务编排、双层并发、退避重试、整轮扫尾
|
||||
│ ├── store.go JSON 原子写 + 任务状态机
|
||||
│ ├── paths.go 目录模板引擎 + 路径安全清洗
|
||||
│ ├── manifest.go 画廊目录清单(.ehdl.json)
|
||||
│ ├── bridge.go 本地 HTTP 桥口(供油猴推送)
|
||||
│ └── *_test.go 单测
|
||||
│ ├── *_test.go 单测
|
||||
│ └── concurrency_test.go 并发/限流/扫尾集成测试
|
||||
├── frontend/
|
||||
│ ├── index.html 界面(沿用原版)
|
||||
│ ├── js/api.js ★ IPC 适配层:window.eh.* → Wails bindings
|
||||
@@ -189,12 +193,79 @@ https://oclzqqk.gyktyewfonyf.hath.network:39656/h/4e9af9f.../keystamp=1791448200
|
||||
| 下载目录 | `~/Downloads/EH` | 每个画廊一个子目录 |
|
||||
| 代理模式 | 自动探测 | off / http / socks5 |
|
||||
| 代理地址 | 自动探测 | 常见端口 7890/7897/1087/1080/8080/10808/10809/8888 |
|
||||
| 并发数 | 3 | 建议 2~4 |
|
||||
| **画廊并发** | **5** | 同时处理几个画廊(1–20) |
|
||||
| **单画廊页并发** | **8** | 每个画廊内同时下几张图(1–20) |
|
||||
| **请求间隔** | **0 秒** | 全局最小请求间隔,0 = 不限流 |
|
||||
| 超时 | 30s | |
|
||||
| 下载分辨率 | 站点默认 | `auto` / `org` / 数字 |
|
||||
| 目录模板 | `{title}` | |
|
||||
| 油猴推送端口 | 37777 | |
|
||||
|
||||
### 并发怎么算
|
||||
|
||||
**总并发 = 画廊并发 × 页并发**,默认 `5 × 8 = 40` 张图同时下载。
|
||||
|
||||
```
|
||||
画廊并发 5
|
||||
├── 画廊A:8 张图同时下
|
||||
├── 画廊B:8 张图同时下
|
||||
├── 画廊C:8 张图同时下
|
||||
├── 画廊D:8 张图同时下
|
||||
└── 画廊E:8 张图同时下
|
||||
= 最多 40 张同时下载
|
||||
```
|
||||
|
||||
如果队列里只有 1 个画廊,就只有 1 份并发在跑(8 张),其余 4 个 worker 空转。
|
||||
|
||||
### 全局限流
|
||||
|
||||
对齐参考实现(E-Hentai Downloader 的「请求间隔」):用一个全局 `nextRequestTime`,
|
||||
每个请求排到它之后,再把它往后推 `interval` 毫秒。
|
||||
|
||||
**限的是所有请求**,包括 `/s/` 解析和图片下载 —— 每页要发 2 个请求,所以
|
||||
40 并发在开启限流时也会被串成每 `interval` 放行一个。
|
||||
|
||||
高并发下频繁遇到 429 时,1~2 秒的间隔很有效。
|
||||
|
||||
### 失败重试与整轮扫尾
|
||||
|
||||
单页失败时的退避重试:
|
||||
|
||||
```
|
||||
第1次失败 → 等 0.8s + 随机 0~0.4s
|
||||
第2次失败 → 等 1.6s + 随机
|
||||
第3次失败 → 等 3.2s + 随机
|
||||
第4次失败 → 放弃,记入失败清单
|
||||
```
|
||||
|
||||
只有「像是临时问题」才重试(连接重置、超时、429/502/503/504、socket hang up)。
|
||||
401/403/404、画廊不存在、页面结构变化这类**重试也没用**的直接判死。
|
||||
|
||||
**整轮扫尾**:所有画廊跑完后,自动把所有失败页**重新扫 1 轮**(同样走完整 4 次重试)。
|
||||
扫尾后仍失败就标记「部分失败」并停下 —— 这时你可以换个代理线路,再点一次
|
||||
「开始下载」,已下的文件会自动跳过,只补缺的那些。
|
||||
|
||||
### 画廊目录清单(.ehdl.json)
|
||||
|
||||
每个画廊目录下会写一份 `.ehdl.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"gid": "4239274",
|
||||
"token": "55e069b6b9",
|
||||
"title": "[Noxe] 0820 Qingxiao",
|
||||
"totalPages": 206,
|
||||
"donePages": [1, 2, 3, 5],
|
||||
"failedPages": [4],
|
||||
"updatedAt": 1791475732842
|
||||
}
|
||||
```
|
||||
|
||||
作用:断点续传的依据是「文件在且非空」,但那有个盲区 —— 如果 `state.json`
|
||||
丢了,程序就不知道这个画廊**原本应该有多少张**。清单补上了这个认知,
|
||||
扫目录就能算出「应该 206 张,现在只有 4 张」。
|
||||
|
||||
状态文件:
|
||||
- Windows:`%APPDATA%\EH Downloader\state.json`
|
||||
- macOS:`~/Library/Application Support/EH Downloader/state.json`
|
||||
@@ -231,7 +302,7 @@ go test ./internal/eh/ -v # 协议层(含端到端)
|
||||
go test -race ./... # 竞态检测
|
||||
```
|
||||
|
||||
37 个用例覆盖:
|
||||
62 个用例覆盖:
|
||||
- 输入解析(多行/分隔符/去重/注释)
|
||||
- hath 直链正则(含不该匹配的负例)
|
||||
- xres 改写(auto/org/数字)
|
||||
@@ -241,6 +312,10 @@ go test -race ./... # 竞态检测
|
||||
- 目录模板展开 + 路径穿越防护 + Windows 保留名
|
||||
- 断点续传、取消清理 .part
|
||||
- 状态机(清除已完成/失败重排/崩溃归位/文件损坏回落)
|
||||
- **双层并发**(mock 服务器实测并发峰值 = 8 页 / 5 画廊)
|
||||
- **全局限流**(并发穿透验证 + 精确计时)
|
||||
- **整轮扫尾**(首轮 4 次重试全挂 → 扫尾救回 / 永久失败 → partial)
|
||||
- **画廊清单**(读写往返、目录扫描、state.json 丢失后的恢复)
|
||||
|
||||
## 免责声明
|
||||
|
||||
|
||||
Reference in New Issue
Block a user