自定义注解,利用AOP实现日志保存(数据库),代码全贴,复制就能用

2022-08-26 00:00:00 创建 请求 异常 日志 切面

前言

1,在一些特定的场景我们往往需要看一下接口的入参,特别是跨系统的接口调用(下发,推送),这个时候的接口入参就很重要,我们保存入参入库,如果出问题就可以马上定位是上游还是下游的问题(方便扯皮)

2,还有一般需要在系统中看普通日志,还有特殊的异常(报错)日志,一般我们可以通过服务器去查看相应的位置,但是由于服务器是一直运行的,日志是一直在生成的,这个时候就不太方便。

3,保存入参,我们之间本地调试的时候就可以不用造数据,这个也是很方便的,只需改改就好。

这个时候就体现入参请求和相应结果的重要性

Aop基本概念

Aspect(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
Advice(增强):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
Target(目标对象):织入 Advice 的目标对象.。
Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程

自定义注解:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserAccess {
    String desc() default "无信息";
}

对自定义注解进行aop切面

一般使用更加详细的日志切面

@Component
@Aspect
public class UserAccessAspect {

    // 这里就是对上面进行切面
    @Pointcut(value = "@annotation(com.xncoding.aop.aspect.UserAccess)")
    public void access() {

    }

    @Before("access()")
    public void deBefore(JoinPoint joinPoint) throws Throwable {
        System.out.println("second before");
    }

    @Around("@annotation(userAccess)")
    public Object around(ProceedingJoinPoint pjp, UserAccess userAccess) {
        //获取注解里的值
        System.out.println("second around:" + userAccess.desc());
        try {
            return pjp.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            return null;
        }
    }
}

日志切面

如果想保存到数据库或者进行更加详细的日志打印就可以使用下面的日志切面,

这里目前是打印,如果想保存到数据库,可以创建相应的实体,set进去,然后进行insert到数据库,如果使用mybatis-plus是很方便的,先创建表,然后用mybatis-plus 自动生成代码,再用相应的mapper.inster() 保存到数据库。

/**
 * 日志切面
 */
@Aspect
@Component
public class LogAspect {
    // 这个目前是对从 com.xncoding.aop.controller.* 下的都进行切入,如果想对上面的自定义注解进行切入,只需改成相对应的路径
    // 例如:@Pointcut(value = "@annotation(com.xncoding.aop.aspect.UserAccess)")
    @Pointcut("execution(public * com.xncoding.aop.controller.*.*(..))")
    public void webLog(){}

    @Before("webLog()")
    public void deBefore(JoinPoint joinPoint) throws Throwable {
        // 接收到请求,记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 记录下请求内容
        System.out.println("URL : " + request.getRequestURL().toString());
        System.out.println("HTTP_METHOD : " + request.getMethod());
        System.out.println("IP : " + request.getRemoteAddr());
        System.out.println("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
        System.out.println("ARGS : " + Arrays.toString(joinPoint.getArgs()));
    }

    @AfterReturning(returning = "ret", pointcut = "webLog()")
    public void doAfterReturning(Object ret) throws Throwable {
        // 处理完请求,返回内容
        System.out.println("方法的返回值 : " + ret);
    }

    //后置异常通知
    @AfterThrowing("webLog()")
    public void throwss(JoinPoint jp){
        System.out.println("方法异常时执行.....");
    }

    //后置终通知,final增强,不管是抛出异常或者正常退出都会执行
    @After("webLog()")
    public void after(JoinPoint jp){
        System.out.println("方法后执行.....");
    }

    //环绕通知,环绕增强,相当于MethodInterceptor
    @Around("webLog()")
    public Object arround(ProceedingJoinPoint pjp) throws Throwable{
        System.out.println("方法环绕start.....");
        try {
            Object o =  pjp.proceed();
            System.out.println("方法环绕proceed,结果是 :" + o);
            return o;
        } catch (Throwable e) {
            throw e;
        }
    }
}

测试controller

@RestController
@RequestMapping("aop")
public class UserController {
    @GetMapping("/first")
    public Object first() {
        return "first controller";
    }

    @GetMapping("/doError")
    public Object error() {
        return 1 / ;
    }

	@GetMapping("/second")
    @UserAccess(desc = "second")
    public Object second() {
        return "second controller 666";
    }

    @PostMapping("/user")
    public void userPost(@RequestBody UserVo userVo){
        System.out.println("我user进来了");
    }

}

user类

@Data
public class UserVo {

    @ApiModelProperty("姓名")
    private String name;

    @ApiModelProperty("性别")
    private Integer sex;
}

创建表SQL语句

CREATE TABLE `sys_log` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `log_type` varchar(50) NOT NULL COMMENT '日志类型',
  `create_user_code` varchar(64) NOT NULL COMMENT '创建用户编码',
  `create_user_name` varchar(100) NOT NULL COMMENT '创建用户名称',
  `create_date` datetime NOT NULL COMMENT '创建时间',
  `request_uri` varchar(500) DEFAULT NULL COMMENT '请求URI',
  `request_method` varchar(10) DEFAULT NULL COMMENT '请求方式',
  `request_params` text COMMENT '请求参数',
  `response_params` text COMMENT '响应参数',
  `request_ip` varchar(20) NOT NULL COMMENT '请求IP',
  `server_address` varchar(50) NOT NULL COMMENT '请求服务器地址',
  `is_exception` char(1) DEFAULT NULL COMMENT '是否异常',
  `exception_info` text COMMENT '异常信息',
  `start_time` datetime NOT NULL COMMENT '开始时间',
  `end_time` datetime NOT NULL COMMENT '结束时间',
  `execute_time` int(11) DEFAULT NULL COMMENT '执行时间',
  `user_agent` varchar(500) DEFAULT NULL COMMENT '用户代理',
  `device_name` varchar(100) DEFAULT NULL COMMENT '操作系统',
  `browser_name` varchar(100) DEFAULT NULL COMMENT '浏览器名称',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_sys_log_lt` (`log_type`) USING BTREE,
  KEY `idx_sys_log_cub` (`create_user_code`) USING BTREE,
  KEY `idx_sys_log_ie` (`is_exception`) USING BTREE,
  KEY `idx_sys_log_cd` (`create_date`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1560559854462500867 DEFAULT CHARSET=utf8 COMMENT='系统日志表';

测试

请求参数:

debug图片

这里只是做打印,也可以在切面里面保存到数据库。

相关文章