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"
}
]
}