手上有两个云盘:115 网盘存了大量影视资源,123 云盘有不错的带宽和直链能力。日常需求很简单——让 媒体库的文件,可以通过 115 和 123 的通道播放和下载

很多工具都是之前已经实现的零碎任务,这次结合核心诉求,把下面的问题整合起来:

  • 两个平台的 API 风格完全不同,一个 OAuth2 PKCE,一个 clientId + clientSecret
  • 文件需要在两边做匹配,靠的是 SHA1 而不是文件名
  • 想做增量同步,就得维护一份文件索引
  • 生成媒体库的 strm 文件,还要写入远程 NAS
  • 最后需要一个下载服务,能把 123 的直链能力用起来

一个个手动操作太累了,于是有了 CloudPanBridge

架构总览

整个项目分为三层:

1
2
3
4
5
6
7
8
9
10
11
12
┌─────────────────────────────────────────────┐
│ CLI 命令行工具 │
│ export dedup match sync strm serve │
├─────────────────────────────────────────────┤
│ CloudPan SDK (统一封装) │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ 115 Client │ │ 123 Client │ │
│ └──────────────┘ └──────────────┘ │
├─────────────────────────────────────────────┤
│ Database (SQLite / MySQL) │
│ HTTP Service (FastAPI) │
└─────────────────────────────────────────────┘

最底层是 SDK 层,分别封装了两个平台的完整开放 API;中间是 CLI 工具层,把业务逻辑编排成一个个命令;上层是 运维层,靠一个 shell 脚本编排定时任务。

数据库是整个系统的核心枢纽,所有信息流经它串联——文件索引、云盘匹配关系、同步任务状态,全部落库管理。

核心工作流

整个自动化管线按照 1→2→3→4→5 的顺序执行:

1. 文件导出

把云盘上的文件列表完整拉到数据库,这是后续一切操作的基础。

115 的导出做了三阶段设计:先分页拉取文件列表(不拉目录信息),再批量调用目录信息 API 构建路径,最后回填数据库。增量模式下,拉到比上次更新时间更旧的文件就自动停止,效率很高。

123 的导出用了 UPSERT 批量写入(5000 条一批),支持并发扫描子目录,还有指数退避的限流保护。回收站里的文件自动跳过。

这部分在代码里是最复杂的——要处理分页、限流、断点续传、增量同步、多用户隔离。

2. 重复文件检测

同一目录下,通过文件大小 + SHA1(或 MD5、ETag)判断重复。特别针对网盘场景做了 --detect-suffix 支持——匹配文件名末尾的 (1)(1) 这类后缀,配合 --delete 可以安全批量清理。

3. 跨云盘匹配

匹配逻辑没有在 Python 里做循环,而是直接生成 SQL,在数据库服务端完成 JOIN:

1
2
3
4
5
SELECT f115.*, f123.*
FROM files f115
INNER JOIN files f123
ON f123.size = f115.size
AND SUBSTR(f123.full_path, offset) = SUBSTR(f115.full_path, offset)

匹配结果通过临时表 + 单条 UPDATE 批量写入,100 万级数据分分钟搞定(1C1G的垃圾服务器)。全程数据库服务端计算,Python 只收结果,内存零压力。

4. 生成 strm 文件

为媒体播放器生成 .strm 文件(本质是一个指向 HTTP URL 的文本文件),内容形如:

1
http://172.16.16.170:8808/sha1/{sha1}/size/{size}/{encoded_name}

支持两种模式:本地写入和远程 SFTP 写入。我用的是 --ssh-password 参数直接写入 NAS 上的目录。47 万个文件,8 线程并行写入,大约 8 分钟跑完。

5. 同步与下载服务

同步(115 → 123) 基于 123 云盘的 SHA1 秒传接口,文件不需要下载再上传,服务端直接复制。大文件优先处理,已匹配的文件自动跳过。并发数通过 -c 参数控制。

下载服务 基于 FastAPI + uvicorn 构建,支持两种模式:

  • 302 重定向模式:查询直链后 302 跳转,服务端不接触文件数据
  • 代理缓存模式--proxy):文件边下边转,支持 HTTP Range 断点续传,缓存到本地磁盘

代理模式做了一些有意思的优化:

1
2
3
4
5
# 多线程分段下载:123 的文件用 2 个线程同时拉
# os.pwrite 按偏移量原子写入,线程安全
# LRU 缓存按 mtime 淘汰,默认 50GB 上限
# 403 自动重试:自动换 URL 再试
# 多 reader 保护:最后一个消费者断开才清理缓存

最妙的是 SHA1 秒传兜底机制——所有常规下载路径都失败时,自动触发 123 的 SHA1 秒传接口完成兜底,并更新数据库关联供后续复用。

技术亮点

双数据库后端的优雅抽象

项目同时支持 SQLite 和 MySQL,但不是用 ORM,而是自己做了抽象层:

1
2
3
DatabaseBackend (抽象基类)
├── SQLiteBackend — UPSERT: ON CONFLICT
└── MySQLBackend — UPSERT: ON DUPLICATE KEY

环境变量一键切换:

1
2
export CLOUDPAN_DB_TYPE=mysql
export CLOUDPAN_DB_HOST=...

Mark-and-Sweep 增量清理

每次导出生成唯一的 sync_id,写入每条记录时标记 last_sync_id = sync_id。导出完成后,清理该云盘下 last_sync_id != sync_id 的记录——这就是云盘上已被删除的文件。增量模式下不执行清理,防止误删。

授权管理

115 用的是 PKCE 扫码授权(比传统授权码模式更安全),Token 刷新后旧的立即作废。123 则是 clientId + clientSecret 直接获取 access_token,30 天有效期。缓存统一存放在 ~/.cloudpan/auth_cache.json

生产部署

日常运维通过一个 shell 脚本编排:

1
2
3
4
5
6
7
8
# 每 4 小时:导出 → 去重 → 匹配 → 生成 strm
0 */4 * * * /path/to/cloudpan-ops.sh every-4h

# 每天凌晨 3 点:同步文件
0 3 * * * /path/to/cloudpan-ops.sh daily

# 下载服务常驻运行
./cloudpan-ops.sh serve

脚本加了日志、PID 管理、命令可用性检测,放在 crontab 里基本不用管。

项目结构

1
2
3
4
5
6
7
8
CloudPanBridge/
├── src/cloudpan_sdk/
│ ├── base/ # 抽象基类、异常、数据模型
│ ├── platform_115/ # 115 API 实现
│ ├── platform_123/ # 123 API 实现
│ ├── cli/ # CLI 工具和业务逻辑
│ └── server/ # FastAPI 下载服务
└──cloudpan-ops.sh # 运维编排脚本

platform_115platform_123 是纯粹的 SDK 封装,cli/ 目录才是真正的业务逻辑所在。这种分层让 SDK 可以独立复用——即使不用这套自动化管线,单纯想调 115 或 123 的 API,也可以直接 pip install 后 import 使用。

写在最后

这个项目不是一个大而全的框架,而是一组解决具体问题的工具集合。每个命令单独拿出来都能用,组合起来就形成了一条完整的自动化管线。

如果你也有类似的场景——管理多个云盘、做媒体库自动化、或者单纯想研究网盘 API 的封装方式,希望对你有参考价值。