Skip to content

v2.2.0

Choose a tag to compare

@github-actions github-actions released this 10 Aug 07:47
· 21 commits to main since this release

BaiduPCS-Rust v2.2.0 发行说明

发布日期:2026-08-10


🎯 版本说明

v2.2.0 是在 v2.1.6 基础上的功能版本,核心是支持百度网盘企业版(apaas)分享转存,并围绕文件夹下载分享同步做了一轮系统性修复。

  • 核心新功能:企业版(apaas)分享转存 —— pan.baidu.com/apaas/share?surl=... 这类链接终于能转存了
  • 下载体验:速度与预计剩余时间不再乱跳(感谢 @wlbksy PR #145);文件夹「跳过」的文件在详情里可见
  • 下载正确性:设置页的冲突策略对内部发起的下载真正生效;重启不再重复下载已完成的文件;失败子任务自动重试而不是把整个文件夹卡死
  • 分享同步:多个订阅同时跑不再互相抢槽位;重启后接着跑而不是推倒重来
  • 转存:网盘空间不足不再被误判成文件数超限空转;普通转存补上分批,超过 500 文件的目录不再直接失败

平滑升级:本版本不涉及破坏性数据迁移,从 v2.1.x 升级无感知。新增的数据库列会在启动时自动补齐(ALTER TABLE ... ADD COLUMN),老数据按默认值兼容。仍建议升级前按惯例备份数据目录(见下方「升级说明」)。

⚠️ 由于本版本新增了数据库列,不建议直接用升级后的数据目录回退到 v2.1.x;如需回退请先恢复升级前的备份。


✨ 企业版(apaas)分享转存

百度网盘的企业版分享与个人版是两套互不兼容的体系,端点、鉴权令牌、参数格式全不相同:

个人版 企业版(apaas)
链接 pan.baidu.com/s/1xxxx pan.baidu.com/apaas/share?surl=xxxx
短链 surl 要补 1 前缀 surl 原样,不能补前缀
分享页 HTML 里刨 yunData 片段 window.yunData = {...} 干净 JSON
验证提取码 /share/verifyrandsk /apaas/api/share/getspwdspwd
列目录 /share/list /apaas/1.0/share/list
转存 /share/transfer /apaas/1.0/share/transfer

此前的现象:拿企业版链接去调个人版接口,/s/1<surl> 会命中一个「链接不存在」的错误页,而该错误页里仍嵌着一个无关的 shareid,于是解析看起来「成功」了,后续 /share/verify 却稳定返回 errno=-12——用户看到的是「提取码错误」,怎么试都过不去。

本版本的做法:新增 netdisk::share 策略层,把两套体系的差异收敛到 ShareProvider 的 6 个方法上,公共部分(文件项解析、异步任务轮询节奏、结果提取)复用同一份实现。

  • 自动识别:解析链接时判定 ShareKindpersonal / apaas),后续走哪套接口由它决定,用户无需做任何选择。
  • 凭据通道复用:企业版的 spwd 与个人版的 randsk 语义相同(都是校验提取码后换来的、后续请求要带的凭据),直接复用现有 randsk 通道,上层无需感知差异。
  • 三条路径全覆盖:普通转存、分享直下、分享同步订阅均自动适配。
  • 向后兼容:老任务持久化的 JSON 里没有 kind 字段,反序列化时退化为个人版,行为与升级前一致。
  • 前端:预览接口回包带 kindtoken,子目录导航原样回传(企业版的 spwd 不在 Cookie 里,翻子目录必须带上)。

✨ 更平稳的速度与预计剩余时间

感谢 @wlbksy 贡献 PR #145

此前的现象:下载速度在 0 / 285 KB/s / 5.2 MB/s 之间反复横跳,ETA 跟着一起跳,完全没法参考。

根因:旧算法是「窗口内字节数 ÷ (now − 最早样本时间)」,分子分母口径不一致。下载按分片落盘,进度回调并非匀速触发——实测出现过「连续 5 秒已下载不动,然后一次跳 256KB」,那一刻窗口里只剩一两个间隔几十毫秒的样本,算出 256KB / 0.05s ≈ 5.2 MB/s,而真实速率只有约 65 KB/s。

