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