Spring AOP 实现日志记录功能

最近项目中需要记录日志,跟大家想的一样 ,利用spring aop去实现,之前也参考了一些代码,自己实现了一套设计,供大家参考。

之前看到网上很多是基于切面类Aspect去实现了,在切面类中定义before after around等逻辑以及要拦截等方法。本文利用注解实现了一套可以扩展等日志记录模块。

1. 定义注解

@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);
}

2. 定义拦截器

本模块主要是后置通知,主要逻辑如下:

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);
            }

        }
    }

}

3 Generator类

继承统一的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;

        }

    }
}

4. 注解应用

 @PutMapping(value = "roles/{roleId}")
    @RequiredLogInterceptor(targetGenerator = "ContentGeneratorForRoleMgt",operateType= OperateType.UPDATE)
    @ApiOperation(value = "修改角色", httpMethod = "PUT", response = Response.class, notes = "修改角色")
    public Response updateRole(@RequestHeader String token,@RequestBody VoRole voRole, @PathVariable("roleId") String roleId) {
        LOGGER.info("updateRole入参:{}", JSONObject.toJSONString(voRole)); 
  

5. Configuration

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");

6.写在结尾

本来实现都代码版本中,所有都日志生成代码都在后置拦截器中,并且根据当前执行都方法都classname和methodname去判断当前都方法,出现很多if 判断,且method name都不一样,有的是addXXX,有的是createXXX,显然设计不合理。后来重新进行了设计,有什么不足,希望大家可以指出。


7.非自定义注解实现方式

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();
	}

}





你可能感兴趣的:(互联网技术栈)