改动

  • 存累计值而非增量:速度直接由「两个快照的字节差 ÷ 两个快照的时间差」得出,分子分母取自同一对时间点,天然自洽。
  • 固定间隔采样:快照按 500ms 落点,窗口内样本数量有上界,速度不再随回调疏密漂移。
  • 跨度不足一秒宁可报 0:不拿几十毫秒的样本外推——那正是分母塌缩的来源。
  • 空闲时间参与平均:即使没有新字节也定期落快照,否则下载停滞时最老的快照永远滚不出窗口,速度会一直停在最后那个值不动(实测见过卡在 5.33 MB/s 好几秒)。
  • 保留窗口外的基准点:只要第二个快照还在窗口内,第一个就得留着当左端点,否则没法算差值。

合并后做了一轮取舍调整(cfd412c):独立下载模式不经过分片调度器、没有它的刷新循环兜底,补了一个自带的刷新协程;同时恢复了进度回调里的事件发布路径(文件夹子任务聚合、备份任务通知),避免统一采样把这两条通知漏掉。


✨ 文件夹「跳过」可见

命中冲突策略被跳过的文件不会创建子任务,此前既不出现在下载详情里、也不产生任何事件,用户看到的是「已完成 + 空列表」,甚至无法判断队列是卡住了还是在正常跳过。

  • 新增 skipped 事件:带 group_id,前端据此把跳过归到对应文件夹,实时证明队列在推进。
  • 新增聚合字段skipped_files / skipped_sizecompleted_count 分开统计,但共同参与终态判定。
  • 明细可查:跳过的文件级明细持久化,通过 GET /downloads/folder/:id/skipped 单独取回(不塞进列表接口,避免大文件夹响应膨胀),前端合成「跳过」状态行并入详情列表。
  • 进度口径修正:整体进度改用 (已下载 + 已跳过) / 总大小——此前一个「全部跳过」的文件夹会显示 0% 却又是「已完成」状态,用户无从判断发生了什么。

🐛 下载·正确性修复

内部发起的下载忽略设置页冲突策略(重要)

问题:用户在设置页选了「跳过」,但离线下载完成后转本地下载转存完成后自动下载这两条路径照样覆盖本地已有文件,表现为「跳过时灵时不灵」。

根因:HTTP handler 会自己读配置后把策略传进来,但这两条内部路径一直传 None,此前被硬编码兜底成 Overwrite

修复:策略解析改为三级回退——调用方显式指定 > 设置页全局默认 > Overwrite;配置存引用而非快照,设置页改动即时生效。

重启后重复下载 / 跳过策略失效 / 进度虚高(重要)

三个问题同源于文件夹快照落盘不全:

  • 重复下载:已完成的子任务归档后会从活跃集合移走,重启时待处理队列里仍留着它们,于是已下完的文件被重新下载一遍。现按 group_id 从历史库查回已成功完成的相对路径集合做对账,剔除后再补任务。
  • 跳过策略失效:文件夹的 conflict_strategy 未持久化,重启后回落到默认值。现随快照一起落盘。
  • 进度虚高已下载max() 保证单调性,一旦「已完成子任务之和」与「活跃任务之和」有一次重复累加,虚高值就被永久锁死——实测出现过 30.78 GB / 31.35 GB = 98.2%,而真实下载量只有 17 GB。现封顶到 total_size 并打告警,把静默的数据损坏变成可定位的信号(total_size == 0 表示扫描未完成,此时不封顶)。

跳过统计与明细同时补进已完成文件夹的归档表,重启后不再丢失。

失败重试无上限导致文件夹永不终态(重要)

问题:子任务耗尽分片级 / 链接级 / 调度级重试后直接判死并计入 failed_count,而「恢复文件夹」只接受 Paused/Failed 状态——用户在下载途中对单个失败文件无法做任何操作,只能删掉整个文件夹任务重来

