package integration.advice;
import cn.hutool.core.util.ReflectUtil;
import integration.annotation.Log;
import integration.exception.NormalBreakException;
import integration.util.Base64Utils;
import integration.util.HttpRequestUtils;
import integration.util.LoginUtil;
import integration.util.RequestThreadLocal;
import integration.util.TryCatchUtil;
import feign.Target;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import static integration.configuration.CommonConfig.isPrd;
import static integration.constant.ThreadConstants.START_TIME;
import static integration.util.AnnotationUtil.*;
import static integration.util.ErrorLogRecord.recordException;
@Component
@Aspect
@Order(110)
@Slf4j
public class LogAdvice {
private static final String[] ERROR_SUFFIX_URL = new String[]{"not postMapping or getMapping!"};
private static final String FEIGN_TARGET_CLASS_NAME = "HardCodedTarget";
private static final String FIELD_TARGET = "target";
@Pointcut("@within(com.ph.sp.integration.annotation.Log)")
public void logCut() {
}
@Before("logCut()")
public void before(JoinPoint joinPoint) {
Map<String, Object> stackValue = RequestThreadLocal.getStack();
if (stackValue != null) {
stackValue.put(costKey(joinPoint), System.currentTimeMillis());
} else {
stackValue = new HashMap<>();
stackValue.put(costKey(joinPoint), System.currentTimeMillis());
RequestThreadLocal.setRequestStack(stackValue);
}
Log log = getAnnotation(joinPoint.getTarget().getClass(), Log.class);
LogAdvice.log.info("==={} request start=== 【desc】: {} 【url】: {} 【param】: {} 【headers】:{} ",
type(joinPoint), desc(joinPoint), url(joinPoint), printObj(log.encrypt(), joinPoint.getArgs()), LoginUtil.getHeaders());
}
private String type(JoinPoint pjp) {
Class<?> tClass = pjp.getTarget().getClass();
if (isFeignClass(tClass)) {
Target.HardCodedTarget<?> target = getFeignTarget(pjp);
return target != null ? target.name() + " Feign" : "Feign";
} else if (isController(tClass)) {
return "APP";
}
return "??";
}
@AfterReturning(pointcut = "logCut()", returning = "result")
public void after(JoinPoint joinPoint, Object result) {
Log log = getAnnotation(joinPoint.getTarget().getClass(), Log.class);
LogAdvice.log.info("==={} request success=== 【desc】: {} 【url】: {} 【cost】: {}ms 【resp】: {}",
type(joinPoint), desc(joinPoint), url(joinPoint), cost(joinPoint), printObj(log.encrypt(), result));
}
@AfterThrowing(pointcut = "logCut()", throwing = "t")
public void exception(JoinPoint joinPoint, Throwable t) {
if (t instanceof NormalBreakException) {
Log log = getAnnotation(joinPoint.getTarget().getClass(), Log.class);
LogAdvice.log.info("==={} request success=== 【desc】: {} 【url】: {} 【cost】: {}ms 【resp】: {}",
type(joinPoint), desc(joinPoint), url(joinPoint), cost(joinPoint), printObj(log.encrypt(), ((NormalBreakException) t).getDefaultData()));
return;
}
recordException(t, "LogAdviceError, msg:{} 【url】: {} 【cost】: {}ms 【req】: {} 【headers】:{} ",
t.toString(), url(joinPoint), cost(joinPoint), joinPoint.getArgs(), LoginUtil.getHeaders());
}
private String costKey(JoinPoint joinPoint) {
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
return START_TIME + method.toString();
}
private long cost(JoinPoint joinPoint) {
Map<String, Object> stackValue = RequestThreadLocal.getStack();
long startTime = (long) stackValue.get(costKey(joinPoint));
return System.currentTimeMillis() - startTime;
}
private String desc(JoinPoint joinPoint) {
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
PostMapping annotation = method.getAnnotation(PostMapping.class);
return annotation != null ? annotation.name() : "";
}
private String url(JoinPoint pjp) {
Class<?> tClass = pjp.getTarget().getClass();
if (isFeignClass(tClass)) {
return getFeignUrlPre(pjp) + getUrlSuf(pjp);
} else if (isController(tClass)) {
return HttpRequestUtils.getRequestUrl();
} else {
recordException("not feign or controller request, actual:" + tClass);
return "not feign or controller request ";
}
}
private String getFeignUrlPre(JoinPoint pjp) {
Target.HardCodedTarget<?> target = getFeignTarget(pjp);
return target == null ? "" : target.url() + target.type().getAnnotation(FeignClient.class).path();
}
private static String getUrlSuf(JoinPoint joinPoint) {
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
return Optional.ofNullable(method.getAnnotation(PostMapping.class)).map(PostMapping::value)
.orElseGet(() -> Optional.ofNullable(method.getAnnotation(GetMapping.class)).map(GetMapping::value)
.orElse(ERROR_SUFFIX_URL))[0];
}
private Target.HardCodedTarget<?> getFeignTarget(JoinPoint pjp) {
Proxy target = (Proxy) pjp.getTarget();
if (target.toString().contains(FEIGN_TARGET_CLASS_NAME)) {
InvocationHandler feignInvocationHandler = Proxy.getInvocationHandler(target);
return (Target.HardCodedTarget<?>) ReflectUtil.getFieldValue(feignInvocationHandler, FIELD_TARGET);
} else {
recordException("have @FeignClient but target is not HardCodedTarget, actual:" + target.toString());
return null;
}
}
private Object printObj(boolean encrypt, Object obj) {
return isPrd && encrypt ? encrypt(obj) : obj;
}
private Object encrypt(Object obj) {
return TryCatchUtil.ignoreError(() -> Base64Utils.encode64(obj), obj);
}
}