Spring AOP 日志拦截
注解
@Inherited
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ShowEvent {
String value() default "";
//环境,默认全部环境打日志
String[] profile() default "";
}
实现
import cn.hutool.core.lang.UUID;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.google.common.collect.Maps;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
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.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import javax.validation.executable.ExecutableValidator;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;
@Order(1)
@Slf4j
@Component
@Aspect
public class LogsAspect {
private static String ING_HEADERS[] = new String[]{
"accept", "accept-encoding", "accept-language", "connection", "content-length", "cookie",
"host", "token"
};
@Value("${spring.profiles.active}")
private String env;
@Resource
private Validator validator;
@Pointcut("@annotation(com.x2era.xcloud.order.base.ShowEvent) ")
public void cutReq() {
}
@Around("cutReq()")
public Object globalLog(ProceedingJoinPoint joinPoint) throws Throwable {
Class<?> clazz = joinPoint.getTarget().getClass();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
Method realMethod = clazz.getMethod(signature.getName(), method.getParameterTypes());
ShowEvent showEvent = method.getAnnotation(ShowEvent.class);
boolean canLog = Arrays.stream(showEvent.profile()).anyMatch(pf->"".equals(pf) || StringUtils.equalsAnyIgnoreCase(pf, env));
// Class<?> returnType = realMethod.getReturnType();
Object[] args = joinPoint.getArgs();
String uuid = UUID.fastUUID().toString(true);
//校验参数
if (canLog) {
Pair<Boolean, String> validateArgs = argsValidate(joinPoint.getTarget(), realMethod, args);
if (!validateArgs.getLeft()) {
log.error(" {} 全局日志 {}", uuid, JsonUtil.toJson(LogData.builder()
.title(String.format("%s validateError, %s.%s", showEvent.value(), clazz.getSimpleName(), realMethod.getName()))
.request(getArgs(args))
.response(validateArgs.getRight())
.build()));
// log.error("{} {}参数校验不通过", showEvent.value(), method.getName());
}
}
Object result = null;
long start = DateUtils.timestamp();
try {
result = joinPoint.proceed();
} catch (IllegalArgumentException e) {
log.error("{} 全局日志 参数错误:{}", uuid, e.getMessage(), e);
return Result.error(ResultCodeEnum.ILLEGAL_PARAM_ERROR.getKey(), ResultCodeEnum.ILLEGAL_PARAM_ERROR.getValue());
} catch (Exception e) {
log.error("{} Exception occurred at .", uuid, e);
throw e;
} finally {
if (canLog) {
log.info("{} 全局日志 {}", uuid, JsonUtil.toJson(LogData.builder()
.title(String.format("%s %s.%s",showEvent.value() ,clazz.getSimpleName(), realMethod.getName()))
.header(getHeaders())
.request(getArgs(args))
.response(result)
.costs(DateUtils.timestamp() - start).build()));
}
}
return result;
}
private Map<String, String> getHeaders() {
Map<String, String> result = new HashMap<>();
RequestAttributes requestAttr = RequestContextHolder.currentRequestAttributes();
if (requestAttr instanceof ServletRequestAttributes) {
HttpServletRequest request = ((ServletRequestAttributes) requestAttr).getRequest();
if (request != null) {
Enumeration<String> hk = request.getHeaderNames();
while (hk.hasMoreElements()) {
String key = hk.nextElement();
if (!ArrayUtils.contains(ING_HEADERS, key.toLowerCase())) {
result.put(key, request.getHeader(key));
}
}
}
}
return result;
}
private Object[] getArgs(Object[] args) {
Object[] arguments = new Object[args.length];
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof ServletRequest ||
args[i] instanceof ServletResponse ||
args[i] instanceof MultipartFile ||
args[i] instanceof BindingResult ) {
//ServletRequest不能序列化,从入参里排除,否则报异常:java.lang.IllegalStateException: It is illegal to call this method if the current request is not in asynchronous mode (i.e. isAsyncStarted() returns false)
//ServletResponse不能序列化 从入参里排除,否则报异常:java.lang.IllegalStateException: getOutputStream() has already been called for this response
continue;
}
arguments[i] = args[i];
}
return arguments;
}
private Pair<Boolean, String> argsValidate(Object validateParamService, Method method, Object[] args) {
Pair<Boolean, String> validate1 = methodArgsValidate(validateParamService, method, args);
if (!validate1.getLeft()) {
return validate1;
}
Pair<Boolean, String> validate2 = methodArgsFieldValidate(args);
if (!validate2.getLeft()) {
return validate2;
}
return Pair.of(true, null);
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class LogData implements Serializable {
private String title;
private Map<String, String> header;
private Object request;
private Object response;
private Long costs;
}
/**
* 方法参数校验
*
* @param validateParamService 拦截的service
* @param method 具体拦截的方法
* @param args 方法参数
* @return true|false、errorMessage
*/
private Pair<Boolean, String> methodArgsValidate(Object validateParamService, Method method, Object[] args) {
if (ArrayUtils.isEmpty(args)) {
return Pair.of(true, null);
}
ExecutableValidator executableValidator = validator.forExecutables();
Set<ConstraintViolation<Object>> constraintViolationSet = executableValidator.validateParameters(validateParamService, method, args);
if (CollectionUtils.isEmpty(constraintViolationSet)) {
return Pair.of(true, null);
}
List<String> errorMessage = constraintViolationSet.stream()
.map(e -> e.getPropertyPath().toString() + e.getMessage())
.collect(Collectors.toList());
return Pair.of(false, JsonUtil.toJson(errorMessage));
}
/**
* 方法参数的属性校验
*
* @param args args
* @return true | false
*/
private Pair<Boolean, String> methodArgsFieldValidate(Object[] args) {
if (ArrayUtils.isEmpty(args)) {
return Pair.of(true, null);
}
for (Object arg : args) {
if (arg != null) {
Set<ConstraintViolation<Object>> error = validator.validate(arg);
if (!CollectionUtils.isEmpty(error)) {
List<String> errorMessage = error.stream()
.map(e -> e.getPropertyPath().toString() + e.getMessage())
.collect(Collectors.toList());
return Pair.of(false, JsonUtil.toJson(errorMessage));
}
}
}
return Pair.of(true, null);
}
}