【文件上传】接口优化之多文件多线程异步上传

♥文件上传 接口优化♥

一、😀问题

(1)、💣发表图文时,上传接口处理时间长,导致用户前端页面一直卡着转圈。

(2)、💣springboot中多线程中使用MultipartFile进行异步操作报错,系统找不到指定的文件(报错)。

二、🔧分析问题

发表图文时,上传接口处理时间长,导致用户前端页面一直卡着转圈:因为接口是同步的,也就是用户上传文件的时候需要等文件上传完毕才会返回结果,当较多人同时或者上传较大文件时候就容易卡死。

三、👍解决问题

❀使用多线程异步上传❀: 用户立马就能获取上传响应结果(此时不包括上传成功与否),文件通过流的形式再后台多线程异步上传,这样缺点也就暴露出来了,也就是用户并不知道文件是否上传成功,对于新产生的这个问题的解决办法也很简单:1、轮询查询上传状态 2、在上传完成逻辑中使用websocket或其他方式向用户推送上传状态;3、没有必要关心上传状态等等;

四、🐂核心代码

项目是基于SpringBoot的向七牛云上传文件的服务。

1、核心代码文件展示

在这里插入图片描述

2、自定义线程池配置

package cn.xhubbq.config;

import cn.xhubbq.utils.Threads;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 线程池配置
 *
 * @author 甲粒子
 **/
@Configuration
public class ThreadPoolConfig
{
    // 核心线程池大小
    private int corePoolSize = 50;

    // 最大可创建的线程数
    private int maxPoolSize = 200;

    // 队列最大长度
    private int queueCapacity = 1000;

    // 线程池维护线程所允许的空闲时间
    private int keepAliveSeconds = 300;

    @Bean(name = "threadPoolTaskExecutor")
    public ThreadPoolTaskExecutor threadPoolTaskExecutor()
    {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setMaxPoolSize(maxPoolSize);
        executor.setCorePoolSize(corePoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.setKeepAliveSeconds(keepAliveSeconds);
        // 线程池对拒绝任务(无线程可用)的处理策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return executor;
    }

    /**
     * 执行周期性或定时任务
     */
    @Bean(name = "scheduledExecutorService")
    protected ScheduledExecutorService scheduledExecutorService()
    {
        return new ScheduledThreadPoolExecutor(corePoolSize,
                new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(),
                new ThreadPoolExecutor.CallerRunsPolicy())
        {
            @Override
            protected void afterExecute(Runnable r, Throwable t)
            {
                super.afterExecute(r, t);
                Threads.printException(r, t);
            }
        };
    }
}

3、异步任务管理器

package cn.xhubbq.manager;

import cn.xhubbq.utils.SpringUtils;
import cn.xhubbq.utils.Threads;

import java.util.TimerTask;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 异步任务管理器
 * 
 * @author 甲粒子
 */
public class AsyncManager
{
    /**
     * 操作延迟10毫秒
     */
    private final int OPERATE_DELAY_TIME = 10;

    /**
     * 异步操作任务调度线程池
     */
    private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService");

    /**
     * 单例模式
     */
    private AsyncManager(){}

    private static AsyncManager me = new AsyncManager();

    public static AsyncManager me()
    {
        return me;
    }

    /**
     * 执行任务
     * 
     * @param task 任务
     */
    public void execute(TimerTask task)
    {
        executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS);
    }

    /**
     * 停止任务线程池
     */
    public void shutdown()
    {
        Threads.shutdownAndAwaitTermination(executor);
    }
}

4、异步工厂(任务产生处)

QiniuUtil.qiniu2即是封装的将文件上传到七牛云的工具:

 public static  Map<String,Object> qiniu2(List<InputStream> files){
        Configuration configuration=new Configuration(Region.region2());
        UploadManager uploadManager=new UploadManager(configuration);
        String key=null;//默认不指定key的情况下,以文件内容的hash值作为文件名

        String imgs="";
        int count=0;
        Map<String,Object> map = new HashMap<>();

        byte [] buffer = new byte[1024*1024*2];// 单个文件不超过2MB
        try{
            Auth auth=Auth.create(AK,SK);
            String upToken=auth.uploadToken("xhubbq");
            Response response;
            for (InputStream file : files) {
                while ( file.read(buffer,0,buffer.length) != -1 ) {
                    response=uploadManager.put(buffer,key,upToken);
                    DefaultPutRet putRet = JSONObject.parseObject(response.bodyString(), DefaultPutRet.class);
                    imgs +=(URL_PREFIX+putRet.key+"|");//注意最后多一个"|"
                    ++count;
                }
                // 关闭 流 防止内存溢出 ==== xxx ===
                file.close();
            }
            map.put("state",1);
            map.put("nums",count);
            map.put("imgs",imgs);
            return  map;

        }catch (Exception e){
            e.printStackTrace();
            map.put("state",0);
            map.put("nums",count);
            map.put("imgs",imgs);
            return map;
        }
    }

