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

Django中1-2GB DCM文件后台持续上传实现方案咨询

解决方案:Service Worker 实现页面无关分片上传 + Celery 后台合并

一、前端改造:用 Service Worker 接管上传进程

当前 Dropzone 上传依赖页面上下文,切换页面会中断 XHR 请求。Service Worker 是浏览器后台线程,页面切换后仍能持续运行,可完美解决这个问题。

1. 注册 Service Worker

在页面初始化时完成注册:

if ('serviceWorker' in navigator) {
  window.addEventListener('load', async () => {
    try {
      await navigator.serviceWorker.register('/sw-upload.js');
    } catch (err) {
      console.error('Service Worker 注册失败:', err);
    }
  });
}

2. 修改 Dropzone 配置,委托上传给 Service Worker

取消默认 XHR 发送,将分片数据传给 Service Worker 处理:

autoProcessQueue: false,
url: url,
maxFiles: 100000000,
maxFilesize: 99999,
acceptedFiles: '.dcm',
timeout: 9999999999,
chunking: true,
chunkSize: 2000000,
clickable: "#dicomDropzone",
parallelChunkUploads: false,
retryChunks: true,
retryChunksLimit: 5,
forceChunking: true,
init: function() {  
    $('#CBCTUpload').click(function(){      
        myDropzone2.processQueue();
    })
    this.on("addedfile", function(file) {
        totalAddedFiles++;
    });
    this.on("sending", function(file, xhr, formData) {
        // 终止默认XHR请求
        xhr.abort();
        // 给Service Worker发送分片数据
        navigator.serviceWorker.controller.postMessage({
            type: 'UPLOAD_CHUNK',
            payload: {
                file: file,
                slug: parametre,
                chunkIndex: formData.get('dzchunkindex'),
                totalChunks: formData.get('dztotalchunkcount'),
                chunkByteOffset: formData.get('dzchunkbyteoffset'),
                filename: file.name
            }
        });
        // 阻止Dropzone继续执行默认流程
        return false;
    });
}

3. 实现 Service Worker 上传逻辑

新建 sw-upload.js 文件,处理分片上传与重试:

self.addEventListener('message', async (event) => {
  if (event.data.type === 'UPLOAD_CHUNK') {
    const { file, slug, chunkIndex, totalChunks, chunkByteOffset, filename } = event.data.payload;
    // 截取当前分片
    const chunk = file.slice(chunkByteOffset, chunkByteOffset + 2000000);
    // 构建与后端兼容的FormData
    const formData = new FormData();
    formData.append('file', chunk);
    formData.append('dzchunkindex', chunkIndex);
    formData.append('dztotalchunkcount', totalChunks);
    formData.append('dzchunkbyteoffset', chunkByteOffset);
    formData.append('slug', slug);
    formData.append('dzfilename', filename);
    
    try {
      const response = await fetch('/your-upload-endpoint/', {
        method: 'POST',
        body: formData,
        credentials: 'include'
      });
      if (!response.ok) throw new Error('分片上传失败');
      // 通知页面上传进度
      event.source.postMessage({
        type: 'CHUNK_UPLOADED',
        payload: { chunkIndex, totalChunks, filename }
      });
    } catch (err) {
      // 重试逻辑,间隔2秒后重新发送
      setTimeout(() => {
        self.postMessage(event.data);
      }, 2000);
    }
  }
});

二、后端改造:Celery 异步合并分片

当前后端直接处理分片,改用 Celery 可在所有分片上传完成后后台执行合并,避免阻塞请求。

1. 配置 Celery

确保项目已配置 Celery(以 Redis 为消息代理为例):

# celery.py
import os
from celery import Celery

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'your_project.settings')
app = Celery('your_project')
app.config_from_object('django.conf:settings', namespace='CELERY')
app.autodiscover_tasks()

2. 修改上传接口,存储分片并触发合并任务

调整视图逻辑,收到最后一个分片时启动 Celery 任务:

from django.http import HttpResponse
from .tasks import merge_dcm_chunks
import os

def upload_chunk(request):
    if request.method == 'POST':
        if 'file' not in request.FILES:
            return HttpResponse(status=500)
        file = request.FILES['file']
        current_chunk = int(request.POST['dzchunkindex']) + 1
        total_chunks = int(request.POST['dztotalchunkcount'])
        folder_slug = request.POST.get('slug')
        filename = request.POST.get('dzfilename')
        
        # 创建临时分片存储目录
        temp_dir = f'/tmp/dcm_uploads/{folder_slug}/{filename}/'
        os.makedirs(temp_dir, exist_ok=True)
        
        # 保存当前分片
        chunk_path = os.path.join(temp_dir, f'chunk_{current_chunk}')
        with open(chunk_path, 'wb+') as destination:
            for chunk in file.chunks():
                destination.write(chunk)
        
        # 最后一个分片上传完成,触发合并任务
        if current_chunk == total_chunks:
            merge_dcm_chunks.delay(folder_slug, filename, total_chunks)
        
        return HttpResponse(status=200)

3. 实现 Celery 合并任务

在 tasks.py 中编写分片合并逻辑:

from celery import shared_task
import os
import shutil

@shared_task
def merge_dcm_chunks(folder_slug, filename, total_chunks):
    temp_dir = f'/tmp/dcm_uploads/{folder_slug}/{filename}/'
    final_path = f'/path/to/your/storage/{folder_slug}/{filename}'
    
    # 创建最终文件存储目录
    os.makedirs(os.path.dirname(final_path), exist_ok=True)
    
    # 合并所有分片
    with open(final_path, 'wb') as final_file:
        for i in range(1, total_chunks + 1):
            chunk_path = os.path.join(temp_dir, f'chunk_{i}')
            with open(chunk_path, 'rb') as chunk_file:
                shutil.copyfileobj(chunk_file, final_file)
            # 删除已合并的分片文件
            os.remove(chunk_path)
    
    # 删除临时目录
    os.rmdir(temp_dir)
    
    # 可添加后续逻辑:如数据库记录上传完成、发送通知等
    return f'文件 {filename} 合并完成'

三、简化替代方案:无状态分片上传 + 前端轮询

若不想引入 Service Worker,可让每个分片上传为独立无状态请求,前端在页面加载时检查未完成的上传任务,继续上传剩余分片。但此方案仍依赖页面存在,可靠性不如 Service Worker 方案。


内容的提问来源于stack exchange,提问作者Ahmet Resul KURU

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 23:15:56