axios结合AbortController取消文件上传

news2025/5/14 19:22:49
<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 控制器实现

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

@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(响应式非阻塞)

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);
            });
    }
}

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2375588.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

spring中的@Async注解详解

一、核心功能与作用 Async 是Spring框架提供的异步方法执行注解&#xff0c;用于将方法标记为异步任务&#xff0c;使其在独立线程中执行&#xff0c;从而提升应用的响应速度和吞吐量。其主要作用包括&#xff1a; 非阻塞调用&#xff1a;主线程调用被标记方法后立即返回&…

MyBatis 报错:Column count doesn‘t match value count at row 1 详解与解决

本文适用于使用 MyBatis MySQL 开发中出现 “Column count doesnt match value count at row 1” 报错的朋友&#xff0c;尤其是在批量插入或更新数据时&#xff0c;遇到 XML 映射文件中 insert 标签报错的问题。 一、遇到的问题&#xff1a; 二、错误原因分析 列数与值数量不…

【人工智能】自然语言编程革命:腾讯云CodeBuddy实战5步搭建客户管理系统,效率飙升90%

CodeBuddy 导读一、产品介绍1.1 **什么是腾讯云代码助手&#xff1f;**1.2 插件安装1.2.1 IDE版本要求1.2.2 注意事项1.2.4 插件安装1.2.4.1 环境安装1.2.4.2 安装腾讯云AI代码助手** 1.2.5 功能介绍1.2.5.1 Craft&#xff08;智能代码生成&#xff09;1.2.5.2 Chat&#xff08…

麦肯锡110页PPT企业组织效能提升调研与诊断分析指南

“战略清晰、团队拼命、资源充足&#xff0c;但业绩就是卡在瓶颈期上不去……”这是许多中国企业面临的真实困境。表面看似健康的企业&#xff0c;往往隐藏着“组织亚健康”问题——跨部门扯皮、人才流失、决策迟缓、市场反应滞后……麦肯锡最新研究揭示&#xff1a;组织健康度…

【MySQL】第二弹——MySQL表的增删改查(CRUD)初阶

文章目录 &#x1f393;一. CRUD&#x1f393;二. 新增(Create)&#x1f393;三. 查询(Rertieve)&#x1f4d6;1. 全列查询&#x1f4d6;2. 指定列查询&#x1f4d6;3. 查询带有表达式&#x1f4d6;4. 起别名查询(as )&#x1f4d6; 5. 去重查询(distinct)&#x1f4d6;6. 排序…

离散制造企业WMS+MES+QMS+条码管理系统高保真原型全解析

在离散型制造企业的生产过程中&#xff0c;库存管理混乱、生产进度不透明、质检流程繁琐等问题常常成为制约企业发展的瓶颈。为了帮助企业实现全流程数字化管控&#xff0c;我们精心打造了一款基于离散型制造企业&#xff08;涵盖单件生产、批量生产、混合生产模式&#xff09;…

基于 Spring Boot 瑞吉外卖系统开发(十三)

基于 Spring Boot 瑞吉外卖系统开发&#xff08;十三&#xff09; 查询套餐 在查询套餐信息时包含套餐的分类名&#xff0c;分类名称在category表中&#xff0c;因此这里需要进行两表关联查询。 自定义SQL如下&#xff1a; select s.* ,c.name as category_name from setmeal…

POSE识别 神经网络

Pose 识别模型介绍 Pose 识别是计算机视觉领域的一个重要研究方向&#xff0c;其目标是从图像或视频中检测出人体的关键点位置&#xff0c;从而估计出人体的姿态。这项技术在许多领域都有广泛的应用&#xff0c;如动作捕捉、人机交互、体育分析、安防监控等。 Pose 识别模型的…

力扣119题:杨辉三角II(滚动数组)

小学生一枚&#xff0c;自学信奥中&#xff0c;没参加培训机构&#xff0c;所以命名不规范、代码不优美是在所难免的&#xff0c;欢迎指正。 标签&#xff1a; 杨辉三角、滚动数组 语言&#xff1a; C 题目&#xff1a; 给定一个非负索引 rowIndex&#xff0c;返回「杨辉三角…

大疆无人机(全系列,包括mini)拉流至电脑,实现直播

