前言
大家好!我是sum墨,一个一线的底层码农,平时喜欢研究和思考一些技术相关的问题并整理成文,限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。
作为一名从业已达六年的老码农,我的工作主要是开发后端Java业务系统,包括各种管理后台和小程序等。在这些项目中,我设计过单/多租户体系系统,对接过许多开放平台,也搞过消息中心这类较为复杂的应用,但幸运的是,我至今还没有遇到过线上系统由于代码崩溃导致资损的情况。这其中的原因有三点:一是业务系统本身并不复杂;二是我一直遵循某大厂代码规约,在开发过程中尽可能按规约编写代码;三是经过多年的开发经验积累,我成为了一名熟练工,掌握了一些实用的技巧。
接口设计是整个系统设计中非常重要的一环,其中包括限流、权限、入参出参、切面等方面。设计一个好的接口可以帮助我们省去很多不必要的麻烦,从而提升整个系统的稳定性和可扩展性。作为接口设计经验分享的第三篇,我想分享一下如何在用户使用过程中留下操作痕迹。在实际开发中,我会采取一些手段来记录用户操作,例如使用日志记录用户行为,或者在数据库中保存用户操作记录。这些痕迹可以帮助我们快速定位和解决问题,同时也可以为后续数据分析和优化提供有价值的参考。
本文参考项目源码地址:summo-springboot-interface-demo
由于文章经常被抄袭,开源的代码甚至被当成收费项,所以源码里面不是全部代码,有需要的同学可以留个邮箱,我给你单独发!
方法一、将接口的参数和结果打印在日志文件中
日志文件是我们记录用户使用痕迹的第一个地方,我之前写过一篇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)
关键代码如下
1package com.summo.aspect; 2 3import java.util.Objects; 4 5import javax.servlet.http.HttpServletRequest; 6 7import com.alibaba.druid.util.StringUtils; 8 9import lombok.extern.slf4j.Slf4j; 10import org.aspectj.lang.ProceedingJoinPoint; 11import org.aspectj.lang.Signature; 12import org.aspectj.lang.annotation.Around; 13import org.aspectj.lang.annotation.Aspect; 14import org.aspectj.lang.annotation.Pointcut; 15import org.slf4j.MDC; 16import org.springframework.stereotype.Component; 17import org.springframework.web.context.request.RequestAttributes; 18import org.springframework.web.context.request.RequestContextHolder; 19import org.springframework.web.context.request.ServletRequestAttributes; 20 21@Aspect 22@Component 23@Slf4j 24public class ControllerLoggingAspect { 25 26 /** 27 * 拦截所有controller包下的方法 28 */ 29 @Pointcut("execution(* com.summo.controller..*.*(..))") 30 private void controllerMethod() { 31 32 } 33 34 @Around("controllerMethod()") 35 public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable { 36 long startTime = System.currentTimeMillis(); 37 //获取本次接口的唯一码 38 String token = java.util.UUID.randomUUID().toString().replaceAll("-", "").toUpperCase(); 39 MDC.put("requestId", token); 40 41 //获取HttpServletRequest 42 RequestAttributes ra = RequestContextHolder.getRequestAttributes(); 43 ServletRequestAttributes sra = (ServletRequestAttributes)ra; 44 HttpServletRequest request = sra.getRequest(); 45 46 // 获取请求相关信息 47 String url = request.getRequestURL().toString(); 48 String method = request.getMethod(); 49 String uri = request.getRequestURI(); 50 String params = request.getQueryString(); 51 if (StringUtils.isEmpty(params) && StringUtils.equals("POST", method)) { 52 if (Objects.nonNull(joinPoint.getArgs())) { 53 for (Object arg : joinPoint.getArgs()) { 54 params += arg; 55 } 56 } 57 } 58 // 获取调用方法相信 59 Signature signature = joinPoint.getSignature(); 60 String className = signature.getDeclaringTypeName(); 61 String methodName = signature.getName(); 62 log.info("@http请求开始, {}#{}() URI: {}, method: {}, URL: {}, params: {}", 63 className, methodName, uri, method, url, params); 64 //result的值就是被拦截方法的返回值 65 try { 66 //proceed方法是调用实际所拦截的controller中的方法,这里的result为调用方法后的返回值 67 Object result = joinPoint.proceed(); 68 long endTime = System.currentTimeMillis(); 69 //定义请求结束时的返回数据,包括调用时间、返回值结果等 70 log.info("@http请求结束, {}#{}(), URI: {}, method: {}, URL: {}, time: {}ms ", 71 className, methodName, uri, method, url, (endTime - startTime)); 72 73 return result; 74 } catch (Exception e) { 75 long endTime = System.currentTimeMillis(); 76 log.error("@http请求出错, {}#{}(), URI: {}, method: {}, URL: {}, time: {}ms", 77 className, methodName, uri, method, url, (endTime - startTime), e); 78 throw e; 79 } finally { 80 MDC.remove("requestId"); 81 } 82 } 83}
2、增加requestId
由于接口的调用都是异步的,所以一旦QPS上来,那么接口的调用就会很混乱,不加一个标识的话,就不知道哪个返回值属于那个请求的了。 这个时候我们则需要加一个requestId(或者叫traceId)用来标识一个请求。
也即这段代码
1//获取本次接口的唯一码 2String token = java.util.UUID.randomUUID().toString().replaceAll("-", "").toUpperCase(); 3MDC.put("requestId", token); 4 5... ... 6MDC.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 <!-- 自定义控制台打印格式 --> 16 <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"/> 17 18 <!-- 将日志滚动输出到application.log文件中 --> 19 <appender name="APPLICATION" 20 class="ch.qos.logback.core.rolling.RollingFileAppender"> 21 <!-- 输出文件目的地 --> 22 <file>${LOG_FILE}</file> 23 <encoder> 24 <pattern>${FILE_LOG_PATTERN}</pattern> 25 <charset>utf8</charset> 26 </encoder> 27 <!-- 设置 RollingPolicy 属性,用于配置文件大小限制,保留天数、文件名格式 --> 28 <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> 29 <!-- 文件命名格式 --> 30 <fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.%i.log</fileNamePattern> 31 <!-- 文件保留最大天数 --> 32 <maxHistory>7</maxHistory> 33 <!-- 文件大小限制 --> 34 <maxFileSize>50MB</maxFileSize> 35 <!-- 文件总大小 --> 36 <totalSizeCap>500MB</totalSizeCap> 37 </rollingPolicy> 38 </appender> 39 40 <!-- 摘取出WARN级别日志输出到warn.log中 --> 41 <appender name="WARN" class="ch.qos.logback.core.rolling.RollingFileAppender"> 42 <file>${WARN_LOG_FILE}</file> 43 <encoder> 44 <!-- 使用默认的输出格式打印 --> 45 <pattern>${CONSOLE_LOG_PATTERN}</pattern> 46 <charset>utf8</charset> 47 </encoder> 48 <!-- 设置 RollingPolicy 属性,用于配置文件大小限制,保留天数、文件名格式 --> 49 <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> 50 <!-- 文件命名格式 --> 51 <fileNamePattern>${LOG_PATH}/warn.%d{yyyy-MM-dd}.%i.log</fileNamePattern> 52 <!-- 文件保留最大天数 --> 53 <maxHistory>7</maxHistory> 54 <!-- 文件大小限制 --> 55 <maxFileSize>50MB</maxFileSize> 56 <!-- 文件总大小 --> 57 <totalSizeCap>500MB</totalSizeCap> 58 </rollingPolicy> 59 <!-- 日志过滤器,将WARN相关日志过滤出来 --> 60 <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> 61 <level>WARN</level> 62 </filter> 63 </appender> 64 65 <!-- 摘取出ERROR级别日志输出到error.log中 --> 66 <appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender"> 67 <file>${ERROR_LOG_FILE}</file> 68 <encoder> 69 <!-- 使用默认的输出格式打印 --> 70 <pattern>${CONSOLE_LOG_PATTERN}</pattern> 71 <charset>utf8</charset> 72 </encoder> 73 <!-- 设置 RollingPolicy 属性,用于配置文件大小限制,保留天数、文件名格式 --> 74 <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> 75 <!-- 文件命名格式 --> 76 <fileNamePattern>${LOG_PATH}/error.%d{yyyy-MM-dd}.%i.log</fileNamePattern> 77 <!-- 文件保留最大天数 --> 78 <maxHistory>7</maxHistory> 79 <!-- 文件大小限制 --> 80 <maxFileSize>50MB</maxFileSize> 81 <!-- 文件总大小 --> 82 <totalSizeCap>500MB</totalSizeCap> 83 </rollingPolicy> 84 <!-- 日志过滤器,将ERROR相关日志过滤出来 --> 85 <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> 86 <level>ERROR</level> 87 </filter> 88 </appender> 89 90 <!-- 配置控制台输出 --> 91 <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> 92 <encoder> 93 <pattern>${FILE_LOG_PATTERN}</pattern> 94 <charset>utf8</charset> 95 </encoder> 96 </appender> 97 98 99 <!-- 配置输出级别 --> 100 <root level="INFO"> 101 <!-- 加入控制台输出 --> 102 <appender-ref ref="CONSOLE"/> 103 <!-- 加入APPLICATION输出 --> 104 <appender-ref ref="APPLICATION"/> 105 <!-- 加入WARN日志输出 --> 106 <appender-ref ref="WARN"/> 107 <!-- 加入ERROR日志输出 --> 108 <appender-ref ref="ERROR"/> 109 </root> 110</configuration> 111
3、效果如下图

4、接口监控遇到的一些坑
返回值数据量很大会刷屏,尽量不要打印返回值。 文件上传接口会直接挂掉,所以上传的接口一般不会加入监控。
方法二、将风险高的操作保存到数据库中
虽然方法一能够记录每个接口的日志,但这些日志只存在于服务器上,并且有大小和时间限制,到期后就会消失。这种做法对所有请求或操作都一视同仁,不会对风险较高的请求进行特殊处理。为了解决危险操作带来的风险,我们需要将其持久化,以便在出现问题时能够快速找到原因。最常见的做法是将风险高的操作保存到数据库中。 实现原理还是使用方法一种的切面,不过这里使用的是注解切面,具体做法请见下文。
1、新建一张log表,存储风险操作
表结构如下:
建表语句我也贴出来
1SET NAMES utf8mb4; 2SET FOREIGN_KEY_CHECKS = 0; 3 4-- ---------------------------- 5-- Table structure for user_oper_log 6-- ---------------------------- 7DROP TABLE IF EXISTS `user_oper_log`; 8CREATE TABLE `user_oper_log` ( 9 `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '物理主键', 10 `operation` varchar(64) DEFAULT NULL COMMENT '操作内容', 11 `time` bigint DEFAULT NULL COMMENT '耗时', 12 `method` text COMMENT '操作方法', 13 `params` text COMMENT '参数内容', 14 `ip` varchar(64) DEFAULT NULL COMMENT 'IP', 15 `location` varchar(64) DEFAULT NULL COMMENT '操作地点', 16 `response_code` varchar(32) DEFAULT NULL COMMENT '应答码', 17 `response_text` text COMMENT '应答内容', 18 `gmt_create` datetime DEFAULT NULL COMMENT '创建时间', 19 `gmt_modified` datetime DEFAULT NULL COMMENT '更新时间', 20 `creator_id` bigint DEFAULT NULL COMMENT '创建人', 21 `modifier_id` bigint DEFAULT NULL COMMENT '更新人', 22 PRIMARY KEY (`id`) 23) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='用户操作日志表'; 24 25SET FOREIGN_KEY_CHECKS = 1;
核心字段为操作方法、参数内容、IP、操作地点、应答码、应答内容、创建人这些,其中IP和操作地址这两个是推算的,不一定很准。这些字段也不是非常全面,如果大家还有自己想记录的字段信息也可以加进来。
2、新建@Log注解和切面处理类LogAspect
注解类
1package com.summo.log; 2 3import java.lang.annotation.ElementType; 4import java.lang.annotation.Retention; 5import java.lang.annotation.RetentionPolicy; 6import java.lang.annotation.Target; 7 8@Target(ElementType.METHOD) 9@Retention(RetentionPolicy.RUNTIME) 10public @interface Log { 11 /** 12 * 接口功能描述 13 * 14 * @return 15 */ 16 String methodDesc() default ""; 17}
切面处理类
1package com.summo.log; 2 3import java.io.Serializable; 4import java.lang.reflect.Method; 5import java.util.ArrayList; 6import java.util.Arrays; 7import java.util.Calendar; 8import java.util.List; 9import java.util.Map; 10import java.util.Set; 11 12import javax.servlet.http.HttpServletRequest; 13 14import com.alibaba.fastjson.JSONObject; 15 16import com.summo.entity.UserOperInfoDO; 17import com.summo.repository.UserOperInfoRepository; 18import com.summo.util.HttpContextUtil; 19import com.summo.util.IPUtil; 20import lombok.extern.slf4j.Slf4j; 21import org.aspectj.lang.ProceedingJoinPoint; 22import org.aspectj.lang.annotation.Around; 23import org.aspectj.lang.annotation.Aspect; 24import org.aspectj.lang.annotation.Pointcut; 25import org.aspectj.lang.reflect.MethodSignature; 26import org.springframework.beans.factory.annotation.Autowired; 27import org.springframework.core.LocalVariableTableParameterNameDiscoverer; 28import org.springframework.stereotype.Component; 29import org.springframework.web.multipart.MultipartFile; 30 31@Slf4j 32@Aspect 33@Component 34public class LogAspect { 35 36 @Autowired 37 private UserOperInfoRepository userOperInfoRepository; 38 39 @Pointcut("@annotation(com.summo.log.Log)") 40 public void pointcut() { 41 // do nothing 42 } 43 44 @Around("pointcut()") 45 public Object around(ProceedingJoinPoint joinPoint) throws Throwable { 46 Object result = null; 47 //默认操作对象为-1L 48 MethodSignature signature = (MethodSignature)joinPoint.getSignature(); 49 Method method = signature.getMethod(); 50 Log logAnnotation = method.getAnnotation(Log.class); 51 UserOperInfoDO log = new UserOperInfoDO(); 52 if (logAnnotation != null) { 53 // 注解上的描述 54 log.setOperation(logAnnotation.methodDesc()); 55 } 56 // 请求的类名 57 String className = joinPoint.getTarget().getClass().getName(); 58 // 请求的方法名 59 String methodName = signature.getName(); 60 log.setMethod(className + "." + methodName + "()"); 61 // 请求的方法参数值 62 Object[] args = joinPoint.getArgs(); 63 // 请求的方法参数名称 64 LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer(); 65 String[] paramNames = u.getParameterNames(method); 66 if (args != null && paramNames != null) { 67 StringBuilder params = new StringBuilder(); 68 params = handleParams(params, args, Arrays.asList(paramNames)); 69 log.setParams(params.toString()); 70 } 71 log.setGmtCreate(Calendar.getInstance().getTime()); 72 long beginTime = System.currentTimeMillis(); 73 // 执行方法 74 result = joinPoint.proceed(); 75 // 执行时长(毫秒) 76 long time = System.currentTimeMillis() - beginTime; 77 HttpServletRequest request = HttpContextUtil.getHttpServletRequest(); 78 // 设置 IP 地址 79 String ip = IPUtil.getIpAddr(request); 80 log.setIp(ip); 81 log.setTime(time); 82 83 //保存操作记录到数据库中 84 userOperInfoRepository.save(log); 85 return result; 86 } 87 88 /** 89 * 参数打印合理化 90 * 91 * @param params 参数字符串 92 * @param args 参数列表 93 * @param paramNames 参数名 94 * @return 95 */ 96 private StringBuilder handleParams(StringBuilder params, Object[] args, List paramNames) { 97 for (int i = 0; i < args.length; i++) { 98 if (args[i] instanceof Map) { 99 Set set = ((Map)args[i]).keySet(); 100 List<Object> list = new ArrayList<>(); 101 List<Object> paramList = new ArrayList<>(); 102 for (Object key : set) { 103 list.add(((Map)args[i]).get(key)); 104 paramList.add(key); 105 } 106 return handleParams(params, list.toArray(), paramList); 107 } else { 108 if (args[i] instanceof Serializable) { 109 Class<?> aClass = args[i].getClass(); 110 try { 111 aClass.getDeclaredMethod("toString", new Class[] {null}); 112 // 如果不抛出 NoSuchMethodException 异常则存在 toString 方法 ,安全的 writeValueAsString ,否则 走 Object的 113 // toString方法 114 params.append(" ").append(paramNames.get(i)).append(": ").append( 115 JSONObject.toJSONString(args[i])); 116 } catch (NoSuchMethodException e) { 117 params.append(" ").append(paramNames.get(i)).append(": ").append( 118 JSONObject.toJSONString(args[i].toString())); 119 } 120 } else if (args[i] instanceof MultipartFile) { 121 MultipartFile file = (MultipartFile)args[i]; 122 params.append(" ").append(paramNames.get(i)).append(": ").append(file.getName()); 123 } else { 124 params.append(" ").append(paramNames.get(i)).append(": ").append(args[i]); 125 } 126 } 127 } 128 return params; 129 } 130}
3、使用方法
在需要监控的接口方法上加上@Log注解
1@PostMapping("/saveRel") 2@Log(methodDesc = "添加记录") 3public Boolean saveRel(@RequestBody SaveRelReq saveRelReq) { 4 return userRoleRelService.saveRel(saveRelReq); 5} 6 7@DeleteMapping("/delRel") 8@Log(methodDesc = "删除记录") 9public Boolean delRel(Long relId) { 10 return userRoleRelService.delRel(relId); 11}
调用一下测试功能

数据库中保存的记录

这里可以看到已经有记录保存在数据库中了,包括两次添加操作、一次删除操作,并且记录了操作人的IP地址(这里我使用的是localhost所以IP是127.0.0.1)和操作时间。但是这里有一个问题:没有记录操作人的ID,也即creator_id字段为空,如果不知道这条记录是谁的,那这个功能就没有意义了,所以在方法三我将会说一下如何记录每一行数据的创建者和修改者。
方法三、记录每一行数据的创建者和修改者
这个功能的实现需要用到一个非常关键的东西:用户上下文。如何实现请看:《优化接口设计的思路》系列:第二篇—接口用户上下文的设计与实现。
那么现在假设我已经有了GlobalUserContext.getUserContext()方法可以获取到用户上下文信息,如何使用呢?
方法二没有记录操作人的ID,现在可以可以通过下面这种方法获取当前操作人的ID:
1log.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
1package com.summo.entity; 2 3import java.io.Serializable; 4import java.util.Date; 5 6import com.baomidou.mybatisplus.annotation.FieldFill; 7import com.baomidou.mybatisplus.annotation.TableField; 8import com.baomidou.mybatisplus.extension.activerecord.Model; 9import lombok.Getter; 10import lombok.Setter; 11 12@Getter 13@Setter 14public class AbstractBaseDO<T extends Model<T>> extends Model<T> implements Serializable { 15 16 /** 17 * 创建时间 18 */ 19 @TableField(fill = FieldFill.INSERT) 20 private Date gmtCreate; 21 22 /** 23 * 修改时间 24 */ 25 @TableField(fill = FieldFill.INSERT_UPDATE) 26 private Date gmtModified; 27 28 /** 29 * 创建人ID 30 */ 31 @TableField(fill = FieldFill.INSERT) 32 private Long creatorId; 33 34 /** 35 * 修改人ID 36 */ 37 @TableField(fill = FieldFill.INSERT_UPDATE) 38 private Long modifierId; 39 40}
3、使用mybatis-plus的MetaObjectHandler全局拦截insert和update操作
自定义MetaObjectHandlerConfig继承MetaObjectHandler,代码如下 MetaObjectHandlerConfig.java
1package com.summo.entity; 2 3import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; 4import org.apache.ibatis.reflection.MetaObject; 5 6@Configuration 7public class MetaObjectHandlerConfig implements MetaObjectHandler { 8 @Override 9 public void insertFill(MetaObject metaObject) { 10 11 } 12 13 @Override 14 public void updateFill(MetaObject metaObject) { 15 16 } 17}
逻辑补全的代码如下
1package com.summo.entity; 2 3import java.util.Calendar; 4import java.util.Date; 5 6import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; 7import com.summo.context.GlobalUserContext; 8import com.summo.context.UserContext; 9import org.apache.ibatis.reflection.MetaObject; 10 11@Configuration 12public class MetaObjectHandlerConfig implements MetaObjectHandler { 13 @Override 14 public void insertFill(MetaObject metaObject) { 15 //获取用户上下文 16 UserContext userContext = GlobalUserContext.getUserContext(); 17 //获取创建时间 18 Date date = Calendar.getInstance().getTime(); 19 //设置gmtCreate 20 this.fillStrategy(metaObject, "gmtCreate", date); 21 //设置gmtModified 22 this.fillStrategy(metaObject, "gmtModified", date); 23 //设置creatorId 24 this.fillStrategy(metaObject, "creatorId", userContext.getUserId()); 25 //设置modifierId 26 this.fillStrategy(metaObject, "modifierId", userContext.getUserId()); 27 } 28 29 @Override 30 public void updateFill(MetaObject metaObject) { 31 //获取用户上下文 32 UserContext userContext = GlobalUserContext.getUserContext(); 33 //获取更新时间 34 Date date = Calendar.getInstance().getTime(); 35 //更新操作修改gmtModified 36 this.setFieldValByName("gmtModified", date, metaObject); 37 //更新操作修改modifierId 38 this.setFieldValByName("modifierId", userContext.getUserId(), metaObject); 39 } 40}
