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