参考视频 【保姆级教程】大疆无人机rtmp推流直播教程_哔哩哔哩_bilibili VLC使用教程&#xff1a; VLC工具使用指南-CSDN博客 目录 实现效果&#xff1a; 电脑端 ​编辑 ​编辑 无人机端 VLC拉流 分析 实现效果&#xff1a; (实验机型&#xff1a;大疆mini4kRC-N2遥控器、大…

uniapp-商城-54-后台 新增商品(页面布局)

后台页面中还存在商品信息的添加和修改等。接下来我们逐步进行分析和展开。包含页面布局和数据库逻辑等等。 1、整体效果 样式效果如下&#xff0c;依然采用了表单形式来完成和商家信息差不多&#xff0c;但在商品属性上多做了一些弹窗等界面&#xff0c;样式和功能点表多。 …

WebpackVite总结篇与进阶

模块化 Webpack Webpack 入口entry 分离app和第三方库入口 这是什么&#xff1f; 这是告诉 webpack 我们想要配置 2 个单独的入口点&#xff08;例如上面的示例&#xff09;。 为什么&#xff1f; 这样你就可以在 vendor.js 中存入未做修改的必要 library 或文件&#xff0…

【python】基础知识点100问

以下是Python基础语法知识的30条要点整理,涵盖数据类型、函数、控制结构等核心内容,结合最新资料归纳总结: 基础30问 一、函数特性 函数多返回值 支持用逗号分隔返回多个值,自动打包为元组,接收时可解包到多个变量 def func(): return 1, "a" x, y = func()匿…

SpringBoot--springboot简述及快速入门

spring Boot是spring提供的一个子项目&#xff0c;用于快速构建spring应用程序 传统方式&#xff1a; 在众多子项目中&#xff0c;spring framework项目为核心子项目&#xff0c;提供了核心的功能&#xff0c;其他的子项目都需要依赖于spring framework&#xff0c;在我们实际…

vscode_python远程调试_pathMappings配置说明

1.使用说明 vscode python 远程调试pathMappings 配置 launch.json "pathMappings": [{"localRoot": "本地代码目录","remoteRoot": "远程代码目录" # 注意不是运行目录, 是远程代码的目录}],2.测试验证 测试目的: 远程代…

遨游5G-A防爆手机:赋能工业通信更快、更安全

在工业数字化转型与5G-A商用进程加速的双重驱动下&#xff0c;中国防爆手机市场正迎来历史性发展机遇。作为“危、急、特”场景通信解决方案服务商&#xff0c;遨游通讯深刻洞察到&#xff1a;当5G-A网络以超高速率、海量连接和毫秒级时延重塑行业生态时&#xff0c;防爆手机这…

Profibus DP主站与Modbus RTU/TCP网关与海仕达变频器轻松实现数据交互

Profibus DP主站与Modbus RTU/TCP网关与海仕达变频器轻松实现数据交互 Profibus DP主站转Modbus RTU/TCP&#xff08;XD-MDPBm20&#xff09;网关在Profibus总线侧实现主站功能&#xff0c;在Modbus串口侧实现从站功能。可将ProfibusDP协议的设备&#xff08;如&#xff1a;海…

「华为」人形机器人赛道投资首秀!

温馨提示&#xff1a;运营团队2025年最新原创报告&#xff08;共210页&#xff09; —— 正文&#xff1a; 近日&#xff0c;【华为】完成具身智能赛道投资首秀&#xff0c;继续加码人形机器人赛道布局。 2025年3月31日&#xff0c;具身智能机器人头部创企【千寻智能&#x…

格雷希尔G10和G15系列自动化快速密封连接器,适用于哪些管件的密封,以及它们相关的特性有哪些?

格雷希尔G10和G15系列快速密封连接器&#xff0c;用于自动化和半自动化过程中的外部或内部密封&#xff0c;通过使用气压驱动来挤压内部的密封圈&#xff0c;创造一个适用于各种管件的无泄漏密封连接&#xff0c;连接器内部的弹性密封圈可以提供其他产品不能提供的卓越密封性能…

专栏特辑丨悬镜浅谈开源风险治理之SBOM与SCA

随着容器、微服务等新技术日新月异&#xff0c;开源软件成为业界主流形态&#xff0c;软件行业快速发展。但同时&#xff0c;软件供应链也越来越趋于复杂化和多样化&#xff0c;软件供应链安全风险不断加剧。 软件供应链安全主要包括软件开发生命周期和软件生存运营周期&#x…