- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-aop</artifactId>
- </dependency>
- 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{
- Map<String, Object> oldMap=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<String, Object>) 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<String, Object> oldMap){
- try {
- Map<String, Object> newMap = (Map<String, Object>) 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();
- /**
- 设置日志信息
- **/
- //获取更行前的数据
- Map<String, Object> oldMap = null;
- Object oldObject;
- try {
- ContentParser contentParser = SpringUtil.getBean(dbAcessLoggerannotation.parseClass());
- //获取更行前的数据
- oldObject = contentParser.getOldResult(pjp, dbAcessLoggerannotation, databaseEnum.sqlsessionName);
- oldMap = (Map<String, Object>) 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<String, Object> oldMap) {
- try {
- Map<String, Object> newMap = (Map<String, Object>) 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, S> {
- T selectById(S id);
- }