目 录CONTENT

文章目录

接口访问日志切面

Jinty
2024-07-20 / 0 评论 / 0 点赞 / 11 阅读 / 7397 字

接口访问日志切面

基本思路

对标注了接口访问日志注解的接口进行切面,可设置记录策略(访问即记录、出错时记录等)根据记录策略设置进行信息的记录。

接口访问日志表设计

DROP TABLE IF EXISTS `sys_api_access_log`;
CREATE TABLE `sys_api_access_log`  (
  `id` bigint NOT NULL COMMENT '主键ID',
  `create_by` bigint NULL DEFAULT NULL COMMENT '创建者',
  `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
  `update_by` bigint NULL DEFAULT NULL COMMENT '最后更新者',
  `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
  `api_endpoint` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '访问的接口端点',
  `request_method` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '请求方法,如GET, POST, PUT, DELETE等',
  `request_params` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '请求参数',
  `request_body` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '请求体,存储请求的具体内容',
  `response_status_code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '响应状态码,记录接口返回的状态码',
  `response_body` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '响应体,存储接口返回的具体内容',
  `err_msg` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '错误消息',
  `access_time` datetime NULL DEFAULT NULL COMMENT '访问时间',
  `client_ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '访问客户端的IP地址',
  `account_id` int NULL DEFAULT NULL COMMENT '访问账号ID,用来关联访问用户的信息',
  `process_interval` double NULL DEFAULT NULL COMMENT '处理耗时',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '接口访问日志' ROW_FORMAT = Dynamic;

接口访问日志注解定义

@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface ApiLog {
    /**
     * 分组
     */
    @AliasFor(value = "group")
    String value() default "";

    /**
     * 分组
     */
    @AliasFor(value = "value")
    String group() default "";

    /**
     * 策略:ACCESS-所有访问,ERROR-仅错误
     */
    ApiLogPolicy policy() default ApiLogPolicy.ACCESS;

    /**
     * 记录请求参数
     */
    boolean isSaveRequestParams() default false;

    /**
     * 是否记录响应内容
     */
    boolean isSaveResponseBody() default false;

    /**
     * 记录请求参数、响应内容支持的最大长度
     */
    int maxSaveLength() default -1;
}

public enum ApiLogPolicy {
    ACCESS, ERROR
}

接口访问日志切面定义

package cn.com.jcoo.log;

import cn.com.jcoo.api.define.ApiRet;
import cn.com.jcoo.enums.ApiCode;
import cn.com.jcoo.exception.ISysError;
import cn.com.jcoo.system.entity.ApiAccessLog;
import cn.com.jcoo.system.service.ApiAccessLogService;
import cn.com.jcoo.util.RequestUtils;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.date.DateTime;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
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.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.math.RoundingMode;

@Aspect
@RequiredArgsConstructor
@Component
public class ApiLogAspect {

    private final ApiAccessLogService apiAccessLogService;

    private final ObjectMapper jsonMapper = new ObjectMapper();

    @Pointcut("@annotation(apiLog)")
    public void apiLogPointcut(ApiLog apiLog) {
    }

    @Around("apiLogPointcut(apiLog)")
    public Object aroundAdvice(ProceedingJoinPoint joinPoint, ApiLog apiLog) throws Throwable {
        long start = System.currentTimeMillis();
        ApiAccessLog apiAccessLog = new ApiAccessLog();
        try {
            HttpServletRequest request = RequestUtils.getRequest();
            initRequestInfo(apiAccessLog, apiLog, request);
            Object result = joinPoint.proceed();
            initResponseInfo(apiAccessLog, apiLog, result);
            return result;
        } catch (Throwable e) {
            initResponseInfo(apiAccessLog, apiLog, e);
            throw e;
        } finally {
            long end = System.currentTimeMillis();
            BigDecimal processInterval = BigDecimal.valueOf(end - start).divide(BigDecimal.valueOf(1000L), 2, RoundingMode.HALF_UP);
            apiAccessLog.setProcessInterval(processInterval);
            apiAccessLogService.saveLog(apiAccessLog);
        }
    }

    private void initRequestInfo(ApiAccessLog apiAccessLog, ApiLog apiLog, HttpServletRequest request) {

        apiAccessLog.setAccessTime(new DateTime());
        apiAccessLog.setApiEndpoint(request.getRequestURI());
        apiAccessLog.setRequestMethod(request.getMethod());
        if (apiLog.isSaveRequestParams() && apiLog.maxSaveLength() != 0) {
            apiAccessLog.setRequestParams("TODO");
        }
        apiAccessLog.setClientIp(RequestUtils.getRequestIP(request));
        if (StpUtil.isLogin()) {
            apiAccessLog.setAccountId((Long) StpUtil.getLoginId());
        }
    }

    @SneakyThrows
    private void initResponseInfo(ApiAccessLog apiAccessLog, ApiLog apiLog, Object result) {
        if (apiLog.isSaveResponseBody() && apiLog.maxSaveLength() != 0) {
            String s = jsonMapper.writeValueAsString(result);
            if (apiLog.maxSaveLength() > 0 && s.length() > apiLog.maxSaveLength()) {
                s = s.substring(0, apiLog.maxSaveLength());
            }
            apiAccessLog.setResponseBody(s);
        }
        if (result instanceof ApiRet) {
            ApiRet<?> apiRet = (ApiRet<?>) result;
            int code = apiRet.getCode();
            apiAccessLog.setResponseStatusCode(String.valueOf(code));
            if (code != ApiCode.OK.code()) {
                apiAccessLog.setErrMsg(apiRet.getMsg());
            }
        } else {
            apiAccessLog.setResponseStatusCode("0");
        }
    }

    private void initResponseInfo(ApiAccessLog apiAccessLog, ApiLog apiLog, Throwable e) {
        apiAccessLog.setErrMsg(e.getMessage());
        if (e instanceof ISysError) {
            ISysError sysError = (ISysError) e;
            int code = sysError.getCode();
            apiAccessLog.setResponseStatusCode(String.valueOf(code));
            String message = sysError.getMessage();
            if (apiLog.maxSaveLength() > 0 && message.length() > apiLog.maxSaveLength()) {
                message = message.substring(0, apiLog.maxSaveLength());
            }
            apiAccessLog.setErrMsg(message);
            throw sysError;
        }
    }
}

0

评论区