开启切面
在启动类上加上注解@EnableAspectJAutoProxy
@EnableAspectJAutoProxy(proxyTargetClass=true)
编写切面类
- 切面需要的信息存储在ThreadLocal里面
@Data
public class MethodRuntimeContext {
/**
* 开始时间
*/
private Long beginTimestamp;
/**
* 结束时间
*/
private Long endTimestamp;
/**
* 方法调用时间
*/
private String actionTime;
/**
* 操作结果0失败1成功
*/
private Integer result;
/**
* 调用地址
*/
private String url;
/**
* 客户端ip
*/
private String clientIp;
/**
* 请求参数
*/
private String requestParams;
/**
* 如果有异常, 保存异常信息
*/
private String errorMsg;
/**
* 调用的用户id
*/
private Integer userId;
}
- 切面实现类
@Aspect
@Component
@Slf4j
public class SystemLogAspect {
/**
* 调用开始时间
*/
private final static ThreadLocal<MethodRuntimeContext> CONTEXT_THREAD_LOCAL = new ThreadLocal<>();
/**
* 拦截controller层所有方法调用
*/
@Pointcut("execution(* com.xxx.controller.api-Controller.*(..))")
public void controllerMethodAspect() {
}
/**
* 方法调用前逻辑
*/
@Before("controllerMethodAspect()")
public void doBefore(){
MethodRuntimeContext context = new MethodRuntimeContext();
long currentTime = System.currentTimeMillis();
context.setBeginTimestamp(currentTime);
context.setActionTime(DateUtil.date(currentTime).toString());
context.setClientIp(RequestHeaderInfoThreadLocal.getRequestHeaderInfo().getClientIp());
context.setUrl(RequestHeaderInfoThreadLocal.getRequestHeaderInfo().getUrl());
context.setUserId(RequestHeaderInfoThreadLocal.getRequestHeaderInfo().getUserId());
CONTEXT_THREAD_LOCAL.set(context);
}
@AfterReturning(value = "controllerMethodAspect()", returning = "result")
public void doAfter(JoinPoint joinPoint, Object result) {
Map<String, Object> paramMap = this.getFieldsMap(joinPoint);
CONTEXT_THREAD_LOCAL.get().setRequestParams(JSONUtil.toJsonStr(paramMap));
CONTEXT_THREAD_LOCAL.get().setEndTimestamp(System.currentTimeMillis());
if(result instanceof JsonObject){
if(((JsonObject) result).isSuccess()) {
CONTEXT_THREAD_LOCAL.get().setResult(1);
}else{
CONTEXT_THREAD_LOCAL.get().setResult(0);
CONTEXT_THREAD_LOCAL.get().setErrorMsg(((JsonObject) result).getMsg());
}
}
try {
systemLogService.addSystemLog(CONTEXT_THREAD_LOCAL.get());
}catch (Exception ignored){
}finally {
CONTEXT_THREAD_LOCAL.remove();
}
}
/**
* 获取调用参数
* @param joinPoint
* @return
*/
@SneakyThrows
private Map<String, Object> getFieldsMap(JoinPoint joinPoint){
String classType = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
// 参数值
Object[] args = joinPoint.getArgs();
Class<?>[] classes = new Class[args.length];
for (int k = 0; k < args.length; k++) {
// 对于接受参数中含有MultipartFile,ServletRequest,ServletResponse类型的特殊处理,我这里是直接返回了null。(如果不对这三种类型判断,会报异常)
if (args[k] instanceof MultipartFile || args[k] instanceof ServletRequest || args[k] instanceof ServletResponse) {
return new HashMap<>();
}
if (!args[k].getClass().isPrimitive()) {
// 当方法参数是封装类型
Class s = args[k].getClass();
classes[k] = s == null ? args[k].getClass() : s;
}
}
ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer();
// 获取指定的方法,第二个参数可以不传,但是为了防止有重载的现象,还是需要传入参数的类型
Method method = Class.forName(classType).getMethod(methodName, classes);
// 参数名
String[] parameterNames = pnd.getParameterNames(method);
// 通过map封装参数和参数值
Map<String, Object> paramMap = new HashMap<>();
if(parameterNames != null) {
for (int i = 0; i < parameterNames.length; i++) {
paramMap.put(parameterNames[i], args[i]);
}
}
return paramMap;
}
}