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