※※ 注意其中的关闭流的代码,因为流是做为参数传入的,一定要关闭流,否则会导致内存泄漏。

❓为什么使用文件流?

首先前端传递过来的文件,会存储到临时文件夹中,即类似这样的一个路径。C:\Users\DELL\AppData\Local\Temp\tomcat.8180.459485606774542746\work\Tomcat\localhost\ROOT\upload_4330f731_2ae0_4955_a484_5862fd4530a2_00000009.tmp (系统找不到指定的文件。)
原因分析
异步执行的时候,主线程结束,临时文件就会被清空了,所以会报错。
解决方案
在开启异步之前,需要把MultipartFile(file)转换为流来进行操作即可file.getInputStream()

【参数说明】:其中AK,SK是七牛云的配置,返回的是上传成功的图片地址和数量,多地址用 | 线隔开的。

👇Post.BBQ代表是表白墙类型,其他的如许愿墙,失物招领等

package cn.xhubbq.manager.factory;


import cn.xhubbq.pojo.post.*;
import cn.xhubbq.service.post.IPostService;
import cn.xhubbq.service.xguser.IUserService;
import cn.xhubbq.utils.QiniuUtil;
import cn.xhubbq.utils.SpringUtils;
import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;

import javax.servlet.http.HttpServletRequest;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.TimerTask;

/**
 * 异步工厂(产生任务用)
 *
 * @author
 */
public class AsyncFactory
{


    private static IUserService userService = SpringUtils.getBean(IUserService.class);


    private static IPostService postService = SpringUtils.getBean(IPostService.class);

    private static final Logger sys_user_logger = LoggerFactory.getLogger("sys-user");

    /**
     * 测试
     * @return 任务task
     */
    public static TimerTask posts( String content, int type, int nums, List<InputStream> inputStreams)
    {

        return new TimerTask()
        {
            @Override
            public void run()
            {
                if (content!=null){
                    boolean b = userService.checkContentAndImages(null, content);
                    if(!b){
                        return;
                    }
                    Map<String, Object> qiniu = QiniuUtil.qiniu2(inputStreams);
                    if( (int)qiniu.get("state") == 0 ){
                        return;
                    }
                    if( (int)qiniu.get("nums") != nums ){
                        return;
                    }
                    String imgs = (String)qiniu.get("imgs");
                    switch (type){
                        case Post.TYPE_BBQ:{
                            Bbq bbq = JSONObject.parseObject(content, Bbq.class);
                            bbq.setImgPath(imgs);
                            postService.savePostInMongoDb(bbq);
                        }
                        case Post.TYPE_XYQ:{
                            Xyq xyq = JSONObject.parseObject(content, Xyq.class);
                            xyq.setImgPath(imgs);
                            postService.savePostInMongoDb(xyq);
                        }
                        case Post.TYPE_SWZL:{
                            Swzl swzl = JSONObject.parseObject(content, Swzl.class);
                            swzl.setImgPath(imgs);
                            postService.savePostInMongoDb(swzl);
                        }
                        case Post.TYPE_TZSC:{
                            Tzsc tzsc = JSONObject.parseObject(content, Tzsc.class);
                            tzsc.setImgPath(imgs);
                            postService.savePostInMongoDb(tzsc);
                        }
                    }
                }
            }
        };
    }
}

5、确保后台退出时,关闭应用线程

package cn.xhubbq.manager;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.PreDestroy;

/**
 * 确保应用退出时能关闭后台线程
 *
 * @author 甲粒子
 */
@Component
public class ShutdownManager
{
    private static final Logger logger = LoggerFactory.getLogger("sys-user");

    @PreDestroy
    public void destroy()
    {
        shutdownAsyncManager();
    }

