若依框架——上传下载

1,755 阅读13分钟

上传 和 下载

一:用户头像

    /**
     * 头像上传
     */
    @Log(title = "用户头像", businessType = BusinessType.UPDATE)
    @PostMapping("/avatar")
    public AjaxResult avatar(@RequestParam("avatarfile") MultipartFile file) throws Exception
    {
        if (!file.isEmpty())
        {
            LoginUser loginUser = getLoginUser();
            String avatar = FileUploadUtils.upload(RuoYiConfig.getAvatarPath(), file, MimeTypeUtils.IMAGE_EXTENSION); // IMAGE_EXTENSION = { "bmp", "gif", "jpg", "jpeg", "png" }
            if (userService.updateUserAvatar(loginUser.getUsername(), avatar))
            {
                AjaxResult ajax = AjaxResult.success();
                ajax.put("imgUrl", avatar);
                // 更新缓存用户头像
                loginUser.getUser().setAvatar(avatar);
                tokenService.setLoginUser(loginUser);
                return ajax;
            }
        }
        return error("上传图片异常,请联系管理员");
    }

1.1 参数

@RequestParam("avatarfile") MultipartFile file @RequestParam 注解表明从请求参数中,获取名为 avatarfile 的文件,并且绑定到 MultipartFile类型的file变量上。

1.2 文件不为空

if (!file.isEmpty())

首先判断文件 , 不可以为空 。

1.3 下载方法

LoginUser loginUser = getLoginUser();
String avatar = FileUploadUtils.upload(RuoYiConfig.getAvatarPath(), file, MimeTypeUtils.IMAGE_EXTENSION);
  • 得到当前登录用户
  • 调用 FileUploadUtils 的 upload 方法,传入三个个参数 一个是
    • 下载路径,
    • 第二个是 头像文件 ,
    • 第三个是文件的类型。
  • 返回的是 上传成功的文件名
1.3.1 upload 方法
/**
     * 文件上传
     *
     * @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);
    }

		int fileNamelength = Objects.requireNonNull(file.getOriginalFilename()).length();
        if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) //默认的文件名最大长度 100
        {
            throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH);
        }
  • 首先调用 MultipartFile 接口自己的 getOriginalFilename() 方法得到文件的原始名,然后得到它的长度
  • 进行判断,如果大于文件名的最大长度 ,抛出 文件名称超长限制异常类
1.3.1.1 assertAllowed 方法
assertAllowed(file, allowedExtension); // 
  • 运用 assertAllowed 方法对文件进行一些校验

  • /**
         * 文件大小校验
         *
         * @param file 上传的文件
         * @return
         * @throws FileSizeLimitExceededException 如果超出最大大小
         * @throws InvalidExtensionException
         */
        public static final void assertAllowed(MultipartFile file, String[] allowedExtension)
                throws FileSizeLimitExceededException, InvalidExtensionException
        {
            long size = file.getSize();
            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);
                }
            }
        }
    
  •         long size = file.getSize(); 
            if (size > DEFAULT_MAX_SIZE) //  DEFAULT_MAX_SIZE = 50 * 1024 * 1024L 50mb
            {
                throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024);
            }
    
  • 首先是进行文件大小的校验, 利用 MultipartFile 接口自己的 getSize 方法 获取文件的大小(以字节为单位),如果文件的大小超过50MB 就抛出 文件名大小限制异常类

  •         String fileName = file.getOriginalFilename();
            String extension = getExtension(file);
    
  • 获取文件原始名, 利用 getExtension 方法

  • 获取文件名后缀

  •     /**
         * 获取文件名的后缀
         *
         * @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())); 					//getContentType() :获取文件的内容类型(MIME 类型 , requireNonNull :如果参数为 null,则抛出空指针异常
            }
            return extension;
        }
    
  • 判断文件的 类型

  • 当 允许得文件类型不为空,并且 当前文件得扩展名,不在允许得范围内,就会进入 if 块

  • 这里是根据不同的格式,抛出不同得文件类型得

  •  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);
                }
            }
    

    运用了 isAllowedExtension 方法

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

    通过遍历循环,来判断,文件得类型是不是符合 允许

1.3.1.2 extractFilename 方法 编码文件名
String fileName = extractFilename(file);
  /**
     * 编码文件名
     */
    public static final String extractFilename(MultipartFile file)
    {
        return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(),
                FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), getExtension(file));
    }
  • 这里是把 文件名字格式进行编写
  • 改为 。。/ 。。_。。. 文件格式 类似于 2025/01/09/example_12345 . jpg
  • FilenameUtils . getBaseName 方法则从原始文件名中提取基本名称部分,即去掉扩展名后的部分。对于 example.jpg,它将返回 example。
  • Seq.getId(Seq.uploadSeqType): 调用 Seq 类的 getId 方法,并传入 Seq.uploadSeqType 参数。这个方法可能会生成一个唯一的标识符,用于确保生成的文件名在同一时间内是唯一的。例如,它可能返回一个递增的数字序列或一个 UUID
