【Spring Boot】026-文件上传、下载、删除

【Spring Boot】026-文件上传、下载、删除

更新时间:2023年5月25日 08点52分

一、单文件上传

1、第一步:编写upload.html文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>上传文件</title>
</head>
<body>
    <form action="/upload" method="post" enctype="multipart/form-data">
        <input type="file" name="uploadFile" value="请选择文件">
        <input type="submit" value="上传">
    </form>

</body>
</html>

2、第二步:编写上传接口

package com.zibo.api.controller;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;

@RestController
public class FileUploadController {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
    @PostMapping("upload")
    public String upload(MultipartFile uploadFile, HttpServletRequest request){
        String realPath = request.getSession().getServletContext().getRealPath("/uploadFile/");
        String format = sdf.format(new Date());
        File folder = new File(realPath + format);
        if(!folder.isDirectory()){
            folder.mkdirs();
        }
        String oldName = uploadFile.getOriginalFilename();
        String newName = UUID.randomUUID().toString() + oldName.substring(oldName.lastIndexOf("."));
        try {
            uploadFile.transferTo(new File(folder,newName));
            return request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + "/uploadFile/" + format + "/" + newName;
        }catch (IOException e){
            e.printStackTrace();
        }
        return "上传失败!";
    }
}

3、第三步:运行测试

文件上传

image-20210816154208073

上传成功之后获得地址

image-20210816154316105

访问获得的地址

image-20210816154402115

注意

如果配置了拦截器可能无法直接访问静态文件,需要进行响应的调整(见参考文章),我这里为了方便直接把拦截器注释了!

参考文章:

【Spring Boot】007-Spring Boot Web开发:静态资源导入、Thymeleaf使用

(其中的“自定义策略”部分)

https://blog.csdn.net/qq_29689343/article/details/108552420

4、关于上传文件一些细节上的设置

# 是否开启文件上传支持,默认为true
spring.servlet.multipart.enabled=true
# 文件写入磁盘的阈值,默认为0
spring.servlet.multipart.file-size-threshold=0
# 文件上传临时保存的位置
spring.servlet.multipart.location=E:\\temp
# 单个文件最大大小
spring.servlet.multipart.max-file-size=1MB
# 多文件上传时总最大大小
spring.servlet.multipart.max-request-size=10MB
# 文件是否延迟解析,默认false
spring.servlet.multipart.resolve-lazily=false

二、多文件上传

1、第一步:修改 upload.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>上传文件</title>
</head>
<body>
    <form action="/uploads" method="post" enctype="multipart/form-data">
        <input type="file" name="uploadFiles" value="请选择文件">
        <input type="submit" value="上传">
    </form>

</body>
</html>

2、第二步:编写对应接口

@PostMapping("uploads")
public String upload(MultipartFile[] uploadFiles, HttpServletRequest request){
	// 遍历 uploadFiles 数组,分别存储即可!
}

三、上传、下载、删除示例

1、Controller

package com.wq.floodserver.controller.system;

import com.wq.floodserver.common.pojo.ApiException;
import com.wq.floodserver.common.pojo.Pair;
import com.wq.floodserver.common.util.FileUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

/**
 * 文件 Controller
 *
 * @author dev
 * @date 2023/3/30 16:55
 * @slogan 真正的大师永远怀着一颗学徒的心。——易大师
 */
@RestController
@RequestMapping("/file")
@Slf4j
public class UploadFileController {

    /**
     * 接收前端上传的文件
     *
     * @param file 文件
     * @return 上传后的文件路径 (如:\\uploads\2023\03\30\) + 文件名.后缀)
     */
    @PostMapping("/upload")
    public Pair<String, String> upload(MultipartFile file) {
        // 文件上传后的路径
        return FileUtil.saveFile(file);
    }

    @GetMapping("/download")
    public void download(String path, HttpServletResponse response) {
        // 读取文件
        File file = new File(FileUtil.USER_DIR, path);
        // 从文件路径中获取文件名
        String fileName = FileUtil.getFileName(path);
        // 获取文件输入流
        try (
                FileInputStream is = new FileInputStream(file)
        ) {
            // 解决文件名中文乱码的问题
            response.setHeader("content-disposition",
                    "attachment;fileName=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8));
            // 设置强制下载不打开
            response.setContentType("application/force-download");
            // 获取响应输出流
            ServletOutputStream os = response.getOutputStream();
            // 文件拷贝
            IOUtils.copy(is, os);
            // 关流方式(优雅)
            IOUtils.closeQuietly(is);
            IOUtils.closeQuietly(os);
        } catch (IOException e) {
            throw new ApiException(e);
        }
    }

    /**
     * 删除文件
     */
    @GetMapping("/delete")
    public Boolean delete(String path) {
        return FileUtil.deleteFile(path);
    }

}

2、文件工具类

package com.wq.floodserver.common.util;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Objects;
import java.util.UUID;
import java.util.regex.Pattern;

import com.wq.floodserver.common.pojo.Pair;
import com.wq.floodserver.common.pojo.ApiException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.multipart.MultipartFile;

/**
 * 文件工具类
 *
 * @author dev
 * @date 2022/7/13 0013 11:57
 */
public class FileUtil {

