SpringBoot AOP处理Web请求日志——访问接口的参数以及返回结果

 

准备工作

引入pom.xml依赖:

<!--引入Web模块-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--引入AOP依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

在完成了引入AOP依赖包后,一般来说并不需要去做其他配置。

实现一个简单请求处理:通过传入name参数和token参数,返回“hello xxx”的功能。

@ApiImplicitParams({
        @ApiImplicitParam(name = "token", value = "token", required = true, dataType = "header"),
        @ApiImplicitParam(name = "name", value = "名称", required = true, paramType = "query", dataType = "String")
})
@RequestMapping(value = "/hello", method = RequestMethod.POST, produces = "application/json;charset=UTF-8")
public String hello(HttpServletRequest request, @RequestParam(required = false) String name) {
     return "Hello " + name;
}

实现Web层的日志切面

  • 使用@Aspect注解将一个java类定义为切面类
  • 使用@Pointcut定义一个切入点,可以是一个规则表达式,比如下例中某个package下的所有函数,也可以是一个注解等。
  • 根据需要在切入点不同位置的切入内容
    • 使用@Before在切入点开始处切入内容
    • 使用@After在切入点结尾处切入内容
    • 使用@AfterReturning在切入点return内容之后切入内容(可以用来对处理返回值做一些加工处理)
    • 使用@Around在切入点前后切入内容,并自己控制何时执行切入点自身的内容
    • 使用@AfterThrowing用来处理当切入内容部分抛出异常之后的处理逻辑
@Component // 将对象交由spring进行管理
@Aspect // 代表此类为一个切面类
public class ControllerAopInterceptor {
    /**
     * 初始化日志打印
     */
    public static final Logger log = LoggerFactory.getLogger("visitLog");

    @Pointcut("execution(public * com.xhchen.www.*.controller.*.*.*(..))")
    public void privilege() {
    }

    @Around("privilege()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        String className = pjp.getTarget().getClass().getName(); // 获取类名
        String methodName = pjp.getSignature().getName(); // 获取执行的方法名称
        String[] parameterNamesArgs = ((MethodSignature) pjp.getSignature()).getParameterNames(); // 获取参数名称
        Object result = null; // 定义返回参数
        Object[] args = pjp.getArgs(); // 获取方法参数
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        // 请求的URL
        String requestURL = request.getRequestURL().toString();
        String ip = getIpAddr(request);

        StringBuffer paramsBuf = new StringBuffer();
        // 获取请求参数集合并进行遍历拼接
        for (int i = 0; i < args.length; i++) {
            if (paramsBuf.length() > 0) {
                paramsBuf.append("|");
            }
            paramsBuf.append(parameterNamesArgs[i]).append(" = ").append(args[i]);
        }
        StringBuffer headerBuf = new StringBuffer();
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String key = (String) headerNames.nextElement();
            String value = request.getHeader(key);
            if (headerBuf.length() > 0) {
                headerBuf.append("|");
            }
            headerBuf.append(key).append("=").append(value);
        }

        // 打印请求参数参数
        long start = System.currentTimeMillis();// 记录开始时间
        log.info("请求| ip:{} | 请求接口:{} | 请求类:{} | 方法 :{} | 参数:{} | 请求header:{}|请求时间 :{}", ip, requestURL, className, methodName, paramsBuf.toString(), headerBuf.toString(), start);
        result = pjp.proceed();// 执行目标方法
        // 获取执行完的时间 打印返回报文
        log.info("返回| 请求接口:{}| 方法 :{} | 请求时间:{} | 处理时间:{} 毫秒 | 返回结果 :{}", requestURL, methodName, start, (System.currentTimeMillis() - start), result);
        return result;
    }

    /**
     * @Description: 获取ip
     */
    public String getIpAddr(HttpServletRequest request) {
        String ipAddress = null;
        ipAddress = request.getHeader("x-forwarded-for");
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("Proxy-Client-IP");
        }
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
            ipAddress = request.getRemoteAddr();
        }
        // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
        if (ipAddress != null && ipAddress.length() > 15) { // "***.***.***.***".length()
            // = 15
            if (ipAddress.indexOf(",") > 0) {
                ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
            }
        }
        // 或者这样也行,对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
        return ipAddress;
    }
}

通过运行程序并访问:http://localhost:8080/swagger-ui.html,通过swagger访问接口可以获得下面的日志输出:

: 请求| ip:0:0:0:0:0:0:0:1 | 请求接口:http://localhost:8080/hello | 请求类:com.xhchen.www.log.controller.back.HelloController | 方法 :hello | 参数:request = org.apache.catalina.connector.RequestFacade@60de57ae|name = log | 请求header:host=localhost:8080|connection=keep-alive|content-length=20|accept=application/json;charset=UTF-8|origin=http://localhost:8080|x-requested-with=XMLHttpRequest|request-origion=SwaggerBootstrapUi|user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36|content-type=application/x-www-form-urlencoded;charset=UTF-8|referer=http://localhost:8080/swagger-ui.html|accept-encoding=gzip, deflate, br|accept-language=zh-CN,zh;q=0.9|请求时间 :1563272621249
: 返回| 请求接口:http://localhost:8080/hello| 方法 :hello | 请求时间:1563272621249 | 处理时间:12 毫秒 | 返回结果 :Hello log

 

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,针对您的问题,我可以给您一些简单的解释。 SpringBoot中的AOP(面向切面编程)可以帮助我们对方法进行拦截和增强,这样我们就可以在方法执行前后进行一些自己想要的操作,比如接口请求数据解密和返回数据加密。 具体来说,我们可以通过定义一个切面类,在其中定义一个前置通知和一个后置通知。前置通知可以在方法执行前进行解密操作,后置通知可以在方法执行后进行加密操作。 下面是一个简单的示例代码: ```java @Aspect @Component public class EncryptAspect { @Autowired private EncryptService encryptService; @Pointcut("execution(public * com.example.controller.*.*(..))") public void encrypt() {} @Before("encrypt()") public void doEncrypt(JoinPoint joinPoint) { Object[] args = joinPoint.getArgs(); for (Object arg : args) { if (arg instanceof String) { String decrypted = encryptService.decrypt((String) arg); // 将解密后的数据重新设置到参数中 // ... } } } @AfterReturning(value = "encrypt()", returning = "result") public void doDecrypt(JoinPoint joinPoint, Object result) { if (result instanceof String) { String encrypted = encryptService.encrypt((String) result); // 将加密后的数据返回 // ... } } } ``` 在上面的示例代码中,我们定义了一个切面类`EncryptAspect`,其中通过`@Pointcut`注解定义了需要拦截的方法,即`com.example.controller`包下的所有方法。在`doEncrypt`方法中,我们可以获取到方法的参数,并进行解密操作;在`doDecrypt`方法中,我们可以获取到方法的返回值,并进行加密操作。 需要注意的是,上面的示例代码中的`EncryptService`是一个加解密服务的接口,具体的加解密实现可以根据自己的需求进行编写。 希望以上解释可以帮助到您。如果还有其他问题,欢迎随时提出。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值