若依spring boot aop记录修改前后的值

若依spring boot aop记录修改前后的值前言 在一些领域 记录数据的变更历史是非常重要的 比如工业采集系统 需要记录指标的信息 再比如一些非常注重安全的系统 希望在必要时可以对所有的历史操作追根溯源 有据可查 数据库效果图 一 数据库设计 CREATE TABLE dbo

大家好,欢迎来到IT知识分享网。

前言:

在一些领域,记录数据的变更历史是非常重要的。比如工业采集系统,需要记录指标的信息。再比如一些非常注重安全的系统,希望在必要时可以对所有的历史操作追根溯源,有据可查。

数据库效果图:

若依spring boot aop记录修改前后的值

一、数据库设计

若依spring boot aop记录修改前后的值

CREATE TABLE [dbo].[sys_trace_log] ( [id] bigint NOT NULL IDENTITY(1,1) , [item_id] bigint NULL , [module_code] varchar(50) NULL , [module_name] varchar(50) NULL , [method_name] varchar(200) NULL , [operate_type] varchar(20) NULL , [content] varchar(5000) NULL , [request_params] varchar(2000) NULL , [response_params] varchar(2000) NULL , [request_url] varchar(100) NULL , [ip] varchar(30) NULL , [status] nchar(1) NULL , [create_by] nvarchar(64) NULL , [create_time] datetime NULL , [remark] nvarchar(500) NULL )

二、添加依赖

 <!-- aop功能 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> <version>2.2.7.RELEASE</version> </dependency>

三、自定义字段翻译注解

1、修改功能时,需要显示如某字段修改前为李四,修改后为王五,name字段对应的中文注释

package com.huawei.common.annotation; import java.lang.annotation.*; / * 写入日志表时,字段对应的中文注释 */ @Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到 @Target({ElementType.FIELD,ElementType.METHOD})//定义注解的作用目标作用范围字段、枚举的常量/方法 @Documented //说明该注解将被包含在javadoc中 public @interface FieldMeta { / * @return 字段名称 */ String name() default ""; }

2、使用FieldMeta在bean实体类中添加自定义注解

package com.huawei.crm.domain; import java.util.Date; import com.fasterxml.jackson.annotation.JsonFormat; import com.huawei.common.annotation.Excel; import com.huawei.common.annotation.FieldMeta; import com.huawei.common.core.domain.BaseEntity; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import org.springframework.format.annotation.DateTimeFormat; / * 付款申请单对象 skd_statement * @author yeyong * @date 2022-10-17 */ public class SkdPayment extends BaseEntity { private static final long serialVersionUID = 1L; / * 送货单号 */ @FieldMeta(name = "送货单号") private String deliveryOrder; }

四、自定义日志注解