    private FileUtil() {
    }

    public static final String USER_DIR = System.getProperty("user.dir");

    public static final String UPLOADS_PATH = StringUtils.join(USER_DIR, File.separator, "uploads", File.separator);

    private static final Integer BUFFER_SIZE = 8192;

    /**
     * 保存文件
     * 文件的相对地址加了多余的反斜杠,为了防止 idea 报错
     *
     * @param file 文件
     * @return 记录原始文件名 + 相对的文件路径(数据库保存的文件地址:时间文件夹(如:\\uploads\2023\03\30\) + 文件名.后缀)
     */
    public static Pair<String, String> saveFile(File file) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(StringUtils.join("yyyy", File.separator, "MM", File.separator, "dd"));
        // 文件夹路径
        String finalPath = StringUtils.join(UPLOADS_PATH, simpleDateFormat.format(new Date()), File.separator);
        // file
        File dir = new File(finalPath);
        // 如果文件夹不存在,则创建
        if (!dir.exists()) {
            boolean mkdirs = dir.mkdirs();
            if (!mkdirs) {
                throw new ApiException(50000, "创建文件夹失败");
            }
        }
        // 记录原始文件名
        String originalName = file.getName();
        // 文件重命名
        String fileName =
                StringUtils.join(finalPath, UUID.randomUUID().toString(), file.getName().substring(file.getName().lastIndexOf(".")));
        // 保存文件
        boolean renameTo = file.renameTo(new File(fileName));
        if (!renameTo) {
            throw new ApiException(50000, "保存文件失败");
        }
        file.deleteOnExit();
        LogUtility.uploadFile(StringUtils.join("上传文件:", originalName, ",保存路径:", fileName.substring(FileUtil.USER_DIR.length())));
        // 直接返回相对路径,直接存入数据库即可
        return Pair.of(originalName, fileName.substring(FileUtil.USER_DIR.length()));
    }

    /**
     * 保存文件
     *
     * @param multipartFile 文件
     * @return 保存后的相对路径
     */
    public static Pair<String, String> saveFile(MultipartFile multipartFile) {
        try {
            return saveFile(multipartFileToFile(multipartFile));
        } catch (NullPointerException e) {
            throw new ApiException(50000, "保存文件失败:" + e.getMessage());
        }
    }

    /**
     * 根据相对路径删除文件,示例地址加了多余的斜杠,实际使用时注意
     *
     * @param path 数据库保存的文件地址:时间文件夹(如:\\uploads\2023\03\30\) + 文件名.后缀
     *             文件上传得到的完整地址:D:\MyFile\GitHub\flood-server\\uploads\2023\03\30\269889b4-359d-48b5-93fb-049e621062d5.xmind
     *             UPLOADS_PATH:D:\MyFile\GitHub\flood-server\\uploads\
     *             数据库保存的文件地址:\\uploads\2023\03\30\269889b4-359d-48b5-93fb-049e621062d5.xmind
     * @return 是否删除成功
     */
    public static Boolean deleteFile(String path) {
        String originalPath = path;
        // 替换掉“\\uploads\”
        path = path.replace("\\uploads\\", "");
        // 验证路径的合法性:(策略)验证是否是以四位数年份开头即可
        String pattern = "^\\d{4}";
        if (Pattern.matches(pattern, path)) {
            throw new ApiException("无效的文件路径:" + originalPath);
        }
        // 创建 File
        File file = new File(StringUtils.join(UPLOADS_PATH, path));
        // 文件删除日志
        LogUtility.deleteFile(path);
        // 删除文件
        return file.delete();
    }

    /**
     * 将 multipartFile 转换成  临时 file 文件.(该临时文件使用完记得 delete掉.)
     *
     * @param multipartFile multipartFile
     * @return 临时的 file 文件.
     */
    public static File multipartFileToFile(MultipartFile multipartFile) {
        if (multipartFile == null) {
            return null;
        }
        File file = new File(Objects.requireNonNull(multipartFile.getOriginalFilename()));
        try (
                OutputStream os = new FileOutputStream(file);
                InputStream inputStream = multipartFile.getInputStream()
        ) {
            int bytesRead;
            byte[] buffer = new byte[8192];
            while ((bytesRead = inputStream.read(buffer, 0, BUFFER_SIZE)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return file;
    }

    /**
     * 从文件路径中获取文件名
     *
     * @param path 文件路径
     * @return 文件名
     */
    public static String getFileName(String path) {
        return path.substring(path.lastIndexOf(File.separator) + 1);
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值