zxl
19 小时以前 b85542765637358cb1473629d0ea767ac076aec3
图片压缩
1个文件已修改
1个文件已添加
293 ■■■■■ 已修改文件
ycl-server/pom.xml 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ycl-server/src/main/java/com/ycl/task/ImageCompressTask.java 287 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ycl-server/pom.xml
@@ -30,7 +30,11 @@
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
        </dependency>
        <dependency>
            <groupId>net.coobird</groupId>
            <artifactId>thumbnailator</artifactId>
            <version>0.4.14</version>
        </dependency>
        <!-- 代码生成-->
        <dependency>
ycl-server/src/main/java/com/ycl/task/ImageCompressTask.java
New file
@@ -0,0 +1,287 @@
package com.ycl.task;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
 * 图片压缩任务(增强版:支持PNG尺寸压缩)
 * 功能:处理D:/testImage/目录下所有图片,用压缩逻辑压缩后替换原文件
 * 新增:PNG图片按最大宽度/高度限制进行尺寸压缩
 * @author : zxl
 */
@Slf4j
@RequiredArgsConstructor
@Component("imageCompressTask")
public class ImageCompressTask {
    // 本地图片目录(固定为D盘testImage)
    private static final String BASE_DIRECTORY = "/opt/zgyw/uploadPath/";
    // 你定义的支持图片格式集合(完全保留)
    private static final Set<String> IMAGE_FORMATS = new HashSet<>(Arrays.asList(
            "jpg", "jpeg", "png", "gif", "bmp", "webp", "svg", "tiff"
    ));
    // PNG尺寸压缩配置
    private static final int PNG_MAX_WIDTH = 1920;  // PNG最大宽度
    private static final int PNG_MAX_HEIGHT = 1080; // PNG最大高度
    private static final boolean PNG_SCALE_ENABLED = true; // 是否启用PNG尺寸压缩
    private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    /**
     * 获取昨天日期的目录路径
     * @return 完整的目录路径,如:/opt/zgyw/uploadPath/2025-09-21
     */
    private String getYesterdayDirectory() {
        LocalDate yesterday = LocalDate.now().minusDays(1);
        String yesterdayStr = yesterday.format(formatter);
        return BASE_DIRECTORY + yesterdayStr;
    }
    /**
     * 主函数:单函数整合本地目录读写+你的压缩逻辑
     */
    public void processLocalImageDirectory() {
        String imageDirectory = getYesterdayDirectory();
        log.info("====== 开始处理本地图片目录:{} ======", imageDirectory);
        log.info("PNG尺寸压缩配置:最大宽度{}px,最大高度{}px,启用状态:{}",
                PNG_MAX_WIDTH, PNG_MAX_HEIGHT, PNG_SCALE_ENABLED);
        // 1. 检查目录有效性
        File imageDir = new File(imageDirectory);
        if (!imageDir.exists() || !imageDir.isDirectory()) {
            log.error("目录不存在或无效:{}", imageDirectory);
            return;
        }
        // 2. 读取目录下所有文件(排除子目录)
        File[] files = imageDir.listFiles(file -> !file.isDirectory());
        if (files == null || files.length == 0) {
            log.info("目录下无文件可处理");
            return;
        }
        // 3. 遍历处理每个本地文件
        int successCount = 0;
        int failCount = 0;
        int skipCount = 0;
        for (File localFile : files) {
            String fileName = localFile.getName();
            String formatName = getFileExtension(fileName);
            if (!isImageFormat(formatName)) {
                log.debug("跳过非图片文件:{}", fileName);
                skipCount++;
                continue;
            }
            try {
                compressAndReplaceLocalFile(localFile, formatName);
                successCount++;
            } catch (Exception e) {
                log.error("压缩图片 {} 失败", fileName, e);
                failCount++;
            }
        }
        // 输出处理结果
        log.info("====== 图片处理完成 ======");
        log.info("统计:成功{}个 | 失败{}个 | 跳过{}个 | 总计{}个",
                successCount, failCount, skipCount, files.length);
    }
    /**
     * 增强版压缩逻辑:支持PNG尺寸压缩 + JPG质量压缩
     */
    private void compressAndReplaceLocalFile(File localFile, String formatName) throws IOException {
        InputStream inputStream = null;
        ByteArrayOutputStream outputStream = null;
        File tempFile = null;
        try {
            inputStream = new FileInputStream(localFile);
            long originalSize = localFile.length();
            // ====================== 增强的压缩逻辑 ======================
            BufferedImage originalImage = ImageIO.read(inputStream);
            if (originalImage == null) {
                throw new IOException("无法读取图片文件:" + localFile.getName());
            }
            outputStream = new ByteArrayOutputStream();
            // PNG特殊处理:尺寸压缩
            if ("png".equalsIgnoreCase(formatName) && PNG_SCALE_ENABLED) {
                BufferedImage scaledImage = scalePngImage(originalImage);
                if (scaledImage != null) {
                    // 使用PNG格式写入缩放后的图片
                    ImageIO.write(scaledImage, "png", outputStream);
                    log.debug("PNG尺寸压缩完成:{}x{} → {}x{}",
                            originalImage.getWidth(), originalImage.getHeight(),
                            scaledImage.getWidth(), scaledImage.getHeight());
                } else {
                    // 缩放失败,使用原图
                    ImageIO.write(originalImage, "png", outputStream);
                }
            }
            // 其他格式:使用原有的质量压缩逻辑
            else {
                Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(formatName);
                if (!writers.hasNext()) {
                    throw new IllegalArgumentException("不支持的图片格式: " + formatName);
                }
                ImageWriter writer = writers.next();
                ImageWriteParam param = writer.getDefaultWriteParam();
                if (param.canWriteCompressed()) {
                    param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
                    param.setCompressionQuality(0.1F);
                }
                writer.setOutput(ImageIO.createImageOutputStream(outputStream));
                writer.write(null, new IIOImage(originalImage, null, null), param);
                writer.dispose();
            }
            inputStream.close();
            inputStream = new ByteArrayInputStream(outputStream.toByteArray());
            // ====================== 压缩逻辑结束 ======================
            // 写入临时文件并替换原文件
            tempFile = File.createTempFile("compressed_", "." + formatName);
            try (OutputStream tempOs = new FileOutputStream(tempFile)) {
                byte[] buffer = new byte[1024];
                int len;
                while ((len = inputStream.read(buffer)) != -1) {
                    tempOs.write(buffer, 0, len);
                }
            }
            long compressedSize = tempFile.length();
            if (compressedSize > 0) {
                if (compressedSize < originalSize) {
                    if (localFile.delete()) {
                        if (tempFile.renameTo(localFile)) {
                            log.info("压缩成功:{}(原大小:{}KB → 压缩后:{}KB,减少:{}%)",
                                    localFile.getName(),
                                    originalSize / 1024,
                                    compressedSize / 1024,
                                    String.format("%.1f", (1 - (double)compressedSize / originalSize) * 100));
                        } else {
                            throw new IOException("替换原文件失败");
                        }
                    } else {
                        throw new IOException("删除原文件失败(可能被占用)");
                    }
                } else {
                    log.warn("压缩无效(体积未减小):{}(原大小:{}KB,压缩后:{}KB)",
                            localFile.getName(), originalSize / 1024, compressedSize / 1024);
                    // 即使体积未减小也替换,因为可能是PNG尺寸压缩但质量更好的情况
                    if (localFile.delete() && tempFile.renameTo(localFile)) {
                        log.info("图片已优化(质量提升):{}", localFile.getName());
                    }
                }
            } else {
                throw new IOException("压缩后文件为空");
            }
        } finally {
            // 清理资源
            if (inputStream != null) {
                try { inputStream.close(); } catch (IOException e) { log.error("输入流关闭失败", e); }
            }
            if (outputStream != null) {
                try { outputStream.close(); } catch (IOException e) { log.error("输出流关闭失败", e); }
            }
            if (tempFile != null && tempFile.exists() && !tempFile.delete()) {
                log.warn("临时文件清理失败:{}", tempFile.getAbsolutePath());
            }
        }
    }
    /**
     * PNG尺寸压缩:按比例缩放图片到最大尺寸限制内
     */
    private BufferedImage scalePngImage(BufferedImage originalImage) {
        int originalWidth = originalImage.getWidth();
        int originalHeight = originalImage.getHeight();
        // 检查是否需要缩放
        if (originalWidth <= PNG_MAX_WIDTH && originalHeight <= PNG_MAX_HEIGHT) {
            log.debug("PNG图片尺寸未超限,无需缩放:{}x{}", originalWidth, originalHeight);
            return null;
        }
        // 计算缩放比例
        double widthRatio = (double) PNG_MAX_WIDTH / originalWidth;
        double heightRatio = (double) PNG_MAX_HEIGHT / originalHeight;
        double ratio = Math.min(widthRatio, heightRatio);
        int newWidth = (int) (originalWidth * ratio);
        int newHeight = (int) (originalHeight * ratio);
        // 确保尺寸不为0
        if (newWidth <= 0) newWidth = 1;
        if (newHeight <= 0) newHeight = 1;
        log.debug("PNG缩放比例:{} → 新尺寸:{}x{}", ratio, newWidth, newHeight);
        // 创建缩放后的图片
        BufferedImage scaledImage = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = scaledImage.createGraphics();
        // 设置渲染参数以获得更好的缩放质量
        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        // 绘制缩放后的图片
        g2d.drawImage(originalImage, 0, 0, newWidth, newHeight, null);
        g2d.dispose();
        return scaledImage;
    }
    /**
     * 完全保留你的方法:获取文件扩展名
     */
    private static String getFileExtension(String fileName) {
        if (fileName == null || !fileName.contains(".")) {
            return "jpg";
        }
        return fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
    }
    /**
     * 完全保留你的方法:判断是否为图片格式
     */
    private static boolean isImageFormat(String formatName) {
        if (formatName == null || formatName.isEmpty()) {
            return false;
        }
        return IMAGE_FORMATS.contains(formatName.toLowerCase());
    }
}