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

iFlyCode+SpecKit

项目背景

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

核心特性

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

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

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

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

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

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

提问过程

将上述需求描述提交给iFlyCode,使用项目环境和设计智能体。

(执行片段如下图:)

技术架构

压缩流程图

核心方法调用链

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

核心代码实现

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

位置 : BkKsxpServiceImpl.java 第822-849行

2. 智能压缩方法(compressImage)

位置 : BkKsxpServiceImpl.java 第1960-2090行

核心特性:

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

  • 二分查找压缩算法

    (压缩次数减少50%)

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

  • 完整的内存管理

  • 三重临时文件清理

markdown 复制代码
3. 质量压缩方法(compressImageWithQuality)

位置 : BkKsxpServiceImpl.java 第2096-2116行

java 复制代码
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)

perl 复制代码
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: 数据库配置(推荐)

java 复制代码
-- 插入配置(如果不存在)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. 大图片预缩放策略

优化前后对比

预缩放触发条件

ruby 复制代码
finallong LARGE_FILE_THRESHOLD = 2 * 1024 * 1024L; // 2MBfinalint LARGE_DIMENSION_THRESHOLD = 2000; // 2000像素// 满足以下任一条件触发预缩放:// 1. 文件大小 > 2MB// 2. 宽度 > 2000px// 3. 高度 > 2000px

缩放比例计算

erlang 复制代码
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. 内存管理优化

内存释放时机

代码示例

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

3. 临时文件清理机制

三重保障

清理代码

vbnet 复制代码
// 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. 性能测试

shell 复制代码
# 测试场景1: 单张大图片- 文件: 4000x3000, 2MB- 预期: 内存占用 < 20MB, 处理时间 < 3秒# 测试场景2: 批量上传- 文件: 100张混合大小照片- 预期: 无内存溢出, 总时间 < 30秒# 测试场景3: 极限测试- 文件: 8000x6000, 10MB- 预期: 成功压缩, 无崩溃

3. 边界测试

arduino 复制代码
// 测试用例1. 损坏的图片文件 → 应返回null,使用原图2. 非JPG格式 → 应正常处理(PNG/GIF)3. 阈值为0 → 应使用默认值300KB4. 极小图片(10KB) → 不压缩5. 正方形图片 → 等比例缩放6. 极窄/极宽图片 → 正确计算缩放比例

测试步骤

准备工作

arduino 复制代码
-- 1. 配置压缩阈值INSERT INTO basis_syscfg(syscfg_key, syscfg_value, syscfg_desc)VALUES('KsXpCompressSize', '300', '照片压缩阈值(KB)');

执行测试

shell 复制代码
# 2. 准备测试照片- 小照片: 150KB (不压缩)- 中照片: 500KB (质量压缩)- 大照片: 2MB (预缩放+压缩)- PNG照片: 带透明背景# 3. 批量上传测试- 打包成ZIP文件- 通过系统上传- 检查日志输出- 验证存储结果# 4. 验证结果- 检查照片大小是否符合阈值- 检查照片质量是否可接受- 检查透明通道是否保留- 检查临时文件是否清理

日志示例

正常压缩日志

scss 复制代码
照片 20240101001.jpg 从 450KB 压缩到 280KB (阈值: 300KB)

大图片预缩放日志

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

尺寸缩放日志

makefile 复制代码
通过缩小尺寸压缩: large_photo.jpg, 新尺寸: 1600x1200, 大小: 295KB

压缩失败日志

lua 复制代码
压缩照片失败: corrupted.jpg, Cannot read input file!

性能指标

压缩效果统计

效果总结

1、原计划3个工作日完成,使用iFlyCode,省去寻找方案的时间,1天左右完成,提效66%;

2、经过生产验证,该压缩方案成功率100%,效果符合预期,即300k以上相片压缩至300k内,不影响打印效果呈现,满足业务需求,后期可以将本方法抽象成为通用方法进行复用。

--- END ---

相关推荐
广白39 分钟前
钉钉小程序直传文件到 阿里云OSS
前端·vue.js·uni-app
zyfts1 小时前
🔥告别 20 分钟等待!NestJS 生产级消息队列 BullMQ 实践指南
前端·后端
GISer_Jing2 小时前
3DThreeJS渲染核心架构深度解析
javascript·3d·架构·webgl
狗头大军之江苏分军2 小时前
【压力】一位一线炼钢工人的消失
前端·后端
拉不动的猪2 小时前
文件下载:后端配置、前端方式与进度监控
前端·javascript·浏览器
Amy_yang2 小时前
前端实现 Server-Sent Events 全解析:从代码到调试的实战指南
前端·uni-app
sean聊前端2 小时前
听说vite要一统江湖了,我看看怎么个事
前端
喝二两啤酒2 小时前
手把手打通 H5 多支付通道(Apple pay、Google pay、第三方卡支付)
前端
特级业务专家2 小时前
续集:Vite 字体插件重构之路 —— 从“能用”到“生产级稳定”
javascript·vue.js·vite