145 lines
4.4 KiB
Java
145 lines
4.4 KiB
Java
package com.ruoyi.warehouse.util;
|
||
|
||
import java.io.InputStream;
|
||
import java.util.Iterator;
|
||
import java.util.Locale;
|
||
import java.util.Set;
|
||
import javax.imageio.ImageIO;
|
||
import javax.imageio.ImageReader;
|
||
import javax.imageio.stream.ImageInputStream;
|
||
import org.apache.commons.io.FilenameUtils;
|
||
import org.springframework.web.multipart.MultipartFile;
|
||
import com.ruoyi.common.exception.ServiceException;
|
||
import com.ruoyi.common.utils.StringUtils;
|
||
|
||
public final class WarehouseFileUtils
|
||
{
|
||
private static final long MAX_IMAGE_PIXELS = 40_000_000L;
|
||
|
||
private WarehouseFileUtils()
|
||
{
|
||
}
|
||
|
||
public static String validateUpload(MultipartFile file, long maxFileSize, Set<String> allowedExtensions)
|
||
{
|
||
if (file == null || file.isEmpty())
|
||
{
|
||
throw new ServiceException("请选择要上传的文件");
|
||
}
|
||
if (file.getSize() > maxFileSize)
|
||
{
|
||
throw new ServiceException("文件不能超过" + (maxFileSize / 1024 / 1024) + "MB");
|
||
}
|
||
String extension = StringUtils.defaultString(FilenameUtils.getExtension(file.getOriginalFilename()))
|
||
.toLowerCase(Locale.ROOT);
|
||
if (!allowedExtensions.contains(extension))
|
||
{
|
||
throw new ServiceException("不支持的文件类型");
|
||
}
|
||
try
|
||
{
|
||
byte[] header;
|
||
try (InputStream input = file.getInputStream())
|
||
{
|
||
header = input.readNBytes(12);
|
||
}
|
||
if ("xls".equals(extension) && !startsWith(header, 0xD0, 0xCF, 0x11, 0xE0))
|
||
{
|
||
throw new ServiceException("文件内容不是有效的Excel文件");
|
||
}
|
||
if ("xlsx".equals(extension) && !startsWith(header, 0x50, 0x4B))
|
||
{
|
||
throw new ServiceException("文件内容不是有效的Excel文件");
|
||
}
|
||
if (isImage(extension))
|
||
{
|
||
validateImage(file, extension, header);
|
||
}
|
||
return extension;
|
||
}
|
||
catch (ServiceException e)
|
||
{
|
||
throw e;
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
throw new ServiceException("无法读取上传文件,请确认文件未损坏");
|
||
}
|
||
}
|
||
|
||
private static void validateImage(MultipartFile file, String extension, byte[] header) throws Exception
|
||
{
|
||
if ("webp".equals(extension))
|
||
{
|
||
if (!matchesAscii(header, 0, "RIFF") || !matchesAscii(header, 8, "WEBP"))
|
||
{
|
||
throw new ServiceException("文件内容不是有效的WebP图片");
|
||
}
|
||
return;
|
||
}
|
||
try (ImageInputStream input = ImageIO.createImageInputStream(file.getInputStream()))
|
||
{
|
||
if (input == null)
|
||
{
|
||
throw new ServiceException("文件内容不是有效图片");
|
||
}
|
||
Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
|
||
if (!readers.hasNext())
|
||
{
|
||
throw new ServiceException("文件内容不是有效图片");
|
||
}
|
||
ImageReader reader = readers.next();
|
||
try
|
||
{
|
||
reader.setInput(input, true, true);
|
||
long pixels = (long) reader.getWidth(0) * reader.getHeight(0);
|
||
if (pixels <= 0 || pixels > MAX_IMAGE_PIXELS)
|
||
{
|
||
throw new ServiceException("图片尺寸过大,最大支持4000万像素");
|
||
}
|
||
}
|
||
finally
|
||
{
|
||
reader.dispose();
|
||
}
|
||
}
|
||
}
|
||
|
||
private static boolean isImage(String extension)
|
||
{
|
||
return Set.of("jpg", "jpeg", "png", "bmp", "webp").contains(extension);
|
||
}
|
||
|
||
private static boolean startsWith(byte[] source, int... prefix)
|
||
{
|
||
if (source.length < prefix.length)
|
||
{
|
||
return false;
|
||
}
|
||
for (int i = 0; i < prefix.length; i++)
|
||
{
|
||
if ((source[i] & 0xFF) != prefix[i])
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
private static boolean matchesAscii(byte[] source, int offset, String expected)
|
||
{
|
||
if (source.length < offset + expected.length())
|
||
{
|
||
return false;
|
||
}
|
||
for (int i = 0; i < expected.length(); i++)
|
||
{
|
||
if (source[offset + i] != (byte) expected.charAt(i))
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
}
|