Boot 图片上传
注意:本页面内容为W3xue原创,未经授权禁止转载,违者必究!
来源:W3xue 发布时间:2019/10/11 11:16:23
图片上传是web开发中经常遇到的问题,本篇我们就来讲讲Spring Boot 的图片上传。
一、在html页增加上传控件和预览功能
我们打开“greeting.html”,在底部控件之前,增加一个上传控件还有预览的区域:
<form th:action="@{/uppic}" id="form1" method="post" enctype="multipart/form-data"> <input type="file" name="headpic" id="headpic" onchange="preImg(this.id,'imgpre');"> <input type="submit" value="立即上传"> <p><img src="" width="120" style="display:none" id="imgpre" /></p> </form>
这里的图片插入控件(ID是“imgpre”)是用来预览将要上传的图片的,它还不能工作,因此,我们在上段代码之前加上JS的预览功能,这里不再赘述其机制,复制粘贴进去即可:
<script> function getFileUrl(sourceId) { var url; if (navigator.userAgent.indexOf("MSIE")>=1) { // IE url = document.getElementById(sourceId).value; } else if(navigator.userAgent.indexOf("Firefox")>0) { // Firefox url = window.URL.createObjectURL(document.getElementById(sourceId).files.item(0)); } else if(navigator.userAgent.indexOf("Chrome")>0) { // Chrome url = window.URL.createObjectURL(document.getElementById(sourceId).files.item(0)); } return url; } function preImg(sourceId, targetId) { var url = getFileUrl(sourceId); var imgPre = document.getElementById(targetId); imgPre.style.display = ''; imgPre.src = url; } </script>
静态模板页就准备好啦!
二、准备需要的工具类
接下来,我们准备好需要用到的工具类。我们在程序的“utils”文件夹,建立一个“StringUtils类”,代码如下:
package com.w3xue.jiaocheng.utils; import java.util.List; public class StringUtils { //判断字符串是否为空 public static boolean isEmpty(String s) { return s == null || "".equals(s.trim()); } //使用指定的连接符,把一系列对象连接起来 public static String join(List args, String separator) { if (args == null) { return ""; } StringBuffer sb = new StringBuffer(args.size()); int i = 0; for (Object s : args) { sb.append(s); i++; if (i < args.size()) { sb.append(separator); } } return sb.toString(); } }
这2个静态方法的作用相信大家都不难理解,注释已经能表明其作用。第一个方法在接下来的第一个例子中会用到,第二个方法会在第二个例子中用到。
再在程序的“utils”文件夹,建立一个“FileUtil类”,代码如下:
package com.w3xue.jiaocheng.utils; import javax.imageio.ImageIO; import javax.imageio.ImageReader; import javax.imageio.stream.ImageInputStream; import java.io.IOException; import java.util.Iterator; import java.io.File; import java.io.FileOutputStream; public class FileUtil { //文件上传方法 public static boolean uploadFile(byte[] fileByte, String filePath, String fileName) throws Exception { File targetFolder = new File(filePath); //为参数文件夹创建File对象 if (!targetFolder.exists()) { //如果参数文件夹不存在,就创建该文件夹 targetFolder.mkdirs(); targetFolder.setWritable(true, false); //如果服务器是Linux系统,设置相关权限 targetFolder.setReadable(true, false); targetFolder.setExecutable(false); //可执行权限为false,增强安全性 } FileOutputStream out = new FileOutputStream(filePath + fileName); //建立流对象 out.write(fileByte);//写入文件 out.flush(); //清空并关闭流对象 out.close(); File file=new File(filePath+ fileName); if (isImage(file)==false) //判断文件是否为图片 { file.delete(); return false; } else return true; } //取得文件后缀名 public static String getFileExt(String fileName) { if (StringUtils.isEmpty(fileName) || fileName.indexOf(".") == -1 || fileName.endsWith(".")) { return ""; } else { String ext = fileName.substring(fileName.lastIndexOf(".") + 1); if (ext.indexOf('?') > -1) { ext = ext.split("[?]")[0]; } return ext; } } //判断是否为文件 public static boolean isImage(File f) { try { ImageInputStream iis = ImageIO.createImageInputStream(f); Iterator<ImageReader> iter = ImageIO.getImageReaders(iis); if (!iter.hasNext()) { return false; } iis.close(); return true; } catch (IOException e) { return false; } } }
这里有3个静态方法,第一个方法的作用就是上传,给定3个参数(字节数组、存放位置、文件名),就能根据这几个参数保存文件,并调用第3个方法,判断文件是否为合法的图片,如果不是图片就立即删除并返回false;第二个方法的作用是根据完整文件名取得文件的后缀名,很简单的方法,不再赘述;第三个方法是根据传来的文件对象,进行图片流的判断,如果不是图片,则返回false,判断上传的文件是否为图片能增加安全性。
三、上传文件的路径和相应方法
我们在MainRestController类中,建立一个路径和相应的方法体,代码如下:
@RequestMapping(value = "/uppic", method = RequestMethod.POST) @ResponseBody public String uppic(@RequestParam("headpic") MultipartFile file,HttpServletRequest request) { String fileName = file.getOriginalFilename(); //图片原始名字 String suffix = FileUtil.getFileExt(fileName).toLowerCase(); //图片的后缀名 String newFileName = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date()) + "." + suffix; if (!"jpg".equals(suffix) && !"jpeg".equals(suffix) && !"gif".equals(suffix) && !"png".equals(suffix)) return "请上传PNG、JPG、GIF格式的图片!"; //文件存放路径 final String sFolder = "/uploads/"; //存放的路径地址,相对于程序根目录而言 String filePath = request.getServletContext().getRealPath(sFolder); try { //调用文件处理类FileUtil的上传文件方法,处理文件,并将文件写入指定位置 if (FileUtil.uploadFile(file.getBytes(), filePath, newFileName)) return filePath + fileName + "<br /><a href=\"/jiaocheng/"+sFolder+newFileName+"\" target=\"_blank\">"+sFolder+newFileName+"</a>"; // 返回图片的存放路径 else return "上传失败"; } catch (Exception e) { return e.toString(); } }
需要引用2个依赖包,一个是处理web上传文件的包,一个是格式化日期的包:
import org.springframework.web.multipart.MultipartFile; import java.text.SimpleDateFormat;
来看看方法体的2个参数,使用依赖包中的MultipartFile类的对象file来响应“greeting.html”模板页中的“headpic”控件,使用HttpServletRequest类的对象request来获得服务器的相对物理位置。在获得文件的原始名字之后,使用格式化的时间字符串替代其原始文件名,这样增强了可用性和安全性。并对文件后缀名进行检查,看是否为文件类型。当然这里的检查仍然不是安全的,因为黑客可能会把病毒木马修改为图片格式上传。然后取得相对于程序根目录而言的存放路径,再把传来的文件转化为byte数组,将其作为参数带入工具类FileUtil类里面的上传方法,并对图片的合法性进行验证,通过验证后保存文件。最后,返回2样信息:一是文件的绝对物理地址,二是相对的地址(可以用来保存到数据库里)。
我们打开“http://localhost:8080/jiaocheng/greeting”,选择一张图片:
点击上传后,会上传成功:
点开链接就会发现图片上传成功。
四、上传文件超限问题
我们在上传图片的时候,偶尔会碰到大图片,另外,为了挡住一部分黑客攻击,也要求我们对上传的图片大小做限制。那么,如何限制上传图片的大小呢?
Tomcat默认的大小是1M。因此,我们首先应该修改这个限制。我们在配置文件application.properties或application.yml上修改上传文件大小限制即可。下面是application.properties的语法:
spring.servlet.multipart.max-file-size=5MB spring.servlet.multipart.max-request-size=10MB
application.yml放在spring 大节下面:
servlet: multipart: max-file-size: 5MB max-request-size: 10MB
第一个是文件大小限制,第二个是总响应大小限制。
但我们设置后,如果有大于5M的文件上传,还是会抛出异常,且这个异常,在Controller层是catch不到的,因为如果文件超限,在进controller之前,异常已经被抛出了。如何解决这个问题呢?详细方法请点击本站的这篇文章:spring boot上传文件超出大小异常无法捕获怎么解决?
这里采用一种简单实用的办法,我们在程序的controller文件夹,新建一个MyExceptionHandler类,并实用@RestControllerAdvice作为类注解。这个注解可以用于定义@ExceptionHandler、@InitBinder、@ModelAttribute,并应用到所有@RequestMapping中。MyExceptionHandler类代码如下:
package com.w3xue.jiaocheng.controller; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.multipart.MaxUploadSizeExceededException; @RestControllerAdvice public class MyExceptionHandler { /* spring默认超出大小捕获异常MaxUploadSizeExceededException */ @ExceptionHandler(MaxUploadSizeExceededException.class) public String handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e) { return "文件大小超出, 请压缩或降低文件质量! "; } }
这时我们重新运行程序,再上传大文件时,就会调用这个方法,返回相应提示。
五、图片的压缩处理
我们上传图片之后,常常需要对图片进行压缩处理。这里我们推荐一款依赖包。首先,在pom.xml中加入如下依赖:
<!-- 图片压缩 --> <dependency> <groupId>net.coobird</groupId> <artifactId>thumbnailator</artifactId> <version>0.4.8</version> </dependency>
IntelliJ IDEA会帮我们自动下载依赖包。下载好之后,我们在MainRestController类,增加如下依赖:
import net.coobird.thumbnailator.Thumbnails;
这样就可以使用图片压缩的功能了。为了使用接下来我们添加的方法,我们再在MainRestController类顶部引入如下3个依赖:
import java.io.File; import java.io.IOException; import com.w3xue.jiaocheng.utils.StringUtils;
接下来我们添加一个新的上传文件路由和方法,这个方法会返回JSON字符串,方便不同的开发需求,其中主要功能部分,我已经加了注释:
@RequestMapping(value = "/uppic2", method = RequestMethod.POST) JSONObject uppic2(@RequestParam("headpic") MultipartFile file, HttpServletRequest request) { Map res = new HashMap(); final String sFolder="/uploads/"; //自定义存储图片文件夹,后面没有斜杠 String filePath = request.getServletContext().getRealPath(sFolder); List<String> errorMsg = new ArrayList<>(5); File targetFolder = new File(filePath); if (!targetFolder.exists()) { targetFolder.mkdirs(); //如果参数文件夹不存在,就创建该文件夹 //如果服务器是Linux系统,设置相关权限 targetFolder.setWritable(true, false); targetFolder.setReadable(true, false); targetFolder.setExecutable(false); } String suffix = FileUtil.getFileExt(file.getOriginalFilename()).toLowerCase(); if ("jpg".equals(suffix) || "jpeg".equals(suffix) || "gif".equals(suffix) || "png".equals(suffix)) { String timeStr=new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date()); String filename = timeStr + "." + suffix; //原图文件名 String filenamezip = timeStr + "-s" + "." + suffix; //缩略图文件名 File targetFile = new File(targetFolder, filename); String path = filePath + filename; //原图文件地址 String pathzip = filePath + filenamezip; //缩略图文件地址 res.put("path", path); try { file.transferTo(targetFile); //MultipartFile类的transferTo方法,将文件保存 // 如果是linux,设置可读权限 targetFile.setReadable(true, false); targetFile.setWritable(true, false); targetFile.setExecutable(false); if (FileUtil.isImage(targetFile)==false) { //判断文件是否为图片 System.gc(); //调用垃圾回收器,否则图片被JVM占用无法删除 targetFile.delete(); errorMsg.add("请上传图片格式的文件!"); res.put("success", false); } else { //如果是图片 if (file.getSize() > 2 * 1024 * 1024) { //如果文件大于2M Thumbnails.of(path).scale(0.5f).outputQuality(0.1f).toFile(pathzip); //压缩缩略图,设置压缩质量比例 } else { Thumbnails.of(path).width(300).toFile(pathzip); //压缩缩略图,设置宽度为300像素,等比例压缩 } res.put("success", true); } } catch (IOException e) { e.printStackTrace(); errorMsg.add(e.getMessage()); res.put("success", false); } } else { errorMsg.add("文件类型不正确"); res.put("success", false); } res.put("ErrorMessage", StringUtils.join(errorMsg, ",")); return (JSONObject) JSON.toJSON(res); }
我们再在“greeting.html”中,把表单的处理地址改为“uppic2”:
<form th:action="@{/uppic2}" id="form1" method="post" enctype="multipart/form-data">
重新运行程序并尝试上传图片,如果上传成功,windows下测试就会返回类似如下的JSON:
{"ErrorMessage":"","path":"C:\\Users\\hp\\AppData\\Local\\Temp\\tomcat-docbase.2013167695990976614.8080\\uploads\\20191018165522615.jpeg","success":true}
Linux系列就会返回类似下面的JSON:
{"ErrorMessage":"","path":"/usr/local/java/tomcat/webapps/jiaocheng/uploads/20191018171340450.jpg","success":true}
如果上传失败,则返回错误信息:
{"ErrorMessage":"文件类型不正确","success":false}
这里,为了保持数据格式一致性,也可以对上一节中的图片大小超限的方法进行修改,让其返回相同的格式,这里就不再赘述了。
注意:本页面内容为W3xue原创,未经授权禁止转载,违者必究!
来源:W3xue 发布时间:2019/10/11 11:16:23