diff --git a/pom.xml b/pom.xml index 263b13c..73edb9b 100644 --- a/pom.xml +++ b/pom.xml @@ -48,6 +48,10 @@ mybatis-spring-boot-starter 3.0.3 + + org.springframework.boot + spring-boot-starter-aop + com.mysql mysql-connector-j diff --git a/src/main/java/asia/yulinling/workflow/aspectj/AopLog.java b/src/main/java/asia/yulinling/workflow/aspectj/AopLog.java new file mode 100644 index 0000000..a4c1d36 --- /dev/null +++ b/src/main/java/asia/yulinling/workflow/aspectj/AopLog.java @@ -0,0 +1,185 @@ +package asia.yulinling.workflow.aspectj; + +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.http.useragent.UserAgent; +import cn.hutool.http.useragent.UserAgentUtil; +import cn.hutool.json.JSONUtil; +import cn.hutool.log.Log; +import jakarta.servlet.http.HttpServletRequest; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +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.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + *

+ * 使用 aop 切面记录请求日志信息 + *

+ * + * @author yulinling + * @since 2025/6/5 + */ +@Aspect +@Component +@Slf4j +public class AopLog { + /** + * 切入点 + */ + @Pointcut("execution(public * asia.yulinling.workflow.controller.*Controller.*(..))") + public void log() { + + } + + /** + * 环绕操作 + * + * @param joinPoint 切入点 + * @return 原方法返回值 + * @throws Throwable 异常信息 + */ + @Around("log()") + public Object aroundLog(ProceedingJoinPoint joinPoint) throws Throwable { + // 开始打印请求日志 + ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + HttpServletRequest request = Objects.requireNonNull(attributes).getRequest(); + + // 打印请求相关参数 + long startTime = System.currentTimeMillis(); + Object result = joinPoint.proceed(); + String header = request.getHeader("User-Agent"); + UserAgent ua = UserAgentUtil.parse(header); + final Log l = Log.builder() + .threadId(Thread.currentThread().getName()) + .threadName(Thread.currentThread().getName()) + .ip(getIp(request)) + .url(request.getRequestURI()) + .httpMethod(request.getMethod()) + .classMethod(joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName()) + .requestParams(getNameAndValue(joinPoint)) + .result(result) + .timeCost(System.currentTimeMillis() - startTime) + .userAgent(header) + .browser(ua.getBrowser().toString()) + .os(ua.getOs().toString()) + .build(); + + log.info("Request Log Info:{}", JSONUtil.toJsonStr(l)); + + return result; + } + + /** + * 获取方法参数名和参数值 + * @param joinPoint 切入点 + * @return 接口参数和参数值 + */ + private Map getNameAndValue(ProceedingJoinPoint joinPoint) { + final Signature signature = joinPoint.getSignature(); + MethodSignature methodSignature = (MethodSignature) signature; + final String[] names = methodSignature.getParameterNames(); + final Object[] args = joinPoint.getArgs(); + + if (ArrayUtil.isEmpty(names) || ArrayUtil.isEmpty(args)) { + return Collections.emptyMap(); + } + if (names.length != args.length) { + log.warn("{}方法参数名和参数数量不一致", methodSignature.getName()); + return Collections.emptyMap(); + } + Map map = new HashMap<>(); + for (int i = 0; i < names.length; i++) { + map.put(names[i], args[i]); + } + return map; + } + + /** + * 获取ip地址 + * @param request 请求体 + * @return 返回ip地址 + */ + public static String getIp(HttpServletRequest request) { + String ip = request.getHeader("X-Forwarded-For"); + if (isInvalidIp(ip)) { + ip = request.getHeader("Proxy-Client-IP"); + } + if (isInvalidIp(ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (isInvalidIp(ip)) { + ip = request.getHeader("HTTP_CLIENT_IP"); + } + if (isInvalidIp(ip)) { + ip = request.getRemoteAddr(); + } + + // 处理多级代理 + if (ip != null && ip.contains(",")) { + ip = ip.split(",")[0].trim(); + } + + // 本地地址处理 + if ("127.0.0.1".equals(ip) || "0:0:0:0:0:0:0:1".equals(ip)) { + try { + ip = InetAddress.getLocalHost().getHostAddress(); + } catch (UnknownHostException e) { + log.error("获取本地 IP 失败", e); + } + } + + return ip; + } + + private static boolean isInvalidIp(String ip) { + return ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip); + } + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + static class Log { + // 线程id + private String threadId; + // 线程名称 + private String threadName; + // ip + private String ip; + // url + private String url; + // http方法 GET POST PUT DELETE PATCH + private String httpMethod; + // 类方法 + private String classMethod; + // 请求参数 + private Object requestParams; + // 返回参数 + private Object result; + // 接口耗时 + private Long timeCost; + // 操作系统 + private String os; + // 浏览器 + private String browser; + // user-agent + private String userAgent; + } + +} diff --git a/src/main/java/asia/yulinling/workflow/controller/TestController.java b/src/main/java/asia/yulinling/workflow/controller/TestController.java new file mode 100644 index 0000000..0543736 --- /dev/null +++ b/src/main/java/asia/yulinling/workflow/controller/TestController.java @@ -0,0 +1,46 @@ +package asia.yulinling.workflow.controller; + +import cn.hutool.core.lang.Dict; +import cn.hutool.json.JSONUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +/** + *

+ * 测试aop + *

+ * + * @author yulinling + * @since 2025/6/5 + */ +@Slf4j +@RestController +public class TestController { + + /** + * 测试方法 GET + * @param who 测试参数 + * @return {@link Dict} + */ + @GetMapping("/test") + public Dict test(String who) { + return Dict.create().set("who", who); + } + + /** + * 测试方法 POST + * @param map 请求的json参数 + * @return {@link Dict} + */ + @PostMapping("/testJson") + public Dict testJson(@RequestBody Map map) { + final String jsonStr = JSONUtil.toJsonStr(map); + log.info(jsonStr); + return Dict.create().set("json", jsonStr); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 97ee4a3..95dfd64 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,3 +1,6 @@ +# ???? +server.port=8080 +server.servlet.context-path=/demo # mysql?? spring.datasource.url=jdbc:mysql://122.152.201.90:9912/workflow?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8