JS分块上传大文件至Flask服务器异常:视频损坏,仅首块正常求助
问题:多文件上传至Flask服务器后视频损坏
上传视频文件到Flask服务器后出现损坏,排查发现仅第一块数据正常存储,其余块不符合预期。以下是现有代码及修复方案:
HTML元素
<input type="file" class="file-input file-input hidden" multiple>
JavaScript代码(存在问题,需修复)
uploadbtn.addEventListener('click', (event) => { event.stopPropagation(); const fileInput = dropAreaSignUp.querySelector('.file-input'); const files = fileInput.files; if (files.length > 0) { let promises = []; for (let i = 0; i < files.length; i++) { promises.push(readFileData(files[i])); } Promise.all(promises) .then(results => { const xhr1 = new XMLHttpRequest(); xhr1.open("POST", "/Upload_files", true); xhr1.setRequestHeader("Content-Type", "application/json"); xhr1.onreadystatechange = function() { if (xhr1.readyState === 4 && xhr1.status === 200) { var response = JSON.parse(xhr1.responseText); console.log(response); } }; xhr1.send(JSON.stringify({ files: results })); }) .catch(error => { console.error("Error reading files:", error); }); } else { console.log('No files selected.'); } }); function readFileData(file) { return new Promise((resolve, reject) => { const CHUNK_SIZE = 1024 * 1024 * 4 ; // 4MB chunks const totalChunks = Math.ceil(file.size / CHUNK_SIZE); const fileChunks = []; let chunkIndex = 0; const reader = new FileReader(); function readNextChunk() { const start = chunkIndex * CHUNK_SIZE; const end = Math.min(start + CHUNK_SIZE, file.size); console.log(start,end) const blob = file.slice(start, end); reader.readAsArrayBuffer(blob); } function arrayBufferToBase64(buffer) { const bytes = new Uint8Array(buffer); let binary = ''; for (let i = 0; i < bytes.length; i++) { binary += String.fromCharCode(bytes[i]); } return window.btoa(binary); } reader.onload = (event) => { const base64String = arrayBufferToBase64(event.target.result); fileChunks.push(base64String); chunkIndex++; if (chunkIndex < totalChunks) { readNextChunk(); } else { resolve({ name: file.name, size: file.size, chunks: fileChunks, }); } }; reader.onerror = () => { reject(new Error(`Error reading file ${file.name}`)); }; readNextChunk(); }); } });
Flask服务器端代码
@app.route('/Upload_files', methods=['POST']) def Upload_files(): data = request.get_json() file_data = data['files'] for file_info in file_data: file_name = file_info['name'] file_size = file_info['size'] file_chunks = file_info['chunks'] print(len(file_chunks)) joined_chunks = ''.join(file_chunks) with open(f'testing0.txt','w')as file: file.write(joined_chunks)
问题原因及修复方案
核心原因
- FileReader复用冲突:同一个
FileReader实例重复读取多块数据,异步回调时序混乱,导致后续块读取失败或数据丢失。 - Base64处理错误:后端直接将Base64字符串写入文本文件,未解码为二进制数据,视频文件无法正常解析;同时
'w'模式会覆盖之前的文件内容。
修复步骤
1. 修复JavaScript分块读取逻辑
每次读取新块时创建独立的FileReader实例,避免复用冲突:
function readFileData(file) { return new Promise((resolve, reject) => { const CHUNK_SIZE = 1024 * 1024 * 4; // 4MB chunks const totalChunks = Math.ceil(file.size / CHUNK_SIZE); const fileChunks = []; let chunkIndex = 0; function readNextChunk() { if (chunkIndex >= totalChunks) { resolve({ name: file.name, size: file.size, chunks: fileChunks, }); return; } const start = chunkIndex * CHUNK_SIZE; const end = Math.min(start + CHUNK_SIZE, file.size); const blob = file.slice(start, end); // 每次读取创建新的FileReader const reader = new FileReader(); reader.onload = (event) => { const base64String = arrayBufferToBase64(event.target.result); fileChunks.push(base64String); chunkIndex++; readNextChunk(); }; reader.onerror = () => { reject(new Error(`Error reading file ${file.name} chunk ${chunkIndex}`)); }; reader.readAsArrayBuffer(blob); } function arrayBufferToBase64(buffer) { const bytes = new Uint8Array(buffer); let binary = ''; for (let i = 0; i < bytes.length; i++) { binary += String.fromCharCode(bytes[i]); } return window.btoa(binary); } readNextChunk(); }); }
2. 修复后端解码与写入逻辑
对Base64块解码为二进制数据,使用'wb'模式写入独立文件:
import base64 @app.route('/Upload_files', methods=['POST']) def Upload_files(): data = request.get_json() file_data = data['files'] for file_info in file_data: file_name = file_info['name'] file_chunks = file_info['chunks'] # 解码所有Base64块为二进制数据 binary_data = b'' for chunk in file_chunks: try: binary_chunk = base64.b64decode(chunk) binary_data += binary_chunk except Exception as e: print(f"Decode error for {file_name}: {e}") return {"status": "error", "message": f"Failed to decode {file_name}"}, 400 # 写入二进制文件,避免覆盖 with open(file_name, 'wb') as file: file.write(binary_data) return {"status": "success", "message": "All files uploaded successfully"}
3. 可选:改用FormData简化上传(推荐)
如果无需分块,直接用FormData上传可避免Base64转换问题:
uploadbtn.addEventListener('click', (event) => { event.stopPropagation(); const fileInput = dropAreaSignUp.querySelector('.file-input'); const files = fileInput.files; if (files.length > 0) { const formData = new FormData(); for (let i = 0; i < files.length; i++) { formData.append('files', files[i]); } const xhr1 = new XMLHttpRequest(); xhr1.open("POST", "/Upload_files", true); xhr1.onreadystatechange = function() { if (xhr1.readyState === 4 && xhr1.status === 200) { var response = JSON.parse(xhr1.responseText); console.log(response); } }; xhr1.send(formData); } else { console.log('No files selected.'); } });
对应后端代码:
@app.route('/Upload_files', methods=['POST']) def Upload_files(): if 'files' not in request.files: return {"status": "error", "message": "No files uploaded"}, 400 files = request.files.getlist('files') for file in files: if file.filename != '': file.save(file.filename) return {"status": "success", "message": "All files uploaded successfully"}
内容的提问来源于stack exchange,提问作者Avijit Bhuin
相关产品推荐
相关产品推荐

