IT序号网

springboot带有进度条的上传

luoye 2021年05月25日 编程语言 374 0

一、说明

  最近要做文件上传,在网上找了很久都没有一个全面的示例,特此记录下来分享给大家。

  1.文件上传接口可按照springboot默认实现,也可用commons-fileupload组件,本示例使用springboot默认文件上传 2.最后也有commons-fileupload组件接口示例

  2.重点在前端JS实现(也可以使用ajax上传),参考了网上大量上传文件显示进度条博客以及技术方案,在此做了一个统一的总结,方便后续使用

  3.这仅仅是一个示例,大家可根据实际需要改进。

二、前端代码

<!DOCTYPE html> 
<html> 
<meta charset="UTF-8" /> 
<head> 
<title>文件上传</title> 
<link href="https://cdn.bootcss.com/bootstrap/3.3.2/css/bootstrap.css" 
    rel="stylesheet"> 
<script src="http://code.jquery.com/jquery-1.10.2.min.js"></script> 
</head> 
<body class="container"> 
    <br /> 
    <span id="time"></span> 
    <div class="row"> 
        <input class="btn btn-info btn-xs" type="file" name="file" /><br /> 
        <div class="col-lg-5" 
            style="padding-left: 0; padding-right: 0; margin-bottom: 0px;"> 
            <div class="progress progress-striped active" style="display: "> 
                <div id="progressBar" class="progress-bar progress-bar-success" 
                    role="progressbar" aria-valuemin="0" aria-valuenow="0" 
                    aria-valuemax="100" style="width: 20%"></div> 
            </div> 
        </div> 
        <!-- 显示上传速度 --> 
        <div id="showInfo" class="col-lg-2">0KB/s</div> 
    </div> 
    <!-- 显示文件信息 --> 
    <div id="showFieInfo" class="row"> 
        <label name="upfileName"></label><br />  
        <label name="upfileSize"></label><br /> 
        <label name="upfileType"></label><br /> 
    </div> 
    <div class="row"> 
        <input class="btn btn-success btn-xs" type="button" name="upload" value="上传" /> 
        <input class="btn btn-success btn-xs" type="button" name="cancelUpload" value="取消" /> 
    </div> 
</body> 
<script type="text/javascript"> 
    var fileBtn = $("input[name=file]"); 
    var processBar= $("#progressBar"); 
    var uploadBtn=$("input[name=upload]"); 
    var canelBtn=$("input[name=cancelUpload]"); 
    var ot;//上传开始时间 
    var oloaded;//已上传文件大小 
    fileBtn.change(function() { 
        var fileObj = fileBtn.get(0).files[0]; //js获取文件对象 
        if (fileObj) { 
            var fileSize = getSize(fileObj.size); 
            $("label[name=upfileName]").text('文件名:' + fileObj.name); 
            $("label[name=upfileSize]").text('文件大小:' + fileSize); 
            $("label[name=upfileType]").text('文件类型:' + fileObj.type); 
            uploadBtn.attr('disabled', false); 
        } 
    }); 
    // 上传文件按钮点击的时候 
    uploadBtn.click(function(){ 
        // 进度条归零 
        setProgress(0); 
        // 上传按钮禁用 
        $(this).attr('disabled', true); 
        // 进度条显示 
        showProgress(); 
        // 上传文件 
        uploadFile(); 
    }); 
    function uploadFile(){ 
        var url ="/to/upload"; 
        var fileObj = fileBtn.get(0).files[0]; 
        if(fileObj==null){ 
            alert("请选择文件"); 
            return; 
        } 
        // FormData 对象 
        var form = new FormData(); 
        form.append('file', fileObj); // 文件对象 
        // XMLHttpRequest 对象 
        var xhr = new XMLHttpRequest(); 
        //true为异步处理 
        xhr.open('post', url, true); 
        //上传开始执行方法 
        xhr.onloadstart = function() { 
             console.log('开始上传') 
             ot = new Date().getTime();   //设置上传开始时间 
             oloaded = 0;//已上传的文件大小为0 
        }; 
        
        xhr.upload.addEventListener('progress', progressFunction, false); 
        xhr.addEventListener("load", uploadComplete, false); 
        xhr.addEventListener("error", uploadFailed, false); 
        xhr.addEventListener("abort", uploadCanceled, false); 
        xhr.send(form); 
         
        function progressFunction(evt) { 
            debugger; 
            if (evt.lengthComputable) { 
                var completePercent = Math.round(evt.loaded / evt.total * 100) 
                        + '%'; 
                processBar.width(completePercent); 
                processBar.text(completePercent); 
                 
                var time = $("#time"); 
                var nt = new Date().getTime();     //获取当前时间 
                var pertime = (nt-ot)/1000;        //计算出上次调用该方法时到现在的时间差,单位为s 
                ot = new Date().getTime();          //重新赋值时间,用于下次计算 
                 
                var perload = evt.loaded - oloaded; //计算该分段上传的文件大小,单位b        
                oloaded = evt.loaded;               //重新赋值已上传文件大小 
             
                //上传速度计算 
                var speed = perload/pertime;//单位b/s 
                var bspeed = speed; 
                var units = 'b/s';//单位名称 
                if(speed/1024>1){ 
                    speed = speed/1024; 
                    units = 'k/s'; 
                } 
                if(speed/1024>1){ 
                    speed = speed/1024; 
                    units = 'M/s'; 
                } 
                speed = speed.toFixed(1); 
                //剩余时间 
                var resttime = ((evt.total-evt.loaded)/bspeed).toFixed(1); 
                $("#showInfo").html(speed+units+',剩余时间:'+resttime+'s'); 
            } 
        } 
 
        //上传成功后回调                                                                  
        function uploadComplete(evt) { 
            uploadBtn.attr('disabled', false); 
            console.log('上传完成') 
        }; 
 
        //上传失败回调             
        function uploadFailed(evt) { 
            console.log('上传失败' + evt.target.responseText); 
        } 
 
        //终止上传       
        function cancelUpload() { 
            xhr.abort(); 
        } 
         
        //上传取消后回调              
        function uploadCanceled(evt) { 
            console.log('上传取消,上传被用户取消或者浏览器断开连接:' + evt.target.responseText); 
        } 
         
        canelBtn.click(function(){ 
            uploadBtn.attr('disabled', false); 
            cancelUpload(); 
        }) 
    } 
    function getSize(size) { 
        var fileSize = '0KB'; 
        if (size > 1024 * 1024) { 
            fileSize = (Math.round(size / (1024 * 1024))).toString() + 'MB'; 
        } else { 
            fileSize = (Math.round(size / 1024)).toString() + 'KB'; 
        } 
        return fileSize; 
    } 
    function setProgress(w) { 
        processBar.width(w + '%'); 
    } 
    function showProgress() { 
        processBar.parent().show(); 
    } 
    function hideProgress() { 
        processBar.parent().hide(); 
    } 
