经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » 编程经验 » 查看文章
《优化接口设计的思路》系列:第三篇—留下用户调用接口的痕迹
来源:cnblogs  作者:sum墨  时间:2023/9/19 8:38:46  对本文有异议

前言

大家好!我是sum墨,一个一线的底层码农,平时喜欢研究和思考一些技术相关的问题并整理成文,限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。

作为一名从业已达六年的老码农,我的工作主要是开发后端Java业务系统,包括各种管理后台和小程序等。在这些项目中,我设计过单/多租户体系系统,对接过许多开放平台,也搞过消息中心这类较为复杂的应用,但幸运的是,我至今还没有遇到过线上系统由于代码崩溃导致资损的情况。这其中的原因有三点:一是业务系统本身并不复杂;二是我一直遵循某大厂代码规约,在开发过程中尽可能按规约编写代码;三是经过多年的开发经验积累,我成为了一名熟练工,掌握了一些实用的技巧。

接口设计是整个系统设计中非常重要的一环,其中包括限流、权限、入参出参、切面等方面。设计一个好的接口可以帮助我们省去很多不必要的麻烦,从而提升整个系统的稳定性和可扩展性。作为接口设计经验分享的第三篇,我想分享一下如何在用户使用过程中留下操作痕迹。在实际开发中,我会采取一些手段来记录用户操作,例如使用日志记录用户行为,或者在数据库中保存用户操作记录。这些痕迹可以帮助我们快速定位和解决问题,同时也可以为后续数据分析和优化提供有价值的参考。

方法一、将接口的参数和结果打印在日志文件中

日志文件是我们记录用户使用痕迹的第一个地方,我之前写过一篇SpringBoot项目如何配置logback.xml的文章来实现系统日志输出,有兴趣的同学可以去看看。
这里我主要讲一下怎么方便将所有接口的出入参打印出来。

1、使用aop监控接口

依赖如下

  1. <!-- aspectj -->
  2. <dependency>
  3. <groupId>org.aspectj</groupId>
  4. <artifactId>aspectjweaver</artifactId>
  5. <version>1.9.5</version>
  6. </dependency>

如果有同学不知道aspectj是啥的,可以看我这篇文章SpringBoot整合aspectj实现面向切面编程(即AOP)

关键代码如下

  1. package com.summo.aspect;
  2. import java.util.Objects;
  3. import javax.servlet.http.HttpServletRequest;
  4. import com.alibaba.druid.util.StringUtils;
  5. import lombok.extern.slf4j.Slf4j;
  6. import org.aspectj.lang.ProceedingJoinPoint;
  7. import org.aspectj.lang.Signature;
  8. import org.aspectj.lang.annotation.Around;
  9. import org.aspectj.lang.annotation.Aspect;
  10. import org.aspectj.lang.annotation.Pointcut;
  11. import org.slf4j.MDC;
  12. import org.springframework.stereotype.Component;
  13. import org.springframework.web.context.request.RequestAttributes;
  14. import org.springframework.web.context.request.RequestContextHolder;
  15. import org.springframework.web.context.request.ServletRequestAttributes;
  16. @Aspect
  17. @Component
  18. @Slf4j
  19. public class ControllerLoggingAspect {
  20. /**
  21. * 拦截所有controller包下的方法
  22. */
  23. @Pointcut("execution(* com.summo.controller..*.*(..))")
  24. private void controllerMethod() {
  25. }
  26. @Around("controllerMethod()")
  27. public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
  28. long startTime = System.currentTimeMillis();
  29. //获取本次接口的唯一码
  30. String token = java.util.UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();
  31. MDC.put("requestId", token);
  32. //获取HttpServletRequest
  33. RequestAttributes ra = RequestContextHolder.getRequestAttributes();
  34. ServletRequestAttributes sra = (ServletRequestAttributes)ra;
  35. HttpServletRequest request = sra.getRequest();
  36. // 获取请求相关信息
  37. String url = request.getRequestURL().toString();
  38. String method = request.getMethod();
  39. String uri = request.getRequestURI();
  40. String params = request.getQueryString();
  41. if (StringUtils.isEmpty(params) && StringUtils.equals("POST", method)) {
  42. if (Objects.nonNull(joinPoint.getArgs())) {
  43. for (Object arg : joinPoint.getArgs()) {
  44. params += arg;
  45. }
  46. }
  47. }
  48. // 获取调用方法相信
  49. Signature signature = joinPoint.getSignature();
  50. String className = signature.getDeclaringTypeName();
  51. String methodName = signature.getName();
  52. log.info("@http请求开始, {}#{}() URI: {}, method: {}, URL: {}, params: {}",
  53. className, methodName, uri, method, url, params);
  54. //result的值就是被拦截方法的返回值
  55. try {
  56. //proceed方法是调用实际所拦截的controller中的方法,这里的result为调用方法后的返回值
  57. Object result = joinPoint.proceed();
  58. long endTime = System.currentTimeMillis();
  59. //定义请求结束时的返回数据,包括调用时间、返回值结果等
  60. log.info("@http请求结束, {}#{}(), URI: {}, method: {}, URL: {}, time: {}ms ",
  61. className, methodName, uri, method, url, (endTime - startTime));
  62. return result;
  63. } catch (Exception e) {
  64. long endTime = System.currentTimeMillis();
  65. log.error("@http请求出错, {}#{}(), URI: {}, method: {}, URL: {}, time: {}ms",
  66. className, methodName, uri, method, url, (endTime - startTime), e);
  67. throw e;
  68. } finally {
  69. MDC.remove("requestId");
  70. }
  71. }
  72. }

