前言
大家好!我是sum墨,一個一線的底層碼農(nóng),平時喜歡研究和思考一些技術(shù)相關(guān)的問題并整理成文,限于本人水平,如果文章和代碼有表述不當之處,還請不吝賜教。
作為一名從業(yè)已達六年的老碼農(nóng),我的工作主要是開發(fā)后端Java業(yè)務系統(tǒng),包括各種管理后臺和小程序等。在這些項目中,我設(shè)計過單/多租戶體系系統(tǒng),對接過許多開放平臺,也搞過消息中心這類較為復雜的應用,但幸運的是,我至今還沒有遇到過線上系統(tǒng)由于代碼崩潰導致資損的情況。這其中的原因有三點:一是業(yè)務系統(tǒng)本身并不復雜;二是我一直遵循某大廠代碼規(guī)約,在開發(fā)過程中盡可能按規(guī)約編寫代碼;三是經(jīng)過多年的開發(fā)經(jīng)驗積累,我成為了一名熟練工,掌握了一些實用的技巧。
接口設(shè)計是整個系統(tǒng)設(shè)計中非常重要的一環(huán),其中包括限流、權(quán)限、入?yún)⒊鰠?、切面等方面。設(shè)計一個好的接口可以幫助我們省去很多不必要的麻煩,從而提升整個系統(tǒng)的穩(wěn)定性和可擴展性。作為接口設(shè)計經(jīng)驗分享的第三篇,我想分享一下如何在用戶使用過程中留下操作痕跡。在實際開發(fā)中,我會采取一些手段來記錄用戶操作,例如使用日志記錄用戶行為,或者在數(shù)據(jù)庫中保存用戶操作記錄。這些痕跡可以幫助我們快速定位和解決問題,同時也可以為后續(xù)數(shù)據(jù)分析和優(yōu)化提供有價值的參考。
本文參考項目源碼地址:summo-springboot-interface-demo
由于文章經(jīng)常被抄襲,開源的代碼甚至被當成收費項,所以源碼里面不是全部代碼,有需要的同學可以留個郵箱,我給你單獨發(fā)!
方法一、將接口的參數(shù)和結(jié)果打印在日志文件中
日志文件是我們記錄用戶使用痕跡的第一個地方,我之前寫過一篇SpringBoot項目如何配置logback.xml的文章來實現(xiàn)系統(tǒng)日志輸出,有興趣的同學可以去看看。
這里我主要講一下怎么方便將所有接口的出入?yún)⒋蛴〕鰜怼?/p>
1、使用aop監(jiān)控接口
依賴如下
<!-- aspectj -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
如果有同學不知道aspectj是啥的,可以看我這篇文章SpringBoot整合aspectj實現(xiàn)面向切面編程(即AOP)
關(guān)鍵代碼如下
package com.summo.aspect;
import java.util.Objects;
import javax.servlet.http.HttpServletRequest;
import com.alibaba.druid.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
@Aspect
@Component
@Slf4j
public class ControllerLoggingAspect {
/**
* 攔截所有controller包下的方法
*/
@Pointcut("execution(* com.summo.controller..*.*(..))")
private void controllerMethod() {
}
@Around("controllerMethod()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
//獲取本次接口的唯一碼
String token = java.util.UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();
MDC.put("requestId", token);
//獲取HttpServletRequest
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes)ra;
HttpServletRequest request = sra.getRequest();
// 獲取請求相關(guān)信息
String url = request.getRequestURL().toString();
String method = request.getMethod();
String uri = request.getRequestURI();
String params = request.getQueryString();
if (StringUtils.isEmpty(params) && StringUtils.equals("POST", method)) {
if (Objects.nonNull(joinPoint.getArgs())) {
for (Object arg : joinPoint.getArgs()) {
params += arg;
}
}
}
// 獲取調(diào)用方法相信
Signature signature = joinPoint.getSignature();
String className = signature.getDeclaringTypeName();
String methodName = signature.getName();
log.info("@http請求開始, {}#{}() URI: {}, method: {}, URL: {}, params: {}",
className, methodName, uri, method, url, params);
//result的值就是被攔截方法的返回值
try {
//proceed方法是調(diào)用實際所攔截的controller中的方法,這里的result為調(diào)用方法后的返回值
Object result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
//定義請求結(jié)束時的返回數(shù)據(jù),包括調(diào)用時間、返回值結(jié)果等
log.info("@http請求結(jié)束, {}#{}(), URI: {}, method: {}, URL: {}, time: {}ms ",
className, methodName, uri, method, url, (endTime - startTime));
return result;
} catch (Exception e) {
long endTime = System.currentTimeMillis();
log.error("@http請求出錯, {}#{}(), URI: {}, method: {}, URL: {}, time: {}ms",
className, methodName, uri, method, url, (endTime - startTime), e);
throw e;
} finally {
MDC.remove("requestId");
}
}
}
2、增加requestId
由于接口的調(diào)用都是異步的,所以一旦QPS上來,那么接口的調(diào)用就會很混亂,不加一個標識的話,就不知道哪個返回值屬于那個請求的了。
這個時候我們則需要加一個requestId(或者叫traceId)用來標識一個請求。
也即這段代碼
//獲取本次接口的唯一碼
String token = java.util.UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();
MDC.put("requestId", token);
... ...
MDC.remove("requestId");
同時logback.xml中也需要加一下requestId的打印,在logback.xml中可以使用%X{requestId}獲取到MDC中添加的遍歷。
完整的logback.xml配置文件如下:
<configuration>
<!-- 默認的一些配置 -->
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<!-- 定義應用名稱,區(qū)分應用 -->
<property name="APP_NAME" value="monitor-test"/>
<!-- 定義日志文件的輸出路徑 -->
<property name="LOG_PATH" value="${user.home}/logs/${APP_NAME}"/>
<!-- 定義日志文件名稱和路徑 -->
<property name="LOG_FILE" value="${LOG_PATH}/application.log"/>
<!-- 定義警告級別日志文件名稱和路徑 -->
<property name="WARN_LOG_FILE" value="${LOG_PATH}/warn.log"/>
<!-- 定義錯誤級別日志文件名稱和路徑 -->
<property name="ERROR_LOG_FILE" value="${LOG_PATH}/error.log"/>
<!-- 自定義控制臺打印格式 -->
<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"/>
<!-- 將日志滾動輸出到application.log文件中 -->
<appender name="APPLICATION"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 輸出文件目的地 -->
<file>${LOG_FILE}</file>
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
<!-- 設(shè)置 RollingPolicy 屬性,用于配置文件大小限制,保留天數(shù)、文件名格式 -->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 文件命名格式 -->
<fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!-- 文件保留最大天數(shù) -->
<maxHistory>7</maxHistory>
<!-- 文件大小限制 -->
<maxFileSize>50MB</maxFileSize>
<!-- 文件總大小 -->
<totalSizeCap>500MB</totalSizeCap>
</rollingPolicy>
</appender>
<!-- 摘取出WARN級別日志輸出到warn.log中 -->
<appender name="WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${WARN_LOG_FILE}</file>
<encoder>
<!-- 使用默認的輸出格式打印 -->
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
<!-- 設(shè)置 RollingPolicy 屬性,用于配置文件大小限制,保留天數(shù)、文件名格式 -->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 文件命名格式 -->
<fileNamePattern>${LOG_PATH}/warn.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!-- 文件保留最大天數(shù) -->
<maxHistory>7</maxHistory>
<!-- 文件大小限制 -->
<maxFileSize>50MB</maxFileSize>
<!-- 文件總大小 -->
<totalSizeCap>500MB</totalSizeCap>
</rollingPolicy>
<!-- 日志過濾器,將WARN相關(guān)日志過濾出來 -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>WARN</level>
</filter>
</appender>
<!-- 摘取出ERROR級別日志輸出到error.log中 -->
<appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${ERROR_LOG_FILE}</file>
<encoder>
<!-- 使用默認的輸出格式打印 -->
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
<!-- 設(shè)置 RollingPolicy 屬性,用于配置文件大小限制,保留天數(shù)、文件名格式 -->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 文件命名格式 -->
<fileNamePattern>${LOG_PATH}/error.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!-- 文件保留最大天數(shù) -->
<maxHistory>7</maxHistory>
<!-- 文件大小限制 -->
<maxFileSize>50MB</maxFileSize>
<!-- 文件總大小 -->
<totalSizeCap>500MB</totalSizeCap>
</rollingPolicy>
<!-- 日志過濾器,將ERROR相關(guān)日志過濾出來 -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
</appender>
<!-- 配置控制臺輸出 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
</appender>
<!-- 配置輸出級別 -->
<root level="INFO">
<!-- 加入控制臺輸出 -->
<appender-ref ref="CONSOLE"/>
<!-- 加入APPLICATION輸出 -->
<appender-ref ref="APPLICATION"/>
<!-- 加入WARN日志輸出 -->
<appender-ref ref="WARN"/>
<!-- 加入ERROR日志輸出 -->
<appender-ref ref="ERROR"/>
</root>
</configuration>
3、效果如下圖

