文件分片上传和下载
最近遇到一个问题,一客户要求将我们的平台部署到新的环境中,但是这个环境的网关对网络传输内容的大小进行了限制,而我们的平台又有很多传输文件的功能,传输内容大小大概率会超过网关限制,因此,我这边就开始想解决方案,最终经过综合考虑,分片上传和下载最为合适(可能会有疑问为什么不用oss,这个是因为客户内部的oss存储,对文件大小依然有限制,和网关限制相同)。
这次虽然是为了客户环境而改用的分片上传和下载,但是,该功能在实际应用中有很多好处:
- 提高传输效率:用户可以从多个服务器或多个镜像站点同时下载文件的不同部分,这样可以显著提高下载速度。
- 减少服务器压力:下载请求被分散到多个分片中,每个分片可以独立处理,从而减轻服务器的负载。
- 断点续传:如果下载过程中发生中断,用户可以从断点继续下载剩余的部分,无需从头开始。
- 支持大文件下载:对于大型文件,分片下载允许即使在没有足够带宽的情况下也能下载。
- 并行处理:下载多个分片可以并行处理,这有助于缩短总的下载时间。
- 可靠性:如果下载过程中某个分片损坏或丢失,可以只重新下载那个分片,而不是整个文件。
- 安全性:分片下载可以减少单次下载的数据量,从而降低数据泄露的风险。
实现
分片上传
其实原理很简单,步骤如下:
- 分片,主要是把文件进行拆分,分成若干个小文件
- 前端调用接口分别进行上传
- 前端上传完后,调用另一个接口,通知服务端,切片上传完成
- 服务端收到前端的通知后,进行文件合并
对于后端,要提供两个接口。(通过md5值将分片的文件存储到指定位置)
- 接收分片文件的接口,接口(post)参数如下:
| 参数名 | 说明 |
|---|---|
| chunked_file | file,分片文件 |
| file_md5 | string,原始文件的md5值,用来唯一表示文件,后续合并请求,通过该值,找到切片文件,然后进行合并 |
| chunked_index | string, 原始文件会切成多个文件,文件的先后顺序,0,1,2...方便服务端根据顺序进行合并文件 |
- 分片上传完成通知接口,接口(post)参数如下:
| 参数名 | 说明 |
|---|---|
| file_md5 | string,原始文件的md5值 |
| filename | string, 文件名,合并时,会以该名进行命名 |
[!WARNING] 对于前端来说,分片时一定要分对,注意把控好每次分片的范围,千万别丢数据,在实际开发中,我们就遇到了这个问题
分片下载
分片下载,主要用到了HTTP范围请求,按照该规范进行接口调用,我用django实现的代码大概如下(细节方面还有欠缺):
file_path = request.data.get('file_path')
range_header = request.headers.get('Range', '')
pattern = r'bytes=(\d+)-(\d+)'
matches = re.match(pattern, range_header)
if matches:
start_pos = int(matches.group(1))
end_pos = int(matches.group(2))
else:
return Response(dict(msg="Range Not Satisfiable"), status=416)
fb = open(file_path, 'rb')
fb.seek(start_pos)
response = FileResponse(fb, as_attachment=True, status=206)
total_size = os.path.getsize(file_path)
# Accept-Ranges
response['Accept-Ranges'] = "bytes"
real_end_pos = total_size - 1 if end_pos > total_size else end_pos
response['Content-Range'] = f"bytes {start_pos}-{real_end_pos}/{total_size}"
response['Content-Length'] = real_end_pos - start_pos + 1 # 该值决定了下载的最终大小
return response