当前位置: 首页 > news >正文

使用切面的权限注解,可以重复修饰同一个接口

目录

    • 前言
    • 设计
    • 参考资料

前言

本文是设计一个权限注解,

这个注解可以根据当前登陆人所拥有的权限做出接口拦截,当拥有该接口的权限时 才可以进入controller接口 (推荐修饰在controller接口上,因为原则来说没有权限直接就不应该进入controller了)

同时如果同一个接口同时有多个权限的时候,只要其中一个权限生效即可进入

技术是用的spring的切面和注解,以及重复注解技术

权限可以在数据库中配置。

设计

表结构:

  • tmenu表(菜单表):menu_id(菜单id),menu_name(菜单名字)
  • tfunc表(菜单权限表):menu_id(菜单id),func_id(权限标识),func_name(权限名字)
  • trole表(角色表):role_id(角色id),role_name(角色名字)
  • trolemenu表(角色菜单表):roleId(角色id),menu_id(菜单id)
  • trolefunc表(角色权限表):role_id(角色id),menu_id(菜单id),func_id(权限标识)

默认登陆的时候,当前登录域里已经存放了当前登陆人所拥有的菜单以及权限列表

首先定义一个注解类,该类用于修饰controller的接口上,所以Target里设有method,EBPermissionRepeat是用于修饰重复注解的,下个再说明

package com.gdxx.core.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// 如果是非重复注解,吧这行去掉即可
@Repeatable(value = MyPermissionRepeat.class)
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyPermissions {
    /**
     * 菜单id
     * 
     * @return
     */
    public String mid() default "";

    /**
     * 功能点id
     * 
     * @return
     */
    public String fid() default "";

}

EBPermissionRepeat类,用于修饰重复注解,如果是非重复注解则不用加

package com.gdxx.core.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/*
 * 用于定义重复注解
 */
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyPermissionRepeat {
    EBPermissions[] value();
}

如果没有这个类,在修饰controller接口时只能修饰一次

@RestController // restful api
@RequestMapping("/user") // 迁移前缀
public class UserController extends BaseController {
 	@EBPermissions(mid = "users", fid = "user.search")
    @PostMapping("/queryUsers")
    public ResultResponse<IPage<UserDo>> queryUsers(
	
	}
}

有了这个类后就可以重复修饰这个接口了,

@RestController // restful api
@RequestMapping("/user") // 迁移前缀
public class UserController extends BaseController {

	@EBPermissions(mid = "users", fid = "user.xxxxx")
 	@EBPermissions(mid = "users", fid = "user.search")
    @PostMapping("/queryUsers")
    public ResultResponse<IPage<UserDo>> queryUsers(
	
	}
}

接下来我们要使用springaop拦截这个注解,即切点就是这个注解,切面则是在进入修饰的接口前。
同时如果多个权限(user.xxxxx或者user.search)中,当前登陆人已经拥有了任意一个,那么都可以进入

注意下面@Pointcut切点:不仅写了EBPermissions还写了EBPermissionRepeat!是同时拦截了两个!滑块右边是有内容的

/**
 * controller接口权限检查
 * 
 * @author 
 */

@Slf4j
@Aspect
@Component
public class MyPermissionsAspect {
    // 配置织入点,注意此处后面有个||,是同时拦截EBPermissions和EBPermissionRepeat
    // 如果是非重复注解,则把后面的EBPermissionRepeat去掉即可
    @Pointcut("@annotation(com.gdxx.core.annotation.MyPermissions) || @annotation(com.gdxx.core.annotation.MyPermissionRepeat)")
    public void permitPointCut() {
    }

    /**
     * 处理完请求后执行
     *
     * @param joinPoint 切点
     */
    @Before("permitPointCut()")
    public void checkPermission(JoinPoint joinPoint) {
        // 获得注解
        MyPermissions[] epArr = getAnnotation(joinPoint);
        if (epArr == null || epArr.length == 0) {
            log.warn("EBPermissions.getAnnotation() is null");
            return;
        }
        // 获取当前登录的用户
        // 每个系统的不一样,这里自己替换自己系统的获取用户
        UserDto user = SessionHelper.getCurrentUser();

        if (user == null) {
            throw new ServiceException(StatusEnum.UNAUTHORIZED);
        }

        // 是否全部权限中有一个拥有
        boolean existsPassCheck = false;
        for (int i = 0; i < epArr.length; i++) {

            MyPermissions ep = epArr[i];
            String rightId = new StringBuilder()
                    .append(ep.mid()).append("_")
                    .append(ep.fid()).toString();

            // 多个相同的注解修饰,只要有一个符合即可
            // 获取当前登录用户是否拥有该权限
        	// 每个系统的不一样,这里自己替换自己系统的获取用户
            if (SessionHelper.checkUserRight(rightId)) {
                // throw new ServiceException(StatusEnum.METHOD_NOT_ALLOWED);
                existsPassCheck = true;
                break;
            }
        }

		// 抛出异常
        if (existsPassCheck == false) {
        // 如果想被全局捕获的话
        // throw new HttpRequestMethodNotSupportedException();
            throw new ServiceException(StatusEnum.METHOD_NOT_ALLOWED);
        }

    }

