1. 程式人生 > >SpringBoot+fileUpload獲取檔案上傳進度

SpringBoot+fileUpload獲取檔案上傳進度

我本人在網上找了很多關於檔案上傳進度獲取的文章,普遍基於spring MVC 框架通過 fileUpload 實現,對於spring Boot 通過 fileUpload 實現的帖子非常少,由於小弟學藝不精,雖然 Spring Boot 和 Spring MVC 相差不大,只是配置方式的差別,還是搞了很久,上傳此文章的目的是希望自己作為文字保留,以便日後檢視備忘,並且希望通過我的例子可以幫助到其他人而已,如果各位大佬發現小弟對於某些知識有誤解,還請不吝賜教,先謝謝各位前輩了!

寫此篇文章之前我查了很多關於spring MVC 框架通過 fileUpload 實現進度條的帖子和文章,在此對各位作者表示感謝!

此方法有一個問題,很嚴重的問題,就是隻能獲取當前伺服器上傳的總進度,沒法分使用者計算,不知道哪位前輩有辦法分使用者計算,煩請不吝賜教,謝謝各位前輩!

本功能基於commons fileUpload 元件實現

1.首先,不能在程式中直接使用 fileUpload.parseRequest(request)的方式來獲取 request 請求中的 multipartFile 檔案物件,原因是因為在 spring 預設的檔案上傳處理器 multipartResolver 指向的類CommonsMultipartResolver 中就是通過 commons fileUpload 元件實現的檔案獲取,因此,在程式碼中再次使用該方法,是獲取不到檔案物件的,因為此時的 request 物件是不包含檔案的,它已經被CommonsMultipartResolver 類解析處理並轉型。

CommonsMultipartResolver 類中相關原始碼片段:

protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException {
		String encoding = determineEncoding(request);
		FileUpload fileUpload = prepareFileUpload(encoding);
		try {
			List<FileItem> fileItems = ((ServletFileUpload) fileUpload).parseRequest(request);
			return parseFileItems(fileItems, encoding);
		}
		catch (FileUploadBase.SizeLimitExceededException ex) {
			throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(), ex);
		}
		catch (FileUploadBase.FileSizeLimitExceededException ex) {
			throw new MaxUploadSizeExceededException(fileUpload.getFileSizeMax(), ex);
		}
		catch (FileUploadException ex) {
			throw new MultipartException("Failed to parse multipart servlet request", ex);
		}
}

2.由於spring 中的 CommonsMultipartResolver 類中並沒有加入 processListener 檔案上傳進度監聽器,所以,直接使用 CommonsMultipartResolver 類是無法監聽檔案上傳進度的,如果我們需要獲取檔案上傳進度,就需要繼承 CommonsMultipartResolver 類並重寫 parseRequest 方法,在此之前,我們需要建立一個實現了 processListener 介面的實現類用於監聽檔案上傳進度。

processListener介面實現類:

import javax.servlet.http.HttpSession;
import org.apache.commons.fileupload.ProgressListener;
import org.springframework.stereotype.Component;

@Component
public class UploadProgressListener implements ProgressListener{
	
    private HttpSession session;  
    
    public void setSession(HttpSession session){  
        this.session=session;  
        ProgressEntity status = new ProgressEntity();  
        session.setAttribute("status", status);  
    }  
  
    /* 
     * pBytesRead 到目前為止讀取檔案的位元數 pContentLength 檔案總大小 pItems 目前正在讀取第幾個檔案 
     */  
	@Override
    public void update(long pBytesRead, long pContentLength, int pItems) {  
        ProgressEntity status = (ProgressEntity) session.getAttribute("status");  
        status.setpBytesRead(pBytesRead);  
        status.setpContentLength(pContentLength);  
        status.setpItems(pItems);  
    }  
}

ProgressEntity 實體類:

import org.springframework.stereotype.Component;

@Component
public class ProgressEntity {  
    private long pBytesRead = 0L;   //到目前為止讀取檔案的位元數   
    private long pContentLength = 0L;    //檔案總大小   
    private int pItems;                //目前正在讀取第幾個檔案   
      
    public long getpBytesRead() {  
        return pBytesRead;  
    }  
    public void setpBytesRead(long pBytesRead) {  
        this.pBytesRead = pBytesRead;  
    }  
    public long getpContentLength() {  
        return pContentLength;  
    }  
    public void setpContentLength(long pContentLength) {  
        this.pContentLength = pContentLength;  
    }  
    public int getpItems() {  
        return pItems;  
    }  
    public void setpItems(int pItems) {  
        this.pItems = pItems;  
    }  
    @Override  
    public String toString() {  
        float tmp = (float)pBytesRead;  
        float result = tmp/pContentLength*100;  
        return "ProgressEntity [pBytesRead=" + pBytesRead + ", pContentLength="  
                + pContentLength + ", percentage=" + result + "% , pItems=" + pItems + "]";  
    }  
}  

