使用 AOP 实现附件上传时压缩图片
运行效果
在附件上传过程中,对图片(例如:jpg、jpeg、png 等)文件进行压缩,以减小存储空间
上图是未压缩上传的图片,下图是压缩后上传的图片,宽高都是原来的50%
实现方法
- 拦截文件上传事件,在文件保存前对文件进行压缩或是其他需求处理
- 使用 Java 后端 springboot aop 技术 @before 实现文件上传前拦截处理
- 实现图片压缩
特别说明
- 使用 aop 实现文件拦截必须是在同一微服务应用内,不支持跨应用
- 当前案例通过aop接管了“/storage/postObject”,因此平台中无论是附件组件、图片组件还是平台数据对应的数据集新增图片等都会受此影响。
使用本地 IDE
Java 后端开发,建议使用本地 IDE
本地 IDE 配置(以 IDEA 开发工具为例)完成后,导入 main 模块,并刷新加载 maven 依赖包,确认 storage.jar 包已加载(很重要,只有正确加载 storage 依赖包后面的操作才有效)
新增上传文件实体类
新建 main.aspect 包,新增 MultipartFileDto 类(上传文件实体类)
MultipartFileDto 类代码如下
ckage main.aspect;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.multipart.MultipartFile;
public class MultipartFileDto implements MultipartFile {
private final String name;
private String originalFilename;
private String contentType;
private final byte[] content;
/**
* Create a new MultipartFileDto with the given content.
* @param name the name of the file
* @param content the content of the file
*/
public MultipartFileDto(String name, byte[] content) {
this(name, "", null, content);
}
/**
* Create a new MultipartFileDto with the given content.
* @param name the name of the file
* @param contentStream the content of the file as stream
* @throws IOException if reading from the stream failed
*/
public MultipartFileDto(String name, InputStream contentStream) throws IOException {
this(name, "", null, FileCopyUtils.copyToByteArray(contentStream));
}
/**
* Create a new MultipartFileDto with the given content.
* @param name the name of the file
* @param originalFilename the original filename (as on the client's machine)
* @param contentType the content type (if known)
* @param content the content of the file
*/
public MultipartFileDto(String name, String originalFilename, String contentType, byte[] content) {
this.name = name;
this.originalFilename = (originalFilename != null ? originalFilename : "");
this.contentType = contentType;
this.content = (content != null ? content : new byte[0]);
}
/**
* Create a new MultipartFileDto with the given content.
* @param name the name of the file
* @param originalFilename the original filename (as on the client's machine)
* @param contentType the content type (if known)
* @param contentStream the content of the file as stream
* @throws IOException if reading from the stream failed
*/
public MultipartFileDto(String name, String originalFilename, String contentType, InputStream contentStream)
throws IOException {
this(name, originalFilename, contentType, FileCopyUtils.copyToByteArray(contentStream));
}
@Override
public String getName() {
return this.name;
}
@Override
public String getOriginalFilename() {
return this.originalFilename;
}
@Override
public String getContentType() {
return this.contentType;
}
@Override
public boolean isEmpty() {
return (this.content.length == 0);
}
@Override
public long getSize() {
return this.content.length;
}
@Override
public byte[] getBytes() throws IOException {
return this.content;
}
@Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(this.content);
}
@Override
public void transferTo(File dest) throws IOException, IllegalStateException {
FileCopyUtils.copy(this.content, dest);
}
}
新增 AOP 拦截处理类
在 main.aspect 包中,新增 FileAspect 类(aop 拦截处理类),使用标准 springboot aop 进行开发即可,代码如下
package main.aspect;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.imageio.ImageIO;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import cn.hutool.core.util.StrUtil;
@Aspect
@Component
public class FileAspect{
private static final Logger logger = LoggerFactory.getLogger(FileAspect.class);
//新增文件上传切面点
@Pointcut("execution(* storage.controller.StorageController.postObject(..))")
public void doSomethingByUpload() {}
@Around("doSomethingByUpload()")
public Object before(ProceedingJoinPoint joinPoint) throws Throwable {
Object returnVal = null;
Object[] args=joinPoint.getArgs();
//获取文件
MultipartFile file=(MultipartFile) args[0];
//获取文件名
String storeFileName=(String) args[1];
//获取图片类型
String fileType=storeFileName.substring(storeFileName.lastIndexOf(".")+1);
if(StrUtil.contains("jpg,jpeg,png,bmp", fileType.toLowerCase())) {
logger.warn("========================图片:{},已被系统压缩==============================",storeFileName);
BufferedImage compressedImage=this.compressImage(file.getInputStream());
// 将压缩后的图片转换回MultipartFile
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(compressedImage, fileType, baos);
byte[] imageBytes = baos.toByteArray();
MultipartFile compressedFile = new MultipartFileDto("userfile", file.getOriginalFilename(),"image/"+fileType, imageBytes);
// 替换原始的MultipartFile参数
args[0]=compressedFile;
}
// 执行切点方法,并传递重新赋值后的参数列表
returnVal = joinPoint.proceed(args);
return returnVal;
}
//图片压缩
private BufferedImage compressImage(InputStream is) throws IOException {
BufferedImage originalImage = ImageIO.read(is);
int width = originalImage.getWidth() / 2;
int height = originalImage.getHeight() / 2;
BufferedImage compressedImage = new BufferedImage(width, height, originalImage.getType());
compressedImage.getGraphics().drawImage(originalImage, 0, 0, width, height, null);
return compressedImage;
}
}
上述开发完成后,新建有附件上传功能页面进行测试