springboot通过切面编程实现系统请求操作日志记录
1、引入依赖包
<!-- aop 依赖包 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.8.6</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.25</version> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.3.8</version> </dependency>
系统操作日志表
CREATE TABLE `sys_log_operation` ( `id` varchar(32) NOT NULL COMMENT 'id', `request_uri` varchar(200) DEFAULT NULL COMMENT '请求URI', `request_method` varchar(20) DEFAULT NULL COMMENT '请求方式', `class_method` varchar(200) DEFAULT NULL COMMENT '请求函数', `request_params` text COMMENT '请求参数', `request_time` bigint DEFAULT NULL COMMENT '请求时长(毫秒)', `user_agent` varchar(500) DEFAULT NULL COMMENT '用户代理', `request_ip` varchar(32) DEFAULT NULL COMMENT '操作ip', `state` int DEFAULT NULL COMMENT '状态(0、失败,1、成功)', `create_time` datetime NOT NULL COMMENT '创建时间', `create_user` varchar(32) DEFAULT NULL COMMENT '创建人', `creator_name` varchar(32) DEFAULT NULL COMMENT '创建人名', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='系统操作日志';
系统错误日志表
CREATE TABLE `sys_log_error` ( `id` varchar(32) NOT NULL COMMENT 'id', `request_uri` varchar(200) DEFAULT NULL COMMENT '请求URI', `request_method` varchar(20) DEFAULT NULL COMMENT '请求方式', `class_method` varchar(200) DEFAULT NULL COMMENT '请求函数', `request_params` text COMMENT '请求参数', `request_time` int DEFAULT NULL COMMENT '请求时长(毫秒)', `user_agent` varchar(500) DEFAULT NULL COMMENT '用户代理', `request_ip` varchar(32) DEFAULT NULL COMMENT '操作ip', `error_info` text COMMENT '异常信息', `create_time` datetime NOT NULL COMMENT '创建时间', `create_user` varchar(32) DEFAULT NULL COMMENT '创建人', `creator_name` varchar(32) DEFAULT NULL COMMENT '创建人名', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='系统错误日志';
package com.bz.bean; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import java.io.Serializable; import java.util.Date; import lombok.Data; /** * 异常日志 */ @Data @TableName(value = "sys_log_error") public class SysLogError implements Serializable { /** * id */ @TableId(value = "id", type = IdType.INPUT) @ApiModelProperty(value = "id") private String id; /** * 请求URI */ @TableField(value = "request_uri") @ApiModelProperty(value = "请求URI") private String requestUri; /** * 请求方式 */ @TableField(value = "request_method") @ApiModelProperty(value = "请求方式") private String requestMethod; /** * 请求参数 */ @TableField(value = "request_params") @ApiModelProperty(value = "请求参数") private String requestParams; /** * 用户代理 */ @TableField(value = "user_agent") @ApiModelProperty(value = "用户代理") private String userAgent; /** * 操作IP */ @TableField(value = "ip") @ApiModelProperty(value = "操作IP") private String ip; /** * 异常信息 */ @TableField(value = "error_info") @ApiModelProperty(value = "异常信息") private String errorInfo; /** * 创建人 */ @TableField(value = "create_user") @ApiModelProperty(value = "创建人") private String createUser; /** * 创建人名 */ @TableField(value = "creator_name") @ApiModelProperty(value = "创建人名") private String creatorName; /** * 创建时间 */ @TableField(value = "create_date") @ApiModelProperty(value = "创建时间") private Date createDate; /** * 类方法 */ @TableField(value = "class_method") @ApiModelProperty(value = "类方法") private String classMethod; private static final long serialVersionUID = 1L; } package com.enzenith.homemaking.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import java.io.Serializable; import java.util.Date; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; /** * 系统操作日志 */ @Data @Builder @AllArgsConstructor @NoArgsConstructor @TableName(value = "sys_log_operation") public class SysLogOperation implements Serializable { /** * id */ @TableId(value = "id", type = IdType.INPUT) @ApiModelProperty(value = "id") private String id; /** * 请求URI */ @TableField(value = "request_uri") @ApiModelProperty(value = "请求URI") private String requestUri; /** * 请求方式 */ @TableField(value = "request_method") @ApiModelProperty(value = "请求方式") private String requestMethod; /** * 请求函数 */ @TableField(value = "class_method") @ApiModelProperty(value = "请求函数") private String classMethod; /** * 请求参数 */ @TableField(value = "request_params") @ApiModelProperty(value = "请求参数") private String requestParams; /** * 请求时长(毫秒) */ @TableField(value = "request_time") @ApiModelProperty(value = "请求时长(毫秒)") private Long requestTime; /** * 用户代理 */ @TableField(value = "user_agent") @ApiModelProperty(value = "用户代理") private String userAgent; /** * 操作ip */ @TableField(value = "request_ip") @ApiModelProperty(value = "操作ip") private String requestIp; /** * 状态(0、失败,1、成功) */ @TableField(value = "state") @ApiModelProperty(value = "状态(0、失败,1、成功)") private Integer state; /** * 创建时间 */ @TableField(value = "create_time") @ApiModelProperty(value = "创建时间") private Date createTime; /** * 创建人 */ @TableField(value = "create_user") @ApiModelProperty(value = "创建人") private String createUser; /** * 创建人名 */ @TableField(value = "creator_name") @ApiModelProperty(value = "创建人名") private String creatorName; /** * 备注(用于数据迁移) */ @TableField(value = "memo") @ApiModelProperty(value = "备注(用于数据迁移)") private String memo; private static final long serialVersionUID = 1L; }
4、切面实现类
package com.bz.aspect; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONUtil; import com.alibaba.fastjson.JSON; import com.bz.bean.SysLogError; import com.bz.bean.SysLogOperation; import com.bz.common.enums.OperationStatusEnum; import com.bz.service.SysLogErrorService; import com.bz.service.SysLogOperationService; import org.apache.commons.lang.StringEscapeUtils; 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.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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 org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletRequest; import java.lang.reflect.Method; import java.util.Date; /** * 切面日志 * @author Buzheng */ @Aspect @Component public class RequestLogAspect { private final static Logger LOGGER = LoggerFactory.getLogger(RequestLogAspect.class); @Autowired(required = false) private SysLogOperationService sysLogOperationService; @Autowired(required = false) private SysLogErrorService sysLogErrorService; /** * 定义切入点 */ @Pointcut("execution(* com.bz.api..*.*(..))") public void requestServer() { } /** * 环绕通知 * 既可以在目标方法之前织入增强动作,也可以在执行目标方法之后织入增强动作; * 可以决定目标方法在什么时候执行,如何执行,甚至可以完全阻止目标目标方法的执行; * 可以改变执行目标方法的参数值,也可以改变执行目标方法之后的返回值; 当需要改变目标方法的返回值时,只能使用Around方法; */ @Around("requestServer()") public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { //获取系统时间 long start = System.currentTimeMillis(); ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); //获取请求信息 HttpServletRequest request = attributes.getRequest(); Object result = proceedingJoinPoint.proceed(); //保存录入数据库 SysLogOperation requestInfo = new SysLogOperation(); requestInfo.setId(IdUtil.createSnowflake(1,1).nextId()); //获取客户端的IP地址 requestInfo.setRequestIp(request.getRemoteAddr()); //获取请求的接口地址 requestInfo.setRequestUri(request.getRequestURL().toString()); requestInfo.setRequestMethod(request.getMethod()); //获取请求哪个类以及哪个方法 requestInfo.setClassMethod(String.format("%s.%s", proceedingJoinPoint.getSignature().getDeclaringTypeName(), proceedingJoinPoint.getSignature().getName())); //获取请求参数 requestInfo.setRequestParams(getRequestParamsByProceedingJoinPoint(proceedingJoinPoint)); requestInfo.setState(OperationStatusEnum.SUCCESS.value()); requestInfo.setRequestTime(System.currentTimeMillis() - start); requestInfo.setCreateTime(new Date()); /** * 判断是否有token,因为登录的时候是没有的。所以需要进行判断 * ps:前端每次请求接口时都会带token进行请求。 */ String token = request.getHeader("token"); if(StrUtil.isNotBlank(token)){ requestInfo.setCreatorName("获取请求者名称"); requestInfo.setCreateUser("获取请求者用户id"); } sysLogOperationService.save(requestInfo); return result; } /** * 异常通知:目标方法抛出异常时执行 */ @AfterThrowing(pointcut = "requestServer()", throwing = "e") public void doAfterThrow(JoinPoint joinPoint, RuntimeException e) { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); SysLogError requestErrorInfo = new SysLogError(); requestErrorInfo.setId(String.valueOf(IdUtil.createSnowflake(1,1).nextId())); requestErrorInfo.setIp(request.getRemoteAddr()); requestErrorInfo.setRequestUri(request.getRequestURL().toString()); requestErrorInfo.setRequestMethod(request.getMethod()); requestErrorInfo.setClassMethod(String.format("%s.%s", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName())); requestErrorInfo.setRequestParams(getRequestParamsByJoinPoint(joinPoint)); //获取报错信息 requestErrorInfo.setErrorInfo(e.getMessage()); requestErrorInfo.setCreateDate(new Date()); /** * 判断是否有token,因为登录的时候是没有的。所以需要进行判断 * ps:前端每次请求接口时都会带token进行请求。 */ String token = request.getHeader("token"); if(StrUtil.isNotBlank(token)){ requestErrorInfo.setCreatorName("获取请求者名称"); requestErrorInfo.setCreateUser("获取请求者用户id"); } boolean save = sysLogErrorService.save(requestErrorInfo); LOGGER.info("Error Request Info : {}", JSON.toJSONString(requestErrorInfo)); } /** * 获取入参 * @param proceedingJoinPoint * */ private String getRequestParamsByProceedingJoinPoint(ProceedingJoinPoint proceedingJoinPoint) { //参数名 String[] paramNames = ((MethodSignature)proceedingJoinPoint.getSignature()).getParameterNames(); //参数值 Object[] paramValues = proceedingJoinPoint.getArgs(); //去除反斜杠 return StringEscapeUtils.unescapeJavaScript(buildRequestParam(paramNames, paramValues).toString()); } /** * 获取入参 * @param proceedingJoinPoint * */ private String getRequestParamsByJoinPoint(JoinPoint joinPoint) { //参数名 String[] paramNames = ((MethodSignature)joinPoint.getSignature()).getParameterNames(); //参数值 Object[] paramValues = joinPoint.getArgs(); return StringEscapeUtils.unescapeJavaScript(buildRequestParam(paramNames, paramValues).toString()); } /** * 将参数名称以及参数值转成json字符串 * @param paramNames 参数名称 * @param paramValues 参数值 * @return cn.hutool.json.JSONObject * @author Buzheng **/ private JSONObject buildRequestParam(String[] paramNames, Object[] paramValues) { JSONObject jsonObject = new JSONObject(); for (int i = 0; i < paramNames.length; i++) { Object value = paramValues[i]; //如果是文件对象 if (value instanceof MultipartFile) { MultipartFile file = (MultipartFile) value; value = file.getOriginalFilename(); //获取文件名 } jsonObject.putOpt(paramNames[i], value != null ? JSONUtil.toJsonStr(value) : null); } return jsonObject; } }