Spring MVC - MultipartResolver组件

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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章