当前位置: 首页 > 科技观察

建筑师必看!操作日志系统构建秘笈

时间:2023-03-22 10:12:47 科技观察

在Java开发中,我们经常会遇到一个棘手的问题:记录用户操作行为。有些操作比较简单,我们可以一一记录。但是有些操作是很难记录的,比如编辑操作。在某个操作中,用户可能编辑了对象A的多个属性,而在下一个操作中,用户可能编辑了对象B的多个属性。如果我们分别记录对象A和对象B的属性变化,整个操作非常复杂的。而且,它将与业务运营高度耦合。今天我们介绍一个名为ObjectLogger的系统,它是一个强大易用的Java对象日志系统,可以分析任意对象的属性变化,实现对象变化的记录和查询。因此可以应用于用户操作日志记录、对象属性变化记录等多种场景。简单易用,是一款功能强大的工具。基于它,我们可以轻松实现以下效果。该系统是github上的一个开源项目,地址为:https://github.com/yeecode/ObjectLogger下面我们简单介绍一下该系统。基于它,我们可以很容易地搭建一个日志系统。1系统特点本系统具有以下特点:一站式集成:系统支持日志记录和查询,开发者只需开发前端接口即可使用。完全独立:与业务系统无耦合,可插拔,不影响主业务流程。应用共享:系统可以同时被多个业务系统使用,互不影响。简单易用:服务端直接启动jar包;业务系统由官方Maven插件支持。自动解析:可自动解析对象的属性变化,支持富文本前后对比。易于扩展:支持自定义对象变化描述和属性变化描述。支持更多对象属性类型的扩展。2快速入门2.1创建数据库使用项目的/server/database/init_data_table.sql文件初始化两个数据表。2.2启动Server下载本项目下最新的Server服务jar包,地址为/server/target/ObjectLogger-*.jar。启动下载的jar包。java-jarObjectLogger-*.jar--spring.datasource.driver-class-name={db_driver}--spring.datasource.url=jdbc:{db}://{db_address}/{db_name}--spring.datasource.username={db_username}--spring.datasource.password={db_password}上述命令中的用户配置项说明如下:db_driver:数据库驱动。com.mysql.jdbc.Driver如果使用MySql数据库;com.microsoft.sqlserver.jdbc.SQLServerDriver如果使用SqlServer数据库。db:数据库类型。如果你用的是MySql数据库,那就是mysql;如果你用的是SqlServer数据库,那就是sqlserver。db_address:数据库连接地址。如果数据库在本地机器上,则为127.0.0.1。db_name:数据库名称,需要包含上一步初始化的两个数据表。db_username:数据库登录用户名。db_password:数据库登录密码。启动jar包后,系统默认服务地址为:http://127.0.0.1:8080/ObjectLogger/访问上述地址,可以看到如下欢迎界面:至此,ObjectLogger系统搭建完成,可以接受业务系统录入和查询操作的日志写入。3业务系统接入本部分讲解如何配置业务系统,将业务系统中的对象变化记录到ObjectLogger中。3.1引入依赖包在pom中添加如下依赖:com.github.yeecode.objectLoggerObjectLoggerClient{***version}3.2ObjectLoggerClient添加bean自动注入3.2.1对于SpringBoot应用,在SpringBoot启动类前添加@ComponentScan注解,在basePackages中添加ObjectLoggerClient的包地址:com.github.yeecode.objectLoggerClient,如:@SpringBootApplication@ComponentScan(basePackages={"{your_beans_root}","com.github.yeecode.objectLogger"})publicclassMyBootAppApplication{publicstaticvoidmain(String[]args){//省略其他代码}}3.2.2对于applicationContext中的Spring应用.xml添加扫描ObjectLoggerClient包地址:3.3完成配置添加:application.properties中的object.logger.add.log.api=http://{ObjectLogger_address}/ObjectLogger/log/addobject.logger.appName={your_app_name}object.logger.autoLog=true对象tLogger_address:该属性指向上一步ObjectLogger的部署地址,例如:127.0.0.1:8080your_app_name:指当前业务系统的应用名称,用于区分日志来源,同时支持多个业务系统.object.logger.autoLog:是否将objectchangelog的所有属性记录到此,至此业务系统配置完成。已经实现了与ObjectLoggerServer端的连接。4日志查询系统运行后,可以通过/ObjectLogger/log/query查询系统中记录的日志,并通过传入参数对日志进行过滤。通过这里,我们可以查询到下一步写入的日志。5日志写入业务系统在任何需要日志的类中引入LogClient。例如:@AutowiredprivateLogClientlogClient;5.1使用简单将对象的零个、一个或多个属性变化放入actionItemModelList中发送出去即可。如果actionItemModelList设置为null,则表示该对象没有需要记录的属性更改。例如在业务应用中调用:logClient.sendLogForItems("TaskModel",5,"actorname","addTask","addTask","viawebpage","somecomments",null);在ObjectLogger中使用以下查询条件:http://{your_ObjectLogger_address}/ObjectLogger/log/query?appName=myBootApp&objectName=TaskModel&objectId=5查询日志:{"respMsg":"success","re??spData":[{"id":16,"appName":"myBootApp","objectName":"TaskModel","objectId":5,"actor":"actorname","action":"addTask","actionName":"addTask","extraWords":"viawebpage","comment":"somecomments","actionTime":"2019-04-10T10:56:15.000+0000","actionItemModelList":[]}],"respCode":"1000"}5.2对象变化自动记录完成新旧对象的比对,根据比对结果将多个属性变化一并写入日志系统。使用时要确保传入的新旧对象属于同一个类。例如,业务系统调用如下:p>"+"

