import com.rum.logger.exception.BusinessException;
import com.corporate.info.common.domain.SubscribeSystem;
import com.corporate.info.domain.InterfaceVisitRecord;
import com.corporate.info.service.InterfaceVisitRecordService;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
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.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.Base64Utils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import static com.corporate.info.domain.InterfaceVisitConstants.FAIL_STATUS;
import static com.corporate.info.domain.InterfaceVisitConstants.SUCCESS_STATUS;
/**
* 统计接口访问记录处理切面
*/
@Aspect
@Slf4j
@Component
public class InterfaceVisitRecordAspect {
private static final String SIGN = "sign";
private static final String MATCH_PATTERN_NAME = "org.springframework.web.servlet.HandlerMapping.bestMatchingPattern";
@Value("${server.servlet.context-path}")
private String baseUrl;
@Autowired
private InterfaceVisitRecordService service;
@Resource
private ObjectMapper objectMapper;
private ThreadLocal<InterfaceVisitRecord> recordEntityThreadLocal = new ThreadLocal<>();
/**
* 定义切点表达式:统计所有的controller的接口
*/
@Pointcut("execution(* com.cxb.info.controller.*.*(..))")
public void interfaceVisitRecord() {
}
/**
* 通知方法会将目标方法封装起来
*
* @param joinPoint
* @return
* @throws Throwable
*/
@Around("interfaceVisitRecord()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
try {
InterfaceVisitRecord visitRecordEntity = new InterfaceVisitRecord();
visitRecordEntity.setCreateTime(LocalDateTime.now());
recordEntityThreadLocal.set(visitRecordEntity);
} catch (Exception e) {
log.error("接口访问统计doAround访问接口前异常,{}", e.getMessage());
}
// 获取访问的结果
Object result = joinPoint.proceed();
try {
InterfaceVisitRecord visitRecordEntity = recordEntityThreadLocal.get();
handleResultInfo();
service.saveRecordToRedisByHour(visitRecordEntity);
recordEntityThreadLocal.remove();
} catch (Exception e) {
log.error("接口访问统计doAround访问接口后异常,{}", e.getMessage());
}
return result;
}
/**
* 异常返回通知,记录异常请求日志
*
* @param joinPoint
* @param e
*/
@AfterThrowing(pointcut = "interfaceVisitRecord()", throwing = "e")
public void handleException(JoinPoint joinPoint, Throwable e) {
try {
// 每次都会先进入around然后有异常才会进入
handleResultInfo();
handelExceptionResultInfo(joinPoint, e);
InterfaceVisitRecord visitRecordEntity = recordEntityThreadLocal.get();
service.saveRecordToRedisByHour(visitRecordEntity);
recordEntityThreadLocal.remove();
} catch (Exception resultException) {
log.error("接口访问统计切面保存日志异常,{}", resultException.getMessage());
}
}
/**
* 处理接口返回记录的产品信息和请求的信息等
*
* @return
*/
private void handleResultInfo() {
try {
InterfaceVisitRecord visitRecordEntity = recordEntityThreadLocal.get();
//获取当前请求对象
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String sign = request.getHeader(SIGN);
SubscribeSystem subscribeSystem = objectMapper.readValue(Base64Utils.decodeFromString(sign), SubscribeSystem.class);
visitRecordEntity.setProductId(subscribeSystem.getProductId());
visitRecordEntity.setValidateWay(subscribeSystem.getValidateWay());
visitRecordEntity.setBusinessType(subscribeSystem.getBusinessType());
// 设置请求的方法
visitRecordEntity.setRequestMethod(request.getMethod());
String bestMatchingPattern = request.getAttribute(MATCH_PATTERN_NAME).toString();
visitRecordEntity.setRequestOriginUri(baseUrl + bestMatchingPattern);
String actualUri = StringUtils.isEmpty(request.getQueryString()) ? request.getRequestURI() : request.getRequestURI() + "?" + request.getQueryString();
visitRecordEntity.setRequestActualUri(actualUri);
LocalDateTime visitEndTime = LocalDateTime.now();
// 计算时间差
Duration between = Duration.between(visitRecordEntity.getCreateTime(), visitEndTime);
visitRecordEntity.setConsumeTime((int) between.toMillis());
visitRecordEntity.setEndTime(visitEndTime);
// 默认设置为成功,异常会赋值1
visitRecordEntity.setStatus(SUCCESS_STATUS);
recordEntityThreadLocal.set(visitRecordEntity);
} catch (Exception e) {
log.error("接口访问统计切面保存日志handleResultInfo方法异常,{}", e.getMessage());
}
}
/**
* 处理异常的请求参数信息和错误信息
*/
public void handelExceptionResultInfo(JoinPoint joinPoint, Throwable e) {
try {
InterfaceVisitRecord visitRecordEntity = recordEntityThreadLocal.get();
// 从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 失败状态
visitRecordEntity.setStatus(FAIL_STATUS);
// 异常信息
if (e instanceof BusinessException) {
BusinessException businessException = (BusinessException) e;
String errorMessage = businessException.getErrorDetails().get(0).getErrorMessage();
visitRecordEntity.setErrorMessage(errorMessage);
} else {
visitRecordEntity.setErrorMessage(e.getMessage());
}
// 请求参数
String[] parameterNames = signature.getParameterNames();
Object[] args = joinPoint.getArgs();
Map<String, Object> paramMap = new HashMap<>();
for (int i = 0; i < parameterNames.length; i++) {
paramMap.put(parameterNames[i], args[i]);
}
visitRecordEntity.setRequestContent(paramMap.toString());
} catch (Exception e1) {
log.error("接口访问统计切面保存日志handelExceptionResultInfo方法异常,{}", e1.getMessage());
}
}
}