ホームページ  >  記事  >  Java  >  Java SpringBoot プロジェクトで操作ログをエレガントに実装するにはどうすればよいですか?

Java SpringBoot プロジェクトで操作ログをエレガントに実装するにはどうすればよいですか?

WBOY
WBOY転載
2023-04-22 12:55:171065ブラウズ

    1. AOP とは何ですか?

    AOP (Aspect-Oriented Programming: アスペクト指向プログラミング)、AOP について言えば、Spring フレームワークを学習したことのあるほとんどの人は、それが Spring の 3 つの中心的なアイデア (IOC: Inversion of Control) の 1 つであることを知っています。 、DI: 依存性注入、AOP: アスペクト指向プログラミング)。ビジネスには関係しないが、一般的にビジネス モジュールによって呼び出されるロジックや責任 (トランザクション処理、ログ管理、権限制御など) をカプセル化して、システム内のコードの重複を減らし、負荷を減らすことができます。モジュール間の結合が容易になり、将来の拡張性と保守性にも役立ちます。

    2.AOP は何をしましたか?

    簡単に言うと、AOP は主に 3 つのことを行います:

    • #1. どこに介入するか、つまり、ビジネス以外の部分に介入するロギングなどのコードが配置されている どの業務コードが実行されるか。

    • 2. ビジネス コードの実行前または実行後、いつ割り込むか。

    • 3. 権限の確認、ログ記録など、スイッチイン後に行うべきこと。

    # 絵で理解できます:

    Java SpringBoot プロジェクトで操作ログをエレガントに実装するにはどうすればよいですか?

    絵の中心となる用語 説明:

    • Pointcut: Pointcut は、ビジネス コードのどこに切り込む (つまり、アスペクトに織り込む) かを決定します。ポイントカットは、実行モードと注釈モードに分かれています。実行モード: パス式を使用して、アスペクトに組み込むクラスを指定できます 注釈モード: アノテーションで変更されたどのコードをアスペクトに組み込むかを指定できます。

    • アドバイス: 処理、処理タイミングや処理内容など。コンテンツの処理とは、アクセス許可の確認やログの記録など、何らかの処理を行うことを意味します。処理タイミングとは、処理内容がいつ実行されるかを指し、前処理(つまり業務コードが実行される前)、後処理(業務コードが実行された後)などに分けられます。

    • アスペクト: アスペクト、つまりポイントカットとアドバイス。

    • ジョイント ポイント:ジョイント ポイントは、プログラムの実行ポイントです。たとえば、メソッドの実行や例外の処理などです。 Spring AOP では、ジョインポイントは常にメソッドの実行を表します。

    • Weaving: Weaving は、動的プロキシを通じてターゲット オブジェクト メソッドのコンテンツを処理するプロセスです。

    3. 実装手順

    (1) アノテーション @Log をカスタマイズする (2) アスペクト クラスを作成し、@Log アノテーションが付けられたメソッドをインターセプトするカット ポイントを設定します. 渡されたパラメータをインターセプトし、ログを記録します (3) インターフェイスで @Log をマークします。

    具体的な実装手順は次のとおりです:

    1. AOP 依存関係を追加します

    コードは次のとおりです (例):

     <dependency>
       	<groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>

    2. ログの注釈をカスタマイズする

    ログでは通常、注釈タイプのポイントカット式が使用されます。 create ログ アノテーション Spring コンテナがこのアノテーションを持つメソッドをスキャンすると、そのアノテーションが強化されます。

    コードは次のとおりです (例):

    @Target({ ElementType.PARAMETER, ElementType.METHOD }) // 注解放置的目标位置,PARAMETER: 可用在参数上  METHOD:可用在方法级别上
    @Retention(RetentionPolicy.RUNTIME)    // 指明修饰的注解的生存周期  RUNTIME:运行级别保留
    @Documented
    public @interface Log {
    
        /**
         * 模块
         */
        String title() default "";
    
        /**
         * 功能
         */
        public BusinessType businessType() default BusinessType.OTHER;
    
        /**
         * 是否保存请求的参数
         */
        public boolean isSaveRequestData() default true;
    
        /**
         * 是否保存响应的参数
         */
        public boolean isSaveResponseData() default true;
    }

    3. アスペクト宣言

    アスペクトクラスを宣言し、Spring コンテナに渡します。管理用。

    コードは次のとおりです (例):

    @Aspect
    @Component
    @Slf4j
    public class LogAspect {
        @Autowired
        private IXlOperLogService operLogService;
        /**
         * 处理完请求后执行
         * @param joinPoint 切点
         */
        @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
        public void doAfterReturnibng(JoinPoint joinPoint, Log controllerLog, Object jsonResult) {
            handleLog(joinPoint, controllerLog, null, jsonResult);
        }
        protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult) {
            try {
                // 获取当前的用户
                JwtUser loginUser = SecurityUtils.getLoginUser();
    
                // 日志记录
                XlOperLog operLog = new XlOperLog();
                operLog.setStatus(0);
                // 请求的IP地址
                String iP = ServletUtil.getClientIP(ServletUtils.getRequest());
                if ("0:0:0:0:0:0:0:1".equals(iP)) {
                    iP = "127.0.0.1";
                }
                operLog.setOperIp(iP);
                operLog.setOperUrl(ServletUtils.getRequest().getRequestURI());
                if (loginUser != null) {
                    operLog.setOperName(loginUser.getUsername());
                }
                if (e != null) {
                    operLog.setStatus(1);
                    operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
                }
                // 设置方法名称
                String className = joinPoint.getTarget().getClass().getName();
                String methodName = joinPoint.getSignature().getName();
                operLog.setMethod(className + "." + methodName + "()");
                operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
                operLog.setOperTime(new Date());
                // 处理设置注解上的参数
                getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
                // 保存数据库
                operLogService.save(operLog);
    
            } catch (Exception exp) {
                log.error("异常信息:{}", exp.getMessage());
                exp.printStackTrace();
            }
        }
        /**
         * 获取注解中对方法的描述信息 用于Controller层注解
         * @param log 日志
         * @param operLog 操作日志
         * @throws Exception
         */
        public void getControllerMethodDescription(JoinPoint joinPoint, Log log, XlOperLog operLog, Object jsonResult) throws Exception {
            // 设置操作业务类型
            operLog.setBusinessType(log.businessType().ordinal());
            // 设置标题
            operLog.setTitle(log.title());
            // 是否需要保存request,参数和值
            if (log.isSaveRequestData()) {
                // 设置参数的信息
                setRequestValue(joinPoint, operLog);
            }
            // 是否需要保存response,参数和值
            if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult)) {
                operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000));
            }
        }
        /**
         * 获取请求的参数,放到log中
         * @param operLog 操作日志
         * @throws Exception 异常
         */
        private void setRequestValue(JoinPoint joinPoint, XlOperLog operLog) throws Exception {
            String requsetMethod = operLog.getRequestMethod();
            if (HttpMethod.PUT.name().equals(requsetMethod) || HttpMethod.POST.name().equals(requsetMethod)) {
                String parsams = argsArrayToString(joinPoint.getArgs());
                operLog.setOperParam(StringUtils.substring(parsams,0,2000));
            } else {
                Map<?,?> paramsMap = (Map<?,?>) ServletUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
                operLog.setOperParam(StringUtils.substring(paramsMap.toString(),0,2000));
            }
        }
        /**
         * 参数拼装
         */
        private String argsArrayToString(Object[] paramsArray) {
            String params = "";
            if (paramsArray != null && paramsArray.length > 0) {
                for (Object object : paramsArray) {
                    // 不为空 并且是不需要过滤的 对象
                    if (StringUtils.isNotNull(object) && !isFilterObject(object)) {
                        Object jsonObj = JSON.toJSON(object);
                        params += jsonObj.toString() + " ";
                    }
                }
            }
            return params.trim();
        }
        /**
         * 判断是否需要过滤的对象。
         * @param object 对象信息。
         * @return 如果是需要过滤的对象,则返回true;否则返回false。
         */
        @SuppressWarnings("rawtypes")
        public boolean isFilterObject(final Object object) {
            Class<?> clazz = object.getClass();
            if (clazz.isArray()) {
                return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
            } else if (Collection.class.isAssignableFrom(clazz)) {
                Collection collection = (Collection) object;
                for (Object value : collection) {
                    return value instanceof MultipartFile;
                }
            } else if (Map.class.isAssignableFrom(clazz)) {
                Map map = (Map) object;
                for (Object value : map.entrySet()) {
                    Map.Entry entry = (Map.Entry) value;
                    return entry.getValue() instanceof MultipartFile;
                }
            }
            return object instanceof MultipartFile || object instanceof HttpServletRequest
                    || object instanceof HttpServletResponse || object instanceof BindingResult;
        }
    }

    4. インターフェイスにマークを付けます

    操作が行われるカスタム アノテーションをマークします。ログを記録する必要がある インターフェイス上のコードは次のとおりです (例):

    	@Log(title = "代码生成", businessType = BusinessType.GENCODE)
        @ApiOperation(value = "批量生成代码")
        @GetMapping("/download/batch")
        public void batchGenCode(HttpServletResponse response, String tables) throws IOException {
            String[] tableNames = Convert.toStrArray(tables);
            byte[] data = genTableService.downloadCode(tableNames);
            genCode(response, data);
        }

    5. 実装の効果

    関連する操作が実行されると、ログが記録されます。いくつかの基本情報が記録され、データ テーブルに保存されます。

    Java SpringBoot プロジェクトで操作ログをエレガントに実装するにはどうすればよいですか?

    以上がJava SpringBoot プロジェクトで操作ログをエレガントに実装するにはどうすればよいですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

    声明:
    この記事はyisu.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。