首页 > 其他分享 >spring boot实现切割分片上传

spring boot实现切割分片上传

时间:2023-09-24 09:55:44浏览次数:41  
标签:code return String spring boot Result 分片 import data

文件上传是web开发中经常会遇到的 springboot的默认配置为10MB,大于10M的是传不上服务器的,需要修改默认配置 但是如果修改支持大文件又会增加服务器的负担。 当文件大于一定程度时,不仅服务器会占用大量内存,而且http传输极可能会中断。 可以采用切割分片上传 html5提供的文件API中可以轻松的对文件进行分割切片,然后通过ajax异步处理向服务器传输数据,突破对大文件上传的限制,同时异步处理在一定程度上也提高了文件上传的效率。 过程描述:   将文件分割成N片   处理分片,前台会多次调用上传接口,每次都会上传文件的一部分到服务端   N个分片都上传完成后,将N个文件合并为一个文件,并将N个分片文件删除 1.服务端 (1)添加依赖

<dependency>
      <groupId>commons-fileupload</groupId>
      <artifactId>commons-fileupload</artifactId>
      <version>1.3.3</version>
</dependency>

(2)UploadController

package com.example.demo.controller;

import com.example.demo.core.Result;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FileUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
@CrossOrigin
@Controller
@RequestMapping("/api/upload")
public class UploadController {
    @PostMapping("/part")
    @ResponseBody
    public Result bigFile(HttpServletRequest request, HttpServletResponse response, String guid, Integer chunk, MultipartFile file, Integer chunks) {
        try {
            String projectUrl = System.getProperty("user.dir").replaceAll("\\\\", "/");
            ;
            boolean isMultipart = ServletFileUpload.isMultipartContent(request);
            if (isMultipart) {
                if (chunk == null) chunk = 0;
                // 临时目录用来存放所有分片文件
                String tempFileDir = projectUrl + "/upload/" + guid;
                File parentFileDir = new File(tempFileDir);
                if (!parentFileDir.exists()) {
                    parentFileDir.mkdirs();
                }
                // 分片处理时,前台会多次调用上传接口,每次都会上传文件的一部分到后台
                File tempPartFile = new File(parentFileDir, guid + "_" + chunk + ".part");
                FileUtils.copyInputStreamToFile(file.getInputStream(), tempPartFile);
            }

        } catch (Exception e) {
            return Result.failMessage(400,e.getMessage());
        }
        return Result.successMessage(200,"上次成功");
    }

