注:已经从若依框架完成拆分,此处单独分析一下人家精彩的封装,也来理解一下怎么做一个通用的上传接口!如有分析的,理解的不透彻的地方,大家多多包含,欢迎批评指正,但是请不要恶语相向!
@PostMapping("/upload")
public AjaxResult uploadFile(MultipartFile file){
try {
// 上传文件路径
String filePath = RuoYiConfig.getUploadPath();
// 上传并返回新文件名称
String fileName = FileUploadUtils.upload(filePath, file);
String url = serverConfig.getUrl() + fileName;
AjaxResult ajax = AjaxResult.success();
ajax.put("url", url);
ajax.put("fileName", fileName);
ajax.put("newFileName", FileUtils.getName(fileName));
ajax.put("originalFilename", file.getOriginalFilename());
return ajax;
} catch (Exception e) {
return AjaxResult.error(e.getMessage());
}
}
@PostMapping("/upload")
:这是一个用于处理HTTP POST请求的注解,它将请求映射到/upload
路径。在这里,它用于处理文件上传请求。
public AjaxResult uploadFile(MultipartFile file)
:这是处理文件上传的方法。它接收一个MultipartFile
对象,这是Spring提供的用于处理文件上传的类。
String filePath = RuoYiConfig.getUploadPath();
:获取文件上传路径,通过RuoYiConfig.getUploadPath()
方法获取,是从配置文件中读取的上传路径。
server:
port: 8888
# 项目相关配置
ruoyi:
# 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath)
profile: ./
String fileName = FileUploadUtils.upload(filePath, file);
:调用FileUploadUtils.upload
方法实现文件上传,该方法包含文件存储逻辑,根据传入的文件路径和MultipartFile
对象,返回新的文件名。下一标题我们将会着重对于这个方法进行解析。
String url = serverConfig.getUrl() + fileName;
:构造文件的访问URL,是通过拼接服务器的URL和上传后的文件名得到的。
AjaxResult ajax = AjaxResult.success();
:创建一个成功的AjaxResult
对象,用于封装返回给客户端的数据。
ajax.put("url", url);
:将文件的访问URL放入AjaxResult中,以便客户端获取上传后的文件的访问地址。
ajax.put("fileName", fileName);
:将上传后的文件名放入AjaxResult中。
ajax.put("newFileName", FileUtils.getName(fileName));
:将上传后的文件名去除路径的部分,只保留文件名放入AjaxResult中。
ajax.put("originalFilename", file.getOriginalFilename());
:将原始文件名放入AjaxResult中。
return ajax;
:返回封装了文件相关信息的AjaxResult对象,向客户端提供文件上传成功的响应。
} catch (Exception e) { return AjaxResult.error(e.getMessage());}
:捕获可能的异常,如果发生异常,返回一个包含异常信息的错误AjaxResult对象,向客户端提供文件上传失败的响应。
upload
文件上传方法点击方法跳进去之后我们可以看到如下代码
package com.it_wanghui_cn.file.utils;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.Objects;
import com.it_wanghui_cn.file.config.RuoYiConfig;
import com.it_wanghui_cn.file.constant.Constants;
import com.it_wanghui_cn.file.exception.FileNameLengthLimitExceededException;
import com.it_wanghui_cn.file.exception.FileSizeLimitExceededException;
import com.it_wanghui_cn.file.exception.InvalidExtensionException;
import com.it_wanghui_cn.file.utils.uuid.Seq;
import org.apache.commons.io.FilenameUtils;
import org.springframework.web.multipart.MultipartFile;
/**
* 文件上传工具类
*
* @author ruoyi
*/
public class FileUploadUtils
{
/**
* 默认大小 50M
*/
public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024;
/**
* 默认大小 50M
*/
public static final long DEFAULT_APP_MAX_SIZE = 200 * 1024 * 1024;
/**
* 默认的文件名最大长度 100
*/
public static final int DEFAULT_FILE_NAME_LENGTH = 100;
/**
* 默认上传的地址
*/
private static String defaultBaseDir = RuoYiConfig.getProfile();
public static void setDefaultBaseDir(String defaultBaseDir)
{
FileUploadUtils.defaultBaseDir = defaultBaseDir;
}
public static String getDefaultBaseDir()
{
return defaultBaseDir;
}
/**
* 以默认配置进行文件上传
*
* @param file 上传的文件
* @return 文件名称
* @throws Exception
*/
public static final String upload(MultipartFile file) throws IOException
{
try
{
return upload(getDefaultBaseDir(), file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
}
catch (Exception e)
{
throw new IOException(e.getMessage(), e);
}
}
/**
* 根据文件路径上传
*
* @param baseDir 相对应用的基目录
* @param file 上传的文件
* @return 文件名称
* @throws IOException
*/
public static final String upload(String baseDir, MultipartFile file) throws IOException
{
try
{
return upload(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
}
catch (Exception e)
{
throw new IOException(e.getMessage(), e);
}
}
/**
* 文件上传
*
* @param baseDir 相对应用的基目录
* @param file 上传的文件
* @param allowedExtension 上传文件类型
* @return 返回上传成功的文件名
* @throws FileSizeLimitExceededException 如果超出最大大小
* @throws FileNameLengthLimitExceededException 文件名太长
* @throws IOException 比如读写文件出错时
* @throws InvalidExtensionException 文件校验异常
*/
public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension)
throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException,
InvalidExtensionException
{
int fileNamelength = Objects.requireNonNull(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);
String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath();
file.transferTo(Paths.get(absPath));
return getPathFileName(baseDir, fileName);
}
/**
* 编码文件名
*/
public static final String extractFilename(MultipartFile file)
{
return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(),
FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), getExtension(file));
}
public static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException
{
File desc = new File(uploadDir + File.separator + fileName);
if (!desc.exists())
{
if (!desc.getParentFile().exists())
{
desc.getParentFile().mkdirs();
}
}
return desc;
}
public static final String getPathFileName(String uploadDir, String fileName) throws IOException
{
int dirLastIndex = RuoYiConfig.getProfile().length() + 1;
String currentDir = StringUtils.substring(uploadDir, dirLastIndex);
return Constants.RESOURCE_PREFIX + "/" + currentDir + "/" + fileName;
}
/**
* 文件大小校验
*
* @param file 上传的文件
* @return
* @throws FileSizeLimitExceededException 如果超出最大大小
* @throws InvalidExtensionException
*/
public static final void assertAllowed(MultipartFile file, String[] allowedExtension)
throws FileSizeLimitExceededException, InvalidExtensionException
{
long size = file.getSize();
String fileSuffix;
if (null != file.getOriginalFilename() && file.getOriginalFilename().contains(".")) {
fileSuffix = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".")+1);
}else {
fileSuffix = null;
}
if ("apk".equals(fileSuffix) || "ipa".equals(fileSuffix)) {
if (size > DEFAULT_APP_MAX_SIZE)
{
throw new FileSizeLimitExceededException(DEFAULT_APP_MAX_SIZE / 1024 / 1024);
}
}else {
if (size > DEFAULT_MAX_SIZE)
{
throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024);
}
}
String fileName = file.getOriginalFilename();
String extension = getExtension(file);
if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension))
{
if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION)
{
throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension,
fileName);
}
else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION)
{
throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension,
fileName);
}
else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION)
{
throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension,
fileName);
}
else if (allowedExtension == MimeTypeUtils.VIDEO_EXTENSION)
{
throw new InvalidExtensionException.InvalidVideoExtensionException(allowedExtension, extension,
fileName);
}
else
{
throw new InvalidExtensionException(allowedExtension, extension, fileName);
}
}
}
/**
* 判断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 (StringUtils.isEmpty(extension))
{
extension = MimeTypeUtils.getExtension(Objects.requireNonNull(file.getContentType()));
}
return extension;
}
}
这是一个文件上传工具类,主要用于处理文件上传的相关逻辑。
常量定义:
DEFAULT_MAX_SIZE
:默认文件大小限制为50MB。DEFAULT_APP_MAX_SIZE
:默认App文件大小限制为200MB。DEFAULT_FILE_NAME_LENGTH
:默认文件名最大长度为100字符。defaultBaseDir
:默认的文件上传基目录,初始化时可能从RuoYiConfig
中获取。上传文件方法:
upload(MultipartFile file)
:使用默认配置上传文件,调用upload(String baseDir, MultipartFile file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION)
。
upload(String baseDir, MultipartFile file)
:根据给定的基目录上传文件,调用upload(String baseDir, MultipartFile file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION)
。
upload(String baseDir, MultipartFile file, String[] allowedExtension)
:文件上传的核心方法,包含文件大小、文件名长度和文件扩展名的校验逻辑。
public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension)
throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException,
InvalidExtensionException
{
int fileNamelength = Objects.requireNonNull(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);
String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath();
file.transferTo(Paths.get(absPath));
return getPathFileName(baseDir, fileName);
}
由于这段代码是文件上传的核心方法,我们对其进行细分析:
文件名长度检查:
int fileNamelength = Objects.requireNonNull(file.getOriginalFilename()).length();
if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) {
throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH);
}
DEFAULT_FILE_NAME_LENGTH
)。FileNameLengthLimitExceededException
异常。文件扩展名和大小校验:
assertAllowed(file, allowedExtension);
调用assertAllowed
方法,对文件的扩展名和大小进行校验。
如果不符合要求,会抛出FileSizeLimitExceededException
、InvalidExtensionException
等异常。
public static final void assertAllowed(MultipartFile file, String[] allowedExtension)
throws FileSizeLimitExceededException, InvalidExtensionException
{
long size = file.getSize();
String fileSuffix;
if (null != file.getOriginalFilename() && file.getOriginalFilename().contains(".")) {
fileSuffix = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".")+1);
}else {
fileSuffix = null;
}
if ("apk".equals(fileSuffix) || "ipa".equals(fileSuffix)) {
if (size > DEFAULT_APP_MAX_SIZE)
{
throw new FileSizeLimitExceededException(DEFAULT_APP_MAX_SIZE / 1024 / 1024);
}
}else {
if (size > DEFAULT_MAX_SIZE)
{
throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024);
}
}
String fileName = file.getOriginalFilename();
String extension = getExtension(file);
if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension))
{
if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION)
{
throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension,
fileName);
}
else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION)
{
throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension,
fileName);
}
else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION)
{
throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension,
fileName);
}
else if (allowedExtension == MimeTypeUtils.VIDEO_EXTENSION)
{
throw new InvalidExtensionException.InvalidVideoExtensionException(allowedExtension, extension,
fileName);
}
else
{
throw new InvalidExtensionException(allowedExtension, extension, fileName);
}
}
}
这是文件上传工具类中的文件校验方法 assertAllowed
,以下是对其进行细分析:
获取文件大小和扩展名:
long size = file.getSize();
String fileSuffix;
if (null != file.getOriginalFilename() && file.getOriginalFilename().contains(".")) {
fileSuffix = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".")+1);
} else {
fileSuffix = null;
}
文件大小校验:
if ("apk".equals(fileSuffix) || "ipa".equals(fileSuffix)) {
if (size > DEFAULT_APP_MAX_SIZE) {
throw new FileSizeLimitExceededException(DEFAULT_APP_MAX_SIZE / 1024 / 1024);
}
} else {
if (size > DEFAULT_MAX_SIZE) {
throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024);
}
}
fileSuffix
)判断文件类型,如果是apk或ipa文件,检查文件大小是否超过默认限制(DEFAULT_APP_MAX_SIZE
),否则检查是否超过常规文件大小限制(DEFAULT_MAX_SIZE
)。FileSizeLimitExceededException
异常。文件扩展名校验:
String fileName = file.getOriginalFilename();
String extension = getExtension(file);
if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension)) {
// ...
}
allowedExtension
不为空且文件扩展名不在允许的扩展名列表中,进入后续的异常判断逻辑。根据文件类型抛出不同的异常:
if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION) {
throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension, fileName);
} else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION) {
throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension, fileName);
} else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION) {
throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension, fileName);
} else if (allowedExtension == MimeTypeUtils.VIDEO_EXTENSION) {
throw new InvalidExtensionException.InvalidVideoExtensionException(allowedExtension, extension, fileName);
} else {
throw new InvalidExtensionException(allowedExtension, extension, fileName);
}
InvalidImageExtensionException
异常。所以,综上所述呢,assertAllowed
方法主要用于对文件的大小和扩展名进行校验,确保文件满足预定义的条件,否则抛出相应的异常。
生成文件名和绝对路径:
String fileName = extractFilename(file);
String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath();
extractFilename
方法,根据上传文件生成编码后的文件名。getAbsoluteFile
方法,获取文件的绝对路径。文件写入磁盘:
file.transferTo(Paths.get(absPath));
transferTo
方法将文件写入磁盘,具体路径由absPath
决定。返回文件相对路径:
return getPathFileName(baseDir, fileName);
getPathFileName
方法,生成相对于上传基目录的文件路径。所以,综上所述呢,这段代码通过一系列步骤完成了文件上传的核心逻辑,包括文件名长度、扩展名、大小的校验,生成文件名,将文件写入磁盘,并返回相对路径。异常的处理确保了在上传过程中出现问题时能够向上层抛出相应的异常。
文件名处理:
extractFilename(MultipartFile file)
:根据上传文件生成编码后的文件名,包括日期路径、文件基名、Seq以及文件扩展名。public static final String extractFilename(MultipartFile file)
{
return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(),
FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), getExtension(file));
}
构造文件名:
StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(),
FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), getExtension(file));
StringUtils.format
方法)构建文件名。这个文件名包括以下几个部分:
{}/
:日期路径,可能是根据上传日期生成的目录结构。{}_
:原始文件名去除扩展名后的部分。{}
:通过某种方式获取的上传序列号或ID。.
:文件名的分隔符。getExtension(file)
:获取文件的扩展名。返回构造的文件名:
return StringUtils.format(...);
所以,综上所述呢,extractFilename
方法主要用于根据上传文件的信息构建一个新的文件名,其中包括日期路径、原始文件名的基本部分、上传序列号或ID,以及文件扩展名。这个文件名通常用于确定上传文件在服务器上的存储位置。
文件路径操作:
getAbsoluteFile(String uploadDir, String fileName)
:获取文件的绝对路径,并确保其父目录存在。
public static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException
{
File desc = new File(uploadDir + File.separator + fileName);
if (!desc.exists())
{
if (!desc.getParentFile().exists())
{
desc.getParentFile().mkdirs();
}
}
return desc;
}
这就是文件上传工具类中的 getAbsoluteFile
方法,以下是对其进行细分析:
构造文件对象:
File desc = new File(uploadDir + File.separator + fileName);
File
对象,表示上传目录 (uploadDir
) 下的特定文件 (fileName
)。检查文件是否存在:
if (!desc.exists()) {
// ...
}
创建文件父目录:
if (!desc.getParentFile().exists()) {
desc.getParentFile().mkdirs();
}
mkdirs()
方法创建父目录。这样可以确保文件存储路径的所有父目录都存在。返回文件对象:
return desc;
File
对象。getAbsoluteFile
方法主要用于获取表示上传文件的 File
对象,并确保文件所在的目录结构是存在的。如果文件所在的目录不存在,会先创建这些目录。这样可以保证文件写入磁盘时,其所在的目录结构是正确的。
getPathFileName(String uploadDir, String fileName)
:根据上传目录和文件名构造相对路径,通常用于构造访问URL。
public static final String getPathFileName(String uploadDir, String fileName) throws IOException
{
int dirLastIndex = RuoYiConfig.getProfile().length() + 1;
String currentDir = StringUtils.substring(uploadDir, dirLastIndex);
return Constants.RESOURCE_PREFIX + "/" + currentDir + "/" + fileName;
}
文件大小和扩展名校验:
assertAllowed(MultipartFile file, String[] allowedExtension)
:对上传的文件进行大小和扩展名的校验,根据文件扩展名调用isAllowedExtension
方法判断是否允许上传。isAllowedExtension(String extension, String[] allowedExtension)
:判断文件扩展名是否在允许的扩展名列表中。其他方法:
getExtension(MultipartFile file)
:获取文件的扩展名,优先使用原始文件名的扩展名,如果为空,则使用文件的MIME类型来获取扩展名。总体而言,该工具类提供了一系列方法,用于方便地进行文件上传,并包含了一些常见的文件校验逻辑。
transferTo
方法再深入 default void transferTo(Path dest) throws IOException, IllegalStateException {
FileCopyUtils.copy(this.getInputStream(), Files.newOutputStream(dest));
}
Files.newOutputStream(dest)
是 Java NIO(New I/O)库提供的一种方式,用于创建一个输出流(OutputStream
)以写入文件。这方法返回一个输出流,你可以使用它来写入字节到指定的文件。
具体来说,Files.newOutputStream(dest)
的参数 dest
是一个 Path
对象,表示文件路径。下面是一些关键的点:
创建输出流:
Path dest = ...; // 指定文件路径
OutputStream outputStream = Files.newOutputStream(dest);
Files.newOutputStream(dest)
创建一个输出流。写入数据:
byte[] data = ...; // 准备写入的数据
outputStream.write(data);
write
方法将数据写入文件。关闭流:
outputStream.close();
close
方法关闭输出流,以释放相关资源。这个方法的主要优点是简洁且易用,不需要手动创建文件或处理一些底层的操作。它是 Java NIO 中用于文件写入的一部分,提供了更灵活和高性能的 I/O 操作。
看来还是千呼万唤始出来,犹抱琵琶半遮面,我们再进一层
public static int copy(InputStream in, OutputStream out) throws IOException {
Assert.notNull(in, "No InputStream specified");
Assert.notNull(out, "No OutputStream specified");
int var2;
try {
var2 = StreamUtils.copy(in, out);
} finally {
close(in);
close(out);
}
return var2;
}
这是一个用于将输入流(InputStream
)的内容复制到输出流(OutputStream
)的辅助方法。以下是对这个方法进行细分析:
参数校验:
Assert.notNull(in, "No InputStream specified");
Assert.notNull(out, "No OutputStream specified");
Assert
工具类,确保输入流和输出流都不为 null
。如果为 null
,抛出 IllegalArgumentException
异常。流复制:
try {
var2 = StreamUtils.copy(in, out);
} finally {
close(in);
close(out);
}
try
块中使用 StreamUtils.copy
方法将输入流的内容复制到输出流。StreamUtils.copy
方法是 Spring Framework 提供的用于复制流的实用方法。finally
块中调用 close
方法关闭输入流和输出流。这确保在复制完成或发生异常时都会关闭这两个流,避免资源泄漏。返回复制的字节数:
return var2;
StreamUtils.copy
方法通常返回复制的字节数,这里将其作为方法的返回值。综合而言,这个方法是一个简化的输入流到输出流的复制操作,确保在完成复制或发生异常时关闭输入流和输出流。这种实现方式通常用于避免手动处理流关闭操作,提高代码的简洁性和可读性。
以上就是我们对于若依文件上传接口的一个分析,可能还是比较浅显,由于我也是一个初学者,对于这套优秀的框架掌握尚欠,欢迎大家进行批评指正!
项目源码: https://gitee.com/wanghui1201/FileOperateUtils
可以直接拿去当做轮子用,大家做毕设啥的可以直接用,简单好用,就不用大家自己拆离了!