课程表

Spring Boot课程

工具箱
速查手册

Boot 图片上传

当前位置:免费教程 » Java相关 » Spring Boot
注意:本页面内容为W3xue原创,未经授权禁止转载,违者必究!
来源:W3xue  发布时间:2019/10/11 11:16:23

图片上传是web开发中经常遇到的问题,本篇我们就来讲讲Spring Boot 的图片上传。


一、在html页增加上传控件和预览功能

我们打开“greeting.html”,在底部控件之前,增加一个上传控件还有预览的区域:

  1. <form th:action="@{/uppic}" id="form1" method="post" enctype="multipart/form-data">
  2.     <input type="file" name="headpic" id="headpic" onchange="preImg(this.id,'imgpre');">
  3.     <input type="submit" value="立即上传">
  4.     <p><img src="" width="120" style="display:none" id="imgpre" /></p>
  5. </form>

这里的图片插入控件(ID是“imgpre)是用来预览将要上传的图片的,它还不能工作,因此,我们在上段代码之前加上JS的预览功能,这里不再赘述其机制,复制粘贴进去即可:

  1. <script>
  2.     function getFileUrl(sourceId) {
  3.         var url;
  4.         if (navigator.userAgent.indexOf("MSIE")>=1) { // IE 
  5.             url = document.getElementById(sourceId).value;
  6.         } else if(navigator.userAgent.indexOf("Firefox")>0) { // Firefox 
  7.             url = window.URL.createObjectURL(document.getElementById(sourceId).files.item(0));
  8.         } else if(navigator.userAgent.indexOf("Chrome")>0) { // Chrome 
  9.             url = window.URL.createObjectURL(document.getElementById(sourceId).files.item(0));
  10.         }
  11.         return url;
  12.     }
  13.     function preImg(sourceId, targetId) {
  14.         var url = getFileUrl(sourceId);
  15.         var imgPre = document.getElementById(targetId);
  16.         imgPre.style.display = '';
  17.         imgPre.src = url;
  18.     }
  19. </script>

静态模板页就准备好啦!


二、准备需要的工具类

接下来,我们准备好需要用到的工具类。我们在程序的“utils”文件夹,建立一个“StringUtils类”,代码如下:

  1. package com.w3xue.jiaocheng.utils;
  2.  
  3. import java.util.List;
  4.  
  5. public class StringUtils {
  6.  
  7.     //判断字符串是否为空
  8.     public static boolean isEmpty(String s) {
  9.         return s == null || "".equals(s.trim());
  10.     }
  11.  
  12.     //使用指定的连接符,把一系列对象连接起来
  13.     public static String join(List args, String separator) {
  14.         if (args == null) {
  15.             return "";
  16.         }
  17.         StringBuffer sb = new StringBuffer(args.size());
  18.         int i = 0;
  19.         for (Object s : args) {
  20.             sb.append(s);
  21.             i++;
  22.             if (< args.size()) {
  23.                 sb.append(separator);
  24.             }
  25.         }
  26.         return sb.toString();
  27.     }
  28.  
  29. }

这2个静态方法的作用相信大家都不难理解,注释已经能表明其作用。第一个方法在接下来的第一个例子中会用到,第二个方法会在第二个例子中用到。

