最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

使用Spring AOP做接口權(quán)限校驗(yàn)和日志記錄

 更新時(shí)間:2025年01月03日 16:40:42   作者:南波塞文  
本文介紹了面向切面編程(AOP)的基本概念、應(yīng)用場(chǎng)景及其在Spring中的實(shí)現(xiàn)原理,通過AOP,可以方便地在不修改原有代碼的情況下,實(shí)現(xiàn)日志記錄、權(quán)限校驗(yàn)等功能,以學(xué)生身份證號(hào)查詢接口為例,展示了如何定義權(quán)限注解、切面類以及權(quán)限驗(yàn)證服務(wù),感興趣的朋友一起看看吧

一、AOP 介紹

AOP: 翻譯為面向切面編程(Aspect Oriented Programming),它是一種編程思想,是面向?qū)ο缶幊蹋∣OP)的一種補(bǔ)充。它的目的是在不修改源代碼的情況下給程序動(dòng)態(tài)添加額外功能。

1.1 AOP 應(yīng)用場(chǎng)景

AOP 的使用場(chǎng)景一般有:數(shù)據(jù)源切換、事務(wù)管理、權(quán)限控制、日志記錄等。根據(jù)它的名字我們不難理解,它的實(shí)現(xiàn)很像是將我們要實(shí)現(xiàn)的代碼切入業(yè)務(wù)邏輯中。

它有以下特點(diǎn):

  • 侵入性小,幾乎可以不改動(dòng)原先代碼的情況下把新的功能加入到業(yè)務(wù)中。
  • 實(shí)現(xiàn)方便,使用幾個(gè)注解就可以實(shí)現(xiàn),使系統(tǒng)更容易擴(kuò)展。
  • 更好的復(fù)用代碼,比如事務(wù)日志打印,簡(jiǎn)單邏輯適合所有情況。

1.2 AOP 中的注解

  • @Aspect:切面,表示一個(gè)橫切進(jìn)業(yè)務(wù)的一個(gè)對(duì)象,一般定義為切面類,它里面包含切入點(diǎn)和通知。
  • @Pointcut:切入點(diǎn), 表示需要切入的位置。比如某些類或者某些方法,也就是先定一個(gè)范圍。
  • @Before:前置通知,切入點(diǎn)的方法體執(zhí)行之前執(zhí)行。
  • @Around:環(huán)繞通知,環(huán)繞切入點(diǎn)執(zhí)行也就是把切入點(diǎn)包裹起來(lái)執(zhí)行。
  • @After:后置通知,在切入點(diǎn)正常運(yùn)行結(jié)束后執(zhí)行。
  • @AfterReturning:后置通知,在切入點(diǎn)正常運(yùn)行結(jié)束后執(zhí)行,異常則不執(zhí)行。
  • @AfterThrowing:后置通知,在切入點(diǎn)運(yùn)行異常時(shí)執(zhí)行。

二、權(quán)限校驗(yàn)

需求介紹:開發(fā)一個(gè)接口用于根據(jù)學(xué)生 id 獲取學(xué)生身份證號(hào),接口上需要做權(quán)限校驗(yàn),只有系統(tǒng)管理員或者是機(jī)構(gòu)管理員組織類型的賬號(hào)才能執(zhí)行此接口,其他組織類別及普通成員執(zhí)行此接口,系統(tǒng)提示:沒有權(quán)限。

2.1 定義權(quán)限注解

/**
 * 權(quán)限要求
 * 此注解用于標(biāo)注于接口方法上, 根據(jù)屬性group和role確定接口準(zhǔn)入要求的組織和角色,
 * 標(biāo)注此注解的方法會(huì)被切面{@link com.test.cloud.ig.vision.data.aspect.AuthorityAspect}攔截
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Auth {
    /**
     * 需要滿足的組織類型
     * 默認(rèn)值:{@link GroupType#SYSTEM}
     *
     * @return
     */
    GroupType[] group() default GroupType.SYSTEM;
    /**
     * 需要滿足的角色類型
     * 默認(rèn)值:{@link RoleType#ADMIN}
     *
     * @return
     */
    RoleType[] role() default RoleType.ADMIN;
}

2.2 定義切面類

@Aspect
@Order(1)
@Component
public class AuthorityAspect {
    @Autowired
    AuthorityService authorityService;
    @Pointcut(value = "@annotation(com.coe.cloud.ig.vision.data.annotation.Auth)")
    public void auth() {
    }
    @Before("auth()&&@annotation(auth)")
    public void before(Auth auth) {
        RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
        HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
        // 從請(qǐng)求頭中獲取賬號(hào)id
        String accountId = request.getHeader("accountId");
        // 校驗(yàn)權(quán)限
        authorityService.checkGroupAuthority(Integer.valueOf(accountId), auth.group(), auth.role());
    }
}

