package java456.com.config;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
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 javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Field;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
/**
* AOP切面:记录Controller层请求的URL、IP、参数、返回值等信息
*/
@Aspect
@Component
public class RequestAspect {
// 对接logback的日志对象(建议用INFO级别,输出到你配置的info日志文件)
private static final Logger logger = LoggerFactory.getLogger(RequestAspect.class);
/**
* execution(public * java456.com.controller.xcx_api.API_XCX_Order_Controller.*(..))
* public 匹配公有方法(如果去掉 public,会匹配所有访问修饰符的方法:私有 / 保护 / 公有)
* 第一个 * 匹配任意返回值类型(比如 String、Integer、void、Order 等,不管方法返回什么都匹配)
* java456.com.controller.xcx_api.API_XCX_Order_Controller 精准匹配这个指定类(只拦截这个类里的方法)
* 第二个 * 匹配这个类里的任意方法名(比如 createOrder、getOrder、cancelOrder 等,不管方法叫什么都匹配)
* (..) 匹配方法的任意参数(0 个参数、1 个参数、多个参数,不管参数类型是什么都匹配)
*
*
* 如果想 java456.com.controller包下面所有的控制器呢。
* @Pointcut("execution(public * java456.com.controller.**. *(..))")
*
*/
@Pointcut("execution(public * java456.com.controller.xcx_api.API_XCX_Order_Controller.*(..))")
public void log() {
}
/**
* 方法执行前:记录请求URL、IP、方法、参数等
*/
@Before("log()")
public void deBefore(JoinPoint joinPoint) {
try {
// 1. 获取HttpServletRequest对象(兼容异步请求,避免空指针)
ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (sra == null) {
logger.warn("【请求日志】无法获取HttpServletRequest(异步请求?)");
return;
}
HttpServletRequest request = sra.getRequest();
// 2. 解析核心请求信息
String url = request.getRequestURI(); // 请求URI(如/api/user/list)
String ip = getClientIp(request); // 真实客户端IP(修复原代码getRemoteHost的缺陷)
String method = request.getMethod(); // 请求方法(GET/POST等)
String classMethod = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName(); // 执行的方法名
Object[] args = joinPoint.getArgs(); // 方法参数
// 3. 解析方法参数为 "key=value" 格式的字符串
String argsStr = parseArgsToKeyValue(args);
// 4. 拼接日志(用logger输出,而非System.out,对接logback配置)
logger.info("【请求开始】URL:{} | IP:{} | 请求方法:{} | 执行方法:{} | 方法参数:{} | 用户信息:{}",
url, ip, method, classMethod, args, argsStr);
} catch (Exception e) {
logger.error("【请求日志】记录请求信息失败", e);
}
}
/**
* 方法执行后:记录请求结束(可选)
*/
@After("log()")
public void doAfter(JoinPoint joinPoint) {
try {
ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (sra != null) {
String url = sra.getRequest().getRequestURI();
logger.info("【请求结束】URL:{}", url);
}
} catch (Exception e) {
logger.error("【请求日志】记录请求结束信息失败", e);
}
}
/**
* 方法返回后:记录返回值
*/
@AfterReturning(returning = "result", pointcut = "log()")
public void doReturn(Object result) {
try {
ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (sra != null) {
String url = sra.getRequest().getRequestURI();
// 注意:如果返回值是大对象(如List<XXX>),建议只记录关键信息,避免日志过大
logger.info("【请求返回】URL:{} | 返回值:{}", url, result);
}
} catch (Exception e) {
logger.error("【请求日志】记录返回值失败", e);
}
}
// ---------------------- 辅助方法 ----------------------
/**
* 获取客户端真实IP(修复原代码getRemoteHost()只能拿到本机IP的问题)
*/
private String getClientIp(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr(); // 最后兜底(可能是网关IP)
}
// 处理多IP场景(X-Forwarded-For格式:192.168.1.1, 10.0.0.1)
return ip != null && ip.contains(",") ? ip.split(",")[0].trim() : ip;
}
/**
* 辅助方法:将方法参数解析为 "key1=value1 | key2=value2" 格式的字符串
* 支持基本类型、自定义对象(反射获取字段)、HttpServletRequest/Response等特殊对象过滤
*/
private String parseArgsToKeyValue(Object[] args) {
if (args == null || args.length == 0) {
return "无参数";
}
StringBuilder argsStr = new StringBuilder();
for (Object arg : args) {
if (arg == null) {
argsStr.append("null | ");
continue;
}
Class<?> argClass = arg.getClass();
// 过滤掉HttpServletRequest/Response等无意义的对象
if (arg instanceof HttpServletRequest || arg instanceof HttpServletResponse) {
continue;
}
// 处理基本类型/字符串(直接拼接)
if (argClass.isPrimitive() || arg instanceof String || arg instanceof Number || arg instanceof Boolean) {
argsStr.append(arg).append(" | ");
}
// 处理自定义对象(反射获取字段和值)
else {
try {
Field[] fields = argClass.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true); // 允许访问私有字段
String fieldName = field.getName();
Object fieldValue = field.get(arg);
argsStr.append(fieldName).append("=").append(fieldValue).append(" | ");
}
} catch (IllegalAccessException e) {
argsStr.append("解析对象失败:").append(argClass.getSimpleName()).append(" | ");
}
}
}
// 去掉最后一个多余的 " | "
if (argsStr.length() > 0) {
argsStr.delete(argsStr.length() - 3, argsStr.length());
}
return argsStr.toString();
}
}站长微信:xiaomao0055
站长QQ:14496453