• springboot带有进度条的上传


    一、说明

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

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

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

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

      4.前端新增vue axios上传进度条实现

    二、前端代码

    vue+axios实现

    <!--引入vue和axios-->
    <script src="https://cdn.staticfile.org/vue/2.4.2/vue.min.js"></script>
    <script src="https://cdn.staticfile.org/axios/0.18.0/axios.min.js"></script>
    
    <input ref="pathClear" @change="handleFileChange($event)" class="form-control" type="file"></input>
    <button @click="submit()" class="btn btn-sm btn-primary">添加</button>
    
    <script type="text/javascript">
    
        // 获取axios post 以multipart/form-data形式上传时的配置参数
        // callBack为上传进度回调函数
        getAxiosMultiPostConfig = function (callBack) {
            // 上传进度回调
            function onUploadProgress(progressEvent) {
                if (progressEvent.lengthComputable) {
                    let val = (progressEvent.loaded / progressEvent.total * 100);
                    val = parseInt(val)
                    // 这里的进度只能表明文件已经上传到后台,但是后台有没有处理完还不知道
                    // 因此不能直接显示为100%,不然用户会误以为已经上传完毕,关掉浏览器的话就可能导致上传失败
                    // 等响应回来时,再将进度设为100%
                    if (val < 100)
                        callBack(val)
                }
            }
            let config = {
                headers: {'Content-Type': 'multipart/form-data'},
                onUploadProgress: onUploadProgress
            };
            return config
        }
        
        new Vue({
            el : "#app",
            data(){
                return {
                    file: null,
                    uploadPercent: 0,//上传进度
                }
            },
            mounted(){
               
            },
            methods:{
                submit(){
                    //表单上传
                    let formData = new FormData();
                    formData.append("file", this.file)
                    axios.post("/admin/product/add",formData,getAxiosMultiPostConfig((val)=>{
                        this.uploadPercent = val
                    }))
                    .then(response => {
                        this.uploadPercent = 100
                        alert("上传成功")
                    })
                    .catch(error => {
                        //hander error
                     })
                    .then(() => {
                        //always excute
                     });
                },
                // 文件选择
                handleFileChange(event,key) {
                    let file = event.target.files[0]
                    this.file = file
                }    
            }
        })
    </script>

    jquery实现

    <!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=" 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); }
  • 相关阅读:
    【Unittest】unittest相关解说
    python中导入模块/包的几种方式
    工具网站
    mapstruct 入门指南
    springboot 全局异常处理器
    多线程读表-压缩成zip下载
    编译执行和解释执行的区别
    swagger常用注解说明
    springboot整合swagger
    springboot整合druid
  • 原文地址:https://www.cnblogs.com/zincredible/p/9060663.html
Copyright © 2020-2023  润新知