package com.huawei.common.annotation; import com.huawei.common.enums.ModifyName; import com.huawei.common.utils.parser.ContentParser; import com.huawei.common.utils.parser.DefaultContentParse; import com.huawei.common.utils.parser.IService; import java.lang.annotation.*; / * 记录编辑详细信息的标注 * @author yeyong */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface EnableModifyLog { / * @return 操作的类型 可以直接调用ModifyName 不传时根据METHOD自动确定 */ ModifyName modifyType() default ModifyName.NONE; / * @return 获取编辑信息的解析类,目前为使用id获取,复杂的解析需要自己实现,默认不填写 * 则使用默认解析类 */ Class<? extends ContentParser> parseClass() default DefaultContentParse.class; / * @return 查询数据库所调用的class文件 */ Class<? extends IService> serviceClass() default IService.class; / * @return 具体业务操作名称 */ String handleName() default ""; / * @return 是否需要默认的改动比较 */ boolean needDefaultCompare() default false; / * @return id的类型 */ Class<?> idType() default Long.class; // 模块名称 String moduleCode() default ""; // 是否使用默认本地缓存 boolean defaultCache() default false; }

五、IService接口

1.定义接口

package com.huawei.common.utils.parser; / * 修改时需要记录修改前后的值时,需要根据主键id去查询修改前的值时需要 * @author yeyong */ public interface IService<T,S> { T selectById(S id); }

2.实现接口,重写方法。

修改功能时,需要实现我们自定义的IService接口,并重写 selectById 方法,在修改前我们需要根据主键id去数据库查询对应的信息,然后在和修改后的值进行比较。

package com.huawei.crm.service.impl; / * 付款申请单Service业务层处理 * @author yeyong * @date 2022-10-17 */ @Service public class SkdPaymentServiceImpl implements ISkdPaymentService, IService<SkdPayment,Long> { / * 需要实现我们自定义的IService接口,并重写 selectById 方法, * 在修改前我们需要根据主键id去数据库查询对应的信息,然后在和修改后的值进行比较。 / @Override public SkdPayment selectById(Long id) { return skdPaymentMapper.selectSkdPaymentById(id); } }

六、定义AOP切面

注意:如不返回操作是否成功状态可能会导致前端出现警告,JSON为空不能被解析。如果用了若依框架则需要返回ajaxResult。

package com.huawei.framework.aspectj; import com.alibaba.fastjson.JSON; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.huawei.common.annotation.EnableModifyLog; import com.huawei.common.annotation.FieldMeta; import com.huawei.common.core.domain.AjaxResult; import com.huawei.common.core.domain.entity.SysTraceLog; import com.huawei.common.enums.ModifyName; import com.huawei.common.utils.*; import com.huawei.common.utils.parser.ContentParser; import com.huawei.common.utils.parser.DefaultContentParse; import com.huawei.common.utils.spring.SpringUtils; import com.huawei.system.mapper.SysTraceLogMapper; 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.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; 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; import static com.huawei.common.core.domain.AjaxResult.error; import static com.huawei.common.core.domain.AjaxResult.success; / * 拦截@EnableGameleyLog注解的方法 * 将具体修改存储到数据库中 * Created by yeyong on 2024/11/21. */ @Aspect @Component public class ModifyAspect { private final static Logger logger = LoggerFactory.getLogger(ModifyAspect.class); @Autowired private DefaultContentParse defaultContentParse; // 环绕通知 @Around("@annotation(enableModifyLog)") public AjaxResult around(ProceedingJoinPoint joinPoint, EnableModifyLog enableModifyLog) throws Throwable { Map<String, Object> oldMap = new HashMap<>(); SysTraceLog traceLog = new SysTraceLog(); ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); //当不传默认modifyType时 根据Method类型自动匹配 setAnnotationType(request, enableModifyLog); // fixme 1.0.9开始不再提供自动存入username功能,请在存储实现类中自行存储 // 从切面织入点处通过反射机制获取织入点处的方法 MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); // 获取切入点所在的方法 String meName = method.getName(); // 获取请求的方法名 String className = joinPoint.getTarget().getClass().getName(); // 获取请求的类名 String methodName=className + "." + meName; String uri=request.getRequestURL().toString(); // 获取请求url traceLog.setModuleCode(enableModifyLog.moduleCode()); //模块code traceLog.setModuleName(enableModifyLog.handleName()); //模块名称 traceLog.setMethodName(methodName); //方法名称 traceLog.setRequestUrl(uri); //请求url Object[] args=joinPoint.getArgs(); // 请求参数 String requestParams = JSON.toJSONString(args); traceLog.setRequestParams(requestParams); //返回参数 traceLog.setIp(ShiroUtils.getIp()); //IP traceLog.setStatus("0"); //状态 traceLog.setOperateType(enableModifyLog.modifyType().toString()); //操作类型 traceLog.setCreateBy(ShiroUtils.getSysUser().getUserName()); //创建人 traceLog.setCreateTime(new Date()); //创建时间 if (ModifyName.UPDATE.equals(enableModifyLog.modifyType())) { try { ContentParser contentParser = (ContentParser) SpringUtils.getBean(enableModifyLog.parseClass()); Object oldObject = contentParser.getOldResult(joinPoint, enableModifyLog); traceLog.setOldObject(oldObject); if (enableModifyLog.needDefaultCompare()) { oldMap = (Map<String, Object>) objectToMap(oldObject); // 存储修改前的对象 //额外新增/ 判断oldMap key中是否包含"id"字段,如包含则将id中。 boolean flag = oldMap.containsKey("id"); if(flag==true){ traceLog.setItemId(oldMap.get("id").toString()); } } } catch (Exception e) { logger.error("service加载失败:", e); } } // joinPoint.proceed()执行前是前置通知,执行后是后置通知 Object object=joinPoint.proceed(); if (ModifyName.UPDATE.equals(enableModifyLog.modifyType())) { ContentParser contentParser; try { String responseParams=JSON.toJSONString(object); traceLog.setResponseParams(responseParams); // 把返回的结果存到log里面 contentParser = (ContentParser) SpringUtils.getBean(enableModifyLog.parseClass()); object = contentParser.getNewResult(joinPoint, enableModifyLog); } catch (Exception e) { logger.error("service加载失败:", e); } // 默认不进行比较,可以自己在logService中自定义实现,降低对性能的影响 if (enableModifyLog.needDefaultCompare()) { traceLog.setContent(defaultDealUpdate(object, oldMap)); } // 如果使用默认缓存 则需要更新到最新的数据 /*if(enableModifyLog.defaultCache() && enableModifyLog.parseClass().equals(DefaultContentParse.class)){ defaultContentParse.updateCache(joinPoint, enableModifyLog,object); }*/ } else{ String responseParams=JSON.toJSONString(object); traceLog.setResponseParams(responseParams); // 返回的结果 } //插入数据库 / * 由于本博主用的是若依框架提示:“服务器错误,请联系管理员”错误,其他的可以根据情况执行决定。 * 若依框架提示错误时,为防止出现textStatus == "parsererror" || textStatus == "error"错误, * 主要是parsererror解析错误,在around方法里最好加上 AjaxResult ajaxResult = num> 0 ? success() : error(); 判断并返回ajaxResult / int num = SpringUtils.getBean(SysTraceLogMapper.class).insertSysTraceLog(traceLog); //logger.info("新增用户操作数据日志成功"); AjaxResult ajaxResult = num > 0 ? success() : error(); return ajaxResult; } private String defaultDealUpdate(Object newObject, Map<String, Object> oldMap) { try { //额外新增/ 判断oldMap key中是否包含"updateTime"字段,如包含则移除,不作新旧值比较。 boolean flag = oldMap.containsKey("updateTime"); if(flag==true){ oldMap.remove("updateTime"); //移除 updateTime 字段 } 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); FieldMeta dataName = field.getAnnotation(FieldMeta.class); if (StringUtils.isNotNull(dataName)) { 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) { logger.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, EnableModifyLog modifyLog) { if (!modifyLog.modifyType().equals(ModifyName.NONE)) { return; } String method = request.getMethod(); if (RequestMethod.GET.name().equalsIgnoreCase(method)) { ReflectAnnotationUtil.updateValue(modifyLog, "modifyType", ModifyName.GET); } else if (RequestMethod.POST.name().equalsIgnoreCase(method)) { ReflectAnnotationUtil.updateValue(modifyLog, "modifyType", ModifyName.SAVE); } else if (RequestMethod.PUT.name().equalsIgnoreCase(method)) { ReflectAnnotationUtil.updateValue(modifyLog, "modifyType", ModifyName.UPDATE); } else if (RequestMethod.DELETE.name().equalsIgnoreCase(method)) { ReflectAnnotationUtil.updateValue(modifyLog, "modifyType", ModifyName.DELETE); } } }

七、在controller层调用时,添加@EnableModifyLog注解

 / * 修改保存付款申请单 */ @EnableModifyLog(modifyType = ModifyName.UPDATE,moduleCode = "edit_payment", handleName = "修改付款申请单",needDefaultCompare=true,serviceClass = SkdPaymentServiceImpl.class) @RequiresPermissions("crm:payment:edit") @Log(title = "付款申请单", businessType = BusinessType.UPDATE) @PostMapping("/edit") @ResponseBody public AjaxResult editSave(SkdPayment skdPayment) throws ParseException, ClassNotFoundException, IllegalAccessException { return toAjax(skdPaymentService.updateSkdPayment(skdPayment)); }

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/162734.html

(0)
上一篇 2024-12-16 13:15
下一篇 2024-12-16 13:26

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信