axios结合AbortController取消文件上传

javascript 复制代码
<template>
  <div>
    <input type="file" multiple @change="handleFileUpload" />
    <button @click="cancelUpload" :disabled="!isUploading">取消上传</button>
    <div>总进度:{{ totalProgress }}%</div>
    <ul>
      <li v-for="(file, index) in fileList" :key="index">
        {{ file.name }} - {{ file.progress }}%
        <span v-if="file.error" style="color:red">(失败: {{ file.error }})</span>
      </li>
    </ul>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      fileList: [],       // 文件列表及各自进度
      totalProgress: 0,   // 总进度
      isUploading: false, // 上传状态
      controllers: new Map(), // 存储每个文件的 AbortController
      uploadedSize: 0,    // 已上传总字节数
      totalSize: 0,       // 总字节数
      prevLoadedMap: {}   // 记录上次回调的 loaded 值
    };
  },
  methods: {
    // 处理文件选择
    async handleFileUpload(event) {
      const files = event.target.files;
      if (!files.length) return;

      this.resetState();
      this.initFiles(files);
      this.isUploading = true;

      try {
        await this.uploadFiles(files);
      } catch (error) {
        if (error.message !== 'canceled') {
          console.error('上传出错:', error);
        }
      } finally {
        this.isUploading = false;
      }
    },

    // 初始化文件列表
    initFiles(files) {
      this.fileList = Array.from(files).map(file => ({
        name: file.name,
        progress: 0,
        error: null
      }));
      this.totalSize = files.reduce((sum, file) => sum + file.size, 0);
    },

    // 并行上传文件
    async uploadFiles(files) {
      const uploadPromises = Array.from(files).map((file, index) => {
        const controller = new AbortController();
        this.controllers.set(file.name, controller);

        const formData = new FormData();
        formData.append('file', file);

        return axios.post('/api/upload', formData, {
          headers: { 'Content-Type': 'multipart/form-data' },
          signal: controller.signal,
          onUploadProgress: (progressEvent) => {
            this.updateProgress(file, index, progressEvent);
          }
        }).catch(error => {
          if (!axios.isCancel(error)) {
            this.fileList[index].error = error.message;
          }
          throw error;
        });
      });

      await Promise.all(uploadPromises);
    },

    // 更新上传进度
    updateProgress(file, index, progressEvent) {
      // 计算本次新增的字节数(避免重复累加)
      const prevLoaded = this.prevLoadedMap[file.name] || 0;
      const newChunk = progressEvent.loaded - prevLoaded;
      this.uploadedSize += newChunk;
      this.prevLoadedMap[file.name] = progressEvent.loaded;

      // 更新单个文件进度
      const fileProgress = Math.round((progressEvent.loaded / file.size) * 100);
      this.fileList[index].progress = fileProgress;

      // 计算总进度
      this.totalProgress = Math.round((this.uploadedSize / this.totalSize) * 100);
    },

    // 取消上传
    cancelUpload() {
      this.controllers.forEach(controller => {
        controller.abort('用户取消上传');
      });
      this.isUploading = false;
    },

    // 重置状态
    resetState() {
      this.uploadedSize = 0;
      this.totalProgress = 0;
      this.prevLoadedMap = {};
      this.controllers.clear();
      this.fileList = [];
    }
  }
};
</script>

2. java后端实现

2.1 控制器实现

java 复制代码
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;

@RestController
public class FileUploadController {

    @PostMapping("/api/upload")
    public String uploadFile(
        @RequestParam("file") MultipartFile file,
        HttpServletRequest request // 用于监听中断
    ) throws Exception {
        
        try (InputStream inputStream = file.getInputStream()) {
            //文件上传业务
            return "上传成功";
        } catch (InterruptedException | IOException e) {
        	//前端取消请求时会触发中断异常,实现取消请求业务
            // 清理已上传的部分文件
            cleanPartialFile(file.getOriginalFilename());
            throw e;
        }
    }

    private void processChunk(byte[] chunk, int length) {
        // 实际业务处理(如写入文件)
    }

    private void cleanPartialFile(String filename) {
        // 删除未完成的上传文件
    }
}

Tomcat通过 Thread.interrupted() 检测中断(Tomcat 会在客户端断开时中断线程)。

2.2 原生 Servlet

java 复制代码
@WebServlet("/upload")
public class FileUploadServlet extends HttpServlet {
    
    protected void doPost(HttpServletRequest request, HttpServletResponse response) {
        try {
           	//文件上传业务实现
            response.getWriter().write("上传成功");
        } catch (Exception e) {
            cleanPartialFile();
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "上传失败");
        }
    }

    private boolean isClientConnected(HttpServletRequest request) {
        try {
            // 尝试读取1字节(如果连接关闭会抛出异常)
            request.getInputStream().read();
            return true;
        } catch (IOException e) {
            return false;
        }
    }
}

Servlet通过 request.getInputStream().isReady() 判断连接是否关闭

2.3 Spring WebFlux(响应式非阻塞)

java 复制代码
import org.springframework.http.codec.multipart.FilePart;
import reactor.core.publisher.Mono;

@RestController
public class ReactiveUploadController {

    @PostMapping("/upload")
    public Mono<String> uploadFile(@RequestPart("file") FilePart filePart) {
        return filePart.content()
            .map(dataBuffer -> {
                // 处理数据块
                byte[] bytes = new byte[dataBuffer.readableByteCount()];
                dataBuffer.read(bytes);
                processChunk(bytes);
                return bytes;
            })
            .then(Mono.just("上传成功"))
            .onErrorResume(e -> {
                cleanPartialFile();
                return Mono.error(e);
            });
    }
}

自动感知客户端断开,无需手动检查

相关推荐
巨龙之路23 分钟前
C语言中的assert
c语言·开发语言
2301_776681651 小时前
【用「概率思维」重新理解生活】
开发语言·人工智能·自然语言处理
juruiyuan1112 小时前
FFmpeg3.4 libavcodec协议框架增加新的decode协议
前端
熊大如如2 小时前
Java 反射
java·开发语言
Peter 谭2 小时前
React Hooks 实现原理深度解析:从基础到源码级理解
前端·javascript·react.js·前端框架·ecmascript
ll7788112 小时前
C++学习之路,从0到精通的征途:继承
开发语言·数据结构·c++·学习·算法
我不想当小卡拉米2 小时前
【Linux】操作系统入门:冯诺依曼体系结构
linux·开发语言·网络·c++
周胡杰3 小时前
鸿蒙接入flutter环境变量配置windows-命令行或者手动配置-到项目的创建-运行demo项目
javascript·windows·flutter·华为·harmonyos·鸿蒙·鸿蒙系统
teacher伟大光荣且正确3 小时前
Qt Creator 配置 Android 编译环境
android·开发语言·qt
炎芯随笔3 小时前
【C++】【设计模式】生产者-消费者模型
开发语言·c++·设计模式