    @RequestMapping("merge")
    @ResponseBody
    public Result mergeFile(String guid, String fileName) {
        // 得到 destTempFile 就是最终的文件
        String projectUrl = System.getProperty("user.dir").replaceAll("\\\\", "/");
        try {
            String sname = fileName.substring(fileName.lastIndexOf("."));
            //时间格式化格式
            Date currentTime = new Date();
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMddHHmmssSSS");
            //获取当前时间并作为时间戳
            String timeStamp = simpleDateFormat.format(currentTime);
            //拼接新的文件名
            String newName = timeStamp + sname;
            simpleDateFormat = new SimpleDateFormat("yyyyMM");
            String path = projectUrl + "/upload/";
            String tmp = simpleDateFormat.format(currentTime);
            File parentFileDir = new File(path + guid);
            if (parentFileDir.isDirectory()) {
                File destTempFile = new File(path + tmp, newName);
                if (!destTempFile.exists()) {
                    //先得到文件的上级目录,并创建上级目录,在创建文件
                    destTempFile.getParentFile().mkdir();
                    try {
                        destTempFile.createNewFile();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                for (int i = 0; i < parentFileDir.listFiles().length; i++) {
                    File partFile = new File(parentFileDir, guid + "_" + i + ".part");
                    FileOutputStream destTempfos = new FileOutputStream(destTempFile, true);
                    //遍历"所有分片文件"到"最终文件"中
                    FileUtils.copyFile(partFile, destTempfos);
                    destTempfos.close();
                }
                // 删除临时目录中的分片文件
                FileUtils.deleteDirectory(parentFileDir);
                return Result.successMessage(200,"合并成功");
            }else{
                return Result.failMessage(400,"没找到目录");
            }

        } catch (Exception e) {
            return Result.failMessage(400,e.getMessage());
        }

    }

}

说明:

  注解 @CrossOrigin 解决跨域问题

(3)Result

package com.example.demo.core;

import com.alibaba.fastjson.JSON;

/**
 * Created by Beibei on 19/02/22
 * API响应结果
 */
public class Result<T> {
    private int code;
    private String message;
    private T data;

    public Result setCode(Integer code) {
        this.code = code;
        return this;
    }

    public int getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }

    public Result setMessage(String message) {
        this.message = message;
        return this;
    }

    public T getData() {
        return data;
    }

    public Result setData(T data) {
        this.data = data;
        return this;
    }

    @Override
    public String toString() {
        return JSON.toJSONString(this);
    }

    public static <T>  Result<T> fail(Integer code,T data) {
        Result<T> ret = new Result<T>();
        ret.setCode(code);
        ret.setData(data);
        return ret;
    }

    public static <T>  Result<T> failMessage(Integer code,String msg) {
        Result<T> ret = new Result<T>();
        ret.setCode(code);
        ret.setMessage(msg);
        return ret;
    }
    public static <T>  Result<T> successMessage(Integer code,String msg) {
        Result<T> ret = new Result<T>();
        ret.setCode(code);
        ret.setMessage(msg);
        return ret;
    }

    public static <T> Result<T> success(Integer code,T data) {
        Result<T> ret = new Result<T>();
        ret.setCode(code);
        ret.setData(data);
        return ret;
    }

}
2.前端   (1)使用插件   webuploader,下载  https://github.com/fex-team/webuploader/releases
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
   <link href="css/webuploader.css" rel="stylesheet" type="text/css" />
   <script type="text/javascript" src="jquery-1.10.1.min.js"></script>
   <script type="text/javascript" src="dist/webuploader.min.js"></script>
</head>
<body>
   <div id="uploader">
      <div class="btns">
         <div id="picker">选择文件</div>
         <button id="startBtn" class="btn btn-default">开始上传</button>
      </div>
   </div>
</body>
<script type="text/javascript">
var GUID = WebUploader.Base.guid();//一个GUID
var uploader = WebUploader.create({
    // swf文件路径
    swf: 'dist/Uploader.swf',
    // 文件接收服务端。
    server: 'http://localhost:8080/api/upload/part',
    formData:{
       guid : GUID
    },
    pick: '#picker',
    chunked : true, // 分片处理
    chunkSize : 1 * 1024 * 1024, // 每片1M,
    chunkRetry : false,// 如果失败,则不重试
    threads : 1,// 上传并发数。允许同时最大上传进程数。
    resize: false
});
$("#startBtn").click(function () {
   uploader.upload();
});
//当文件上传成功时触发。
uploader.on( "uploadSuccess", function( file ) {
    $.post('http://localhost:8080/api/upload/merge', { guid: GUID, fileName: file.name}, function (data) {
       if(data.code == 200){
          alert('上传成功!');
       }
     });
});
</script>
</html>
  (2)不使用插件    直接用HTML5的File API
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <script src="jquery-1.10.1.min.js" type="text/javascript">
        </script>
        <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
    </head>
    <body>
        <div id="uploader">
            <div class="btns">
                <input id="file" name="file" type="file"/>
                <br>
                    <br>
                        <button id="startBtn">
                            开始上传
                        </button>
                    </br>
                </br>
            </div>
            <div id="output">
            </div>
        </div>
    </body>
    <script type="text/javascript">
        var status = 0;
        var page = {
        init: function(){
            $("#startBtn").click($.proxy(this.upload, this));
        },
        upload: function(){
            status = 0;
            var GUID = this.guid();
            var file = $("#file")[0].files[0],  //文件对象
                name = file.name,        //文件名
                size = file.size;        //总大小
            var shardSize = 20 * 1024 * 1024,    //以1MB为一个分片
                shardCount = Math.ceil(size / shardSize);  //总片数
            for(var i = 0;i < shardCount;++i){
                //计算每一片的起始与结束位置
                var start = i * shardSize,
                end = Math.min(size, start + shardSize);
                var partFile = file.slice(start,end);
                this.partUpload(GUID,partFile,name,shardCount,i);
            }
        },
        partUpload:function(GUID,partFile,name,chunks,chunk){
            //构造一个表单,FormData是HTML5新增的
            var  now = this;
            var form = new FormData();
            form.append("guid", GUID);
            form.append("file", partFile);  //slice方法用于切出文件的一部分
            form.append("fileName", name);
            form.append("chunks", chunks);  //总片数
            form.append("chunk", chunk);        //当前是第几片
                //Ajax提交
                $.ajax({
                    url: "http://localhost:8080/api/upload/part",
                    type: "POST",
                    data: form,
                    async: true,        //异步
                    processData: false,  //很重要,告诉jquery不要对form进行处理
                    contentType: false,  //很重要,指定为false才能形成正确的Content-Type
                    success: function(data){
                        status++;
                        if(data.code == 200){
                            $("#output").html(status+ " / " + chunks);
                        }
                        if(status==chunks){
                            now.mergeFile(GUID,name);
                        }
                    }
                });
        },
        mergeFile:function(GUID,name){
            var formMerge = new FormData();
            formMerge.append("guid", GUID);
            formMerge.append("fileName", name);
            $.ajax({
                url: "http://localhost:8080/api/upload/merge",
                type: "POST",
                data: formMerge,
                processData: false,  //很重要,告诉jquery不要对form进行处理
                contentType: false,  //很重要,指定为false才能形成正确的Content-Type
                success: function(data){
                    if(data.code == 200){
                        alert('上传成功!');
                    }
                }
            });
        },
        guid:function(prefix){
                var counter = 0;
                var guid = (+new Date()).toString( 32 ),
                    i = 0;
                for ( ; i < 5; i++ ) {
                    guid += Math.floor( Math.random() * 65535 ).toString( 32 );
                }
                return (prefix || 'wu_') + guid + (counter++).toString( 32 );
        }
    };