2.3 權(quán)限驗(yàn)證服務(wù)

@Service
public class AuthorityService {
    @Autowired
    AccountService accountService;
    /**
     * 判斷賬號(hào)是否有對(duì)應(yīng)的組織操作權(quán)限
     *
     * @param accountId
     * @param groups    滿足條件的組織級(jí)別
     * @param roles     滿足條件的角色
     */
    public void checkGroupAuthority(Integer accountId, GroupType[] groups, RoleType[] roles) {
        // 根據(jù)賬號(hào)ID獲取賬號(hào)信息
        TAccount account = accountService.findById(accountId);
        // 判斷賬號(hào)是否能操作此組織級(jí)別
        List<GroupType> controlGroups = GroupUtil.getControlGroups(GroupType.getByCode(account.getBizType()));
        controlGroups.retainAll(Arrays.asList(groups));
        AssertUtil.isFalse(controlGroups.isEmpty(), ResultCodes.NO_AUTHORITY);
        // 判斷賬號(hào)是否滿足角色要求
        RoleType role = RoleType.getByCode(account.getRole());
        AssertUtil.isTrue(Arrays.asList(roles).contains(role), ResultCodes.NO_AUTHORITY);
    }
}    

2.4 織入切點(diǎn)

/**
 * 學(xué)生接口
 *
 * @author: huangBX
 * @date: 2023/5/24 15:16
 * @description:
 * @version: 1.0
 */
@RestController
@RequestMapping("/student")
public class StudentController {
    @Autowired
    StudentService studentService;
    @Autowired
    AccountService accountService;
    @Autowired
    AuthorityService authorityService;
    /**
     * 通過學(xué)生Id查詢身份證號(hào)
     *
     * @param accountId
     * @param studentId
     * @return
     */
    @GetMapping ("/selectByStudentId")
    @Auth(group = {GroupType.SYSTEM, GroupType.ORGAN}, role = {RoleType.ADMIN})
    public Result<String> idCard(@RequestHeader("accountId") Integer accountId, @RequestParam("studentId") Integer studentId) {
        TAccount account = accountService.findById(accountId);
        AssertUtil.isNotNull(account, ResultCodes.ACCOUNT_NOT_FOUND);
        //校驗(yàn)是否有該學(xué)校的數(shù)據(jù)權(quán)限
        authorityService.checkDataAuthority(accountId, account.getBizId(), GroupType.ORGAN);
        TStudent student = studentService.findById(studentId);
        AssertUtil.isNotNull(student, ResultCodes.STUDENT_NOT_FOUND);
        return Result.success(student.getIdCard());
    }
}   

2.5 測(cè)試

賬號(hào)信息表

role 角色字段若為 MEMBER 訪問接口則提示沒有權(quán)限。

將 MEMBER 改為 ADMIN,重新發(fā)送請(qǐng)求,能夠返回學(xué)生身份證號(hào)信息。

三、日志記錄

3.1 日志切面類

/**
 * Controller日志切面, 用于打印請(qǐng)求相關(guān)日志
 *
 * @author: huangbx
 * @date: 2022/9/30 09:05
 * @description:
 * @version: 1.0
 */