1.3.1.3 getAbsoluteFile 方法
String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath();

确保了指定文件的父目录存在,并返回一个代表该文件绝对路径的 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;
    }

File . separator 是一个与系统相关的路径分隔符,在 Windows 系统上是 \,在 Unix/Linux 系统上是 /

首先创建一个 文件对象

首先检查 desc 所代表的文件或目录是否存在。如果不存在,进一步检查该文件的父目录(即 desc. getParentFile())是否存在

如果父目录不存在,调用 mkdirs() 方法创建父目录及其所有必要的上级目录。mkdirs() 方法会创建指定路径中的所有不存在的目录。

  • 例如,如果要创建的文件路径是 /a/b/c/example . jpg,而 /a 和 /a/b 目录都不存在,mkdirs() 会一次性创建 /a 和 /a/b 目录。
1.3.1.4 文件保存
   	file.transferTo(Paths.get(absPath));//absolutePath 绝对路径 :D:/ruoyi/uploadPath/avatar/2020/03/06/xxxx.jpg

Paths . get(absPath) 将绝对路径 absPath 转换为 Path 对象,Path 是 Java NIO.2 中用于处理文件路径的类。

transferTo 方法将上传的文件内容写入到由 Paths . get(absPath) 指定的文件路径中,从而完成文件的保存操作。

1.3.1.5 返回
return getPathFileName(baseDir, fileName);//baseDir = D:/ruoyi/uploadPath/avatar 
									   //fileName = 2020/03/06/xxxx_202022324.jpg

截取路径,将路径变成 类似于 /profile/avatar/2024/12/25/詹姆随——black_ 20241225103235A001 . jpg 这样

    public static final String getPathFileName(String uploadDir, String fileName) throws IOException
    {
        int dirLastIndex = RuoYiConfig.getProfile().length() + 1; //RuoYiConfig.getProfile(): D:/ruoyi/uploadPath
        String currentDir = StringUtils.substring(uploadDir, dirLastIndex); //截取 D:/ruoyi/uploadPath/ 之后的部分
        return Constants.RESOURCE_PREFIX + "/" + currentDir + "/" + fileName;
    }
1.3.2 数据库、缓存 更新
 if (userService.updateUserAvatar(loginUser.getUsername(), avatar))
            {
                AjaxResult ajax = AjaxResult.success();
                ajax.put("imgUrl", avatar);
                // 更新缓存用户头像
                loginUser.getUser().setAvatar(avatar);
                tokenService.setLoginUser(loginUser);
                return ajax;
            }
  • 运用 userService 的 updateUserAvatar 方法,更新数据库中的头像的值
  • 运用 tokenService的 setLoginUser方法,更新缓存中的用户信息
1.3.3 图片为空 , 报异常
return error("上传图片异常,请联系管理员");

二:下载

/**
 * 通用下载请求
 * 
 * @param fileName 文件名称
 * @param delete 是否删除
 */
@GetMapping("/download")
public void fileDownload(String fileName, Boolean delete, HttpServletResponse response, HttpServletRequest request)
{
    try
    {
        if (!FileUtils.checkAllowDownload(fileName))
        {
            throw new Exception(StringUtils.format("文件名称({})非法,不允许下载。 ", fileName));
        }
        String realFileName = System.currentTimeMillis() + fileName.substring(fileName.indexOf("_") + 1);
        String filePath = RuoYiConfig.getDownloadPath() + fileName;

        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
        FileUtils.setAttachmentResponseHeader(response, realFileName);
        FileUtils.writeBytes(filePath, response.getOutputStream());
        if (delete)
        {
            FileUtils.deleteFile(filePath);
        }
    }
    catch (Exception e)
    {
        log.error("下载文件失败", e);
    }
}

2.1 try —— catch 包裹

