diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/ImgExtUtil.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ImgExtUtil.java new file mode 100644 index 000000000..9cf262485 --- /dev/null +++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/ImgExtUtil.java @@ -0,0 +1,85 @@ +package com.ruoyi.common.utils; + +import cn.hutool.core.img.ImgUtil; +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.lang.Assert; +import lombok.extern.slf4j.Slf4j; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.*; + +/** + * @author liangyq + * @date 2025-03-26 09:24 + */ +@Slf4j +public class ImgExtUtil extends ImgUtil { + + private static final byte[] JPEG_HEADER = new byte[]{(byte) 0xFF, (byte) 0xD8, (byte) 0xFF}; + private static final byte[] PNG_HEADER = new byte[]{(byte) 0x89, 0x50, 0x4E, 0x47}; + private static final byte[] GIF_HEADER = new byte[]{0x47, 0x49, 0x46, 0x38}; + private static final byte[] BMP_HEADER = new byte[]{0x42, 0x4D}; + + /** + * 缩放图像(按高度和宽度缩放)
+ * 缩放后默认为jpeg格式,此方法并不关闭流 + * + * @param image 图像 + * @param destStream 缩放后的图像目标流 + * @param width 缩放后的宽度 + * @param height 缩放后的高度 + * @param fixedColor 比例不对时补充的颜色,不补充为{@code null} + * @throws IORuntimeException IO异常 + */ + public static void scale(BufferedImage image, OutputStream destStream, int width, int height, Color fixedColor) throws IORuntimeException { + Assert.notNull(image, "图片不能为空"); + try { + scale(image, getImageOutputStream(destStream), width, height, fixedColor); + } finally { + flush(image); + } + } + + /** + * 判断文件是否是图片(jpeg、png、bmp、gif) + * + * @param file + * @return + * @throws IOException + */ + public static boolean isImageByMagicNumber(File file) { + if (file == null || !file.exists() || file.isDirectory()) { + return false; + } + byte[] header = new byte[4]; + try (InputStream is = new FileInputStream(file)) { + int readBytes = is.read(header); + if (readBytes == -1) return false; + } catch (Exception e) { + log.error("判断文件是否图片失败", e); + return false; + } + // 常见图片格式的魔数 + if (bytesStartWith(header, JPEG_HEADER)) { // JPEG + return true; + } else if (bytesStartWith(header, PNG_HEADER)) { // PNG + return true; + } else if (bytesStartWith(header, GIF_HEADER)) { // GIF + return true; + } else if (bytesStartWith(header, BMP_HEADER)) { // BMP + return true; + } + return false; + } + + private static boolean bytesStartWith(byte[] source, byte[] target) { + if (source.length < target.length) return false; + for (int i = 0; i < target.length; i++) { + if (source[i] != target[i]) return false; + } + return true; + } + + +} diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/oss/OSSClientWrapper.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/oss/OSSClientWrapper.java index ff45504d7..46cb17647 100644 --- a/ruoyi-framework/src/main/java/com/ruoyi/framework/oss/OSSClientWrapper.java +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/oss/OSSClientWrapper.java @@ -250,7 +250,7 @@ public class OSSClientWrapper { /** * 下载文件 */ - public void download(OSSClient client, String key, String filename) + public void download(String key, String filename) throws OSSException, ClientException { client.getObject(new GetObjectRequest(configuration.getBucketName(), key), new File(filename)); } diff --git a/xkt/src/main/java/com/ruoyi/xkt/dto/picture/DownloadResultDTO.java b/xkt/src/main/java/com/ruoyi/xkt/dto/picture/DownloadResultDTO.java new file mode 100644 index 000000000..500d6d99b --- /dev/null +++ b/xkt/src/main/java/com/ruoyi/xkt/dto/picture/DownloadResultDTO.java @@ -0,0 +1,25 @@ +package com.ruoyi.xkt.dto.picture; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author liangyq + * @date 2025-03-26 15:51 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class DownloadResultDTO { + + private String fileName; + + private String filePath; + + private String tempDirName; + + private String tempDirPath; +} diff --git a/xkt/src/main/java/com/ruoyi/xkt/dto/picture/PicZipDTO.java b/xkt/src/main/java/com/ruoyi/xkt/dto/picture/PicZipDTO.java new file mode 100644 index 000000000..173e12154 --- /dev/null +++ b/xkt/src/main/java/com/ruoyi/xkt/dto/picture/PicZipDTO.java @@ -0,0 +1,27 @@ +package com.ruoyi.xkt.dto.picture; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author liangyq + * @date 2025-03-26 16:35 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class PicZipDTO { + /** + * 原始图包key + */ + private String key; + /** + * 450图包key + */ + private String keyOf450; + /** + * 750图包key + */ + private String keyOf750; +} diff --git a/xkt/src/main/java/com/ruoyi/xkt/service/IPictureService.java b/xkt/src/main/java/com/ruoyi/xkt/service/IPictureService.java new file mode 100644 index 000000000..4a2f4f89e --- /dev/null +++ b/xkt/src/main/java/com/ruoyi/xkt/service/IPictureService.java @@ -0,0 +1,18 @@ +package com.ruoyi.xkt.service; + +import com.ruoyi.xkt.dto.picture.PicZipDTO; + +/** + * @author liangyq + * @date 2025-03-26 15:42 + */ +public interface IPictureService { + + /** + * 处理图包 + * @param key + * @return + */ + PicZipDTO processPicZip(String key); + +} diff --git a/xkt/src/main/java/com/ruoyi/xkt/service/impl/PictureServiceImpl.java b/xkt/src/main/java/com/ruoyi/xkt/service/impl/PictureServiceImpl.java new file mode 100644 index 000000000..0ad1edc19 --- /dev/null +++ b/xkt/src/main/java/com/ruoyi/xkt/service/impl/PictureServiceImpl.java @@ -0,0 +1,175 @@ +package com.ruoyi.xkt.service.impl; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.file.FileNameUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.NumberUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.core.util.ZipUtil; +import com.ruoyi.common.core.text.CharsetKit; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.ImgExtUtil; +import com.ruoyi.framework.config.properties.OSSProperties; +import com.ruoyi.framework.oss.OSSClientWrapper; +import com.ruoyi.xkt.dto.picture.DownloadResultDTO; +import com.ruoyi.xkt.dto.picture.PicZipDTO; +import com.ruoyi.xkt.service.IPictureService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.util.List; + +/** + * @author liangyq + * @date 2025-03-26 15:43 + */ +@Slf4j +@Service +public class PictureServiceImpl implements IPictureService { + + @Autowired + private OSSClientWrapper ossClient; + @Autowired + private OSSProperties ossProperties; + + @Override + public PicZipDTO processPicZip(String key) { + checkProcessAccess(key); + //下载至系统临时文件夹 + DownloadResultDTO downloadResult = downloadFile2TempDir(key); + String tempDirPath = downloadResult.getTempDirPath(); + try { + String midDirName = "unzip_" + downloadResult.getTempDirName(); + //解压 + File unzipFile = unzip(downloadResult.getFilePath(), tempDirPath + midDirName); + List allFiles = FileUtil.loopFiles(unzipFile); + for (File file : allFiles) { + if (file == null || !file.exists() || file.isDirectory()) { + continue; + } + //处理图片 + String new450Path = file.getPath().replace(midDirName, "450"); + String new750Path = file.getPath().replace(midDirName, "750"); + if (ImgExtUtil.isImageByMagicNumber(file)) { + FileUtil.mkParentDirs(new450Path); + FileUtil.mkParentDirs(new750Path); + //450p图片 + try (InputStream is = new FileInputStream(file); + FileOutputStream fos = new FileOutputStream(new450Path)) { + BufferedImage in = ImageIO.read(is); + int height = in.getHeight(); + int weight = in.getWidth(); + int neww = 450; + int newh = (int) (NumberUtil.div(height, weight, 5) * neww); + ImgExtUtil.scale(in, fos, neww, newh, null); + } + //750p图片 + try (InputStream is = new FileInputStream(file); + FileOutputStream fos = new FileOutputStream(new750Path)) { + BufferedImage in = ImageIO.read(is); + int height = in.getHeight(); + int weight = in.getWidth(); + int neww = 750; + int newh = (int) (NumberUtil.div(height, weight, 5) * neww); + ImgExtUtil.scale(in, fos, neww, newh, null); + } + } else { + //非图片 + FileUtil.copy(file, new File(new450Path), true); + FileUtil.copy(file, new File(new750Path), true); + } + } + //打包 + String nameOf450 = FileNameUtil.getPrefix(downloadResult.getFileName()) + "_450.zip"; + String pathOf450 = tempDirPath + nameOf450; + String nameOf750 = FileNameUtil.getPrefix(downloadResult.getFileName()) + "_750.zip"; + String pathOf750 = tempDirPath + nameOf750; + ZipUtil.zip(tempDirPath + "450", pathOf450, CharsetKit.CHARSET_GBK, false); + ZipUtil.zip(tempDirPath + "750", pathOf750, CharsetKit.CHARSET_GBK, false); + //上传 + String keyOf450 = StrUtil.replaceLast(key, downloadResult.getFileName(), nameOf450); + String keyOf750 = StrUtil.replaceLast(key, downloadResult.getFileName(), nameOf750); + try (InputStream is = new FileInputStream(pathOf450)) { + ossClient.upload(keyOf450, is); + } + try (InputStream is = new FileInputStream(pathOf750)) { + ossClient.upload(keyOf750, is); + } + return new PicZipDTO(key, keyOf450, keyOf750); + } catch (Exception e) { + log.error("图片处理异常", e); + throw new ServiceException("图片处理失败"); + } finally { + //删除临时文件夹 + FileUtil.del(tempDirPath); + } + } + + /** + * 解压文件 + * TODO 仅支持zip + * + * @param source + * @param target + * @return + */ + private File unzip(String source, String target) { + Assert.notEmpty(source); + Assert.notEmpty(target); + File file = null; + try { + file = ZipUtil.unzip(source, target, CharsetKit.CHARSET_GBK); + } catch (IllegalArgumentException e) { + if (e.getMessage().equals("MALFORMED")) { + //尝试用UTF8编码 + file = ZipUtil.unzip(source, target, CharsetKit.CHARSET_UTF_8); + } + } + return file; + } + + /** + * OSS下载文件到系统临时文件夹 + * + * @param ossKey + * @return + */ + private DownloadResultDTO downloadFile2TempDir(String ossKey) { + Assert.notEmpty("ossKey不能为空"); + try { + String baseTempDir = ossProperties.getTempDir(); + String tempDirName = IdUtil.fastSimpleUUID(); + String tempDirPath = baseTempDir + tempDirName + "/"; + //创建临时文件夹 + FileUtil.mkdir(tempDirPath); + String fileName = FileNameUtil.getName(ossKey); + String filePath = tempDirPath + fileName; + ossClient.download(ossKey, filePath); + return DownloadResultDTO + .builder() + .fileName(fileName) + .filePath(filePath) + .tempDirName(tempDirName) + .tempDirPath(tempDirPath) + .build(); + } catch (Exception e) { + log.error("文件下载异常", e); + throw new ServiceException("文件下载失败"); + } + } + + private void checkProcessAccess(String key) { + if (!"zip".equals(FileNameUtil.getSuffix(key))) { + throw new IllegalArgumentException("非zip格式图包"); + } + } + +}