多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
[TOC] # 配置 单位支持MB和KB ~~~ # 支持的最大的文件 spring.servlet.multipart.max-file-size=100MB # 文件请求最大限制 spring.servlet.multipart.max-request-size=100MB ~~~ MultipartFile 是 Spring 上传⽂文件的封装类,包含了文件的二进制流和文件属性等信息 除过以上配置,常⽤用的配置信息如下: * spring.servlet.multipart.enabled=true,是否⽀支持 multipart 上传⽂文件 * spring.servlet.multipart.file-size-threshold=0,支持文件写⼊入磁盘 * spring.servlet.multipart.location=,上传⽂文件的临时⽬目录 * spring.servlet.multipart.max-file-size=10Mb,最大支持⽂件大小 * spring.servlet.multipart.max-request-sizee=10Mb,最⼤大⽀支持请求⼤小 * spring.servlet.multipart.resolve-lazily=false,是否⽀支持 multipart 上传⽂文件时懒加载 # 启动类 ~~~ public class UserApplication { public static void main(String[] args) { ApplicationContext context = SpringApplication.run(UserApplication.class, args); } @Bean public TomcatServletWebServerFactory tomcatEmbedded() { TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory(); tomcat.addConnectorCustomizers( (TomcatConnectorCustomizer) connector -> { if ( (connector.getProtocolHandler() instanceof AbstractHttp11JsseProtocol<?>)) { //-1 means unlimited ((AbstractHttp11JsseProtocol<?>)connector.getProtocolHandler()).setMaxSwallowSize(-1); } } ); return tomcat; } } ~~~ TomcatServletWebServerFactory() ⽅方法主要是为了了解决上传⽂文件⼤大于 10M 出现连接重置的问题,此异常内容 GlobalException 也捕获不不到。 ![](https://img.kancloud.cn/63/b4/63b48b1525e53c10a5d2e98f02363d87_688x662.png) # 前端页面 **上传页面** ~~~ <!DOCTYPE html> <html lang="zh-CN" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>spring boot file upload example</h1> <form method="post" action="/upload" enctype="multipart/form-data"> <input type="file" name="file" /> <br /> <input type="submit" value="submit" /> </form> </body> </html> ~~~ **上传状态页面** ~~~ <!DOCTYPE html> <html lang="zh-CN" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>spring boot - upload status</h1> <div th:if="${message}"> <h2 th:text="${message}" /> </div> </body> </html> ~~~ # 上传控制类 ~~~ private static final String UPLOADED_FOLDER = "/Users/jdxia/Desktop/MyFile/"; ~~~ ~~~ @GetMapping("/upload") public String uploadTest(Model model) { return "upload.html"; } @PostMapping("/upload") public String singleFileUpload(@RequestParam("file")MultipartFile file, RedirectAttributes redirectAttributes) { if (file.isEmpty()) { redirectAttributes.addFlashAttribute("message", "Please select a file"); return "uploadStatus.html"; } try { //Get the file and save it somewhere byte[] bytes = file.getBytes(); //UPLOADED_FOLDER 文件本地存储地址 Path path = Paths.get(UPLOADED_FOLDER + file.getOriginalFilename()); Files.write(path, bytes); redirectAttributes.addFlashAttribute("message", "You successfully uploaded " + file.getOriginalFilename()); } catch (IOException e) { e.printStackTrace(); } return "uploadStatus.html"; } ~~~ 通过MultipartFile读取文件信息,如果文件为空跳转到结果页并给出提示. 如果不为空读取文件流并写入到指定目录,最后将结果展示到页面. ~~~ @ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(MultipartException.class) public String handleError1(MultipartException e, RedirectAttributes redirectAttributes) { redirectAttributes.addFlashAttribute("message", e.getCause().getMessage()); } } ~~~ 设置一个 @ControllerAdvice 用来监控 Multipart 上传的文件大小是否受限,当出现此异常时在前端页面给出提示 # 上传多个文件 ## 前端 ~~~ <!DOCTYPE html> <html lang="zh-CN" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>spring boot files upload example</h1> <form method="post" action="/uploadMore" enctype="multipart/form-data"> 文件1: <input type="file" name="file" /> <br /> 文件2: <input type="file" name="file" /> <br /> 文件3: <input type="file" name="file" /> <br /> <input type="submit" value="submit" /> </form> </body> </html> ~~~ ## 后端 ~~~ private static final String UPLOADED_FOLDER = "/Users/jdxia/Desktop/MyFile/"; ~~~ ~~~ @GetMapping("/more") public String uploadMore() { return "uploadMore"; } @PostMapping("/uploadMore") public String moreFileUpload(@RequestParam("file")MultipartFile[] files, RedirectAttributes redirectAttributes) { if(files.length == 0) { redirectAttributes.addFlashAttribute("message", "Please select a file to upload"); return "uploadStatus"; } for (MultipartFile file:files) { try { byte[] bytes = file.getBytes(); Path path = Paths.get(UPLOADED_FOLDER + file.getOriginalFilename()); Files.write(path, bytes); } catch (IOException e) { e.printStackTrace(); } } redirectAttributes.addFlashAttribute("message", "You successfully uploaded all"); return "uploadStatus"; } ~~~ 同时是先判断数组是否是空. 然后循环遍历将内容写入到指定目录下 # 文件后缀 ~~~ // 文件名 String fileName = file.getOriginalFilename(); System.out.println("文件名: " + fileName); // 文件后缀 String suffixName = fileName.substring(fileName.lastIndexOf(".")); System.out.println("文件后缀名: " + suffixName); ~~~ ~~~ //文件类型 public static String IMG_TYPE_PNG = "PNG"; public static String IMG_TYPE_JPG = "JPG"; public static String IMG_TYPE_JPEG = "JPEG"; public static String IMG_TYPE_DMG = "BMP"; public static String IMG_TYPE_GIF = "GIF"; public static String IMG_TYPE_SVG = "SVG"; ~~~ ~~~ @PostMapping("/uploadFlatMap") public Result<String> uploadFlatMap( @RequestPart(value = "file",required = true) MultipartFile file){ if(StringUtils.isEmpty(file.getName())){ return sendFailedMsg(EnumUtil.BUS_ENUM.FILE_NOTFONUD_ERROR.KEY,file.getOriginalFilename()); } log.info("正在做上传操作,上传文件为:{}",file.getOriginalFilename()); String suffix = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".") + 1); if(!(Consts.IMG_TYPE_DMG.equals(suffix.toUpperCase()) || Consts.IMG_TYPE_GIF.equals(suffix.toUpperCase()) || Consts.IMG_TYPE_JPEG.equals(suffix.toUpperCase()) || Consts.IMG_TYPE_JPG.equals(suffix.toUpperCase()) || Consts.IMG_TYPE_PNG.equals(suffix.toUpperCase()) || Consts.IMG_TYPE_SVG.equals(suffix.toUpperCase()))) { return sendFailedMsg(EnumUtil.BUS_ENUM.FILE_TYPE_ERROR.KEY,file.getOriginalFilename()); } ~~~ # 文件头判断类型 | 文件类型 | 文件头 | | --- | --- | | JPEG (jpg), | 文件头:FFD8FF | | PNG (png), | 文件头:89504E47 | | GIF (gif), | 文件头:47494638 | | TIFF (tif), | 文件头:49492A00  | | Windows Bitmap (bmp), | 文件头:424D | | CAD (dwg), | 文件头:41433130 | | Adobe Photoshop (psd), | 文件头:38425053 | | Rich Text Format (rtf), | 文件头:7B5C727466 | | XML (xml), | 文件头:3C3F786D6C | HTML (html), | 文件头:68746D6C3E | Email \[thorough only\] (eml), | 文件头:44656C69766572792D646174653A | | Outlook Express (dbx), | 文件头:CFAD12FEC5FD746F  | | Outlook (pst), | 文件头:2142444E  | | MS Word/Excel (xls.or.doc), | 文件头:D0CF11E0 | | MS Access (mdb), | 文件头:5374616E64617264204A | | WordPerfect (wpd), | 文件头:FF575043 | | Postscript. (eps.or.ps), | 文件头:252150532D41646F6265 | | Adobe Acrobat (pdf), | 文件头:255044462D312E | | Quicken (qdf), | 文件头:AC9EBD8F  | | Windows Password (pwl), | 文件头:E3828596  | | ZIP Archive (zip), | 文件头:504B0304  | | RAR Archive (rar), | 文件头:52617221  | | Wave (wav), | 文件头:57415645  | | AVI (avi), | 文件头:41564920  | | Real Audio (ram), | 文件头:2E7261FD  | | Real Media (rm), | 文件头:2E524D46  | | MPEG (mpg), | 文件头:000001BA  | | MPEG (mpg), | 文件头:000001B3 | | Quicktime (mov), | 文件头:6D6F6F76  | | Windows Media (asf), | 文件头:3026B2758E66CF11  | | MIDI (mid), | 文件头:4D546864 | ## 配置文件 checkFileHeader.properties 在src/main/resources中增加配置文件checkFileHeader.properties,文件内容: ~~~ JPEG=FFD8FF PNG=89504E47 GIF=47494638 TXT=75736167 PDF=255044462D312E DOC=D0CF11E0 XML=3C3F786D6C DOCX=504B0304 APK=504B030414000808 IPA=504B03040A000000 ~~~ ##读取properties文件类 读取checkFileHeader.properties文件内容,用于拦截器判断 ~~~ /** * 读取文件流头信息 */ public class FileHeaderHelper { private static FileHeaderHelper me ; private static List<String> headerList ; private FileHeaderHelper(){} public static FileHeaderHelper getInstance(){ if(me == null){ me = new FileHeaderHelper() ; } return me ; } public List<String> getHeaderList(){ if(headerList == null){ headerList = new ArrayList<String>() ; PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); String classpathResource = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + "/fileheader.properties"; Properties p = new Properties(); try { Resource[] res = resolver.getResources(classpathResource) ; for (Resource re : res) { p.load(re.getInputStream()); break ; } } catch (IOException e) { e.printStackTrace(); } for (Map.Entry<Object, Object> item : p.entrySet()) { headerList.add(item.getValue().toString()) ; } } return headerList ; } } ~~~ ## 编写拦截器 拦截去中,获取文件流,读取文件流前8个字节,根据需要可以读取更多字节判读,8个字节转成16进制为16个字符串,我这里最长的APK/IPA文件也就16个字节,所以读取8个字节,读取字节后判断是否checkFileHeader.properties文件中字符串 ~~~ public class FileHeaderCheckInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 判断是否为文件上传请求 if (request != null && request instanceof MultipartHttpServletRequest) { MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request; Map<String, MultipartFile> files = multipartRequest.getFileMap(); Iterator<String> iterator = files.keySet().iterator(); while (iterator.hasNext()) { String formKey = (String) iterator.next(); MultipartFile multipartFile = multipartRequest.getFile(formKey); //String filename = multipartFile.getOriginalFilename(); byte[] file = multipartFile.getBytes() ; ////获取字节流前8字节,差不多够了,不行再加 int HEADER_LENGTH = 8 ; if(file.length>HEADER_LENGTH){ //转成16进制 StringBuilder sb = new StringBuilder(); for(int i=0;i<HEADER_LENGTH;i++){ int v = file[i] & 0xFF; String hv = Integer.toHexString(v); if (hv.length() < 2) { sb.append(0); } sb.append(hv); } boolean isFound = false ; String fileHead = sb.toString().toUpperCase() ; List<String> headerList = FileHeaderHelper.getInstance().getHeaderList() ; for(String header : headerList){ if(fileHead.startsWith(header)){ isFound = true ; break ; } } if(!isFound){ // throw new BaseRunException("上传文件有异常,已被系统禁止!") ; System.out.println("----------上传文件有异常,已被系统禁止!头部信息:"+fileHead); response.setCharacterEncoding("UTF-8"); response.setContentType("application/json;charset=utf-8"); PrintWriter printWriter = response.getWriter(); printWriter.write("上传文件有异常,已被系统禁止!"); return false; } } } } return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { // TODO Auto-generated method stub } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // TODO Auto-generated method stub } } ~~~ ## 配置拦截文件 拦截器写完了,配置下让它生效,在Configuration中配置拦截器,拦截文件流进行判断 ~~~ @Configuration public class MyfWebAppConfiguration extends WebMvcConfigurerAdapter { //拦截器,拦截文件流 public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new FileHeaderCheckInterceptor()) .addPathPatterns("/**"); } // //注册过滤 // @Bean // public FilterRegistrationBean myFilterRegistration() { // // FilterRegistrationBean registration = new FilterRegistrationBean(); // registration.setFilter(new LoginFilter()); // registration.addUrlPatterns("/serviceInvoke"); // //registration.addInitParameter("paramName", "paramValue"); // registration.setName("loginFilter"); // registration.setOrder(1); // return registration; // } // // // //注册servlet // @Bean // public ServletRegistrationBean myServletRegistration() { // ServletRegistrationBean registration = new ServletRegistrationBean(new DownloadServlet()); // registration.addUrlMappings("/download"); // return registration; // } } ~~~