接口访问日志切面
基本思路
对标注了接口访问日志注解的接口进行切面,可设置记录策略(访问即记录、出错时记录等)根据记录策略设置进行信息的记录。
接口访问日志表设计
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;
}
}
}
评论区