飞道的博客

我们已经不用AOP做操作日志了!

436人阅读  评论(0)

 

前言

用户在操作我们系统的过程中,针对一些重要的业务数据进行增删改查的时候,我们希望记录一下用户的操作行为,以便发生问题时能及时的找到依据,这种日志就是业务系统的操作日志。

本篇我们来探讨下常见操作日志的实现方案和可行性

 

常见的操作日志类型

  • 用户登录日志
  • 重要数据查询日志 (但电商可能不重要的数据也做埋点,比如在淘宝上你搜索什么商品,即使不买,一段时间内首页也会给你推荐类似的东西)
  • 重要数据变更日志 (如密码变更,权限变更,数据修改等)
  • 数据删除日志
  • ......

总结来说,就是重要的增删改查根据业务的需要来做操作日志的埋点。

 

实现方案对比

基于AOP(切面)传统的实现方案

  • 优点:实现思路简单;
  • 缺点:增加数据库的负担,强依赖前端的传参,不方便拓展,不支持批量操作,不支持多表关联;

 

基于数据库Binlog

  • 优点:解除了数据新旧变化的耦合,支持批量操作,方便多表关联拓展,不依赖开发语言;
  • 缺点:数据库表设计需要统一的约定;

 

方案实现细节

 

一、基于AOP切面+注解的传统方案

传统的做法就是切面+注解的方式,这种对代码的侵入性不强,通常记录ip、业务模块、操作账号、操作场景、操作来源等等,一般在注解+拦截器里这些值都拿得到,如下图所示:

 

这种常见的我们在通用方法都可以处理,但是在数据变更方面,一直没有较好的实现方式,比如数据在变更前是多少,变更后是多少。

以我们以前实现的一套方案来说,基于数据变更的记录方式不仅要和需求方约定好模板(上百个字段的不可能都做展示和记录),也要和前端做一些约定,比如在修改之前的值是多少,修改后的值是多少,如下代码客观请看:


  
  1.      @Valid
  2.      @NotNull(message = "新值不能为空")
  3.      @UpdateNewDataOperationLog
  4.      private T newData;
  5.      @Valid
  6.      @NotNull(message = "旧值不能为空")
  7.      @UpdateOldDataOperationLog
  8.      private T oldData;

存在的问题:

  • 1.旧值如果不多查询一次数据库则需要依赖前端把旧值封装到oldData对象中,很有可能已经不是修改前的值;
  • 2.无法处理批量的List数据;
  • 3.不支持多表操作;

再以一个场景为例,再删除之前需要记录删除前的值,是不是还得再查一次~


  
  1.      @PostMapping("/delete")
  2.      @ApiOperation(value = "删除用户信息", notes = "删除用户信息")
  3.      @DeleteOperationLog(system = SystemNameNewEnum.SYS_JMS_LMDM, module = ModuleNameNewEnum.LMDM_AUTH, table = LogBaseTableNameEnum.TABLE_USER, methodName = "detail")

 

二、基于数据库Binlog 方案

系统架构图如下:

 

「主要分为3块:」

  • 1:业务应用 生成每次操作的traceid,并更新到操作的业务表中,发送1条业务消息,包含当前操作的操作人相关的信息;
  • 2:日志收集应用 对业务日志和转换后的binlog日志做整合,提供对外的日志查询搜索API;
  • 3:日志处理应用

  
  1. { "data":[{ "id": "122158992930664499", "bill_type": "1", "create_time": "2020-04-2609:15:13", "update_time": "2020-04-2613:45:46", "version": "2", "trace_id": "exclude-f04ff706673d4e98a757396efb711173"}],
  2. "database": "yl_spmibill_8",
  3. "es": 1587879945200,
  4. "id": 17161259,
  5. "isDdl": false,
  6. "mysqlType":{ "id": "bigint(20)",
  7. "bill_type": "tinyint(2)",
  8. "create_time": "timestamp",
  9. "update_time": "timestamp",
  10. "version": "int(11)",
  11. "trace_id": "varchar(50)"},
  12. "old":[{ "update_time": "2020-04-2613:45:45",
  13. "version": "1",
  14. "trace_id": "exclude-36aef98585db4e7a98f9694c8ef28b8c"}],
  15. "pkNames":[ "id"], "sql": "",
  16. "sqlType":{ "id":- 5, "bill_type":- 6, "create_time": 93, "update_time": 93, "version": 4, "trace_id": 12},
  17. "table": "xxx_transfer_bill_117",
  18. "ts": 1587879945698, "type": "UPDATE"}