try{
    
}
 catch (Exception e)
        {
            log.error("下载文件失败", e);
        }

2.2 判断文件是否可以下载

            if (!FileUtils.checkAllowDownload(fileName))
            {
                throw new Exception(StringUtils.format("文件名称({})非法,不允许下载。 ", fileName));
            }
2.2.1 FileUtils 的 checkAllowDownload 方法
/**
     * 检查文件是否可下载
     * 
     * @param resource 需要下载的文件
     * @return true 正常 false 非法
     */
    public static boolean checkAllowDownload(String resource)
    {
        // 禁止目录上跳级别
        if (StringUtils.contains(resource, ".."))
        {
            return false;
        }

        // 检查允许下载的文件规则
        if (ArrayUtils.contains(MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION, FileTypeUtils.getFileType(resource)))
        {
            return true;
        }

        // 不在允许下载的文件规则
        return false;
    }

首先判断,文件名中,有无 .. (禁止目录上跳级别)

2.2.2 FileTypeUtils 的 getFileType方法 获取文件后缀
/**
     * 获取文件类型
     * <p>
     * 例如: ruoyi.txt, 返回: txt
     *
     * @param fileName 文件名
     * @return 后缀(不含".")
     */
    public static String getFileType(String fileName)
    {
        int separatorIndex = fileName.lastIndexOf(".");
        if (separatorIndex < 0)
        {
            return "";
        }
        return fileName.substring(separatorIndex + 1).toLowerCase();
    }
2.2.3 允许下载的文件格式
   public static final String[] DEFAULT_ALLOWED_EXTENSION = {
            // 图片
            "bmp", "gif", "jpg", "jpeg", "png",
            // word excel powerpoint
            "doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt",
            // 压缩文件
            "rar", "zip", "gz", "bz2",
            // 视频格式
            "mp4", "avi", "rmvb",
            // pdf
            "pdf" };

2.3 生成文件名

String realFileName = System.currentTimeMillis() + fileName.substring(fileName.indexOf("_") + 1);

这里生成一个 时间戳(170397480228040) 加上 原文件名_ 之后的名字拼接

2.4 生成文件路径

String filePath = RuoYiConfig.getDownloadPath() + fileName; 
//RuoYiConfig.getDownloadPath()= D:/ruoyi/uploadPath/download/

生成一个 D:/ruoyi/uploadPath/download/ 固定开头加上 文件名的 文件路径

2.5 设置响应头

response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
FileUtils.setAttachmentResponseHeader(response, realFileName);
  • response . setContentType(MediaType . APPLICATION_OCTET_STREAM_VALUE):设置响应的内容类型为application/octet-stream,表示这是一个二进制文件流,浏览器会将其作为下载文件处理。
  • FileUtils . setAttachmentResponseHeader(response, realFileName):调用FileUtils类的setAttachmentResponseHeader方法,设置响应头,将文件名设置为realFileName,这样在下载时,浏览器显示的文件名就是realFileName。
/**
 * 下载文件名重新编码
 *
 * @param response 响应对象
 * @param realFileName 真实文件名
 */
public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) throws UnsupportedEncodingException
{
    String percentEncodedFileName = percentEncode(realFileName);

    StringBuilder contentDispositionValue = new StringBuilder();
    contentDispositionValue.append("attachment; filename=")
            .append(percentEncodedFileName)
            .append(";")
            .append("filename*=")
            .append("utf-8''")
            .append(percentEncodedFileName);

    response.addHeader("Access-Control-Expose-Headers", "Content-Disposition,download-filename");
    response.setHeader("Content-disposition", contentDispositionValue.toString());
    response.setHeader("download-filename", percentEncodedFileName);
}
  • /**
     * 百分号编码工具方法
     *
     * @param s 需要百分号编码的字符串
     * @return 百分号编码后的字符串
     */
    public static String percentEncode(String s) throws UnsupportedEncodingException
    {
        String encode = URLEncoder.encode(s, StandardCharsets.UTF_8.toString());
        return encode.replaceAll("\\+", "%20");
    }
    

2.6 写入文件内容到响应

FileUtils.writeBytes(filePath, response.getOutputStream());

FileUtils 的 writeBytes 方法

