SpringBoot+AOP+自定义注解,实现日志记录/权限验证

news2024/11/18 17:40:52

目录

  • 自定义注解简介
  • AOP实现日志记录
    • 1.导入依赖
    • 2.创建自定义注解
    • 3.编写切面类
    • 4.编写测试接口
    • 5.测试
  • AOP实现权限验证
    • 1.创建自定义注解
    • 2.编写切面类
    • 3.编写测试接口:
    • 4.测试

自定义注解简介

为什么要通过AOP来实现日志记录

在业务处理中,我们经常需要将一些用户操作、或系统日志记录到数据库中,并在后台做展示。一般情况下我们需要在每个需要进行记录的业务方法中做sql操作,这样一来日志记录这种非业务层面的代码就会和业务代码耦合,显得非常难看。那么有没有一种优雅记录日志的办法呢?当然是有的,以下介绍一种基于自定义注解的使用AOP来记录日志的办法。

描述

注解是一种能被添加到java源代码中的元数据,单独使用注解,就相当于在类、方法、参数和包上加上一个装饰,什么功能也没有,仅仅是一个标志,然后这个标志可以加上一些自己定义的参数。然后就可以使用这个注解了,加在我们需要装饰的方法上,但是什么功能也没有,就像下面这样。

package com.test.boot.aop;

import java.lang.annotation.*;

/**
 * @Author: laz
 * @CreateTime: 2022-12-28  16:32
 * @Version: 1.0
 */
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {

    String value() default "";

}

自定义注解介绍

  • 修饰符:访问修饰符必须为public,不写默认为pubic
  • 关键字:关键字为@interface
  • 注解名称:注解名称为自定义注解的名称,例如上面的MyLog 就是注解名称
  • 注解类型元素:注解类型元素是注解中内容,根据需要标志参数,例如上面的注解的value

创建自定义注解前需要了解两个注解:@Target ,@Retention

@Target——用于描述注解的使用范围,该注解可以使用在什么地方

Target类型描述
ElementType.TYPE应用于类、接口(包括注解类型)、枚举
ElementType.FIELD应用于属性(包括枚举中的常量)
ElementType.METHOD应用于方法
ElementType.PARAMETER应用于方法的形参
ElementType.CONSTRUCTOR应用于构造函数
ElementType.LOCAL_VARIABLE应用于局部变量
ElementType.ANNOTATION_TYPE应用于注解类型
ElementType.PACKAGE应用于包

@Retention——表明该注解的生命周期

生命周期类型描述
RetentionPolicy.SOURCE编译时被丢弃,不包含在类文件中
RetentionPolicy.CLASSJVM加载时被丢弃,包含在类文件中,默认值
RetentionPolicy.RUNTIME由JVM 加载,包含在类文件中,在运行时可以被获取到

本篇文章主要介绍如何简单使用aop实现日志记录以及权限验证
源码地址:https://gitee.com/lianaozhe/springboot-aop

AOP实现日志记录

1.导入依赖

        <!-- aop -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

2.创建自定义注解

package com.test.boot.aop;

import java.lang.annotation.*;

/**
 * @Author: laz
 * @CreateTime: 2022-12-28  16:32
 * @Version: 1.0
 */
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {

    String value() default "";

}

3.编写切面类

package com.test.boot.annotations;

import com.test.boot.aop.MyLog;
import com.test.boot.entity.SysLog;
import com.test.boot.mapper.SysLogMapper;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;

/**
 * @Author laz
 * @Description
 * @Date 2022-12-28 10:42
 * @Version 1.0
 *
 * 第一步:明一个切面类
 */
@Slf4j
@Aspect
@Component
public class LogAspect {


    @Autowired
    private SysLogMapper sysLogMapper;

    /**
     * 用于记录方法执行时间
     */
    ThreadLocal<Long> startTime = new ThreadLocal<>();

    /**
     * 第二步:定义一个切入点,含义:只对MyLog注解生效
     */
    @Pointcut(value="@annotation(com.test.boot.aop.MyLog)")
    public void MyLogPointcut(){ }


