通过Aop实现的方式比较简单:
1.自定义一个注解,让它可用于 想要记录日志的方法上;
2.通过Aop 统一处理这些标记了自定义注解的类;
3.在Aop通知中添加逻辑,获取操作日志想要记录的信息,最后添加到自己设计的操作日志表里去;功能完成;
1.自定义一个注解,让它可用于 想要记录日志的方法上;
package com.lance.flowable.operationLog;
import java.lang.annotation.*;
/**
* @description:自定义操作日志注解
*/
@Target(ElementType.METHOD)//描述注解使用范围,现在代表可以在方法上使用
@Retention(RetentionPolicy.RUNTIME)//标识注解的生命周期
@Documented //元注解,
public @interface Log {
//下面的属性可以自定义
/**
* 操作模块
* @return
*/
String modul() default "";
/**
* 操作类型
* @return
*/
String type() default "";
/**
* 操作说明
* @return
*/
String desc() default "";
}
2.通过Aop 统一处理这些标记了自定义注解的类;
3.在Aop通知中添加逻辑,获取操作日志想要记录的信息,最后添加到自己设计的操作日志表里去;功能完成;
package com.lance.flowable.operationLog;
import com.alibaba.fastjson.JSON;
import com.lance.flowable.utils.IPUtils;
import com.lance.flowable.utils.SecurityUserUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
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.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
/**
* @description:切面处理类,操作日志,异常日志,记录,处理
*/
@Aspect
@Component
public class LogAspect {
/**
* 操作版本号
* 项目启动时从命令行传入,例如:java -jar xxx.war --version=201902
*/
@Value("${version}")
private String version;
/**
* 统计请求的处理时间
*/
ThreadLocal<Long> startTime = new ThreadLocal<>();
@Autowired
private LogInfoService logInfoService;
@Autowired
private LogErrorInfoService logErrorInfoService;
/**
* @methodName:logPoinCut
* @description:设置操作日志切入点 记录操作日志 在注解的位置切入代码
* @author:tanyp
* @dateTime:2021/11/18 14:22
* @Params: []
* @Return: void
* @editNote:
*/
@Pointcut("execution(* com.lance.flowable.controller..*.*(..))")
public void logPoinCut() {
}
/**
* @methodName:exceptionLogPoinCut
* @description:设置操作异常切入点记录异常日志 扫描所有controller包下操作
* @author:tanyp
* @dateTime:2021/11/18 14:22
* @Params: []
* @Return: void
* @editNote:
*/
@Pointcut("execution(* com.lance.flowable.controller..*.*(..))")
public void exceptionLogPoinCut() {
}
//前置通知,
@Before("logPoinCut()")
public void doBefore() {
// 接收到请求,记录请求开始时间
startTime.set(System.currentTimeMillis());
}
/**
* @methodName:doAfterReturning
* @description:正常返回通知,拦截用户操作日志,连接点正常执行完成后执行, 如果连接点抛出异常,则不会执行
* @author:tanyp
* @Params: [joinPoint, keys] 连接点
* @Return: void
* @editNote:
* // 声明keys时指定的类型会限制目标方法必须返回指定类型的值或没有返回*值
* // 此处将rvt的类型声明为Object,意味着对目标方法的返回值不加限制
* 1)pointcut/value:这两个属性的作用是一样的,它们都属于指定切入点对应的切入表达式。一样既可以是已有的切入点,也可直接定义切入点表达式。当指定了pointcut属性值后,value属性值将会被覆盖。
*2)returning:该属性指定一个形参名,用于表示Advice方法中可定义与此同名的形参,该形参可用于访问目标方法的返回值。除此之外,在Advice方法中定义该形参(代表目标方法的返回值)时指定的类型,会限制目标方法必须返回指定类型的值或没有返回值。 */
@AfterReturning(value = "logPoinCut()", returning = "keys")
public void doAfterReturning(JoinPoint joinPoint, Object keys) {
// 获取RequestAttributes
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// 从获取RequestAttributes中获取HttpServletRequest的信息
HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
com.demo.domain.LogInfo logInfo = com.demo.domain.LogInfo.builder().build();
try {
// 从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取切入点所在的方法
Method method = signature.getMethod();
// 获取请求的类名
String className = joinPoint.getTarget().getClass().getName();
// 获取操作
Log log = method.getAnnotation(Log.class);
if (Objects.nonNull(log)) {
logInfo.setModule(log.modul());
logInfo.setType(log.type());
logInfo.setMessage(log.desc());
}
logInfo.setId(UUID.randomUUID().toString());
logInfo.setMethod(className + "." + method.getName()); // 请求的方法名
logInfo.setReqParam(JSON.toJSONString(converMap(request.getParameterMap()))); // 请求参数
logInfo.setResParam(JSON.toJSONString(keys)); // 返回结果
logInfo.setUserId(SecurityUserUtils.getUser().getId()); // 请求用户ID
logInfo.setUserName(SecurityUserUtils.getUser().getUsername()); // 请求用户名称
logInfo.setIp(IPUtils.getIpAddress(request)); // 请求IP
logInfo.setUri(request.getRequestURI()); // 请求URI
logInfo.setCreateTime(LocalDateTime.now()); // 创建时间
logInfo.setVersion(version); // 操作版本
logInfo.setTakeUpTime(System.currentTimeMillis() - startTime.get()); // 耗时
logInfoService.save(logInfo);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* @methodName:doAfterThrowing
* @description:异常返回通知,用于拦截异常日志信息 连接点抛出异常后执行
* @author:tanyp
* @dateTime:2021/11/18 14:23
* @Params: [joinPoint, e]
* @Return: void
* @editNote:
*/
@AfterThrowing(pointcut = "exceptionLogPoinCut()", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
// 获取RequestAttributes
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// 从获取RequestAttributes中获取HttpServletRequest的信息
HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
try {
// 从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取切入点所在的方法
Method method = signature.getMethod();
// 获取请求的类名
String className = joinPoint.getTarget().getClass().getName();
logErrorInfoService.save(
LogErrorInfo.builder()
.id(UUID.randomUUID().toString())
.reqParam(JSON.toJSONString(converMap(request.getParameterMap()))) // 请求参数
.method(className + "." + method.getName()) // 请求方法名
.name(e.getClass().getName()) // 异常名称
.message(stackTraceToString(e.getClass().getName(), e.getMessage(), e.getStackTrace())) // 异常信息
.userId(SecurityUserUtils.getUser().getId()) // 操作员ID
.userName(SecurityUserUtils.getUser().getUsername()) // 操作员名称
.uri(request.getRequestURI()) // 操作URI
.ip(IPUtils.getIpAddress(request)) // 操作员IP
.version(version) // 版本号
.createTime(LocalDateTime.now()) // 发生异常时间
.build()
);
} catch (Exception e2) {
e2.printStackTrace();
}
}
/**
* @methodName:converMap
* @description:转换request 请求参数
* @author:tanyp
* @dateTime:2021/11/18 14:12
* @Params: [paramMap]
* @Return: java.util.Map<java.lang.String, java.lang.String>
* @editNote:
*/
public Map<String, String> converMap(Map<String, String[]> paramMap) {
Map<String, String> rtnMap = new HashMap<String, String>();
for (String key : paramMap.keySet()) {
rtnMap.put(key, paramMap.get(key)[0]);
}
return rtnMap;
}
/**
* @methodName:stackTraceToString
* @description:转换异常信息为字符串
* @author:tanyp
* @dateTime:2021/11/18 14:12
* @Params: [exceptionName, exceptionMessage, elements]
* @Return: java.lang.String
* @editNote:
*/
public String stackTraceToString(String exceptionName, String exceptionMessage, StackTraceElement[] elements) {
StringBuffer strbuff = new StringBuffer();
for (StackTraceElement stet : elements) {
strbuff.append(stet + "
");
}
String message = exceptionName + ":" + exceptionMessage + "
" + strbuff.toString();
return message;
}
}
1. @Aspect 标记在类上 , 代表为切面类
2. @Component 标记在类上,通过扫描 创建对象
3. @Pointcut 标记在方法上,方法不需要返回值,不需要参数, 用来配置切入点表达式
4. @Before 前置增强,标记在方法上,参数指定切入点表达式对应的方法名
5. @After 最终增强,标记在方法上,参数指定切入点表达式对应的方法名
6. @AfterReturning 后置增强,标记在方法上,参数指定切入点表达式对应的方法名
7. @AfterThrowing 异常增强,标记在方法上,参数指定切入点表达式对应的方法名
8. @Around 环绕增强,一般单独使用,不会与前四种增强混合,标记在方法上,参数指定切入点表达式对应的方法名
9. <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
开启aop的注解支持,
10. @EnableAspectJAutoProxy 纯注解时 开启aop注解支持
原因1.启动类没有扫描到Aop的类,也就是启动类或者AOP类的位置不对;修改位置或者@ComponentScan扫描
原因2.没有正确创建类; 正确的做法: 通过Java Class创建aspect类,然后加上 @Aspect和@Component注解。 我试了从Java Class创建aspect类,把之前无效的代码完整拷贝到这个新建的文件里,就生效了。至于上面那种创建aspect类为何会导致无效的内在原因,还不清楚,有知情的朋友请留言回复。 虽然两种创建方式不同,但最终代码呈现完全是一样的,就是一个有用一个无用。
原因3:以上两个方法没有解决我的问题: 最终是在 启动类上添加 @EnableAspectJAutoProxy 纯注解时 (开启aop注解支持 );
默认情况下SpringBoot是自动支持这个注解的,但是不知道为什么没用;
2022/08/02