最近项目中需要记录日志,跟大家想的一样 ,利用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(); } }