    /**
     * 停止异步执行任务
     */
    private void shutdownAsyncManager()
    {
        try
        {
            logger.info("====关闭后台任务任务线程池====");
           AsyncManager.me().shutdown();
        }
        catch (Exception e)
        {
            logger.error(e.getMessage(), e);
        }
    }
}

6、接口层代码

@ResponseBody
    @RequestMapping(value = "/post/v2/{content}/{type}/{nums}",produces = "application/json;charset=UTF-8",method = RequestMethod.POST)
    public String postsV2(@PathVariable String content, @PathVariable int type, @PathVariable int nums, HttpServletRequest request) {
        Map<String, Object> map = new HashMap<>();
            //上传文件
            List<MultipartFile> files;
            List<InputStream> inputStreams = new ArrayList<>();
            MultipartHttpServletRequest req = (MultipartHttpServletRequest) request;
            files = req.getFiles("imgs");

            for (MultipartFile file : files) {
                try {
                    inputStreams.add(file.getInputStream());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            AsyncManager.me().execute(AsyncFactory.posts(content,type,nums,inputStreams));
            map.put("state", 1);
            return JSONObject.toJSONString(map);
    }
  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: qftp是一种支持多线程上传文件文件传输协议。它的工作原理是通过将文件切割成多个片段,利用多个线程同时上传这些片段,从而提高上传的速度。这种方式可以使文件在网络上传输时更快地到达目标服务器,从而缩短文件上传时间。同时,qftp还支持断点续传功能,即如果在上传过程中出现了网络问题或者其他异常情况,qftp可以在上传过程中断的地方重新启动上传,继续上传上传完的那部分文件,而不是重新从头开始上传整个文件。这项功能在传输大文件时非常实用。qftp多线程上传文件是一种高效、安全、快速的文件上传方法,它在文件传输领域具有广泛的应用。无论是在企业内部文件共享,还是在网站建设中,qftp都能提供可靠的文件传输服务,提高文件传输的效率和速度,实现快速上传,快速分享和无缝协作。 ### 回答2: QFTP是一种基于FTP协议的文件传输工具,它可以通过多线程技术实现文件的快速上传多线程上传的主要思想是将大的文件分割成多个小的部分,并且同时上传多个小部分,这样可以大大减少文件上传的时间。 具体来说,多线程上传的步骤如下: 首先,将要上传文件切分成多个大小相等的部分,每个部分称为文件块。 其次,定义线程池和任务队列,将要上传的每个文件块放入任务队列中。 然后,创建多个工作线程,从任务队列中获取一个文件块,使用FTP协议上传到服务器,直到任务队列为空为止。 最后,等所有线程任务结束后进行文件块合并,将所有小文件合并为一个大文件完整上传多线程上传可以大大减少文件上传时间,提高FTP文件传输的效率。然而需要注意的是,在多线程上传的过程中,需要对文件传输时发生的各种异常进行捕获和处理,以确保文件上传的完整性和正确性。 总之,多线程上传是一个非常有用的技术,可以更加高效地进行文件上传,同时也能提高企业的生产效率和竞争力。 ### 回答3: QFTP是一个功能强大的FTP文件传输工具,它可以通过多线程上传和下载文件,使文件传输速度更快、更稳定。多线程上传文件指的是将一个大文件分成多个小文件,分别用不同的线程进行上传,从而提高上传效率。 使用QFTP多线程上传文件的步骤如下: 1. 连接FTP服务器:在QFTP中选择连接选项,输入FTP服务器的IP地址、用户名和密码,然后点击连接按钮连接到FTP服务器。 2. 选择上传文件:在QFTP中选择上传选项,选择要上传文件,并选择上传目录。 3. 分割文件:在QFTP中选择文件分割选项,将要上传的大文件分割成多个小文件。可以根据需要进行分割,分割大小通常为1MB或10MB左右。 4. 启动上传:在QFTP中选择多线程上传选项,选择上传的小文件数量,以及每个线程上传文件大小。然后就可以启动上传了。 5. 监控上传进度:在上传过程中,QFTP会显示上传进度条,可以随时查看上传进度,以及上传速度和剩余时间等信息。 总之,使用QFTP多线程上传文件可以快速、稳定地上传文件,提高文件传输效率。同时,QFTP还支持断点续传、压缩传输等功能,使文件传输更加方便和安全。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值