    $(function(){
        page.init();
    });
    </script>
</html>

3.优化 

springboot的默认配置为10MB,前端分片改为20M时,就会报错

org.apache.tomcat.util.http.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (10486839) exceeds the configured maximum (10485760)

  解决方法:

  在 src/main/resources 下的 application.properties里添加

spring.servlet.multipart.max-file-size=30MB
spring.servlet.multipart.max-request-size=35MB

  说明:

    设置的数值最好比前端传过来的大,要不容易报错

 

参考文章:http://blog.ncmem.com/wordpress/2023/09/24/spring-boot%e5%ae%9e%e7%8e%b0%e5%88%87%e5%89%b2%e5%88%86%e7%89%87%e4%b8%8a%e4%bc%a0/

欢迎入群一起讨论

 

 

 

 

标签:code,return,String,spring,boot,Result,分片,import,data
From: https://www.cnblogs.com/songsu/p/17725620.html

相关文章

  • SpringBoot开发实战(微课视频版)
    ISBN:978-7-302-52819-7编著:吴胜页数:311页阅读时间:2023-06-24推荐指数:★★★★☆本文介绍SpringBoot2.0.5、JDK1.8,虽然现在已经不维护了,但是大体的流程还是对口的,而且书里面讲解的也比较简单易懂,还是推荐阅读的。第一章:SpringBoot简介SpringBoot发展背景、特征、工......
  • springboot的Maven的镜像
     Maven的镜像<!--阿里镜像--><mirror><id>alimaven</id><mirrorOf>central</mirrorOf><name>aliyunmaven</name><url>http://maven.aliyun.com/nexus/content/repositories/central/</url>......
  • 负载均衡 —— SpringCloud Netflix Ribbon
    Ribbon简介Ribbon是Netfix客户端的负载均衡器,可对HTTP和TCP客户端的行为进行控制。为Ribbon配置服务提供者地址后,Ribbon就可以基于某种负载均衡算法自动帮助服务消费者去请求。Ribbon默认提供了很多负载均衡算法,例如轮询、随机等,也可以为Ribbon实现自定义的负载均......
  • SpringCloud-Config配置中心搭建保姆级教程
    一、分布式配置中⼼在使⽤微服务架构开发的项⽬中,每个服务都有⾃⼰的配置⽂件(application.yml),如果将每个服务的配置⽂件直接写在对应的服务中,存在以下问题:1.服务开发完成之后,需要打包部署,配置⽂件也会打包在jar⽂件中,不便于项⽬部署之后的配置修改(在源码中修改——重新打包—......
  • SpringCloud --> 什么是微服务?
    微服务我们可以理解为是一种架构设计风格,就是将一个项目拆分成一个或者多个服务,每个服务都可以单独的运行,而且每个服务都会占用线程。从字面意思上我们可以理解为"微小的服务",我们从微小、服务来理解微小:强调的是单一项目的体积小,一个微服务通常只提供单个业务的功能,一个......
  • SpringBoot返回字符串乱码如何解决
    场景:controller某个接口是直接返回字符串,用于测试的。当返回中文字符串的时候乱码。解决方法:在controller的映射加上produces="text/html;charset=utf-8"示例:@RequestMapping(value="/ping",produces="text/html;charset=utf-8")publicStringping(){return"pon......
  • Spring Boot框架知识总结(超详细,一次性到位)
    前言本篇文章包含Springboot配置文件解释、热部署、自动装配原理源码级剖析、内嵌tomcat源码级剖析、缓存深入、多环境部署等等,如果能耐心看完,想必会有不少收获。一、SpringBoot基础应用SpringBoot特征概念:约定优于配置,简单来说就是你所期待的配置与约定的配置一致,那么就可以不做......
  • 关于初次new springboot项目
    如果是新手初学,然后做springboot项目报各种错,改来改去最终都无法出现successful字样。请先检查,maven环境是否配好。maven环境决定你下载依赖的速度,以及能否下载成功。 maven管理你的springboot项目,所以配置它的环境是必不可少的。 比如未配置maven环境直接运行项目可能会......
  • spring事务控制的原理解析2
    上一篇:[spring事务控制的原理解析1](https://www.cnblogs.com/chengxuxiaoyuan/p/16581334.html)上一篇中总结了在spring中事务控制的基本原理,这一篇来记录下在spring源码中是如何实现的。一、TransactionInterceptorspring中的事务控制是通过aop实现的,提到aop肯定会有一个......
  • Springboot+Vue(二)
    上传的静态资源文件放到static文件夹下即可表单的enctype属性规定再发送到服务器之前该如何对表单数据进行编码默认情况下数据格式为key=value&key=value需要改成enctype="multipart/form-data"默认情况下限制了单次请求文件的大小单个文件不能超过1Mb所有文件不能超过......