You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)

问题原因及修复方案

核心原因

  1. FileReader复用冲突:同一个FileReader实例重复读取多块数据,异步回调时序混乱,导致后续块读取失败或数据丢失。
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 08:27:01