修复:增加第四层——文件夹层的自动重试(默认上限 3 次)。

  • 立即重新入队:放在「释放槽位 + 拉起等待任务」之后,此刻空出的槽位已被排在前面的子任务抢走,本任务只能进等待队列——即「回到队尾」,不会插队。
  • 复用原任务(Failed → Pending)而非重建:已下载的分片全部保留,大文件在 90% 处失败不会前功尽弃。
  • 刻意不做延迟重试:定时器只存在于内存,服务在退避窗口内重启会把重试整个吞掉(任务永远停在 Failed 无人再管);立即入队则任务以 Pending 落进 WAL,重启后能正常恢复。
  • 重试期间不计入 failed_count:否则 已完成 + 已跳过 + 已失败 会提前凑满总文件数,重试还没跑文件夹就被判成终态。

同时补齐了重启时失败子任务的恢复路径。

403/404/410 被当成网络抖动反复轰炸

问题:一个 CDN 节点对某文件持续返回 403 时,任务会以 100ms 退避反复重试同一个坏链接,直到连续 6 次分片失败才触发换链接——实测因此卡了 8 分钟才自愈。

根因:错误分类只看「链接是否用尽」,没用尽就一律判 Transient

修复:永久性 HTTP 错误(403/404/410)直接判 Fatal,并立刻把该链接打入指数退避冷却(10s→40s,复用探测失败那套),链接选择器随即跳过它。


🐛 分享同步

多个订阅同时执行时互相抢槽位(重要)

问题:多个分享同步都显示「在下载」,但交替推进、谁也不快。

根因:发放「文件夹固定任务位」时只校验了「固定位是否已被别的子任务占用」,漏掉了「文件夹自己是否确实持有一个固定位」。文件夹创建时若抢不到槽位(例如只有 1 个任务槽、且已被另一个文件夹以 Normal 优先级持有——Normal 抢不动 Normal),fixed_slot_id 会是 None 且没有任何重试补偿;此时子任务仍带着 uses_folder_fixed_slot=true 被拉起,却不占用任务槽池的任何槽位——直接绕过任务槽上限进入活跃集合。实测日志(1 个任务槽、3 个目录分享同步):

文件夹 4803c7cd 无法获得固定任务位
→ 分配文件夹固定槽位: folder=4803c7cd, folder_fixed_slot_id=None
→ ⚠️ 活跃任务数 2 超过任务槽上限 1

两个任务随后在分片调度器里 round-robin 瓜分唯一的下载线程,于是谁都很慢。

修复:补上「文件夹自己持有固定位」这一条件,这类子任务会回落到全局槽位分配路径,拿不到就正常进等待队列,等持有者释放后按序拉起。

顺带修正槽位优先级:文件夹下载此前无条件用 Normal,两个后果——① 后台同步占住槽位后用户临时想下载别的文件会被挡住;② 分享同步的文件夹会把同一个订阅里正在跑的单文件下载(Backup)踢下槽位。现在自动备份与分享同步的内部下载归为 Backup(只用空闲槽、不抢占任何在跑的任务、自身可被用户手动下载抢占),只有用户手动发起的文件夹下载才是 Normal

重启后接着跑,而不是推倒重来(重要)

问题:进程重启后,run 永远停在 Interrupted、基线永远不推进、下一轮全部重做。

根因:等待转存任务的 future 跑在执行栈里,进程一重启就没了。转存任务本身会被恢复、下载监控也会重建,但没有任何东西在等它、给 run 收尾

修复:新增「续跑接管」。判定能否续跑要三个条件缺一不可——

  1. 候选快照还在:没有它就无法在收尾时推进基线,续跑的活儿等于白干;
  2. run_items 里记着 transfer_task_id:这是找回「上一轮在做什么」的唯一线索;
  3. 该订阅还有没下完的下载:注意看的是下载而不是转存任务——转存任务一旦启动了自动下载就会被落盘成 completed 并移入历史库,重启后根本不会被恢复成活跃任务,拿它当判据永远判不出「可续跑」。

