工作中上传文件是常见的场景之一,最典型的情况就是上传头像等,上传文档。自己上传文件中遇到的坑,自己已经填了,现在把它写下来,方便提醒自己,我使用的是Spring Boot 上传文件
主要引入了web
和thymeleaf
启动器
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
<dependency>
<groupId>commons-iogroupId>
<artifactId>commons-ioartifactId>
<version>2.5version>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-lang3artifactId>
dependency>
spring:
servlet:
multipart:
max-file-size: 10MB # 最大支持文件大小
max-request-size: 10MB # 最大支持请求大小
#enabled: true #默认支持文件上传.
# file-size-threshold: #支持文件写入磁盘.
#location: #上传文件的临时目录
UploadController
首页的显示
@Controller
public class UploadController {
@GetMapping("/")
public String index() {
return "upload";
}
}
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","上传成功");
model.addAttribute("img",newImg);
return "upload";
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
model.addAttribute("msg","上传失败");
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","下载成功");
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","下载失败");
return "upload";
}
DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<h1>Spring Boot file upload exampleh1>
<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>
解决:
String filePath = request.getServletContext().getRealPath("/");
获取的是tomcat目录下webapps
下的目录及类路径(class文件存放的路径),因为我使用的是SpringBoot
项目,而SringBoot项目内嵌了tomcat,路径为C:\Users\L15096000421\AppData\Local\Temp\tomcat-docbase.2191751665660359817.8080\
的一个临时目录,重启项目,文件就丢失了String filePath = request.getServletContext().getRealPath("/")
做为下载的路径去下载文件,后台报错没有权限,使用绝对路径下载,及使用绝对路径上传private static final String parentPath = ClassUtils.getDefaultClassLoader().getResource("static/images").getPath();
获取springboot项目static/images的目录/**
* 字符串工具类,抽取一些常用操作
*/
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();
}
}
/**
* 媒体类型工具类
*
*/
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 "";
}
}
}
/**
* 文件上传工具类
*/
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;
}
}
/**
* 文件名字长度超过限制异常,用于文件校验
*/
public class FileNameLengthLimitExceededException extends RuntimeException{
private static final long serialVersionUID = 1L;
public FileNameLengthLimitExceededException(){
}
public FileNameLengthLimitExceededException(String message){
super(message);
}
}
/**
* 文件大小超过限制异常,用于文件校验
*/
public class FileSizeLimitExceededException extends RuntimeException{
private static final long serialVersionUID = 1L;
public FileSizeLimitExceededException(){
}
public FileSizeLimitExceededException(String message){
super(message);
}
}
/**
* 文件后缀无效异常,用于文件校验
*/
public class InvalidExtensionException extends RuntimeException{
private static final long serialVersionUID = 1L;
public InvalidExtensionException(){
}
public InvalidExtensionException(String message){
super(message);
}
}
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>Titletitle>
head>
<body>
<img th:src="@{http://localhost:8080{uploadUrl}(uploadUrl=${uploadUrl})}"/>
body>
html>