Object的分块上传
更新时间:2025-09-19
Object的分块上传
除了通过putObject()方法上传文件到BOS以外,BOS还提供了另外一种上传模式:分块上传(Multipart Upload)。用户可以在如下的应用场景内(但不仅限于此),使用分块上传模式,如:
- 需要支持断点上传。
 - 上传超过5GB大小的文件。
 - 网络条件较差,和BOS的服务器之间的连接经常断开。
 - 需要流式地上传文件。
 - 上传文件之前,无法确定上传文件的大小。
 
分块上传比直接上传稍微复杂一点,分块上传需要分为三个阶段:
- 开始上传(initiateMultipartUpload)
 - 上传分块(uploadPartFromBlob)
 - 上传完成(completeMultipartUpload)
 
浏览器端代码示例
对文件进行分块
                JavaScript
                
            
            1let options = {
2  'Cache-Control': 'public, max-age=31536000', // 指定缓存指令
3  'Content-Disposition': 'attachment; filename="example.jpg"', // 指示回复的内容该以何种形式展示
4
5  'x-bce-meta-foo1': 'bar1', // 添加自定义meta信息
6  'x-bce-meta-foo2': 'bar2', // 添加自定义meta信息
7  'x-bce-meta-foo3': 'bar3' // 添加自定义meta信息
8}; 
9let PART_SIZE = 5 * 1024 * 1024; // 指定分块大小
10
11function getTasks(file, uploadId, bucketName, key) {
12    let leftSize = file.size;
13    let offset = 0;
14    let partNumber = 1;
15
16    let tasks = [];
17
18    while (leftSize > 0) {
19        let partSize = Math.min(leftSize, PART_SIZE);
20        tasks.push({
21            file: file,
22            uploadId: uploadId,
23            bucketName: bucketName,
24            key: key,
25            partNumber: partNumber,
26            partSize: partSize,
27            start: offset,
28            stop: offset + partSize - 1
29        });
30
31        leftSize -= partSize;
32        offset += partSize;
33        partNumber += 1;
34    }
35    return tasks;
36}
            处理每个分块的上传逻辑
                JavaScript
                
            
            1function uploadPartFile(state, client) {
2    return function(task, callback) {
3        let blob = task.file.slice(task.start, task.stop + 1);
4        client.uploadPartFromBlob(task.bucketName, task.key, task.uploadId, task.partNumber, task.partSize, blob)
5            .then(function(res) {
6                ++state.loaded;
7                callback(null, res);
8            })
9            .catch(function(err) {
10                callback(err);
11            });
12    };
13}
            初始化uploadID,开始上传分块,并完成上传
                JavaScript
                
            
            1let uploadId;
2client.initiateMultipartUpload(bucket, key, options)
3    .then(function(response) {
4        uploadId = response.body.uploadId; // 开始上传,获取服务器生成的uploadId
5
6        let deferred = sdk.Q.defer();
7        let tasks = getTasks(blob, uploadId, bucket, key);
8        let state = {
9            lengthComputable: true,
10            loaded: 0,
11            total: tasks.length
12        };
13
14        // 为了管理分块上传,使用了async(https://github.com/caolan/async)库来进行异步处理
15        let THREADS = 2; // 同时上传的分块数量
16        async.mapLimit(tasks, THREADS, uploadPartFile(state, client), function(err, results) {
17            if (err) {
18                deferred.reject(err);
19            } else {
20                deferred.resolve(results);
21            }
22        });
23        return deferred.promise;
24    })
25    .then(function(allResponse) {
26        let partList = [];
27        allResponse.forEach(function(response, index) {
28            // 生成分块清单
29            partList.push({
30                partNumber: index + 1,
31                eTag: response.http_headers.etag
32            });
33        });
34        return client.completeMultipartUpload(bucket, key, uploadId, partList); // 完成上传
35    })
36    .then(function (res) {
37        // 上传完成
38    })
39    .catch(function (err) {
40        // 上传失败,添加您的代码
41        console.error(err);
42    });
            Node.js端分块上传
