package com.tc.api.common; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONObject; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.*; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.net.Inet4Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.util.*; /** * @Description: Api控制层切面日志 */ @Aspect @Component @Slf4j public class ApiLogAspect { /** * 定义切点表达式,指定通知功能被应用的范围 */ @Pointcut("execution(public * com.tc.api..controller..*.*(..))") public void logRecord() { } @Before("logRecord()") public void doBefore(JoinPoint joinPoint) throws Throwable { } /**value切入点位置 * returning 自定义的变量,标识目标方法的返回值,自定义变量名必须和通知方法的形参一样 * 特点:在目标方法之后执行的,能够获取到目标方法的返回值,可以根据这个返回值做不同的处理 */ @AfterReturning(value = "logRecord()", returning = "ret") public void doAfterReturning(Object ret) throws Throwable { } /** * 通知包裹了目标方法,在目标方法调用之前和之后执行自定义的行为 * ProceedingJoinPoint切入点可以获取切入点方法上的名字、参数、注解和对象 * @param joinPoint * @return * @throws Throwable */ @Around("logRecord()") public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable { long startTime = System.currentTimeMillis(); //获取当前请求对象 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (attributes == null) { //controller内抛入事件会把切面也带过去执行 return joinPoint.proceed(); } HttpServletRequest request = attributes.getRequest(); HttpServletResponse response = attributes.getResponse(); // 请求ID String requestId = getRequestId(request); // 设置响应头 response.setHeader("requestId", requestId); response.setHeader("requestTime", String.valueOf(startTime)); //记录请求信息 ApiLogRecord logRecord = new ApiLogRecord(); Object result; try { Signature signature = joinPoint.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; Method method = methodSignature.getMethod(); logRecord.setRequestId(requestId); logRecord.setIp(getRemoteIP(request)); logRecord.setMethod(request.getMethod()); logRecord.setHeader(getHeader(request)); logRecord.setParameter(Arrays.toString(getParamArgs(joinPoint.getArgs()))); logRecord.setParameterBodyOrUrl(getParameter(method, joinPoint.getArgs())); logRecord.setStartTime(startTime); logRecord.setUri(request.getRequestURI()); //前面是前置通知,后面是后置通知 result = joinPoint.proceed(); logRecord.setResult(result); // 设置响应头-响应时间 response.setHeader("responseTime", String.valueOf(System.currentTimeMillis())); } catch (Exception e) { logRecord.setResult("error:" + e.getMessage()); throw e; } finally { long endTime = System.currentTimeMillis(); logRecord.setSpendTime((int) (endTime - startTime)); log.info("Api日志切面信息:{}", JSON.toJSONString(logRecord)); } return result; } private Object[] getParamArgs(Object[] objectAry) { if(objectAry == null || objectAry.length <= 0){ return new Object[0]; } Object[] newObjAry = new Object[objectAry.length]; for(int i=0; i<objectAry.length; i++){ Object obj = objectAry[i]; if(obj != null && obj instanceof MultipartFile){ //MultipartFile 太大 这里日志只搜集附件名 newObjAry[i] = ((MultipartFile) obj).getOriginalFilename(); } else { newObjAry[i] = obj; } } return newObjAry; } /** * 根据方法和传入的参数获取请求参数 */ private Object getParameter(Method method, Object[] args) { List<Object> argList = new ArrayList<>(); Parameter[] parameters = method.getParameters(); for (int i = 0; i < parameters.length; i++) { if(args[i] != null && args[i] instanceof MultipartFile){ //MultipartFile 转json异常,这里日志只搜集附件名 args[i] = ((MultipartFile)args[i]).getOriginalFilename(); } //将RequestBody注解修饰的参数作为请求参数 RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class); if (requestBody != null) { argList.add(args[i]); } //将RequestParam注解修饰的参数作为请求参数 RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class); if (requestParam != null) { Map<String, Object> map = new HashMap<>(); String key = parameters[i].getName(); if (!StringUtils.isEmpty(requestParam.value())) { key = requestParam.value(); } map.put(key, args[i]); argList.add(map); } } if (argList.size() == 0) { return null; } else if (argList.size() == 1) { return argList.get(0); } else { return argList; } } /** * 获取客户访问IP * @param request * @return */ private String getRemoteIP(HttpServletRequest request) { String ip = null; // X-Forwarded-For:Squid 服务代理 String ipAddresses = request.getHeader("X-Forwarded-For"); if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) { // Proxy-Client-IP:apache 服务代理 ipAddresses = request.getHeader("Proxy-Client-IP"); } if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) { // WL-Proxy-Client-IP:weblogic 服务代理 ipAddresses = request.getHeader("WL-Proxy-Client-IP"); } if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) { // HTTP_CLIENT_IP:有些代理服务器 ipAddresses = request.getHeader("HTTP_CLIENT_IP"); } if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) { // X-Real-IP:nginx服务代理 ipAddresses = request.getHeader("X-Real-IP"); } // 有些网络通过多层代理,那么获取到的ip就会有多个,一般都是通过逗号(,)分割开来,并且第一个ip为客户端的真实IP if (ipAddresses != null && ipAddresses.length() != 0) { ip = ipAddresses.split(",")[0]; } // 还是不能获取到,最后再通过request.getRemoteAddr();获取 if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) { ip = request.getRemoteAddr(); } //如果获取到的是127.0.0.1或0:0:0:0:0:0:0:1,就获取本地ip try { ip = getHostIP(); } catch (SocketException e) { log.error("获取本机IP异常!", e); } return ip; } /** * 获取本机IP * @return * @throws SocketException */ private String getHostIP() throws SocketException { Enumeration<NetworkInterface> allNetInterfaces = NetworkInterface.getNetworkInterfaces(); while (allNetInterfaces.hasMoreElements()) { NetworkInterface netInterface = (NetworkInterface) allNetInterfaces.nextElement(); // 去除回环接口,子接口,未运行接口 if (netInterface.isLoopback() || netInterface.isVirtual() || !netInterface.isUp()) { continue; } if (!netInterface.getDisplayName().contains("Intel") && !netInterface.getDisplayName().contains("Realtek")) { continue; } Enumeration<InetAddress> addresses = netInterface.getInetAddresses(); while (addresses.hasMoreElements()) { InetAddress ip = (InetAddress) addresses.nextElement(); if (ip != null && ip instanceof Inet4Address) { return ip.getHostAddress(); } } } return null; } /** * 获取请求ID * @param request * @return */ private String getRequestId(HttpServletRequest request) { String requestId = request.getHeader("requestId"); if (StringUtils.isEmpty(requestId)) { requestId = UUID.randomUUID().toString().replace("-", ""); } return requestId; } /** * 获取请求头 * @param request * @return */ private Object getHeader(HttpServletRequest request) { if (request.getHeaderNames() == null) { return new JSONObject(); } JSONObject header = new JSONObject(); Enumeration<String> headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()) { String name = headerNames.nextElement(); header.put(name, request.getHeader(name)); } return header; } }
====================================================
=======================================================
package com.tc.api.common; import lombok.Data; import java.io.Serializable; /** * @Description: Api控制层切面日志 */ @Data public class ApiLogRecord implements Serializable { /** * 操作描述 */ private String description; /** * 请求ID */ private String requestId; /** * 操作时间 */ private Long startTime; /** * 消耗时间 */ private Integer spendTime; /** * URI */ private String uri; /** * URL */ private String url; /** * 请求类型 */ private String method; /** * IP地址 */ private String ip; /** * 请求参数 */ private Object parameterBodyOrUrl; /** * 参数 */ private Object parameter; /** * 请求头 */ private Object header; /** * 请求返回的结果 */ private Object result; }