@Aspect
@Component
public class ControllerLogAspect {
    private static final Logger LOGGER = LoggerFactory.getLogger(ControllerLogAspect.class);
    /**
     * 標(biāo)注有@RequestMapping、@PostMapping、@DeleteMapping、@PutMapping、@Override注解的方法
     * 考慮到Feign繼承的情況, 可能實(shí)現(xiàn)類里未必會(huì)有以上注解, 所以對(duì)于標(biāo)有@Override注解的方法, 也納入范圍
     */
    @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping) " +
            "|| @annotation(org.springframework.web.bind.annotation.PostMapping)" +
            "|| @annotation(org.springframework.web.bind.annotation.DeleteMapping)" +
            "|| @annotation(org.springframework.web.bind.annotation.PutMapping)" +
            "|| @annotation(java.lang.Override)")
    public void requestMapping() {
    }
    /**
     * 標(biāo)注有@Controller或@RestController的類的所有方法
     */
    @Pointcut("@within(org.springframework.stereotype.Controller) || @within(org.springframework.web.bind.annotation.RestController)")
    public void controller() {
    }
    @Around("controller()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 請(qǐng)求方法
        String method = request.getMethod();
        // 請(qǐng)求相對(duì)路徑
        String requestURI = request.getRequestURI();
        // 請(qǐng)求參數(shù)
        Map<String, String> parameterMap = getParameterMap(request);
        String parameterStr = buildParamStr(parameterMap);
        // 根據(jù)請(qǐng)求路徑和參數(shù)構(gòu)建請(qǐng)求連接
        String requestURL = parameterStr != null && !parameterStr.isEmpty() ? requestURI + "?" + parameterStr : requestURI;
        // 請(qǐng)求體
        Object[] body = point.getArgs();
        if ("GET".equalsIgnoreCase(method)) {
            LOGGER.info("{} {}", method, requestURL);
        } else {
            LOGGER.info("{} {}, body:{}", method, requestURL, body);
        }
        // 請(qǐng)求處理開始時(shí)間
        long startTime = System.currentTimeMillis();
        Object result = point.proceed();
        // 結(jié)束時(shí)間
        long endTime = System.currentTimeMillis();
        if ("GET".equalsIgnoreCase(method)) {
            LOGGER.info("{} {}, result:{}, cost:{}ms", method, requestURL, result, endTime - startTime);
        } else {
            LOGGER.info("{} {}, body:{}, result:{}, cost:{}ms", method, requestURL, body, result, endTime - startTime);
        }
        return result;
    }
    @AfterThrowing(pointcut = "controller()", throwing = "e")
    public void afterThrowing(JoinPoint point, Throwable e) {
        try {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            // 請(qǐng)求方法
            String method = request.getMethod();
            // 請(qǐng)求相對(duì)路徑
            String requestURI = request.getRequestURI();
            // 請(qǐng)求參數(shù)
            Map<String, String> parameterMap = getParameterMap(request);
            String parameterStr = buildParamStr(parameterMap);
            // 根據(jù)請(qǐng)求路徑和參數(shù)構(gòu)建請(qǐng)求連接
            String requestURL = parameterStr != null && !parameterStr.isEmpty() ? requestURI + "?" + parameterStr : requestURI;
            // 請(qǐng)求體
            Object[] body = point.getArgs();
            if (e instanceof BusinessException) {
                BusinessException exception = (BusinessException) e;
                if ("GET".equalsIgnoreCase(method)) {
                    LOGGER.warn("{} {}, code:{}, msg:{}", method, requestURL, exception.getExceptionCode(), exception.getMessage());
                } else {
                    LOGGER.warn("{} {}, body:{}, code:{}, msg:{}", method, requestURL, body, exception.getExceptionCode(), exception.getMessage());
                }
            } else {
                if ("GET".equalsIgnoreCase(method)) {
                    LOGGER.error("{} {}", method, requestURL, e);
                } else {
                    LOGGER.error("{} {}, body:{}", method, requestURL, body, e);
                }
            }
        } catch (Exception ex) {
            LOGGER.error("執(zhí)行切面afterThrowing方法異常", ex);
        }
    }
    /**
     * 獲取HTTP請(qǐng)求中的參數(shù)
     *
     * @param request
     * @return 參數(shù)鍵值對(duì)
     */
    private Map<String, String> getParameterMap(HttpServletRequest request) {
        Map<String, String> parameterMap = new HashMap<>();
        if (request != null && request.getParameterNames() != null) {
            Enumeration<String> parameterNames = request.getParameterNames();
            while (parameterNames.hasMoreElements()) {
                String parameterName = parameterNames.nextElement();
                String parameterValue = request.getParameter(parameterName);
                parameterMap.put(parameterName, parameterValue);
            }
        }
        return parameterMap;
    }
    /**
     * 根據(jù)請(qǐng)求參數(shù)map構(gòu)建請(qǐng)求參數(shù)字符串, 參數(shù)間采用&分隔
     */
    private String buildParamStr(Map<String, String> parameterMap) {
        if (parameterMap == null || parameterMap.isEmpty()) {
            return null;
        }
        StringBuilder paramBuilder = new StringBuilder();
        parameterMap.forEach((key, value) -> paramBuilder.append(key).append("=").append(value).append("&"));
        return paramBuilder.substring(0, paramBuilder.length() - 1);
    }
}

3.2 異常統(tǒng)一處理

/**
 * 默認(rèn)的Controller切面
 * 主要對(duì)Controller異常進(jìn)行轉(zhuǎn)換, 轉(zhuǎn)換為相應(yīng)的Result進(jìn)行返回
 *
 * @author: huangbx
 * @date: 2022/9/23 16:41
 * @description:
 * @version: 1.0
 */
