课程表

Spring Boot课程

工具箱
速查手册

Boot 图片上传

当前位置:免费教程 » Java相关 » Spring 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”,选择一张图片:

1.jpg

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

2.jpg

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


四、上传文件超限问题

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

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