/**
     * 输出指定文件的byte数组
     * 
     * @param filePath 文件路径
     * @param os 输出流
     * @return
     */
    public static void writeBytes(String filePath, OutputStream os) throws IOException
    {
        FileInputStream fis = null;
        try
        {
            File file = new File(filePath);
            if (!file.exists())
            {
                throw new FileNotFoundException(filePath);
            }
            fis = new FileInputStream(file);
            byte[] b = new byte[1024];
            int length;
            while ((length = fis.read(b)) > 0)
            {
                os.write(b, 0, length);
            }
        }
        catch (IOException e)
        {
            throw e;
        }
        finally
        {
            IOUtils.close(os);
            IOUtils.close(fis);
        }
    }
      FileInputStream fis = null;

首先声明一个 空的输入流对象,用于后续读取内容

 File file = new File(filePath);
            if (!file.exists())
            {
                throw new FileNotFoundException(filePath);
            }

通过 File 类根据传入的文件路径 filePath 创建一个 File 对象 file。然后检查该文件是否存在,如果不存在,抛出 FileNotFoundException 异常

 fis = new FileInputStream(file);
            byte[] b = new byte[1024];
            int length;
            while ((length = fis.read(b)) > 0)
            {
                os.write(b, 0, length);
            }

创建一个 FileInputStream 对象 fis 来读取文件。定义一个大小为 1024 字节的字节数组 b 作为缓冲区。在 while 循环中,通过 fis.read(b) 从文件中读取数据到缓冲区 b 中,并将读取的字节数赋值给 length。只要读取的字节数大于 0,就表示读取到了数据,然后通过 os.write(b, 0, length) 将缓冲区 b 中从偏移量 0 开始长度为 length 的字节数据写入到输出流 os 中。

catch (IOException e)
        {
            throw e;
        }
        finally
        {
            IOUtils.close(os);
            IOUtils.close(fis);
        }

在 try - catch 块结束后,无论是否发生异常,都会执行 finally 块中的代码。这里使用 IOUtils . close 方法(推测 IOUtils 是一个工具类,提供了关闭流的方法)来关闭输出流 os 和文件输入流 fis,确保资源被正确释放,避免资源泄漏

2.7 判断是否删除

 if (delete)
            {
                FileUtils.deleteFile(filePath);
            }

如果delete参数为true,表示下载完成后需要删除服务器上的文件。调用FileUtils类的deleteFile方法删除指定路径(filePath)的文件。

/**
 * 删除文件
 * 
 * @param filePath 文件
 * @return
 */
public static boolean deleteFile(String filePath)
{
    boolean flag = false;
    File file = new File(filePath);
    // 路径为文件且不为空则进行删除
    if (file.isFile() && file.exists())
    {
        flag = file.delete();
    }
    return flag;
}

三:上传

3.1、 上传单个

/**
     * 通用上传请求(单个)
     */
    @PostMapping("/upload")
    public AjaxResult uploadFile(MultipartFile file) throws Exception
    {
        try
        {
            // 上传文件路径
            String filePath = RuoYiConfig.getUploadPath(); //D:/ruoyi/uploadPath/upload
            // 上传并返回新文件名称
            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());
        }
    }
3.1.1 FileUploadUtils 的 upload 方法
  			// 上传并返回新文件名称
            String fileName = FileUploadUtils.upload(filePath, file);
    /**
     * 根据文件路径上传
     *
     * @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);
        }
    }

允许的文件类型

    public static final String[] DEFAULT_ALLOWED_EXTENSION = {
            // 图片
            "bmp", "gif", "jpg", "jpeg", "png",
            // word excel powerpoint
            "doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt",
            // 压缩文件
            "rar", "zip", "gz", "bz2",
            // 视频格式
            "mp4", "avi", "rmvb",
            // pdf
            "pdf" };

这里调用 upload 方法 和 头像上传 的方法是一致的 (参考1.3.1)

3.1.2 上传文件 url
String url = serverConfig.getUrl() + fileName;
   /**
     * 获取完整的请求路径,包括:域名,端口,上下文访问路径
     * 
     * @return 服务地址
     */
    public String getUrl()
    {
        HttpServletRequest request = ServletUtils.getRequest();
        return getDomain(request);
    }

    public static String getDomain(HttpServletRequest request)
    {
        StringBuffer url = request.getRequestURL();
        String contextPath = request.getServletContext().getContextPath();
        return url.delete(url.length() - request.getRequestURI().length(), url.length()).append(contextPath).toString();
    }

request . getRequestURL() 方法返回一个 StringBuffer 对象,它包含了完整的请求 URL,例如 example.com:8080/app/resourc…

