玖叶教程网

前端编程开发入门

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<UserOperateLog> 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<UserOperateLog> userOperateLogList=(List<UserOperateLog>)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<Menus> 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<UserOperateLog> generateContent(Object returnValue, Object[] args, OperateType operateType) {
 {
 List<UserOperateLog> 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<Object> 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<HttpMessageConverter<?>> 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();
	}
 
}

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言