@RestControllerAdvice
public class DefaultControllerAdvice {
    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultControllerAdvice.class);
    /**
     * BusinessException異常的統(tǒng)一處理
     *
     * @param e
     * @return
     */
    @ExceptionHandler(BusinessException.class)
    @ResponseBody
    public Result handleBizException(BusinessException e) {
        return Result.fail(e.getExceptionCode(), e.getMessage());
    }
    @ExceptionHandler(MissingServletRequestParameterException.class)
    @ResponseBody
    public Result handMissingServletRequestParameterException(MissingServletRequestParameterException e) {
        return Result.fail(ResultCodeEnum.PARAMETER_ERROR.getCode(), "參數(shù)" + e.getParameterName() + "不能為空");
    }
    @ExceptionHandler(MissingRequestHeaderException.class)
    @ResponseBody
    public Result handlMissingRequestHeaderException(MissingRequestHeaderException e) {
        return Result.fail(ResultCodeEnum.PARAMETER_ERROR.getCode(), "請(qǐng)求頭" + e.getHeaderName() + "不能為空");
    }
    @ExceptionHandler({MethodArgumentNotValidException.class})
    public Result handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        BindingResult bindingResult = e.getBindingResult();
        // 判斷異常中是否有錯(cuò)誤信息,如果存在就使用異常中的消息,否則使用默認(rèn)消息
        if (bindingResult.hasErrors()) {
            List<ObjectError> allErrors = bindingResult.getAllErrors();
            if (!allErrors.isEmpty()) {
                // 這里列出了全部錯(cuò)誤參數(shù),按正常邏輯,只需要第一條錯(cuò)誤即可
                FieldError fieldError = (FieldError) allErrors.get(0);
                return Result.fail(ResultCodeEnum.PARAMETER_ERROR.getCode(), fieldError.getDefaultMessage());
            }
        }
        return Result.fail(ResultCodeEnum.PARAMETER_ERROR);
    }
    @ExceptionHandler({BindException.class})
    public Result handleBindException(BindException e) {
        BindingResult bindingResult = e.getBindingResult();
        // 判斷異常中是否有錯(cuò)誤信息,如果存在就使用異常中的消息,否則使用默認(rèn)消息
        if (bindingResult.hasErrors()) {
            List<ObjectError> allErrors = bindingResult.getAllErrors();
            if (!allErrors.isEmpty()) {
                // 這里列出了全部錯(cuò)誤參數(shù),按正常邏輯,只需要第一條錯(cuò)誤即可
                FieldError fieldError = (FieldError) allErrors.get(0);
                return Result.fail(ResultCodeEnum.PARAMETER_ERROR.getCode(), fieldError.getDefaultMessage());
            }
        }
        return Result.fail(ResultCodeEnum.PARAMETER_ERROR);
    }
    /**
     * Exception異常的統(tǒng)一處理
     *
     * @param e
     * @return
     */
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public Result handleOtherException(Exception e) {
        LOGGER.error("unexpected exception", e);
        return Result.fail(ResultCodeEnum.SYSTEM_ERROR);
    }
}

四、AOP 底層原理

Spring AOP(面向切面編程)的實(shí)現(xiàn)原理主要基于動(dòng)態(tài)代理技術(shù),它提供了對(duì)業(yè)務(wù)邏輯各個(gè)方面的關(guān)注點(diǎn)分離和模塊化,使得非功能性需求(如日志記錄、事務(wù)管理、安全檢查、權(quán)限控制等)可以集中管理和維護(hù),而不是分散在各個(gè)業(yè)務(wù)模塊中。

Spring AOP 實(shí)現(xiàn)原理的關(guān)鍵要點(diǎn)如下:

  • JDK 動(dòng)態(tài)代理:對(duì)于實(shí)現(xiàn)了接口的目標(biāo)類,Spring AOP 默認(rèn)使用 JDK 的 java.lang.reflect.Proxy 類來(lái)創(chuàng)建代理對(duì)象。代理對(duì)象會(huì)在運(yùn)行時(shí)實(shí)現(xiàn)代理接口,并覆蓋其中的方法,在方法調(diào)用前后執(zhí)行切面邏輯(即通知 advice)。
  • CGLIB 動(dòng)態(tài)代理:對(duì)于未實(shí)現(xiàn)接口的類,Spring AOP 會(huì)選擇使用 CGLIB 庫(kù)來(lái)生成代理對(duì)象。CGLIB 通過字節(jié)碼技術(shù)創(chuàng)建目標(biāo)類的子類,在子類中重寫目標(biāo)方法并在方法調(diào)用前后插入切面邏輯。

