「インターフェース設計を最適化するアイデア」シリーズ:第 3 回 ユーザーの呼び出しの痕跡をインターフェースに残す

ここに画像の説明を挿入します
シリーズ記事ナビゲーション
「インターフェイス設計の最適化のアイデア」 シリーズ:第 1 回 - インターフェイス パラメータの紆余曲折
「インターフェイス設計の最適化のアイデア」 シリーズ:第 2 回 - インターフェイス ユーザー コンテキストの設計と実装
「インターフェイス設計の最適化のアイデア」 》連載:第3回 ― インターフェースにユーザー呼び出しの痕跡を残す

序文

こんにちは、みんな!最前線のローレベルコードファーマーのSum Moです。普段はテクノロジー関連のことを勉強したり考えたりして記事にまとめるのが好きです。あくまで自分のレベルに限定したものです。文中に不適切な表現があった場合は、記事またはコードについて、お気軽に教えてください。

6 年間働いているベテランのプログラマーとして、私の仕事は主に、さまざまな管理バックエンドやアプレットを含むバックエンド Java ビジネス システムを開発することです。これらのプロジェクトでは、シングル/マルチテナント システムを設計し、多くのオープン プラットフォームとインターフェイスし、メッセージ センターなどのより複雑なアプリケーションに取り組んできましたが、幸いなことに、コード A のクラッシュによる資本の損失によるオンライン システムにはまだ遭遇したことがありません。損失。その理由は3つあり、1つ目は業務システム自体が複雑ではないこと、2つ目は常に大手メーカーのコード仕様に従い、開発段階ではできる限り仕様通りにコードを書いてきたこと、3つ目は年月が経ったことです。開発経験を積み、実践的なスキルを身につけたジャーニーマンになりました。

インターフェイス設計は、電流制限、権限、入出力パラメータ、側面などを含むシステム設計全体の非常に重要な部分です。優れたインターフェイスを設計すると、不要なトラブルを大幅に回避でき、システム全体の安定性と拡張性が向上します。インターフェースデザイン体験共有の第3回目として、ユーザー使用時の操作痕跡を残す方法について共有したいと思います。実際の開発では、ログを利用してユーザーの行動を記録したり、ユーザーの操作記録をデータベースに保存したりするなど、ユーザーの操作を記録するための対策を講じます。これらのトレースは、問題を迅速に特定して解決するのに役立ち、その後のデータ分析と最適化のための貴重な参照も提供します。

方法 1: インターフェイスのパラメータと結果をログ ファイルに出力する

ログ ファイルは、ユーザーの使用状況トレースを記録する最初の場所です。私は以前、SpringBoot プロジェクトで logback.xml を構成してシステム ログ出力を実現する方法に関する記事を書きました。興味のある学生はそれを読むことができます。
ここでは主に、すべてのインターフェイスの入力パラメータと出力パラメータを簡単に出力する方法について説明します。

1. aop監視インターフェースを使用する

依存関係は以下の通り

<!-- aspectj -->
<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.9.5</version>
</dependency>

アスペクトj が何なのかを知らない学生がいる場合は、私の記事「SpringBoot はアスペクトj を統合してアスペクト指向プログラミング (つまり AOP) を実装する」を読むことができます。

