最近项目中需要记录日志,跟大家想的一样 ,利用spring aop去实现,之前也参考了一些代码,自己实现了一套设计,供大家参考。
之前看到网上很多是基于切面类Aspect去实现了,在切面类中定义before after around等逻辑以及要拦截等方法。本文利用注解实现了一套可以扩展等日志记录模块。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public abstract @interface RequiredLogInterceptor {
boolean required() default true;
String targetGenerator() default "";
OperateType operateType() default OperateType.GET;
}
requried:注解是否生效
targetGenerator: 每个模块记录等内容不同,入口参数不同,所以需要个性化定制日志等记录内容,每个模块的日志生成有自己定义的generator类,并且重写generateContent方法。
operateType:当前方法是增加,删除,还是修改
public abstract class ContentGerator {
public static String SPLIT="/";
public static String CONTENT_SPLIT=",";
public static String VALUE_SPLIT=":";
abstract List generateContent(Object returnValue, Object[] args, OperateType operateType);
}
本模块主要是后置通知,主要逻辑如下:
1.拦截方法,判断是否有注解loginterceptor
2. 如果有判断是否执行成功,成功则记录log,失败不记录
3. 获取注解中配置的generator类,利用反射调用generateContent方法,生成个性化日志内容
5.在日志中添加其他公共属性,比如用户id,创建时间等等。所有个性化定制的日志信息都是在generator类中产生。
public class LogAfterInterceptor implements AfterReturningAdvice {
@Autowired
private LogService logService;
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
RequiredLogInterceptor requiredLogInterceptor = AnnotationUtils.findAnnotation(method, RequiredLogInterceptor.class);
if (requiredLogInterceptor != null) {
if(returnValue!=null&&returnValue instanceof Response){
Response response=(Response)returnValue;
String code=response.getCode();
String code200= MegCodeEnums.ResponseCodeEnum.C200.getCode();
String code201= MegCodeEnums.ResponseCodeEnum.C201.getCode();
if (!Strings.isNullOrEmpty(code)&&!code.equalsIgnoreCase(code200)&&!code.equalsIgnoreCase(code201)){
return;
}
}
String targetGeneratorName=requiredLogInterceptor.targetGenerator();
OperateType operateType=requiredLogInterceptor.operateType();
Class targetGeneratorclass=Class.forName("com.puhui.flowplatform.manage.log."+targetGeneratorName);
Method executeMethod=targetGeneratorclass.getMethod("generateContent",Object.class,Object[].class,OperateType.class);
ContentGerator targetGeneratorBean=(ContentGerator)SpringContextHolder.getBean(targetGeneratorclass);
List userOperateLogList=(List)executeMethod.invoke(targetGeneratorBean,returnValue,args,operateType);
if(CollectionUtils.isNotEmpty(userOperateLogList)){
userOperateLogList.forEach(userOperateLog -> {
userOperateLog.setCreateTime(new Date());
//token
long userId=0L;
if (args.length>0&&args[0] instanceof String){
userId = CommonUtils.getManageCurUserId(args[0].toString());
}
userOperateLog.setUserId(userId);
});
logService.batchInsertLog(userOperateLogList);
}
}
}
}
继承统一的ContentGenerator类,便于共享一些常量。根据当前操作类型,生成对应的日志内容就可以了。如果需要新增模块, 先定义自己的日志generator类,然后添加注解到对应模块就可以。
@Service
public class ContentGeneratorForRoleMgt extends ContentGerator {
@Autowired
private MenuService menuService;
private String generateMenus(VoRole voRole){
List menusList=voRole.getMenusList();
StringBuffer stringBuffer=new StringBuffer();
if (CollectionUtils.isNotEmpty(menusList)){
menusList.forEach(menus -> {
Long menuId=menus.getId();
Menus menusTemp=menuService.queryMenuByMenuId(menuId);
stringBuffer.append(menusTemp.getDisplayTitle()+CONTENT_SPLIT);
});
stringBuffer.deleteCharAt(stringBuffer.length() - 1);
}
return stringBuffer.toString();
}
@Override
public List generateContent(Object returnValue, Object[] args, OperateType operateType) {
{
List userOperateLogList=new ArrayList<>();
UserOperateLog userOperateLog=new UserOperateLog();
if (operateType==OperateType.ADD||operateType==OperateType.UPDATE){
VoRole voRole=(VoRole)args[1];
String menus=generateMenus(voRole);
userOperateLog.setOperateContent("角色名称"+VALUE_SPLIT+voRole.getDisplayName()+SPLIT+"权限"+VALUE_SPLIT+menus);
userOperateLog.setOperateType(operateType==OperateType.ADD?LogOperateTypeEnum.ADD_ROLE.getCode():LogOperateTypeEnum.UPDATE_ROLE.getCode());
}
if (operateType==OperateType.DELETE){
if(returnValue!=null){
Response response=(Response) returnValue;
String roleName=response.getData().toString();
userOperateLog.setOperateContent(roleName);
userOperateLog.setOperateType(LogOperateTypeEnum.DELETE_ROLE.getCode());
}
}
userOperateLogList.add(userOperateLog);
return userOperateLogList;
}
}
}
@PutMapping(value = "roles/{roleId}")
@RequiredLogInterceptor(targetGenerator = "ContentGeneratorForRoleMgt",operateType= OperateType.UPDATE)
@ApiOperation(value = "修改角色", httpMethod = "PUT", response = Response.class, notes = "修改角色")
public Response
public class SpringMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void configureMessageConverters(List> converters) {
super.configureMessageConverters(converters);
// 初始化转换器
FastJsonHttpMessageConverter fastConvert = new FastJsonHttpMessageConverter();
// 初始化一个转换器配置
FastJsonConfig fastJsonConfig = new FastJsonConfig();
fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
// 将配置设置给转换器并添加到HttpMessageConverter转换器列表中
fastConvert.setFastJsonConfig(fastJsonConfig);
converters.add(fastConvert);
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/swagger-ui.html").addResourceLocations(
ResourceUtils.CLASSPATH_URL_PREFIX + "/META-INF/resources/");
registry.addResourceHandler("/static/**").addResourceLocations(ResourceUtils.CLASSPATH_URL_PREFIX + "/static/",
ResourceUtils.CLASSPATH_URL_PREFIX + "/dist/static/");
registry.addResourceHandler("/page/**").addResourceLocations(ResourceUtils.CLASSPATH_URL_PREFIX + "/dist/");
super.addResourceHandlers(registry);
}
@Bean
public ViewResolver viewResolver() {
FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();
resolver.setCache(true);
resolver.setPrefix(ResourceUtils.CLASSPATH_URL_PREFIX + "templates/");
resolver.setSuffix(".ftl");
resolver.setContentType("text/html; charset=UTF-8");
return resolver;
}
// 创建Advice或Advisor
@Bean
public BeforeAdvice beforeControllerInterceptor() {
return new BeforeControllerInterceptor();
}
@Bean
public AfterAdvice logAfterInterceptor() {
return new LogAfterInterceptor();
}
// 创建Advice或Advisor
@Bean
public BeforeAdvice logBeforeInterceptor() {
return new LogBeforeInterceptor();
}
// 使用BeanNameAutoProxyCreator来创建代理
@Bean
public BeanNameAutoProxyCreator beanAutoProxyCreator() {
BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator();
beanNameAutoProxyCreator.setProxyTargetClass(true); // 设置要创建代理的那些Bean的名字
beanNameAutoProxyCreator.setBeanNames("*Controller"); //
// 设置拦截链名字(这些拦截器是有先后顺序的)
beanNameAutoProxyCreator.setInterceptorNames("logAfterInterceptor");
return beanNameAutoProxyCreator;
}
@Bean
public BeanNameAutoProxyCreator beanBeforeAutoProxyCreator() {
BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator();
beanNameAutoProxyCreator.setProxyTargetClass(true);
// 设置要创建代理的那些Bean的名字
beanNameAutoProxyCreator.setBeanNames("*Controller");
// 设置拦截链名字(这些拦截器是有先后顺序的)
beanNameAutoProxyCreator.setInterceptorNames("beforeControllerInterceptor");
beanNameAutoProxyCreator.setInterceptorNames("logBeforeInterceptor");
本来实现都代码版本中,所有都日志生成代码都在后置拦截器中,并且根据当前执行都方法都classname和methodname去判断当前都方法,出现很多if 判断,且method name都不一样,有的是addXXX,有的是createXXX,显然设计不合理。后来重新进行了设计,有什么不足,希望大家可以指出。
package com.puhui.flowplatform.manage.interceptor;
import com.puhui.flowplatform.common.model.manage.UserOperateLog;
import com.puhui.flowplatform.manage.service.LogService;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import java.lang.reflect.Method;
import java.util.Date;
@Aspect
public class LogAspect {
public Long id=null;
@Autowired
LogService logService;
/**
* 添加业务逻辑方法切入点
*/
@Pointcut("execution(* com.puhui.flowplatform.manage.service.*.add*(..))")
public void insertCell() {
}
/**
* 修改业务逻辑方法切入点
*/
@Pointcut("execution(* com.puhui.flowplatform.manage.service.*.update*(..))")
public void updateCell() {
}
/**
* 删除业务逻辑方法切入点
*/
@Pointcut("execution(* com.puhui.flowplatform.manage.service.*.delete*(..))")
public void deleteCell() {
}
/**
* 添加操作日志(后置通知)
*
* @param joinPoint
* @param object
*/
@AfterReturning(value = "insertCell()", argNames = "object", returning = "object")
public void insertLog(JoinPoint joinPoint, Object object) throws Throwable {
// Admin admin=(Admin)
// request.getSession().getAttribute("businessAdmin");
// 判断参数
if (joinPoint.getArgs() == null) {// 没有参数
return;
}
// 获取方法名
String methodName = joinPoint.getSignature().getName();
// 获取操作内容
String opContent = optionContent(joinPoint.getArgs(), methodName);
UserOperateLog log = new UserOperateLog();
log.setOperateContent(opContent);
log.setUserId(id);;
log.setOperateType(1);//enum 增加
log.setCreateTime(new Date());
logService.insertLog(log);
}
/**
* 管理员修改操作日志(后置通知)
*
* @param joinPoint
* @param object
* @throws Throwable
*/
@AfterReturning(value = "updateCell()", argNames = "object", returning = "object")
public void updateLog(JoinPoint joinPoint, Object object) throws Throwable {
// Admin admin=(Admin)
// request.getSession().getAttribute("businessAdmin");
// 判断参数
if (joinPoint.getArgs() == null) {// 没有参数
return;
}
// 获取方法名
String methodName = joinPoint.getSignature().getName();
// 获取操作内容
String opContent = optionContent(joinPoint.getArgs(), methodName);
// 创建日志对象
UserOperateLog log = new UserOperateLog();
log.setOperateContent(opContent);
log.setUserId(id);;
log.setOperateType(2);//enum 修改
log.setCreateTime(new Date());
logService.insertLog(log);
}
/**
* 删除操作
*
* @param joinPoint
* @param object
*/
@AfterReturning(value = "deleteCell()", argNames = "object", returning = "object")
public void deleteLog(JoinPoint joinPoint, Object object) throws Throwable {
// Admin admin=(Admin)
// request.getSession().getAttribute("businessAdmin");
// 判断参数
if (joinPoint.getArgs() == null) {// 没有参数
return;
}
// 获取方法名
String methodName = joinPoint.getSignature().getName();
StringBuffer rs = new StringBuffer();
rs.append(methodName);
String className = null;
for (Object info : joinPoint.getArgs()) {
// 获取对象类型
className = info.getClass().getName();
className = className.substring(className.lastIndexOf(".") + 1);
rs.append("[参数,类型:" + className + ",值:(id:"
+ joinPoint.getArgs()[0] + ")");
}
// 创建日志对象
UserOperateLog log = new UserOperateLog();
log.setOperateContent(rs.toString());
log.setUserId(id);;
log.setOperateType(3);//删除
log.setCreateTime(new Date());
logService.insertLog(log);
}
/**
* 使用Java反射来获取被拦截方法(insert、update)的参数值, 将参数值拼接为操作内容
*
* @param args
* @param mName
* @return
*/
public String optionContent(Object[] args, String mName) {
if (args == null) {
return null;
}
StringBuffer rs = new StringBuffer();
rs.append(mName);
String className = null;
int index = 1;
// 遍历参数对象
for (Object info : args) {
// 获取对象类型
className = info.getClass().getName();
className = className.substring(className.lastIndexOf(".") + 1);
rs.append("[参数" + index + ",类型:" + className + ",值:");
// 获取对象的所有方法
Method[] methods = info.getClass().getDeclaredMethods();
// 遍历方法,判断get方法
for (Method method : methods) {
String methodName = method.getName();
// 判断是不是get方法
if (methodName.indexOf("get") == -1) {// 不是get方法
continue;// 不处理
}
Object rsValue = null;
try {
// 调用get方法,获取返回值
rsValue = method.invoke(info);
} catch (Exception e) {
continue;
}
// 将值加入内容中
rs.append("(" + methodName + ":" + rsValue + ")");
}
rs.append("]");
index++;
}
return rs.toString();
}
}