diff --git a/README.md b/README.md index 2887d92ae36a6ba34c05be3f93c5992b63b7d24f..1d640757a1b76431177f7fecd0773e25ceb814b4 100644 --- a/README.md +++ b/README.md @@ -1,365 +1,218 @@ -## objectlog +# objectlog - - -### 工具优势 - - (持续更新)对于重要的一些数据,我们需要`记录一条记录的所有版本变化过程`,做到持续追踪,为后续问题追踪提供思路。`objectlog工具`是一个记录单个对象属性变化的日志工具,工具`采用spring切面和mybatis拦截器`相关技术`编写了api依赖包`,以`非侵入方式`实现对标记的对象属性`进行记录`,`仅需要导入依赖即可,几乎不需要对原系统代码改动`,下面展示简单的效果(根据对象field渲染即可): - -![输入图片说明](images/FVAFMQJC5BWQ5D2S5DIUQEQP.png) +## 工具优势 该系统具有以下特点: -- 简单易用:系统将核心逻辑抽离,采用非侵入方式,只需要导入依赖后标注相关注解即可。 -- 业务共享:系统可以同时供多个业务系统使用,彼此之间互不影响。 -- 自动解析:能自动解析对象的属性变化,自动生成变化记录。 -- 便于扩展:支持更多对象属性类型的扩展,支持自定义解析处理逻辑等。 -- 工具性能:工具采用线程模式,脱离业务主线程,避免了解析过程对业务性能的影响。 - - - -### 工具介绍 - -工具主要为分为core,domain,feign三个包。位于objectlog-api下,其中`core包是核心内容,另外一个包根据自身业务可以进行删除和变更`。 - -- `core包`:核心逻辑的实现,包含类型处理器类和值处理器类等。 -- `domain包`:自身业务的基类BaseEntity(核心是主键id字段,其他字段可有可无),自动填充字段逻辑BaseMetaObjectHandler和操作记录查询封装类ObjectOperationDto。 - -> 注意在仓库中objectlog-api-test中则为本次的演示代码。 -> + BASE : 单个的对象记录(包含增删改),不存在子对象的记录 -> + COMMENT: 一组对象的记录(包含增删改),不存在父子关系,如批量导入 -> + COMPLEX:单个对象的记录(包含增删改),存在子对象的记录 +- **简单易用**:系统将核心逻辑抽离,采用非侵入方式,只需要导入依赖后标注相关注解即可。 +- **功能全面**:支持多种操作类型(新增、修改、删除、批量操作、复杂对象关联等),并提供灵活的扩展机制。 +- **高度可定制**:支持自定义值处理器和类型处理器,满足不同业务场景下的日志记录需求。 +![输入图片说明](images/FVAFMQJC5BWQ5D2S5DIUQEQP.png) -### 快速上手 +## 快速上手 -#### 基础操作 +### 基础操作 使用过程主要分为两步骤: -+ 标记需要解析的类对象和字段 -+ 标记切入点 +1. **标记需要解析的类对象和字段** +2. **标记切入点** + +第一步:我们对需要记录的类对象使用 `@LogEntity` 进行标注,其中放在类上表示开启解析标记,否则不进行记录。与此同时,对于未标记的属性也不进行记录。 -第一步:我们对需要记录的类对象使用`@LogEntity`进行标注,其中放在类上表示开启解析标记,否则不进行记录。与此同时,对于未标记的属性也不进行记录。 +示例代码: -~~~java +```java @LogEntity public class SysUserModel extends BaseEntity { - //关联主键字段 + // 关联主键字段 @LogEntity(alias = "角色信息", associationValue = true, serviceImplClass = SysRoleServiceImpl.class, entityFieldName = "roleName") private String roleId; - - //基础字段 + + // 基础字段 @LogEntity(alias = "用户昵称") private String userName; - - //多行文本串 - @LogEntity(alias = "备注" ,attributeTypeEnum = AttributeTypeEnum.TEXT) + + // 多行文本串 + @LogEntity(alias = "备注", attributeTypeEnum = AttributeTypeEnum.TEXT) private String remark; - - //富文本字段 - @LogEntity(alias = "富文本内容" , attributeTypeEnum = AttributeTypeEnum.RICHTEXT) + + // 富文本字段 + @LogEntity(alias = "富文本内容", attributeTypeEnum = AttributeTypeEnum.RICHTEXT) private String richText; - - //枚举字段 - @LogEntity(alias = "帐号状态",enumValue = true, enumClass = StatusEnum.class) + + // 枚举字段 + @LogEntity(alias = "帐号状态", enumValue = true, enumClass = StatusEnum.class) private Integer status; } +``` - -~~~ - -~~~java +```java public enum StatusEnum { NORMAL(0,"正常"), - DEACTIVATE(1,"停用"),; + DEACTIVATE(1,"停用"); } -~~~ - - +``` -第二步:我们我们标记切入点 +第二步:我们标记切入点 -~~~java - @LogPoint( serviceHandler = SysUserServiceImpl.class, entityHandler = SysUserModel.class, - moduleName = "sysUser", remark = "测试用户模块") - public void add(SysUserModel insertModel) { - this.insert(insertModel); - } -~~~ +```java +@LogPoint(serviceHandler = SysUserServiceImpl.class, entityHandler = SysUserModel.class, + moduleName = "sysUser", remark = "测试用户模块") +public void add(SysUserModel insertModel) { + this.insert(insertModel); +} +``` 以下是当前对象的属性(当前操作是新增): -~~~java - @GetMapping(value = "/add") - @ResponseBody - public void testAdd(){ - SysUserModel userModel = new SysUserModel(); - userModel.setRoleId("9830274072323"); - userModel.setUserName("哈哈"); - userModel.setRemark("这是第一行\n" + "这是第二行"); - userModel.setStatus(0); - userModel.setRichText("富文本"); - userService.add(userModel); - //查看数据库 - } -~~~ - -其中roleId在数据库中存在一条记录: - -> INSERT INTO `objectlog`.`sys_role` (`id`, `role_name`, `role_key`, `role_sort`, `status`, `del_flag`, `create_time`, `update_time`) VALUES ('9830274072323', '快速上手', NULL, NULL, NULL, 0, NULL, NULL); - -执行结果如下所示,其中`diffValue`属性表示前后对比变化,可以看到`枚举字段和关联主键字段自动进行了转化`。`富文本字段,多行文本串被详细区分为行信息`。 - -> attributes:表示这个对象具体属性的变化记录 -> -> operationId:属性变化源自的操作id。 - -~~~json +```json { - "96c2b007f9854b3aa73046d317fb7b6d": [ - { - "id": "ebe6de00ddc84c8baf6881587e144d64", - "createTime": "2024-03-10T14:07:30", - "updateTime": "2024-03-10T14:07:30", - "delFlag": 0, - "parentId": null, - "moduleName": "sysUser", - "objectName": "SysUserModel", - "objectId": "96c2b007f9854b3aa73046d317fb7b6d", - "object": "{\"roleId\":\"9830274072323\",\"remark\":\"这是第一行\\n这是第二行\",\"updateTime\":1710050850367,\"userName\":\"哈哈\",\"richText\":\"富文本\",\"createTime\":1710050850367,\"id\":\"96c2b007f9854b3aa73046d317fb7b6d\",\"status\":0}", - "operationType": "ADD", - "version": 1, - "comment": "测试用户模块", - "attributes": [ - { - "operationId": "ebe6de00ddc84c8baf6881587e144d64", - "attributeType": "NORMAL", - "attributeName": "status", - "attributeAlias": "帐号状态", - "oldValue": "", - "newValue": "正常", - "diffValue": "帐号状态从\"\"修改为\"正常\"" - }, - { - "operationId": "ebe6de00ddc84c8baf6881587e144d64", - "attributeType": "NORMAL", - "attributeName": "userName", - "attributeAlias": "用户昵称", - "oldValue": "", - "newValue": "哈哈", - "diffValue": "用户昵称从\"\"修改为\"哈哈\"" - }, - { - "operationId": "ebe6de00ddc84c8baf6881587e144d64", - "attributeType": "NORMAL", - "attributeName": "roleId", - "attributeAlias": "角色信息", - "oldValue": "", - "newValue": "9830274072323", - "diffValue": "角色信息从\"\" 修改为 \"快速上手\"" - }, - { - "operationId": "ebe6de00ddc84c8baf6881587e144d64", - "attributeType": "TEXT", - "attributeName": "remark", - "attributeAlias": "备注", - "oldValue": "", - "newValue": "这是第一行\n这是第二行", - "diffValue": "{\"version\":\"1.0.0\",\"content\":[{\"lineNumber\":1,\"partList\":[{\"partContent\":\"这是第一行\",\"partType\":\"CHANGE_NEW\"},{\"partContent\":\"这是第二行\",\"partType\":\"CHANGE_NEW\"}]}]}" - }, - { - "operationId": "ebe6de00ddc84c8baf6881587e144d64", - "attributeType": "RICHTEXT", - "attributeName": "richText", - "attributeAlias": "富文本内容", - "oldValue": "", - "newValue": "富文本", - "diffValue": "{\"version\":\"1.0.0\",\"content\":[{\"lineNumber\":1,\"partList\":[{\"partContent\":\"富文本\",\"partType\":\"CHANGE_NEW\"}]}]}" - } - ], - } - ] + "attributeAlias": "富文本内容", + "oldValue": "", + "newValue": "富文本", + "diffValue": "{\"version\":\"1.0.0\",\"content\":[{\"lineNumber\":1,\"partList\":[{\"partContent\":\"富文本\",\"partType\":\"CHANGE_NEW\"}]}]}" } -~~~ - - - -#### 父级联合 - -上面我们展示了简单的对象记录过程,可以看见使用非常的方便和快捷。但针对以下些业务场景,却显得有所乏力,为此我们引入了一个新的概念父操作。 +``` ->+ 情景一:对于批量导入,除了记录某一个对象a本身的变化过程,我们还需要可以直观了解这个对象a创建的源头,在源头记录中(也就是批量导入这一操作),我们可以获取源头操作所有操作内容(包含了对象a的记录变更,也包含其他对象的记录变更),实现不同对象的联动。 ->+ 情景二:对于单个对象a,一次变更操作过程中,若存在关联表字段b对象的修改,需要自动将b对象的变更记录和对象a操作记录绑定(当然对象a本身也会产生操作记录) +### 父级联合 +上面我们展示了简单的对象记录过程,可以看见使用非常方便和快捷。但针对以下业务场景,却显得有所乏力,为此我们引入了一个新的概念——父操作。 +> + 情景一:对于批量导入,除了记录某一个对象 A 本身的变化过程,我们还需要可以直观了解这个对象 A 创建的源头,在源头操作记录中(也就是批量导入这一操作),我们可以获取源头操作的所有内容(包含了对象 A 的记录变更,也包含其他对象的记录变更),实现不同对象的联动。 +> + 情景二:对于单个对象 A,一次变更操作过程中,若存在关联表字段 B 对象的修改,需要自动将 B 对象的变更记录和对象 A 操作记录绑定(当然对象 A 本身也会产生操作记录)。 我们还是以前面需要记录的对象为例,对这两种场景进行介绍和说明: -**情景一**: - -~~~java - @GetMapping(value = "/addlist") - @ResponseBody - public void testAddList(){ - List insertList = new ArrayList<>(); - SysUserModel userModel = new SysUserModel(); - userModel.setRoleId("9830274072323"); - userModel.setUserName("哈哈"); - userModel.setRemark("这是第一行\n" + "这是第二行"); - userModel.setStatus(0); - userModel.setRichText("富文本"); - - SysUserModel userMode2 = new SysUserModel(); - userMode2.setRoleId("9830274072323"); - userMode2.setUserName("哈哈1"); - userMode2.setRemark("这是第一行1\n" + "这是第二行1"); - userMode2.setStatus(1); - userMode2.setRichText("富文本1"); - insertList.add(userModel); - insertList.add(userMode2); - userService.addList(insertList); - //查看数据库 - } -~~~ - -~~~java - @LogPoint( serviceHandler = SysUserServiceImpl.class, entityHandler = SysUserModel.class, - operation = OperationEnum.COMMON, moduleName = "sysUser", remark = "测试用户模块") - public void addList(List insertModelList) { - this.insertBatch(insertModelList); - } -~~~ - -相关结果如下所示,可以看见批量导入的两个对象的操作记录id都被挂载在一个父操作记录id=44012d8d444944e1a6233c327b2bed02下. - - -![输入图片说明](images/45GCVF6I91O7BBSDB42YU.png) +#### 情景一:批量导入 +```java +@GetMapping(value = "/addlist") +@ResponseBody +public void testAddList(){ + // 用户 + SysUserModel userModel1 = new SysUserModel(); + userModel1.setUserName("用户1"); + userModel1.setNickName("昵称1"); + userModel1.setPassword("123456"); + userModel1.setEmail("123456@qq.com"); + userModel1.setPhonenumber("12345678901"); + userModel1.setStatus(0); + + SysUserModel userModel2 = new SysUserModel(); + userModel2.setUserName("用户2"); + userModel2.setNickName("昵称2"); + userModel2.setPassword("123456"); + userModel2.setEmail("123456@qq.com"); + userModel2.setPhonenumber("12345678901"); + userModel2.setStatus(0); + + List userModelList = new ArrayList<>(); + userModelList.add(userModel1); + userModelList.add(userModel2); + + userService.addList(userModelList); +} +``` -(注:图二操作记录表) +```java +@Override +@LogPoint(serviceHandler = SysUserServiceImpl.class, entityHandler = SysUserModel.class, + operation = OperationEnum.COMMON, moduleName = "sysUser", remark = "addList") +public void addList(List insertModelList) { + this.saveBatch(insertModelList); +} +``` -**情景二**: +#### 情景二:关联对象变更 -~~~java +```java @LogEntity public class SysUserModel extends BaseEntity { - //.... 部分属性忽略 - //子对象model + // 子对象model @TableField(exist = false) private SysRoleModel roleModel; } +``` -~~~ - - - -~~~java - @GetMapping(value = "/addassociate") - @ResponseBody - public void addassociate(){ - //用户 - SysUserModel userModel = new SysUserModel(); - userModel.setRoleId("9830274072323"); - userModel.setUserName("哈哈"); - userModel.setRemark("这是第一行\n" + "这是第二行"); - userModel.setStatus(0); - userModel.setRichText("富文本"); - - //角色 - SysRoleModel roleModel = new SysRoleModel(); - roleModel.setRoleName("测试啊啊啊啊"); - roleModel.setRoleKey("testaaa"); - roleModel.setStatus(0); - - //关联 - userModel.setRoleModel(roleModel); - - userService.addassociate(userModel); - //查看数据库 - } -~~~ - - - -~~~java - @Override - @LogPoint( serviceHandler = SysUserServiceImpl.class, entityHandler = SysUserModel.class, - operation = OperationEnum.COMPLEX, moduleName = "sysUser", remark = "测试用户模块") - public void addassociate(SysUserModel userModel) { - this.insert(userModel); - roleService.insert(userModel.getRoleModel()); - } -~~~ -![输入图片说明](images/R725D7BPBCXV62I2EB8U2.png) - -(注:图三操作记录表) - +```java +@GetMapping(value = "/addassociate") +@ResponseBody +public void addassociate(){ + // 用户 + SysUserModel userModel = new SysUserModel(); + userModel.setUserName("关联用户"); + userModel.setNickName("关联昵称"); + userModel.setPassword("123456"); + userModel.setEmail("123456@qq.com"); + userModel.setPhonenumber("12345678901"); + userModel.setStatus(0); + + // 角色 + SysRoleModel roleModel = new SysRoleModel(); + roleModel.setRoleName("测试啊啊啊啊"); + roleModel.setRoleKey("testaaa"); + roleModel.setStatus(0); + + // 关联 + userModel.setRoleModel(roleModel); + + userService.addAssociate(userModel); +} +``` +```java +@Override +@LogPoint(serviceHandler = SysUserServiceImpl.class, entityHandler = SysUserModel.class, + operation = OperationEnum.COMPLEX, moduleName = "sysUser", remark = "addAssociate") +public void addAssociate(SysUserModel userModel) { + this.save(userModel); + roleService.logInsert(userModel.getRoleModel()); +} +``` ### 工具扩展 -在核心包中有一个handler包,下面提供字段如何字段转换和解析。在包中提供了基础类型(`NORMAL`,`RICHTEXT`,`TEXT`)的类型处理器和值处理器,他们分别实现了`AttributeTypeHandler`,`AttributeValueHandler`中的方法。当默认逻辑不符合我们需求时,我们通过重写逻辑实现不一样的效果,下面我们进行相关介绍。 - #### 值处理器 -在属性记录字段`diffvalue中`,我们`注意到记录变化格式通常是xxx从xx修改为xx`,现在我们将其格式进行变化:(当然你也可以直接改动objectlog-api中的格式,但是不一定满足所有的需求,那么就可以通过这种方式来实现自定义)。 - -> INSERT INTO `objectlog`.`sys_user` (`id`, `role_id`, `user_name`, `remark`, `rich_text`, `status`, `del_flag`, `create_time`, `update_time`) VALUES ('8ad4497100ea480f841b2f645f3c512c', NULL, '哈哈', NULL, NULL, 0, 0, '2024-03-10 07:14:57', '2024-03-10 07:14:57'); +默认情况下,系统会根据字段类型自动处理值的对比和展示。但如果你希望自定义字段值的处理方式,可以通过实现 `AttributeValueHandler` 接口来完成。 -~~~java -@LogEntity -public class SysUserModel extends BaseEntity { - @LogEntity(alias = "用户昵称",extendedValue = true, attributeValueHandler = CustomerValueHandler.class) - private String userName; -} -~~~ +示例代码: -~~~java +```java public class CustomerValueHandler implements AttributeValueHandler { - //新的对比方式 + // 新的对比方式 @Override public String handlerValue(ObjectFieldWrapper objectFieldWrapper) { return objectFieldWrapper.getOldValueString() + "->" + objectFieldWrapper.getNewValueString(); } } -~~~ - +``` -![输入图片说明](images/608FAXNFZD5N2EORNLKWO.png) +然后在字段上指定该处理器: +```java +@LogEntity(alias = "备注", extendedType = "TYPE_HANDLER", attributeTypeHandler = CustomerTypeHandler.class) +private String remark; +``` #### 类型处理器 -~~~java -@LogEntity -public class SysUserModel extends BaseEntity { - @LogEntity(alias = "备注" ,extendedType = "TYPE_HANDLER", attributeTypeHandler = CustomerTypeHandler.class) - private String remark; -} -~~~ - -~~~java - @GetMapping(value = "/typehandler") - @ResponseBody - public void testTypeHandler(){ - SysUserModel userModel = new SysUserModel(); - userModel.setId("8ad4497100ea480f841b2f645f3c512c"); - userModel.setRemark("嘻嘻"); - userService.add(userModel); - //查看数据库 - } -~~~ +如果你希望对特定字段的处理逻辑进行更复杂的定制,可以实现 `AttributeTypeHandler` 接口。 + +示例代码: -~~~java +```java public class CustomerTypeHandler implements AttributeTypeHandler { @Override public ObjectAttributeModel handlerAttributeChange(ObjectFieldWrapper fieldWrapper) { return this.dealAttributeModel(fieldWrapper); } + public ObjectAttributeModel dealAttributeModel(ObjectFieldWrapper fieldWrapper) { ObjectAttributeModel attributeModel = this.getAttribute(fieldWrapper); - //根据需要判断是否生成 + // 根据需要判断是否生成 if (null != attributeModel) { dealBaseInfo(fieldWrapper, attributeModel, fieldWrapper.getExtendedType()); attributeModel.setDiffValue(this.dealDiffValue(fieldWrapper.getOldValueString(), fieldWrapper.getNewValueString())); @@ -374,72 +227,74 @@ public class CustomerTypeHandler implements AttributeTypeHandler { public ObjectAttributeModel getAttribute(ObjectFieldWrapper fieldWrapper) { return new ObjectAttributeModel(); } + public static void dealBaseInfo(ObjectFieldWrapper fieldWrapper, ObjectAttributeModel attributeModel, String attributeType) { - //固定值 + // 固定值 attributeModel.setOldValue(fieldWrapper.getOldValueString()); attributeModel.setNewValue(fieldWrapper.getNewValueString()); - attributeModel.setAttributeName (fieldWrapper.getAttributeName()); + attributeModel.setAttributeName(fieldWrapper.getAttributeName()); attributeModel.setAttributeAlias(fieldWrapper.getAttributeAlias()); attributeModel.setAttributeType(attributeType); } } -~~~ - - -![输入图片说明](images/992ILC7D6E8EN25C50HW5DX8.png) - -注:建议继承BaseAttributeTypeHandler实现重写逻辑。 - - - -### 引入项目 - -> 系统现在默认将日志记录api作为依赖`集成到某个具体的业务模块中`,当然`也可以直接将日志做为一个新的服务`,`下面我们按集成到某个具体的业务模块中进行介绍`。 - -+ **改变基类对象**,目前主流的mybatis-plug提供了自带curd操作类`ServiceImpl`,`IService`,`BaseMapper`,在`@LogPoint`中ServiceImpl切换自身业务系统基于`ServiceImpl`进一步封装类,其次是 `BaseEntity.class`(核心是主键id字段,其他字段可有可无),这个是你自身业务系统的基类。 - ![输入图片说明](images/ZVR70YART5DGR54OB25WVDB.png) - +``` -+ **导入表**,`objectlog-api-test`模块下的 `object_attribute.sql` 和`object_operation.sql`导入到业务模块中 +![输入图片说明](images/608FAXNFZD5N2EORNLKWO.png) +## 引入项目 -+ **配置服务地址**,在引入的依赖服务中配置一下信息 +要将本工具引入你的项目,请按照以下步骤操作: -```yml -object-log: - host: http://127.0.0.1:8888 #服务地址 - path: /objectlog/add #存储接口 - threadPool: - corePoolSize: 5 - maxPoolSize: 10 - queueCapacity: 100 - keepAliveTime: 60 - threadNamePrefix: object-log-thread- - header: #转发需要携带的请求头信息,主要避免转发时认证信息丢失导致不成功 - - Authorization -``` +1. **添加依赖** -+ **业务服务补充保存接口**,以演示代码为例: + 在你的 `pom.xml` 文件中添加以下依赖: -```java -@RestController -@RequestMapping(value = "/objectlog") -public class ObjectOperationController { - @Resource - private ObjectOperationService operationService; - @PostMapping("/add") - public void addLog(@RequestBody ObjectOperationDto model) { - //TODO 保存逻辑 - } -} -``` + ```xml + + org.sweetie.objectlog + objectlog-api + 1.0.0 + + ``` +2. **配置日志服务** + 在 `application.yml` 或 `application.properties` 中添加以下配置: + ```yaml + object-log: + host: http://localhost:8080 + path: /objectlog/add + header: + - Content-Type=application/json + thread-pool: + core-pool-size: 5 + max-pool-size: 10 + queue-capacity: 100 + keep-alive-time: 60 + thread-name-prefix: objectlog-pool- + rejected-execution-handler: CALLER_RUNS + ``` +3. **启用日志功能** + 在 Spring Boot 启动类上添加以下注解: + ```java + @EnableAspectJAutoProxy + @MapperScan("org.sweetie.objectlog.**.mapper") + ``` +4. **使用注解标记需要记录的操作** + 在需要记录变更的方法上添加 `@LogPoint` 注解,并指定对应的实体类和服务类。 + ```java + @LogPoint(serviceHandler = SysUserServiceImpl.class, entityHandler = SysUserModel.class, + moduleName = "sysUser", remark = "测试用户模块") + public void add(SysUserModel insertModel) { + this.insert(insertModel); + } + ``` +通过以上步骤,你就可以轻松地在项目中集成并使用本工具来记录对象变更日志了。 \ No newline at end of file