前言

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

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

常见的操作日志类型

  • 用户登录日志

  • 重要数据查询日志 (但电商可能不重要的数据也做埋点,比如在淘宝上你搜索什么商品,即使不买,一段时间内首页也会给你推荐类似的东西)

  • 重要数据变更日志 (如密码变更,权限变更,数据修改等)

  • 数据删除日志

  • ......

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

实现方案对比

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

  • 优点:实现思路简单;

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

基于数据库Binlog

  • 优点:解除了数据新旧变化的耦合,支持批量操作,方便多表关联拓展,不依赖开发语言;

  • 缺点:数据库表设计需要统一的约定;

方案实现细节

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

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

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

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

    @Valid@NotNull(message = "新值不能为空")@UpdateNewDataOperationLogprivate T newData;@Valid@NotNull(message = "旧值不能为空")@UpdateOldDataOperationLogprivate T oldData;

存在的问题:

  • 1.旧值如果不多查询一次数据库则需要依赖前端把旧值封装到oldData对象中,很有可能已经不是修改前的值;

  • 2.无法处理批量的List数据;

  • 3.不支持多表操作;

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

    @PostMapping("/delete")@ApiOperation(value = "删除用户信息", notes = "删除用户信息")@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:日志处理应用

    • 利用canal采集和解析业务库的binlog日志并投递到kafka中,解析后的记录中记录了当前操作的操作类型,如属于删除、修改、新增,和新旧值的记录,格式如下:

{"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"}],
"database":"yl_spmibill_8",
"es":1587879945200,
"id":17161259,
"isDdl":false,
"mysqlType":{"id":"bigint(20)",
"bill_type":"tinyint(2)",
"create_time":"timestamp",
"update_time":"timestamp",
"version":"int(11)",
"trace_id":"varchar(50)"},
"old":[{"update_time":"2020-04-2613:45:45",
"version":"1",
"trace_id":"exclude-36aef98585db4e7a98f9694c8ef28b8c"}],
"pkNames":["id"],"sql":"",
"sqlType":{"id":-5,"bill_type":-6,"create_time":93,"update_time":93,"version":4,"trace_id":12},
"table":"xxx_transfer_bill_117",
"ts":1587879945698,"type":"UPDATE"}

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

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

库表设计

  • 1:所有业务系统表需要添加trace_id字段,每次操作生成一个随机字符串并保存到业务表中;

  • 2:日志收集应用库表设计

CREATE TABLE `table_config` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',`database_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '数据库名',`table_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT ' 数据库表名',PRIMARY KEY (`id`),UNIQUE KEY `unq_data_name_table_name` (`database_name`,`table_name`) USING BTREE COMMENT '数据库名表名联合索引'
) ENGINE=InnoDB AUTO_INCREMENT=35 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='数据库配置表';CREATE TABLE `table_field_config` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`table_config_id` bigint(20) DEFAULT NULL,`field` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '字段 数据库',`field_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '字段 中文名称',`enum_flag` tinyint(2) DEFAULT NULL COMMENT '是否枚举字段(1:是,0:否)',`relevance_flag` tinyint(2) DEFAULT NULL COMMENT '是否是关联字段(1:是,0否)',`sort` int(11) DEFAULT NULL COMMENT '排序',PRIMARY KEY (`id`),KEY `idx_table_config_id` (`table_config_id`) USING BTREE COMMENT '表ID索引'
) ENGINE=InnoDB AUTO_INCREMENT=2431 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='数据库字段配置表';CREATE TABLE `table_field_value` (`id` bigint(20) NOT NULL,`field_config_id` bigint(20) DEFAULT NULL,`field_key` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT ' 枚举',`filed_value` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '枚举名称',PRIMARY KEY (`id`),KEY `ids_field_config_id` (`field_config_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='数据字典配置表';

效果

基于binlog实现方案未来规划

  1. 优化发送业务消息的实现,使用切面拦截减少对业务代码的侵入;

  2. 目前暂时不支持对多表关联操作日志记录,需要拓展;

总结

本文以操作日志为题材讨论了操作日志的实现方案和可行性,并且都已经在功能上进行实现,其中使用aop方案也是大部分中小企业的首选实现方案,但是在一些金融领域以及erp相关系统,对操作日志记录明细要求极高,常见技术方案很难满足,即使能够满足也会带来一些代码强侵入以及性能问题。

所以我们又讨论了基于binlog实现的方案,该方案虽然比对aop来说增强了技术的复杂性,但是对于有一定技术积累的团队来说不算什么难事,并且该方案我们都实现了上线,并且解决了代码层面上的侵入,属于跨语言级别的,相信对读者还是有一定的启发。

