MultipartResolver
/**
* 依照<a href="https://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>,對於二進制文件上傳的策略接口。
* 在一個應用程序上下文和單機模式,都是可使用的。
*
* 對於Spring 3.1來說,有兩個具體的實現類:
* {@link org.springframework.web.multipart.commons.CommonsMultipartResolver}
* for Apache Commons FileUpload
* {@link org.springframework.web.multipart.support.StandardServletMultipartResolver}
* for the Servlet 3.0+ Part API
*
* 對於Spring的DispatcherServlet沒有默認的解析器實現,因爲一個應用程序可能自己選擇去分析它的二進制請求。
* 爲了定義一個實現類,創建一個id爲“multipartResolver”在DispatcherServlet中的application context裏。
* 這樣的一個解析器會應用到DispatchServlet的所有請求中
*
* 如果一個DispatcherServlet發現了一個二進制請求,會通過配置的MultipartResolver進行解析並且傳遞一個包裝的HttpServletRequest。
* 控制器這時候可以將給定的請求轉換成MultipartHttpServletRequest接口,這個接口允許訪問任何MultipartFile。
* 注意:這個轉換僅僅是在一個二進制請求中得到支持。
*
* <pre class="code">
* public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) {
* MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
* MultipartFile multipartFile = multipartRequest.getFile("image");
* ...
* }</pre>
*
* 沒有直接訪問,命令或者控制器可以註冊一個ByteArrayMultipartFileEditor或者StringMultipartFileEditor帶有它們的數據綁定器
* 自動將二進制內容轉換形成bean屬性。
*
* 作爲使用一個帶有DispatcherServlet的MultipartResolver的一個選項,MultipartFilter可以被註冊到web.xml中。
* 它委派給root application context中的MultipartResolver類型的bean。這個主要是針對的是沒有Spring MVC框架的應用程序。
*
* 注意:從來也不需要從代碼層面去訪問MultipartResolver。它簡單地在幕後工作,使MultipartHttpServletRequest對於控制器可用。
*/
public interface MultipartResolver {
/**
* 確定給定的請求是否包含二進制內容。
* 將會檢查內容類型“multipart/form-data”,但是實際接收的請求可能依賴於解析器的實現類。
*/
boolean isMultipart(HttpServletRequest request);
/**
* 將給定的http請求解析成二進制文件和參數,然後包裝請求成一個MultipartHttpServletRequest對象。
* 這個對象,提供對文件描述符的訪問和使內含的參數可以訪問,通過標準的ServletRequest方法。
*/
MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException;
/**
* 清理二進制處理的任何資源,比如上傳文件的一個存儲。
*/
void cleanupMultipart(MultipartHttpServletRequest request);
}
StandardServletMultipartResolver
/**
* MultipartResolver的標準實現接口,基於Servlet 3.0的 {@link javax.servlet.http.Part} API。
* 在一個Spring DispatcherServlet上下文中,添加"multipartResolver"bean,沒有任何額外的代碼級別的配置。
*
* 注意:爲了使用基於二進制解析的Servlet 3.0,你需要標記在web.xml中“multipart-config”部分的受到影響的servlet,
* 或者使用以編程式的servlet註冊的MultipartConfigElement或者在servlet class中的使用MultipartConfig註解。
* 例如最大的文件大小或者存儲位置的配置設置使用的是servlet註冊的級別;Servlet3.0不允許以MultipartResolver中設置它們。
*
* public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
* // ...
* @Override
* protected void customizeRegistration(ServletRegistration.Dynamic registration) {
* // Optionally also set maxFileSize, maxRequestSize, fileSizeThreshold
* registration.setMultipartConfig(new MultipartConfigElement("/tmp"));
* }
* }
*/
public class StandardServletMultipartResolver implements MultipartResolver {
private boolean resolveLazily = false;
/**
* 設置是否在訪問文件或者參數的時候,延遲解析二進制請求。
* 默認是false。
*/
public void setResolveLazily(boolean resolveLazily) {
this.resolveLazily = resolveLazily;
}
@Override
public boolean isMultipart(HttpServletRequest request) {
// 內容類型以"multipart/"開頭
return StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/");
}
@Override
public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
return new StandardMultipartHttpServletRequest(request, this.resolveLazily);
}
/**
* 實際刪除的是Part
*/
@Override
public void cleanupMultipart(MultipartHttpServletRequest request) {
if (!(request instanceof AbstractMultipartHttpServletRequest) ||
// MultiValueMap<String, MultipartFile> != null
((AbstractMultipartHttpServletRequest) request).isResolved()) {
// To be on the safe side: explicitly delete the parts,
// but only actual file parts (for Resin compatibility)
try {
for (Part part : request.getParts()) {
if (request.getFile(part.getName()) != null) {
part.delete();
}
}
}
catch (Throwable ex) {
LogFactory.getLog(getClass()).warn("Failed to perform cleanup of multipart items", ex);
}
}
}
}
CommonsMultipartResolver
暫時放置,以後會更新的。
demo
/**
* @Description: 批量上傳文件
* @Author: apple
* @CreateDate: 2019/7/9
* @UpdateUser: apple
* @UpdateDate: 2019/7/9
*/
@Slf4j
@Controller
@RequestMapping("/file")
public class UploadFileController {
/** 文件上傳路徑 **/
@Value("${spring.servlet.multipart.location}")
private String uploadLocation;
/** 用於記錄當前上傳的次數 **/
private AtomicLong uploadCount = new AtomicLong();
@GetMapping(value = {"/upload/page", ""})
public String uploadPage() {
return "/upload.html";
}
/**
* 上傳文件
* (對於HttpServletRequest類型的解析)
*
* @param request 上傳請求
* @return 處理結果
*/
@PostMapping("/upload/request")
@ResponseBody
public Map<String, Object> uploadRequest(HttpServletRequest request) {
MultipartHttpServletRequest multiRequest = null;
if (request instanceof MultipartHttpServletRequest) {
multiRequest = (MultipartHttpServletRequest) request;
} else {
dealResultMap(false, generateFailResolve());
}
Assert.notNull(multiRequest, "Request can not be null.");
// (注:"file"是前端定義的input標籤的name,批量上傳的name要保持一致)
List<MultipartFile> multipartFiles = multiRequest.getFiles("file");
Assert.notNull(multipartFiles, "MultipartFile list is null. Fail to resolve files.");
Map<String, Object> finalMap = new HashMap<>(multipartFiles.size());
Map<String, Object> resultMap;
if (multipartFiles.size() > 0) {
for (MultipartFile multipartFile : multipartFiles) {
Assert.notNull(multipartFile, "MultipartFile can not be null");
resultMap = transferTo(multipartFile);
if (resultMap != null) {
finalMap.put("No." + uploadCount.incrementAndGet() + " uploads", resultMap);
}
}
}
return finalMap;
}
/**
* 上傳文件
* (對於MultipartFile類型的解析)
*
* @param multipartFiles MultipartFile類型的文件組
* @return 處理結果
*/
@PostMapping("/upload/multipart")
@ResponseBody
public Map<String, Object> uploadMultipartFile(@RequestParam("file") MultipartFile[] multipartFiles) {
Map<String, Object> finalMap = new HashMap<>(multipartFiles.length);
Map<String, Object> resultMap;
for (MultipartFile multipartFile : multipartFiles) {
try {
resultMap = transferTo(multipartFile);
if (resultMap != null) {
finalMap.put("No." + uploadCount.incrementAndGet() + " uploads", resultMap);
}
} catch (Exception e) {
if (log.isErrorEnabled()) {
log.error(multipartFile.getOriginalFilename() + " fail to upload");
}
}
}
return finalMap;
}
/**
* 上傳文件
*
* @param multipartFile MultipartFile類型的文件
* @return 處理後的信息map
*/
private Map<String, Object> transferTo(MultipartFile multipartFile) {
String fileName = multipartFile.getOriginalFilename();
Assert.notNull(fileName, "File name must not be null");
fileName = generateWithTimeMillis(replaceStr(fileName));
File file = new File(fileName);
try {
// 上傳文件(核心語句)
multipartFile.transferTo(file);
} catch (IOException e) {
// 如果上傳失敗
if (log.isErrorEnabled()) {
log.error(generateUploadMsg(false, fileName), e.getMessage());
}
return dealResultMap(false, generateUploadMsg(false, fileName));
}
// 上傳成功
if (log.isInfoEnabled()) {
log.info(generateUploadMsg(true, fileName));
}
return dealResultMap(true, generateUploadMsg(true, fileName));
}
/**
* 對文件名加時間數字處理
*
* @param fileName 文件名
* @return 返回生成帶有時間數字的文件名
*/
private String generateWithTimeMillis(String fileName) {
String suffix = "";
String fileRealName;
final String symbol = ".";
int index;
if ((index = fileName.lastIndexOf(symbol)) != -1) {
suffix = fileName.substring(index);
fileRealName = fileName.substring(0, index);
} else {
fileRealName = fileName;
}
return fileRealName +
"-" +
System.currentTimeMillis() +
suffix;
}
/**
* 生成上傳處理後的信息
* (無論成功與否)
*
* @param success 是否上傳成功
* @param fileName 上傳的文件名
* @return 處理後的信息
*/
private String generateUploadMsg(boolean success, String fileName) {
StringBuilder builder = new StringBuilder();
builder.append("File-[");
builder.append(fileName);
if (success) {
builder.append("] succeed to upload, to ").append(uploadLocation);
} else {
builder.append("] fail to upload");
}
return builder.toString();
}
/**
* 生成解析失敗的信息
*
* @return 解析失敗的信息
*/
private String generateFailResolve() {
return "Fail to resolve files";
}
/**
* 定義返回的信息格式
*
* @param success 處理成功
* @param msg 處理後的信息
* @return 信息map
*/
private Map<String, Object> dealResultMap(boolean success, String msg) {
Map<String, Object> resultMap = new HashMap<>(2);
resultMap.put("isSuccessful", success);
resultMap.put("msg", msg);
return resultMap;
}
/**
* 對於文件路徑帶有"/"的,全部轉換成"\\"格式
* 然後截取最後一個"\\"後的文件名
*
* @param fileName 文件路徑名
* @return 符合格式的文件名
*/
private String replaceStr(String fileName) {
String replaceStr = "\\";
fileName = fileName.replaceAll("/", replaceStr);
if (fileName.lastIndexOf(replaceStr) != -1) {
fileName = fileName.substring(fileName.lastIndexOf("\\"));
}
return fileName;
}
}
Spring Boot的相關配置
application.properties
是否開啓二進制上傳
spring.servlet.multipart.enabled=true
文件上傳的路徑
spring.servlet.multipart.location=/usr/local/file
最大的文件大小
spring.servlet.multipart.maxFileSize=100MB
最大的請求大小
spring.servlet.multipart.maxRequestSize=1GB
寫入磁盤的閾值
spring.servlet.multipart.fileSizeThreshold=1TB
延遲解析二進制請求,只有當訪問參數或者文件時纔去解析
spring.servlet.multipart.resolverLazily=false