当处理大文件上传时,分块上传是一种常见的技术。它允许将大文件分成较小的块,并逐个上传这些块,以降低上传过程中的网络负载和内存占用。在本篇博客中,我们将使用Python来实现大文件分块上传的示例。

背景

在传统的文件上传过程中,将整个大文件一次性上传可能会导致网络传输较慢和内存占用过高的问题。而使用分块上传可以将大文件划分为多个较小的块,分别上传,从而提高上传效率和系统性能。

原理

大文件分块上传的原理是将大文件划分为多个固定大小的块,通常每个块的大小为几兆字节。然后,逐个上传每个块,并在服务器端将这些块合并成一个完整的文件。这种分块上传的方法允许在上传过程中进行断点续传、并行上传等操作。

以下是大文件分块上传的基本流程:

  1. 客户端将大文件切分为固定大小的块。
  2. 客户端逐个上传每个块到服务器端。
  3. 服务器端接收每个块并保存到临时存储位置。
  4. 当所有块都上传完成后,服务器端将这些块合并成一个完整的文件。

示例

下面是一个使用Python实现大文件分块上传的示例代码:

  1. 第一步,确认文件名。/api/upload/init:用于初始化上传会话,返回一个文件 ID。
  2. 第二步,上传分片文件。/api/upload:用于接收分片文件和文件索引,将分片文件保存到临时文件夹。
  3. 第三步,合并文件,处理后续逻辑。/api/upload/complete:用于完成文件上传,将分片文件合并成一个完整的文件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
from flask import Flask, request
import os
from uuid import uuid4
from flask_cors import CORS
import shutil

'''
分片上传, 3个api
1. 获取文件id。
2. 上传文件id,分片文件,文件index。生成临时文件。
3. 合并文件,删除临时文件。
'''
app = Flask(__name__)
# 跨域
CORS(app)

UPLOAD_PATH = './uploads'
UPLOAD_TEMP_PATH = './uploads/tmp'


@app.route('/api/upload/init', methods=['POST'])
def start():
file_id = str(uuid4())
return {"filename": file_id}, 200


@app.route('/api/upload', methods=['POST'])
def upload_chunk():
chunk = request.files.get('chunk')
filename = request.form.get('filename')
index = request.form.get('index')

if not os.path.exists(UPLOAD_TEMP_PATH):
os.makedirs(UPLOAD_TEMP_PATH)

# 分片名称
chunk_filename = f"{filename}.part{index}"
chunk_path = os.path.join(UPLOAD_TEMP_PATH, chunk_filename)

# 保存分片文件
chunk.save(chunk_path)

# 返回上传成功的消息
return {"message": f"Chunk {index} uploaded successfully"}, 200


@app.route('/api/upload/complete', methods=['POST'])
def complete_upload():
filename = request.form.get('filename')
nums = int(request.form.get('nums'))

# 检查所有分片文件是否都已经上传完成
chunk_paths = [os.path.join(UPLOAD_TEMP_PATH, f"{filename}.part{i}") for i in range(nums)]
for chunk_path in chunk_paths:
if not os.path.exists(chunk_path):
return {'message': 'Missing chunk file'}, 400

if not os.path.exists(UPLOAD_PATH):
os.makedirs(UPLOAD_PATH)
# 创建目标文件
target_path = os.path.join(UPLOAD_PATH, filename)
with open(target_path, "wb") as target_file:
# 合并所有分片文件到目标文件
for chunk_path in chunk_paths:
with open(chunk_path, "rb") as chunk_file:
shutil.copyfileobj(chunk_file, target_file)

# 删除临时文件夹
shutil.rmtree(UPLOAD_TEMP_PATH)

# 返回上传成功的消息
return {"message": "Upload completed successfully"}, 200


if __name__ == '__main__':
app.run()

示例说明

这段代码是一个使用 Flask 框架实现的分块上传的示例。它包含了三个 API:

  1. /api/upload/init:用于初始化上传会话,返回一个文件 ID。
  2. /api/upload:用于接收分片文件和文件索引,将分片文件保存到临时文件夹。
  3. /api/upload/complete:用于完成文件上传,将分片文件合并成一个完整的文件。

让我们逐个分析这些 API 的功能和实现细节:

  • /api/upload/init API:
    • 使用 uuid4 生成一个唯一的文件 ID,作为文件的标识。
    • 返回生成的文件 ID。
  • /api/upload API:
    • 获取分片文件 (chunk)、文件名 (filename) 和文件索引 (index)。
    • 创建存储分片文件的临时文件夹(UPLOAD_TEMP_PATH)。
    • 构建分片文件的名称,并将其保存到临时文件夹。
    • 返回上传成功的消息。
  • /api/upload/complete API:
    • 获取文件名 (filename) 和分片数量 (nums)。
    • 检查所有分片文件是否都已经上传完成,如果有缺失的分片文件则返回错误消息。
    • 创建存储完整文件的目标文件夹(UPLOAD_PATH)。
    • 构建目标文件的路径,并打开目标文件。
    • 逐个读取每个分片文件并将其内容写入目标文件。
    • 删除临时文件夹(UPLOAD_TEMP_PATH)。
    • 返回上传成功的消息。

此示例使用了 Flask 框架处理 HTTP 请求,并使用了 Flask-CORS 扩展来处理跨域请求。它通过三个 API 实现了大文件分块上传的过程,包括初始化上传会话、上传分片文件和完成文件上传的操作。

需要注意的是,示例中使用了两个文件夹来保存文件,分别是 UPLOAD_PATHUPLOAD_TEMP_PATH。其中 UPLOAD_PATH 用于保存最终合并的完整文件,而 UPLOAD_TEMP_PATH 则用于保存临时的分片文件。在完成文件上传后,会将临时文件夹删除,只保留最终的完整文件。

这个示例可以作为一个简单的大文件分块上传的实现,但在实际应用中可能还需要添加更多的错误处理、安全验证和性能优化等功能。