文件格式识别更改
This commit is contained in:
@@ -55,7 +55,7 @@ public class TencentCosUploadController extends BaseController {
|
||||
String fileName = file.getOriginalFilename();
|
||||
InputStream inputStream = file.getInputStream();
|
||||
String fileUrl = uploadService.uploadByStream(inputStream, fileName);
|
||||
String fileType = FileTypeDetector.detectFileType(inputStream);
|
||||
String fileType = FileTypeDetector.detectFileType(inputStream, fileName);
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("url", fileUrl);
|
||||
jsonObject.put("fileName", fileName);
|
||||
|
@@ -8,232 +8,94 @@ import java.io.InputStream;
|
||||
@Slf4j
|
||||
public class FileTypeDetector {
|
||||
|
||||
public static String detectFileType(InputStream is) throws IOException {
|
||||
public static String detectFileType(InputStream inputStream, String fileName) throws IOException {
|
||||
// 读取文件前16字节(足够识别大多数格式)
|
||||
byte[] fileHeader = new byte[16];
|
||||
int read = inputStream.read(fileHeader, 0, fileHeader.length);
|
||||
|
||||
try {
|
||||
// 读取文件头部的字节(通常16-32字节足够)
|
||||
byte[] header = new byte[32];
|
||||
int bytesRead = is.read(header);
|
||||
// 转为十六进制字符串(大写)
|
||||
String header = bytesToHex(fileHeader).toUpperCase();
|
||||
|
||||
if (bytesRead < 4) {
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
// 根据魔数判断文件类型
|
||||
if (isPng(header)) {
|
||||
return "PNG";
|
||||
} else if (isJpeg(header)) {
|
||||
return "JPEG";
|
||||
} else if (isGif(header)) {
|
||||
return "GIF";
|
||||
} else if (isMp3(header)) {
|
||||
return "MP3";
|
||||
} else if (isMp4(header)) {
|
||||
return "MP4";
|
||||
} else if (isAvi(header)) {
|
||||
return "AVI";
|
||||
} else if (isWebP(header)) {
|
||||
return "WEBP";
|
||||
} else if (isBmp(header)) {
|
||||
return "BMP";
|
||||
} else if (isTiff(header)) {
|
||||
return "TIFF";
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("FileTypeDetector.detectFileType, e:{}", e.getMessage(), e);
|
||||
// 图像格式检测
|
||||
if (header.startsWith("FFD8FF")) {
|
||||
return "JPG";
|
||||
} else if (header.startsWith("89504E47")) {
|
||||
return "PNG";
|
||||
} else if (header.startsWith("47494638")) {
|
||||
return "GIF";
|
||||
} else if (header.startsWith("424D")) {
|
||||
return "BMP";
|
||||
} else if (header.startsWith("52494646") && header.contains("57454250")) {
|
||||
return "WEBP";
|
||||
} else if (header.startsWith("49492A00") || header.startsWith("4D4D002A")) {
|
||||
return "TIFF";
|
||||
} else if (isMp4(fileHeader)) {
|
||||
return "MP4";
|
||||
} else if (isAvi(fileHeader)) {
|
||||
return "AVI";
|
||||
}
|
||||
|
||||
String upperCase = fileName.toUpperCase();
|
||||
|
||||
if (upperCase.contains("JPG")) {
|
||||
return "JPG";
|
||||
} else if (upperCase.contains("PNG")) {
|
||||
return "PNG";
|
||||
} else if (upperCase.contains("GIF")) {
|
||||
return "GIF";
|
||||
} else if (upperCase.contains("BMP")) {
|
||||
return "BMP";
|
||||
} else if (upperCase.contains("WEBP")) {
|
||||
return "WEBP";
|
||||
} else if (upperCase.contains("TIFF")) {
|
||||
return "TIFF";
|
||||
} else if (upperCase.contains("MP4")) {
|
||||
return "MP4";
|
||||
} else if (upperCase.contains("AVI")) {
|
||||
return "AVI";
|
||||
}
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
private static boolean isPng(byte[] header) {
|
||||
return header[0] == (byte) 0x89 &&
|
||||
header[1] == 'P' &&
|
||||
header[2] == 'N' &&
|
||||
header[3] == 'G';
|
||||
}
|
||||
|
||||
private static boolean isJpeg(byte[] header) {
|
||||
return header[0] == (byte) 0xFF &&
|
||||
header[1] == (byte) 0xD8;
|
||||
}
|
||||
|
||||
private static boolean isGif(byte[] header) {
|
||||
return header[0] == 'G' &&
|
||||
header[1] == 'I' &&
|
||||
header[2] == 'F';
|
||||
}
|
||||
|
||||
private static boolean isPdf(byte[] header) {
|
||||
return header[0] == '%' &&
|
||||
header[1] == 'P' &&
|
||||
header[2] == 'D' &&
|
||||
header[3] == 'F';
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测是否为MP3文件
|
||||
* MP3文件可能有多种签名格式
|
||||
* @param header 文件头部字节数组
|
||||
* @return 如果是MP3文件返回true
|
||||
* 将字节数组转换为十六进制字符串
|
||||
*/
|
||||
private static boolean isMp3(byte[] header) {
|
||||
// 检查ID3标签 (ID3v2)
|
||||
if (header.length >= 3 &&
|
||||
header[0] == 'I' && header[1] == 'D' && header[2] == '3') {
|
||||
return true;
|
||||
private static String bytesToHex(byte[] bytes) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : bytes) {
|
||||
sb.append(String.format("%02x", b));
|
||||
}
|
||||
|
||||
// 检查MPEG帧头 (FF E0-FB)
|
||||
if (header.length >= 2 &&
|
||||
header[0] == (byte) 0xFF && (header[1] & 0xE0) == 0xE0) {
|
||||
// 检查有效的MPEG版本和层
|
||||
byte versionLayer = (byte) ((header[1] & 0x18) >>> 3);
|
||||
byte layer = (byte) ((header[1] & 0x06) >>> 1);
|
||||
return versionLayer != 1 && layer != 0;
|
||||
}
|
||||
|
||||
// 检查ID3v1标签 (文件末尾的"TAG")
|
||||
// 注意: 这个方法无法检测到ID3v1标签,因为它位于文件末尾
|
||||
|
||||
return false;
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测是否为MP4文件
|
||||
* MP4文件基于"ftyp"盒子识别
|
||||
* @param header 文件头部字节数组
|
||||
* @return 如果是MP4文件返回true
|
||||
* 检测MP4格式
|
||||
* MP4文件通常包含"ftyp"盒子(66 74 79 70)
|
||||
*/
|
||||
private static boolean isMp4(byte[] header) {
|
||||
// 需要至少12字节来检测
|
||||
if (header.length < 12) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查文件大小和"ftyp"标记
|
||||
// MP4文件通常以大小(4字节)和"ftyp"(4字节)开始
|
||||
// 但有些MP4文件可能在前面有额外的元数据
|
||||
|
||||
// 查找"ftyp"标记(66 74 79 70)
|
||||
// 检查可能的"ftyp"位置
|
||||
for (int i = 0; i <= header.length - 8; i++) {
|
||||
if (header[i] == 'f' && header[i+1] == 't' &&
|
||||
header[i+2] == 'y' && header[i+3] == 'p') {
|
||||
// 检查常见的MP4子类型
|
||||
if (i + 8 < header.length) {
|
||||
// 检查子类型(如"mp42", "isom", "avc1"等)
|
||||
String majorBrand = new String(header, i+4, 4);
|
||||
return majorBrand.equals("mp42") || majorBrand.equals("isom") ||
|
||||
majorBrand.equals("avc1") || majorBrand.equals("iso2") ||
|
||||
majorBrand.equals("M4V ") || majorBrand.equals("M4A ");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查一些MP4变体可能以"moov"开头
|
||||
if (header[4] == 'm' && header[5] == 'o' &&
|
||||
header[6] == 'o' && header[7] == 'v') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测是否为AVI格式文件
|
||||
* 检测AVI格式
|
||||
* AVI文件签名: RIFF....AVI
|
||||
* @param header 文件头部字节数组(至少需要12字节)
|
||||
* @return 如果是AVI文件返回true
|
||||
*/
|
||||
private static boolean isAvi(byte[] header) {
|
||||
// 最少需要12字节来识别AVI文件
|
||||
if (header.length < 12) {
|
||||
return false;
|
||||
}
|
||||
if (header.length < 12) return false;
|
||||
|
||||
// 检查RIFF签名(52 49 46 46)
|
||||
boolean isRiff = header[0] == 'R' &&
|
||||
header[1] == 'I' &&
|
||||
header[2] == 'F' &&
|
||||
header[3] == 'F';
|
||||
|
||||
// 检查AVI签名(41 56 49 20 或 41 56 49 58)
|
||||
boolean isAvi = (header[8] == 'A' &&
|
||||
header[9] == 'V' &&
|
||||
header[10] == 'I' &&
|
||||
(header[11] == ' ' || header[11] == 'X'));
|
||||
|
||||
return isRiff && isAvi;
|
||||
}
|
||||
/**
|
||||
* 检测是否为WebP格式文件
|
||||
* WebP签名: RIFF....WEBP
|
||||
* @param header 文件头部字节数组(至少需要12字节)
|
||||
* @return 如果是WebP文件返回true
|
||||
*/
|
||||
private static boolean isWebP(byte[] header) {
|
||||
// 最少需要12字节来识别WebP文件
|
||||
if (header.length < 12) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查RIFF签名(52 49 46 46)
|
||||
boolean isRiff = header[0] == 'R' &&
|
||||
header[1] == 'I' &&
|
||||
header[2] == 'F' &&
|
||||
header[3] == 'F';
|
||||
|
||||
// 检查WEBP签名(57 45 42 50)
|
||||
boolean isWebP = header[8] == 'W' &&
|
||||
header[9] == 'E' &&
|
||||
header[10] == 'B' &&
|
||||
header[11] == 'P';
|
||||
|
||||
return isRiff && isWebP;
|
||||
}
|
||||
/**
|
||||
* 检测是否为BMP格式文件
|
||||
* BMP签名: BM (42 4D)
|
||||
* @param header 文件头部字节数组(至少需要2字节)
|
||||
* @return 如果是BMP文件返回true
|
||||
*/
|
||||
private static boolean isBmp(byte[] header) {
|
||||
// 最少需要2字节来识别BMP文件
|
||||
if (header.length < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查BM签名(42 4D)
|
||||
return header[0] == 'B' && header[1] == 'M';
|
||||
// 检查RIFF签名(52 49 46 46)和AVI签名(41 56 49 20)
|
||||
return header[0] == 'R' && header[1] == 'I' &&
|
||||
header[2] == 'F' && header[3] == 'F' &&
|
||||
header[8] == 'A' && header[9] == 'V' &&
|
||||
header[10] == 'I' && header[11] == ' ';
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测是否为TIFF格式文件
|
||||
* TIFF签名:
|
||||
* 小端序: II 2A 00
|
||||
* 大端序: MM 00 2A
|
||||
* @param header 文件头部字节数组(至少需要4字节)
|
||||
* @return 如果是TIFF文件返回true
|
||||
*/
|
||||
private static boolean isTiff(byte[] header) {
|
||||
// 最少需要4字节来识别TIFF文件
|
||||
if (header.length < 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查小端序格式(II 2A 00)
|
||||
boolean isLittleEndian = header[0] == 'I' &&
|
||||
header[1] == 'I' &&
|
||||
header[2] == 0x2A &&
|
||||
header[3] == 0x00;
|
||||
|
||||
// 检查大端序格式(MM 00 2A)
|
||||
boolean isBigEndian = header[0] == 'M' &&
|
||||
header[1] == 'M' &&
|
||||
header[2] == 0x00 &&
|
||||
header[3] == 0x2A;
|
||||
|
||||
return isLittleEndian || isBigEndian;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user