request.getServletContext() . getContextPath() 获取当前 Web 应用的上下文路径。例如,如果应用部署在 /app 下,那么上下文路径就是 /app;如果是根部署,上下文路径就是空字符串 " "

url.delete(url.length() - request.getRequestURI().length(), url.length()):这部分代码从完整的请求 URL 中删除请求 URI 部分。request.getRequestURI() 返回请求的资源路径部分,例如 /app/resource。通过删除这部分,就得到了包含协议和主机名的基础部分,例如 example.com:8080

.append(contextPath):将获取到的上下文路径追加到上述基础部分之后

.toString():将最终构建好的 StringBuffer 对象转换为字符串并返回。例如,如果上下文路径是 /app,最终返回的可能是 example.com:8080/app

3.1.3 返回 成功响应对象
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;
3.1.3.1 getName 方法 获取文件名称
    /**
     * 获取文件名称 /profile/upload/2022/04/16/ruoyi.png -- ruoyi.png
     * 
     * @param fileName 路径名称
     * @return 没有文件路径的名称
     */
    public static String getName(String fileName)
    {
        if (fileName == null)
        {
            return null;
        }
        int lastUnixPos = fileName.lastIndexOf('/');
        int lastWindowsPos = fileName.lastIndexOf('\\');
        int index = Math.max(lastUnixPos, lastWindowsPos);
        return fileName.substring(index + 1);
    }

首先检查传入的 fileName 是否为 null。如果是 null,直接返回 null,避免后续操作引发空指针异常。

分别查找字符串中最后一个 Unix 路径分隔符 / 和最后一个 Windows 路径分隔符 \ 的位置。lastIndexOf 方法返回指定字符在字符串中最后一次出现的位置,如果不存在则返回 -1

使用 Math.max 方法获取 lastUnixPos 和 lastWindowsPos 中的较大值。这是因为路径可能是 Unix 风格、Windows 风格,或者混合风格,通过取最大值可以确保获取到最后一个路径分隔符的位置

使用 substring 方法从 fileName 字符串中提取从 index + 1 位置开始到字符串末尾的子字符串,即文件名部分。例如,对于路径/profile/upload/2022/04/16/ruoyi.png,index 为最后一个/的位置,index + 1 就是文件名ruoyi.png的起始位置,提取后返回ruoyi.png

3.1.4 捕获异常,抛出异常信息
 catch (Exception e)
        {
            return AjaxResult.error(e.getMessage());
        }

3.2、 上传多个

/**
     * 通用上传请求(多个)
     */
    @PostMapping("/uploads")
    public AjaxResult uploadFiles(List<MultipartFile> files) throws Exception
    {
        try
        {
            // 上传文件路径
            String filePath = RuoYiConfig.getUploadPath();
            List<String> urls = new ArrayList<String>();
            List<String> fileNames = new ArrayList<String>();
            List<String> newFileNames = new ArrayList<String>();
            List<String> originalFilenames = new ArrayList<String>();
            for (MultipartFile file : files)
            {
                // 上传并返回新文件名称
                String fileName = FileUploadUtils.upload(filePath, file);
                String url = serverConfig.getUrl() + fileName;
                urls.add(url);
                fileNames.add(fileName);
                newFileNames.add(FileUtils.getName(fileName));
                originalFilenames.add(file.getOriginalFilename());
            }
            AjaxResult ajax = AjaxResult.success();
            ajax.put("urls", StringUtils.join(urls, FILE_DELIMETER));
            ajax.put("fileNames", StringUtils.join(fileNames, FILE_DELIMETER));
            ajax.put("newFileNames", StringUtils.join(newFileNames, FILE_DELIMETER));
            ajax.put("originalFilenames", StringUtils.join(originalFilenames, FILE_DELIMETER));
            return ajax;
        }
        catch (Exception e)
        {
            return AjaxResult.error(e.getMessage());
        }
    }

遍历循环处理

遍历 files 列表中的每个文件:
调用 FileUploadUtils.upload(filePath, file) 方法将文件上传到指定路径,并获取上传后的新文件名。
通过 serverConfig.getUrl() 获取服务器的 URL,与新文件名拼接成文件的完整访问 URL,并添加到 urls 列表中。
将新文件名添加到 fileNames 列表中。
调用 FileUtils.getName(fileName) 方法从新文件名中提取仅文件名部分(去除路径),添加到 newFileNames 列表中。
将文件的原文件名添加到 originalFilenames 列表中。