java使用AOP切面获取请求日志并记录

前言:为了完整的记录外部接口请求记录,记录内容包括:请求参数,响应参数,请求耗时,请求IP地址,请求成功还是失败,请求方法名称,请求接口地址等重要信息。

1.引入maven

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-aop</artifactId>
     <version>1.4.1.RELEASE</version>
</dependency>

备注:此包是为了在aop中获取request对象而引入 

2.日志记录model如下

package com.tjair.log.model;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.Column;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;

@Table(name = "t_tjapi_log")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TjapiLog {
    @Id
    private Integer id;

    /**
     * 采购编码
     */
    @Column(name = "partnerId")
    private String partnerid;

    /**
     * 请求地址
     */
    private String url;

    /**
     * 接口描述
     */
    @Column(name = "api_name")
    private String apiName;

    /**
     * 请求时间
     */
    @Column(name = "create_time")
    private Date createTime;

    /**
     * 请求方式
     */
    private String method;

    /**
     * 请求参数
     */
    private String params;

    /**
     * 请求状态:1 成功 0 失败
     */
    private Integer status;

    /**
     * 请求IP
     */
    private String ip;

    /**
     * 请求来源地址
     */
    private String location;

    /**
     * 请求耗时
     */
    private Integer time;

    /**
     * 返回内容
     */
    private String response;
}

3.AOP切面如下

package com.tjair.tjapi.config;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.tjair.common.log.TjapiLogVo;
import com.tjair.common.util.IPUtil;
import com.tjair.tjapi.feign.LogFeignService;
import com.tjair.tjapi.util.HttpHelper;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
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.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Date;
import java.util.Objects;


/**
 * @description 拦截所有控制器的返回,记录响应报文
 * @author unhejing
 * @date 2021-03-17 上午11:52:26
 */
@Aspect
@Configuration
@Slf4j
public class LogAspect {

    @Autowired
    private LogFeignService logFeignService;

    public LogAspect() {
    }

    @Pointcut("execution(public * com.tjair.tjapi.controller.*.*(..))")
    public void pointCutMethod() {
    }

    // 声明环绕通知
    @Around("pointCutMethod()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        Long startTime = System.currentTimeMillis();
        ApiOperation apiOperation = ((MethodSignature)pjp.getSignature()).getMethod().getAnnotation(ApiOperation.class);
        // 获取request对象
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        HttpServletRequest request = sra.getRequest();
        Object ret = pjp.proceed();
        Long endTime = System.currentTimeMillis();
        TjapiLogVo tjapiLogVo = getTjapiLogVo(request,Objects.nonNull(apiOperation) ? apiOperation.value() : "",endTime-startTime,JSON.toJSONString(ret));
        // 添加日志
        addLog(tjapiLogVo);
        log.info("响应数据耗时{}:响应数据{}",endTime-startTime,JSON.toJSONString(ret));
        return ret;
    }

    public void addLog(TjapiLogVo tjapiLogVo) {
        log.info("添加日志:{}",JSON.toJSONString(tjapiLogVo));
        Long startTime = System.currentTimeMillis();
        logFeignService.addTjapiLog(tjapiLogVo);
        Long endtime = System.currentTimeMillis();
        log.info("添加日志耗时:{}",endtime-startTime);
    }


    private TjapiLogVo getTjapiLogVo(HttpServletRequest request,String apiName,Long time,String response) throws IOException {
        TjapiLogVo tjapiLogVo = new TjapiLogVo();
        String jsonBody = HttpHelper.getBodyString(request);
        log.info("请求参数:{}",jsonBody);
        JSONObject reqObj = Objects.nonNull(JSON.parseObject(jsonBody)) ? JSON.parseObject(jsonBody) : new JSONObject();
        JSONObject resObj = Objects.nonNull(JSON.parseObject(response)) ? JSON.parseObject(response) : new JSONObject();
        //设置请求参数
        tjapiLogVo.setParams(jsonBody);
        tjapiLogVo.setPartnerid(Objects.nonNull(reqObj.getString("partnerId")) ? reqObj.getString("partnerId") : "-1");
        // 设置IP地址
        tjapiLogVo.setIp(IPUtil.getIpAddr(request));
        // 设置位置
        tjapiLogVo.setLocation(IPUtil.getCityInfo(tjapiLogVo.getIp()));
        //设置请求方法,GET,POST...
        tjapiLogVo.setMethod(request.getMethod());
        //设置请求路径
        tjapiLogVo.setUrl(request.getRequestURI());
        // 设置请求方法名称
        tjapiLogVo.setApiName(apiName);
        // 设置创建时间
        tjapiLogVo.setCreateTime(new Date());
        // 设置请求状态
        Integer code = Objects.nonNull(resObj.getInteger("code")) ? resObj.getInteger("code") : 1;
        tjapiLogVo.setStatus(code);
        // 设置接口消耗时间
        tjapiLogVo.setTime(time.intValue());
        // 设置响应内容
        tjapiLogVo.setResponse(response);
        return tjapiLogVo;
    }
}