キーコードは以下の通りです

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();

        // 获取请求相关信息
        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;
                }
            }
        }
        // 获取调用方法相信
        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方法是调用实际所拦截的controller中的方法,这里的result为调用方法后的返回值
            Object result = joinPoint.proceed();
            long endTime = System.currentTimeMillis();
            //定义请求结束时的返回数据,包括调用时间、返回值结果等
            log.info("@http请求结束, {}#{}(), 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を追加します

インターフェイスへの呼び出しはすべて非同期であるため、QPS が起動すると、インターフェイスへの呼び出しは非常に混乱し、識別子がないと、どの戻り値がどのリクエストに属するかがわかりません。
このとき、リクエストを識別するために requestId (または TraceId) を追加する必要があります。

つまり、このコードは

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

... ... 
MDC.remove("requestId");

同時に requestId の出力も logback.xml に追加する必要があり、logback.xml では%X{requestId}MDC に追加されたトラバーサルを取得できます。
完全な logback.xml 構成ファイルは次のとおりです。

<configuration>
    <!-- 默认的一些配置 -->
    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    <!-- 定义应用名称,区分应用 -->
    <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>
        <!-- 设置 RollingPolicy 属性,用于配置文件大小限制,保留天数、文件名格式 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- 文件命名格式 -->
            <fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!-- 文件保留最大天数 -->
            <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>
        <!-- 设置 RollingPolicy 属性,用于配置文件大小限制,保留天数、文件名格式 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- 文件命名格式 -->
            <fileNamePattern>${LOG_PATH}/warn.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!-- 文件保留最大天数 -->
            <maxHistory>7</maxHistory>
            <!-- 文件大小限制 -->
            <maxFileSize>50MB</maxFileSize>
            <!-- 文件总大小 -->
            <totalSizeCap>500MB</totalSizeCap>
        </rollingPolicy>
        <!-- 日志过滤器,将WARN相关日志过滤出来 -->
        <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>
        <!-- 设置 RollingPolicy 属性,用于配置文件大小限制,保留天数、文件名格式 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!-- 文件命名格式 -->
            <fileNamePattern>${LOG_PATH}/error.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <!-- 文件保留最大天数 -->
            <maxHistory>7</maxHistory>
            <!-- 文件大小限制 -->
            <maxFileSize>50MB</maxFileSize>
            <!-- 文件总大小 -->
            <totalSizeCap>500MB</totalSizeCap>
        </rollingPolicy>
        <!-- 日志过滤器,将ERROR相关日志过滤出来 -->
        <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. インターフェース監視で遭遇するいくつかの落とし穴

戻り値のデータ量が多い場合は画面が更新されますので、戻り値の出力は行わないようにしてください。
ファイル アップロード インターフェイスは直接ハングアップするため、通常、アップロード インターフェイスは監視されません。

方法 2: リスクの高い操作をデータベースに保存する

方法 1 ではインターフェイスごとにログを記録できますが、これらのログはサーバー上にのみ存在し、サイズと時間の制限があり、有効期限が切れると消えます。このアプローチでは、すべてのリクエストまたは操作が同等に扱われ、リスクの高いリクエストには特別な扱いが与えられません。危険な作業によるリスクに対処するには、問題が発生したときに原因をすぐに特定できるように、危険な作業を永続化する必要があります。最も一般的な方法は、リスクの高い操作をデータベースに保存することです。
実装原則はやはりアスペクトを使用しますが、ここではアノテーション・アスペクトを使用します。具体的な方法は以下を参照してください。

1. リスク操作を保存する新しいログテーブルを作成します。

テーブル構造は次のとおりです。

テーブル作成ステートメントも投稿します。

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 '操作内容',
  `time` bigint DEFAULT NULL COMMENT '耗时',
  `method` text COMMENT '操作方法',
  `params` text COMMENT '参数内容',
  `ip` varchar(64) DEFAULT NULL COMMENT 'IP',
  `location` varchar(64) DEFAULT NULL COMMENT '操作地点',
  `response_code` varchar(32) DEFAULT NULL COMMENT '应答码',
  `response_text` text COMMENT '应答内容',
  `gmt_create` datetime DEFAULT NULL COMMENT '创建时间',
  `gmt_modified` datetime DEFAULT NULL COMMENT '更新时间',
  `creator_id` bigint DEFAULT NULL COMMENT '创建人',
  `modifier_id` bigint DEFAULT NULL COMMENT '更新人',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4  COMMENT='用户操作日志表';

SET FOREIGN_KEY_CHECKS = 1;

主要なフィールドは、操作方法、パラメータの内容、IP、操作場所、応答コード、応答内容、作成者などです。このうち、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 + "()");
        // 请求的方法参数值
        Object[] args = joinPoint.getArgs();
        // 请求的方法参数名称
        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();
        // 执行方法
        result = joinPoint.proceed();
        // 执行时长(毫秒)
        long time = System.currentTimeMillis() - beginTime;
        HttpServletRequest request = HttpContextUtil.getHttpServletRequest();
        // 设置 IP 地址
        String ip = IPUtil.getIpAddr(request);
        log.setIp(ip);
        log.setTime(time);

        //保存操作记录到数据库中
        userOperInfoRepository.save(log);
        return result;
    }

    /**
     * 参数打印合理化
     *
     * @param params     参数字符串
     * @param args       参数列表
     * @param paramNames 参数名
     * @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. 使用方法

監視する必要があるインターフェースメソッドに @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);
}

テスト関数を呼び出す

データベースに保存されたレコード

ここでは、2 つの追加操作と 1 つの削除操作を含むレコードがデータベースに保存されており、オペレーターの IP アドレス (ここではローカルホストを使用しているため IP は 127.0.0.1) と操作時間が記録されていることがわかります。 。ただし、ここで問題があります:オペレーターの ID が記録されていない、つまり、creator_id フィールドが空です。このレコードが誰に属しているかがわからない場合、この関数は意味がありません。そのため、方法 3 でその方法について説明します。データ行の作成者と変更者をそれぞれ記録します。

方法 3: データの各行の作成者と変更者を記録する

この関数の実装には、非常に重要なものであるユーザー コンテキストを使用する必要があります。実装方法については、「インターフェイス設計を最適化するアイデア」シリーズ: 第 2 回 - インターフェイス ユーザー コンテキストの設計と実装を参照してください。

さて、GlobalUserContext.getUserContext()ユーザーコンテキスト情報を取得するメソッドがすでにあると仮定すると、それをどのように使用すればよいでしょうか?
方法二オペレーターの ID は記録されませんが、次の方法で現在のオペレーターの ID を取得できるようになります。

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

しかし!ここでのタイトルは次のとおりです。データの各行の作成者と変更者を記録するということは、 user_oper_log 内のデータの各行を操作するだけではなく、システム内のすべてのテーブルのデータのすべての行を操作することを意味します。ここで問題は、この要件をどのように実現するかということです。
最も愚かな方法は、新しいコードと更新されたコードの下に setCreatorId コードと setModifierId コードを追加することです。これは実装できますが、あまりにも低すぎると感じられるため、これらのコードを最適化するためのアイデアと例を提供します。

1. フィールド名と種類を統一する

gmt_create (datetime 作成時刻)、gmt_modified (datetime 更新時刻)、creator_id (bigint 作成者 ID)、modifier_id (bigint 更新者 ID) を各テーブルに追加します。これらの補助フィールドにすべてのテーブルで均一に名前を付け、入力します。これにより、統合処理の基礎。

2. これらのフィールドを抽象クラスに統合します。

これを行うと次の 2 つの利点があります。

  • 他のテーブルの DO クラスがこの抽象クラスを継承する場合、DO で上記の 4 つのフィールドを定義する必要はありません。
  • 均一に処理できる唯一のクラスは抽象クラスです。

ヒント: この関数を実装するには、mybatis-plus を使用することを強くお勧めします。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 {
    
    

    /**
     * 创建时间
     */
    @TableField(fill = FieldFill.INSERT)
    private Date gmtCreate;

    /**
     * 修改时间
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date gmtModified;

    /**
     * 创建人ID
     */
    @TableField(fill = FieldFill.INSERT)
    private Long creatorId;

    /**
     * 修改人ID
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long modifierId;

}

3. mybatis-plus の MetaObjectHandler を使用して、挿入および更新操作をグローバルにインターセプトします。

カスタマイズされた 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();
        //获取创建时间
        Date date = Calendar.getInstance().getTime();
        //设置gmtCreate
        this.fillStrategy(metaObject, "gmtCreate", date);
        //设置gmtModified
        this.fillStrategy(metaObject, "gmtModified", date);
        //设置creatorId
        this.fillStrategy(metaObject, "creatorId", userContext.getUserId());
        //设置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);
    }
}

おすすめ

転載: blog.csdn.net/weixin_33005117/article/details/132992136