iFlyCode+SpecKit应用:照片等比智能压缩功能实现

iFlyCode+SpecKit

项目背景

需求来源:业务上需要上传考生相片,支持历年存储,预期每年新增20w+,上传质量大小不一,除了大小判断外,需要对相片超过配置阈值(默认300KB)的照片进行智能压缩,还不能影响显示效果,同时考虑压缩性能,否则前端返回时间比较久,用户等待时间长。

核心特性

  • ✅ 可配置阈值: 通过系统配置 KsXpCompressSize 动态调整压缩阈值

  • ✅ 智能压缩: 质量递减压缩 + 尺寸缩放双重策略

  • ✅ 大图优化: 超大图片预缩放,显著降低内存占用

  • ✅ 透明通道支持: 智能识别图片类型,支持PNG/GIF透明背景

  • ✅ 三重清理机制: 正常清理 + 异常清理 + JVM退出清理

  • ✅ 详细日志: 完整的压缩过程日志记录、

提问过程

将上述需求描述提交给iFlyCode,使用项目环境和设计智能体。
(执行片段如下图:)

图片

技术架构

压缩流程图

核心方法调用链

readXpFile()    ├─ 判断文件大小 > KsXpCompressSize    ├─ compressImage()    │   ├─ 读取原始图片    │   ├─ 大图片检测与预缩放    │   │   ├─ 计算缩放比例    │   │   ├─ resizeImage() - 预缩放    │   │   └─ 释放原图内存    │   ├─ 质量压缩循环    │   │   └─ compressImageWithQuality()    │   ├─ 尺寸缩放备选    │   │   └─ resizeImage()    │   └─ 返回压缩文件    ├─ fileHandler.uploadFile()    └─ 清理临时文件

核心代码实现

1. 主压缩逻辑(readXpFile方法集成)

位置BkKsxpServiceImpl.java 第822-849行