再在程序的“utils”文件夹,建立一个“FileUtil类”,代码如下:

  1. package com.w3xue.jiaocheng.utils;
  2. import javax.imageio.ImageIO;
  3. import javax.imageio.ImageReader;
  4. import javax.imageio.stream.ImageInputStream;
  5. import java.io.IOException;
  6. import java.util.Iterator;
  7. import java.io.File;
  8. import java.io.FileOutputStream;
  9.  
  10.  
  11. public class FileUtil {
  12.  
  13.     //文件上传方法
  14.     public static boolean uploadFile(byte[] fileByte, String filePath, String fileName) throws Exception {
  15.         File targetFolder = new File(filePath); //为参数文件夹创建File对象
  16.         if (!targetFolder.exists()) { //如果参数文件夹不存在,就创建该文件夹
  17.             targetFolder.mkdirs();
  18.             targetFolder.setWritable(true, false); //如果服务器是Linux系统,设置相关权限
  19.             targetFolder.setReadable(true, false);
  20.             targetFolder.setExecutable(false); //可执行权限为false,增强安全性
  21.         }
  22.         FileOutputStream out = new FileOutputStream(filePath + fileName); //建立流对象
  23.         out.write(fileByte);//写入文件
  24.         out.flush(); //清空并关闭流对象
  25.         out.close();
  26.         File file=new File(filePath+ fileName);
  27.         if (isImage(file)==false) //判断文件是否为图片
  28.         {
  29.             file.delete();
  30.             return false;
  31.         }
  32.         else
  33.             return true;
  34.     }
  35.  
  36.     //取得文件后缀名
  37.     public static String getFileExt(String fileName) {
  38.         if (StringUtils.isEmpty(fileName) || fileName.indexOf(".") == -1 || fileName.endsWith(".")) {
  39.             return "";
  40.         } else {
  41.             String ext = fileName.substring(fileName.lastIndexOf(".") + 1);
  42.             if (ext.indexOf('?') > -1) {
  43.                 ext = ext.split("[?]")[0];
  44.             }
  45.             return ext;
  46.         }
  47.     }
  48.  
  49.  
  50.     //判断是否为文件
  51.     public static boolean isImage(File f) {
  52.         try {
  53.             ImageInputStream iis = ImageIO.createImageInputStream(f);
  54.             Iterator<ImageReader> iter = ImageIO.getImageReaders(iis);
  55.             if (!iter.hasNext()) {
  56.                 return false;
  57.             }
  58.             iis.close();
  59.             return true;
  60.         } catch (IOException e) {
  61.             return false;
  62.         }
  63.     }
  64.  
  65.  
  66. }

这里有3个静态方法,第一个方法的作用就是上传,给定3个参数(字节数组、存放位置、文件名),就能根据这几个参数保存文件,并调用第3个方法,判断文件是否为合法的图片,如果不是图片就立即删除并返回false;第二个方法的作用是根据完整文件名取得文件的后缀名,很简单的方法,不再赘述;第三个方法是根据传来的文件对象,进行图片流的判断,如果不是图片,则返回false,判断上传的文件是否为图片能增加安全性。


三、上传文件的路径和相应方法

我们在MainRestController类中,建立一个路径和相应的方法体,代码如下:

  1. @RequestMapping(value = "/uppic", method = RequestMethod.POST)
  2. @ResponseBody
  3. public String uppic(@RequestParam("headpic") MultipartFile file,HttpServletRequest request) {
  4.     String fileName = file.getOriginalFilename();  //图片原始名字
  5.     String suffix = FileUtil.getFileExt(fileName).toLowerCase(); //图片的后缀名
  6.     String newFileName = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date()) + "." + suffix;
  7.     if (!"jpg".equals(suffix) && !"jpeg".equals(suffix) && !"gif".equals(suffix) && !"png".equals(suffix))
  8.         return "请上传PNG、JPG、GIF格式的图片!";
  9.     //文件存放路径
  10.     final String sFolder = "/uploads/";  //存放的路径地址,相对于程序根目录而言
  11.     String filePath = request.getServletContext().getRealPath(sFolder);
  12.     try {
  13.         //调用文件处理类FileUtil的上传文件方法,处理文件,并将文件写入指定位置
  14.         if (FileUtil.uploadFile(file.getBytes(), filePath, newFileName))
  15.             return filePath + fileName + "<br /><a href=\"/jiaocheng/"+sFolder+newFileName+"\" target=\"_blank\">"+sFolder+newFileName+"</a>";   // 返回图片的存放路径
  16.         else
  17.             return "上传失败";
  18.     } catch (Exception e) {
  19.         return e.toString();
  20.     }
  21. }

需要引用2个依赖包,一个是处理web上传文件的包,一个是格式化日期的包:

  1. import org.springframework.web.multipart.MultipartFile;
  2. import java.text.SimpleDateFormat;

