【SpringBoot新手篇】SpringBoot优雅文件上传方式

工作中上传文件是常见的场景之一,最典型的情况就是上传头像等,上传文档。自己上传文件中遇到的坑,自己已经填了,现在把它写下来,方便提醒自己,我使用的是Spring Boot 上传文件

Pom

主要引入了webthymeleaf启动器

<!-- web start-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- web end-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

<!-- io常用工具类 -->
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.5</version>
</dependency>

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
</dependency>

yml

spring:
  servlet:
    multipart:
      max-file-size: 10MB  # 最大支持文件大小
      max-request-size: 10MB  # 最大支持请求大小
      #enabled: true  #默认支持文件上传.
      # file-size-threshold: #支持文件写入磁盘.
      #location: #上传文件的临时目录

controller

UploadController

UploadController首页的显示

@Controller
public class UploadController {

    @GetMapping("/")
    public String index() {
        return "upload";
    }

}

FileController

FileController文件上传,下载请求

文件上传

/**
     * @author: 三月三
     * @description:  单个文件上传
     * @param: [file]
     * @return: java.lang.String
     */
    @RequestMapping(value = "/upload")
    public String upload(HttpServletRequest request, @RequestParam("file") MultipartFile file, Model model) {
        // 测试MultipartFile接口的各个方法
        System.out.println("文件类型ContentType=" + file.getContentType());
        System.out.println("文件组件名称Name=" + file.getName());
        System.out.println("文件原名称OriginalFileName=" + file.getOriginalFilename());
        System.out.println("文件大小Size=" + file.getSize()/1024 + "KB");
        try {
            if (file.isEmpty()) {
                return "文件为空";
            }
            // 获取文件名
            String fileName = file.getOriginalFilename();
            log.info("上传的文件名为:" + fileName);
            // 获取文件的后缀名
            String suffixName = fileName.substring(fileName.lastIndexOf("."));
            log.info("文件的后缀名为:" + suffixName);

            // 获取文件相对类路径
            //String filePath = request.getServletContext().getRealPath("/");
            //文件绝对路径,项目中一般使用相对类路径,即使文件变更路径也会跟着变
            String filePath = request.getServletContext().getRealPath("G:\\dev_workspace\\springboot-learning-examples\\springboot-13-fileupload\\src\\main\\resources\\static");
            System.out.println("path = " + filePath);
            //构造一个路径
            String newImg = UUID.randomUUID()+suffixName;
            String path = filePath + newImg;
            log.info("构造路径"+path);

            File dest = new File(path);
            // 检测是否存在目录
            if (!dest.getParentFile().exists()) {
                dest.getParentFile().mkdirs();// 新建文件夹
            }
            file.transferTo(dest);// 文件写入
            model.addAttribute("msg","<font color=\"green\">上传成功</font>");
            model.addAttribute("img",newImg);
            return "upload";
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        model.addAttribute("msg","<font color=\"green\">上传失败</font>");
        return "upload";
    }

多文件上传

 /**
     * @author: 三月三
     * @description: 多个文件上传
     * @param: [request]
     * @return: java.lang.String
     */
    @PostMapping("/batch")
    public String handleFileUpload(Model model,HttpServletRequest request) {
        List<MultipartFile> files = ((MultipartHttpServletRequest) request).getFiles("file");
        MultipartFile file = null;
        BufferedOutputStream stream = null;
        for (int i = 0; i < files.size(); ++i) {
            file = files.get(i);
            //文件绝对路径,项目中一般使用相对类路径,即使文件变更路径也会跟着变
            String filePath = request.getServletContext().getRealPath("G:\\dev_workspace\\springboot-learning-examples\\springboot-13-fileupload\\src\\main\\resources\\static");
            if (!file.isEmpty()) {
                try {
                    byte[] bytes = file.getBytes();
                    stream = new BufferedOutputStream(new FileOutputStream(
                            new File(filePath + file.getOriginalFilename())));//设置文件路径及名字
                    stream.write(bytes);// 写入
                    stream.close();
                } catch (Exception e) {
                    stream = null;

                    model.addAttribute("msg", "第 " + i + " 个文件上传失败 ==> "
                            + e.getMessage());
                    return "upload";
                }
            } else {
                model.addAttribute("msg","第 " + i
                        + " 个文件上传失败因为文件为空");
                return "upload";
            }
        }
        model.addAttribute("msg","上传成功");
        return "upload";
    }

文件下载

/**
     * @author: 三月三
     * @description:  文件下载
     * @param: [model, request, response, fileName]
     * @return: java.lang.String
     */
    @PostMapping("/download")
    public String downloadFile(Model model,HttpServletRequest request, HttpServletResponse response,String fileName) {
        if (fileName != null) {
            //设置文件路径   真实环境是存放在数据库中的
           // String filePath = request.getServletContext().getRealPath("/");
            //文件绝对路径,项目中一般使用相对类路径,即使文件变更路径也会跟着变
            String filePath = request.getServletContext().getRealPath("G:\\dev_workspace\\springboot-learning-examples\\springboot-13-fileupload\\src\\main\\resources\\static");
            File file = new File(filePath);
            if (file.exists()) {
                response.setContentType("application/force-download");// 设置强制下载不打开
                response.addHeader("Content-Disposition", "attachment;fileName=" + fileName);// 设置文件名
                byte[] buffer = new byte[1024];
                FileInputStream fis = null;
                BufferedInputStream bis = null;
                try {
                    fis = new FileInputStream(file);

                    bis = new BufferedInputStream(fis);
                    // 创建输出对象
                    OutputStream os = response.getOutputStream();
                    int i = bis.read(buffer);
                    while (i != -1) {
                        os.write(buffer, 0, i);
                        i = bis.read(buffer);
                    }
                    model.addAttribute("msg","<font color=\"green\">下载成功</font>");
                    return "upload";
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (bis != null) {
                        try {
                            bis.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                    if (fis != null) {
                        try {
                            fis.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
        model.addAttribute("msg","<font color=\"red\">下载失败</font>");
        return "upload";
    }

页面

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>

<h1>Spring Boot file upload example</h1>

<p>单文件上传</p>
<form action="upload" method="POST" enctype="multipart/form-data">
    文件:<input type="file" name="file"/>
    <input type="submit"/>
</form>

<span  th:if="${msg!=null}" th:utext="${msg}"></span>


<div th:if="${img!=null}">
    <img th:src="${img}">
    <p>文件下载</p>
    <form action="download" method="post">
        <input type="hidden" th:value="${img}" name="fileName"/>
        <input type="submit" value="下载文件"/>
    </form>
</div>


<p>多文件上传</p>
<form method="POST" enctype="multipart/form-data" action="batch">
    <p>文件1:<input type="file" name="file"/></p>
    <p>文件2:<input type="file" name="file"/></p>
    <p><input type="submit" value="上传"/></p>
</form>
</body>
</html>

BUG

  1. 文件上传路径问题
  2. 文件下载的路径问题

解决:

  1. String filePath = request.getServletContext().getRealPath("/");获取的是tomcat目录下webapps下的目录及类路径(class文件存放的路径),因为我使用的是SpringBoot项目,而SringBoot项目内嵌了tomcat,路径为C:\Users\L15096000421\AppData\Local\Temp\tomcat-docbase.2191751665660359817.8080\的一个临时目录,重启项目,文件就丢失了
  2. 还有使用String filePath = request.getServletContext().getRealPath("/")做为下载的路径去下载文件,后台报错没有权限,使用绝对路径下载,及使用绝对路径上传
  3. 可以使用 private static final String parentPath = ClassUtils.getDefaultClassLoader().getResource("static/images").getPath();获取springboot项目static/images的目录

封装版

工具类

StringUtil

/**
 * 字符串工具类,抽取一些常用操作
 */
public class StringUtil {

    /**
     * 判断val是否不为空(null/"")
     * @param val
     * @return
     */
    public static boolean isNotEmpty(String val){
        return val != null && !"".equals(val);
    }

    /**
     * 将给定的驼峰命名值转换为下划线命名
     * @param val
     * @return
     */
    public static String toUnderScoreCase(String val){
        if(!isNotEmpty(val)){
            return val;
        }
        StringBuilder sb = new StringBuilder(val);
        for(int i = 0; i < sb.length(); i++){
            if(sb.charAt(i) >= 'A' && sb.charAt(i) <= 'Z'){
                //将大写字母 "A" 替换为 "_a"
                sb.replace(i, i + 1, "_" + (char)(sb.charAt(i) + 32));
            }
        }
        return sb.toString();
    }

}

MimeTypeUtils

/**
 * 媒体类型工具类
 *
 */
public class MimeTypeUtils
{
    public static final String IMAGE_PNG = "image/png";

    public static final String IMAGE_JPG = "image/jpg";

    public static final String IMAGE_JPEG = "image/jpeg";

    public static final String IMAGE_BMP = "image/bmp";

    public static final String IMAGE_GIF = "image/gif";

    public static final String[] IMAGE_EXTENSION = { "bmp", "gif", "jpg", "jpeg", "png" };

    public static final String[] DEFAULT_ALLOWED_EXTENSION = {"bmp", "gif", "jpg", "jpeg", "png"};

    public static String getExtension(String prefix)
    {
        switch (prefix)
        {
            case IMAGE_PNG:
                return "png";
            case IMAGE_JPG:
                return "jpg";
            case IMAGE_JPEG:
                return "jpeg";
            case IMAGE_BMP:
                return "bmp";
            case IMAGE_GIF:
                return "gif";
            default:
                return "";
        }
    }
}

FileUploadUtils

/**
 * 文件上传工具类
 */
public class FileUploadUtils
{
    /**
     * 默认大小 50M
     */
    public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024;

    /**
     * 默认的文件名最大长度 100
     */
    public static final int DEFAULT_FILE_NAME_LENGTH = 100;

    /**
     * 默认存储图片目录
     */
    private static final String parentPath = ClassUtils.getDefaultClassLoader().getResource("static/images").getPath();

    public static final String actorPath = "/actor";
    public static final String cinemaPath = "/cinema";
    public static final String moviePath = "/movie";
    public static final String userPath = "/user";


    /**
     * 默认上传的地址
     */
    private static String defaultBaseDir = userPath;

    public static void setDefaultBaseDir(String defaultBaseDir)
    {
        FileUploadUtils.defaultBaseDir = defaultBaseDir;
    }

    public static String getDefaultBaseDir()
    {
        return defaultBaseDir;
    }

    public static String getParentPath() {
        return parentPath;
    }

    /**
     * 以默认配置进行文件上传
     *
     * @param file 上传的文件
     * @return 文件名称
     * @throws Exception
     */
    public static final String upload(MultipartFile file) throws IOException
    {
        try
        {
            return upload(getParentPath() + getDefaultBaseDir(), file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
        }
        catch (Exception e)
        {
            throw new IOException(e.getMessage(), e);
        }
    }

    /**
     * 文件上传
     *
     * @param baseDir 相对应用的基目录
     * @param file 上传的文件
     * @param allowedExtension 上传文件类型
     * @return 返回上传成功的文件名
     */
    public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension)
            throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException,
            InvalidExtensionException
    {
        int fileNamelength = file.getOriginalFilename().length();
        if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH)
        {
            throw new FileNameLengthLimitExceededException("文件名称长度不能超过" + FileUploadUtils.DEFAULT_FILE_NAME_LENGTH);
        }
        //文件大小校验
        assertAllowed(file, allowedExtension);
        //编码文件名
        String fileName = extractFilename(file);
        //
        File desc = getAbsoluteFile(baseDir, fileName);
        file.transferTo(desc);
        String pathFileName = getPathFileName(baseDir, fileName);
        return pathFileName;
    }

    /**
     * 编码文件名 如 : images/user/2020/12/4/***.png
     */
    public static final String extractFilename(MultipartFile file)
    {
        String fileName = file.getOriginalFilename();
        String extension = getExtension(file);
        fileName = DateFormatUtils.format(new Date(), "yyyy/MM/dd") + "/" + UUID.randomUUID().toString().replaceAll("-","") + "." + extension;
        return fileName;
    }

    private static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException
    {
        File desc = new File(uploadDir + File.separator + fileName);

        if (!desc.getParentFile().exists())
        {
            desc.getParentFile().mkdirs();
        }
        if (!desc.exists())
        {
            desc.createNewFile();
        }
        return desc;
    }

    private static final String getPathFileName(String uploadDir, String fileName) throws IOException
    {
        int dirLastIndex = parentPath.length() + 1;
        String currentDir = StringUtils.substring(uploadDir, dirLastIndex);
        String pathFileName = "/images/" + currentDir + "/" + fileName;
        return pathFileName;
    }

    /**
     * 文件大小校验
     *
     * @param file 上传的文件
     * @return
     * @throws FileSizeLimitExceededException 如果超出最大大小
     * @throws InvalidExtensionException
     */
    public static final void assertAllowed(MultipartFile file, String[] allowedExtension)
            throws FileSizeLimitExceededException, InvalidExtensionException
    {
        long size = file.getSize();
        if (DEFAULT_MAX_SIZE != -1 && size > DEFAULT_MAX_SIZE)
        {
            throw new FileSizeLimitExceededException("文件大小不能超过" + DEFAULT_MAX_SIZE / 1024 / 1024 + "MB");
        }

        String fileName = file.getOriginalFilename();
        String extension = getExtension(file);
        if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension))
        {
           // if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION)

                throw new InvalidExtensionException("图片格式不支持" + extension + "格式");

        }

    }

    /**
     * 判断MIME类型是否是允许的MIME类型
     *
     * @param extension
     * @param allowedExtension
     * @return
     */
    public static final boolean isAllowedExtension(String extension, String[] allowedExtension)
    {
        for (String str : allowedExtension)
        {
            if (str.equalsIgnoreCase(extension))
            {
                return true;
            }
        }
        return false;
    }

    /**
     * 获取文件名的后缀
     *
     * @param file 表单文件
     * @return 后缀名
     */
    public static final String getExtension(MultipartFile file)
    {
        String extension = FilenameUtils.getExtension(file.getOriginalFilename());
        if (!StringUtil.isNotEmpty(extension))
        {
            extension = MimeTypeUtils.getExtension(file.getContentType());
        }
        return extension;
    }

}

异常类

FileNameLengthLimitExceededException

/**
 * 文件名字长度超过限制异常,用于文件校验
 */
public class FileNameLengthLimitExceededException extends RuntimeException{

    private static final long serialVersionUID = 1L;

    public FileNameLengthLimitExceededException(){

    }

    public FileNameLengthLimitExceededException(String message){
        super(message);
    }

}

FileSizeLimitExceededException

/**
 * 文件大小超过限制异常,用于文件校验
 */
public class FileSizeLimitExceededException extends RuntimeException{

    private static final long serialVersionUID = 1L;

    public FileSizeLimitExceededException(){

    }

    public FileSizeLimitExceededException(String message){
        super(message);
    }

}

InvalidExtensionException

/**
 * 文件后缀无效异常,用于文件校验
 */
public class InvalidExtensionException extends RuntimeException{

    private static final long serialVersionUID = 1L;

    public InvalidExtensionException(){

    }

    public InvalidExtensionException(String message){
        super(message);
    }

}

Controller层

multifile.html页面

    <form action="/uploadFile" method="post" enctype="multipart/form-data">
        <p align="center">选择文件1:<input type="file" name="file"/></p>
        <p align="center"><input type="submit" value="提交"/></p>
    </form>

Controller

    @PostMapping("/uploadFile")
    public String uploadFile(@RequestPart("file") MultipartFile file,Model model) throws IOException {
        String upload = FileUploadUtils.upload(file);

        log.info("上传路径:{}",upload);
        model.addAttribute("uploadUrl",upload);
        return "img";
    }

img.html页面

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">

<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<img th:src="@{http://localhost:8080{uploadUrl}(uploadUrl=${uploadUrl})}"/>
</body>
</html>
  • 8
    点赞
  • 108
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李熠漾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值