**应用场景:**当我们想要查看前端传的数据是否正确,后端的返回值是否符合前端要求时,就可以打印请求参数和响应参数出来看,这样就有利于前后端对接;
**重点说明:**结合使用 fastjson2 对请求对象和返回对象实现序列化打印,再配置相关过滤器,用来过滤敏感信息和无效的过长信息;
**实际原理:**在前端请求后端的是后,AOP 会最先起作用。你可以使用 AOP 的 @Before 注解来打印请求参数,@Around 注解打印返回参数,@Pointcut 注解来定义切点。
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.filter.SimplePropertyPreFilter;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.JoinPoint;
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.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
@Aspect
@Component
public class LogAspect {
private final static Logger LOG = LoggerFactory.getLogger(LogAspect.class);
// 定义一个切点
@Pointcut("execution(public * com.example.*.controller..*Controller.*(..))")
public void controllerPointcut() {
}
@Before("controllerPointcut()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
// 开始打印请求日志
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
Signature signature = joinPoint.getSignature();
String name = signature.getName();
// 打印前端请求信息
LOG.info("=== 开始 ===");
LOG.info("请求地址: {} {}", request.getRequestURL().toString(), request.getMethod());
LOG.info("类名方法: {}.{}", signature.getDeclaringTypeName(), name);
LOG.info("远程地址: {}", request.getRemoteAddr());
// 打印请求参数
Object[] args = joinPoint.getArgs();
Object[] arguments = new Object[args.length];
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof ServletRequest
|| args[i] instanceof ServletResponse
|| args[i] instanceof MultipartFile) {
continue;
}
arguments[i] = args[i];
}
// 当某些字段太敏感,或者是太长时,就不显示
String[] excludeProperties = {
"password", "file"};
SimplePropertyPreFilter filters = new SimplePropertyPreFilter();
for (String str : excludeProperties){
filters.getExcludes().add(str);
}
LOG.info("请求的参数: {}", JSON.toJSONString(arguments, filters));
}
@Around("controllerPointcut()")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = proceedingJoinPoint.proceed();
// 当某些字段太敏感,或者是太长时,就不显示
String[] excludeProperties = {
"password", "file"};
SimplePropertyPreFilter filters = new SimplePropertyPreFilter();
for (String str : excludeProperties){
filters.getExcludes().add(str);
}
LOG.info("返回的结果: {}", JSON.toJSONString(result, filters));
LOG.info("=== 结束时,总耗时:{} ms ===", System.currentTimeMillis() - startTime);
return result;
}
/**
* 使用 Nginx 进行反向代理,这个方法主要是用来获取远程 IP
* @param request
* @return
*/
public String getRemoteIp(HttpServletRequest request) {
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}