tp5记录用户的操作日志-用户操作日志系统如何实现?架构师必读_第1页
tp5记录用户的操作日志-用户操作日志系统如何实现?架构师必读_第2页
tp5记录用户的操作日志-用户操作日志系统如何实现?架构师必读_第3页
tp5记录用户的操作日志-用户操作日志系统如何实现?架构师必读_第4页
tp5记录用户的操作日志-用户操作日志系统如何实现?架构师必读_第5页
已阅读5页,还剩7页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

tp5记录⽤户的操作⽇志_⽤户操作⽇志系统如何实现?架构师必读!★⽤户操作⽇志是系统中常见的功能,可它该如何开发呢?这篇⽂章将给你答案。本⽂将详细介绍⽤户⽇志系统的设计、开发过程。”系统开发中我们经常使⽤⼀些⽇志框架(如JAVA中的log4j/logback/slf4j等),⽤来调试、追踪、输出系统运⾏状况等,这些⽇志通常是给程序员看的,暂且叫它”系统⽇志“。⽽对于普通⽤户来说,也需要⼀个⽇志功能,可以⽅便查阅⾃⼰做过哪些操作,这些⽇志是⾯向普通⽤⽤户的,暂且叫它”⽤户操作⽇志“。那⽤户操作⽇志系统该如何实现呢?这涉及软件架构设计与开发的多个⽅⾯,具有很强的通⽤性。研究好这个问题对于开发能⼒的提升很⼤。今天有时间,我们来解答⼀下这个问题。并且,最后还会附上实现代码。实现的效果如下,这是实际截取的图:整个回答不仅包含实现,还包括架构设计过程,会⽐较长。如果有不清楚的地⽅,⼤家可以在评论区提问。整个解答包括问题定义、模型设计、⽅案设计、最终实现等多个环节。展现了系统架构设计的全部流程。⽬录如下:1功能定义2模型设计2.1上层切⾯2.2下层切⾯2.3混合切⾯3对象属性对⽐功能实现4对象属性处理4.1普通属性4.2特殊属性4.3业务属性5易⽤性注1功能定义在开发⼀个系统之前,我们先要对系统进⾏明确的定义。在⼀个软件系统中,通常存在增删改查四类操作。对于⽇志系统⽽已,这四类操作的处理难度不同。查询操作往往不需要记录⽇志,增加和删除操作涉及⼀个对象状态,编辑操作涉及对象编辑前和编辑后的两个状态。因此,编辑操作是整个⽇志模块中最难处理的。只要掌握了编辑操作,则新增操作、删除操作、查询操作都很简单了。毕竟,新增操作可以理解为null到新对象的编辑,删除操作可以理解为旧对象到null的编辑,查询操作可以理解为旧对象到旧对象的编辑。因此,本⽂主要以编辑操作为例进⾏介绍。为了便于描述,我们假设⼀个学校卫⽣⼤扫除系统。这个系统中包含很多⽅法,例如分配⼤扫除⼯作的assignTask⽅法,开始某个具体⼯作的startTask⽅法,验收某个具体⼯作的checkTask⽅法,增加新的⼈员的addUser⽅法等。每个⽅法都有不同的参数,涉及不同的对象。以startTask⽅法为例,开始⼀个任务需要在任务中记录开始时间、责任⼈、使⽤的⼯具,整个⽅法如下:publicStringstartTask(StringtaskId,IntegeruserId,DatestartTime,Tooltool){//业务代码}最简单的记录⽇志的⽅法便是在代码中直接根据业务逻辑写⼊⽇志操作语句,例如:publicStringstartTask(StringtaskId,IntegeruserId,DatestartTime,Tooltool){//业务代码log.add("操作类型:开始任务。任务编号:"+taskId+";责任⼈:如果你真的打算使⽤上述的⽅法记录⽇志,那已经没有什么可以教你的了。你要做的就是提升⾃⼰Ctrl+C和Ctrl+V的速度,努⼒成为⼀个真正的CV⼤神直到顶级CRUD⼯程师。⽽如果你想要设计⼀个较为专业、通⽤、易⽤的⽇志模块,那请继续向下阅读。我们必须从模型设计开始慢慢展开。2模型设计设计系统的第⼀步是抽象,抽象出⼀个简单的便于处理的模型。我们可以把⽤户操作抽象为下⾯的模型,即⽤户通过业务逻辑修改了持久层中的数据。要想记录⽇志,那我们需要在整个流程中设置⼀道切⾯,⽤以获取和记录操作的影响。⽽这⼀道切⾯的位置⼗分关键,我们下⾯探讨这⼀点。本章节主要讨论⼀个问题:单⼀切⾯能否实现⽤户操作⽇志的记录。如果使⽤单⼀的切⾯能实现⽇志记录功能,那就太好了。这意味着我们只要在系统中定义⼀个⽇志切⾯,则所有的⽤户操作都会被记录。⽽如果单⼀的切⾯⽆法做到,那我们的⽇志操作就需要侵⼊业务逻辑。在展开讨论之前要注意,这⾥只是模型设计,请忽略⼀些细节。例如,参数是英⽂变量名,不便于表意;某些参数是id,与系统强耦合等。这些都不是模型层需要考虑的,我们会在后续的设计中解决这些问题。2.1上层切⾯⾸先,我们考虑在整个业务逻辑的最上层设置切⾯如下图所⽰:这⼀层其实就是业务逻辑⼊⼝处,以下⾯的⽅法为例:publicStringstartTask(StringtaskId,IntegeruserId,DatestartTime,Tooltool){//业务代码}我们可以得到的⽇志信息有:startTask:⽅法的名称-taskId:⽅法的参数名,及其对应的参数值,例如15-userId:⽅法的参数名,及其对应的参数值,例如3-startTime:⽅法的参数名,及可见这些信息的特点是贴近业务逻辑。因为startTask表明了我们要进⾏的业务逻辑的操作类型,⽽后⾯的操作参数则表明了业务逻辑的参数。然⽽缺点也很明显:⾸先,⽆法获得编辑前的旧对象。即我们不知道startTask执⾏前task对象的状态。其次,它不能反映真正的数据变动。这⼀点是致命的。好,我们接下来说明⼀下第⼆点。因为我们是上层切⾯,从⼊参处获取信息。但是,⼊参的信息却不⼀定是最终持久化的信息。假设⽅法中存在下⾯的业务逻辑:publicStringstartTask(StringtaskId,IntegeruserId,DatestartTime,Tooltool){//其他业务代码while(taskBusiness.queryByTaskId(taskId).isFinished()){则上层切⾯获得的taskId信息可能是效的,甚⾄,整个操作都是效的。因此,上层切⾯的特点是:贴近业务逻辑、不能反映真实数据变动。因此,上层切⾯⽆法直接采⽤。2.2下层切⾯下层切⾯就是在业务逻辑的最下层设置切⾯,如下图所⽰:这⼀层其实就是在持久层获取⽇志信息。startTask⽅法可能在持久层对应了下⾯的update操作:updateTask(TaskModeltaskModel);//该⽅法对应了MyBatis等⼯具中的SQL语句通过这个⽅法可以得到的⽇志信息有:updateTask:-taskId-userId-startTime-toolId-taskName-taskDescription⾸先,以上信息是准确的。因为这些信息是从写⼊持久层的操作中获取的,例如从SQL语句的前⼀步获取。这⾥⾯的taskId、userId等值可能和⼊参的值不⼀样,但⼀定是准确的。但是,它仍然存在两个问题:⾸先,⽆法获得编辑前的旧对象。同上。其次,它脱离业务逻辑。我们还是主要说明⼀下第⼆点,例如,⽇志信息中的updateTask反应了这是⼀次任务编辑操作,但是任务编辑操作是很多的:assignTask、startTask、checkTask、changeTaskName等不同的业务操作可能都会映射为⼀次SQL操作中的update操作。在这⾥,我们⽆法区分了。并且,编辑操作⼀般写的⼤⽽全,例如常写为下⾯的形式:UPDATEtaskuserId=#{userId},startTime=#{startTime},toolId=#{toolId},taskName=#{taskName},taskDes当我们调⽤updateTask⽅法时,task对象的各个属性都会被传⼊。但是这些属性中,有很多并没有发变动,是没有必要被⽇志系统记录的。可见,下层切⾯的特点是:反映真实数据变动,脱离业务逻辑。因此,下层切⾯⽆法直接采⽤。2.3混合切⾯上层切⾯和下层切⾯都不能单独使⽤,这意味着我们不可能使⽤⼀个简单的切⾯完成⽇志操作。那最终怎么解决呢?使⽤混合“切⾯”,即吸收下层切⾯的准确性、整合上层切⾯的业务逻辑信息,并顺便解决旧对象的获取问题。对“切⾯”加引号是因为这不是⼀个绝对纯粹的切⾯,它对业务逻辑存在⼀定的侵⼊性。但这是没有办法的。我们需要在业务逻辑中增加⼀⾏类似下⾯的代码:logClient.logXXX(params...);⾄于这⾏代码如何写,后⾯的逻辑如何,我们后⾯细化。但是我们知道,这⾏代码中传⼊的参数要既包含上层信息也包含下层信息。以下层信息为主(因为它准确),以上层信息为辅(因为它包含业务信息)。如下图所⽰。接下来我们会⼀步⼀步介绍其实现。3对象属性对⽐功能实现我们说道在下⾯⽅法中,获得的信息以下层信息为主,以上层信息为辅。那我们先说下层信息,显然就是数据库中的⽼对象和修改后的新对象,因此,其⼊参形式如下:logClient.logObject(oldObject,newObject);⽽在处理⽇志的第⼀步,就是找出新对象和⽼对象之间属性的不同。假设tool对象的属性如下:toolId:编号toolName:⼯具名称price:价格position:存放位置要想把新旧两个tool对象的属性不同找出来,可以使⽤类似下⾯的代码。//对⽐⼯具的名称toolNameif(!oldTool.getToolName().equals(newTool.getToolName())){log.add("toolName",diff(oldTool.getToolName(),newTool.getToolNam这种代码可以实现功能,但是…仅仅适⽤于tool对象。如果换成了task对象,则⼜要重新写⼀套。假设task对象的属性如下:taskId:编号userId:责任⼈编号startTime:开始时间toolId:需要的⼯具的编号taskName:任务名taskDescription:任务描述那是不是只能根据task对象的属性再写⼀套if……如果你真的就是打算使⽤上述的⽅法记录⽇志,那我已经没有什么可以教你的了。你要做的就是提升⾃⼰Ctrl+C和Ctrl+V的速度,努⼒成为⼀个真正的CV⼤神直到顶级CRUD⼯程师。⽇志模块的使⽤场景不同,要处理的对象(即oldObject和newObject)千奇百怪。因此,上⾯的这种代码显然也是不可取的。所以说,我们要⾃动分析对象的属性不同,然后记录。即将对象拆解开来,逐⼀对⽐两个对象(来⾃同⼀个类)的各个属性,然后将不同的记录下来。显然,要⽤反射。那这个问题就解决了,如果对反射不了解的,可以学习反射相关知识。这些⽐较基本,我就不赘述了。使⽤反射之后,我们要记录新⽼对象的变动则只需要如下调⽤:logClient.logObject(oldObj,newObj);然后在这个⽅法中采⽤反射找出对象的各个属性,然后依次进⾏⽐对。其实现代码如下:/***⽐较两个任意对象的属性不同*@paramoldObj第⼀个对象*@paramnewObj第⼆个对象*@return两个对象的属性不同*/publicstaticMapdiffObj(Objectold这样,下层的新⽼对象信息就处理完成了。我们可以在⽅法中通过参数补充⼀些上层业务信息。因此,上述⽅法可以修改为:logClient.logObject("操作⽅法","操作⽅法别名","触发该操作的⽤户等其他信息",oldObj,newObj);logObject⽅法就是我们要实现的⽅法,其核⼼操作逻辑就是分析对⽐新对象和旧对象的不同,将不同记录下来,作为此次操作引发的变动。4对象属性处理我们已经介绍了实现新旧对象属性⽐对的基本实现逻辑,但是⼀切并没有这么简单。因为,对象的属性本⾝就⾮常复杂。例如,有些属性(例如userId)是对其他对象的引⽤,把它们写⼊⽇志会让⼈觉着摸不着头脑(例如应该换成⽤户姓名或⼯号);有些属性(例如富⽂本)则⼗分复杂,在写⼊⽇志前需要进⾏特殊的处理。在这⼀节,我们将介绍这些特殊的属性处理逻辑。4.1普通属性当我们⽐较出新⽼对象的属性时,有⼀些属性可以直接计⼊⽇志。直接记录为“从{oldValue}修改为{newValue}”的形式即可。例如,tool对象的价格,可以计⼊为:price:从47修改为51其中47是属性的旧值,51是属性的新值。4.2特殊属性但是有⼀些属性不可以,例如长⽂本。我们采⽤新值旧值的形式记录其变动是不合理的。例如:description:从“今天天⽓好\n真好\n哈哈嘿嘿哈哈”修改为“今天天⽓好\n哈哈嘿嘿哈哈”这种形式显然很难看、很难懂。我们想要的结果应该是:description:删除了第2⾏“真好”这时,我们可以设置⼀种机制,对复杂⽂本的属性进⾏特殊的处理。最终得到下⾯的结果。这样⼀来,效果是不是好多了。在具体实现上,我们可以使⽤注解来标明⼀个属性的值需要特殊处理的类型,如下:@LogTag(innerType=InnerType.FullText)privateStringdescription;这样,我们在⽇志模块设计机制,识别出InnerType.FullText的属性后使⽤富⽂本处理⽅式对其进⾏新旧值的⽐对处理。当然,这种机制不仅适⽤于富⽂本,还有⼀些其他的属性,例如图⽚。我们可以引⽤新旧图⽚的地址进⾏展⽰。4.3业务属性还有⼀种属性,更为特殊。task对象中的责任⼈。我们采⽤下⾯的⽅式记录显然不太友好:userId:从4修改为5在task对象的userId属性中存放的是⽤户编号,4、5都是⽤户编号。但在⽇志中我们更希望看到⼈员姓名。可是⽤户编号到姓名信息⽇志模块是没有的。因此,这时候我们需要业务模块实现⽇志模块提供的接⼝,来完成上述映射。得到如下结果:userId:从“王⼆丫”修改为“李⼤笨”不只是userId,还有toolId等各种业务属性也适⽤这种处理⽅式。这样处理还带了⼀个优点:解耦。当⼀个⽇志系统记录下某个⽇志时,例如,记录下“⼩明删除了⽂件A”时,即使业务系统将⼩明的userId和⼩李的userId互换,则⽇志系统也不能将⽇志变为“⼩李删除了⽂件A”。因此,⽇志系统中的数据应该是⼀经落库⽴刻封存。在具体实现上,我们可以使⽤注解来标明⼀个属性的值需要由业务系统辅助处理,如下:@LogTag(extendedType="userIdType")privateintuserId;这样,我们在⽇志模块设计机制,识别出userId属性后使⽤userIdType处理⽅式调⽤业务模块提供的接⼝对其进⾏新旧值的⽐对处理。5易⽤性注解经过上⾯的处理,我们已经能够拿到类似下⾯的⽇志结果:userId:从“王⼆丫”修改为“李⼤笨”description:删除了第2⾏“真好”price:从47修改为51其形式已经不错了。但是这⾥的userId、description、price是⼀个属性名,当给⽤户展⽰时,⽤户并不知道其确切含义。因此,我们需要提升其易⽤性。在具体实现上,我们可以使⽤注解来标明⼀个属性的值需要由业务系统辅助处理,如下:@LogTag(alias="责任⼈",extendedType="userIdType")privateintuserId;@LogTag(alias="说明",innerType=InnerType.FullText)privateStringdescription;然后在⽇志模块中,我们对注解进⾏处理,可以得到下⾯形式的⽇志信息:责任⼈:从“王⼆丫”修改为“李⼤笨”说明:删除了第2⾏“真好”价格:从47修改为51这样,整个⽇志的输出形式就⽐较友好了。6存储设计获取了对象的不同之后,我们应该将其存储起来。显然,最简单的:CREATETABLE`log`(`objectId`varchar(500)NOTNULLDEFAULT'',`operationName`varchar(500)NOTNULL,`diff`varchar(5000)DEFAULTNULL这样就记录了objectId的对象因为operationName操作发⽣了diff的变动。然后把下⾯的⽂字作为⼀个完整的字符串存⼊diff字段中。责任⼈:从“王⼆丫”修改为“李⼤笨”说明:删除了⼀⾏“真好”价格:从47修改为51如果你真的打算使⽤上述的⽅法记录⽇志,那我已经没有什么可以教你的了。没,开玩笑。这个不⾄于,因为这个只是考虑不全⾯导致的个⼩问题。我们不能使⽤diff就简简单单地将各个属性杂糅在⼀起,将原本结构化的数据变为了⾮结构化的数据。我们可以采⽤操作表+属性表的形式来存储。⼀次操作会操作⼀个对象,这些都记录到操作表中;这次操作会变更多个属性,这些都记录到属性表中。进⼀步,我们可以在操作表中记录被操作对象的类型,这样,防⽌不同对象具有相同的id⽽混淆。⽽且,我们还可以设置⼀个appName字段,从⽽使得这个⽇志模块可以供多个应⽤共⽤,成为⼀个独⽴的⽇志应⽤。我们也可以在记录操作名“startTask”的同时记录下其别名“开始任务”,等等。从⽽全⾯提升⽇志模块的功能性、易⽤性。同样的,属性表中我们可以记录各个属性的类型,便于我们进⾏分别的展⽰。记录属性的旧值、新值、前后变化等。不多说了,我直接给出两个表的DDL:CREATETABLE`operation`(`id

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论