添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

项目地址:https://gitee.com/y_project/RuoYi-Vue

文件上传下载 package com.ruoyi.web.controller.common;

通用下载请求

@RestController
@RequestMapping("/common")
public class CommonController
     * 通用下载请求
     * @param fileName 文件名称
     * @param delete 是否删除
    @GetMapping("/download")
    public void fileDownload(String fileName, Boolean delete, HttpServletResponse response, HttpServletRequest request)
            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);
public class FileUtils
    * 检查文件是否可下载
    * @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 t rue;
        // 不在允许下载的文件规则
        return false;
MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION:
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" };
public static String getFileType(String fileName)
    int separatorIndex = fileName.lastIndexOf(".");
    if (separatorIndex < 0)
        return "";
    return fileName.substring(separatorIndex + 1).toLowerCase();

要先通过 文件名称合法性之后 才能下载 这里只是单纯对参数进行检查 获取文件类型的方法也是若依框架自己写的

checkAllowDownload内做了路径安全的判断,且只能是 MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION内的类型才能下载

否则就抛出一个异常 StringUtils.format其实只是简单将占位符 { } 按照顺序替换为参数

* 格式化字符串<br> * 此方法只是简单将占位符 {} 按照顺序替换为参数<br> * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可<br> * 例:<br> * 通常使用:format("this is {} for {}", "a", "b") -> this is a for b<br> * 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a<br> * 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b<br> * @param strPattern 字符串模板 * @param argArray 参数列表 * @return 结果 public static String format(final String strPattern, final Object... argArray) if (StringUtils.isEmpty(strPattern) || StringUtils.isEmpty(argArray)) return strPattern; final int strPatternLength = strPattern.length(); // 初始化定义好的长度以获得更好的性能 StringBuilder sbuf = new StringBuilder(strPatternLength + 50); int handledPosition = 0; int delimIndex;// 占位符所在位置 for (int argIndex = 0; argIndex < argArray.length; argIndex++) delimIndex = strPattern.indexOf(EMPTY_JSON, handledPosition); if (delimIndex == -1) if (handledPosition == 0) return strPattern; { // 字符串模板剩余部分不再包含占位符,加入剩余部分后返回结果 sbuf.append(strPattern, handledPosition, strPatternLength); return sbuf.toString(); if (delimIndex > 0 && strPattern.charAt(delimIndex - 1) == C_BACKSLASH) if (delimIndex > 1 && strPattern.charAt(delimIndex - 2) == C_BACKSLASH) // 转义符之前还有一个转义符,占位符依旧有效 sbuf.append(strPattern, handledPosition, delimIndex - 1); sbuf.append(Convert.utf8Str(argArray[argIndex])); handledPosition = delimIndex + 2; // 占位符被转义 argIndex--; sbuf.append(strPattern, handledPosition, delimIndex - 1); sbuf.append(C_DELIM_START); handledPosition = delimIndex + 1; // 正常占位符 sbuf.append(strPattern, handledPosition, delimIndex); sbuf.append(Convert.utf8Str(argArray[argIndex])); handledPosition = delimIndex + 2; // 加入最后一个占位符后所有的字符 sbuf.append(strPattern, handledPosition, strPattern.length()); return sbuf.toString();

假若文件类型通过判断 再设置文件返回前端时新的命名和响应头以及写入二进制字符流

realFileName是 系统当前从1970到现在总的毫秒数 加上 字符串中从第一次出现 ‘_’ 的位置一直到最后的截取 这是因为上传的时候会改名成 2022/07/07/若依环境使用手册_20220707211840A002.docx的格式 结果为 165720082723620220707211840A002.docx

filePathRuoYiConfig.getDownloadPath()是从配置文件中 getProfile() + "/download/"

这里不是上传的 /upload/目录了 所以测试下刚上传的文件立即是下载不下来的 得新建一个 /download//upload/复制进去就可以测试下载了

最后判断是否需要删除再对服务器上保存的文件进行删除

文件上传(单个)

@PostMapping("/upload")
public AjaxResult uploadFile(MultipartFile file) throws Exception
        // 上传文件路径
        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());

和下载一样先通过配置文件拼接出来文件上传路径

* 文件上传 * @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);

文件上传的具体逻辑是先判断文件名是否超过长度 再对他进行文件大小和类型校验 String[] allowedExtension内存放的是允许的文件后缀

* 文件大小校验 * @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); throw new InvalidExtensionException(allowedExtension, extension, fileName); * 获取文件名的后缀 * @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;

以上检验都没有问题之后再对文件的名称进行重命名

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 String datePath()
    Date now = new Date();
    return DateFormatUtils.format(now, "yyyy/MM/dd");

这里 StringUtils.format就是对 {}进行替换 分别替换为 当前时间、文件原名、序列号、后缀名

