2019年底我开始捣鼓家庭存储手机里的照片越堆越多NAS舍不得买最后选来选去还是用天翼云盘当主存储。电信宽带的上传速度还算不错存照片视频这种场景完全够用唯一的问题是官方客户端做得太板正批量重命名、按日期归档、从NAS定时同步这类操作基本别指望。于是我先写了一个叫“天翼云盘助手”的小工具帮自己把天翼云盘变成“能写脚本的网盘”。后来收到越来越多用户的反馈整个工具在架构上推倒重来了一遍升级成了现在的“云融盘”。这篇就聊聊我从一个单网盘辅助工具升级成多网盘融合工具的全过程包括设计取舍、核心模块、踩过的坑和现在的部署方式。如果你也在做网盘周边工具、NAS同步脚本或者打算把自己的单云盘项目抽成通用框架这篇文章里的思路应该能给你省不少时间。1. 起点一个只想“把天翼云盘变成目录”的小工具1.1 真痛点不是速度是没法编程天翼云盘这个产品本身并不差电信用户用起来尤其顺滑毕竟走自家骨干网下载大文件的时候速率能跑得很满。可问题出在“自动化”这三个字上。官方客户端提供的基本都是人工交互你要点开某个文件夹手动勾选文件再点上传。如果我想每天晚上自动把NAS里新增的照片同步到云盘对应目录官方客户端完全做不到。当时市面上的方案大致分两类一类是反编译客户端、模拟官方App协议的脚本另一类是套一层WebDAV服务的工具。反编译方案维护成本极高官方一改协议就废而纯WebDAV方案只解决了“挂载”的问题没解决“批量管理、增量同步、多账号”的问题。所以我的第一个版本选了Python语言基于天翼云盘自己提供的开放接口做封装写成一个命令行工具。用法大概是这样的tianyi login --user yourname --password-file /path/to/secret tianyi ls /照片/2024 tianyi upload ./camera/2024-08-01/*.jpg /照片/2024-08-01/ tianyi rename /照片/IMG_001.jpg /照片/爷爷家聚餐.jpg就是这么朴素。但正是这个简单的CLI让我在两个月内把两万张照片全部按日期归好了档。1.2 第一版的功能边界不碰不该碰的第一版工具我只做了六件事登录、目录列表、上传、下载、重命名、删除。看着简单但每件事背后都有一套完整逻辑。以目录列表为例天翼云盘的API返回的是扁平化的资源列表带父目录ID。要还原成树状结构我得在本地维护一张“ID到完整路径”的映射表。为了不每次都全量拉取我设计了简单的增量缓存首次全量同步一次之后每5分钟对比一次变更时间戳。上传的部分则考虑了分片。当时天翼云盘单文件限制我记得比较大但上传稳定性不行动不动就断连。我加了8MiB分片和断点续传每次断线后从失败的分片开始重传。这段时间我学到的最大教训是工具类项目要把边界划清楚。我的助手只做“能让脚本控制天翼云盘”这件事动画界面、手机推送、离线下载这些我全都拒绝加。事实证明这个决定很正确因为后面整个架构升级时这些边界完整的模块让我少改了很多代码。1.3 为什么名字里是“助手”而不是“盘”给工具取名“天翼云盘助手”而不是“天翼云盘”其实是我有意的。当时我给这个工具的定位很清楚它是一个辅助性质的桥接层把天翼云盘的能力开放给更多场景。它不是官方客户端也没有野心替代官方客户端。这种取名的直接好处是用户预期很清晰。来给我提需求的人一般都知道“助手能帮我在命令行里操作天翼云盘”而不是“助手应该给我一个像百度网盘那样的界面”。这种预期管理在开源小工具里非常关键它可以避免你被一堆不切实际的issue淹没。不过“助手”这个名字也在后来给我带来了一些麻烦。因为它的名字里带着“天翼云盘”很多人觉得它就只能处理天翼云盘出问题时也会习惯性地找我。直到我做出云融盘后才慢慢把这种认知纠正过来。2. 升级的判断从“伺候一个网盘”到“融合所有网盘”2.1 三句话把我点醒天翼云盘助手发布半年后我在一个存储爱好者小社群里聊了几次收到的反馈慢慢集中成三句话“能不能支持百度网盘我资料全在那上面。”“我想把天翼云盘里的片子搬到阿里云盘里因为阿里盘空间大但手动下载再上传太累了。”“工具挺好用的但装了三台电脑每台电脑都要单独跑一个能做成常驻服务吗”这三句话让我意识到用户真正需要的不是一个网盘的CLI而是一个“把你所有的云盘变成同一个目录树”的东西。功能上要多盘挂载、多盘互传、常驻服务。要做到这三件事原来那个坚定围绕“天翼云盘API”写的助手必须推翻重来而不是打补丁。2.2 抽象层把每个网盘当成一个驱动程序升级成云融盘之后整个架构最核心的变化不是增加了多少网盘支持而是引入了一个统一的存储接口层。术语上我参考了rclone的思路——每个云存储后端就是一个实现同一组方法的驱动上层业务只认这组方法。我定义的接口非常简单只有七类操作class StorageDriver(Protocol): def list(self, remote_path: str) - list[FileInfo]: ... def mkdir(self, remote_path: str) - None: ... def delete(self, remote_path: str) - None: ... def rename(self, old_path: str, new_path: str) - None: ... def upload(self, local_file: str, remote_path: str, progressNone) - str: ... def download(self, remote_path: str, local_file: str, progressNone) - str: ... def copy(self, src_driver, src_path: str, dest_path: str) - str: ...这里的copy接口我做了特殊处理。如果源驱动和目标驱动是同一个网盘就调用网盘自己的服务器端复制如果跨网盘就走“边下载边上传”的流式转发内存中转不落本地磁盘。有了这一层抽象多盘互传、统一目录树、统一任务队列这些功能都变成了“驱动之上的业务逻辑”。每次新接一个网盘我只需要实现这七个方法以及一段时间令牌刷新逻辑就能被整个系统识别。2.3 减法做在哪里天翼云盘专属逻辑下沉重构时我最担心的是把“天翼云盘助手”里的各种专属逻辑也带进新架构。比如天翼云盘的某些文件支持“秒传”它的上传接口返回一个哈希标识如果服务器端已存在相同哈希就直接完成上传不实际传字节。这是天翼云盘自己的特性不同网盘的秒传机制完全不一样甚至大部分网盘根本没有秒传。我的处理方法是把这部分逻辑全部藏进各自的驱动内部核心调度层不感知。“云融盘”只要调用upload驱动自己判断是走实传还是秒传不需要上层关心。类似地分享链接解析、转存验证码、手机号换绑这类专属于某个网盘的VIP功能我都移到了驱动内部。云融盘核心只关心一件事提供一个统一的文件系统视图然后让任务调度器去操作这个视图。这个减法做下来代码量并没有减少太多但可维护性和可扩展性大大提高。3. 云融盘里几个值得参考的设计细节3.1 虚拟文件树和懒加载缓存多网盘统一视图的最大难点是目录树。不同网盘的目录结构、文件ID、排序规则甚至大小字段的精度都不一样直接全部堆在一个列表里会卡到怀疑人生。我在云融盘里维护了一棵“虚拟文件树”。它不主动拉取任何网盘的全部文件而是模仿操作系统文件系统的懒加载模式初始化时只加载每个网盘根目录下的第一级条目当用户打开某个子目录时才去向对应的网盘驱动请求那个目录的子项。为了避免频繁调用网盘API每个目录的列表结果都会在本地SQLite库中缓存2到5分钟并记录一个etag或类似标记。如果网盘API支持增量变更查询就优先用增量接口做增量缓存不支持的话就靠“到期失效强制刷新”的方式兜底。实际测试中普通用户目录10000个文件以下打开目录的响应速度基本稳定在200毫秒左右。3.2 账号会话池token过期是所有云盘的共同麻烦云存储接口几乎清一色采用OAuth或自研Token机制但各家Tick到的时间不一样有的是10分钟有效有的是7天有效还有的是“直到刷新失败才失效”。如果每次请求都完整走一遍鉴权多账号场景下会话管理会变成灾难。云融盘里我专门做了一个会话池模块。每个账号实例在后台维护自己的刷新机制核心调度层只需根据配置里的account名称去取一个可用会话取不到就自动触发刷新。刷新如果失败调度的表现不是让整个任务挂掉而是把该账号下涉及的任务标记为auth_failed暂停等待重试不影响其他账号的下传任务。这里有一个细节值得分享同一网盘多账号时单个账号的瞬时并发必须限制。比如天翼云盘账号A正在上传大文件如果账号B也同时上传某些网盘的限流策略会直接断掉其中一个账号的全部连接。所以会话池里每个账号都带一个独立的并发信号量默认并发数3账号之间互不影响。3.3 任务队列与分片传输网盘助手时代的逻辑是“一条命令做一次上传”升级后云融盘把所有传输任务都纳入了统一队列。队列的设计参考了简单的TODO状态机pending排队等待调度running正在处理某一分片waiting_retry网络中断等临时错误等待按指数退避重试finished任务完成failed重试超过次数上限每种传输默认分片为8MiB并发数为3重试次数为3次重试间隔为2秒、4秒、8秒的指数退避。上传分片时每个分片都会记录一个完整的多段上传会话ID断线后按已提交的分片继续而不是从头再来。这套设计移植到了云融盘后多盘互传的场景才真正落地。比如从百度网盘迁移到天翼云盘任务队列会先把百度网盘的某个源文件按Range方式拉取到内存边下载边切分再以分片方式上传到天翼云盘。整个过程不落盘带宽占用可控。对用户来说他看到的效果就是一个“搬家进度条”。3.4 WebDAV还是本地盘符很多用户问我为什么云融盘挂载出来的是WebDAV服务而不是直接生成一个本地盘符。其实这取决于平台和授权环境。WebDAV的优势非常明显几乎所有平台都能原生支持或通过小工具支持系统文件管理器里可以直接访问。我早期测试时Windows的“映射网络驱动器”、macOS的“连接服务器”、部分安卓文件管理器都能直接连。对于视频播放这种场景客户端自己会处理Range请求拖动进度条毫无压力。真正的本地盘符需要借助WinFsp或rclone mount这类机制。我自己实测过WinFsp方案在Windows下确实能提供“看得见摸得着的Z盘”但崩溃恢复和权限控制比WebDAV复杂得多。我在云融盘中默认优先启用WebDAV本地盘符以插件方式放在后续计划里。4. 升级路上踩过的坑你最好提前知道4.1 旧版本配置直接导入的结果是“一堆乱路径”天翼云盘助手时代我用的缓存键是天翼云盘资源ID不是路径。当时觉得ID够稳定用起来方便。但云融盘的虚拟文件树是全路径驱动再拿旧数据库去套新逻辑所有路径都会失效文件列表直接变成一团乱麻。处理办法是写一个preflight迁移脚本启动新版本时先扫描旧配置目录把所有以数字ID为键的缓存记录全部标记失效并要求首次启动时对每个网盘根目录做一次全量重新索引。这个迁移过程虽然让第一批升级用户在启动时等了十分钟左右但至少不会出现神秘的文件丢失现象。如果你也要做类似升级我建议越早换用“路径类型最后修改时间”三元组作为缓存主键越好不要等到升级时再改。4.2 目录递归扫描的性能黑洞有一位用户买了个钛合金“存片盘”里面30万个小文件。他让云融盘去扫描整个目录树结果WebDAV服务直接卡死日志里全是内存溢出的报错。罪魁祸首就是我的递归扫描逻辑它试图一次性拉取某个大目录下的所有子目录和文件再在内存里建树。根因找到了修起来就不难。我引入了三个级别的扫描策略浅扫描只拉取当前目录第一级子项。深扫描需要进入某个子目录时才按需触发。全量扫描通过后台任务异步进行不阻塞交互并且强制限制单次拉取条目数。更重要的是我把全量扫描任务切成了“按目录逐层推进的队列”每层只处理1000个条目处理完再继续。这样即使全盘有30万文件也只是慢不会死机。后来这个限制变成了云融盘的默认防护策略任何网盘的懒加载目录都不会一次性返回超过2000个子项超过的部分自动分页。4.3 “秒传”不是免费的天翼云盘助手在单网盘场景下做得特别顺手部分原因是天翼云盘内部对相同文件有秒传优化。升级到云融盘后有用户反馈说“从百度往天翼搬一个大目录进度条卡在99%一直不结束。”排查到最后发现百度网盘侧没有秒传机制任务队列必须完整下载所有分片并校验通过后才认为源端读取完成。而天翼云盘侧上传时又不小心命中了某些大文件的本地缓存副本导致“看起来秒传了”实际上会话一直等一个根本不会到来的分片确认。这个问题的根源是驱动之间对“任务完成”的认定标准不一致。后来我在任务状态机里统一增加了transfer_complete判断条件无论源网盘是否秒传都以下载端实际收到最后一个字节并校验CRC为准再做状态切换。跨盘互传不能指望秒传老老实实边传边校验才是正路。4.4 各家网盘对并发的容忍度完全不同这是最容易被低估的坑。天翼云盘对用户带宽友好并发开4路问题不大但某家主打免费空间的网盘并发一上4路分分钟返回503。我把默认并发数从8一路调到3才在大多数平台上达到稳定状态。后来我在配置里增加了一个驱动级别的max_concurrency字段每个网盘后端可以单独覆盖默认值。如果你也要做这类聚合工具一定要把这个参数做成可调不要全局一刀切。我不知道你用的哪个网盘服务商的限流策略但可以确定的是永远不要把某家网盘的接口并发上限当成其他家也能接受的。5. 现在的云融盘怎么部署适合谁5.1 部署方式和默认参数云融盘现在以常驻服务方式运行部署非常简单。我自己喜欢用Docker但纯二进制跑也无所谓。这是我最常用的一套参数习惯监听端口15280数据库SQLite放在配置目录下state.dbWebDAV路径前缀/d/{网盘别名}/...默认并发3默认分片8MiB默认重试3次指数退避 2s / 4s / 8s日志级别info按大小轮转单文件50MB保留3份Docker方式大致是这样docker run -d \ --name cloudrong \ --restart unless-stopped \ -p 15280:15280 \ -v /opt/cloudrong:/data \ cloudrong/cloudrong:latest配置文件放在/opt/cloudrong/config.yaml里。5.2 配置文件长什么样云融盘的配置核心是三段式服务端、账号列表、挂载点。写出来大概是这样server: listen: :15280 access_token: 替换成你自己的密钥 drivers: tianyi: enabled: true accounts: - name: home username: yourexample.com auth_type: session auth_params: password_file: /opt/cloudrong/secrets/tianyi_home.txt baidu: enabled: true accounts: - name: backup refresh_token_env: BAIDU_REFRESH_TOKEN mounts: - path: /d/tianyi driver: tianyi account: home read_only: false - path: /d/baidu driver: baidu account: backup read_only: true transfer: chunk_size_mib: 8 concurrency: 3 retries: 3 retry_backoff_base_sec: 2配置的含义很好理解把天翼云盘挂到/d/tianyi把百度网盘挂到/d/baidu之后所有对这两个路径的读写都会自动落到对应网盘。5.3 三个典型用法升级成云融盘之后我自己的用法其实发生了很大变化现在最常用的是这三种场景NAS增量备份我设置了一个cron任务每天凌晨3点执行一次用云融盘CLI把NAS里指定目录的增量文件传到天翼云盘对应目录。因为有了统一的文件视图脚本逻辑不再关注后端实现只是调用/d/tianyi/照片/2025/…而已。多盘互迁用户最多用的“搬家”功能。从百度网盘往天翼云盘迁数据时我可以直接启动一个任务目标路径写/d/tianyi/迁移归档/云融盘会自己完成流式转发、分片上传、校验重试。相册归档改靠WebDAV手机上装支持WebDAV的客户端把云融盘挂载成一个远程目录。拍完照片后选中相册复制进挂载目录由同步任务负责把文件扔进正确的分类目录。这个过程比我最早用CLI手动敲命令舒服太多了。6. 关于改名字和我的一点体会最后说几句关于“云融盘”这个名字。我当时取名字的逻辑是“融”代表融合多盘而不是融资、证券那套意思。“盘”字可能有点歧义但站在普通用户角度它确实比“A轮聚合驱动服务”这种名字好理解一百倍。天翼云盘助手这个项目带给了我前半段的技术积累云融盘则是后半段的架构升级。回头看我觉得最值得分享的一条心得体会是如果一个工具只能绑定某个特定服务商那么它的生命力必然局限在那个服务商的产品周期里。但当你把它做成一层的适配层让所有业务逻辑都不再关心后端是谁的时候这个工具才真正成了你自己的东西。如果你打算从单网盘工具往多网盘聚合方向升级我的建议是先重构出那份“抽象层接口”再谈功能扩展千万不要在一个已经写满“某某网盘专属逻辑”的代码库上硬塞第二家网盘。架构上的加法永远比不上架构上的减法。