| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598 |
- package com.ruoyi.app.utils;
- import com.ruoyi.common.config.RuoYiConfig;
- import com.ruoyi.common.constant.Constants;
- import com.ruoyi.common.utils.StringUtils;
- import net.coobird.thumbnailator.Thumbnails;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import java.io.File;
- import java.io.IOException;
- import java.nio.file.Paths;
- /**
- * 图片压缩工具类 - 使用Thumbnailator实现
- *
- * @author ruoyi
- */
- public class ImageCompressUtils {
- private static final Logger log = LoggerFactory.getLogger(ImageCompressUtils.class);
- /**
- * 目标文件大小(200KB)
- */
- private static final long TARGET_SIZE = 100 * 1024; // 200KB = 200 * 1024 字节
- /**
- * 压缩图片 - 根据Web访问路径
- *
- * @param webPath Web访问路径 (如: /profile/upload/2025/08/05/image.jpg)
- * @return 压缩后的Web访问路径,如果不需要压缩则返回原路径
- */
- public static String compressImageByWebPath(String webPath) {
- log.info("开始处理Web路径图片压缩: {}", webPath);
- System.out.println("开始处理Web路径图片压缩: " + webPath);
- if (StringUtils.isEmpty(webPath)) {
- log.warn("Web路径为空,直接返回");
- System.out.println("Web路径为空,直接返回");
- return webPath;
- }
- try {
- // 将Web路径转换为本地文件路径
- String localPath = convertWebPathToLocalPath(webPath);
- log.debug("Web路径转换为本地路径: {} -> {}", webPath, localPath);
- System.out.println("Web路径转换为本地路径: " + webPath + " -> " + localPath);
- if (StringUtils.isEmpty(localPath)) {
- log.warn("无法转换Web路径到本地路径");
- System.out.println("无法转换Web路径到本地路径");
- return webPath;
- }
- File originalFile = new File(localPath);
- if (!originalFile.exists() || !originalFile.isFile()) {
- log.warn("文件不存在或不是文件: {}", localPath);
- System.out.println("文件不存在或不是文件: " + localPath);
- return webPath;
- }
- // 如果文件小于等于200KB,直接返回原路径
- if (originalFile.length() <= TARGET_SIZE) {
- log.info("文件大小 {} 字节,小于等于目标大小,无需压缩", originalFile.length());
- System.out.println("文件大小 " + originalFile.length() + " 字节,小于等于目标大小,无需压缩");
- return webPath;
- }
- // 生成压缩后的本地文件路径
- String compressedLocalPath = generateCompressedLocalPath(localPath);
- log.debug("生成压缩后本地路径: {}", compressedLocalPath);
- System.out.println("生成压缩后本地路径: " + compressedLocalPath);
- // 执行压缩
- log.info("开始压缩图片: {} -> {}", localPath, compressedLocalPath);
- System.out.println("开始压缩图片: " + localPath + " -> " + compressedLocalPath);
- if (compressImageWithThumbnailator(localPath, compressedLocalPath)) {
- // 将压缩后的本地路径转换为Web访问路径
- String compressedWebPath = convertLocalPathToWebPath(compressedLocalPath);
- // String compressedWebPath = compressedLocalPath;
- log.info("图片压缩成功: {} -> {}", webPath, compressedWebPath);
- System.out.println("图片压缩成功: " + webPath + " -> " + compressedWebPath);
- return compressedWebPath;
- } else {
- log.warn("图片压缩失败,返回原始路径: {}", webPath);
- System.out.println("图片压缩失败,返回原始路径: " + webPath);
- return webPath;
- }
- } catch (Exception e) {
- log.error("处理Web路径图片压缩时发生异常: " + webPath, e);
- System.err.println("处理Web路径图片压缩时发生异常: " + webPath + ", 错误: " + e.getMessage());
- e.printStackTrace();
- return webPath;
- } finally {
- // 建议垃圾回收
- System.gc();
- }
- }
- /**
- * 压缩图片 - 根据本地文件路径
- *
- * @param localPath 本地文件路径
- * @return 压缩后的本地文件路径,如果不需要压缩则返回原路径
- */
- public static String compressImageByLocalPath(String localPath) {
- log.info("开始处理本地路径图片压缩: {}", localPath);
- System.out.println("开始处理本地路径图片压缩: " + localPath);
- if (StringUtils.isEmpty(localPath)) {
- log.warn("本地路径为空,直接返回");
- System.out.println("本地路径为空,直接返回");
- return localPath;
- }
- File originalFile = new File(localPath);
- if (!originalFile.exists() || !originalFile.isFile()) {
- log.warn("文件不存在或不是文件: {}", localPath);
- System.out.println("文件不存在或不是文件: " + localPath);
- return localPath;
- }
- // 如果文件小于等于200KB,直接返回原路径
- if (originalFile.length() <= TARGET_SIZE) {
- log.info("文件大小 {} 字节,小于等于目标大小 {} 字节,无需压缩", originalFile.length(), TARGET_SIZE);
- System.out.println("文件大小 " + originalFile.length() + " 字节,小于等于目标大小 " + TARGET_SIZE + " 字节,无需压缩");
- return localPath;
- }
- try {
- // 生成压缩后的文件路径
- String compressedPath = generateCompressedLocalPath(localPath);
- log.debug("生成压缩后路径: {}", compressedPath);
- System.out.println("生成压缩后路径: " + compressedPath);
- // 执行压缩
- log.info("开始压缩图片: {} -> {}", localPath, compressedPath);
- System.out.println("开始压缩图片: " + localPath + " -> " + compressedPath);
- if (compressImageWithThumbnailator(localPath, compressedPath)) {
- log.info("本地图片压缩成功: {} -> {}", localPath, compressedPath);
- System.out.println("本地图片压缩成功: " + localPath + " -> " + compressedPath);
- return compressedPath;
- } else {
- log.warn("本地图片压缩失败,返回原始路径: {}", localPath);
- System.out.println("本地图片压缩失败,返回原始路径: " + localPath);
- return localPath;
- }
- } catch (Exception e) {
- log.error("处理本地路径图片压缩时发生异常: " + localPath, e);
- System.err.println("处理本地路径图片压缩时发生异常: " + localPath + ", 错误: " + e.getMessage());
- e.printStackTrace();
- return localPath;
- } finally {
- System.gc();
- }
- }
- /**
- * 压缩图片 - 简单方法,传入图片路径,返回压缩后的路径
- *
- * @param imagePath 图片路径
- * @return 压缩后的图片路径,如果不需要压缩则返回原路径
- */
- public static String compressImage(String imagePath) {
- log.info("开始压缩图片: {}", imagePath);
- System.out.println("开始压缩图片: " + imagePath);
- if (StringUtils.isEmpty(imagePath)) {
- log.warn("图片路径为空");
- System.out.println("图片路径为空");
- return imagePath;
- }
- // 检查文件是否存在
- File imageFile = new File(imagePath);
- if (!imageFile.exists() || !imageFile.isFile()) {
- log.warn("图片文件不存在: {}", imagePath);
- System.out.println("图片文件不存在: " + imagePath);
- return imagePath;
- }
- // 获取文件大小
- long fileSize = imageFile.length();
- long fileSizeKB = fileSize / 1024;
- log.info("图片文件大小: {} 字节 ({} KB)", fileSize, fileSizeKB);
- System.out.println("图片文件大小: " + fileSize + " 字节 (" + fileSizeKB + " KB)");
- // 如果文件小于等于200KB,直接返回原路径
- if (fileSize <= TARGET_SIZE) {
- log.info("文件大小 {} KB,小于等于200KB,无需压缩", fileSizeKB);
- System.out.println("文件大小 " + fileSizeKB + " KB,小于等于200KB,无需压缩");
- return imagePath;
- }
- log.info("文件大小 {} KB,大于200KB,开始压缩", fileSizeKB);
- System.out.println("文件大小 " + fileSizeKB + " KB,大于200KB,开始压缩");
- // 调用本地路径压缩方法
- return compressImageByLocalPath(imagePath);
- }
- /**
- * 将Web访问路径转换为本地文件路径
- *
- * @param webPath Web路径 (如: /profile/upload/2025/08/05/image.jpg)
- * @return 本地文件路径 (如: /www/wwwroot/server/uploadPath/upload/2025/08/05/image.jpg)
- */
- private static String convertWebPathToLocalPath(String webPath) {
- log.debug("转换Web路径到本地路径: {}", webPath);
- System.out.println("转换Web路径到本地路径: " + webPath);
- if (StringUtils.isEmpty(webPath)) {
- return null;
- }
- // 检查是否是有效的资源路径
- if (!webPath.startsWith(Constants.RESOURCE_PREFIX + "/")) {
- log.warn("不是有效的资源路径: {}", webPath);
- System.out.println("不是有效的资源路径: " + webPath);
- return null;
- }
- // 移除资源前缀,获取相对路径
- String relativePath = webPath.substring(Constants.RESOURCE_PREFIX.length() + 1);
- log.debug("相对路径: {}", relativePath);
- System.out.println("相对路径: " + relativePath);
- // 策略1: 使用配置的profile路径
- String profilePath = RuoYiConfig.getProfile();
- String localPath = Paths.get(profilePath, relativePath).toString();
- log.debug("策略1 - 使用profile路径: {} -> {}", profilePath, localPath);
- System.out.println("策略1 - 使用profile路径: " + profilePath + " -> " + localPath);
- // 检查文件是否存在
- File file = new File(localPath);
- if (file.exists()) {
- log.debug("策略1成功 - 文件存在: {}", localPath);
- System.out.println("策略1成功 - 文件存在: " + localPath);
- return localPath;
- } else {
- log.debug("策略1失败 - 文件不存在: {}", localPath);
- System.out.println("策略1失败 - 文件不存在: " + localPath);
- }
- return null;
- }
- /**
- * 将本地文件路径转换为Web访问路径
- *
- * @param localPath 本地文件路径
- * @return Web访问路径
- */
- private static String convertLocalPathToWebPath(String localPath) {
- // 步骤1:统一替换路径分隔符为正斜杠(兼容所有系统)
- if (StringUtils.isEmpty(localPath)) return null;
- String normalizedPath = localPath.replace("\\", "/"); // 关键点1:统一使用/
- log.debug("转换本地路径到Web路径: {}", normalizedPath);
- String profilePath = RuoYiConfig.getProfile();
- if (profilePath != null) {
- profilePath = profilePath.replace("\\", "/"); // 关键点2:配置路径也统一
- }
- // 步骤2:优化路径匹配逻辑(使用统一的分隔符)
- String webPath = null;
- // 策略1: 检查配置目录
- if (profilePath != null && normalizedPath.startsWith(profilePath)) {
- int startIndex = profilePath.endsWith("/") ?
- profilePath.length() :
- profilePath.length() + 1;
- String relativePath = normalizedPath.substring(startIndex);
- webPath = Constants.RESOURCE_PREFIX + "/" + relativePath;
- log.debug("策略1成功 - Web路径: {}", webPath);
- }
- // 策略2: 处理含upload的路径
- else if (normalizedPath.contains("/upload/")) {
- int uploadIndex = normalizedPath.indexOf("/upload/") + 1;
- webPath = Constants.RESOURCE_PREFIX + "/" + normalizedPath.substring(uploadIndex);
- log.debug("策略2成功 - Web路径: {}", webPath);
- }
- // 策略3: 兜底方案(文件名直用)
- else {
- int lastSlash = normalizedPath.lastIndexOf("/");
- String fileName = (lastSlash != -1) ?
- normalizedPath.substring(lastSlash + 1) :
- normalizedPath;
- webPath = Constants.RESOURCE_PREFIX + "/upload/" + fileName;
- log.debug("策略3成功 - Web路径: {}", webPath);
- }
- // 步骤3:确保结果有效性
- if (webPath != null) {
- log.info("转换成功: {} → {}", normalizedPath, webPath);
- return webPath;
- } else {
- log.error("转换失败: {}", normalizedPath);
- return null;
- }
- }
- /**
- * 使用Thumbnailator压缩图片
- *
- * @param inputPath 输入路径
- * @param outputPath 输出路径
- * @return 是否压缩成功
- */
- private static boolean compressImageWithThumbnailator(String inputPath, String outputPath) {
- log.info("使用Thumbnailator压缩图片: {} -> {}", inputPath, outputPath);
- System.out.println("使用Thumbnailator压缩图片: " + inputPath + " -> " + outputPath);
- try {
- // 确保输出目录存在
- File outputFile = new File(outputPath);
- File parentDir = outputFile.getParentFile();
- if (!parentDir.exists()) {
- log.debug("创建输出目录: {}", parentDir.getAbsolutePath());
- System.out.println("创建输出目录: " + parentDir.getAbsolutePath());
- parentDir.mkdirs();
- }
- // 使用二分法查找合适的压缩质量
- log.debug("查找最优压缩质量");
- System.out.println("查找最优压缩质量");
- float quality = findOptimalQuality(inputPath, outputPath);
- // 执行最终压缩
- log.debug("执行最终压缩,质量: {}", quality);
- System.out.println("执行最终压缩,质量: " + quality);
- Thumbnails.of(inputPath)
- .scale(1.0f) // 保持原尺寸
- .outputQuality(quality) // 设置压缩质量
- .toFile(outputPath);
- // 检查压缩后的文件大小
- File compressedFile = new File(outputPath);
- boolean success = compressedFile.exists() && compressedFile.length() <= TARGET_SIZE;
- log.info("压缩完成,文件存在: {}, 文件大小: {} 字节, 压缩成功: {}",
- compressedFile.exists(), compressedFile.length(), success);
- System.out.println("压缩完成,文件存在: " + compressedFile.exists() +
- ", 文件大小: " + compressedFile.length() + " 字节, 压缩成功: " + success);
- return success;
- } catch (IOException e) {
- log.error("压缩图片时发生IO异常: " + inputPath, e);
- System.err.println("压缩图片时发生IO异常: " + inputPath + ", 错误: " + e.getMessage());
- e.printStackTrace();
- return false;
- }
- }
- /**
- * 使用二分法查找最优压缩质量
- */
- private static float findOptimalQuality(String inputPath, String outputPath) {
- log.debug("使用二分法查找最优压缩质量: {}", inputPath);
- System.out.println("使用二分法查找最优压缩质量: " + inputPath);
- float minQuality = 0.1f;
- float maxQuality = 1.0f;
- float bestQuality = 0.8f;
- // 创建临时文件用于测试
- String tempPath = generateTempPath(outputPath);
- log.debug("创建临时文件用于测试: {}", tempPath);
- System.out.println("创建临时文件用于测试: " + tempPath);
- try {
- // 确保临时文件目录存在
- File tempFile = new File(tempPath);
- File parentDir = tempFile.getParentFile();
- if (!parentDir.exists()) {
- log.debug("创建临时文件目录: {}", parentDir.getAbsolutePath());
- System.out.println("创建临时文件目录: " + parentDir.getAbsolutePath());
- parentDir.mkdirs();
- }
- // 二分查找最优质量值
- for (int i = 0; i < 10; i++) { // 减少迭代次数,提高效率
- float testQuality = (minQuality + maxQuality) / 2;
- log.debug("第{}次测试,压缩质量: {}", i + 1, testQuality);
- System.out.println("第" + (i + 1) + "次测试,压缩质量: " + testQuality);
- try {
- // 确保临时文件不存在
- if (tempFile.exists()) {
- tempFile.delete();
- }
- // 测试当前质量的文件大小
- Thumbnails.of(inputPath)
- .scale(1.0f)
- .outputQuality(testQuality)
- .toFile(tempPath);
- // 等待文件写入完成
- Thread.sleep(100);
- long fileSize = tempFile.length();
- log.debug("测试结果 - 文件大小: {} 字节, 目标大小: {} 字节", fileSize, TARGET_SIZE);
- System.out.println("测试结果 - 文件大小: " + fileSize + " 字节, 目标大小: " + TARGET_SIZE + " 字节");
- if (fileSize <= TARGET_SIZE) {
- bestQuality = testQuality;
- minQuality = testQuality;
- log.debug("文件大小符合要求,调整最小质量为: {}", minQuality);
- System.out.println("文件大小符合要求,调整最小质量为: " + minQuality);
- } else {
- maxQuality = testQuality;
- log.debug("文件大小超过目标,调整最大质量为: {}", maxQuality);
- System.out.println("文件大小超过目标,调整最大质量为: " + maxQuality);
- }
- // 如果已经很接近目标大小,提前退出
- if (Math.abs(fileSize - TARGET_SIZE) < 10240) { // 10KB的容差
- bestQuality = testQuality;
- log.info("已接近目标大小,提前退出,最优质量: {}", bestQuality);
- System.out.println("已接近目标大小,提前退出,最优质量: " + bestQuality);
- break;
- }
- // 如果质量范围已经很小,提前退出
- if (Math.abs(maxQuality - minQuality) < 0.05f) {
- log.info("质量范围已经很小,提前退出,最优质量: {}", bestQuality);
- System.out.println("质量范围已经很小,提前退出,最优质量: " + bestQuality);
- break;
- }
- } catch (IOException e) {
- log.warn("测试压缩质量 {} 时出现异常: {}", testQuality, e.getMessage());
- System.out.println("测试压缩质量 " + testQuality + " 时出现异常: " + e.getMessage());
- // 如果当前质量测试失败,调整范围继续测试
- maxQuality = testQuality;
- continue;
- } catch (InterruptedException e) {
- log.warn("等待文件写入时被中断");
- System.out.println("等待文件写入时被中断");
- Thread.currentThread().interrupt();
- break;
- }
- // 删除临时文件
- if (tempFile.exists()) {
- tempFile.delete();
- }
- }
- } catch (Exception e) {
- log.error("查找最优压缩质量时发生异常", e);
- System.err.println("查找最优压缩质量时发生异常: " + e.getMessage());
- e.printStackTrace();
- // 如果出错,使用默认质量
- bestQuality = 0.6f;
- } finally {
- // 清理可能残留的临时文件
- File tempFile = new File(tempPath);
- if (tempFile.exists()) {
- log.debug("清理残留临时文件: {}", tempPath);
- System.out.println("清理残留临时文件: " + tempPath);
- tempFile.delete();
- }
- }
- log.info("最优压缩质量查找完成: {}", bestQuality);
- System.out.println("最优压缩质量查找完成: " + bestQuality);
- return bestQuality;
- }
- /**
- * 生成临时文件路径,保持正确的图片扩展名
- *
- * @param outputPath 输出文件路径
- * @return 临时文件路径
- */
- private static String generateTempPath(String outputPath) {
- File outputFile = new File(outputPath);
- String fileName = outputFile.getName();
- String parentDir = outputFile.getParent();
- int dotIndex = fileName.lastIndexOf('.');
- if (dotIndex == -1) {
- // 没有扩展名,默认添加.jpg
- return Paths.get(parentDir, fileName + "_comp.jpg").toString();
- } else {
- String nameWithoutExt = fileName.substring(0, dotIndex);
- String extension = fileName.substring(dotIndex);
- return Paths.get(parentDir, nameWithoutExt + "_comp" + extension).toString();
- }
- }
- /**
- * 生成压缩后的本地文件路径(在同一目录下)
- *
- * @param originalPath 原文件路径
- * @return 压缩后的文件路径
- */
- private static String generateCompressedLocalPath(String originalPath) {
- log.debug("生成压缩后本地文件路径: {}", originalPath);
- System.out.println("生成压缩后本地文件路径: " + originalPath);
- File originalFile = new File(originalPath);
- String parentDir = originalFile.getParent();
- String fileName = originalFile.getName();
- int dotIndex = fileName.lastIndexOf('.');
- String compressedPath;
- if (dotIndex == -1) {
- compressedPath = Paths.get(parentDir, fileName + "_comp.jpg").toString();
- } else {
- String nameWithoutExt = fileName.substring(0, dotIndex);
- String extension = fileName.substring(dotIndex);
- // 对于PNG文件,转换为JPG以获得更好的压缩效果
- if (".png".equalsIgnoreCase(extension)) {
- extension = ".jpg";
- }
- String compressedFileName = nameWithoutExt + "_comp" + extension;
- compressedPath = Paths.get(parentDir, compressedFileName).toString();
- }
- log.debug("生成的压缩文件路径: {}", compressedPath);
- System.out.println("生成的压缩文件路径: " + compressedPath);
- return compressedPath;
- }
- /**
- * 批量压缩图片(Web路径)
- *
- * @param webPaths Web访问路径数组
- * @return 压缩后的Web访问路径数组
- */
- public static String[] compressImagesByWebPath(String[] webPaths) {
- log.info("批量压缩图片,数量: {}", webPaths != null ? webPaths.length : 0);
- System.out.println("批量压缩图片,数量: " + (webPaths != null ? webPaths.length : 0));
- if (webPaths == null) {
- log.warn("Web路径数组为空");
- System.out.println("Web路径数组为空");
- return null;
- }
- String[] compressedPaths = new String[webPaths.length];
- for (int i = 0; i < webPaths.length; i++) {
- log.debug("批量压缩第{}张图片: {}", i + 1, webPaths[i]);
- System.out.println("批量压缩第" + (i + 1) + "张图片: " + webPaths[i]);
- compressedPaths[i] = compressImageByWebPath(webPaths[i]);
- }
- log.info("批量压缩完成");
- System.out.println("批量压缩完成");
- return compressedPaths;
- }
- /**
- * 检查路径是否为若依资源路径
- *
- * @param path 路径
- * @return 是否为资源路径
- */
- public static boolean isResourcePath(String path) {
- boolean result = StringUtils.isNotEmpty(path) && path.startsWith(Constants.RESOURCE_PREFIX + "/");
- log.debug("检查是否为资源路径: {} -> {}", path, result);
- System.out.println("检查是否为资源路径: " + path + " -> " + result);
- return result;
- }
- /**
- * 获取文件大小(字节)
- *
- * @param webPath Web访问路径
- * @return 文件大小,如果文件不存在返回-1
- */
- public static long getFileSize(String webPath) {
- log.debug("获取文件大小: {}", webPath);
- System.out.println("获取文件大小: " + webPath);
- String localPath = convertWebPathToLocalPath(webPath);
- if (StringUtils.isEmpty(localPath)) {
- log.warn("无法转换Web路径到本地路径");
- System.out.println("无法转换Web路径到本地路径");
- return -1;
- }
- File file = new File(localPath);
- long fileSize = file.exists() ? file.length() : -1;
- log.debug("文件大小: {} 字节", fileSize);
- System.out.println("文件大小: " + fileSize + " 字节");
- return fileSize;
- }
- }
|