到此這篇關(guān)于使用Spring AOP做接口權(quán)限校驗(yàn)和日志記錄的文章就介紹到這了,更多相關(guān)Spring AOP接口權(quán)限校驗(yàn)和日志記錄內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java詳解IO流創(chuàng)建讀取與寫入操作

    Java詳解IO流創(chuàng)建讀取與寫入操作

    這篇文章主要介紹了Java IO流,同時(shí)也介紹了流中的一些相關(guān)的內(nèi)容,并且通過大量的案例供大家理解。最后通過一些經(jīng)典的案例幫助大家對(duì)前面所學(xué)的知識(shí)做了一個(gè)綜合的應(yīng)用,需要的朋友可以參考一下
    2022-05-05
  • 淺談Java中hashCode的正確求值方法

    淺談Java中hashCode的正確求值方法

    這篇文章主要介紹了淺談Java中hashCode的正確求值方法,分享了相關(guān)代碼示例,小編覺得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下
    2018-02-02
  • 老生常談Eclipse中的BuildPath(必看篇)

    老生常談Eclipse中的BuildPath(必看篇)

    下面小編就為大家?guī)?lái)一篇老生常談Eclipse中的BuildPath(必看篇)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來(lái)看看吧
    2017-06-06
  • 在Linux上搭建一個(gè)Java部署環(huán)境的詳細(xì)步驟

    在Linux上搭建一個(gè)Java部署環(huán)境的詳細(xì)步驟

    這篇文章主要介紹了在Linux上搭建一個(gè)Java部署環(huán)境,安裝jdk有很多種方式,但是我們這里推薦的是使用yum直接安裝openjdk,本文給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2023-01-01
  • Spring Boot整合Seata的過程詳解(AT 模式)

    Spring Boot整合Seata的過程詳解(AT 模式)

    文章介紹了如何在SpringBoot項(xiàng)目中整合Seata框架來(lái)管理跨服務(wù)、跨數(shù)據(jù)庫(kù)的分布式事務(wù),本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧
    2025-10-10
  • 基于Java實(shí)現(xiàn)簡(jiǎn)單貪吃蛇游戲

    基于Java實(shí)現(xiàn)簡(jiǎn)單貪吃蛇游戲

    這篇文章主要為大家詳細(xì)介紹了基于Java實(shí)現(xiàn)簡(jiǎn)單貪吃蛇,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-04-04
  • Java編程通過list接口實(shí)現(xiàn)數(shù)據(jù)的增刪改查代碼示例

    Java編程通過list接口實(shí)現(xiàn)數(shù)據(jù)的增刪改查代碼示例

    這篇文章是介紹Java編程基礎(chǔ)方面的內(nèi)容,涉及l(fā)ist接口的操作,通過list接口實(shí)現(xiàn)對(duì)數(shù)據(jù)的增刪改查的相關(guān)代碼,具有一定參考價(jià)值,需要的朋友可以了解下。
    2017-10-10
  • 淺析Java中內(nèi)部鎖和顯示鎖的使用

    淺析Java中內(nèi)部鎖和顯示鎖的使用

    在java中鎖主要兩類,內(nèi)部鎖synchronized和顯示鎖java.util.concurrent.locks.Lock,本文將來(lái)和大家聊聊這兩者的相關(guān)知識(shí),需要的可以參考一下
    2023-06-06
  • springboot之SpringApplication生命周期和事件機(jī)制解讀

    springboot之SpringApplication生命周期和事件機(jī)制解讀

    這篇文章主要介紹了springboot之SpringApplication生命周期和事件機(jī)制,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-06-06
  • SpringBoot集成Redisson實(shí)現(xiàn)分布式鎖的方法示例

    SpringBoot集成Redisson實(shí)現(xiàn)分布式鎖的方法示例

    這篇文章主要介紹了SpringBoot集成Redisson實(shí)現(xiàn)分布式鎖的方法示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-10-10

最新評(píng)論

五华县| 舟山市| 祁东县| 陇南市| 策勒县| 龙口市| 汉中市| 巴彦县| 克山县| 普格县| 泽普县| 舞阳县| 明星| 灵寿县| 乐业县| 五大连池市| 诸城市| 新余市| 南投县| 资源县| 子长县| 崇仁县| 稻城县| 乳源| 左云县| 仁寿县| 青田县| 马龙县| 桃源县| 白玉县| 大港区| 汝城县| 永济市| 盐边县| 玉树县| 哈密市| 石渠县| 桑日县| 康马县| 翁牛特旗| 海阳市|