最新要闻
- 毕业季,科学城幼儿园大班萌娃用画笔勾勒未来|当前热文
- 辽宁:全链条打击证券犯罪 去年以来追赃挽损7.14亿余元
- 上海电气印度业务被诈骗88亿 反被索赔21亿?官方辟谣-环球微头条
- 腾讯回应数百人部门解散:正常架构调整 不涉及人员优化 每日聚焦
- iPhone上市开售16周年:共38款机型 售价越来越贵_独家焦点
- 全国首位视障播音硕士毕业:键盘盲打完成6万字论文 9年读了百本盲书-天天热门
- 不惯着!女子人肉占车位撒泼打滚:结局舒适-世界要闻
- 我市举行天然气应急救援演练|世界快看点
- 7月见!华为大动作不断:将发布新款存储 面向AI大模型 世界观焦点
- 凯迪拉克格局打开!赞助节目中允许宣传理想L9 还邀请李想体验新车-天天即时
- 辣眼睛!男子鞋底塞112张《塞尔达传说:王国之泪》卡带入境被查|天天新消息
- 每日热点:董丽娜,毕业快乐!
- cad什么版本最好用_cad那个版本比较好用
- 对标苹果Studio Display!三星首款5K显示器开售:9999元还送1T SSD
- 网飞版《三体》预告播放破千万 粉丝喊话别拍砸了:国内拍技术落后?_环球新资讯
- ChatGPT假装奶奶哄睡:能免费生成Win系统激活码
手机
光庭信息跌4.57% 2021上市超募11亿2022扣非降74% 时快讯
搜狐汽车全球快讯 | 大众汽车最新专利曝光:仪表支持拆卸 可用手机、平板替代-环球关注
- 光庭信息跌4.57% 2021上市超募11亿2022扣非降74% 时快讯
- 搜狐汽车全球快讯 | 大众汽车最新专利曝光:仪表支持拆卸 可用手机、平板替代-环球关注
- 视点!美国首位女总统即将诞生?拜登恐怕要提前下岗,美政坛迎来变局?
- 当前速递!用理想仪器实现更好的颗粒 德国新帕泰克亮相CPHI & PMEC China获好评
- 微粒贷怎么申请开通 开通方法如下
- 焦点简讯:心疼!这位40岁的云南缉毒警,已是满头白发
家电
Spring Boot 项目设计业务操作日志功能,写得太好了!-世界即时看
前言
很久以前都想写这篇文章,一直没有空,但直到现在我对当时的情景还有印象,之所以有印象是因为需求很简单,业务操作日志的记录与查询的功能,但是具体实现真的很烂,具体的烂法会在反面示例里细说,领导以及客户层面很认可,一系列迷之操作,让我印象深刻。
需求描述与分析
客户侧提出需求很简单:要对几个关键的业务功能进行操作日志记录,即什么人在什么时间操作了哪个功能,操作前的数据报文是什么、操作后的数据报文是什么,必要的时候可以一键回退。
日志在业务系统中是必不可少的一个功能,常见的有系统日志、操作日志等:
【资料图】
系统日志
这里的系统日志是指的是程序执行过程中的关键步骤,根据实际场景输出的debug、info、warn、error等不同级别的程序执行记录信息,这些一般是给程序员或运维看的,一般在出现异常问题的时候,可以通过系统日志中记录的关键参数信息和异常提示,快速排除故障。
操作日志
操作日志,是用户实际业务操作行为的记录,这些信息一般存储在数据库里,如什么时间哪个用户点了某个菜单、修改了哪个配置等这类业务操作行为,这些日志信息是给普通用户或系统管理员看到。
通过对需求的分析,客户想要是一个业务操作日志管理的功能:
1、记录用户的业务操作行为,记录的字段有:操作人、操作时间、操作功能、日志类型、操作内容描述、操作内容报文、操作前内容报文
2、提供一个可视化的页面,可以查询用户的业务操作行为,对重要操作回溯;
3、提供一定的管理功能,必要的时候可以对用户的误操作回滚;
反面实现
明确需求后,就是怎么实现的问题了,这里先上一个反面的实现案例,也是因为这一个反面案例,才让我对这个简单的需求印象深刻。
这里我以一个人员管理的功能为例还原一下,当时的具体实现:
1、每个接口里都加一段记录业务操作日志的记录;
2、每个接口里都要捕获一下异常,记录异常业务操作日志;
下面是伪代码:
@RestController@Slf4j@BusLog(name = "人员管理")@RequestMapping("/person")public class PersonController2 { @Autowired private IPersonService personService; @Autowired private IBusLogService busLogService; //添加人员信息 @PostMapping public Person add(@RequestBody Person person) { try{ //添加信息信息 Person result = this.personService.registe(person); //保存业务日志 this.saveLog(person); log.info("//增加person执行完成"); }catch(Exception e){ //保存异常操作日志 this.saveExceptionLog(e); } return result; }}
这种通过硬编码实现的业务操作日志管理功能,最大的问题就是业务操作日志收集与业务逻辑耦合严重,和代码重复,新开发的接口在完成业务逻辑后要织入一段业务操作日志保存的逻辑,已开发上线的接口,还要重新再修改织入业务操作日志保存的逻辑并测试,且每个接口需要织入的业务操作日志保存的逻辑是一样的。
推荐一个开源免费的 Spring Boot 实战项目:
https://github.com/javastacks/spring-boot-best-practice
设计思路
如果对AOP有一些印象的话,最好的方法就是使用aop实现:
1、定义业务操作日志注解,注解内可以定义一些属性,如操作功能名称、功能的描述等;
2、把业务操作日志注解标记在需要进行业务操作记录的方法上(在实际业务中,一些简单的业务查询行为通常没有必要记录);
3、定义切入点,编写切面:切入点就是标记了业务操作日志注解的目标方法;切面的主要逻辑就是保存业务操作日志信息;
Spring AOP
AOP (Aspect Orient Programming),直译过来就是 面向切面编程,AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。面向切面编程,实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术,AOP可以拦截指定的方法并且对方法增强,而且无需侵入到业务代码中,使业务与非业务处理逻辑分离;
而SpringAOP,则是AOP的一种具体实现,Spring内部对SpringAOP的应用最经典的场景就是Spring的事务,通过事务注解的配置,Spring会自动在业务方法中开启、提交业务,并且在业务处理失败时,执行相应的回滚策略;与过滤器、拦截器相比,更加重要的是其适用范围不再局限于SpringMVC项目,可以在任意一层定义一个切点,织入相应的操作,并且还可以改变返回值;
Filter和HandlerInterceptor
之所以没有选择Filter和HandlerInterceptor,而是AOP来实现业务操作日志功能,是因为Filter和HandlerInterceptor自身的一些局限性:
过滤器
过滤器(Filter)是与servlet相关联的一个接口,主要适用于java web项目中,依赖于Servlet容器,是利用java的回调机制来实现过滤拦截来自浏览器端的http请求,可以拦截到访问URL对应的方法的请求和响应(ServletRequest request, ServletResponse response),但是不能对请求和响应信息中的值进行修改;一般用于设置字符编码、鉴权操作等;
如果想要做到更细一点的类和方法或者是在非servlet环境中使用,则是做不到的;所以凡是依赖Servlet容器的环境,过滤器都可以使用,如Struts2、SpringMVC;
拦截器
拦截器的(HandlerInterceptor)使用范围以及功能和过滤器很类似,但是也是有区别的。首先,拦截器(HandlerInterceptor)适用于SpringMVC中,因为HandlerInterceptor接口是SpringMVC相关的一个接口,而实现java Web项目,SpringMVC是目前的首选选项,但不是唯一选项,还有struts2等;因此,如果是非SpingMVC的项目,HandlerInterceptor无法使用的;
其次,和过滤器一样,拦截器可以拦截到访问URL对应的方法的请求和响应(ServletRequest request, ServletResponse response),但是不能对请求和响应信息中的值进行修改;一般用于设置字符编码、鉴权操作等;如果想要做到更细一点的类和方法或者是在非servlet环境中使用,则也是是做不到的;
总之,过滤器和拦截器的功能很类似,但是拦截器的适用范围比过滤器更小;
SpringAOP、过滤器、拦截器对比
在匹配中同一目标时,过滤器、拦截器、SpringAOP的执行优先级是:过滤器>拦截器>SpringAOP,执行顺序是先进后出,具体的不同则体现在以下几个方面:
1、作用域不同
- 过滤器依赖于servlet容器,只能在 servlet容器,web环境下使用,对请求-响应入口处进行过滤拦截;
- 拦截器依赖于springMVC,可以在SpringMVC项目中使用,而SpringMVC的核心是DispatcherServlet,而DispatcherServlet又属于Servlet的子类,因此作用域和过滤器类似;
- SpringAOP对作用域没有限制,只要定义好切点,可以在请求-响应的入口层(controller层)拦截处理,也可以在请求的业务处理层(service层)拦截处理;
2、颗粒度的不同
- 过滤器的控制颗粒度比较粗,只能在doFilter()中对请求和响应进行过虑和拦截处理;
- 拦截器提供更精细颗粒度的控制,有preHandle()、postHandle()、afterCompletion(),可以在controller对请求处理之前、请求处理后、请求响应完毕织入一些业务操作;
- SpringAOP,提供了前置通知、后置通知、返回后通知、异常通知、环绕通知,比拦截器更加精细化的颗粒度控制,甚至可以修改返回值;
实现方案
环境配置
- jdk版本:1.8开发工具:Intellij iDEA 2020.1
- springboot:2.3.9.RELEASE
- mybatis-spring-boot-starter:2.1.4
依赖配置
org.springframework.boot spring-boot-starter-aop
表结构设计
create table if not exists bus_log( id bigint auto_increment comment "自增id" primary key, bus_name varchar(100) null comment "业务名称", bus_descrip varchar(255) null comment "业务操作描述", oper_person varchar(100) null comment "操作人", oper_time datetime null comment "操作时间", ip_from varchar(50) null comment "操作来源ip", param_file varchar(255) null comment "操作参数报文文件")comment "业务操作日志" default charset ="utf8";
代码实现
1、定义业务日志注解@BusLog,可以作用在控制器或其他业务类上,用于描述当前类的功能;也可以用于方法上,用于描述当前方法的作用;
/** * 业务日志注解 * 可以作用在控制器或其他业务类上,用于描述当前类的功能; * 也可以用于方法上,用于描述当前方法的作用; */@Target({ElementType.METHOD,ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)public @interface BusLog { /** * 功能名称 * @return */ String name() default ""; /** * 功能描述 * @return */ String descrip() default ""; }
2、把业务操作日志注解BusLog标记在PersonController类和方法上;
@RestController@Slf4j@BusLog(name = "人员管理")@RequestMapping("/person")public class PersonController { @Autowired private IPersonService personService; private Integer maxCount=100; @PostMapping @NeedEncrypt @BusLog(descrip = "添加单条人员信息") public Person add(@RequestBody Person person) { Person result = this.personService.registe(person); log.info("//增加person执行完成"); return result; } @PostMapping("/batch") @BusLog(descrip = "批量添加人员信息") public String addBatch(@RequestBody List personList){ this.personService.addBatch(personList); return String.valueOf(System.currentTimeMillis()); } @GetMapping @NeedDecrypt @BusLog(descrip = "人员信息列表查询") public PageInfo list(Integer page, Integer limit, String searchValue) { PageInfo pageInfo = this.personService.getPersonList(page,limit,searchValue); log.info("//查询person列表执行完成"); return pageInfo; } @GetMapping("/{loginNo}") @NeedDecrypt @BusLog(descrip = "人员信息详情查询") public Person info(@PathVariable String loginNo,String phoneVal) { Person person= this.personService.get(loginNo); log.info("//查询person详情执行完成"); return person; } @PutMapping @NeedEncrypt @BusLog(descrip = "修改人员信息") public String edit(@RequestBody Person person) { this.personService.update(person); log.info("//查询person详情执行完成"); return String.valueOf(System.currentTimeMillis()); } @DeleteMapping @BusLog(descrip = "删除人员信息") public String edit(@PathVariable(name = "id") Integer id) { this.personService.delete(id); log.info("//查询person详情执行完成"); return String.valueOf(System.currentTimeMillis()); }}
3、编写切面类BusLogAop,并使用@BusLog定义切入点,在环绕通知内执行过目标方法后,获取目标类、目标方法上的业务日志注解上的功能名称和功能描述, 把方法的参数报文写入到文件中,最后保存业务操作日志信息;
@Component@Aspect@Slf4jpublic class BusLogAop implements Ordered { @Autowired private BusLogDao busLogDao; /** * 定义BusLogAop的切入点为标记@BusLog注解的方法 */ @Pointcut(value = "@annotation(com.fanfu.anno.BusLog)") public void pointcut() { } /** * 业务操作环绕通知 * * @param proceedingJoinPoint * @retur */ @Around("pointcut()") public Object around(ProceedingJoinPoint proceedingJoinPoint) { log.info("----BusAop 环绕通知 start"); //执行目标方法 Object result = null; try { result = proceedingJoinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } //目标方法执行完成后,获取目标类、目标方法上的业务日志注解上的功能名称和功能描述 Object target = proceedingJoinPoint.getTarget(); Object[] args = proceedingJoinPoint.getArgs(); MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature(); BusLog anno1 = target.getClass().getAnnotation(BusLog.class); BusLog anno2 = signature.getMethod().getAnnotation(BusLog.class); BusLogBean busLogBean = new BusLogBean(); String logName = anno1.name(); String logDescrip = anno2.descrip(); busLogBean.setBusName(logName); busLogBean.setBusDescrip(logDescrip); busLogBean.setOperPerson("fanfu"); busLogBean.setOperTime(new Date()); JsonMapper jsonMapper = new JsonMapper(); String json = null; try { json = jsonMapper.writeValueAsString(args); } catch (JsonProcessingException e) { e.printStackTrace(); } //把参数报文写入到文件中 OutputStream outputStream = null; try { String paramFilePath = System.getProperty("user.dir") + File.separator + DateUtil.format(new Date(), DatePattern.PURE_DATETIME_MS_PATTERN) + ".log"; outputStream = new FileOutputStream(paramFilePath); outputStream.write(json.getBytes(StandardCharsets.UTF_8)); busLogBean.setParamFile(paramFilePath); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (outputStream != null) { try { outputStream.flush(); outputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } //保存业务操作日志信息 this.busLogDao.insert(busLogBean); log.info("----BusAop 环绕通知 end"); return result; } @Override public int getOrder() { return 1; }}
测试
调试方法
平时后端调试接口,一般都是使用postman,这里给大家安利一款工具,即Intellij IDEA的Test RESTful web service,功能和使用和postman差不多,唯一的好处就是不用在电脑上再额外装个postman,功能入口:工具栏的Tools-->http client-->Test RESTful web
另外还有一种用法,我比较喜欢用这种,简单几句就可以发起一个http请求,还可以一次批量执行;
验证结果
总结
业务操作日志记录中包含了用户操作的功能名称、功能描述、操作人、操作时间和操作的参数报文,参数报文之所以选择存储在文件中,是因为正常情况下,是不需要知道具体的参数报文,只有在回滚操作的时候才会用到,可以根据上一次的参数报文逆向操作。
版权声明:本文为CSDN博主「凡夫贩夫」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/fox9916/article/details/130175379
近期热文推荐:
1.1,000+ 道 Java面试题及答案整理(2022最新版)
2.劲爆!Java 协程要来了。。。
3.Spring Boot 2.x 教程,太全了!
4.别再写满屏的爆爆爆炸类了,试试装饰器模式,这才是优雅的方式!!
5.《Java开发手册(嵩山版)》最新发布,速速下载!
觉得不错,别忘了随手点赞+转发哦!
关键词:
Spring Boot 项目设计业务操作日志功能,写得太好了!-世界即时看
毕业季,科学城幼儿园大班萌娃用画笔勾勒未来|当前热文
辽宁:全链条打击证券犯罪 去年以来追赃挽损7.14亿余元
上海电气印度业务被诈骗88亿 反被索赔21亿?官方辟谣-环球微头条
腾讯回应数百人部门解散:正常架构调整 不涉及人员优化 每日聚焦
iPhone上市开售16周年:共38款机型 售价越来越贵_独家焦点
全国首位视障播音硕士毕业:键盘盲打完成6万字论文 9年读了百本盲书-天天热门
不惯着!女子人肉占车位撒泼打滚:结局舒适-世界要闻
一次日志配置未生效问题排查记录
linux把文件压缩成.tar.gz的命令&各个压缩解压命令大全--九五小庞
MFC中使用多线程
希望所有计算机专业学生都知道这门课-世界速看
我市举行天然气应急救援演练|世界快看点
7月见!华为大动作不断:将发布新款存储 面向AI大模型 世界观焦点
凯迪拉克格局打开!赞助节目中允许宣传理想L9 还邀请李想体验新车-天天即时
辣眼睛!男子鞋底塞112张《塞尔达传说:王国之泪》卡带入境被查|天天新消息
每日热点:董丽娜,毕业快乐!
环球快播:Linux-vs-MacOS-,你更喜欢哪个系统?
世界热点评!【读财报】一季度信用卡消费者投诉透视:监管处理信用卡业务投诉超3.2万件 工商银行被投诉总量高
cad什么版本最好用_cad那个版本比较好用
对标苹果Studio Display!三星首款5K显示器开售:9999元还送1T SSD
网飞版《三体》预告播放破千万 粉丝喊话别拍砸了:国内拍技术落后?_环球新资讯
ChatGPT假装奶奶哄睡:能免费生成Win系统激活码
热讯:“泡面神器”彻底凉凉!亚马逊Kindle中国电子书店明日停运
SSD等存储要拉开涨价序幕了:有厂商财报明确!
读发布!设计与部署稳定的分布式系统(第2版)笔记15_快速失败和替换 焦点日报
“泰坦”号潜水器残骸中发现“疑似人类遗骸”-天天报道
印度狂买俄罗斯石油:买的多到转手倒卖给欧美
全球热推荐:中超卧龙凤雏诞生!任航被高畠勉培养成武士了!谢晖一语杀人诛心
pta题目集6-8_当前动态
为什么不应该给用户提示错误码-每日观察
21、MyBatis动态<if>标签的使用详解 当前快报
Blazor如何跟随“系统主题”? 环球快资讯
再次认识using 天天报资讯
黄仁勋:NVIDIA的AI产品 已经给客户打1折了_今日看点
RTX 4070换装暴力涡轮风扇 卖到6999元!瞬间降温21℃
秘鲁山谷惊现1500米长的神秘孔带:至今不知道是做什么的-全球观天下
4年亏损近30亿:美特斯邦威以3亿元出售房产自救
短讯!48GB DDR5-8200!芝奇超高频、大容量内存新增白色
热点聚焦:快餐帝国(01843.HK)年度纯利184.3万坡元 同比减少34.83%
影响43万亿美元交易!追踪美联储决策的一大利器即将消亡
阿邦拉霍:红军不能将萨拉赫卖给沙特球队,没有他球队进不了前六|全球聚看点
北方地区将继续受到40℃高温炙烤 温度远远赶超“四大火炉”
影片《消失的她》票房突破11亿元 累计2635万人观影
影帝梁朝伟迎来61岁生日 妻子刘嘉玲晒照为其庆生
《原神》枫丹地区大量内容遭泄露 米哈游将依法追究相关主体法律责任
湖南株洲一小学生暑假作业贴满免作业券 引发网友围观
环球快看:在Linux下如何使用JNI
焦点热门:python: BytesIO 中 read 用法
数码宝贝重启_数码宝贝dw6 0
全球通讯!苹果极力宣传的Apple Watch车祸检测功能频繁闹乌龙!让人头大
国外一飞机尴尬着陆:画面犹如“狗啃泥”
下月开始:Win11要强制更新了
古筝来历小故事_古筝来历
部署zabbix5.0以及使用 全球微头条
天天微速讯:Python教程(1)——python环境的下载与安装
每日焦点!郑少秋女儿辛酸成长史:8个月父亲出轨8岁患上三高,半生缺爱
【天天报资讯】有人坐吗?高级动卧票价一站8分钟420元 跟二等座差价70倍:12306回应
焦点消息!4520元贵妇级护肤品复刻版卖47元:相似度能达到99.99%
nethttp和gin 路由
文心一言 VS 讯飞星火 VS chatgpt (50)-- 算法导论6.2 2题-简讯
Golang 简单的数据对齐可提高程序速度和内存使用率_环球新消息
【前沿】尺寸接近米13!大厂新机渲染图出炉 白色打几分?|全球关注
最便宜4系来了!宝马发布i4 eDriver35四门纯电轿跑:大降4万
今日视点:胃癌几乎都是吃出来的:钟爱烧烤、熏制食物的朋友要注意
女孩用“免作业券”不写暑假作业 网友点赞:凭实力不写-要闻速递
每日聚焦:三星Galaxy S23 FE外观曝光:后置长焦三摄、边角圆润
差评第一!景区摆渡车不能兜来绕去只为钱
【Java】使用 fasterxml.jackson 进行反序列化以及部分注意事项
微动态丨可观测性是什么? 入门指南
WPF MVVM之点滴分享
Donaukit用户创建
【财经分析】东北地区国资国企改革深化推进 或迎来哪些针对性政策?
我莫得感情也莫得耳朵_我莫得感情哪里的梗
续航38小时/能水洗!万魔S50运动蓝牙耳机图赏
爸爸给女儿买RTX 4060 Ti显卡当生日礼物 小女孩表情亮了
来试试手气?《逆水寒》手游6月30日上线:官方宣布抽奖送鹤岗一套房-环球速读
每日消息!比电脑还大:24GB+1TB!今年最能打的直屏旗舰杀疯了
迅雷云盘TV版最高可领180天超级会员特权 支持4K电视超清播放-天天信息
天天微资讯!广州杨大爷退休后,喝酒不喝茅五剑,青睐三款廉价酒
强化学习从基础到进阶-常见问题和面试必知必答[8]:近端策略优化(proximal policy optimization,PPO)算法
债市日报:6月28日|全球新视野
债市日报:6月28日
农村三胞胎姐妹均高分超一本线:成绩一直很优秀
极氪001欧洲价格公布:起售价47万元 比国内贵17万
高管曾言等本土车企“烧死了”再来抢市场!起亚EV6开启盲订 每日速递
今天起 韩国人集体“年轻一两岁”:与一项法案有关
XREAL Beam投屏盒子首次亮相MWC!AR空间屏体验太酷了
一般小县城稀缺行业有哪些 今日热搜
跑得更快!华为云GaussDB以出色的性能守护“ERP的心脏”
记录--不定高度展开收起动画 css/js 实现
今日播报!Cognos教程_编程入门自学教程_菜鸟教程-免费教程分享
LRU 缓存淘汰算法
Mac反编译安卓APK
世界看点:原来是他们!退市股获举牌4个交易日翻倍,步步高系大佬要进董事会
【金融街发布】财政部:5月地方债发行规模为7554亿元 新增债券占比近四成_即时看
天天即时看!注意!天孚通信:股东朱国栋计划减持公司股份不超过约395万股
游戏主播被大额打赏后剃光头!结果被坑惨:打赏退款了
尽享丝滑!MWC现场体验领克08魅族Flyme Auto车机:流畅度爆表 环球新要闻
焦点资讯:萧敬腾求婚好多地方下雨 “雨神”真有这么神?