**应用场景:**当我们想要查看前端传的数据是否正确,后端的返回值是否符合前端要求时,就可以打印请求参数和响应参数出来看,这样就有利于前后端对接;
**重点说明:**结合使用 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; } }