来看看方法体的2个参数,使用依赖包中的MultipartFile类的对象file来响应“greeting.html”模板页中的“headpic控件,使用HttpServletRequest类的对象request来获得服务器的相对物理位置。在获得文件的原始名字之后,使用格式化的时间字符串替代其原始文件名,这样增强了可用性和安全性。并对文件后缀名进行检查,看是否为文件类型。当然这里的检查仍然不是安全的,因为黑客可能会把病毒木马修改为图片格式上传。然后取得相对于程序根目录而言的存放路径,再把传来的文件转化为byte数组,将其作为参数带入工具类FileUtil类里面的上传方法,并对图片的合法性进行验证,通过验证后保存文件。最后,返回2样信息:一是文件的绝对物理地址,二是相对的地址(可以用来保存到数据库里)。

我们打开“http://localhost:8080/jiaocheng/greeting”,选择一张图片:

1.jpg

点击上传后,会上传成功:

2.jpg

点开链接就会发现图片上传成功。


四、上传文件超限问题

我们在上传图片的时候,偶尔会碰到大图片,另外,为了挡住一部分黑客攻击,也要求我们对上传的图片大小做限制。那么,如何限制上传图片的大小呢?

Tomcat默认的大小是1M。因此,我们首先应该修改这个限制。我们在配置文件application.properties或application.yml上修改上传文件大小限制即可。下面是application.properties的语法:

  1. spring.servlet.multipart.max-file-size=5MB
  2. spring.servlet.multipart.max-request-size=10MB

application.yml放在spring 大节下面:

  1. servlet:
  2.   multipart:
  3.     max-file-size: 5MB
  4.     max-request-size: 10MB

第一个是文件大小限制,第二个是总响应大小限制。

但我们设置后,如果有大于5M的文件上传,还是会抛出异常,且这个异常,在Controller层是catch不到的,因为如果文件超限,在进controller之前,异常已经被抛出了。如何解决这个问题呢?详细方法请点击本站的这篇文章:spring boot上传文件超出大小异常无法捕获怎么解决?

这里采用一种简单实用的办法,我们在程序的controller文件夹,新建一个MyExceptionHandler类,并实用@RestControllerAdvice作为类注解。这个注解可以用于定义@ExceptionHandler、@InitBinder、@ModelAttribute,并应用到所有@RequestMapping中。MyExceptionHandler类代码如下:

  1. package com.w3xue.jiaocheng.controller;
  2.  
  3. import org.springframework.web.bind.annotation.ExceptionHandler;
  4. import org.springframework.web.bind.annotation.RestControllerAdvice;
  5. import org.springframework.web.multipart.MaxUploadSizeExceededException;
  6.  
  7. @RestControllerAdvice
  8. public class MyExceptionHandler {
  9.     /* spring默认超出大小捕获异常MaxUploadSizeExceededException */
  10.     @ExceptionHandler(MaxUploadSizeExceededException.class)
  11.     public String handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e) {
  12.         return "文件大小超出, 请压缩或降低文件质量! ";
  13.     }
  14. }

这时我们重新运行程序,再上传大文件时,就会调用这个方法,返回相应提示。


五、图片的压缩处理

我们上传图片之后,常常需要对图片进行压缩处理。这里我们推荐一款依赖包。首先,在pom.xml中加入如下依赖:

  1. <!-- 图片压缩 -->
  2. <dependency>
  3.     <groupId>net.coobird</groupId>
  4.     <artifactId>thumbnailator</artifactId>
  5.     <version>0.4.8</version>
  6. </dependency>

IntelliJ IDEA会帮我们自动下载依赖包。下载好之后,我们在MainRestController类,增加如下依赖:

  1. import net.coobird.thumbnailator.Thumbnails;

这样就可以使用图片压缩的功能了。为了使用接下来我们添加的方法,我们再在MainRestController类顶部引入如下3个依赖:

  1. import java.io.File;
  2. import java.io.IOException;
  3. import com.w3xue.jiaocheng.utils.StringUtils;