我们已经不用 AOP 做日志很久了!相关推荐

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

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 前言 用户在操作我们系统的过程中,针对一些重要的业务数据进行增删改 ...

  2. 我们已经不用AOP做操作日志了! | 原力计划

    来源 | JAVA葵花宝典 责编 | 王晓曼.Carol  头图 | CSDN下载自东方IC 前言 用户在操作我们系统的过程中,针对一些重要的业务数据进行增删改查的时候,我们希望记录一下用户的操作行为 ...

  3. 做开发很久了 Remoting 一直没有碰过,正好最近的项目上面用,就拿出来给大家看看

    首先  Remoting 严格来说分为3个部分, 服务端 ,客户端,中间件,中间件是指把要公布的方法和接口写成dll的形式,给客户端和服务端调用. 不说其他的了 ,直接上代码 RemotingMode ...

  4. “上财后勤服务网”RTM版出炉啦!“抢滩登录”差不多结束了,不用Dday做日志编号了,缓一缓,缓一缓。。。

    RTM版,呵呵,正式发行前的试用版,整个色调改了一些,flash banner加上去了,js的导航也重新做了,自认很漂亮 的样子. 该有的元素也添加了,现在应该大改动不会有了吧,可能还要添加若干几个小 ...

  5. HDU 4379 水题,大水,但我WA了很多次,做了很久

    http://blog.csdn.net/diannaok/article/details/7875086?reload 我是看着他的写的,但是有个小错误,在初始化值时,令>L/2中最大的数的初 ...

  6. 运用aop做日志,实现请求方法的入参、返回结果日志统一打印,避免日志打印格式杂乱,同时减少重复代码

    文章目录 一.自定义注解 二.切面类 三.应用 一.自定义注解 自定义切面注解@PrintlnLog 用来输出日志,注解权限 @Target({ElementType.METHOD}) 限制只在方法上 ...

  7. 推送(Push)做了这么久还是误入“歧途”——MobPush

    在移动操作系统中,App可以实现消息推送(push)的功能.push是能够起到提醒或者唤醒用户的作用的,也是app运营渠道之一,运用得当可以帮助产品运营人员更高效地实现运营目标,相反盲目运用也会让AP ...

  8. python common很久不用我都忘了一些函数了,给boss添麻烦了

    python common很久不用我都忘了一些函数了,给boss添麻烦了 #! /usr/bin/env python #coding=utf-8 list=[1,2] dict={} dict[&q ...

  9. 我也不知道怎么回事_刚上场说做php的_女嘉宾把灯全灭了,这位程序员哥哥 其实我觊觎你很久了...

    原标题:这位程序员哥哥 其实我觊觎你很久了 激动人心的周五又来啦 羊(zui)驼(mei)小编闪亮出场 请自觉在评论区 燥起来 分别两周 亲爱的猿猿 有米有想我丫 想我的请敲1 不想的请敲2 哼哼 ( ...

最新文章

  1. Swift - 访问通讯录联系人(使用系统提供的通讯录交互界面)
  2. iOS分享短信导航条上白色,无取消按钮,无法返回应用
  3. [收集] 经典C/C++面试题(三)
  4. elementui表格添加滚动条_如何给PDF文档添加超链接?
  5. angularjs 开发流程_超级棒的30款web前端开发工具汇总,一定要收藏!
  6. 作者:袁明轩(1980-),男,华为诺亚方舟实验室研究员。
  7. linux自定义全局命令
  8. Xshell6和Xftp6 破解免安装版(亲测可用)
  9. AliSQL编译安装
  10. Axure PR9的安装与激活
  11. 三角形~~行列式~~皮克公式~~gcd
  12. Failed to compile../public/UEditor/dialogs/template/template.html 1:0Module parse failed: Unexpec
  13. Django rest framework --- Routers
  14. 转屏动画 - 安卓R
  15. 2022 个人基本计划
  16. mysql-下划线转驼峰-转载
  17. linux踢出硬盘,Linux MD/RAID 踢盘机制
  18. H3C交换机与cisco交换机对接配置案例和注意事项
  19. JAVA计算机毕业设计体育城场地预定系统后台源码+系统+mysql数据库+lw文档
  20. 《高等代数》重要概念复习

热门文章

  1. 基元检测 Primitive Detection
  2. Day 26: TogetherJS —— 让我们一起来编程!
  3. JS 得到时间的各个部分
  4. python 安装scrapy,openssl opensslv.h错误的解决办法
  5. ASP.NET完整打包卸载更新攻略(By Installshield 2010)【转】
  6. Fedora 19配置心得
  7. IIS7下 301重定向 添加WWW
  8. 乔布斯成功的七条秘诀
  9. 利用apache-commons-fileupload写jsp上传文件
  10. 【TensorFlow】PyCharm中无法识别tensorflow、numpy等Python库问题的解决办法