接管后唤醒残留下载(重启后都是暂停态,没人拉起就永远不动)、等转存跑完、正常收尾并推进基线。几个细节:

  • 接管即把 run 标回 running:启动时只收编 running 的 run,若留在 interrupted,进程再被杀一次这批活儿就成孤儿(实测踩到过:接管跑到一半被杀,重启后一条续跑日志都没有,前端全是暂停)。
  • 硬上限兜底:waiter 期间占着该订阅的 in-flight 标记,调度器无法再起新一轮;万一转存任务永远不到终态,没有上限就意味着这个订阅永久停摆。复用与正常 run 相同的 7 天上限(BAIDUPCS_SHARE_SYNC_TASK_HARD_TIMEOUT_SECS)。

转存记账时机导致续跑判据丢失

transfer_task_id 原来是「等待完成后」才写进 run_items,进程若在下载途中被杀,那个正在跑的任务压根没进 run_items——续跑判据找不到它,只能退化成清理重跑,已下载的进度全部作废。现改为提交成功就先记账(在途状态),成功/失败分支再覆盖成终态。

续跑期间速度不动

正常 run 会启动每秒一帧的 item_progress 广播器,续跑接管路径漏掉了——前端在整个续跑期间收不到任何推送,界面停在接管那一刻不动,只能靠切页面触发 REST 兜底拉取(表现就是「速度不动,切菜单回来才刷新」)。现已补齐,并保证任一退出分支都停掉广播器。


🐛 转存

网盘空间不足被误判为文件数超限(重要)

问题:网盘空间满了以后,分享同步会一路对半拆批重试,每批都因空间不足失败——实测刷出 347 次 errno=-32 仍在原地空转。

根因:百度返回 errno=12 + info[0].errno=-32(剩余空间不足)的这条响应里不带 target_file_nums_limit 字段,旧代码 unwrap_or(0) 后判定「转存文件数 32 超过上限 0」——文案离谱,更糟的是分享同步按「超过上限」把它归类成可二分重试

修复-32 在文件数检查之前拦截,归类为 quota_full(资源类错误,拆多小都没用),走早停并提示用户清理空间后手动触发;同时只有响应确实带了 target_file_nums_limit(>0)才做超限判定,避免字段缺失时把任意错误都算成「超过上限 0」。

超过 500 文件的目录直接 errno=12 失败(重要)

问题:百度单次转存有目标文件数上限(默认 500),且按「递归展开后的文件总数」判定,不是按提交的 fs_id 个数——选中一个含 800 个文件的目录只有 1 个 fs_id,照样撞 errno=12。此前只有分享同步侧有拆批,普通转存完全没有。

修复:普通转存补上分批,采用「零请求预拆 + 惰性下钻」两段式:

  • 预拆(零请求):先按父目录分组还原目录结构,再按上限把每组切成多批。只按选中项个数切,一个组里塞了 1200 个文件时必然超限,不用问百度也知道要拆。
  • 惰性下钻:选中项是目录时无从判断(1 个 fs_id 底下可能有上万文件),真撞上超限了才去爬那一批的子树,用与分享同步同一套拆分函数精确拆分后重提。没超限的转存一路零额外请求。
  • 为什么不做主动预扫:要知道一个目录底下有多少文件就得递归列目录,而绝大多数转存根本不到 500——实测选中 99 项含目录时预扫要跑十分钟。
  • 共享上下文:目录缓存让多个批次下钻到同一目录时不重复列;限速器让「列目录 + 转存提交」合计受同一个全局 RPS 约束,防 errno=132
  • 临时错误退避重试errno=4「请求超时,请稍后再试」 这类重试同一批就能过,不该让一次抖动废掉整批(实测批次 2/3 就是这么挂的)。
  • 进度分母跟随:下钻把目录展开成子项后分母随之增长,不再出现「转存 34 / 共 8」这种分子大于分母的显示。

混合虚拟根前缀导致目标多一层 /sharelink…/