最后就是进行文件存储了 第一步先拼接出来文件存储的文件目录 比如 2022/07/07/若依环境使用手册_20220707211840A002.docx他会在 /upload/判断有没有 /2022/、 /07/、 /07三个目录没有就新建 然后存放目标文件

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是系统文件文件的分割符为了方便,它被表示为一个字符串。在不同系统会有不同的显示。

第二步就是把文件输出到目标地址 file.transferTo(Paths.get(absPath));这里 transferTo的参数 不能是相对目录只能是绝对目录

最后返回给前端的 fileName注释写的是新文件名称 实际上经过为的测试是类似于 /profile/upload/2022/07/07/upupup_20220707182757A001.txt的返回值

url是拼接而成的 结果为: http://127.0.0.1:8080/profile/upload/2022/07/07/若依环境使用手册_20220707195630A001.docx

String url = serverConfig.getUrl() + fileName;
public class ServerConfig
     * 获取完整的请求路径,包括:域名,端口,上下文访问路径
     * @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() 返回全路径
request.getRequestURI() 返回除去host(域名或者ip)部分的路径
request.getContextPath() 返回工程名部分,如果工程映射为/,此处返回则为空
request.getServletPath() 返回除去host和工程名部分的路径
此处是运行结果
request.getRequestURL():http://127.0.0.1:8080/common/upload
request.getRequestURI():/common/upload
request.getContextPath():空
request.getServletPath():/common/upload
--- https://blog.csdn.net/weixin_44226263/article/details/104796650
* 通用上传请求(多个)
@PostMapping("/uploads")
public AjaxResult uploadFiles(List<MultipartFile> files) throws Exception
        // 上传文件路径
        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());

批量上传就是用一个for循环一个一个上传最后插入一个列表返回给前端

版权声明:本文为博主原创文章,遵循
CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://blog.csdn.net/weixin_43677689/article/details/125667825 RuoYi-Vue集成cas 集成步骤一、后端配置1、添加cas依赖2、修改配置文件3、修改LoginUser.java4、修改Constants.java5、添加 CasProperties.java6、添加CasUserDetailsService.java7、添加CasAuthenticationSuccessHandler.java8、修改

从零开始搭建一个项目最重要的是选择一个自己熟悉的框架,此项目使用Springboot框架来构建后端结构,使用vue来构建前端页面。数据层我们常用的是Mybatis,这里我大部分使用了Mybatis-plus简化配置,在涉及到多表联合查询的时候使用了Mybatis。登录功能使用的单点登录,使用jwt作为我们的用户身份验证。引入了

学习资源来自于B站UP,up他讲的非常详细,对于熟悉两大框架很有用。 我的作业源代码在文章末尾,欢迎有需要的同学,学习参考使用,内置SQL文件,导入后,开启springboot和vue服务即可使用,注意更改自己的数据库信息配置,一起学习,一起进步哦!! 一、所使用的环境配置: 编译器:IDEA 后台框架:SpringBoot Mybati

前言一、环境准备二、项目打包1.项目准备2.后端打包3.前端打包三、项目部署1.后端项目部署2.前端项目部署四、遇到的问题 本文参考视频实践若依的前后端分离项目的部署运行,记录整理一下自己的整个部署运行过程和遇到的问题,小白入门~ 视频在此: https://www.bilibili.com/video/BV1uK411p7B

  • springboot整合dubbo、redis和ssm_李振华的博客的博客-爱代码爱编程
  • 暑假 springboot学习 简单的helloworld程序_名泽的博客-爱代码爱编程
  • springboot+vue项目部署到wsl2的ubuntu上,并可通过公网访问_dddexter的博客-爱代码爱编程
  • springboot原理篇:bean的多种加载方法之 后处理机制beandefinitionregistrypostprocessor、加载顺序比较_梨轻巧的博客-爱代码爱编程
  • 关于spring中使用责任链模式_喝酸奶要舔盖儿的博客-爱代码爱编程
  • springboot启动代码和自动装配源码分析_yuan_mr_的博客-爱代码爱编程
  • [acwing-springboot] 2.配置git环境与项目创建_gzmobject的博客-爱代码爱编程
  • springboot_vue实现电影院售票系统_xiaocao1223的博客-爱代码爱编程
  • idea(spring boot)热部署_云端听风声的博客-爱代码爱编程
  • 基于springboot搭建java项目(五)——第一个springboot项目_dreamer_0423的博客-爱代码爱编程
  • java调用harbor v2.0接口_坚持奋斗的李洛克的博客-爱代码爱编程
  • i/o模型_伏案追剧的博客-爱代码爱编程
  • 【mybatis系列】核心类及其作用_sh!yu的博客-爱代码爱编程
  • 子父类中静态代码块、普通代码块、无参构造函数的执行顺序_落日余晖~xxh的博客-爱代码爱编程
  • redis 高可用原理_java廖廖的博客-爱代码爱编程
  • java流程控制01:scanner_liangxt1997的博客-爱代码爱编程
  •