指定接口,记录请求的日志。
接口日志的核心内容包括:请求方法,接口路径,请求参数等。
采用的方案是:AOP
+ 自定义注解
说明:
@ApiLog
,则此接口的请求所包含的信息,会被记录到日志;开关配置
,可以选择是否开启接口日志;消息队列
(比如:RocketMQ
),异步处理;将接口的日志发送到消息队列里,由专门的日志记录服务器去处理,比如写入专门的数据库。这样可以减少接口的同步处理的时间,避免客户端等待时间过长,提升总体性能。本文仅为示例,所以只做了最简单的日志打印。package com.example.core.log.annotation;
import java.lang.annotation.*;
/**
* 接口日志注解
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ApiLog {
}
package com.example.core.log.aspect;
import com.example.core.property.BaseFrameworkConfigProperties;
import com.example.core.util.JsonUtil;
import io.swagger.v3.oas.annotations.Operation;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Slf4j
@Aspect
@Order(20)
@Component
public class ApiLogAspect {
@Value("${spring.application.name:}")
private String applicationName;
private final BaseFrameworkConfigProperties properties;
public ApiLogAspect(BaseFrameworkConfigProperties properties) {
this.properties = properties;
}
// 定义一个切点:所有被 ApiLog 注解修饰的方法会织入advice
@Pointcut("@annotation(com.example.core.log.annotation.ApiLog)")
private void pointcut() {
}
// Before表示 advice() 将在目标方法执行前执行
@Before("pointcut()")
public void advice(JoinPoint joinPoint) {
if (!properties.getApiLog().isEnabled()) {
return;
}
log.info("\n-------------------- 接口日志,开始 --------------------");
log.info("applicationName:{}", applicationName);
// 获取请求信息
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
// 用户IP
String clientIp = request.getRemoteAddr();
log.info("clientIp:{}", clientIp);
// URL
String requestURL = request.getRequestURL().toString();
log.info("url:{}", requestURL);
// 请求方法
String requestMethod = request.getMethod();
log.info("requestMethod:{}", requestMethod);
// 接口路径
String path = request.getServletPath();
log.info("path:{}", path);
}
// 接口参数
Object[] args = joinPoint.getArgs();
// 获取有效的接口参数(排除 HttpServletRequest 和 HttpServletResponse,否则会导致接口卡死)
List<Object> validArgs = Stream.of(args).filter(this::isInclusiveArgument).collect(Collectors.toList());
log.info("args:{}", JsonUtil.toJson(validArgs));
// 方法签名
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
log.info("methodSignature:{}", methodSignature);
// 方法参数名称列表
String[] parameterNames = methodSignature.getParameterNames();
log.info("parameterNames:{}", JsonUtil.toJson(parameterNames));
// 获取接口的注解
Operation operation = methodSignature.getMethod().getAnnotation(Operation.class);
if (operation != null) {
// 接口概述
String summary = operation.summary();
log.info("summary:{}", summary);
// 接口描述
String description = operation.description();
log.info("description:{}", description);
}
log.info("\n-------------------- 接口日志,结束 --------------------\n");
}
/**
* 是需要包含的参数。
* 不需要包含的参数(会导致接口卡死):
* 1. HttpServletRequest
* 2. HttpServletResponse
*
* @param arg 参数对象
*/
private Boolean isInclusiveArgument(Object arg) {
return !(arg instanceof HttpServletRequest) && !(arg instanceof HttpServletResponse);
}
}
package com.example.core.property;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* BaseFramework 配置文件
*
* @author songguanxun
* 2019/08/27 15:40
* @since 1.0.0
*/
@Data
@Component
@ConfigurationProperties(prefix = "base-framework")
public class BaseFrameworkConfigProperties {
/**
* 接口日志配置
*/
private ApiLog apiLog = new ApiLog();
/**
* 接口日志配置
*/
@Data
public static class ApiLog {
/**
* 是否开启接口日志
*/
private boolean enabled = false;
}
}
# 自定义配置
base-framework:
api-log:
enabled: true
package com.example.web.exception.controller;
import com.example.core.log.annotation.ApiLog;
import com.example.core.model.PageQuery;
import com.example.web.exception.query.UserQuery;
import com.example.web.model.vo.UserVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@RestController
@RequestMapping("exception")
@Tag(name = "异常统一处理")
public class ExceptionController {
@ApiLog
@GetMapping(path = "users")
@Operation(summary = "查询用户列表", description = "测试:BindException。参数校验异常:Get请求,Query参数,以对象的形式接收。")
public List<UserVO> listUsers(@Valid UserQuery userQuery, PageQuery pageQuery,
HttpServletRequest request, HttpServletResponse response, HttpSession session) {
log.info("查询用户列表。userQuery={},pageQuery={}", userQuery, pageQuery);
String queryName = userQuery.getName();
String queryPhone = userQuery.getPhone();
return listMockUsers().stream().filter(user -> {
boolean isName = true;
boolean isPhone = true;
if (StringUtils.hasText(queryName)) {
isName = user.getName().contains(queryName);
}
if (StringUtils.hasText(queryPhone)) {
isPhone = user.getPhone().contains(queryPhone);
}
return isName && isPhone;
}).collect(Collectors.toList());
}
private List<UserVO> listMockUsers() {
List<UserVO> list = new ArrayList<>();
UserVO vo = new UserVO();
vo.setId("1234567890123456789");
vo.setName("张三");
vo.setPhone("18612345678");
vo.setEmail("[email protected]");
list.add(vo);
UserVO vo2 = new UserVO();
vo2.setId("1234567890123456781");
vo2.setName("李四");
vo2.setPhone("13412345678");
vo2.setEmail("[email protected]");
list.add(vo2);
return list;
}
}