早期使用spring boot的时候写数据权限是通过使用自定义一个切面@Aspect配合自定义DataScope注解来实现。
DataScopeAspect 类
@Aspect
@Component
public class DataScopeAspect {
@Resource
private SysDeptService sysDeptService;
@Resource
private SysUserRoleService sysUserRoleService;
@Resource
private SysRoleDeptService sysRoleDeptService;
/**
* 配置织入点
*/
@Pointcut("@annotation(cn.harry.common.annotation.DataScope)")
public void dataScopePointCut() {
}
@Before("dataScopePointCut()")
public void doBefore(JoinPoint point) {
handleDataScope(point);
}
protected void handleDataScope(final JoinPoint joinPoint) {
Object params = joinPoint.getArgs()[0];
if (params instanceof Map) {
SysUser user = SecurityUtils.getSysUser();
// 如果不是超级管理员,则进行数据过滤
if (!SecurityUtils.isAdmin(user.getId())) {
// 根据用户ID 获取数据权限标识 如果数据权限包含全部数据 直接返回
List<Integer> dataScopes = sysUserRoleService
.listDataScopesByUserId(user.getId());
if (!CollectionUtils.isEmpty(dataScopes)) {
if (dataScopes.contains(DataScopeEnums.ALL.getKey())) {
return;
}
}
Map map = (Map) params;
map.put(CommonConstant.SQL_FILTER, this.getSQLFilter(user, joinPoint));
}
return;
}
throw new ApiException("数据权限接口,只能是Map类型参数,且不能为NULL");
}
/**
* 获取数据过滤的SQL
* @param user
* @param point
* @return
*/
private String getSQLFilter(SysUser user, JoinPoint point) {
MethodSignature signature = (MethodSignature) point.getSignature();
DataScope dataFilter = signature.getMethod().getAnnotation(DataScope.class);
// 获取表的别名
String tableAlias = dataFilter.tableAlias();
if (StringUtils.isNotBlank(tableAlias)) {
tableAlias += ".";
}
// 部门ID列表
Set<Long> deptIdList = new HashSet<>();
StringBuilder sqlFilter = new StringBuilder();
// 1. 根据用户ID 获取角色列表
List<Long> roleIdList = sysUserRoleService.listRoleIdByUserId(user.getId());
if (!roleIdList.isEmpty()) {
// 2.用户角色对应的部门ID列表
List<Long> idList = sysRoleDeptService.queryDeptIdList(roleIdList);
deptIdList.addAll(idList);
}
// 用户子部门ID列表
if (dataFilter.subDept()) {
List<Long> subDeptIdList = sysDeptService.getSubDeptIdList(user.getDeptId());
deptIdList.addAll(subDeptIdList);
}
sqlFilter.append(" (");
if (!deptIdList.isEmpty()) {
sqlFilter.append(tableAlias).append(dataFilter.deptId()).append(" in(")
.append(StringUtils.join(deptIdList, ",")).append(")");
}
// 没有设置本部门数据权限,也能查询本部门数据 user.getDeptId()
if (dataFilter.user()) {
if (!deptIdList.isEmpty()) {
sqlFilter.append(" or ");
}
sqlFilter.append(tableAlias).append(dataFilter.deptId()).append("=")
.append(user.getDeptId());
}
sqlFilter.append(")");
if ("()".equals(sqlFilter.toString().trim())) {
return null;
}
return sqlFilter.toString();
}
}
DataScope 注解类
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataScope {
/** 表的别名 */
String tableAlias() default "";
/** true:没有本部门数据权限,也能查询本人数据 */
boolean user() default true;
/** true:拥有子部门数据权限 */
boolean subDept() default false;
/** 部门ID */
String deptId() default "dept_id";
/** 用户ID */
String userId() default "user_id";
}
早期Spring Boot 项目中实现项目权限的写法,入参使用map,service层实现时使用wrapper.apply
拼接了sql语句,虽说也能实现数据权限的控制,但同时代码可读性很差.
在整合Spring Boot 3框架的时候,发现MyBatis-Plus 提供了一个数据权限插件,这样我就可以不用破坏它原本的结构,实现数据权限的管理。我们看一下MyBatis-Plus 数据权限插件。
地址: https://baomidou.com/plugins/data-permission/
DataPermissionInterceptor 是 MyBatis-Plus 提供的一个插件,用于实现数据权限控制。它通过拦截执行的 SQL 语句,并动态拼接权限相关的 SQL 片段,来实现对用户数据访问的控制。
DataPermissionInterceptor 的工作原理与租户插件类似,它会在 SQL 执行前拦截 SQL 语句,并根据用户权限动态添加权限相关的 SQL 片段。这样,只有用户有权限访问的数据才会被查询出来。
/**
* 数据权限控制器
*
* @author harry
* @公众号 Harry技术
*/
@Slf4j
public class MybatisPlusDataPermissionHandler implements DataPermissionHandler {
@Override
@SneakyThrows
public Expression getSqlSegment(Expression where, String mappedStatementId) {
log.info(" 数据权限控制器 :{}", mappedStatementId);
Class<?> clazz = Class.forName(mappedStatementId.substring(0, mappedStatementId.lastIndexOf(StringPool.DOT)));
String methodName = mappedStatementId.substring(mappedStatementId.lastIndexOf(StringPool.DOT) + 1);
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
if (method.getName().equals(methodName)) {
DataScope annotation = method.getAnnotation(DataScope.class);
// 如果没有注解或者是超级管理员,直接返回
if (annotation == null || SecurityUtils.isRoot()) {
return where;
}
return dataScopeFilter(annotation, where);
}
}
return where;
}
@SneakyThrows
private Expression dataScopeFilter(DataScope annotation, Expression where) {
String deptAlias = annotation.deptAlias();
String userAlias = annotation.userAlias();
String deptIdColumnName = annotation.deptIdColumnName();
String userIdColumnName = annotation.userIdColumnName();
String deptColumnName = StrUtil.isNotBlank(deptAlias) ? (deptAlias + StringPool.DOT + deptIdColumnName) : deptIdColumnName;
String userColumnName = StrUtil.isNotBlank(userAlias) ? (userAlias + StringPool.DOT + userIdColumnName) : userIdColumnName;
// 获取当前用户的数据权限
Integer dataScope = SecurityUtils.getDataScope();
DataScopeEnums dataScopeEnum = IBaseEnum.getEnumByValue(dataScope, DataScopeEnums.class);
Long deptId, userId;
String appendSqlStr;
switch (dataScopeEnum) {
case ALL:
return where;
case DEPT:
deptId = SecurityUtils.getDeptId();
appendSqlStr = deptColumnName + StringPool.EQUALS + deptId;
break;
case SELF:
userId = SecurityUtils.getUserId();
appendSqlStr = userColumnName + StringPool.EQUALS + userId;
break;
// 默认部门及子部门数据权限
default:
deptId = SecurityUtils.getDeptId();
appendSqlStr = deptColumnName + " IN ( SELECT id FROM sys_dept WHERE id = " + deptId + " OR FIND_IN_SET( " + deptId + " , ancestors ) )";
break;
}
if (StrUtil.isBlank(appendSqlStr)) {
return where;
}
Expression appendExpression = CCJSqlParserUtil.parseCondExpression(appendSqlStr);
if (where == null) {
return appendExpression;
}
return new AndExpression(where, appendExpression);
}
}
再Mapper上使用自定义注解
@Override
@DataScope(userIdColumnName = "id")
List<SysUser> selectList(IPage<SysUser> page, @Param(Constants.WRAPPER) Wrapper<SysUser> queryWrapper);
userIdColumnName = "id"
表示我们将用户ID传递为id
定义权限枚举
/**
* 数据权限枚举
*
* @author harry
* @公众号 Harry技术
*/
@Getter
@AllArgsConstructor
public enum DataScopeEnums implements IBaseEnum<Integer> {
/**
* value 越小,数据权限范围越大
*/
ALL(0, "所有数据权限"),
DEPT_AND_SUB(1, "本部门及子部门数据"),
DEPT(2, "本部门数据权限"),
SELF(3, "本人数据权限"),
;
private final Integer value;
private final String label;
}
定义四类枚举值:根据不同的枚举类型,拼接sql语句
关于上面的数据的实现已经整合到Harry技术
项目中,你可以下载源码学习使用
基于 JDK 17、Spring Boot 3、Spring Security 6、JWT、Redis、Mybatis-Plus、Knife4j等构建后端,基于Vue 3、Element-Plus 、TypeScript等构建前端的分离单体权限管理系统。
gitee: https://gitee.com/harry-tech/harry.git
gitcode: https://gitcode.com/harry-tech/harry.git
gitee: https://gitee.com/harry-tech/harry-vue.git
https://gitcode.com/harry-tech/harry-vue.git
Watermark 水印
暗黑模式
觉着有帮助,给个Star再走呗
公众号搜“Harry技术”,关注我,带你看不一样的人间烟火!
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。