4、接口監(jiān)控遇到的一些坑
返回值數(shù)據(jù)量很大會刷屏,盡量不要打印返回值。
文件上傳接口會直接掛掉,所以上傳的接口一般不會加入監(jiān)控。
方法二、將風險高的操作保存到數(shù)據(jù)庫中
雖然方法一能夠記錄每個接口的日志,但這些日志只存在于服務器上,并且有大小和時間限制,到期后就會消失。這種做法對所有請求或操作都一視同仁,不會對風險較高的請求進行特殊處理。為了解決危險操作帶來的風險,我們需要將其持久化,以便在出現(xiàn)問題時能夠快速找到原因。最常見的做法是將風險高的操作保存到數(shù)據(jù)庫中。
實現(xiàn)原理還是使用方法一種的切面,不過這里使用的是注解切面,具體做法請見下文。
1、新建一張log表,存儲風險操作
表結(jié)構(gòu)如下:

建表語句我也貼出來
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for user_oper_log
-- ----------------------------
DROP TABLE IF EXISTS `user_oper_log`;
CREATE TABLE `user_oper_log` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '物理主鍵',
`operation` varchar(64) DEFAULT NULL COMMENT '操作內(nèi)容',
`time` bigint DEFAULT NULL COMMENT '耗時',
`method` text COMMENT '操作方法',
`params` text COMMENT '參數(shù)內(nèi)容',
`ip` varchar(64) DEFAULT NULL COMMENT 'IP',
`location` varchar(64) DEFAULT NULL COMMENT '操作地點',
`response_code` varchar(32) DEFAULT NULL COMMENT '應答碼',
`response_text` text COMMENT '應答內(nèi)容',
`gmt_create` datetime DEFAULT NULL COMMENT '創(chuàng)建時間',
`gmt_modified` datetime DEFAULT NULL COMMENT '更新時間',
`creator_id` bigint DEFAULT NULL COMMENT '創(chuàng)建人',
`modifier_id` bigint DEFAULT NULL COMMENT '更新人',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='用戶操作日志表';
SET FOREIGN_KEY_CHECKS = 1;
核心字段為操作方法、參數(shù)內(nèi)容、IP、操作地點、應答碼、應答內(nèi)容、創(chuàng)建人這些,其中IP和操作地址這兩個是推算的,不一定很準。這些字段也不是非常全面,如果大家還有自己想記錄的字段信息也可以加進來。
2、新建@Log注解和切面處理類LogAspect
注解類
package com.summo.log;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
/**
* 接口功能描述
*
* @return
*/
String methodDesc() default "";
}
切面處理類
package com.summo.log;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import com.alibaba.fastjson.JSONObject;
import com.summo.entity.UserOperInfoDO;
import com.summo.repository.UserOperInfoRepository;
import com.summo.util.HttpContextUtil;
import com.summo.util.IPUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
@Slf4j
@Aspect
@Component
public class LogAspect {
@Autowired
private UserOperInfoRepository userOperInfoRepository;
@Pointcut("@annotation(com.summo.log.Log)")
public void pointcut() {
// do nothing
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
Object result = null;
//默認操作對象為-1L
MethodSignature signature = (MethodSignature)joinPoint.getSignature();
Method method = signature.getMethod();
Log logAnnotation = method.getAnnotation(Log.class);
UserOperInfoDO log = new UserOperInfoDO();
if (logAnnotation != null) {
// 注解上的描述
log.setOperation(logAnnotation.methodDesc());
}
// 請求的類名
String className = joinPoint.getTarget().getClass().getName();
// 請求的方法名
String methodName = signature.getName();
log.setMethod(className + "." + methodName + "()");
// 請求的方法參數(shù)值
Object[] args = joinPoint.getArgs();
// 請求的方法參數(shù)名稱
LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
String[] paramNames = u.getParameterNames(method);
if (args != null && paramNames != null) {
StringBuilder params = new StringBuilder();
params = handleParams(params, args, Arrays.asList(paramNames));
log.setParams(params.toString());
}
log.setGmtCreate(Calendar.getInstance().getTime());
long beginTime = System.currentTimeMillis();
// 執(zhí)行方法
result = joinPoint.proceed();
// 執(zhí)行時長(毫秒)
long time = System.currentTimeMillis() - beginTime;
HttpServletRequest request = HttpContextUtil.getHttpServletRequest();
// 設(shè)置 IP 地址
String ip = IPUtil.getIpAddr(request);
log.setIp(ip);
log.setTime(time);
//保存操作記錄到數(shù)據(jù)庫中
userOperInfoRepository.save(log);
return result;
}
/**
* 參數(shù)打印合理化
*
* @param params 參數(shù)字符串
* @param args 參數(shù)列表
* @param paramNames 參數(shù)名
* @return
*/
private StringBuilder handleParams(StringBuilder params, Object[] args, List paramNames) {
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Map) {
Set set = ((Map)args[i]).keySet();
List<Object> list = new ArrayList<>();
List<Object> paramList = new ArrayList<>();
for (Object key : set) {
list.add(((Map)args[i]).get(key));
paramList.add(key);
}
return handleParams(params, list.toArray(), paramList);
} else {
if (args[i] instanceof Serializable) {
Class<?> aClass = args[i].getClass();
try {
aClass.getDeclaredMethod("toString", new Class[] {null});
// 如果不拋出 NoSuchMethodException 異常則存在 toString 方法 ,安全的 writeValueAsString ,否則 走 Object的
// toString方法
params.append(" ").append(paramNames.get(i)).append(": ").append(
JSONObject.toJSONString(args[i]));
} catch (NoSuchMethodException e) {
params.append(" ").append(paramNames.get(i)).append(": ").append(
JSONObject.toJSONString(args[i].toString()));
}
} else if (args[i] instanceof MultipartFile) {
MultipartFile file = (MultipartFile)args[i];
params.append(" ").append(paramNames.get(i)).append(": ").append(file.getName());
} else {
params.append(" ").append(paramNames.get(i)).append(": ").append(args[i]);
}
}
}
return params;
}
}
3、使用方法
在需要監(jiān)控的接口方法上加上@Log注解
@PostMapping("/saveRel")
@Log(methodDesc = "添加記錄")
public Boolean saveRel(@RequestBody SaveRelReq saveRelReq) {
return userRoleRelService.saveRel(saveRelReq);
}
@DeleteMapping("/delRel")
@Log(methodDesc = "刪除記錄")
public Boolean delRel(Long relId) {
return userRoleRelService.delRel(relId);
}
調(diào)用一下測試功能

