org.springframework.boot spring-boot-starter-aop
package com.test.common.annotation; import java.lang.annotation.*; /** * 写入日志表时,字段对应的中文注释 */ @Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到 @Target({ElementType.FIELD,ElementType.METHOD})//定义注解的作用目标**作用范围字段、枚举的常量/方法 @Documented //说明该注解将被包含在javadoc中 public @interface FieldMeta { /** * 汉字全称 * @return */ String value(); }
使用FieldMeta自定义注解,看个人业务自行觉得是否需要重新定义实体
package com.test.customer.domain; import java.math.BigDecimal; import java.util.Date; import java.util.List; import java.util.Map; import com.test.common.annotation.FieldMeta; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import com.test.common.core.domain.BaseEntity; public class TestDangAnDetail extends BaseEntity { private static final long serialVersionUID = 1L; /** * 次数 */ @FieldMeta("总次数") private Long CountNum; }
//抽象类 public interface ContentParser { /** * 获取信息返回查询出的对象 * * @param joinPoint 查询条件的参数 * @param dbAccessLogger 注解 * @return 获得的结果 */ Object getOldResult(JoinPoint joinPoint, DBAccessLogger dbAccessLogger,String sqlSessionFactoryName); /** * 获取信息返回查询出的对象 * * @param joinPoint 查询条件的参数 * @param dbAccessLogger 注解 * @return 获得的结果 */ Object getNewResult(JoinPoint joinPoint, DBAccessLogger dbAccessLogger,String sqlSessionFactoryName); }
实现类 :通过该实现类获取更新前后的数据。
该实现类的实现原理为:获取入参出入的id值,获取sqlSessionFactory,通过sqlSessionFactory获取selectByPrimaryKey()该方法,执行该方法可获取id对应数据更新操作前后的数据。
@Component public class DefaultContentParse implements ContentParser { /** * 获取更新方法的第一个参数解析其id * @param joinPoint 查询条件的参数 * @param enableModifyLog 注解类容 * @return */ @Override public Object getOldResult(JoinPoint joinPoint, DBAccessLogger enableModifyLog,String sqlSessionFactoryName) { Object info = joinPoint.getArgs()[0]; Object id = ReflectionUtils.getFieldValue(info, "id"); Assert.notNull(id,"未解析到id值,请检查入参是否正确"); Class> aClass = enableModifyLog.serviceClass(); Object result = null; try { SqlSessionFactory sqlSessionFactory = SpringUtil.getBean(sqlSessionFactoryName); Object instance = Proxy.newProxyInstance( aClass.getClassLoader(), new Class[]{aClass}, new MyInvocationHandler(sqlSessionFactory.openSession().getMapper(aClass)) ); Method selectByPrimaryKey = aClass.getDeclaredMethod("selectByPrimaryKey", Long.class); //调用查询方法 result = selectByPrimaryKey.invoke(instance, id); } catch (Exception e) { e.printStackTrace(); } return result; } @Override public Object getNewResult(JoinPoint joinPoint, DBAccessLogger enableModifyLog,String sqlSessionFactoryName) { return getOldResult(joinPoint,enableModifyLog,sqlSessionFactoryName); } }
注意:如不返回操作是否成功状态可能会导致前端出现警告,JSON为空不能被解析
import com.test.common.annotation.FieldMeta; import com.test.common.core.domain.entity.SysUser; import com.test.common.enums.BusinessStatus; import com.test.common.enums.OperatorType; import com.test.common.utils.*; import com.test.customer.domain.UserDataOperationLogs; import com.test.customer.service.IUserDataOperationLogsService; import com.test.framework.service.OperateLog; import com.test.common.enums.BusinessType; import com.test.framework.service.ContentParser; import com.test.system.domain.SysOperLog; import com.test.system.service.ISysOperLogService; import lombok.extern.slf4j.Slf4j; import com.alibaba.fastjson.JSON; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Date; import java.util.HashMap; import java.util.Map; /** * 拦截@EnableGameleyLog注解的方法 * 将具体修改存储到数据库中 */ @Aspect @Component @Slf4j public class OperateAspect { @Autowired private IUserDataOperationLogsService iUserDataOperationLogsService; @Autowired private DefaultContentParse defaultContentParse; @Autowired private ApplicationContext applicationContext; // 环绕通知 @Around("@annotation(operateLog)") public Object around(ProceedingJoinPoint joinPoint, OperateLog operateLog) throws Throwable{ MapoldMap=new HashMap<>(); UserDataOperationLogs lg = new UserDataOperationLogs(); ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); // 当不传默认operateType时 根据Method类型自动匹配 setAnnotationType(request, operateLog); // fixme 1.0.9开始不再提供自动存入username功能,请在存储实现类中自行存储 // 从切面织入点处通过反射机制获取织入点处的方法 MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Object[] args=joinPoint.getArgs(); // 请求参数 // 获取当前的用户 SysUser currentUser = ShiroUtils.getSysUser(); lg.setCreateBy(currentUser.getLoginName()); lg.setStatus(0l); lg.setUpdateTime(new Date()); if (OperateType.UPDATE.equals(operateLog.operateType())) { try { ContentParser contentParser=(ContentParser) applicationContext.getBean(operateLog.parseclass()); Object oldObject = contentParser.getResult(joinPoint, operateLog); if (operateLog.needDefaultCompare()) { oldMap = (Map ) objectToMap(oldObject); // 存储修改前的对象 lg.setStoreId((Long) oldMap.get("storeId")); lg.setItemId((Long) oldMap.get("itemId")); } } catch (Exception e) { e.printStackTrace(); log.error("service加载失败:", e); } } // joinPoint.proceed()执行前是前置通知,执行后是后置通知 Object object=joinPoint.proceed(); if (OperateType.UPDATE.equals(operateLog.operateType())) { ContentParser contentParser; try { String responseParams=JSON.toJSONString(object); contentParser=(ContentParser) applicationContext.getBean(operateLog.parseclass()); object = contentParser.getResult(joinPoint, operateLog); } catch (Exception e) { log.error("service加载失败:", e); } // 默认不进行比较,可以自己在logService中自定义实现,降低对性能的影响 if (operateLog.needDefaultCompare()) { lg.setContent(defaultDealUpdate(object, oldMap)); } // 如果使用默认缓存 则需要更新到最新的数据 if(operateLog.defaultCache() && operateLog.parseclass().equals(DefaultContentParse.class)){ defaultContentParse.updateCache(joinPoint, operateLog,object); } } else{ String responseParams=JSON.toJSONString(object); } //保存当前日志到数据库中 int logs = iUserDataOperationLogsService.insertUserDataOperationLogs(lg); log.info("新增用户操作数据日志成功"); //返回用户的操作是否成功 AjaxResult ajaxResult = logs > 0 ? success() : error(); return ajaxResult; } private String defaultDealUpdate(Object newObject, Map oldMap){ try { Map newMap = (Map ) objectToMap(newObject); StringBuilder str = new StringBuilder(); Object finalNewObject = newObject; oldMap.forEach((k, v) -> { Object newResult = newMap.get(k); if (null!=v && !v.equals(newResult)) { Field field = ReflectionUtils.getAccessibleField(finalNewObject, k); FieldMeta dataName = field.getAnnotation(FieldMeta.class); SysUser currentUser = ShiroUtils.getSysUser(); if (null!=dataName) { str.append("【"+currentUser.getLoginName()+"】").append("修改了【").append(dataName.value() +"】").append("将【").append(v).append("】").append(" 改为:【").append(newResult).append("】。\n"); } } }); return str.toString(); } catch (Exception e) { log.error("比较异常", e); throw new RuntimeException("比较异常",e); } } private Map, ?> objectToMap(Object obj) { if (obj == null) { return null; } ObjectMapper mapper = new ObjectMapper(); mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); // 如果使用JPA请自己打开这条配置 // mapper.addMixIn(Object.class, IgnoreHibernatePropertiesInJackson.class); Map, ?> mappedObject = mapper.convertValue(obj, Map.class); return mappedObject; } private void setAnnotationType(HttpServletRequest request,OperateLog modifyLog){ if(!modifyLog.operateType().equals(OperateType.NONE)){ return; } String method=request.getMethod(); if(RequestMethod.GET.name().equalsIgnoreCase(method)){ ReflectAnnotationUtil.updateValue(modifyLog, "operateType", OperateType.SELECT); } else if(RequestMethod.POST.name().equalsIgnoreCase(method)){ ReflectAnnotationUtil.updateValue(modifyLog, "operateType", OperateType.ADD); } else if(RequestMethod.PUT.name().equalsIgnoreCase(method)){ ReflectAnnotationUtil.updateValue(modifyLog, "operateType", OperateType.UPDATE); } else if(RequestMethod.DELETE.name().equalsIgnoreCase(method)){ ReflectAnnotationUtil.updateValue(modifyLog, "operateType", OperateType.DELETE); } } }
//环绕通知切面,切点:DBAccessLogger 更新拦截数据 @Aspect @Component @Order(1) public class DBAccessLoggerAspect { // 注入Service用于把日志保存数据库 @Autowired private LogService logService; @Around("@annotation(com.****.log.DBAccessLogger)") // 环绕通知 public Object execute(ProceedingJoinPoint pjp) throws Exception { // 获得当前访问的class Class> className = pjp.getTarget().getClass(); // 获得访问的方法名 String methodName = pjp.getSignature().getName(); @SuppressWarnings("rawtypes") Class[] argClass = ((MethodSignature) pjp.getSignature()).getParameterTypes(); // 操作结果,默认为成功 Long operResult = DictLogConstant.LOGS_OPER_SUCCESS; //返回值 Object rvt = null; Method method = className.getMethod(methodName, argClass); DBAccessLogger dbAcessLoggerannotation = method.getAnnotation(DBAccessLogger.class); String accessTable = dbAcessLoggerannotation.accessTable(); DBOperationType accessType = dbAcessLoggerannotation.accessType(); DatabaseEnum databaseEnum = dbAcessLoggerannotation.accessDatasource(); String accessDatasource = databaseEnum.constName; //crd操作直接执行方法 if (accessType == DBOperationType.DELETE || accessType == DBOperationType.SELECT || accessType == DBOperationType.CREATE) { try { rvt = pjp.proceed(); } catch (Throwable e) { e.printStackTrace(); throw new RuntimeException(e.getMessage()); } // 如果没有返回结果,则不将该日志操作写入数据库。 if (rvt == null) return rvt; } if ((accessType == DBOperationType.DELETE) && ((CUDResult) rvt).getReturnVal() == 0) { operResult = DictLogConstant.LOGS_OPER_FAILURE; } if (accessTable != null) { if (accessType == DBOperationType.SELECT) { Log sysLog = new Log(); /** 设置要存放的日志信息 **/ logService.createLog(sysLog); } else if (accessType == DBOperationType.DELETE || accessType == DBOperationType.CREATE) { for (Long recordId : ((CUDResult) rvt).getRecordIds()) { Log sysLog = new Log(); /** 设置要存放的日志信息 **/ logService.createLog(sysLog); } } else { //更新操作 Log sysLog = new Log(); /** 设置日志信息 **/ //获取更行前的数据 MapoldMap = null; Object oldObject; try { ContentParser contentParser = SpringUtil.getBean(dbAcessLoggerannotation.parseClass()); //获取更行前的数据 oldObject = contentParser.getOldResult(pjp, dbAcessLoggerannotation, databaseEnum.sqlsessionName); oldMap = (Map ) objectToMap(oldObject); } catch (Exception e) { e.printStackTrace(); } //执行service Object serviceReturn; try { serviceReturn = pjp.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); throw new RuntimeException(throwable.getMessage()); } CUDResult crudResult = (CUDResult) serviceReturn; Object afterResult = null; try { ContentParser contentParser = SpringUtil.getBean(dbAcessLoggerannotation.parseClass()); //更新后的数据 afterResult = contentParser.getNewResult(pjp, dbAcessLoggerannotation, databaseEnum.sqlsessionName); } catch (Exception e) { e.printStackTrace(); } //修改前后的变化 sysLog.setOperation(accessType.getValue()); try { String updatedefrent = defaultDealUpdate(afterResult, oldMap); sysLog.setParams(updatedefrent); } catch (Exception e) { e.printStackTrace(); } /** 设置日志信息 **/ logService.createLog(sysLog); return serviceReturn; } } if (operResult.longValue() == DictLogConstant.LOGS_OPER_FAILURE) { // 当数据库的UPDATE 和 DELETE操作没有对应的数据记录存在时,抛出异常 throw new DBAccessException(accessType.getValue() + "的数据记录不存在!"); } return rvt; } private Map, ?> objectToMap(Object obj) { if (obj == null) { return null; } ObjectMapper mapper = new ObjectMapper(); mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); //如果使用JPA请自己打开这条配置 //mapper.addMixIn(Object.class, IgnoreHibernatePropertiesInJackson.class); Map, ?> mappedObject = mapper.convertValue(obj, Map.class); return mappedObject; } /** * * @param newObject 更新过后的结果 * @param oldMap 更新前的结果 * @return */ private String defaultDealUpdate(Object newObject, Map oldMap) { try { Map newMap = (Map ) objectToMap(newObject); StringBuilder str = new StringBuilder(); Object finalNewObject = newObject; oldMap.forEach((k, v) -> { Object newResult = newMap.get(k); if (v != null && !v.equals(newResult)) { Field field = ReflectionUtils.getAccessibleField(finalNewObject, k); //获取类上的注解 DataName dataName = field.getAnnotation(DataName.class); if (field.getType().getName().equals("java.util.Date")) { SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); if (dataName != null) { str.append("【").append(dataName.name()).append("】从【") .append(format.format(v)).append("】改为了【").append(format.format(newResult)).append("】;\n"); } else { str.append("【").append(field.getName()).append("】从【") .append(format.format(v)).append("】改为了【").append(format.format(newResult)).append("】;\n"); } } else { if (dataName != null) { str.append("【").append(dataName.name()).append("】从【") .append(v).append("】改为了【").append(newResult).append("】;\n"); } else { str.append("【").append(field.getName()).append("】从【") .append(v).append("】改为了【").append(newResult).append("】;\n"); } } } }); return str.toString(); } catch (Exception e) { throw new RuntimeException("比较异常", e); } } }
package com.test.framework.service; import com.test.common.utils.IService; import com.test.common.utils.OperateType; import com.test.framework.service.impl.DefaultContentParse; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 记录编辑详细信息的标注 * @author */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface OperateLog { // 模块名称 String moduleCode() default ""; // 操作的类型 可以直接调用OperateType 不传时根据METHOD自动确定 OperateType operateType() default OperateType.NONE; // 获取编辑信息的解析类,目前为使用id获取,复杂的解析需要自己实现,默认不填写则使用默认解析类 Class parseclass() default DefaultContentParse.class; // 查询数据库所调用的class文件 Class serviceclass() default IService.class; // 具体业务操作名称 String handleName() default ""; // 是否需要默认的改动比较 boolean needDefaultCompare() default false; // id的类型 Class idType() default Long.class; // 是否使用默认本地缓存 boolean defaultCache() default false; }
/** * 修改记录详情 * @OperateLog:AOP自定义日志注解 */ @PostMapping("/editSave") @OperateLog(moduleCode="editSave", operateType= OperateType.UPDATE, handleName="修改档案信息", needDefaultCompare=true) @ResponseBody public AjaxResult editSave(TCustomerItemRecDetail tCustomerItemRecDetail){ return toAjax(testDangAnDetailService.updateTestDangAnDetail(tCustomerItemRecDetail)); }
修改功能时,需要实现我们自定义的IService接口,并重写 selectById 方法,在修改前我们需要根据主键id去数据库查询对应的信息,然后在和修改后的值进行比较。
/** * 修改时需要记录修改前后的值时,需要根据主键id去查询修改前的值时需要 * @author zhang */ public interface IService{ T selectById(S id); }