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