接下来我们添加一个新的上传文件路由和方法,这个方法会返回JSON字符串,方便不同的开发需求,其中主要功能部分,我已经加了注释:

  1. @RequestMapping(value = "/uppic2", method = RequestMethod.POST)
  2. JSONObject uppic2(@RequestParam("headpic") MultipartFile file, HttpServletRequest request) {
  3.     Map res = new HashMap();
  4.     final String sFolder="/uploads/";  //自定义存储图片文件夹,后面没有斜杠
  5.     String filePath = request.getServletContext().getRealPath(sFolder);
  6.     List<String> errorMsg = new ArrayList<>(5);
  7.     File targetFolder = new File(filePath);
  8.     if (!targetFolder.exists()) {
  9.         targetFolder.mkdirs(); //如果参数文件夹不存在,就创建该文件夹
  10.         //如果服务器是Linux系统,设置相关权限
  11.         targetFolder.setWritable(true, false);
  12.         targetFolder.setReadable(true, false);
  13.         targetFolder.setExecutable(false);
  14.     }
  15.     String suffix = FileUtil.getFileExt(file.getOriginalFilename()).toLowerCase();
  16.     if ("jpg".equals(suffix) || "jpeg".equals(suffix) || "gif".equals(suffix) || "png".equals(suffix)) {
  17.         String timeStr=new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date());
  18.         String filename = timeStr + "." + suffix; //原图文件名
  19.         String filenamezip = timeStr + "-s" + "." + suffix; //缩略图文件名
  20.         File targetFile = new File(targetFolder, filename);
  21.         String path = filePath + filename; //原图文件地址
  22.         String pathzip = filePath + filenamezip; //缩略图文件地址
  23.         res.put("path", path);
  24.         try {
  25.             file.transferTo(targetFile); //MultipartFile类的transferTo方法,将文件保存
  26.             // 如果是linux,设置可读权限
  27.             targetFile.setReadable(true, false);
  28.             targetFile.setWritable(true, false);
  29.             targetFile.setExecutable(false);
  30.             if (FileUtil.isImage(targetFile)==false) { //判断文件是否为图片
  31.                 System.gc(); //调用垃圾回收器,否则图片被JVM占用无法删除
  32.                 targetFile.delete();
  33.                 errorMsg.add("请上传图片格式的文件!");
  34.                 res.put("success", false);
  35.             }
  36.             else {  //如果是图片
  37.                 if (file.getSize() > 2 * 1024 * 1024) { //如果文件大于2M
  38.                     Thumbnails.of(path).scale(0.5f).outputQuality(0.1f).toFile(pathzip); //压缩缩略图,设置压缩质量比例
  39.                 } else {
  40.                     Thumbnails.of(path).width(300).toFile(pathzip); //压缩缩略图,设置宽度为300像素,等比例压缩
  41.                 }
  42.                 res.put("success", true);
  43.             }
  44.         } catch (IOException e) {
  45.             e.printStackTrace();
  46.             errorMsg.add(e.getMessage());
  47.             res.put("success", false);
  48.         }
  49.     }
  50.     else {
  51.         errorMsg.add("文件类型不正确");
  52.         res.put("success", false);
  53.     }
  54.     res.put("ErrorMessage", StringUtils.join(errorMsg, ","));
  55.     return (JSONObject) JSON.toJSON(res);
  56. }

我们再在“greeting.html”中,把表单的处理地址改为“uppic2”:

  1. <form th:action="@{/uppic2}" id="form1" method="post" enctype="multipart/form-data">

重新运行程序并尝试上传图片,如果上传成功,windows下测试就会返回类似如下的JSON

  1. {"ErrorMessage":"","path":"C:\\Users\\hp\\AppData\\Local\\Temp\\tomcat-docbase.2013167695990976614.8080\\uploads\\20191018165522615.jpeg","success":true}

Linux系列就会返回类似下面的JSON:

  1. {"ErrorMessage":"","path":"/usr/local/java/tomcat/webapps/jiaocheng/uploads/20191018171340450.jpg","success":true}

如果上传失败,则返回错误信息:

  1. {"ErrorMessage":"文件类型不正确","success":false}

这里,为了保持数据格式一致性,也可以对上一节中的图片大小超限的方法进行修改,让其返回相同的格式,这里就不再赘述了。

注意:本页面内容为W3xue原创,未经授权禁止转载,违者必究!
来源:W3xue  发布时间:2019/10/11 11:16:23
 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号