Spring Boot 通过AOP+自定义注解实现日志管理


Spring Boot 通过AOP+自定义注解实现日志管理

操作日志的记录:
记录用户的行为(Controller 请求日志),这叫做业务运行日志
业务运行日志的作用:
1,记录用户的行为 用于后续的分析
2,记录用户的所有的操作

1:操作日志数据结构表

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS `xcy_operation_log`;
CREATE TABLE `xcy_operation_log`  (
  `id` bigint(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '操作日志自增id',
  `user_id` bigint(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT '操作人id',
  `ad_account` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '操作人AD账号',
  `operation_ip` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '请求ip地址',
  `operation_model` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '操作模块',
  `operation_type` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '操作类型',
  `operation_desc` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '操作描述',
  `operation_time` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) COMMENT '操作时间',
  `operation_uri` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '请求uri',
  `operation_method` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '请求方法',
  `request_param` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '请求参数',
  `response_param` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '响应参数',
  `result` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '操作结果',
  `create_time` datetime(0) NOT NULL COMMENT '创建时间',
  `update_time` datetime(0) NOT NULL COMMENT '修改时间',
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `user_id`(`user_id`) USING BTREE,
  INDEX `operation_model`(`operation_model`) USING BTREE,
  INDEX `ad_account`(`ad_account`) USING BTREE,
  INDEX `id`(`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '操作日志表' ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;

2:操作日志实体类

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("xcy_operation_log")
public class OperationLog implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 操作日志自增id
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    /**
     * 操作人id
     */
    private Long userId;

    /**
     * 操作人AD账号
     */
    private String adAccount;

    /**
     * 请求ip地址
     */
    private String operationIp;

    /**
     * 操作模块
     */
    private String operationModel;

    /**
     * 操作类型
     */
    private String operationType;

    /**
     * 操作描述
     */
    private String operationDesc;

    /**
     * 操作时间
     */
    private LocalDateTime operationTime;

    /**
     * 请求uri
     */
    private String operationUri;

    /**
     * 请求方法
     */
    private String operationMethod;

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

    /**
     * 响应参数
     */
    private String responseParam;

    /**
     * 操作结果
     */
    private String result;

    /**
     * 创建时间
     */
    private LocalDateTime createTime;

    /**
     * 修改时间
     */
    private LocalDateTime updateTime;


}

3:操作日志服务类

public interface OperationLogService extends IService<OperationLog> {
}

4:操作日志实现类

@Service
public class OperationLogServiceImpl extends ServiceImpl<OperationLogMapper, OperationLog> implements OperationLogService {
}

5:mapper接口层

public interface OperationLogMapper extends BaseMapper<OperationLog> {
}

6:mapper.xml层

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xc.contract.dao.mapper.OperationLogMapper">
</mapper>

7:操作日志注解类

@Target(ElementType.METHOD)//注解放置的目标位置即方法级别
@Retention(RetentionPolicy.RUNTIME)//注解在哪个阶段执行
@Documented
public @interface OperLog {
    /**
     * 操作模块
     * @return
     */
    String module() default "";

    /**
     * 操作类型
     * @return
     */
    String type() default "";

    /**
     * 操作说明
     * @return
     */
    String desc() default "";
}

8:操作日志切面处理类

@Slf4j
@Aspect
@Component
public class OperationLogAspect {

    @Autowired
    private OperationLogService logService;

    /**
     * 设置操作日志切入点 记录操作日志 在注解的位置切入代码
     */
    @Pointcut("@annotation(OperLog)")
    public void operLogPoinCut() {}


    /**
     * 正常返回通知,拦截用户操作日志,连接点正常执行完成后执行, 如果连接点抛出异常,则不会执行
     * @param joinPoint 切入点
     * @param data 返回结果
     */
    @AfterReturning(value = "operLogPoinCut()", returning = "data")
    public void saveOperLog(JoinPoint joinPoint, Object data) {
        SessionPayload sessionPayload = SessionPayload.current();
        // 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 从获取RequestAttributes中获取HttpServletRequest的信息
        HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
        //操作日志
        WqOperationLog operationLog = new WqOperationLog();
        try{
            // 从切面织入点处通过反射机制获取织入点处的方法
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            //获取切入点所在的方法
            Method method = signature.getMethod();
            OperLog annotation = method.getAnnotation(OperLog.class);
            if (annotation!=null){
                //操作模块
                operationLog.setOperationModel(annotation.module());
                //操作类型
                operationLog.setOperationType(annotation.type());
                //操作描述
                operationLog.setOperationDesc(annotation.desc());
            }
            //获取请求的类名
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = method.getName();
            //请求的方法名
            methodName = className+"."+methodName;
            //操作方法
            operationLog.setOperationMethod(methodName);
            //请求参数
            operationLog.setRequestParam(getRequestParams(joinPoint.getArgs()));
            //响应参数
            operationLog.setResponseParam(JsonUtil.toJson(data));
            //操作员
            operationLog.setUserId(sessionPayload.getUserId());
            operationLog.setAccount(sessionPayload.getAccount());
            operationLog.setOperationTime(LocalDateTime.now());
            //请求uri
            operationLog.setOperationUri(request.getRequestURI());
            //请求ip
            operationLog.setOperationIp(IpAddressUtil.getIpAddr(request));
            //操作结果
            ReturnData returnData = (ReturnData) data;
            if (returnData!=null){
                operationLog.setResult(returnData.getMessage());
            }else {
                operationLog.setResult("");
            }
            //创建时间
            operationLog.setCreateTime(LocalDateTime.now());
            //修改时间
            operationLog.setUpdateTime(LocalDateTime.now());
            //保存日志
            logService.save(operationLog);
        }catch (Exception e){
            log.error(e.getMessage());
        }
    }

    /**
     * 转换request参数
     * @param paramsArray
     * @return
     */
    public String getRequestParams(Object[] paramsArray) throws Exception{
        String params = "";
        if (paramsArray != null && paramsArray.length > 0) {
            for (Object o : paramsArray) {
                String jsonObj = JSONUtil.toJsonStr(o);
                params += jsonObj.toString()+"";
            }
        }
        return params.trim();
    }
}

IP地址工具类

public class IpAddressUtil {
    public static String getIpAddr(HttpServletRequest request) {
        String Xip = request.getHeader("X-Real-IP");
        String XFor = request.getHeader("X-Forwarded-For");
        if (StringUtils.isNotEmpty(XFor) && !"unKnown".equalsIgnoreCase(XFor)) {
            //多次反向代理后会有多个ip值,第一个ip才是真实ip
            int index = XFor.indexOf(",");
            if (index != -1) {
                return XFor.substring(0, index);
            } else {
                return XFor;
            }
        }
        XFor = Xip;
        if (StringUtils.isNotEmpty(XFor) && !"unKnown".equalsIgnoreCase(XFor)) {
            return XFor;
        }
        if (StringUtils.isBlank(XFor) || "unknown".equalsIgnoreCase(XFor)) {
            XFor = request.getHeader("Proxy-Client-IP");
        }
        if (StringUtils.isBlank(XFor) || "unknown".equalsIgnoreCase(XFor)) {
            XFor = request.getHeader("WL-Proxy-Client-IP");
        }
        if (StringUtils.isBlank(XFor) || "unknown".equalsIgnoreCase(XFor)) {
            XFor = request.getHeader("HTTP_CLIENT_IP");
        }
        if (StringUtils.isBlank(XFor) || "unknown".equalsIgnoreCase(XFor)) {
            XFor = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (StringUtils.isBlank(XFor) || "unknown".equalsIgnoreCase(XFor)) {
            XFor = request.getRemoteAddr();
        }
        return XFor;
    }
}

9:RequestWrapper类(无需)

RequestWrapper继承了HttpServletRequestWrapper,初始化的时候读取Request的整个请求体。然后重载了getInputStream和getReader方法,这两个方法会从类变量body中读取内容

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;

public class RequestWrapper extends HttpServletRequestWrapper {
    private final String body;

    public RequestWrapper(HttpServletRequest request) {
        super(request);
        StringBuilder stringBuilder = new StringBuilder();
        BufferedReader bufferedReader = null;
        InputStream inputStream = null;
        try {
            inputStream = request.getInputStream();
            if (inputStream != null) {
                bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                char[] charBuffer = new char[128];
                int bytesRead = -1;
                while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
                    stringBuilder.append(charBuffer, 0, bytesRead);
                }
            } else {
                stringBuilder.append("");
            }
        } catch (IOException ex) {

        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                }
                catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                }
                catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        body = stringBuilder.toString();
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
        ServletInputStream servletInputStream = new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }
            @Override
            public boolean isReady() {
                return false;
            }
            @Override
            public void setReadListener(ReadListener readListener) {
            }
            @Override
            public int read() throws IOException {
                return byteArrayInputStream.read();
            }
        };
        return servletInputStream;

    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(this.getInputStream()));
    }

    public String getBody() {
        return this.body;
    }

}

10:使用你定义的自定义注解

在这里插入图片描述
***在这里插入图片描述

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Spring Boot中使用AOP自定义注解可以帮助我们更方便地进行方法的拦截和处理。下面是一种最佳实践过程: 1. 首先,我们需要引入`spring-boot-starter-aop`依赖。可以在`pom.xml`文件中添加如下内容: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> ``` 2. 创建一个自定义注解。可以使用`@interface`关键字创建一个注解类,并在注解类中定义需要的属性。例如: ```java @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface MyAnnotation { // 定义需要的属性 } ``` 3. 创建一个切面类。切面类用来定义需要进行拦截和处理的方法。可以使用`@Aspect`注解标记该类,并使用`@Around`注解指定要拦截处理的目标方法。例如: ```java @Aspect @Component public class MyAspect { @Around("@annotation(com.example.MyAnnotation)") public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable { // 在方法执行前进行处理 // ... Object result = joinPoint.proceed(); // 在方法执行后进行处理 // ... return result; } } ``` 4. 在Spring Boot的启动类上添加`@EnableAspectJAutoProxy`注解,开启AOP的支持。例如: ```java @SpringBootApplication @EnableAspectJAutoProxy public class Application { // ... } ``` 5. 在需要应用切面的方法上添加自定义注解。 ```java @MyAnnotation public void someMethod() { // ... } ``` 通过以上步骤,我们就可以成功地在Spring Boot中使用AOP自定义注解来进行方法的拦截和处理了。需要注意的是,如果定义的注解只用于标记方法,而不需要定义属性,可以简化为一个空接口。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值