2、增加requestId

由于接口的调用都是异步的,所以一旦QPS上来,那么接口的调用就会很混乱,不加一个标识的话,就不知道哪个返回值属于那个请求的了。
这个时候我们则需要加一个requestId(或者叫traceId)用来标识一个请求。

也即这段代码

  1. //获取本次接口的唯一码
  2. String token = java.util.UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();
  3. MDC.put("requestId", token);
  4. ... ...
  5. MDC.remove("requestId");

同时logback.xml中也需要加一下requestId的打印,在logback.xml中可以使用%X{requestId}获取到MDC中添加的遍历。
完整的logback.xml配置文件如下:

  1. <configuration>
  2. <!-- 默认的一些配置 -->
  3. <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
  4. <!-- 定义应用名称,区分应用 -->
  5. <property name="APP_NAME" value="monitor-test"/>
  6. <!-- 定义日志文件的输出路径 -->
  7. <property name="LOG_PATH" value="${user.home}/logs/${APP_NAME}"/>
  8. <!-- 定义日志文件名称和路径 -->
  9. <property name="LOG_FILE" value="${LOG_PATH}/application.log"/>
  10. <!-- 定义警告级别日志文件名称和路径 -->
  11. <property name="WARN_LOG_FILE" value="${LOG_PATH}/warn.log"/>
  12. <!-- 定义错误级别日志文件名称和路径 -->
  13. <property name="ERROR_LOG_FILE" value="${LOG_PATH}/error.log"/>
  14. <!-- 自定义控制台打印格式 -->
  15. <property name="FILE_LOG_PATTERN" value="%green(%d{yyyy-MM-dd HH:mm:ss.SSS}) [%blue(requestId: %X{requestId})] [%highlight(%thread)] ${PID:- } %logger{36} %-5level - %msg%n"/>
  16. <!-- 将日志滚动输出到application.log文件中 -->
  17. <appender name="APPLICATION"
  18. class="ch.qos.logback.core.rolling.RollingFileAppender">
  19. <!-- 输出文件目的地 -->
  20. <file>${LOG_FILE}</file>
  21. <encoder>
  22. <pattern>${FILE_LOG_PATTERN}</pattern>
  23. <charset>utf8</charset>
  24. </encoder>
  25. <!-- 设置 RollingPolicy 属性,用于配置文件大小限制,保留天数、文件名格式 -->
  26. <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
  27. <!-- 文件命名格式 -->
  28. <fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
  29. <!-- 文件保留最大天数 -->
  30. <maxHistory>7</maxHistory>
  31. <!-- 文件大小限制 -->
  32. <maxFileSize>50MB</maxFileSize>
  33. <!-- 文件总大小 -->
  34. <totalSizeCap>500MB</totalSizeCap>
  35. </rollingPolicy>
  36. </appender>
  37. <!-- 摘取出WARN级别日志输出到warn.log中 -->
  38. <appender name="WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
  39. <file>${WARN_LOG_FILE}</file>
  40. <encoder>
  41. <!-- 使用默认的输出格式打印 -->
  42. <pattern>${CONSOLE_LOG_PATTERN}</pattern>
  43. <charset>utf8</charset>
  44. </encoder>
  45. <!-- 设置 RollingPolicy 属性,用于配置文件大小限制,保留天数、文件名格式 -->
  46. <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
  47. <!-- 文件命名格式 -->
  48. <fileNamePattern>${LOG_PATH}/warn.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
  49. <!-- 文件保留最大天数 -->
  50. <maxHistory>7</maxHistory>
  51. <!-- 文件大小限制 -->
  52. <maxFileSize>50MB</maxFileSize>
  53. <!-- 文件总大小 -->
  54. <totalSizeCap>500MB</totalSizeCap>
  55. </rollingPolicy>
  56. <!-- 日志过滤器,将WARN相关日志过滤出来 -->
  57. <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
  58. <level>WARN</level>
  59. </filter>
  60. </appender>
  61. <!-- 摘取出ERROR级别日志输出到error.log中 -->
  62. <appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
  63. <file>${ERROR_LOG_FILE}</file>
  64. <encoder>
  65. <!-- 使用默认的输出格式打印 -->
  66. <pattern>${CONSOLE_LOG_PATTERN}</pattern>
  67. <charset>utf8</charset>
  68. </encoder>
  69. <!-- 设置 RollingPolicy 属性,用于配置文件大小限制,保留天数、文件名格式 -->
  70. <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
  71. <!-- 文件命名格式 -->
  72. <fileNamePattern>${LOG_PATH}/error.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
  73. <!-- 文件保留最大天数 -->
  74. <maxHistory>7</maxHistory>
  75. <!-- 文件大小限制 -->
  76. <maxFileSize>50MB</maxFileSize>
  77. <!-- 文件总大小 -->
  78. <totalSizeCap>500MB</totalSizeCap>
  79. </rollingPolicy>
  80. <!-- 日志过滤器,将ERROR相关日志过滤出来 -->
  81. <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
  82. <level>ERROR</level>
  83. </filter>
  84. </appender>
  85. <!-- 配置控制台输出 -->
  86. <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
  87. <encoder>
  88. <pattern>${FILE_LOG_PATTERN}</pattern>
  89. <charset>utf8</charset>
  90. </encoder>
  91. </appender>
  92. <!-- 配置输出级别 -->
  93. <root level="INFO">
  94. <!-- 加入控制台输出 -->
  95. <appender-ref ref="CONSOLE"/>
  96. <!-- 加入APPLICATION输出 -->
  97. <appender-ref ref="APPLICATION"/>
  98. <!-- 加入WARN日志输出 -->
  99. <appender-ref ref="WARN"/>
  100. <!-- 加入ERROR日志输出 -->
  101. <appender-ref ref="ERROR"/>
  102. </root>
  103. </configuration>

