1. 程式人生 > 程式設計 >springboot帶有進度條的上傳功能完整例項

springboot帶有進度條的上傳功能完整例項

本文例項講述了springboot帶有進度條的上傳功能。分享給大家供大家參考,具體如下:

一、說明

  最近要做檔案上傳,在網上找了很久都沒有一個全面的示例,特此記錄下來分享給大家。

  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="external nofollow" 
  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',true)
      // 上傳開始執行方法
      xhr.onloadstart = function() {
        console.log('開始上傳')
        // 設定上傳開始時間
        startTime = new Date().getTime() 
        // 已上傳的檔案大小為0
        oloaded = 0 
      }
      xhr.upload.addEventListener('progress',progressMonitor,false)
      xhr.addEventListener("load",false)
      xhr.addEventListener("error",false)
      xhr.addEventListener("abort",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,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);
  }

更多關於java相關內容感興趣的讀者可檢視本站專題:《Spring框架入門與進階教程》、《Java資料結構與演算法教程》、《Java操作DOM節點技巧總結》、《Java檔案與目錄操作技巧彙總》和《Java快取操作技巧彙總》

希望本文所述對大家java程式設計有所幫助。