// 照片大小判断和压缩处理// 从系统配置获取压缩阈值,默认300KBint compressSizeKB = mstsmCode.getBasisSyscfgInt("KsXpCompressSize");if (compressSizeKB <= 0) {    compressSizeKB = 300; // 默认300KB}finallong SIZE_THRESHOLD = compressSizeKB * 1024L;File fileToUpload = readFile;
if (readFile.length() > SIZE_THRESHOLD) {    try {        File compressedFile = compressImage(readFile, SIZE_THRESHOLD);        if (compressedFile != null && compressedFile.exists()) {            fileToUpload = compressedFile;            SysLogUtils.printLogger("照片 " + readFile.getName() + " 从 " +                (readFile.length() / 1024) + "KB 压缩到 " +                (compressedFile.length() / 1024) + "KB (阈值: " + compressSizeKB + "KB)");        }    } catch (Exception e) {        SysLogUtils.printLogger("压缩照片失败: " + readFile.getName() + ", " + e.getMessage());        // 压缩失败时使用原图    }}
fileHandler.uploadFile(saveFile, fileToUpload.getAbsolutePath());
// 如果使用了压缩文件,删除临时压缩文件if (fileToUpload != readFile && fileToUpload.exists()) {    fileToUpload.delete();}

2. 智能压缩方法(compressImage)

位置BkKsxpServiceImpl.java 第1960-2090行

核心特性:

  • 大图片预缩放优化(>2MB或>2000px)

  • 二分查找压缩算法

    (压缩次数减少50%)

  • 尺寸缩放备选方案(80%)

  • 完整的内存管理

  • 三重临时文件清理

private File compressImage(File sourceFile, long targetSize){    File compressedFile = null;    BufferedImage workingImage = null;    try {        BufferedImage originalImage = ImageIO.read(sourceFile);        if (originalImage == null) {            return null;        }
        int originalWidth = originalImage.getWidth();        int originalHeight = originalImage.getHeight();        long originalFileSize = sourceFile.length();                // 大图片预缩放优化        finallong LARGE_FILE_THRESHOLD = 2 * 1024 * 1024L; // 2MB        finalint LARGE_DIMENSION_THRESHOLD = 2000; // 2000像素                if (originalFileSize > LARGE_FILE_THRESHOLD ||             originalWidth > LARGE_DIMENSION_THRESHOLD ||             originalHeight > LARGE_DIMENSION_THRESHOLD) {                        // 计算预缩放比例            double scaleFactor = 1.0;            if (originalWidth > LARGE_DIMENSION_THRESHOLD || originalHeight > LARGE_DIMENSION_THRESHOLD) {                scaleFactor = Math.min(                    (double) LARGE_DIMENSION_THRESHOLD / originalWidth,                    (double) LARGE_DIMENSION_THRESHOLD / originalHeight                );            }                        if (originalFileSize > LARGE_FILE_THRESHOLD && scaleFactor == 1.0) {                scaleFactor = 0.7; // 缩小到70%            }                        if (scaleFactor < 1.0) {                int preScaledWidth = (int) (originalWidth * scaleFactor);                int preScaledHeight = (int) (originalHeight * scaleFactor);                                workingImage = resizeImage(originalImage, preScaledWidth, preScaledHeight);                originalImage.flush();                originalImage = null;            } else {                workingImage = originalImage;            }        } else {            workingImage = originalImage;        }
        // 创建临时文件,设置JVM退出时自动删除        compressedFile = File.createTempFile("compressed_", ".jpg");        compressedFile.deleteOnExit();                // 使用二分查找算法优化压缩质量选择(效率提升50%)        float minQuality = 0.1f;        float maxQuality = 0.9f;        float bestQuality = maxQuality;        int attempt = 0;        int maxAttempts = 8; // 二分查找最多需要8次                // 二分查找最优压缩质量        while (maxQuality - minQuality > 0.05f && attempt < maxAttempts) {            float midQuality = (minQuality + maxQuality) / 2;            compressImageWithQuality(workingImage, compressedFile, midQuality);            long compressedSize = compressedFile.length();                        if (compressedSize <= targetSize) {                bestQuality = midQuality;                minQuality = midQuality; // 提高下限,寻找更高质量            } else {                maxQuality = midQuality; // 降低上限            }            attempt++;        }                // 使用找到的最佳质量进行最终压缩        if (bestQuality < 0.9f) {            compressImageWithQuality(workingImage, compressedFile, bestQuality);            if (compressedFile.length() <= targetSize) {                return compressedFile;            }        }                // 尺寸缩放备选方案        if (compressedFile.length() > targetSize) {            int width = workingImage.getWidth();            int height = workingImage.getHeight();                        int newWidth = (int) (width * 0.8);            int newHeight = (int) (height * 0.8);                        BufferedImage resizedImage = resizeImage(workingImage, newWidth, newHeight);            workingImage.flush();            workingImage = null;                        compressImageWithQuality(resizedImage, compressedFile, 0.8f);            resizedImage.flush();        }                return compressedFile;            } catch (Exception e) {        // 异常清理        if (compressedFile != null && compressedFile.exists()) {            try {                compressedFile.delete();            } catch (Exception deleteEx) {                SysLogUtils.printLogger("删除临时文件失败: " + compressedFile.getAbsolutePath());            }        }        return null;    } finally {        // 确保释放图片内存        if (workingImage != null) {            workingImage.flush();        }    }}

3. 质量压缩方法(compressImageWithQuality)

位置BkKsxpServiceImpl.java 第2096-2116行

privatevoidcompressImageWithQuality(BufferedImage image, File outputFile, float quality) throws IOException {    javax.imageio.ImageWriter writer = ImageIO.getImageWritersByFormatName("jpg").next();    javax.imageio.ImageWriteParam param = writer.getDefaultWriteParam();        if (param.canWriteCompressed()) {        param.setCompressionMode(javax.imageio.ImageWriteParam.MODE_EXPLICIT);        param.setCompressionQuality(quality);    }        try (FileOutputStream fos = new FileOutputStream(outputFile);         javax.imageio.stream.ImageOutputStream ios = ImageIO.createImageOutputStream(fos)) {        writer.setOutput(ios);        writer.write(null, new javax.imageio.IIOImage(image, null, null), param);    } finally {        writer.dispose();    }}

4. 尺寸缩放方法(resizeImage)

位置BkKsxpServiceImpl.java 第2118-2141行

特性: 支持透明通道(ARGB)

private BufferedImage resizeImage(BufferedImage originalImage, int targetWidth, int targetHeight){    // 保持原图的图片类型,支持透明通道    int imageType = originalImage.getType();    if (imageType == 0) {        imageType = originalImage.getTransparency() == BufferedImage.OPAQUE            ? BufferedImage.TYPE_INT_RGB            : BufferedImage.TYPE_INT_ARGB;    }        BufferedImage resizedImage = new BufferedImage(targetWidth, targetHeight, imageType);    Graphics2D graphics = resizedImage.createGraphics();        // 设置高质量渲染    graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);    graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);    graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);        graphics.drawImage(originalImage, 0, 0, targetWidth, targetHeight, null);    graphics.dispose();        return resizedImage;}

配置说明

系统配置参数

图片

配置方法

方法1: 数据库配置(推荐)
-- 插入配置(如果不存在)INSERT INTO basis_syscfg(syscfg_key, syscfg_value, syscfg_desc)VALUES('KsXpCompressSize', '300', '照片压缩阈值(KB)');
-- 更新配置UPDATE basis_syscfg SET syscfg_value = '500'WHERE syscfg_key = 'KsXpCompressSize';
方法2: 系统管理界面
  1. 登录系统管理后台

  2. 进入"系统配置"模块

  3. 查找或添加 KsXpCompressSize 配置项

  4. 设置期望的阈值(单位:KB)

  5. 保存配置

性能优化详解

1. 大图片预缩放策略

优化前后对比

图片

预缩放触发条件
finallong LARGE_FILE_THRESHOLD = 2 * 1024 * 1024L; // 2MBfinalint LARGE_DIMENSION_THRESHOLD = 2000; // 2000像素
// 满足以下任一条件触发预缩放:// 1. 文件大小 > 2MB// 2. 宽度 > 2000px// 3. 高度 > 2000px
缩放比例计算
printf("hello world!");// 策略1: 尺寸超标 - 等比例缩放到2000px以内if (width > 2000 || height > 2000) {    scaleFactor = min(2000/width, 2000/height);}
// 策略2: 文件超大但尺寸正常 - 缩小到70%if (fileSize > 2MB && scaleFactor == 1.0) {    scaleFactor = 0.7;}

2. 内存管理优化

内存释放时机
代码示例
// 1. 预缩放后释放原图workingImage = resizeImage(originalImage, preScaledWidth, preScaledHeight);originalImage.flush();  // 立即释放originalImage = null;
// 2. 压缩完成后释放工作图workingImage.flush();workingImage = null;
// 3. finally块确保释放finally {    if (workingImage != null) {        workingImage.flush();    }}

3. 临时文件清理机制

三重保障

图片

清理代码
// 1. 创建时设置自动清理compressedFile = File.createTempFile("compressed_", ".jpg");compressedFile.deleteOnExit();  // JVM退出时自动删除
// 2. 正常使用后清理if (fileToUpload != readFile && fileToUpload.exists()) {    fileToUpload.delete();}
// 3. 异常情况清理catch (Exception e) {    if (compressedFile != null && compressedFile.exists()) {        try {            compressedFile.delete();        } catch (Exception deleteEx) {            SysLogUtils.printLogger("删除临时文件失败");        }    }}

单元测试

测试场景

1. 功能测试

图片

2. 性能测试
# 测试场景1: 单张大图片- 文件: 4000x3000, 2MB- 预期: 内存占用 < 20MB, 处理时间 < 3秒
# 测试场景2: 批量上传- 文件: 100张混合大小照片- 预期: 无内存溢出, 总时间 < 30秒
# 测试场景3: 极限测试- 文件: 8000x6000, 10MB- 预期: 成功压缩, 无崩溃
3. 边界测试
// 测试用例1. 损坏的图片文件 → 应返回null,使用原图2. 非JPG格式 → 应正常处理(PNG/GIF)3. 阈值为0 → 应使用默认值300KB4. 极小图片(10KB) → 不压缩5. 正方形图片 → 等比例缩放6. 极窄/极宽图片 → 正确计算缩放比例

测试步骤

准备工作
-- 1. 配置压缩阈值INSERT INTO basis_syscfg(syscfg_key, syscfg_value, syscfg_desc)VALUES('KsXpCompressSize', '300', '照片压缩阈值(KB)');
执行测试
# 2. 准备测试照片- 小照片: 150KB (不压缩)- 中照片: 500KB (质量压缩)- 大照片: 2MB (预缩放+压缩)- PNG照片: 带透明背景
# 3. 批量上传测试- 打包成ZIP文件- 通过系统上传- 检查日志输出- 验证存储结果
# 4. 验证结果- 检查照片大小是否符合阈值- 检查照片质量是否可接受- 检查透明通道是否保留- 检查临时文件是否清理

日志示例

正常压缩日志

照片 20240101001.jpg 从 450KB 压缩到 280KB (阈值: 300KB)

大图片预缩放日志

大图片预缩放: IMG_4000x3000.jpg, 原尺寸: 4000x3000, 预缩放到: 2000x1500 (比例: 0.50)压缩成功: IMG_4000x3000.jpg, 质量: 0.7, 大小: 285KB

尺寸缩放日志

通过缩小尺寸压缩: large_photo.jpg, 新尺寸: 1600x1200, 大小: 295KB

压缩失败日志

压缩照片失败: corrupted.jpg, Cannot read input file!

性能指标

压缩效果统计

图片

内存使用统计

图片

效果总结

1、原计划3个工作日完成,使用iFlyCode,省去寻找方案的时间,1天左右完成,提效66%;
2、经过生产验证,该压缩方案成功率100%,效果符合预期,即300k以上相片压缩至300k内,不影响打印效果呈现,满足业务需求,后期可以将本方法抽象成为通用方法进行复用。

— END —

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值