數(shù)據(jù)庫中保存的記錄
[圖片上傳失敗...(image-4c12cf-1715226275211)]
這里可以看到已經(jīng)有記錄保存在數(shù)據(jù)庫中了,包括兩次添加操作、一次刪除操作,并且記錄了操作人的IP地址(這里我使用的是localhost所以IP是127.0.0.1)和操作時間。但是這里有一個問題:沒有記錄操作人的ID,也即creator_id字段為空,如果不知道這條記錄是誰的,那這個功能就沒有意義了,所以在方法三我將會說一下如何記錄每一行數(shù)據(jù)的創(chuàng)建者和修改者。
方法三、記錄每一行數(shù)據(jù)的創(chuàng)建者和修改者
這個功能的實現(xiàn)需要用到一個非常關(guān)鍵的東西:用戶上下文。如何實現(xiàn)請看:《優(yōu)化接口設(shè)計的思路》系列:第二篇—接口用戶上下文的設(shè)計與實現(xiàn)。
那么現(xiàn)在假設(shè)我已經(jīng)有了GlobalUserContext.getUserContext()方法可以獲取到用戶上下文信息,如何使用呢?
方法二沒有記錄操作人的ID,現(xiàn)在可以可以通過下面這種方法獲取當前操作人的ID:
log.setCreatorId(GlobalUserContext.getUserContext().getUserId());
但是?。?!我這里的標題是:記錄每一行數(shù)據(jù)的創(chuàng)建者和修改者,可不僅僅是只操作user_oper_log的每一行數(shù)據(jù),而是系統(tǒng)中的每一張表的每一行數(shù)據(jù)!那現(xiàn)在問題來了,如何實現(xiàn)這個需求?
最笨的辦法就是在每個新增、更新的代碼下都加上setCreatorId和setModifierId這些代碼,實現(xiàn)是可以實現(xiàn),但是感覺太low了,所以我這里提供一個思路和一個例子來優(yōu)化這些代碼。
1、統(tǒng)一字段名和類型
在每張表中都加入gmt_create(datetime 創(chuàng)建時間)、gmt_modified(datetime 更新時間)、creator_id(bigint 創(chuàng)建人ID)、modifier_id(bigint 更新人ID),我們將所有表中的這些輔助字段統(tǒng)一命名、統(tǒng)一類型,這樣給我們統(tǒng)一處理提供了基礎(chǔ)。
2、將這些字段集成到一個抽象類中
這樣做的好處有兩個:
- 其他表的DO類繼承這個抽象類,那么DO中就不需要再定義以上4個字段
- 統(tǒng)一處理的類只有抽象類一個了
tips:非常建議使用mybatis-plus來實現(xiàn)這個功能,maven依賴如下:
<!-- mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
<version>3.3.2</version>
</dependency>
類名定義和代碼如下
AbstractBaseDO.java
package com.summo.entity;
import java.io.Serializable;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class AbstractBaseDO<T extends Model<T>> extends Model<T> implements Serializable {
/**
* 創(chuàng)建時間
*/
@TableField(fill = FieldFill.INSERT)
private Date gmtCreate;
/**
* 修改時間
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date gmtModified;
/**
* 創(chuàng)建人ID
*/
@TableField(fill = FieldFill.INSERT)
private Long creatorId;
/**
* 修改人ID
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long modifierId;
}
3、使用mybatis-plus的MetaObjectHandler全局攔截insert和update操作
自定義MetaObjectHandlerConfig繼承MetaObjectHandler,代碼如下
MetaObjectHandlerConfig.java
package com.summo.entity;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
@Configuration
public class MetaObjectHandlerConfig implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
}
@Override
public void updateFill(MetaObject metaObject) {
}
}
邏輯補全的代碼如下
package com.summo.entity;
import java.util.Calendar;
import java.util.Date;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.summo.context.GlobalUserContext;
import com.summo.context.UserContext;
import org.apache.ibatis.reflection.MetaObject;
@Configuration
public class MetaObjectHandlerConfig implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
//獲取用戶上下文
UserContext userContext = GlobalUserContext.getUserContext();
//獲取創(chuàng)建時間
Date date = Calendar.getInstance().getTime();
//設(shè)置gmtCreate
this.fillStrategy(metaObject, "gmtCreate", date);
//設(shè)置gmtModified
this.fillStrategy(metaObject, "gmtModified", date);
//設(shè)置creatorId
this.fillStrategy(metaObject, "creatorId", userContext.getUserId());
//設(shè)置modifierId
this.fillStrategy(metaObject, "modifierId", userContext.getUserId());
}
@Override
public void updateFill(MetaObject metaObject) {
//獲取用戶上下文
UserContext userContext = GlobalUserContext.getUserContext();
//獲取更新時間
Date date = Calendar.getInstance().getTime();
//更新操作修改gmtModified
this.setFieldValByName("gmtModified", date, metaObject);
//更新操作修改modifierId
this.setFieldValByName("modifierId", userContext.getUserId(), metaObject);
}
}