@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",
"doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt",
"rar", "zip", "gz", "bz2",
"mp4", "avi", "rmvb",
"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
filePath
中 RuoYiConfig.getDownloadPath()
是从配置文件中 getProfile() + "/download/"
这里不是上传的 /upload/
目录了 所以测试下刚上传的文件立即是下载不下来的 得新建一个 /download/
把 /upload/
复制进去就可以测试下载了
最后判断是否需要删除再对服务器上保存的文件进行删除