    /**
     * 第三步:定义处理事件
     * @param joinPoint: 连接点(可以在这个类中获取对应的注解参数和方法参数)
     */
    @Before(value ="MyLogPointcut()")
    public void logTest(JoinPoint joinPoint){
        startTime.set(System.currentTimeMillis());

    }


    /**
     * 返回通知
     * @param ret
     * @throws Throwable
     */
    @AfterReturning(value = "MyLogPointcut()",returning = "ret")
    public void doAfterReturning(JoinPoint joinPoint,Object ret) throws Throwable {


        // 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 从获取RequestAttributes中获取HttpServletRequest的信息
        HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
        // 从切面织入点处通过反射机制获取织入点处的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 获取切入点所在的方法
        Method method = signature.getMethod();
        // 获取注解操作
        MyLog myLog = method.getAnnotation(MyLog.class);


        Object[] args = joinPoint.getArgs();
        String declaringTypeName = signature.getDeclaringTypeName();
        String name = signature.getName();
        String ip = getIp(request);
        SysLog sysLog = new SysLog();
        sysLog.setContent(myLog.value());
        sysLog.setMethod(name);
        sysLog.setRequestMethod(request.getMethod());
        sysLog.setRequestParam(JSONArray.fromObject(args).toString());
        sysLog.setResponseResult(ret.toString());
        //用户名在真实环境中由工具类获取或者从当前登录者的账号信息中获取,这里只是测试
        sysLog.setOperName("测试");
        sysLog.setIp(getIp(request));
        sysLog.setRequestUrl(request.getRequestURL().toString());
        sysLog.setOperTime(new Date());
        sysLog.setStatus(0);
        Long takeTime = System.currentTimeMillis() - startTime.get();
        sysLog.setTakeTime(takeTime);
        sysLogMapper.insert(sysLog);

        log.info("请求参数:{}",args);
        log.info("调用类名:{}",declaringTypeName);
        log.info("方法名称: {}",name);
        log.info("接口名称: {}",myLog.value());
        log.info("请求ip: {}",ip);
        log.info("请求方式: {}",request.getMethod());
        log.info("请求uri: {}",request.getRequestURI());
        log.info("方法的返回值 : [{}]",ret);
    }

    /**
     * 异常通知
     * @param joinPoint
     * @param e
     */
    @AfterThrowing(throwing = "e", pointcut = "MyLogPointcut()")
    public void throwss(JoinPoint joinPoint,Exception e){


        // 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 从获取RequestAttributes中获取HttpServletRequest的信息
        HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
        // 从切面织入点处通过反射机制获取织入点处的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 获取切入点所在的方法
        Method method = signature.getMethod();
        // 获取操作
        MyLog myLog = method.getAnnotation(MyLog.class);


        Object[] args = joinPoint.getArgs();
        String name = signature.getName();
        SysLog sysLog = new SysLog();
        sysLog.setContent(myLog.value());
        sysLog.setMethod(name);
        sysLog.setRequestMethod(request.getMethod());
        sysLog.setRequestParam(JSONArray.fromObject(args).toString());
        //用户名在真实环境中由工具类获取或者从当前登录者的账号信息中获取,这里只是测试
        sysLog.setOperName("测试");
        sysLog.setIp(getIp(request));
        sysLog.setRequestUrl(request.getRequestURL().toString());
        sysLog.setOperTime(new Date());
        sysLog.setStatus(1);
        sysLog.setErrorMsg(stackTraceToString(e.getClass().getName(), e.getMessage(), e.getStackTrace()));
        Long takeTime = System.currentTimeMillis() - startTime.get();
        sysLog.setTakeTime(takeTime);
        sysLogMapper.insert(sysLog);
    }

    /**
     * 转换异常信息为字符串
     */
    public String stackTraceToString(String exceptionName, String exceptionMessage, StackTraceElement[] elements) {
        StringBuffer strbuff = new StringBuffer();
        for (StackTraceElement stet : elements) {
            strbuff.append(stet + "\n");
        }
        String message = exceptionName + ":" + exceptionMessage + "\n\t" + strbuff.toString();
        message = substring(message,0 ,2000);
        return message;
    }