3、效果如下图

4、接口监控遇到的一些坑

返回值数据量很大会刷屏,尽量不要打印返回值。
文件上传接口会直接挂掉,所以上传的接口一般不会加入监控。

方法二、将风险高的操作保存到数据库中

虽然方法一能够记录每个接口的日志,但这些日志只存在于服务器上,并且有大小和时间限制,到期后就会消失。这种做法对所有请求或操作都一视同仁,不会对风险较高的请求进行特殊处理。为了解决危险操作带来的风险,我们需要将其持久化,以便在出现问题时能够快速找到原因。最常见的做法是将风险高的操作保存到数据库中。
实现原理还是使用方法一种的切面,不过这里使用的是注解切面,具体做法请见下文。

1、新建一张log表,存储风险操作

表结构如下:

建表语句我也贴出来

  1. SET NAMES utf8mb4;
  2. SET FOREIGN_KEY_CHECKS = 0;
  3. -- ----------------------------
  4. -- Table structure for user_oper_log
  5. -- ----------------------------
  6. DROP TABLE IF EXISTS `user_oper_log`;
  7. CREATE TABLE `user_oper_log` (
  8. `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '物理主键',
  9. `operation` varchar(64) DEFAULT NULL COMMENT '操作内容',
  10. `time` bigint DEFAULT NULL COMMENT '耗时',
  11. `method` text COMMENT '操作方法',
  12. `params` text COMMENT '参数内容',
  13. `ip` varchar(64) DEFAULT NULL COMMENT 'IP',
  14. `location` varchar(64) DEFAULT NULL COMMENT '操作地点',
  15. `response_code` varchar(32) DEFAULT NULL COMMENT '应答码',
  16. `response_text` text COMMENT '应答内容',
  17. `gmt_create` datetime DEFAULT NULL COMMENT '创建时间',
  18. `gmt_modified` datetime DEFAULT NULL COMMENT '更新时间',
  19. `creator_id` bigint DEFAULT NULL COMMENT '创建人',
  20. `modifier_id` bigint DEFAULT NULL COMMENT '更新人',
  21. PRIMARY KEY (`id`)
  22. ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='用户操作日志表';
  23. SET FOREIGN_KEY_CHECKS = 1;

核心字段为操作方法、参数内容、IP、操作地点、应答码、应答内容、创建人这些,其中IP和操作地址这两个是推算的,不一定很准。这些字段也不是非常全面,如果大家还有自己想记录的字段信息也可以加进来。

2、新建@Log注解和切面处理类LogAspect

注解类

  1. package com.summo.log;
  2. import java.lang.annotation.ElementType;
  3. import java.lang.annotation.Retention;
  4. import java.lang.annotation.RetentionPolicy;
  5. import java.lang.annotation.Target;
  6. @Target(ElementType.METHOD)
  7. @Retention(RetentionPolicy.RUNTIME)
  8. public @interface Log {
  9. /**
  10. * 接口功能描述
  11. *
  12. * @return
  13. */
  14. String methodDesc() default "";
  15. }

切面处理类

  1. package com.summo.log;
  2. import java.io.Serializable;
  3. import java.lang.reflect.Method;
  4. import java.util.ArrayList;
  5. import java.util.Arrays;
  6. import java.util.Calendar;
  7. import java.util.List;
  8. import java.util.Map;
  9. import java.util.Set;
  10. import javax.servlet.http.HttpServletRequest;
  11. import com.alibaba.fastjson.JSONObject;
  12. import com.summo.entity.UserOperInfoDO;
  13. import com.summo.repository.UserOperInfoRepository;
  14. import com.summo.util.HttpContextUtil;
  15. import com.summo.util.IPUtil;
  16. import lombok.extern.slf4j.Slf4j;
  17. import org.aspectj.lang.ProceedingJoinPoint;
  18. import org.aspectj.lang.annotation.Around;
  19. import org.aspectj.lang.annotation.Aspect;
  20. import org.aspectj.lang.annotation.Pointcut;
  21. import org.aspectj.lang.reflect.MethodSignature;
  22. import org.springframework.beans.factory.annotation.Autowired;
  23. import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
  24. import org.springframework.stereotype.Component;
  25. import org.springframework.web.multipart.MultipartFile;
  26. @Slf4j
  27. @Aspect
  28. @Component
  29. public class LogAspect {
  30. @Autowired
  31. private UserOperInfoRepository userOperInfoRepository;
  32. @Pointcut("@annotation(com.summo.log.Log)")
  33. public void pointcut() {
  34. // do nothing
  35. }
  36. @Around("pointcut()")
  37. public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
  38. Object result = null;
  39. //默认操作对象为-1L
  40. MethodSignature signature = (MethodSignature)joinPoint.getSignature();
  41. Method method = signature.getMethod();
  42. Log logAnnotation = method.getAnnotation(Log.class);
  43. UserOperInfoDO log = new UserOperInfoDO();
  44. if (logAnnotation != null) {
  45. // 注解上的描述
  46. log.setOperation(logAnnotation.methodDesc());
  47. }
  48. // 请求的类名
  49. String className = joinPoint.getTarget().getClass().getName();
  50. // 请求的方法名
  51. String methodName = signature.getName();
  52. log.setMethod(className + "." + methodName + "()");
  53. // 请求的方法参数值
  54. Object[] args = joinPoint.getArgs();
  55. // 请求的方法参数名称
  56. LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
  57. String[] paramNames = u.getParameterNames(method);
  58. if (args != null && paramNames != null) {
  59. StringBuilder params = new StringBuilder();
  60. params = handleParams(params, args, Arrays.asList(paramNames));
  61. log.setParams(params.toString());
  62. }
  63. log.setGmtCreate(Calendar.getInstance().getTime());
  64. long beginTime = System.currentTimeMillis();
  65. // 执行方法
  66. result = joinPoint.proceed();
  67. // 执行时长(毫秒)
  68. long time = System.currentTimeMillis() - beginTime;
  69. HttpServletRequest request = HttpContextUtil.getHttpServletRequest();
  70. // 设置 IP 地址
  71. String ip = IPUtil.getIpAddr(request);
  72. log.setIp(ip);
  73. log.setTime(time);
  74. //保存操作记录到数据库中
  75. userOperInfoRepository.save(log);
  76. return result;
  77. }
  78. /**
  79. * 参数打印合理化
  80. *
  81. * @param params 参数字符串
  82. * @param args 参数列表
  83. * @param paramNames 参数名
  84. * @return
  85. */
  86. private StringBuilder handleParams(StringBuilder params, Object[] args, List paramNames) {
  87. for (int i = 0; i < args.length; i++) {
  88. if (args[i] instanceof Map) {
  89. Set set = ((Map)args[i]).keySet();
  90. List<Object> list = new ArrayList<>();
  91. List<Object> paramList = new ArrayList<>();
  92. for (Object key : set) {
  93. list.add(((Map)args[i]).get(key));
  94. paramList.add(key);
  95. }
  96. return handleParams(params, list.toArray(), paramList);
  97. } else {
  98. if (args[i] instanceof Serializable) {
  99. Class<?> aClass = args[i].getClass();
  100. try {
  101. aClass.getDeclaredMethod("toString", new Class[] {null});
  102. // 如果不抛出 NoSuchMethodException 异常则存在 toString 方法 ,安全的 writeValueAsString ,否则 走 Object的
  103. // toString方法
  104. params.append(" ").append(paramNames.get(i)).append(": ").append(
  105. JSONObject.toJSONString(args[i]));
  106. } catch (NoSuchMethodException e) {
  107. params.append(" ").append(paramNames.get(i)).append(": ").append(
  108. JSONObject.toJSONString(args[i].toString()));
  109. }
  110. } else if (args[i] instanceof MultipartFile) {
  111. MultipartFile file = (MultipartFile)args[i];
  112. params.append(" ").append(paramNames.get(i)).append(": ").append(file.getName());
  113. } else {
  114. params.append(" ").append(paramNames.get(i)).append(": ").append(args[i]);
  115. }
  116. }
  117. }
  118. return params;
  119. }
  120. }

3、使用方法

在需要监控的接口方法上加上@Log注解

  1. @PostMapping("/saveRel")
  2. @Log(methodDesc = "添加记录")
  3. public Boolean saveRel(@RequestBody SaveRelReq saveRelReq) {
  4. return userRoleRelService.saveRel(saveRelReq);
  5. }
  6. @DeleteMapping("/delRel")
  7. @Log(methodDesc = "删除记录")
  8. public Boolean delRel(Long relId) {
  9. return userRoleRelService.delRel(relId);
  10. }

调用一下测试功能

数据库中保存的记录

这里可以看到已经有记录保存在数据库中了,包括两次添加操作、一次删除操作,并且记录了操作人的IP地址(这里我使用的是localhost所以IP是127.0.0.1)和操作时间。但是这里有一个问题:没有记录操作人的ID,也即creator_id字段为空,如果不知道这条记录是谁的,那这个功能就没有意义了,所以在方法三我将会说一下如何记录每一行数据的创建者和修改者。

方法三、记录每一行数据的创建者和修改者

这个功能的实现需要用到一个非常关键的东西:用户上下文。如何实现请看:《优化接口设计的思路》系列:第二篇—接口用户上下文的设计与实现。

那么现在假设我已经有了GlobalUserContext.getUserContext()方法可以获取到用户上下文信息,如何使用呢?
方法二没有记录操作人的ID,现在可以可以通过下面这种方法获取当前操作人的ID:

  1. log.setCreatorId(GlobalUserContext.getUserContext().getUserId());

但是!!!我这里的标题是:记录每一行数据的创建者和修改者,可不仅仅是只操作user_oper_log的每一行数据,而是系统中的每一张表的每一行数据!那现在问题来了,如何实现这个需求?
最笨的办法就是在每个新增、更新的代码下都加上setCreatorId和setModifierId这些代码,实现是可以实现,但是感觉太low了,所以我这里提供一个思路和一个例子来优化这些代码。

1、统一字段名和类型

在每张表中都加入gmt_create(datetime 创建时间)、gmt_modified(datetime 更新时间)、creator_id(bigint 创建人ID)、modifier_id(bigint 更新人ID),我们将所有表中的这些辅助字段统一命名、统一类型,这样给我们统一处理提供了基础。

2、将这些字段集成到一个抽象类中

这样做的好处有两个:

  • 其他表的DO类继承这个抽象类,那么DO中就不需要再定义以上4个字段
  • 统一处理的类只有抽象类一个了

tips:非常建议使用mybatis-plus来实现这个功能,maven依赖如下:

  1. <!-- mybatis-plus -->
  2. <dependency>
  3. <groupId>com.baomidou</groupId>
  4. <artifactId>mybatis-plus-boot-starter</artifactId>
  5. <version>3.3.2</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>com.baomidou</groupId>
  9. <artifactId>mybatis-plus-extension</artifactId>
  10. <version>3.3.2</version>
  11. </dependency>

类名定义和代码如下
AbstractBaseDO.java

  1. package com.summo.entity;
  2. import java.io.Serializable;
  3. import java.util.Date;
  4. import com.baomidou.mybatisplus.annotation.FieldFill;
  5. import com.baomidou.mybatisplus.annotation.TableField;
  6. import com.baomidou.mybatisplus.extension.activerecord.Model;
  7. import lombok.Getter;
  8. import lombok.Setter;
  9. @Getter
  10. @Setter
  11. public class AbstractBaseDO<T extends Model<T>> extends Model<T> implements Serializable {
  12. /**
  13. * 创建时间
  14. */
  15. @TableField(fill = FieldFill.INSERT)
  16. private Date gmtCreate;
  17. /**
  18. * 修改时间
  19. */
  20. @TableField(fill = FieldFill.INSERT_UPDATE)
  21. private Date gmtModified;
  22. /**
  23. * 创建人ID
  24. */
  25. @TableField(fill = FieldFill.INSERT)
  26. private Long creatorId;
  27. /**
  28. * 修改人ID
  29. */
  30. @TableField(fill = FieldFill.INSERT_UPDATE)
  31. private Long modifierId;
  32. }

3、使用mybatis-plus的MetaObjectHandler全局拦截insert和update操作

自定义MetaObjectHandlerConfig继承MetaObjectHandler,代码如下
MetaObjectHandlerConfig.java

  1. package com.summo.entity;
  2. import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
  3. import org.apache.ibatis.reflection.MetaObject;
  4. @Configuration
  5. public class MetaObjectHandlerConfig implements MetaObjectHandler {
  6. @Override
  7. public void insertFill(MetaObject metaObject) {
  8. }
  9. @Override
  10. public void updateFill(MetaObject metaObject) {
  11. }
  12. }

逻辑补全的代码如下

  1. package com.summo.entity;
  2. import java.util.Calendar;
  3. import java.util.Date;
  4. import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
  5. import com.summo.context.GlobalUserContext;
  6. import com.summo.context.UserContext;
  7. import org.apache.ibatis.reflection.MetaObject;
  8. @Configuration
  9. public class MetaObjectHandlerConfig implements MetaObjectHandler {
  10. @Override
  11. public void insertFill(MetaObject metaObject) {
  12. //获取用户上下文
  13. UserContext userContext = GlobalUserContext.getUserContext();
  14. //获取创建时间
  15. Date date = Calendar.getInstance().getTime();
  16. //设置gmtCreate
  17. this.fillStrategy(metaObject, "gmtCreate", date);
  18. //设置gmtModified
  19. this.fillStrategy(metaObject, "gmtModified", date);
  20. //设置creatorId
  21. this.fillStrategy(metaObject, "creatorId", userContext.getUserId());
  22. //设置modifierId
  23. this.fillStrategy(metaObject, "modifierId", userContext.getUserId());
  24. }
  25. @Override
  26. public void updateFill(MetaObject metaObject) {
  27. //获取用户上下文
  28. UserContext userContext = GlobalUserContext.getUserContext();
  29. //获取更新时间
  30. Date date = Calendar.getInstance().getTime();
  31. //更新操作修改gmtModified
  32. this.setFieldValByName("gmtModified", date, metaObject);
  33. //更新操作修改modifierId
  34. this.setFieldValByName("modifierId", userContext.getUserId(), metaObject);
  35. }
  36. }

原文链接:https://www.cnblogs.com/wlovet/p/17533984.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号