SpringBoot3与AOP完美结合:轻松追踪用户操作,实现精准日志记录

程序员必备宝典icon-default.png?t=N7T8https://tmxkj.top/#/

1.pom文件

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

       <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.78</version>
        </dependency>

2.Annotation 注解

import java.lang.annotation.*;

/**
 * 请求记录日志注解
 */
@Target({ElementType.TYPE, ElementType.METHOD}) //注解放置的目标位置,METHOD是可注解在方法级别上
@Retention(RetentionPolicy.RUNTIME) //注解在哪个阶段执行
@Documented
public @interface RequestLog {
    String value() default "";
}

3.Entity实体类和Dao


import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;

import java.io.Serial;
import java.io.Serializable;
import java.util.Date;

@Data
@TableName(value = "sys_log",autoResultMap = true)
public class Log implements Serializable {
    @Serial
    private static final long serialVersionUID = 1L;

    @TableId(type = IdType.AUTO)
    /**
     * id
     */
    private Integer id;

    /**
     * 用户ip
     */
    private String requestIp;

    /**
     * 用户id
     */
    private String userId;

    /**
     * 用户名称
     */
    private String userName;

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

    /**
     * 请求接口名称
     */
    private String requestName;

    /**
     * 请求格式
     */
    private String requestMethod;

    /**
     * 请求头信息
     */
    private String requestHeader;

    /**
     * 请求查询参数
     */
    private String requestQuery;

    /**
     * 请求体参数
     */
    private String requestParam;

    /**
     * 请求耗时(秒)
     */
    private Integer requestCost;

    /**
     * 请求状态
     */
    private String requestCode = "200";

    /**
     * 请求位置
     */
    private String requestPosition;

    /**
     * 响应状态
     */
    private String responseCode ="200";

    /**
     * 响应结果
     */
    private String responseResult;

    /**
     * 报错信息
     */
    private String reportErrors;

    /**
     * 创建时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date createTime;

    /**
     * 结束时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date endTime;

}
CREATE TABLE `tmxtestsql`.`Untitled`  (
  `id` int NOT NULL AUTO_INCREMENT,
  `request_ip` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户ip',
  `user_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户id',
  `user_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户名称',
  `request_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '请求地址',
  `request_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '请求接口名称',
  `request_method` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '请求格式',
  `request_header` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '请求头信息',
  `request_query` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '请求查询参数',
  `request_param` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '请求体参数',
  `request_cost` int NULL DEFAULT NULL COMMENT '请求耗时(秒)',
  `request_code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '请求状态',
  `request_position` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '请求位置',
  `response_code` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '响应状态',
  `response_result` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '响应结果',
  `report_errors` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '报错信息',
  `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
  `end_time` datetime NULL DEFAULT NULL COMMENT '结束时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

 Dao数据层

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.portalwebsiteservice.demos.web.Entity.Log;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface LogDao extends BaseMapper<Log> {
}

 4.Util工具类(获取ip地址)

/**
 * 获取IP真实地址
 * 备注:在本地运行是获取不到真实地址,需要部署到服务上才能获取得到
 */
public class IpUtils {
        public static String getIpAddr(HttpServletRequest request) {
            String ipAddress = null;
            try {
                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();
                    if (ipAddress.equals("127.0.0.1")) {
                        // 根据网卡取本机配置的IP
                        InetAddress inet = null;
                        try {
                            inet = InetAddress.getLocalHost();
                        } catch (UnknownHostException e) {
                            e.printStackTrace();
                        }
                        ipAddress = inet.getHostAddress();
                    }
                }
                // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
                if (ipAddress != null && ipAddress.length() > 15) { // "***.***.***.***".length()
                    // = 15
                    if (ipAddress.indexOf(",") > 0) {
                        ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
                    }
                }
            } catch (Exception e) {
                ipAddress="";
            }
            return ipAddress;
        }
    }

5.Aspect切面类(业务流程)


import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.portalwebsiteservice.demos.web.Annotation.RequestLog;
import com.portalwebsiteservice.demos.web.Dao.LogDao;
//import com.portalwebsiteservice.demos.web.Dto.JwtInfo;
import com.portalwebsiteservice.demos.web.Dto.Result;
import com.portalwebsiteservice.demos.web.Entity.Log;
//import com.portalwebsiteservice.demos.web.Service.JwtRedistService;
import org.aspectj.lang.JoinPoint;
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.aspectj.lang.reflect.MethodSignature;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static com.alibaba.fastjson.JSON.toJSONString;
import static com.portalwebsiteservice.demos.web.Util.IpUtils.getIpAddr;


@Aspect
@Component
public class LoggingAspect {

    @Resource
    private LogDao logDao;

   // @Resource
   // private JwtRedistService jwtRedistService;

    /**
     * execution是给指定区域,切入点(目前已去掉)
     * annotation是让特定类使用注解,切入点
     */
    @Pointcut("@annotation(com.portalwebsiteservice.demos.web.Annotation.RequestLog)")
    public void logPointCut() {}

    Date startDate;

    @Before("logPointCut()")
    public void beforeRequest() {
        startDate = new Date();
    }