</script> 
</html>

效果:

三、对上传代码进行组件化封装

UploadCommon.js

/** 
 * 上传文件公共组件 
 *  
 * @param url 上传地址 
 * @param processBar 进度条 jquery获取的页面组件 
 * @param speedLab 显示上传速度Label jquery获取的页面组件 
 * @param uploadBtn 上传按钮 jquery获取的页面组件 
 * @param cancelBtn 取消上传按钮  jquery获取的页面组件 
 * @param callBack 上传完成回调函数 上传完成后的回调函数,可以不传 
 * @author 
 * @returns 
 */ 
function UploadCommon(url, processBar, speedLab, uploadBtn, cancelBtn, callBack){ 
     
    function init() { 
        // 每次回调监测上传开始时间 
        var startTime = null 
        // 已上传文件大小 
        var oloaded = null  
        var xhr = new XMLHttpRequest() 
        function setProgress(w) { 
            processBar.width(w + '%'); 
            processBar.text(w + '%'); 
        } 
        function progressMonitor(evt){ 
            if (evt.lengthComputable) { 
                var completePercent = Math.round(evt.loaded / evt.total * 100) 
                setProgress(completePercent) 
                var nowTime = new Date().getTime() 
                // 计算出上次调用该方法时到现在的时间差,单位为s 
                var pertime = (nowTime - startTime) / 1000 
          // 重新赋值时间,用于下次计算 startTime = new Date().getTime() // 计算该分段上传的文件大小,单位b var perload = evt.loaded - oloaded // 重新赋值已上传文件大小,用以下次计算 oloaded = evt.loaded // 上传速度计算,单位b/s var speed = perload / pertime var bspeed = speed // 单位名称 var units = 'bit/s'if (speed / 1024 > 1) { speed = speed / 1024 units = 'Kb/s' } if (speed / 1024 > 1) { speed = speed / 1024 units = 'Mb/s' } speed = speed.toFixed(1); // 剩余时间 var resttime = ((evt.total - evt.loaded) / bspeed).toFixed(1) speedLab.html(speed + units + ',剩余时间:' + resttime + 's') } } // 上传成功后回调 function uploadComplete(evt) { uploadBtn.attr('disabled', false) var status = evt.currentTarget.status if (status == 401) { alert('请登录后再上传') return } if (status == 403) { alert('无权限操作') return } if (status != 200) { alert('上传异常,错误码' + status) return } var response=JSON.parse(evt.currentTarget.response) if (response.code!='200') { alert('上传处理异常' + response.msg) return } console.log('上传成功') if ( callBack != null && typeof callBack != 'undefined') { callBack() } }; // 上传失败回调 function uploadFailed(evt) { alert('上传处理失败' + evt.target.responseText) } // 终止上传 function cancelUpload() { xhr.abort() } // 上传取消后回调 function uploadCanceled(evt) { alert('文件上传已终止:' + evt.target.responseText) } // 添加取消上传事件 cancelBtn.click(function() { uploadBtn.attr('disabled', false) cancelUpload(); }) this.uploadFile = function(formData) { // 上传按钮禁用 uploadBtn.attr('disabled', true); setProgress(0) // true为异步处理 xhr.open('post', url, true) // 上传开始执行方法 xhr.onloadstart = function() { console.log('开始上传') // 设置上传开始时间 startTime = new Date().getTime() // 已上传的文件大小为0 oloaded = 0 } xhr.upload.addEventListener('progress', progressMonitor, false) xhr.addEventListener("load", uploadComplete, false) xhr.addEventListener("error", uploadFailed, false) xhr.addEventListener("abort", uploadCanceled, false) xhr.send(formData); } this.setProgressValue=function(w){ processBar.width(w + '%') processBar.text(w + '%') } } return new init() }

调用

$(document).ready(function() { 
    var addVersionBtn=$('#addVersionBtn')  //<button> 
    var cancelUploadBtn=$('#cancelUploadBtn') //<button> 
    var fileInput=$("#filewareFile")  //<input> 
    var processBar = $("#progressBar"); //div 
    var fileNameLab=$("label[name=upfileName]") //<label> 
    var fileSizeLab=$("label[name=upfileSize]") //... 
    var fileTypeLab=$("label[name=upfileType]") //... 
    var speedLab=$("#showSpeed") //<label> 
     
    var url='/api/file' 
 
    //获取文件上传实例 
    var upload=UploadCommon(url,processBar,speedLab,addVersionBtn,cancelUploadBtn,initPageInfo) 
     
    // 文件选择框变更事件 
    fileInput.change(function() { 
        var fileObj = fileInput.get(0).files[0]; // js获取文件对象 
        if (fileObj) { 
            var fileSize = getSize(fileObj.size); 
            fileNameLab.text('文件名:' + fileObj.name); 
            fileSizeLab.text('文件大小:' + fileSize); 
            fileTypeLab.text('文件类型:' + fileObj.type); 
            addVersionBtn.attr('disabled', false); 
        } 
    }); 
     
    // 点击上传固件事件 
    addVersionBtn.click(function(){ 
        var versionInfo=$('#version').val() 
        var file = fileInput.get(0).files[0] 
        var strategyInfo=$('#versionType').val() 
        if(file==null){ 
            alert("固件文件不能为空") 
            return 
        } 
        if(versionInfo==''){ 
            alert("版本号不能为空") 
            return 
        } 
        if(strategyInfo==''){ 
            alert("升级策略不能为空") 
            return 
        } 
         
        // 创建提交数据 
        var formData = new FormData(); 
        formData.append('firmFile', fileInput.get(0).files[0]);  
        formData.append('version', versionInfo); 
        formData.append('strategy', strategyInfo);  
         
        // 上传文件 
        upload.uploadFile(formData)         
    }) 
}); 
 

四,服务端接口

1.springboot默认实现

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 
    <modelVersion>4.0.0</modelVersion> 
    <groupId>com.demo</groupId> 
    <artifactId>demo</artifactId> 
    <version>0.0.1-SNAPSHOT</version> 
    <parent> 
        <groupId>org.springframework.boot</groupId> 
        <artifactId>spring-boot-starter-parent</artifactId> 
        <version>1.5.10.RELEASE</version> 
        <relativePath/> 
    </parent> 
 
    <properties> 
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> 
        <java.version>1.8</java.version> 
    </properties> 
    <dependencies> 
        <dependency> 
            <groupId>org.springframework.boot</groupId> 
            <artifactId>spring-boot-devtools</artifactId> 
            <optional>true</optional> 
        </dependency> 
        <dependency> 
            <groupId>org.springframework.boot</groupId> 
            <artifactId>spring-boot-starter-web</artifactId> 
        </dependency> 
        <dependency> 
            <groupId>org.springframework.boot</groupId> 
            <artifactId>spring-boot-starter-thymeleaf</artifactId> 
        </dependency> 
        <dependency>  
            <groupId>net.sourceforge.nekohtml</groupId>  
            <artifactId>nekohtml</artifactId> 
        </dependency> 
        <dependency> 
            <groupId>org.springframework.boot</groupId> 
            <artifactId>spring-boot-starter-test</artifactId> 
            <scope>test</scope> 
        </dependency> 
        <!-- 添加Swagger2依赖,用于生成接口文档 --> 
        <dependency> 
            <groupId>io.springfox</groupId> 
            <artifactId>springfox-swagger2</artifactId> 
            <version>2.7.0</version> 
        </dependency> 
        <dependency> 
            <groupId>io.springfox</groupId> 
            <artifactId>springfox-swagger-ui</artifactId> 
            <version>2.7.0</version> 
        </dependency> 
        <!--end--> 
    </dependencies> 
   
    <build> 
        <plugins> 
            <plugin> 
                <groupId>org.springframework.boot</groupId> 
                <artifactId>spring-boot-maven-plugin</artifactId> 
            </plugin> 
        </plugins> 
    </build> 
</project>

application.yml

server: 
  port: 8080 
  tomcat: 
    uri-encoding: UTF-8 
  application: 
    name: demo 
  thymeleaf: 
    encoding: UTF-8 
    cache: true 
    mode: LEGACYHTML5 
  devtools: 
    restart: 
      enabled: true 
  http: 
    multipart: 
      maxFileSize: 500Mb 
      maxRequestSize: 500Mb 
      location: D:/tmp 
debug: false

 接口:

@PostMapping("/upload")
public String uploadFile(@RequestParam("file") MultipartFile file) { if (file == null || file.isEmpty()) { return "file is empty"; } // 获取文件名 String fileName = file.getOriginalFilename(); // 文件存储路径 String filePath = "D:/data/" + UUID.randomUUID().toString().replaceAll("-", "") + "_" + fileName; logger.info("save file to:" + filePath); File dest = new File(filePath); if (!dest.getParentFile().exists()) { dest.getParentFile().mkdirs(); } try { file.transferTo(dest); return "success"; } catch (Exception e) { e.printStackTrace(); } return "fail"; }

启动类

import org.springframework.boot.SpringApplication; 
import org.springframework.boot.autoconfigure.SpringBootApplication; 
import org.springframework.boot.builder.SpringApplicationBuilder; 
import org.springframework.boot.web.support.SpringBootServletInitializer; 
import org.springframework.transaction.annotation.EnableTransactionManagement; 
 
@SpringBootApplication 
@EnableTransactionManagement 
public class Application extends SpringBootServletInitializer { 
 
    @Override 
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { 
        return application.sources(Application.class); 
    } 
 
    public static void main(String[] args) { 
        SpringApplication.run(Application.class, args); 
    } 
}

2.使用commons-fileupload上传组件

application.yml

server:  
  port: 8080 
  tomcat:  
  uri-encoding :  UTF-8 
spring:  
  application:  
    name: svc-demo 
  thymeleaf:  
    encoding: UTF-8 
    cache: false 
    mode: LEGACYHTML5 
debug: false

 pom .xml

  <parent> 
      <groupId>org.springframework.boot</groupId> 
      <artifactId>spring-boot-starter-parent</artifactId> 
      <version>1.5.10.RELEASE</version> 
      <relativePath/> 
  </parent> 
  <properties> 
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 
     <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> 
     <java.version>1.8</java.version> 
  </properties> 
  <dependencies> 
      <dependency> 
         <groupId>org.springframework.boot</groupId> 
         <artifactId>spring-boot-devtools</artifactId> 
         <optional>true</optional> 
      </dependency> 
      <dependency> 
         <groupId>org.springframework.boot</groupId> 
         <artifactId>spring-boot-starter-web</artifactId> 
      </dependency> 
      <dependency> 
         <groupId>org.springframework.boot</groupId> 
         <artifactId>spring-boot-starter-thymeleaf</artifactId> 
      </dependency> 
      <dependency> 
         <groupId>org.springframework.boot</groupId> 
         <artifactId>spring-boot-starter-test</artifactId> 
         <scope>test</scope> 
      </dependency> 
      <!--添加文件上传支持--> 
      <dependency> 
            <groupId>commons-io</groupId> 
            <artifactId>commons-io</artifactId> 
            <version>2.4</version> 
      </dependency> 
      <dependency> 
            <groupId>commons-fileupload</groupId> 
            <artifactId>commons-fileupload</artifactId> 
            <version>1.3.1</version> 
     </dependency> 
     <!--添加html5支持--> 
     <dependency>  
            <groupId>net.sourceforge.nekohtml</groupId>  
            <artifactId>nekohtml</artifactId> 
      </dependency>  
  </dependencies> 
  <build> 
      <plugins> 
         <plugin> 
            <groupId>org.springframework.boot</groupId> 
            <artifactId>spring-boot-maven-plugin</artifactId> 
         </plugin> 
      </plugins> 
  </build>

进程类

public class Progress{ 
    private long bytesRead; //已读取文件的比特数 
    private long contentLength;//文件总比特数 
    private long items; //正读的第几个文件 
 
    public long getBytesRead(){ 
        return bytesRead; 
    } 
 
    public void setBytesRead(long bytesRead){ 
        this.bytesRead = bytesRead; 
    } 
 
    public long getContentLength() { 
        return contentLength; 
    } 
 
    public void setContentLength(long contentLength) { 
        this.contentLength = contentLength; 
    } 
 
    public long getItems() { 
        return items; 
    } 
 
    public void setItems(long items)\{ 
        this.items = items; 
    } 
}

监听类

@Component 
public class FileUploadProgressListener implements ProgressListener{ 
 
    private HttpSession session; 
    public void setSession(HttpSession session){ 
        this.session=session; 
        Progress status = new Progress();//保存上传状态 
        session.setAttribute("status", status); 
    } 
    @Override 
    public void update(long bytesRead, long contentLength, int items) { 
        Progress status = (Progress) session.getAttribute("status"); 
        status.setBytesRead(bytesRead); 
        status.setContentLength(contentLength); 
        status.setItems(items); 
 
    } 
 
}

文件上传处理类

public class CustomMultipartResolver extends CommonsMultipartResolver{ 
    // 注入第二步写的FileUploadProgressListener 
    @Autowired 
    private FileUploadProgressListener progressListener; 
 
    public void setFileUploadProgressListener(FileUploadProgressListener progressListener){ 
        this.progressListener = progressListener; 
    } 
 
    @Override 
    public MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException{ 
        String encoding = determineEncoding(request); 
        FileUpload fileUpload = prepareFileUpload(encoding); 
     //fileUpload.setFileSizeMax(1024 * 1024 * 500);// 单个文件最大500M 
        //fileUpload.setSizeMax(1024 * 1024 * 500);// 一次提交总文件最大500M 
        progressListener.setSession(request.getSession());// 问文件上传进度监听器设置session用于存储上传进度 
        fileUpload.setProgressListener(progressListener);// 将文件上传进度监听器加入到 fileUpload 中 
        try{ 
            List<FileItem> fileItems = ((ServletFileUpload) fileUpload).parseRequest(request); 
            return parseFileItems(fileItems, encoding); 
        } catch (FileUploadBase.SizeLimitExceededException ex) { 
            throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(), ex); 
        } catch (FileUploadException ex){ 
            throw new MultipartException("Could not parse multipart servlet request", ex); 
        } 
    } 
}

控制器

@RestController 
public class FileController{ 
    @PostMapping("/upload") 
    public String uploadFile(@RequestParam("file") MultipartFile file) { 
        if (file.isEmpty()) { 
            return "文件为空"; 
     } 
        // 获取文件名 
        String fileName = file.getOriginalFilename();// 文件上传后的路径 
        // 文件上传后的路径 
        String filePath = null; 
        try{ 
            filePath = new File("").getCanonicalPath() + "/tmp/uploadFile/"; 
        } catch (IOException e){ 
            e.printStackTrace(); 
        } 
        //存储路径 
        String tagFilePath = filePath + CommonUtil.getCurrentTime() + fileName; 
        File dest = new File(tagFilePath); 
        // 检测是否存在目录 
        if (!dest.getParentFile().exists()){ 
            dest.getParentFile().mkdirs(); 
        } 
        try{ 
            file.transferTo(dest); 
        } catch (IllegalStateException e){ 
            e.printStackTrace(); 
        } catch (IOException e){ 
            e.printStackTrace(); 
        } 
        return fileName + "上传失败"; 
    } 
}

启动类

//注意取消自动Multipart配置,否则可能在上传接口中拿不到file的值
@EnableAutoConfiguration(exclude = { MultipartAutoConfiguration.class }) @SpringBootApplication public class Application extends SpringBootServletInitializer{ //注入自定义的文件上传处理类 @Bean(name = "multipartResolver") public MultipartResolver multipartResolver() { CustomMultipartResolver customMultipartResolver = new CustomMultipartResolver(); return customMultipartResolver; } @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application){ return application.sources(Application.class); } public static void main(String[] args) { SpringApplication.run(Application.class, args); }


评论关闭
IT序号网

微信公众号号:IT虾米 (左侧二维码扫一扫)欢迎添加!