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 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 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; } }