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; // 2MBfinalint 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_syscfgSET syscfg_value = '500'WHERE syscfg_key = 'KsXpCompressSize';
方法2: 系统管理界面
-
登录系统管理后台
-
进入"系统配置"模块
-
查找或添加
KsXpCompressSize配置项 -
设置期望的阈值(单位:KB)
-
保存配置
性能优化详解
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 —
5928

被折叠的 条评论
为什么被折叠?