    //字符串截取
    public static String substring(String str, int start, int end) {
        if (str == null) {
            return null;
        } else {
            if (end < 0) {
                end += str.length();
            }

            if (start < 0) {
                start += str.length();
            }

            if (end > str.length()) {
                end = str.length();
            }

            if (start > end) {
                return "";
            } else {
                if (start < 0) {
                    start = 0;
                }

                if (end < 0) {
                    end = 0;
                }
                return str.substring(start, end);
            }
        }
    }

    /**
     * 后置通知
     */
    @After("MyLogPointcut()")
    public void after(){
        log.info("后置通知.....");
    }


    //根据HttpServletRequest获取访问者的IP地址
    public static String getIp(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }
}

4.编写测试接口

package com.test.boot.controller;

import com.test.boot.aop.Auth;
import com.test.boot.aop.MyLog;
import com.test.boot.enums.Role;
import com.test.boot.utils.ResultResponse;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author: laz
 * @CreateTime: 2022-12-28  16:38
 * @Version: 1.0
 */
@RestController
@RequestMapping("test")
public class TestController {

    @RequestMapping("testLogAnnotation/{id}")
    @MyLog(value = "测试日志注解接口")
    public ResultResponse testLogAnnotation(@PathVariable("id")int id){
        return ResultResponse.success("id:"+id);
    }
}

5.测试

启动项目,调用该接口,查看输出日志:

在这里插入图片描述
数据库记录:

在这里插入图片描述

注:sql文件已提交至:https://gitee.com/lianaozhe/springboot-aop

AOP实现权限验证

1.创建自定义注解

package com.test.boot.aop;

import com.test.boot.enums.Role;

import java.lang.annotation.*;


/**
 * @Author: laz
 * @CreateTime: 2022-12-28  16:32
 * @Version: 1.0
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Auth {

    Role [] value();
}

Role类:

package com.test.boot.enums;

/**
 * 小程序角色
 * @author chengfengluo
 * @date 2022-12-28  16:36
 */
public enum Role {
    // 普通用户
    ORDINARY_USER("user"),
    // 管理员
    ADMIN("admin");

    private String name;

    Role(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }
}

2.编写切面类

package com.test.boot.annotations;

import com.test.boot.aop.Auth;
import com.test.boot.enums.Role;
import com.test.boot.utils.ResultResponse;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
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.stereotype.Component;

import java.lang.reflect.Method;

/**
 * @author laz
 * @date 2022-12-29 17:34
 */
@Component
@Aspect
@Slf4j
public class AuthAspect {


    /**
     * 切入点:待增强的方法
     */
    @Pointcut("@annotation(com.test.boot.aop.Auth)")
    public void AuthAround() {}

    @Around(value= "AuthAround()")
    public Object checkAuth(ProceedingJoinPoint joinPoint) throws Throwable {

        // 从切面织入点处通过反射机制获取织入点处的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 获取切入点所在的方法
        Method method = signature.getMethod();
        // 获取注解操作
        Auth auth = method.getAnnotation(Auth.class);
        Role[] value = auth.value();

        for (Role role : value) {
            //当注解无user角色的时候,返回错误信息,真实环境中可以通过对应用户的角色,来判断是否可以访问该接口
            if (!role.getName().equals("user")){
                return ResultResponse.fail("暂无权限!");
            }
        }

        return joinPoint.proceed();
    }

}

3.编写测试接口:

package com.test.boot.controller;

import com.test.boot.aop.Auth;
import com.test.boot.aop.MyLog;
import com.test.boot.enums.Role;
import com.test.boot.utils.ResultResponse;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author: laz
 * @CreateTime: 2022-12-28  16:38
 * @Version: 1.0
 */
@RestController
@RequestMapping("test")
public class TestController {

    @RequestMapping("testAuthAnnotation/{id}")
    @MyLog(value = "测试权限注解接口")
    @Auth(Role.ORDINARY_USER)
    public ResultResponse testAuthAnnotation(@PathVariable("id")int id){
        return ResultResponse.success("id:"+id);
    }


}

4.测试

启动项目,访问该接口:

在这里插入图片描述