    /**
     * 是否存在注解,如果存在就获取
     */
    private MyPermissions[] getAnnotation(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();

        if (method != null) {

			//  重复注解的获取
            return method.getAnnotationsByType(MyPermissions.class);
            // 如果是非重复注解则使用这种,直接返回EBPermissions而不是数组
            // return method.getAnnotation(EBPermissions.class);

        }
        return null;
    }
}

至此完成!
异常还可以用全局异常做捕获(可选)
可以搜索下@RestControllerAdvice这个注解用法,是spring自带的处理异常的解决方案


/**
 * 全局异常处理器
 * 
 * @author rain
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    /**
     * 处理HTTP请求方法不支持异常
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public ResultResponse<Object> handleHttpRequestMethodNotSupportedException(
            HttpRequestMethodNotSupportedException ex,
            HttpServletRequest request) {
        String message = String.format("不支持'%s'请求方法", ex.getMethod());
        log.warn(message + ", 请求路径: {}", request.getRequestURI());
        return ResultResponse.error(StatusEnum.METHOD_NOT_ALLOWED, message).path(request.getRequestURI());
    }
}

参考资料

  • Spring AOP自定义可重复注解没有生效问题.
  • java8新特性——重复注解.
http://www.dtcms.com/a/122056.html

相关文章:

  • vue3腾讯云直播 前端拉流(前端页面展示直播)
  • Green-AI-Resources开源程序是用于环境可持续 AI 开发和部署的精选研究、工具和最佳实践集合
  • centos-LLM-生物信息-BioGPT安装
  • RecyclerView 和 ListView从 设计理念、性能优化 和 扩展能力 三个维度展开分析
  • 基于开源 AI 大模型 AI 智能名片 S2B2C 商城小程序的京城首家无人智慧书店创新模式研究
  • 编码常见的 3类 23种设计模式——学习笔记
  • python处理excel文件
  • 127.0.0.1本地环回地址(Loopback Address)
  • LeetCode 相交链表题解:双指针的精妙应用
  • 我的NISP二级之路-04
  • 系统分析师(二)--操作系统
  • CD24.【C++ Dev】类和对象(15)初始化列表(下)和对象隐式类型转换
  • 深入理解Spring是如何解决循环依赖的
  • [250409] GitHub Copilot 全面升级,推出AI代理模式,可支援MCP | Devin 2.0 发布
  • 数据库管理工具实战:IDEA 与 DBeaver 连接 TDengine(一)
  • Vue2-实现elementUI的select全选功能
  • 卷积神经网络(CNN)基础
  • MicroPython 开发ESP32应用教程 之 WIFI、BLE共用常见问题处理及中断处理函数注意事项
  • 基于视觉密码的加密二值图像可逆数据隐藏
  • 颠覆传统!复旦微软联合研发MagicMotion,重新定义图生视频可能性
  • 品牌出海新思路:TikTok Shop东南亚FACT经营矩阵实操指南
  • 游戏开发中 C#、Python 和 C++ 的比较
  • 六、继承(二)
  • JavaScript学习教程,从入门到精通,JavaScript 运算符及语法知识点详解(8)
  • 2025年Java无服务器架构实战:AWS Lambda与Spring Cloud Function深度整合
  • uniapp 打包 H5 向 打包的APP 使用 @dcloudio/uni-webview-js 传值
  • 数据结构实验4.3:利用队列实现杨辉三角的输出
  • BOTA六维力矩传感器在三层AI架构中的集成实践:从数据采集到力控闭环
  • 绿算技术团队受邀出席英伟达GTC2025大会丨重塑AI存储新范式
  • 【android bluetooth 框架分析 01】【关键线程 3】【bt_jni_thread 线程介绍】