4.测试接口

package com.tjair.tjapi.controller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.tjair.common.service.RedisUtils;
import com.tjair.common.util.Result;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author jingyujie
 * @create 2021-03-16 下午2:39
 **/
@RestController
@RequestMapping("/test")
@Api(tags = "测试API控制器")
public class TestController {

    @Autowired
    private RedisUtils redisUtils;
    
    @PostMapping("/apiTest")
    @ApiOperation("接口测试")
    public Result apiTest(@RequestBody JSONObject obj){
        return Result.returnSuccess("接口已联通,请求入参:"+ JSON.toJSONString(obj));
    }

}

 

5.请求结果如下

备注说明:

1.LogFeignService是添加日志的service

2.IPUtil是获取IP和地址信息相关的工具类

3.HttpHelper是解析request中的请求参数的工具类

4.ApiOperation是获取接口的注解上面的接口描述

5.partnerId是我请求参数里面必传的用户编码

6.code是响应参数里面的状态码,用于判断接口是否请求成功

末尾附上工具类:

IPUtil.java

类中的/ip2region.db这个是一个地址库,可直接百度下载即可。

package com.tjair.common.util;

import org.apache.commons.io.FileUtils;
import org.lionsoul.ip2region.DataBlock;
import org.lionsoul.ip2region.DbConfig;
import org.lionsoul.ip2region.DbSearcher;
import org.lionsoul.ip2region.Util;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.lang.reflect.Method;

/**
 * ip操作相关工具类
 * @author jingyujie
 * @create 2020-06-24 下午2:11
 **/

public class IPUtil {

    public static String getCityInfo(String ip){
        try {
            //db
            String dbPath = IPUtil.class.getResource("/ip2region.db").getPath();
            System.out.println("路径:"+dbPath);
            File file = new File(dbPath);
            if (file.exists() == false) {
                System.out.println("Error: Invalid ip2region.db file");
                String tmpDir = System.getProperties().getProperty("java.io.tmpdir");
                dbPath = tmpDir + "ip.db";
                System.out.println(dbPath);
                file = new File(dbPath);
                FileUtils.copyInputStreamToFile(IPUtil.class.getClassLoader().getResourceAsStream("classpath:ip2region.db"), file);
            }

            //查询算法
            int algorithm = DbSearcher.BTREE_ALGORITHM; //B-tree
            //DbSearcher.BINARY_ALGORITHM //Binary
            //DbSearcher.MEMORY_ALGORITYM //Memory
            DbSearcher searcher = null;
            try {
                DbConfig config = new DbConfig();
                searcher = new DbSearcher(config, dbPath);

                //define the method
                Method method = null;
                switch (algorithm) {
                    case DbSearcher.BTREE_ALGORITHM:
                        method = searcher.getClass().getMethod("btreeSearch", String.class);
                        break;
                    case DbSearcher.BINARY_ALGORITHM:
                        method = searcher.getClass().getMethod("binarySearch", String.class);
                        break;
                    case DbSearcher.MEMORY_ALGORITYM:
                        method = searcher.getClass().getMethod("memorySearch", String.class);
                        break;
                }

                DataBlock dataBlock = null;
                if (Util.isIpAddress(ip) == false) {
                    System.out.println("Error: Invalid ip address");
                }

                dataBlock = (DataBlock) method.invoke(searcher, ip);

                return dataBlock.getRegion();

            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (searcher != null) {
                    searcher.close();
                }
            }
            return null;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static String getIpAddr(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        System.out.println("x-forwarded-for ip: " + ip);
        if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
            // 多次反向代理后会有多个ip值,第一个ip才是真实ip
            if( ip.indexOf(",")!=-1 ){
                ip = ip.split(",")[0];
            }
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
            System.out.println("Proxy-Client-IP ip: " + ip);
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
            System.out.println("WL-Proxy-Client-IP ip: " + ip);
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
            System.out.println("HTTP_CLIENT_IP ip: " + ip);
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
            System.out.println("HTTP_X_FORWARDED_FOR ip: " + ip);
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
            System.out.println("X-Real-IP ip: " + ip);
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
            System.out.println("getRemoteAddr ip: " + ip);
        }
        System.out.println("获取客户端ip: " + ip);
        return ip;
    }
}

 HttpHelper.java

此方法要获取request body参数还需要HttpServletRequestWrapper 实现对request body的二次读取,具体实现请自行百度。

package com.tjair.tjapi.util;

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;

public class HttpHelper {
    public static String getBodyString(HttpServletRequest request) throws IOException {
        StringBuilder sb = new StringBuilder();
        InputStream inputStream = null;
        BufferedReader reader = null;
        try {
            inputStream = request.getInputStream();
            reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
            String line = "";
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return sb.toString();
    }
}

 

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值