程序员必备宝典https://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 {@Serialprivate 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")) {// 根据网卡取本机配置的IPInetAddress inet = null;try {inet = InetAddress.getLocalHost();} catch (UnknownHostException e) {e.printStackTrace();}ipAddress = inet.getHostAddress();}}// 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割if (ipAddress != null && ipAddress.length() > 15) { // "***.***.***.***".length()// = 15if (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 {@Resourceprivate 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 = "用户登录")中的注解valueRequestLog requestLogName = method.getAnnotation(RequestLog.class);if (requestLogName != null) {String value = requestLogName.value();log.setRequestName(value);}//获取用户IpString 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));//获取请求tokenString 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"}]
}