对文件进行分块,并初始化UploadID,上传分块
                JavaScript
                
            
            1let options = {
2  'Cache-Control': 'public, max-age=31536000', // 指定缓存指令
3  'Content-Disposition': 'attachment; filename="example.jpg"', // 指示回复的内容该以何种形式展示
4
5  'x-bce-meta-foo1': 'bar1', // 添加自定义meta信息
6  'x-bce-meta-foo2': 'bar2', // 添加自定义meta信息
7  'x-bce-meta-foo3': 'bar3' // 添加自定义meta信息
8}; 
9let PART_SIZE = 5 * 1024 * 1024;            // 指定分块大小
10let uploadId;
11client.initiateMultipartUpload(bucket, key, options)
12    .then(function(response) {
13        uploadId = response.body.uploadId; // 开始上传,获取服务器生成的uploadId
14        let deferred = sdk.Q.defer();
15        let blob = {
16            // 使用fs文件库获取文件大小
17            size: fs.statSync(localFileName).size,
18            filename: localFileName
19        }
20        let tasks = getTasks(blob, uploadId, bucket, key);
21        let state = {
22            lengthComputable: true,
23            loaded: 0,
24            total: tasks.length
25        };
26        // 为了管理分块上传,使用了async(https://github.com/caolan/async)库来进行异步处理
27        let THREADS = 2; // 同时上传的分块数量
28        async.mapLimit(tasks, THREADS, uploadPartFile(state, client), function(err, results) {
29            if (err) {
30                deferred.reject(err);
31            } else {
32                deferred.resolve(results);
33            }
34        });
35        return deferred.promise;
36    })
37    .then(function(allResponse) {
38        let partList = [];
39        allResponse.forEach(function(response, index) {
40            // 生成分块清单
41            partList.push({
42                partNumber: index + 1,
43                eTag: response.http_headers.etag
44            });
45        });
46        return client.completeMultipartUpload(bucket, key, uploadId, partList); // 完成上传
47    })
48    .then(function (res) {
49        // 上传完成
50    })
51    .catch(function (err) {
52        // 上传失败,添加您的代码
53        console.error(err);
54    });
55
56function getTasks(file, uploadId, bucketName, key) {
57    let leftSize = file.size;
58    let offset = 0;
59    let partNumber = 1;
60    let tasks = [];
61    while (leftSize > 0) {
62        let partSize = Math.min(leftSize, PART_SIZE);
63        tasks.push({
64            file: file.filename,
65            uploadId: uploadId,
66            bucketName: bucketName,
67            key: key,
68            partNumber: partNumber,
69            partSize: partSize,
70            start: offset,
71            stop: offset + partSize - 1
72        });
73
74        leftSize -= partSize;
75        offset += partSize;
76        partNumber += 1;
77    }
78    return tasks;
79}
80
81
82function uploadPartFile(state, client) {
83    return function(task, callback) {
84        console.log("task: ", task)
85        return client.uploadPartFromFile(task.bucketName, task.key, task.uploadId, task.partNumber, task.partSize, task.file , task.start)
86            .then(function(res) {
87                ++state.loaded;
88                console.log("ok")
89                callback(null, res);
90            })
91            .catch(function(err) {
92                console.log("bad")
93                callback(err);
94            });
95    };
96}
            取消分块上传事件
用户可以使用abortMultipartUpload方法取消分块上传。
                JavaScript
                
            
            1client.abortMultipartUpload(<BucketName>, <Objectkey>, <UploadID>);
            获取未完成的分块上传事件
用户可以使用listMultipartUploads方法获取Bucket内未完成的分块上传事件。
                JavaScript
                
            
            1client.listMultipartUploads(<bucketName>)
2    .then(function (response) {
3        // 遍历所有上传事件
4        for (var i = 0; i < response.body.multipartUploads.length; i++) {
5            console.log(response.body.multipartUploads[i].uploadId);
6        }
7    });
            获取所有已上传的块信息
用户可以使用listParts方法获取某个上传事件中所有已上传的块。
                JavaScript
                
            
            1client.listParts(<bucketName>, <key>, <uploadId>)
2    .then(function (response) {
3        // 遍历所有上传事件
4        for (var i = 0; i < response.body.parts.length; i++) {
5            console.log(response.body.parts[i].partNumber);
6        }
7    });
            