最近需要帮助用户解决图片压缩优化结合Base64存储的问题。用户之前已经提到他们目前将图片以Base64格式存储在数据库中,导致接口传输数据过大,加载时间长达3秒。他们现在想通过图片压缩来优化系统,但不采用WebP格式转换。我需要详细思考如何在不改变存储格式的情况下,通过压缩和其他优化手段来减少数据量,提升性能。
我们先给出一个简单的设计流程图
需要的依赖包
<dependency>
<groupId>net.coobird</groupId>
<artifactId>thumbnailator</artifactId>
<version>0.4.20</version>
</dependency>
第一种:原始图片通过流的形式获取后,再进行压缩
解决方式:使用Thumbnailator进行压缩
大致代码思路:在保存为Base64前最大限度减小图片体积。这种方式通过获取原始图片后,然后进行压缩形成Base64位的图片编码
// 使用Thumbnailator进行压缩(Java示例)
public String compressAndConvertToBase64(MultipartFile file) throws IOException {
// 压缩参数
double quality = 0.7; // 质量压缩到70%
int maxWidth = 1920; // 限制最大宽度
// 压缩处理
BufferedImage compressedImage = Thumbnails.of(file.getInputStream())
.width(maxWidth)
.outputQuality(quality)
.asBufferedImage();
// 转为Base64
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(compressedImage, "jpg", baos);
return Base64.getEncoder().encodeToString(baos.toByteArray());
}
如果采用压缩比70%的方式进行压缩,则压缩比可以对照如下
原图参数 | 压缩前大小 | 压缩后大小 | Base64数据量减少 |
---|---|---|---|
4000x3000 PNG(5MB) | 5MB | 1.2MB | 5MB → 1.6MB |
1920x1080 JPG(2MB) | 2MB | 0.6MB | 2.6MB → 0.8MB |
接口启用:GZIP压缩优化
# Spring Boot配置(application.yml)
server:
compression:
enabled: true
mime-types: text/html,text/css,text/javascript,application/json,image/svg+xml
min-response-size: 1024
第二种:如果图片已经是Base64格式,则我们可以通过解码方式再进行压缩
代码思路:
public String recompressBase64(String originalBase64) throws IOException {
// 去除Base64头信息(如"data:image/jpeg;base64,")
String pureBase64 = originalBase64.split(",")[1];
// 解码为字节数组
byte[] imageBytes = Base64.getDecoder().decode(pureBase64);
// 使用Thumbnailator压缩
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
Thumbnails.of(new ByteArrayInputStream(imageBytes))
.scale(1)
.outputQuality(0.7)
.outputFormat("jpg")
.toOutputStream(outputStream);
// 重新编码为Base64
return "data:image/jpeg;base64," +
Base64.getEncoder().encodeToString(outputStream.toByteArray());
}
关键参数调优:
参数 | 推荐值 | 说明 |
---|---|---|
outputQuality | 0.6-0.8 | 质量压缩比(1为无损) |
scale | 按需调整 | 缩放比例(0.5=50%尺寸) |
outputFormat | jpg/png | 输出格式(jpg压缩率更高) |
处理阶段 | 原始方案(无压缩) | 前端预压缩 | 后端二次压缩 | 混合方案 |
---|---|---|---|---|
传输数据量 | 13MB | 2.6MB | 13MB | 2.6MB |
服务器CPU消耗 | 低 | 低 | 高 | 中 |
客户端处理时间 | 0 | 1.2秒 | 0 | 0.8秒 |
最终存储体积 | 13MB | 2.6MB | 3.2MB | 1.8MB |
针对Base64图片编码进行压缩,我们优化后的代码,包含内存安全管理和尺寸限制。大家可以参考如下代码
public String recompressBase64(String originalBase64) throws IOException {
// 分离Base64头信息和数据部分
String[] parts = originalBase64.split(",");
if (parts.length < 2) {
throw new IllegalArgumentException("无效的Base64格式");
}
String mimeType = parts[0].split(";")[0].replace("data:", "");
String pureBase64 = parts[1];
// 解码为字节数组
byte[] imageBytes = Base64.getDecoder().decode(pureBase64);
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(imageBytes);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
// 读取原始图片信息
BufferedImage originalImage = ImageIO.read(inputStream);
if (originalImage == null) {
throw new IOException("无法解码图片数据");
}
// 新增像素限制检查 ---------------------------------------------------
int maxPixels = 1920 * 1080; // 约200万像素
long actualPixels = (long) originalImage.getWidth() * originalImage.getHeight();
if (actualPixels > maxPixels) {
throw new IOException("图片尺寸超过限制(最大允许1920x1080像素)");
}
// 自动旋转处理(修复手机图片方向问题)
Thumbnails.Builder<BufferedImage> builder = Thumbnails.of(originalImage)
.outputFormat(getOutputFormat(mimeType)) // 保持原始格式
.outputQuality(0.7);
// 应用尺寸限制(保持宽高比)
int originalWidth = originalImage.getWidth();
if (originalWidth > 1920) {
builder.width(1920);
} else {
builder.scale(1.0); // 保持原始尺寸
}
// 处理透明背景(如果是PNG转换为JPG)
if ("image/png".equalsIgnoreCase(mimeType) && originalImage.getTransparency() != Transparency.OPAQUE) {
builder.imageType(BufferedImage.TYPE_INT_RGB); // 转换为不透明格式
}
// 执行压缩并获取结果
builder.toOutputStream(outputStream);
// 重新编码为Base64
byte[] compressedBytes = outputStream.toByteArray();
return String.format("data:%s;base64,%s",
getMimeTypeWithFormat(mimeType),
Base64.getEncoder().encodeToString(compressedBytes));
}
}
// 获取输出格式
private String getOutputFormat(String mimeType) {
return switch (mimeType.toLowerCase()) {
case "image/png" -> "png";
case "image/bmp" -> "bmp";
case "image/gif" -> "gif";
default -> "jpg"; // 默认转为JPG
};
}
// 处理MIME类型
private String getMimeTypeWithFormat(String originalMimeType) {
return originalMimeType.startsWith("image/") ? originalMimeType : "image/jpeg";
}
优化说明
- 位置选择:在读取图片后立即检查,避免后续处理无效的大图
- 溢出防护:使用
(long)
强制转换防止int
溢出 - 错误信息:明确提示允许的最大尺寸
- 逻辑顺序:先检查尺寸 → 再处理旋转和压缩