第二行

"+"

第三行

");TaskModelnewTaskModel=newTaskModel();newTaskModel.setId(9);newTaskModel.setTaskName("newName");newTaskModel.setUserId(5);newTaskModel.setDescription("

thefirstline

"+"

thesecondline

"+"

thelastline

");logClient.sendLogForObject(9,“actorname”,“editTask”,“editTask”,“viaapp”,“somecomments”,oldTaskModel,newTaskModel);那么我们可以使用如下查询条件:http://{your_ObjectLogger_address}/ObjectLogger/log/query?appName=myBootApp&objectName=TaskModel&objectId=9查询到如下结果:{"respMsg":"success","re??spData":[{"id":15,"appName":"myBootApp","objectName":"TaskModel","objectId":9,"actor":"actorname","action":"editTask","actionName":"editTask","extraWords":"viaapp","comment":"somecomments","actionTime":"2019-04-10T10:56:17.000+0000","actionItemModelList":[{"id":18,"actionId":15,"attributeType":"NORMAL","attribute":"taskName","attributeName":"TASK","oldValue":"oldName","newValue":"newName","diffValue":null},{"id":19,"actionId":15,"attributeType":"USERID","attribute":"userId","attributeName":"USER","oldValue":"USER:3","newValue“:”用户:5","diffValue":"diffValue"},{"id":20,"actionId":15,"attributeType":"TEXT","attribute":"description","attributeName":"DESCRIPTION","oldValue":""\t

第一行

\n\t

第二行

\n\t

第三行

"","newValue":""\t

thefirstline

\n\t

thesecondline

\n\t

thelastline

"","diffValue":"第6行更改:
-:the3thline
+:thelastline
"}]}],"respCode":"1000"}6对象属性过滤一些对象属性的变化Logging不是required,比如updateTime,hashCode等。ObjectLogger支持对象属性的过滤,只跟踪我们感兴趣的属性。并且,对于每一个属性,我们可以改变它在ObjectLogger系统中记录的具体方式,比如修改name等,要启用该功能,首先将配置中的object.logger.autoLog改为false,object.logger.autoLog=false,然后在需要更改的属性上加上@LogTag注解。没有此注释的任何属性将在记录期间自动跳过。比如注解配置如下,id字段的变化会被忽略。privateIntegerid;@LogTag(name="TaskName")privateStringtaskName;@LogTag(name="UserId",extendedType="userIdType")privateintuserId;@LogTag(name="Description",builtinType=BuiltinTypeHandler.TEXT)privateStringdescription;注解属性介绍如下:name:必填,对应写入日志后的attributeName值。builtinType:ObjectLogger的内置类型,即BuiltinTypeHandler的值。默认为BuiltinTypeHandler.NORMAL。BuiltinTypeHandler.NORMAL:记录属性的新旧值,比较值为nullBuiltinTypeHandler.TEXT:用户富文本比较。记录属性值的新值和旧值,将新旧值转换为明文后逐行比较差异,在比较值中记录差异extendedType:扩展属性类型。使用ObjcetLogger时,用户可以扩展某些字段的处理方式。7属性处理扩展在很多情况下,用户希望能够独立决定如何处理某些对象属性。例如,对于示例中Task对象的userId属性,用户可能希望将其转换成名称存储在日志系统中,从而使日志系统与userId完全解耦。ObjectLogger完全支持这种情况,允许用户独立决定如何记录某些属性。要实现这个功能,首先要在需要扩展的属性上给@LogTag的extendedType属性赋一个字符串值。例如:@LogTag(name="UserId",extendedType="userIdType")privateintuserId;然后在业务系统中声明一个Bean来继承BaseExtendedTypeHandler作为一个hook,进行自由扩展。代码如下:@ServicepublicclassExtendedTypeHandlerimplementsBaseExtendedTypeHandler{@OverridepublicBaseItemModelhandleAttributeChange(StringattributeName,StringlogTagName,ObjectoldValue,ObjectnewValue){returnnull;}}接下来,ObjectLogger在处理这个属性时,会将属性的相关信息传递给扩展的handleAttributeChange方法豆。然后用户可以自己处理。传入的四个参数解释如下:extendedType:扩展类型值,即@LogTag注解的extendedType值。在此示例中,userIdType。attributeName:属性名称。本例中的userId。logTagName:@LogTag注解的name值,可以为null。本例中的UserId。oldValue:属性的旧值。newValue:属性的新值。例如,我们可以通过以下方式处理userIdType属性:USER_oldValue);baseActionItemModel.setNewValue("USER_"+newValue);baseActionItemModel.setDiffValue(oldValue+"->"+newValue);}returnbaseActionItemModel;}8摘要是什么,是ObjectLoggerhttps://github.com/yeecode/ObjectLogger的存在极大的方便了我们的日志操作。