在上一章内容中——使用logback管理日志,我们详细讲述了如何将日志生成文件进行存储。但是在实际开发中,使用文件存储日志用来快速查询问题并不是最方便的,一个优秀系统除了日志文件还需要将操作日志进行持久化,来监控平台的操作记录。今天我们一起来学习一下如何通过apo来记录日志。

为了让记录日志更加灵活,我们将使用自定义的注解来实现重要操作的日志记录功能。

一 日志记录表

日志记录表主要包含几个字段,业务模块,操作类型,接口地址,处理状态,错误信息以及操作时间。数据库设计如下:

  1. CREATE TABLE `sys_oper_log` (
  2. `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '日志主键',
  3. `title` varchar(50) CHARACTER SET utf8 DEFAULT '' COMMENT '模块标题',
  4. `business_type` int(2) DEFAULT '0' COMMENT '业务类型(0其它 1新增 2修改 3删除)',
  5. `method` varchar(255) CHARACTER SET utf8 DEFAULT '' COMMENT '方法名称',
  6. `status` int(1) DEFAULT '0' COMMENT '操作状态(0正常 1异常)',
  7. `error_msg` varchar(2000) CHARACTER SET utf8 DEFAULT '' COMMENT '错误消息',
  8. `oper_time` datetime DEFAULT NULL COMMENT '操作时间',
  9. PRIMARY KEY (`id`)
  10. ) ENGINE=InnoDB CHARSET=utf8mb4 CHECKSUM=1 COMMENT='操作日志记录'

对应的实体类如下:

  1. @Data
  2. @NoArgsConstructor
  3. @AllArgsConstructor
  4. public class SysOperLog implements Serializable {
  5. private static final long serialVersionUID = 1L;
  6. /** 日志主键 */
  7. private Long id;
  8. /** 操作模块 */
  9. private String title;
  10. /** 业务类型(0其它 1新增 2修改 3删除) */
  11. private Integer businessType;
  12. /** 请求方法 */
  13. private String method;
  14. /** 错误消息 */
  15. private String errorMsg;
  16. private Integer status;
  17. /** 操作时间 */
  18. private Date operTime;
  19. }

二 自定义注解及处理

自定义注解包含两个属性,一个是业务模块title,另一个是操作类型businessType

  1. @Target({ ElementType.PARAMETER, ElementType.METHOD })
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. public @interface Log {
  5. /**
  6. * 模块
  7. */
  8. String title() default "";
  9. /**
  10. * 功能
  11. */
  12. BusinessType businessType() default BusinessType.OTHER;
  13. }

使用aop对自定义的注解进行处理

  1. @Aspect
  2. @Component
  3. @Slf4j
  4. public class LogAspect {
  5. @Autowired
  6. private AsyncLogService asyncLogService;
  7. // 配置织入点
  8. @Pointcut("@annotation(com.javatrip.aop.annotation.Log)")
  9. public void logPointCut() {}
  10. /**
  11. * 处理完请求后执行
  12. *
  13. * @param joinPoint 切点
  14. */
  15. @AfterReturning(pointcut = "logPointCut()", returning = "jsonResult")
  16. public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) {
  17. handleLog(joinPoint, null, jsonResult);
  18. }
  19. /**
  20. * 拦截异常操作
  21. *
  22. * @param joinPoint 切点
  23. * @param e 异常
  24. */
  25. @AfterThrowing(value = "logPointCut()", throwing = "e")
  26. public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
  27. handleLog(joinPoint, e, null);
  28. }
  29. protected void handleLog(final JoinPoint joinPoint, final Exception e, Object jsonResult) {
  30. try {
  31. // 获得注解
  32. Log controllerLog = getAnnotationLog(joinPoint);
  33. if (controllerLog == null) {
  34. return;
  35. }
  36. SysOperLog operLog = new SysOperLog();
  37. operLog.setStatus(0);
  38. if (e != null) {
  39. operLog.setStatus(1);
  40. operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
  41. }
  42. // 设置方法名称
  43. String className = joinPoint.getTarget().getClass().getName();
  44. String methodName = joinPoint.getSignature().getName();
  45. operLog.setMethod(className + "." + methodName + "()");
  46. // 处理设置注解上的参数
  47. getControllerMethodDescription(joinPoint, controllerLog, operLog);
  48. // 保存数据库
  49. asyncLogService.saveSysLog(operLog);
  50. } catch (Exception exp) {
  51. log.error("==前置通知异常==");
  52. log.error("日志异常信息 {}", exp);
  53. }
  54. }
  55. /**
  56. * 获取注解中对方法的描述信息 用于Controller层注解
  57. *
  58. * @param log 日志
  59. * @param operLog 操作日志
  60. * @throws Exception
  61. */
  62. public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog) {
  63. // 设置action动作
  64. operLog.setBusinessType(log.businessType().ordinal());
  65. // 设置标题
  66. operLog.setTitle(log.title());
  67. }
  68. /**
  69. * 是否存在注解,如果存在就获取
  70. */
  71. private Log getAnnotationLog(JoinPoint joinPoint) {
  72. Signature signature = joinPoint.getSignature();
  73. MethodSignature methodSignature = (MethodSignature) signature;
  74. Method method = methodSignature.getMethod();
  75. if (method != null) {
  76. return method.getAnnotation(Log.class);
  77. }
  78. return null;
  79. }
  80. }

操作类型的枚举类:

  1. public enum BusinessType {
  2. /**
  3. * 其它
  4. */
  5. OTHER,
  6. /**
  7. * 新增
  8. */
  9. INSERT,
  10. /**
  11. * 修改
  12. */
  13. UPDATE,
  14. /**
  15. * 删除
  16. */
  17. DELETE,
  18. }

使用异步方法将操作日志存库,为了方便我直接使用jdbcTemplate在service中进行存库操作。

  1. @Service
  2. public class AsyncLogService {
  3. @Autowired
  4. private JdbcTemplate jdbcTemplate;
  5. /**
  6. * 保存系统日志记录
  7. */
  8. @Async
  9. public void saveSysLog(SysOperLog log) {
  10. String sql = "INSERT INTO sys_oper_log(title,business_type,method,STATUS,error_msg,oper_time) VALUES(?,?,?,?,?,?)";
  11. jdbcTemplate.update(sql,new Object[]{log.getTitle(),log.getBusinessType(),log.getMethod(),log.getStatus(),log.getErrorMsg(),new Date()});
  12. }
  13. }

三 编写接口测试

将自定义注解写在业务方法上,测试效果

  1. @RestController
  2. @RequestMapping("person")
  3. public class PersonController {
  4. @GetMapping("/{name}")
  5. @Log(title = "system",businessType = BusinessType.OTHER)
  6. public Person getPerson(@PathVariable("name") String name, @RequestParam int age){
  7. return new Person(name,age);
  8. }
  9. @PostMapping("add")
  10. @Log(title = "system",businessType = BusinessType.INSERT)
  11. public int addPerson(@RequestBody Person person){
  12. if(StringUtils.isEmpty(person)){
  13. return -1;
  14. }
  15. return 1;
  16. }
  17. @PutMapping("update")
  18. @Log(title = "system",businessType = BusinessType.UPDATE)
  19. public int updatePerson(@RequestBody Person person){
  20. if(StringUtils.isEmpty(person)){
  21. return -1;
  22. }
  23. return 1;
  24. }
  25. @DeleteMapping("/{name}")
  26. @Log(title = "system",businessType = BusinessType.DELETE)
  27. public int deletePerson(@PathVariable(name = "name") String name){
  28. if(StringUtils.isEmpty(name)){
  29. return -1;
  30. }
  31. return 1;
  32. }
  33. }

当然,还可以在数据库中将请求参数和响应结果也进行存储,这样就能看出具体接口的操作记录了。

此是spring-boot-route系列的第十七篇文章,这个系列的文章都比较简单,主要目的就是为了帮助初次接触Spring Boot 的同学有一个系统的认识。本文已收录至我的github,欢迎各位小伙伴star

githubhttps://github.com/binzh303/spring-boot-route

点关注、不迷路

如果觉得文章不错,欢迎关注点赞收藏,你们的支持是我创作的动力,感谢大家。

如果文章写的有问题,请不要吝啬,欢迎留言指出,我会及时核查修改。

如果你还想更加深入的了解我,可以微信搜索「Java旅途」进行关注。回复「1024」即可获得学习视频及精美电子书。每天7:30准时推送技术文章,让你的上班路不在孤独,而且每月还有送书活动,助你提升硬实力!

spring-boot-route(十七)使用aop记录操作日志的更多相关文章

  1. 使用SpringBoot AOP 记录操作日志、异常日志

    平时我们在做项目时经常需要对一些重要功能操作记录日志,方便以后跟踪是谁在操作此功能:我们在操作某些功能时也有可能会发生异常,但是每次发生异常要定位原因我们都要到服务器去查询日志才能找到,而且也不能对发 ...

  2. Spring aop 记录操作日志 Aspect

    前几天做系统日志记录的功能,一个操作调一次记录方法,每次还得去收集参数等等,太尼玛烦了.在程序员的世界里,当你的一个功能重复出现多次,就应该想想肯定有更简单的实现方法.于是果断搜索各种资料,终于搞定了 ...

  3. Spring aop 记录操作日志 Aspect 自定义注解

    时间过的真快,转眼就一年了,没想到随手写的笔记会被这么多人浏览,不想误人子弟,于是整理了一个优化版,在这里感谢智斌哥提供的建议和帮助,话不多说,进入正题 所需jar包 :spring4.3相关联以及a ...

  4. springmvc集成aop记录操作日志

    首先说明一下,这篇文章只做了记录日志相关事宜 具体springmvc如何集成配置aop对cotroller进行拦截,请看作者的另一篇文章 http://www.cnblogs.com/guokai87 ...

  5. 用AOP记录操作日志,并写进数据库。

    先用AOP注解 1 package com.vlandc.oss.apigate.log.aspect; import java.util.Map; import java.util.Optional ...

  6. Spring Boot中使用AOP记录请求日志

    这周看别人写的springboot后端代码中有使用AOP记录请求日志,以前没接触过,因此学习下. 一.AOP简介 AOP为Aspect Oriented Programming的缩写,意为:面向切面编 ...

  7. [编码实践]SpringBoot实战:利用Spring AOP实现操作日志审计管理

    设计原则和思路: 元注解方式结合AOP,灵活记录操作日志 能够记录详细错误日志为运营以及审计提供支持 日志记录尽可能减少性能影响 操作描述参数支持动态获取,其他参数自动记录. 1.定义日志记录元注解, ...

  8. Spring Boot 自定义注解,AOP 切面统一打印出入参请求日志

    其实,小哈在之前就出过一篇关于如何使用 AOP 切面统一打印请求日志的文章,那为什么还要再出一篇呢?没东西写了? 哈哈,当然不是!原因是当时的实现方案还是存在缺陷的,原因如下: 不够灵活,由于是以所有 ...

  9. spring boot / cloud (十七) 快速搭建注册中心和配置中心

    spring boot / cloud (十七) 快速搭建注册中心和配置中心 本文将使用spring cloud的eureka和config server来搭建. 然后搭建的模式,有很多种,本文主要聊 ...

随机推荐

  1. Kubernetes笔记(五):了解Pod(容器组)

    Kubernetes 中, 容器总是以 Pod(容器组)的方式进行调度与运行.因此对 Pod 的理解与掌握是学习 Kubernetes 的基础. 理解 Pod Pod(容器组)是 Kubernetes ...

  2. 第一次编程作业(My Own Score)

    博客班级 https://edu.cnblogs.com/campus/fzzcxy/2018SE2 作业要求 https://edu.cnblogs.com/campus/fzzcxy/2018SE ...

  3. JS语法_其他

    严格模式 let obj = { name: 'oceans', } function f1() { with (obj) { console.log(name) } } function f2() ...

  4. CentOS6.10下安装MongoDB和Redis

    安装mongodb 首先考虑离线安装,但是安装过程中在启动服务的时候出现了问题,centOS出于稳定原因考虑,系统自带的glibc版本过低, 而编译需要使用较高版本,这个问题我查询了一下,需要升级gl ...

  5. 我的Python自学之路-001 列表的知识

    #_date_:2020/9/11 '''列表和字典是python中用的最多的数据类型 假如要存储一个班级的人名,需要怎么做?有这么几种方法:1.定义很多个变量: name0 = 'wucaho' n ...

  6. Autofac的使用

    Autofac的使用 「Autofac简介」 「基本的使用方式」 「如何实现webapi控制器自动注入接口」 一.Autofac简介 ❝ Autofac是实现依赖注入的类库之一,他可以帮助你实现程序的 ...

  7. java ThreadLocal理解和使用

    一.ThreadLoal的理解 ThreadLoal 变量,它的基本原理是,同一个 ThreadLocal 所包含的对象(对ThreadLocal< String >而言即为 String ...

  8. 从 LRU Cache 带你看面试的本质

    前言 大家好,这里是<齐姐聊算法>系列之 LRU 问题. 在讲这道题之前,我想先聊聊「技术面试究竟是在考什么」这个问题. 技术面试究竟在考什么 在人人都知道刷题的今天,面试官也都知道大家会 ...

  9. ps -ef | grep使用详解

    转载于: https://www.cnblogs.com/freinds/p/8074651.html   ps命令将某个进程显示出来 grep命令是查找 中间的|是管道命令 是指ps命令与grep同 ...

  10. Java基础——克隆

    1.克隆 假设有一个对象object1,在某处又需要一个跟object1一样的实例object2,这两个对象是绝对独立的,不会因为某一个修改另一个随之改变,这样,我们不能直接将对象objec1t的引用 ...