最新项目中有遇到 对于上传文件类型的判断 我们需要上传的文件是 excel类型的
原来我的做法很简单 就是 获取文件名然后去判断 后缀名是否是 .xls 或者是 .xlsx 类型
//判断文件类型是否正确
String originalFilename = file.getOriginalFilename();
String fileType = originalFilename.substring(originalFilename.lastIndexOf("."));
if (!".xls".equalsIgnoreCase(fileType) && !".xlsx".equalsIgnoreCase(fileType)) {
return BaseResultUtil.resultError("文件格式不对");
}
但是这种判断是不严谨的 假如有人恶意攻击 将恶心文件的后缀名改成 xls/xlsx 就能轻易的进行next....所有需要进一步的判断
下面我们可以借助 Hutool工具类的 FileTypeUtil 来实现 这个的原理就是读取文件首部几个二进制位来判断常用的文件类型
废话不多说 先摆上我的最终实现 1、先判断后缀名 2、 然后通过 FileTypeUtil 来判断 符合后缀名的文件是否是 对于的文件类型
private static final String FILE_ZIP="zip";
private static final String FILE_XLS=".xls";
private static final String FILE_XLSX=".xlsx";
public boolean checkFileType(MultipartFile file) {
String filename = file.getOriginalFilename();
String fileType = filename.substring(filename.lastIndexOf("."));
//先判断后缀名
if (FILE_XLS.equalsIgnoreCase(fileType) || FILE_XLSX.equalsIgnoreCase(fileType)) {
String type ;
try {
type = FileTypeUtil.getType(file.getInputStream());
//根据首部字节判断文件类型
if (FILE_ZIP.contains(type)||FILE_XLS.contains(type)){
return true;
}
} catch (Exception e) {
log.error("上传工作状态excel失败", e);
}
}
return false;
}
FileTypeUtil文件的简单介绍 ----参考Hutool 工具类的官方文档
https://hutool.cn/docs/#/core/IO/%E6%96%87%E4%BB%B6%E7%B1%BB%E5%9E%8B%E5%88%A4%E6%96%AD-FileTypeUtil
为什么 会用到 zip 类型 下面也有介绍 xlsx、docx本质上是各种XML打包为zip的结果,因此会被识别为zip格式。
使用
这个工具类使用非常简单,通过调用FileTypeUtil.getType
即可判断,这个方法同时提供众多的重载方法,用于读取不同的文件和流。
File file = FileUtil.file("d:/test.jpg");
String type = FileTypeUtil.getType(file);
//输出 jpg则说明确实为jpg文件
Console.log(type);
Copy to clipboardErrorCopied
原理和局限性
这个类是通过读取文件流中前N个byte值来判断文件类型,在类中我们通过Map形式将常用的文件类型做了映射,这些映射都是网络上搜集而来。也就是说,我们只能识别有限的几种文件类型。但是这些类型已经涵盖了常用的图片、音频、视频、Office文档类型,可以应对大部分的使用场景。
对于某些文本格式的文件我们并不能通过首部byte判断其类型,比如
JSON
,这类文件本质上是文本文件,我们应该读取其文本内容,通过其语法判断类型。
自定义类型
为了提高FileTypeUtil
的扩展性,我们通过putFileType
方法可以自定义文件类型。
FileTypeUtil.putFileType("ffd8ffe000104a464946", "new_jpg");
Copy to clipboardErrorCopied
第一个参数是文件流的前N个byte的16进制表示,我们可以读取自定义文件查看,选取一定长度即可(长度越长越精确),第二个参数就是文件类型,然后使用FileTypeUtil.getType
即可。
注意 xlsx、docx本质上是各种XML打包为zip的结果,因此会被识别为zip格式。
部分源码
// MS Excel 注意:word、msi 和 excel的文件头一样
fileTypeMap.put("d0cf11e0a1b11ae10", "xls");
fileTypeMap.put("504B0304", "zip");
/**
* 根据文件流的头部信息获得文件类型
*
* <pre>
* 1、无法识别类型默认按照扩展名识别
* 2、xls、doc、msi头信息无法区分,按照扩展名区分
* 3、zip可能为docx、xlsx、pptx、jar、war头信息无法区分,按照扩展名区分
* </pre>
*
* @param file 文件 {@link File}
* @return 类型,文件的扩展名,未找到为<code>null</code>
* @throws IORuntimeException 读取文件引起的异常
*/
public static String getType(File file) throws IORuntimeException {
String typeName;
FileInputStream in = null;
try {
in = IoUtil.toStream(file);
typeName = getType(in);
} finally {
IoUtil.close(in);
}
if (null == typeName) {
// 未成功识别类型,扩展名辅助识别
typeName = FileUtil.extName(file);
} else if ("xls".equals(typeName)) {
// xls、doc、msi的头一样,使用扩展名辅助判断
final String extName = FileUtil.extName(file);
if ("doc".equalsIgnoreCase(extName)) {
typeName = "doc";
} else if ("msi".equalsIgnoreCase(extName)) {
typeName = "msi";
}
} else if ("zip".equals(typeName)) {
// zip可能为docx、xlsx、pptx、jar、war等格式,扩展名辅助判断
final String extName = FileUtil.extName(file);
if ("docx".equalsIgnoreCase(extName)) {
typeName = "docx";
} else if ("xlsx".equalsIgnoreCase(extName)) {
typeName = "xlsx";
} else if ("pptx".equalsIgnoreCase(extName)) {
typeName = "pptx";
} else if ("jar".equalsIgnoreCase(extName)) {
typeName = "jar";
} else if ("war".equalsIgnoreCase(extName)) {
typeName = "war";
}
}
return typeName;
}