前端在分享内导航时,不同目录可能拿到不同的 uk(取不到时为 0),于是同一次选择里会混进 /sharelink0-<shareid>//sharelink<真实uk>-<shareid>/ 两种前缀——整体剥离随即失效,虚拟根被当成目录名转存出来(用户看到目标位置多出一层 /sharelink…/,实测日志里出现过两次)。现改为逐项各剥各的,混合前缀也能归到同一命名空间;同时以根目录响应里的 uk/shareid 为权威值(分享页常常提取不到 uk,而超限后下钻列子目录必须带正确的 uk)。

跨账号聚合把部分成功显示成全部成功

历史库是全局共享的,非归属账号的 manager 也会从里面捞到同一条任务并重建,而那份重建副本的 transferred_count 是按「全部成功」伪造的;「先到先得」的去重会让伪造副本盖掉真实进度(实测部分成功的任务被显示成 7/7)。现在聚合时先收集内存里的活跃任务


🔧 工程

  • 修复两个既有失败用例:分片测试的构造尺寸必须大于小文件单分片阈值(10MB),否则永远只得到 1 个分片;槽位回收测试改用 checked_sub 构造过去时刻,避免开机不足 31 分钟时 Instant::now() - 31min 直接 panic(overflow when subtracting duration from instant)。

🔧 升级说明

Docker 用户

# 升级前建议先备份挂载的 data 卷

# 拉取指定版本镜像
docker pull komorebicarry/baidupcs-rust:v2.2.0

# 或使用 latest 标签
docker pull komorebicarry/baidupcs-rust:latest

# 重启容器
docker-compose down && docker-compose up -d

二进制用户

Releases 页面下载对应平台的二进制文件,替换原有可执行文件后重启服务。

配置与数据

  • 本版本无破坏性数据迁移,从 v2.1.x 升级无需任何手动操作。启动时会自动补齐新增的数据库列(文件夹历史的跳过统计与明细、分享同步快照的 run_id),老数据按默认值兼容。
  • 由于新增了数据库列,不建议直接用升级后的数据目录回退到 v2.1.x;如需回退请先恢复升级前的备份。
  • 升级仍建议按惯例备份 backend/config/backend/wal/(Docker 用户备份挂载的 data 卷)。
  • 默认端口仍为 18888,不影响现有反向代理 / 防火墙 / 书签。
  • 企业版分享无需任何配置,直接粘贴 pan.baidu.com/apaas/share?surl=... 链接即可,与个人版分享同一套操作流程。
  • 冲突策略:若你此前发现「设置页选了跳过但没生效」,升级后离线下载转本地、转存后自动下载这两条路径会正确遵循设置页的默认策略——如果你原先是靠这个 bug 让内部下载始终覆盖的,请留意行为变化。
  • 文件夹子任务自动重试默认 3 次,耗尽才计入失败;升级后此前那些「卡在失败、只能删掉整个文件夹重来」的场景会自动重试恢复。

📁 下载

平台 文件名
Windows (x86_64) BaiduPCS-Rust-v2.2.0-windows-x86_64.zip
Linux (x86_64) BaiduPCS-Rust-v2.2.0-linux-x86_64.tar.gz
Linux (ARM64) BaiduPCS-Rust-v2.2.0-linux-aarch64.tar.gz
macOS (x86_64) BaiduPCS-Rust-v2.2.0-macos-x86_64.tar.gz
macOS (ARM64) BaiduPCS-Rust-v2.2.0-macos-aarch64.tar.gz
Docker 镜像 BaiduPCS-Rust-v2.2.0-docker.tar.gz

🙏 致谢

  • 感谢 @wlbksy 提交 PR #145,重写速度计算的采样口径,让下载速度与预计剩余时间不再乱跳
  • 感谢所有提交 Issue 和反馈问题的用户,帮助定位下载进度、分享同步调度等边界问题

完整更新日志:参见项目根目录的 CHANGELOG.md
问题反馈:欢迎通过 GitHub Issues 进行反馈