可以看到,接口请求成功。

更换注解值为‘ADMIN’:

在这里插入图片描述

重启项目,再次访问该接口:

在这里插入图片描述

可以看到,由于切面类设定的业务逻辑,此处无权访问!

再次提供源码地址:https://gitee.com/lianaozhe/springboot-aop

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/128448.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于springboot家政管理系统

大家好✌&#xff01;我是CZ淡陌。一名专注以理论为基础实战为主的技术博主&#xff0c;将再这里为大家分享优质的实战项目&#xff0c;本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目&#xff0c;希望你能有所收获&#xff0c;少走一些弯路…

Python数据容器--字符串(str)

1、字符串的定义 字符串是字符的容器&#xff0c;一个字符串可以存放任意数量的字符。 如&#xff1a;字符串&#xff1a;“itheima”。 2、字符串的相关操作 2.1 字符串的下标索引取值 从前向后&#xff0c;下标从0开始。从后向前&#xff0c;下标从-1开始。 my_str &quo…

【LeetCode每日一题】——611.有效三角形的个数

文章目录一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【解题思路】七【题目提示】八【时间频度】九【代码实现】十【提交结果】一【题目类别】 二分查找 二【题目难度】 中等 三【题目编号】 611.有效三角形的个数 四【题目描述】 给定一…

数据库管理-第五十期 半百(20221230)

数据库管理 2022-12-30第五十期 半百1 半百2 展望3 计划总结第五十期 半百 1 半百 不知不觉&#xff0c;来到了第五十期&#xff0c;自从立了flag以后也几乎保持了一周一篇文章的频率&#xff0c;当然不得不承认&#xff0c;文章质量还是参差不齐的&#xff0c;有好几篇还是挺…

海尔智家:科技引领的目的是让用户生活幸福

随着时代发展&#xff0c;科技从未像今天这样影响着每个人的“幸福感”。因为科技&#xff0c;我们的通讯可以“随时随地”&#xff0c;交通出行可以“咫尺之间”&#xff0c;购物消费可以“跨越国界”......每一项引领科技的诞生&#xff0c;都能让用户的幸福生活更进一步。 …

git远程仓库下载以及上传

一、远程仓库 通常我们并不常亲自建立远程仓库&#xff0c;而是委托给『第三方』。 常见的操作是&#xff0c;先在第三方上创建『远程 Git 文档库』&#xff0c;然后复制&#xff08;Clone&#xff09;出一个『本地 Git 文档库』 1、在托管网站上创建远程仓库 常见的代码托管…

CDGA|自然资源数据治理五部曲,实现数据融合关联、共享可用

在自然资源行业&#xff0c;数据治理是自然资源信息化的工作基础&#xff0c;是推进自然资源治理体系和治理能力现代化进程的必经之路。 为积极应对数据治理新要求&#xff0c;着力解决资源分散、质量不齐、关联不高等问题&#xff0c;围绕自然资源治理对象&#xff0c;构建集…

有哪些好用的设备巡检类的软件?

现今许多企业&#xff0c;尤其是制造业&#xff0c;规模日趋机械化、自动化、大型化、高速化和复杂化&#xff0c;对设备巡检工作的要求越加精细。 因此&#xff0c;选择好的设备巡检软件&#xff0c;是设备管理的关键。为企业提供设备巡检的解决方案&#xff0c;确保设备巡检…

2022国内网络安全事件大盘点

转眼又到了年底&#xff0c;回望过去的一年&#xff0c;网络攻击事件依旧层出不穷&#xff0c;尤其以俄乌战争为代表&#xff0c;网络空间俨然已经成了第二战场。再回看国内&#xff0c;网络攻击导致的大规模数据泄露事件不但给企业带来巨额财务和品牌损失&#xff0c;同时随着…

项目实战-----产品经理要做什么?

文章目录写在前面&#xff1a;兴奋地开始干我在AR的第一个彻头彻尾的需求&#xff0c;发现原来产品经理脑袋竟然要转这么多圈&#xff0c;果然是术业有专攻啊~&#xff0c;但是既然要成为一个全栈工程师&#xff0c;我也来挑战一下吧~第一版显示我学的课还有我教的课&#xff0…