    /**
     * 日志存入
     */
    @AfterReturning(value = "logPointCut()", returning = "result")
    public void saveLog(JoinPoint joinPoint, Result result) {
      try {
          // 获取请求头
          ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
          HttpServletRequest request = null;
          //创建实体类实例
          Log log = new Log();
          if (requestAttributes != null) {
              //获取到响应数据
              HttpServletResponse response = requestAttributes.getResponse();
              //获取请求头信息
              request = requestAttributes.getRequest();
              //从切面织入点处通过反射机制获取织入点处的方法
              MethodSignature signature = (MethodSignature) joinPoint.getSignature();
              //获取切入点所在的方法
              Method method = signature.getMethod();
              //------------------------以下方法是设置实体类参数----------------------------//
              // 获取@SystemLog(value = "用户登录")中的注解value
              RequestLog requestLogName = method.getAnnotation(RequestLog.class);
              if (requestLogName != null) {
                  String value = requestLogName.value();
                  log.setRequestName(value);
              }

              //获取用户Ip
              String clientIp = getIpAddr(request);
              log.setRequestIp(clientIp);

              //设置请求路径
              log.setRequestUrl(request.getRequestURI());
              //请求格式
              log.setRequestMethod(request.getMethod());
              //设置请求状态
              if (response != null) {
                  log.setRequestCode(String.valueOf(response.getStatus()));
              }
              //设置请求头信息
              Map<String, String> map = new HashMap<>();
              Enumeration<String> headerNames = request.getHeaderNames();
              while (headerNames.hasMoreElements()) {
                  String key = headerNames.nextElement();
                  String value = request.getHeader(key);
                  map.put(key, value);
              }
              log.setRequestHeader(toJSONString(map));
              //获取请求token
              String Authorization = request.getHeader("Authorization");
              //获取用户信息(这步骤是我的业务逻辑,你根据自己情况获取用户信息)
              if (Authorization != null) {
                 // JwtInfo jwtInfo =jwtRedistService.getUserInfo(Authorization);
                //  if (jwtInfo.getPass()){
                  //    log.setUserId(jwtInfo.getUserId());
                   //   log.setUserName(jwtInfo.getUser().getUserName());
                 // }
              }
              //设置查询参数
              log.setRequestQuery(request.getQueryString());
              //设置请求参数
              if (request.getMethod().equals("POST")) {
                  Object[] list = joinPoint.getArgs();
                  if (list != null && list.length > 0) {
                      String params = toJSONString(list[0]);
                      log.setRequestParam(params);
                  }

              }
              //设置响应结果
              log.setResponseResult(String.valueOf(result));
              //设置响应状态
              log.setResponseCode(String.valueOf(result.getCode()));
              //设置创建时间
              log.setCreateTime(startDate);
              Date nowTime = new Date();
              //设置结束时间
              log.setEndTime(nowTime);
              //设置请求时长
              long durationInMillis = nowTime.getTime() - startDate.getTime();
              long durationInSeconds = (TimeUnit.MILLISECONDS.toSeconds(durationInMillis))+1L;
              log.setRequestCost((int) durationInSeconds);
              //插入数据
              logDao.insert(log);
          }

      }catch (Exception e) {
          e.fillInStackTrace();
      }
    }


    /**
     * 定时任务清除数据
     */
    @Scheduled(cron = "0 0 2 * * ?")
    public void executeTask() {
        // 获取当前时间并减去3个月
        LocalDateTime threeMonthsAgo = LocalDateTime.now().minus(3, ChronoUnit.MONTHS);
        LambdaQueryWrapper<Log> lqw = new LambdaQueryWrapper<>();
        lqw.lt(Log::getCreateTime, threeMonthsAgo);
        List<Log> logList = logDao.selectList(lqw);
        if (logList != null && logList.size() > 0) {
            logDao.deleteBatchIds(logList);
        }
    }

备注:如果你使用了定时任务,记得在启动类添加@EnableScheduling注解

 6.使用(调用接口即可)

 运行结果:

{
  "RECORDS": [
    {
      "id": 22,
      "request_ip": "183.136.77777.78",
      "user_id": null,
      "user_name": null,
      "request_url": "/api-net/phone-info",
      "request_name": "获取手机号信息接口",
      "request_method": "GET",
      "request_header": "{\"remote-host\":\"\",\"referer\":\"http://api.aa1.cn\",\"cdn-loop\":\"cloudflare\",\"cf-ipcountry\":\"CN\",\"cf-ray\":\"8bac2457bd9093fa-LHR\",\"x-forwarded-proto\":\"https\",\"accept-language\":\"en-US,en;q=0.9\",\"x-forwarded-for\":\"183.136.132.78, 172.70.160.221\",\"x-host\":\"yubin-fuwu.top:80\",\"accept\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"x-real-ip\":\"172.70.160.221\",\"cf-visitor\":\"{\\\"scheme\\\":\\\"https\\\"}\",\"host\":\"yubin-fuwu.top:80\",\"connection\":\"upgrade\",\"cf-connecting-ip\":\"183.136.132.78\",\"x-scheme\":\"http\",\"cache-control\":\"max-age=0\",\"accept-encoding\":\"gzip, br\",\"user-agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36\"}",
      "request_query": "mobile=578887",
      "request_param": null,
      "request_cost": 3,
      "request_code": "200",
      "request_position": null,
      "response_code": "200",
      "response_result": "Result(code=200, msg=获取手机号信息成功, data=PhoneInfo(phoneNumber=77777, province=云南, city=文山, zipCode=663000, areaCode=0876, phoneType=电信))",
      "report_errors": null,
      "create_time": "29/8/2024 19:18:44",
      "end_time": "29/8/2024 19:18:46"
    }
  ]
}

  • 10
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值