处理完binlon日志转换后的操作日志,如下:


  
  1.   {
  2.    "id": "120716921250250776",
  3.    "relevanceInfo": "XX0000097413282,",
  4.    "remark": "签收财务网点编码由【】改为【380000】,
  5.   签收网点名称由【】改为【泉州南安网点】,签收网点code由【】改为【2534104】,运单状态code由【204】改为【205】,签收财务网点名称由【】改为【福建代理区】,签收网点id由【0】改为【461】,签收标识,1是,0否由【0】改为【1】,签收时间由【null】改为【2020-04-24 21:09:47】,签收财务网点id由【0】改为【400】,",
  6.    "traceId": "120716921250250775"
  7.   }

库表设计

  • 1:所有业务系统表需要添加trace_id字段,每次操作生成一个随机字符串并保存到业务表中;
  • 2:日志收集应用库表设计

  
  1. CREATE  TABLE  `table_config` (
  2.    `id`  bigint( 20NOT  NULL AUTO_INCREMENT  COMMENT  'id',
  3.    `database_name`  varchar( 50CHARACTER  SET utf8mb4  COLLATE utf8mb4_general_ci  DEFAULT  NULL  COMMENT  '数据库名',
  4.    `table_name`  varchar( 50CHARACTER  SET utf8mb4  COLLATE utf8mb4_general_ci  DEFAULT  NULL  COMMENT  ' 数据库表名',
  5.   PRIMARY  KEY ( `id`),
  6.    UNIQUE  KEY  `unq_data_name_table_name` ( `database_name`, `table_name`USING BTREE  COMMENT  '数据库名表名联合索引'
  7. ENGINE= InnoDB AUTO_INCREMENT= 35  DEFAULT  CHARSET=utf8mb4  COLLATE=utf8mb4_0900_ai_ci  COMMENT= '数据库配置表';

  
  1. CREATE  TABLE  `table_field_config` (
  2.    `id`  bigint( 20NOT  NULL AUTO_INCREMENT,
  3.    `table_config_id`  bigint( 20DEFAULT  NULL,
  4.    `field`  varchar( 50CHARACTER  SET utf8mb4  COLLATE utf8mb4_general_ci  DEFAULT  NULL  COMMENT  '字段 数据库',
  5.    `field_name`  varchar( 50CHARACTER  SET utf8mb4  COLLATE utf8mb4_general_ci  DEFAULT  NULL  COMMENT  '字段 中文名称',
  6.    `enum_flag` tinyint( 2DEFAULT  NULL  COMMENT  '是否枚举字段(1:是,0:否)',
  7.    `relevance_flag` tinyint( 2DEFAULT  NULL  COMMENT  '是否是关联字段(1:是,0否)',
  8.    `sort`  int( 11DEFAULT  NULL  COMMENT  '排序',
  9.   PRIMARY  KEY ( `id`),
  10.    KEY  `idx_table_config_id` ( `table_config_id`USING BTREE  COMMENT  '表ID索引'
  11. ENGINE= InnoDB AUTO_INCREMENT= 2431  DEFAULT  CHARSET=utf8mb4  COLLATE=utf8mb4_0900_ai_ci  COMMENT= '数据库字段配置表';

  
  1. CREATE  TABLE  `table_field_value` (
  2.    `id`  bigint( 20NOT  NULL,
  3.    `field_config_id`  bigint( 20DEFAULT  NULL,
  4.    `field_key`  varchar( 50CHARACTER  SET utf8mb4  COLLATE utf8mb4_general_ci  DEFAULT  NULL  COMMENT  ' 枚举',
  5.    `filed_value`  varchar( 50CHARACTER  SET utf8mb4  COLLATE utf8mb4_general_ci  DEFAULT  NULL  COMMENT  '枚举名称',
  6.   PRIMARY  KEY ( `id`),
  7.    KEY  `ids_field_config_id` ( `field_config_id`USING BTREE
  8. ENGINE= InnoDB  DEFAULT  CHARSET=utf8mb4  COLLATE=utf8mb4_0900_ai_ci  COMMENT= '数据字典配置表';

 

效果

 

基于binlog实现方案未来规划

  1. 优化发送业务消息的实现,使用切面拦截减少对业务代码的侵入;
  2. 目前暂时不支持对多表关联操作日志记录,需要拓展;

 

总结

本文以操作日志为题材讨论了操作日志的实现方案和可行性,并且都已经在功能上进行实现,其中使用aop方案也是大部分中小企业的首选实现方案,但是在一些金融领域以及erp相关系统,对操作日志记录明细要求极高,常见技术方案很难满足,即使能够满足也会带来一些代码强侵入以及性能问题,所以我们又讨论了基于binlog实现的方案,该方案虽然比对aop来说增强了技术的复杂性,但是对于有一定技术积累的团队来说不算什么难事,并且该方案我们都实现了上线,并且解决了代码层面上的侵入,属于跨语言级别的,相信对读者还是有一定的启发。

 

 


转载:https://blog.csdn.net/wuzhiwei549/article/details/105965646
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场