最後,是繼承 CommonsMultipartResolver 類的自定義檔案上傳處理類:

import java.util.List;  
import javax.servlet.http.HttpServletRequest;  
import org.apache.commons.fileupload.FileItem;  
import org.apache.commons.fileupload.FileUpload;  
import org.apache.commons.fileupload.FileUploadBase;  
import org.apache.commons.fileupload.FileUploadException;  
import org.apache.commons.fileupload.servlet.ServletFileUpload;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.web.multipart.MaxUploadSizeExceededException;  
import org.springframework.web.multipart.MultipartException;  
import org.springframework.web.multipart.commons.CommonsMultipartResolver;  

public class CustomMultipartResolver extends CommonsMultipartResolver{

	@Autowired
	private UploadProgressListener uploadProgressListener;
	
	@Override
	protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException {
		String encoding = determineEncoding(request);
		FileUpload fileUpload = prepareFileUpload(encoding);
		uploadProgressListener.setSession(request.getSession());//問檔案上傳進度監聽器設定session用於儲存上傳進度
		fileUpload.setProgressListener(uploadProgressListener);//將檔案上傳進度監聽器加入到 fileUpload 中
		try {
			List<FileItem> fileItems = ((ServletFileUpload) fileUpload).parseRequest(request);
			return parseFileItems(fileItems, encoding);
		}
		catch (FileUploadBase.SizeLimitExceededException ex) {
			throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(), ex);
		}
		catch (FileUploadBase.FileSizeLimitExceededException ex) {
			throw new MaxUploadSizeExceededException(fileUpload.getFileSizeMax(), ex);
		}
		catch (FileUploadException ex) {
			throw new MultipartException("Failed to parse multipart servlet request", ex);
		}
	}
	
}

3.此時,所有需要的類已經準備好,接下來我們需要將 spring 預設的檔案上傳處理類取消自動配置,並將 multipartResolver 指向我們剛剛建立好的繼承 CommonsMultipartResolver 類的自定義檔案上傳處理類。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.multipart.MultipartResolver;

import com.example.listener.CustomMultipartResolver;

/*
 * 將 spring 預設的檔案上傳處理類取消自動配置,這一步很重要,沒有這一步,當multipartResolver重新指向了我們定義好
 * 的新的檔案上傳處理類後,前臺傳回的 file 檔案在後臺獲取會是空,加上這句話就好了,推測不加這句話,spring 依然
 * 會先走預設的檔案處理流程並修改request物件,再執行我們定義的檔案處理類。(這只是個人推測)
 * exclude表示自動配置時不包括Multipart配置
 */
@EnableAutoConfiguration(exclude = {MultipartAutoConfiguration.class})

@Configuration
@ComponentScan(basePackages = {"com.example"})
@ServletComponentScan(basePackages = {"com.example"})
public class UploadProgressApplication {

/*
 * 將 multipartResolver 指向我們剛剛建立好的繼承 CommonsMultipartResolver 類的自定義檔案上傳處理類
 */
@Bean(name = "multipartResolver")
public MultipartResolver multipartResolver() {
	CustomMultipartResolver customMultipartResolver = new CustomMultipartResolver();
	return customMultipartResolver;
}

public static void main(String[] args) {
	SpringApplication.run(UploadProgressApplication.class, args);
}
}

至此,準備工作完成,我們再建立一個測試用的 controller 和 html 頁面用於檔案上傳。
controller:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;

@Controller
@RequestMapping("/uploadProgress")
public class UploadController {
	
	@RequestMapping(value = "/showUpload", method = RequestMethod.GET)
	public ModelAndView showUpload() {
		return new ModelAndView("/UploadProgressDemo");
	}
	
	@RequestMapping("/upload")
	@ResponseBody
	public void uploadFile(MultipartFile file) {
		System.out.println(file.getOriginalFilename());
	}
	
}

HTML:

<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8"></meta>
	<title>測試</title>這裡寫程式碼片
</head>
<body>
	這是檔案上傳頁面
	<form action="/uploadProgress/upload" method="POST" enctype="multipart/form-data">
		<input type="file" name="file"/>
		<br/>
		<input type="submit" value="提交"/>
	</form>
</body>
</html>

經本人測試,確實可以獲取檔案上傳進度,前臺頁面修改進度條進度可以採用前臺頁面輪詢的方式訪問後臺,在相應action中通過儲存在session中的物件 status 來獲取最新的上傳進度並返回展示即可。