記錄用戶業(yè)務(wù)請(qǐng)求日志
在用戶的一般使用的時(shí)候,對(duì)于很多操作類型的接口,為了后面便于追查問題,需要記錄用戶的請(qǐng)求日志。
用戶的請(qǐng)求日志目前主流的存儲(chǔ)方式有:
- 日志文件
- 數(shù)據(jù)庫
- MongoDB
- ElasticSearch
在商城的項(xiàng)目中暫時(shí)存放在MySQL中了。
增加注解
增加專門的注解標(biāo)識(shí)哪些是需要記錄用戶日志的。
注解就叫BizLog注解:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BizLog {
String value() default "";
}
其中的value參數(shù),用來接收用戶自定義的日志描述。
后面只要有接口打上了這個(gè)注解,就會(huì)自動(dòng)將用戶的業(yè)務(wù)請(qǐng)求日志記錄到數(shù)據(jù)庫中。
增加業(yè)務(wù)日志表
為了方便后續(xù)追溯用戶的請(qǐng)求行為,將用戶的業(yè)務(wù)請(qǐng)求日志記錄到數(shù)據(jù)庫的某一張表中。
這樣以后就可以通過這張表查詢數(shù)據(jù)了。
CREATE TABLE `biz_log` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'ID',
`method_name` varchar(30) NOT NULL COMMENT '方法名稱',
`description` varchar(30) NOT NULL COMMENT '描述',
`request_ip` varchar(15) NOT NULL COMMENT '請(qǐng)求ip',
`browser` varchar(200) NULL COMMENT '瀏覽器',
`url` varchar(100) NOT NULL COMMENT '請(qǐng)求地址',
`param` varchar(300) NULL COMMENT '請(qǐng)求參數(shù)',
`time` int NOT NULL COMMENT '耗時(shí),毫秒級(jí)',
`exception` varchar(300) NULL COMMENT '異常',
`status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '狀態(tài) 1:成功 0:失敗',
`create_user_id` bigint NOT NULL COMMENT '創(chuàng)建人ID',
`create_user_name` varchar(30) NOT NULL COMMENT '創(chuàng)建人名稱',
`create_time` datetime(3) DEFAULT NULL COMMENT '創(chuàng)建日期',
`update_user_id` bigint DEFAULT NULL COMMENT '修改人ID',
`update_user_name` varchar(30) DEFAULT NULL COMMENT '修改人名稱',
`update_time` datetime(3) DEFAULT NULL COMMENT '修改時(shí)間',
`is_del` tinyint(1) DEFAULT '0' COMMENT '是否刪除 1:已刪除 0:未刪除',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='業(yè)務(wù)日志表';
增加業(yè)務(wù)日志攔截器
package com.kailong.interceptor;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.kailong.annotation.BizLog;
import com.kailong.entity.log.BizLogEntity;
import com.kailong.service.log.BizLogService;
import com.kailong.util.IpUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
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.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
@Aspect
@Component
public class BizLogAspect {
@Autowired
private BizLogService bizLogService;
@Pointcut("@annotation(com.kailong.annotation.BizLog)")
public void pointcut() {
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
HttpServletRequest httpServletRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
Object result = joinPoint.proceed();
long time = System.currentTimeMillis() - startTime;
BizLogEntity bizLogEntity = createBizLogEntity(joinPoint, httpServletRequest);
bizLogEntity.setTime((int) time);
bizLogEntity.setStatus(1);
bizLogService.save(bizLogEntity);
return result;
}
private String getParam(JoinPoint joinPoint) {
StringBuilder params = new StringBuilder("{");
Object[] argValues = joinPoint.getArgs();
String[] argNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
if (argValues != null) {
for (int i = 0; i < argValues.length; i++) {
params.append(" ").append(argNames[i]).append(": ").append(argValues[i]);
}
}
return params.append("}").toString();
}
private BizLogEntity createBizLogEntity(JoinPoint joinPoint, HttpServletRequest httpServletRequest) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
BizLog bizLog = method.getAnnotation(BizLog.class);
String methodName = joinPoint.getTarget().getClass().getName() + "." + signature.getName();
BizLogEntity bizLogEntity = new BizLogEntity();
bizLogEntity.setDescription(bizLog.value());
bizLogEntity.setMethodName(methodName);
bizLogEntity.setStatus(1);
bizLogEntity.setRequestIp(IpUtil.getIpAddr(httpServletRequest));
bizLogEntity.setUrl(httpServletRequest.getRequestURI());
bizLogEntity.setBrowser(getBrowserName(httpServletRequest));
bizLogEntity.setParam(getParam(joinPoint));
return bizLogEntity;
}
@AfterThrowing(pointcut = "pointcut()", throwing = "e")
public void logAfterThrowing(JoinPoint joinPoint, Throwable e) {
HttpServletRequest httpServletRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
BizLogEntity bizLogEntity = createBizLogEntity(joinPoint, httpServletRequest);
bizLogEntity.setStatus(0);
bizLogEntity.setException(e.getMessage());
bizLogService.save(bizLogEntity);
}
private String getBrowserName(HttpServletRequest httpServletRequest) {
String userAgentString = httpServletRequest.getHeader("User-Agent");
UserAgent ua = UserAgentUtil.parse(userAgentString);
return ua.getBrowser().toString();
}
}
這個(gè)攔截器會(huì)記錄用戶業(yè)務(wù)請(qǐng)求的ip、地址、參數(shù)、瀏覽器和接口耗時(shí)都數(shù)據(jù)。
如果用戶業(yè)務(wù)請(qǐng)求失敗了,也會(huì)記錄一條失敗的數(shù)據(jù)。
BizLog相關(guān)類
(基礎(chǔ)設(shè)置,實(shí)際項(xiàng)目需修改)
BizLogEntity
package com.kailong.entity.log;
/**
* created by kailong on 2025/9/23
*/
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.kailong.entity.BaseEntity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
/**
* 業(yè)務(wù)日志實(shí)體類
* 對(duì)應(yīng)表:biz_log
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("biz_log") // 指定MyBatis-Plus對(duì)應(yīng)的表名
@Schema(description = "業(yè)務(wù)日志實(shí)體類,用于記錄系統(tǒng)業(yè)務(wù)操作日志") // Swagger3類級(jí)別描述
public class BizLogEntity extends BaseEntity implements Serializable {//可以不繼承BaseEntity
private static final long serialVersionUID = 1L;
/**
* 日志ID(主鍵,自增)
*/
@TableId(value = "id", type = IdType.AUTO) // MyBatis-Plus主鍵注解,指定自增策略
@Schema(description = "日志ID", example = "1") // Swagger3字段描述+示例值
private Long id;
/**
* 方法名稱(記錄調(diào)用的業(yè)務(wù)方法名)
*/
@Schema(description = "方法名稱", example = "getUserInfo")
private String methodName;
/**
* 操作描述(記錄業(yè)務(wù)操作的簡(jiǎn)要說明)
*/
@Schema(description = "操作描述", example = "查詢用戶信息")
private String description;
/**
* 請(qǐng)求IP(記錄發(fā)起請(qǐng)求的客戶端IP地址)
*/
@Schema(description = "請(qǐng)求IP", example = "192.168.1.100")
private String requestIp;
/**
* 瀏覽器類型(記錄發(fā)起請(qǐng)求的瀏覽器信息,如Chrome、Firefox)
*/
@Schema(description = "瀏覽器類型", example = "Chrome 120.0.0.0")
private String browser;
/**
* 請(qǐng)求地址(記錄請(qǐng)求的URL路徑,如/api/user/info)
*/
@Schema(description = "請(qǐng)求地址", example = "/api/user/info")
private String url;
/**
* 請(qǐng)求參數(shù)(記錄請(qǐng)求的參數(shù)信息,如{"userId":1})
*/
@Schema(description = "請(qǐng)求參數(shù)", example = "{\"userId\":1}")
private String param;
/**
* 耗時(shí)(記錄業(yè)務(wù)方法執(zhí)行的耗時(shí),單位:毫秒)
*/
@Schema(description = "耗時(shí)(毫秒)", example = "50")
private Integer time;
/**
* 異常信息(記錄業(yè)務(wù)方法執(zhí)行過程中拋出的異常信息,無異常則為空)
*/
@Schema(description = "異常信息", example = "java.lang.NullPointerException: 用戶不存在")
private String exception;
/**
* 狀態(tài)(1:成功 0:失敗,記錄業(yè)務(wù)操作的執(zhí)行結(jié)果)
*/
@Schema(description = "狀態(tài)(1:成功 0:失敗)", example = "1")
private int status;
/**
* 創(chuàng)建人ID(記錄創(chuàng)建該日志的用戶ID)
*/
@Schema(description = "創(chuàng)建人ID", example = "1001")
private Long createUserId;
/**
* 創(chuàng)建人名稱(記錄創(chuàng)建該日志的用戶名)
*/
@Schema(description = "創(chuàng)建人名稱", example = "admin")
private String createUserName;
/**
* 創(chuàng)建時(shí)間(記錄日志的創(chuàng)建時(shí)間,默認(rèn)為當(dāng)前時(shí)間)
*/
@Schema(description = "創(chuàng)建時(shí)間", example = "2025-09-23 17:13:18")
private Date createTime;
/**
* 修改人ID(記錄最后修改該日志的用戶ID,無修改則為空)
*/
@Schema(description = "修改人ID", example = "1002")
private Long updateUserId;
/**
* 修改人名稱(記錄最后修改該日志的用戶名,無修改則為空)
*/
@Schema(description = "修改人名稱", example = "operator")
private String updateUserName;
/**
* 修改時(shí)間(記錄最后修改該日志的時(shí)間,無修改則為空)
*/
@Schema(description = "修改時(shí)間", example = "2025-09-23 17:15:30")
private Date updateTime;
/**
* 是否刪除(1:已刪除 0:未刪除,邏輯刪除標(biāo)記)
*/
@Schema(description = "是否刪除(1:已刪除 0:未刪除)", example = "0")
private Integer isDel;
}
BizLogMapper(用的MyBatisplus)
@Mapper
public interface BizLogMapper extends BaseMapper<BizLogEntity> {
}
BizLogService
public interface BizLogService {
public void save(BizLogEntity bizLogEntity);
}
BizLogServiceImpl
@Service
public class BizLogServiceImpl implements BizLogService {
@Autowired
private BizLogMapper bizLogMapper;
@Override
public void save(BizLogEntity bizLogEntity) {
FillUserUtil.fillCreateUserInfo(bizLogEntity);
bizLogMapper.insert(bizLogEntity);
}
}
測(cè)試效果



浙公網(wǎng)安備 33010602011771號(hào)