数据报告 | 美国民众健康状况和医疗需求研究报告

在美国&#xff0c;自2020年COVID-19流行开始&#xff0c;以数字健康为关键词的医疗领域正在发⽣⾰命性的变化。全球范围发生的疫情&#xff0c;促进了医疗行业的变革与创新。 本研究将从美国医疗费用和民众疾病等方面&#xff0c;对美国民众健康状况和医疗需求进行分析。 |美…

Android Kotlin之Coroutine(协程)详解

协程是一种并发设计模式&#xff0c;您可以在 Android 平台上使用它来简化异步执行的代码。 在 Android 上&#xff0c;协程有助于管理长时间运行的任务&#xff0c;如果管理不当&#xff0c;这些任务可能会阻塞主线程并导致应用无响应。 协程的优点&#xff1a; 轻量 您可以…

【日常系列】LeetCode《22·回溯1》

数据规模->时间复杂度 <10^4 &#x1f62e;(n^2) <10^7:o(nlogn) <10^8:o(n) 10^8<:o(logn),o(1) 内容 lc 112 和 113【剑指 34】 &#xff1a;路径总和 https://leetcode.cn/problems/path-sum/ https://leetcode.cn/problems/path-sum-ii/ 提示1&#xff1a…

一文分析Linux虚拟化KVM-Qemu(概念篇)

说明&#xff1a; KVM版本&#xff1a;5.9.1 QEMU版本&#xff1a;5.0.0 工具&#xff1a;Source Insight 3.5&#xff0c; Visio 1. 概述 从这篇文章开始&#xff0c;将开始虚拟化的系列研究了&#xff0c;大概会涉及到ARM64虚拟化支持、KVM、QEMU等分析&#xff1b; 虚拟…

小米裁员,我有话说

大家好&#xff0c;我是梦想家Alex 。好久没有为大家分享有关职场人生相关的文章了&#xff0c;最近阳康之后&#xff0c;灵感爆棚&#xff0c;想跟大家好好聊一聊。 如果你问我&#xff0c;近期互联网圈热度最高的事情&#xff0c;我想一定少不了小米和腾讯这两家科技巨头的身…

html实现ip输入框功能代码

伪代码&#xff1a; <style> .login-user {display: flex;color: #000;line-height: 30px; } .login-user input{height: 46px;border-radius: 4px;margin: 0px 4px; } </style><div class"layui-form-item login-user"><inp…

数据可视化②:bi解决方案之大学生就业分析

大学生就业是和我们息息相关的话题&#xff0c;每一位大学生都关注着&#xff0c;我们常常在网络上看到有关大学生就业的话题&#xff0c;比如毕业季的一些讨论。在大一的创新创业课中&#xff0c;我们也了解到自己所学的专业和以后如何就业&#xff0c;往哪方面就业。但我们了…

进程与线程的区别和联系是什么?

进程概念的引入 在多道程序的环境下&#xff0c;程序的并发执行代替了程序的顺序执行。它破坏了程序的封闭性和可再现性&#xff0c;使得程序和计算不再一一对应&#xff0c;而且由于资源共享和程序的并发执行导致在各个程序活动之间可能存在相互制约关系。总之&#xff0c;程…

MobTech秒验 I 寒冬加时,金融行业如何走出阴霾?

互联网流量红利将逝&#xff0c;企业运营重点已从“以产品为重心”转移至“以用户为导向”&#xff0c;而用户时间的碎片化及不聚焦&#xff0c;往往造成了线上运营推广投放后的石沉大海。 当下&#xff0c;迫切需要去做的是对存量数据的精细化管理&#xff0c;企业应打破数据…

Kafka的终极UI工具丨Offset Explorer功能简介

Offset Explorer&#xff08;以前称为Kafka Tool&#xff09;是一个用于管理和使Apache Kafka 集群的GUI应用程序。它提供了一个直观的UI&#xff0c;允许人们快速查看Kafka集群中的对象以及存储在集群主题中的消息。它包含面向开发人员和管理员的功能。 一些关键功能包括&…