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格式图包");
+ }
+ }
+
+}