使用切面的权限注解,可以重复修饰同一个接口
目录
- 前言
- 设计
- 参考资料
前言
本文是设计一个权限注解,
这个注解可以根据当前登陆人所拥有的权限做出接口拦截,当拥有该接口的权限时 才可以进入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新特性——重复注解.