背景
公司需要新增一个配置,用于控制文件存储到本地还是MinIO服务器,使用AOP动态代理中的环绕通知控制MINIO上传逻辑。
代码
- 自定义注解,用于接收切点方法参数信息
/**
* AOP执行自定义存储的方法
*
* @author Lyr
* @projectName ecgnetwork
* @date 2023/03/15 16:07
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface StorageAopMethod {
/**
* 指定storage中的执行方法
*/
String storageMethod();
/**
* 桶名
*/
int bucketName();
/**
* 全限定文件路径参数在方法参数中的位置下标
* 参数类型必须为String
*/
int pathIndex();
/**
* 参数文件位置
*/
int paramFilePosition() default -1;
}
- 定义切点
/**
* 文件下载
*
* @param bucketName 桶名称
* @param objectName 路径
* @return java.io.InputStream
* @author Lyr
* @date 2023/3/16 17:57
*/
@StorageAopMethod(storageMethod = "download", bucketName = 0, pathIndex = 1)
public InputStream download(String bucketName, String objectName) {
MinioClient minioClient = getMinIoClient(bucketName);
if (minioClient == null) {
return null;
}
try {
minioClient.statObject(bucketName, objectName);
} catch (Exception e) {
log.error("objectName no exist {}", e.getMessage(), e);
return null;
}
try {
return minioClient.getObject(bucketName, objectName);
} catch (Exception e) {
log.error("objectName download error {}", e.getMessage(), e);
return null;
}
}
- 定义切面类,环绕通知执行本地上传逻辑
import com.alibaba.fastjson.JSON;
import com.medwings.ecgnetwork.annotation.StorageAopMethod;
import com.medwings.storage.request.UploadParameter;
import com.medwings.storage.server.UploadFileService;
import lombok.extern.slf4j.Slf4j;
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.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.net.*;
import java.util.Enumeration;
/**
* MinIO切面类 处理
*
* @author Lyr
* @projectName ecgnetwork
* @date 2023/03/15 11:40
*/
@Slf4j
@Aspect
@Component
public class MinIoToStorageAspect {
@Value("${minio.enable}")
private boolean enableMinIo;
@Value("${minio.storage-disk}")
private String disk;
@Value("${server.port}")
private String port;
/**
* 定义切点:
* 在MinioConfig类中将所有被@StorageAopMethod注解过的方法为切点
*
* @param
* @return void
* @author Lyr
* @date 2023/3/15 16:35
*/
@Pointcut("within(com.medwings.ecgnetwork.config.MinioConfig) && @annotation(com.medwings.ecgnetwork.annotation.StorageAopMethod)")
public void storageAspectLog() {
log.info("执行的MinIO的AOP逻辑");
}
/**
* AOP环绕处理
*
* @param joinPoint joinPoint
* @return java.lang.Object
* @author Lyr
* @date 2023/3/15 14:59
*/
@Around("storageAspectLog()")
public Object storageAspect(ProceedingJoinPoint joinPoint) throws Throwable {
if (enableMinIo) {
Object proceed = joinPoint.proceed();
log.info("MinIO处理完成:{}", proceed);
return proceed;
}
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
//获取代理对象实例
UploadFileService uploadFileService = UploadFileService.getUploadFileService();
//通过注解获取执行storage方法名称、相关参数信息
StorageAopMethod storageAopMethod = method.getAnnotation(StorageAopMethod.class);
//获取执行storage方法名称
String storageMethodName = storageAopMethod.storageMethod();
log.info("AOP处理MinIO相关逻辑:{}", storageMethodName);
//获取代理类class对象
Class<UploadFileService> uploadFileServiceClass = UploadFileService.class;
//获取被代理方法实参
Object[] args = joinPoint.getArgs();
//上传逻辑单独处理
Object results;
if ("upload".equals(storageMethodName)) {
UploadParameter uploadParameter = this.storageUpload(storageAopMethod, args);
Method storageMethod = uploadFileServiceClass.getMethod(storageMethodName, UploadParameter.class);
results = storageMethod.invoke(uploadFileService, uploadParameter);
String url = this.getServerUrl();
results = url + results;
} else {
String bucketName = args[storageAopMethod.bucketName()].toString();
String path = args[storageAopMethod.pathIndex()].toString();
Method storageMethod = uploadFileServiceClass.getMethod(storageMethodName, String.class);
results = storageMethod.invoke(uploadFileService, this.pathSplice(path, bucketName));
}
log.info("AOP处理MinIO相关逻辑完成,返回值:{}", JSON.toJSONString(results));
return results;
}
/**
* 处理上传参数处理
*
* @param storageAopMethod storage服务中的上传方法
* @param args minIo入参
* @return com.medwings.storage.request.UploadParameter
* @author Lyr
* @date 2023/3/17 17:25
*/
private UploadParameter storageUpload(StorageAopMethod storageAopMethod, Object[] args) {
UploadParameter uploadParameter = new UploadParameter();
String bucketName = args[storageAopMethod.bucketName()].toString();
String path = args[storageAopMethod.pathIndex()].toString();
String s = this.pathSplice(path, bucketName);
uploadParameter.setFilePath(s);
int i = storageAopMethod.paramFilePosition();
uploadParameter.setContent(args[i]);
return uploadParameter;
}
/**
* 桶名与参数路径拼接
*
* @param path 路径
* @param bucketName 桶名称
* @return java.lang.String
* @author Lyr
* @date 2023/3/21 16:02
*/
private String pathSplice(String path, String bucketName) {
return disk + bucketName + "/" + path;
}
/**
* 获取当前 Java 服务的地址和端口号
*
* @return 地址和端口号,格式为 "http://<hostname>:<port>"
* @throws UnknownHostException 如果无法解析本机的主机名时抛出此异常
*/
private String getServerUrl() throws SocketException, UnknownHostException {
String localAddr = null;
Enumeration<NetworkInterface> ifaces = NetworkInterface.getNetworkInterfaces();
while (ifaces.hasMoreElements()) {
NetworkInterface iface = ifaces.nextElement();
Enumeration<InetAddress> addrs = iface.getInetAddresses();
while (addrs.hasMoreElements()) {
InetAddress addr = addrs.nextElement();
if (!addr.isLoopbackAddress() && addr instanceof Inet4Address) {
localAddr = addr.getHostAddress();
break;
}
}
if (localAddr != null) {
break;
}
}
if (localAddr == null) {
localAddr = InetAddress.getLocalHost().getHostAddress();
}
String format = String.format("http://%s:%s", localAddr, port);
return format + "/api/minio/getFileInputStream?id=";
}
总结:
- 当方法执行到MinIO上传逻辑时,进入切面方法
- 在环绕逻辑里根据配置判断是执行原方法还是继续执行切面方法
- 原方法上传到MinIO环绕逻辑继续执行则将文件信息上传到本地