HTML5提供了File API、Blob对象等能力,让前端可以直接操作本地文件,结合加密算法和分片上传逻辑,就能实现安全的大文件上传功能。整个流程分为前端分片切割、分片加密、分片上传、后端合并几个核心环节,下面逐步讲解实现方式。

核心实现思路
整体流程如下:首先通过<input type="file">获取用户选择的文件,使用Blob的slice方法将文件切割为固定大小的分片,对每个分片使用加密算法处理后再上传到服务端,服务端接收所有分片后按顺序合并为完整文件,同时校验文件完整性。
前端核心实现步骤
1. 文件选择与分片切割
首先通过File API获取文件对象,定义分片大小,循环切割文件得到所有分片:
// 获取文件对象
const fileInput = document.querySelector('input[type="file"]');
const file = fileInput.files[0];
// 定义分片大小 2MB
const chunkSize = 2 * 1024 * 1024;
const chunkCount = Math.ceil(file.size / chunkSize);
const chunks = [];
for (let i = 0; i < chunkCount; i++) {
const start = i * chunkSize;
const end = Math.min(start + chunkSize, file.size);
// 切割分片
const chunk = file.slice(start, end);
chunks.push({
index: i,
chunk: chunk,
total: chunkCount,
fileName: file.name,
fileSize: file.size
});
}
2. 分片加密处理
这里使用常见的AES加密算法对分片内容加密,需要先引入加密库,也可以自行实现基础加密逻辑,以下示例使用CryptoJS进行AES加密:
// 引入CryptoJS后使用
const encryptKey = 'your_encrypt_key_16'; // 16位加密密钥
function encryptChunk(chunk) {
return new Promise((resolve) => {
const reader = new FileReader();
reader.readAsArrayBuffer(chunk);
reader.onload = function(e) {
const wordArray = CryptoJS.lib.WordArray.create(e.target.result);
// AES加密
const encrypted = CryptoJS.AES.encrypt(wordArray, CryptoJS.enc.Utf8.parse(encryptKey), {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
});
resolve(encrypted.toString());
};
});
}
3. 分片上传实现
遍历所有分片,先加密再上传,同时可以添加断点续传逻辑,通过记录已上传的分片索引避免重复上传:
const uploadedChunks = new Set(); // 记录已上传分片索引
async function uploadChunks() {
for (let item of chunks) {
if (uploadedChunks.has(item.index)) continue;
const encryptedData = await encryptChunk(item.chunk);
const formData = new FormData();
formData.append('chunk', encryptedData);
formData.append('index', item.index);
formData.append('total', item.total);
formData.append('fileName', item.fileName);
formData.append('fileSize', item.fileSize);
// 上传分片
await fetch('https://ipipp.com/upload_chunk', {
method: 'POST',
body: formData
});
uploadedChunks.add(item.index);
}
// 所有分片上传完成后通知服务端合并
await fetch('https://ipipp.com/merge_chunks', {
method: 'POST',
body: JSON.stringify({
fileName: file.name,
total: chunkCount,
fileSize: file.size
}),
headers: {
'Content-Type': 'application/json'
}
});
}
后端实现要点
1. 分片接收存储
服务端接收分片后,将加密的分片临时存储,按文件名和分片索引命名,方便后续合并:
import os
from flask import Flask, request, jsonify
app = Flask(__name__)
UPLOAD_DIR = './upload_chunks'
@app.route('/upload_chunk', methods=['POST'])
def upload_chunk():
chunk = request.files.get('chunk')
index = request.form.get('index')
file_name = request.form.get('fileName')
# 创建临时存储目录
file_dir = os.path.join(UPLOAD_DIR, file_name)
if not os.path.exists(file_dir):
os.makedirs(file_dir)
# 保存分片 命名为 索引_分片内容
chunk.save(os.path.join(file_dir, f'{index}.chunk'))
return jsonify({'code': 0, 'msg': '上传成功'})
2. 分片合并与解密
接收合并请求后,按索引顺序读取所有分片,先解密再合并为完整文件:
import CryptoJS
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import json
@app.route('/merge_chunks', methods=['POST'])
def merge_chunks():
data = json.loads(request.data)
file_name = data.get('fileName')
total = data.get('total')
file_dir = os.path.join(UPLOAD_DIR, file_name)
# 按索引顺序读取分片
chunks = []
for i in range(total):
chunk_path = os.path.join(file_dir, f'{i}.chunk')
with open(chunk_path, 'rb') as f:
encrypted_data = f.read()
# AES解密
cipher = AES.new(b'your_encrypt_key_16', AES.MODE_ECB)
decrypted_data = unpad(cipher.decrypt(encrypted_data), AES.block_size)
chunks.append(decrypted_data)
# 合并为完整文件
final_path = os.path.join('./upload_files', file_name)
with open(final_path, 'wb') as f:
for chunk in chunks:
f.write(chunk)
# 删除临时分片
for i in range(total):
os.remove(os.path.join(file_dir, f'{i}.chunk'))
os.rmdir(file_dir)
return jsonify({'code': 0, 'msg': '文件合并完成'})
注意事项
- 加密密钥需要妥善保管,避免前端硬编码密钥导致泄露,生产环境建议使用服务端下发临时密钥的方式。
- 分片大小可以根据网络情况调整,一般建议1-5MB之间,过小会增加请求次数,过大容易上传失败。
- 可以添加文件哈希校验,比如使用SparkMD5计算文件整体哈希,上传完成后校验哈希值确保文件完整。
- 断点续传需要服务端提供查询已上传分片的接口,前端初始化时先查询已上传的分片索引,跳过已上传的部分。