私有云同步相册/文件(OneDrive)

开发 约 3.8 千字

核心模块:

  1. 网络与通信模块(frp、CORS、RESTful)
  2. 文件传输模块(Multipart、分片、合并)
  3. 同步与算法(哈希、秒传、增量同步)
  4. 多端客户端模块(线程、MediaStore、IPC)

网络与通信模块:

内网穿透:使用自有租赁VPS部署frps,服务器使用frpc进行内网穿透式的端口映射。

跨域(CORS):5173端口的Vue请求8080的SpringBoot时,浏览器检测到Vue的JS请求了不同源的接口,主动拦截,否则可能会被恶意网站偷发请求进行CSRF攻击;只需在服务端响应头加入Access-Control-Allow-Origin: *声明接口可被调用。

RESTful 与 HTTP 协议:HTTP 是快递单,RESTful 是填单子的规范。POST 用来新建(提交上传文件),GET 用来查询(查已上传的分片、查同步变更)。multipart/form-data 是专门用来传二进制文件(图片、视频)的信封,而普通的文本数据一般用 application/json。

Q/A:

1.企业也用frp吗?

一般不用,大部分使用Ngrok,花生壳,阿里云/腾讯云的API网关;请求一般先到 网关/负载均衡服务器 再转发到内网服务器。

2.跨域安全风险如何解决?

两种标准做法:

  1. 设置白名单(最常用):不要让所有人都能跨域,只允许你的域名。比如:javaregistry.addMapping(“/**”) .allowedOrigins(“https://yourdomain.com”, “http://localhost:5173”) // 生产+开发环境 .allowedMethods(“GET”, “POST”, “PUT”, “DELETE”);
  2. 同源部署(免跨域):这是最彻底的。把你的 Vue 打包成静态文件(一堆 HTML/JS/CSS),放到 Nginx 里面,让 Nginx 的 80 端口既托管前端页面,又反向代理 /api 到 Spring Boot(8080)。这样浏览器看到的前端和后端都是 https://yourdomain.com,端口、域名、协议全一样,根本不存在跨域。

3.图解 HTTP 与 RESTful

4.Http加密过程?

HTTPS 的加密过程,分两个阶段:

  1. 握手阶段:用非对称加密(RSA/ECDHE) 和 CA 证书机制,安全地商量出一个“对称密钥”。(解决了防冒充、防篡改)
  2. 通信阶段:用这个“对称密钥”(AES)去对称加密真正的业务数据,也就是照片和密码。(解决了效率问题,防窃听)。

文件传输模块:

指纹(SHA-256):每个文件使用SHA-256计算出一个哈希值,方便后面的秒传/合并验证。

断点(/api/chunk/status):客户端询问服务端的指纹和总片数,服务端在storage/temp/{sha256}/中查找分片,返回接收到的片,客户端接收到返回值验证还有哪些需要断点续传,只传对应的片即可。

传分片(/api/chunk/upload):客户端按照 1MB 的大小切割文件,用 HTTP 的 multipart/form-data 把 切片数据、指纹、当前是第几片 发给服务端。服务端收到后,在 temp/{sha256}/ 目录下,创建一个叫 4.part 的文件,把字节写进去。项目里是 for 循环一片一片传的。在生产环境里,通常会让 3~5 个分片同时并发上传,大幅提升速度。(但并发传会打乱顺序,所以才需要第四步)。

合并(/api/chunk/merge):客户端发现所有分片都传完了,发一个 POST 请求告诉服务端分片已全部传输完成。服务器循环累计把文件合并回一个完整文件,删除temp/{sha256}目录,把文件挪到正式区。

Q/A:

1.为什么不把合并的逻辑写在上传分片的接口里?

  • 原因 1:如果每传一片都去检查一遍“是不是全都传齐了”,不仅极其消耗 CPU 和磁盘 IO,还会导致并发冲突。
  • 原因 2(核心):职责分离。upload 只负责接收和落盘,merge 只负责校验和拼接。网络断开时,只要 merge 没触发,临时文件就一直保留着,下次还能续传。

2.并发上传打乱顺序了,怎么保证合并出来的文件是好的?

  • 约定的文件名:0.part、1.part、2.part。
  • 合并的时候,服务端代码是 for (int i = 0; i < total; i++),是死板地按数字顺序读文件的。无论客户端是并发传还是乱序传,只要文件名叫 5.part 且内容没错,合并时一定会被排在第 6 个位置。

同步与算法:

秒传的底层逻辑(SHA-256 哈希):客户端计算上传文件的哈希,与服务器对比,查询到直接显示成功,不会进入上传进程。

增量同步(Change Log + Seq 序列号):引入了一个单调递增的序号(Seq),服务端每发生一次“新增”或“删除”,就给这个动作编个号(seq = 1, 2, 3...),记在ChangeLog里;客户端本地记住lastSeq;下次同步时,客户端带 since=lastSeq 去查询变化;服务器只需要返回比这个数字大的记录,数据传输量极小。

内存与磁盘的一致性:

启动时重建:你的代码里 @PostConstruct 的时候扫描磁盘,重建内存 Map。这叫“数据以磁盘为唯一真相源(Single Source of Truth)”。

实时校验:读取索引时,顺手检查一下真实文件在不在(你后来修复 bug 就是这么干的)。

企业级做法:引入数据库(MySQL)作为中间层。由于文件在磁盘、索引在数据库,一般会有一个“巡检任务”,每天对比数据库和对象存储,修复死链。

Q/A:

1.哈希碰撞攻击怎么办?

服务端在合并时,不能只信客户端,要自己算一遍合并后文件的真实哈希。如果发现合并后的哈希对不上,直接丢弃。

2.客户端太久未同步,产生大量同步日志如何下载?

  1. 分页拉取:别一次性拉 10000 条。服务端规定一次只返回 100 条。客户端拉取 since=0,拿到第 1 到 100 条,以及 nextSeq=100;然后客户端再带着 since=100 拉下一次。这就是为什么微信朋友圈有“下拉加载更多”。
  2. 快照(Snapshot)机制(更高级):真实的同步系统里,ChangeLog 不会无限保留。比如你的服务器规定只保留 30 天的日志。如果客户端离线了一年,它带的 lastSeq 早就在服务器日志里被淘汰了。这时候服务端会直接告诉客户端:“你的记录过期了,别拉日志了,我直接给你一份当前最新的全量列表(Snapshot)吧”。客户端清空本地,直接下载全量,然后重新开始同步。