Mybatis 自定义数据权限

吴书松
吴书松
发布于 2025-05-06 / 6 阅读
0

Mybatis 自定义数据权限

mybatis 自定义数据权限

就是在mybatis sql组装时,拼接自己的逻辑进去

1、定义注解

该注解,作用在mapper层 方法上,用来开启数据权限,并识别权限表名,列名

package com.wss.common.mybatis.annotation;

import com.wss.common.mybatis.constant.DbPermissionTypeEnum;

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

/**
 * 数据权限注解,该注解需要添加在mapper层 方法上面
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataPermission {

	/**
	 * 表名:必填,可以填多个
	 */
	String[] table_name() default {};

	/**
	 * 拼接权限字段名称:必填,可以填多个,和table_name对应
	 */
	String[] column_name() default {};

	/**
	 * 权限类型
	 */
	DbPermissionTypeEnum dbPermissionTypeEnum() default DbPermissionTypeEnum.manageUser;

}

2、定义切面,处理自定义注解 DataPermission

主要是用来保存sql拼接前的数据处理,如当前登录用户信息,用户管理的人员信息,方便sql拼接的时候,拼接权限数据

package com.wss.common.mybatis.aspect;

import com.wss.common.core.constant.SecurityConstants;
import com.wss.common.core.domain.dto.SysUserDto;
import com.wss.common.core.exception.BusinessException;
import com.wss.common.core.feign.RemoteUserService;
import com.wss.common.core.handler.LoginUserContextHolder;
import com.wss.common.core.util.R;
import com.wss.common.core.util.SpringContextHolder;
import com.wss.common.mybatis.annotation.DataPermission;
import com.wss.common.mybatis.config.DataPermissionContext;
import com.wss.common.mybatis.config.DataPermissionDto;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;

import java.lang.reflect.Method;
import java.util.List;

/**
 * 数据权限注解切面
 */
@Aspect
public class DataPermissionAspect {

    /**
     * 环绕通知,拦截带有 @DataPermission 注解的方法
     */
    @Around("@annotation(com.wss.common.mybatis.annotation.DataPermission)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        // 获取方法上的 @DataPermission 注解
        DataPermission dataPermission = method.getAnnotation(DataPermission.class);
        if (dataPermission != null) {
			DataPermissionDto dataPermissionDto = new DataPermissionDto();
            // 将注解信息存储在上下文中,供 MyBatis 拦截器使用
			dataPermissionDto.setDataPermission(dataPermission);

			SysUserDto sysUserDto = LoginUserContextHolder.get();
			if(null != sysUserDto){
				if(!sysUserDto.isAdmin()){
					RemoteUserService remoteUserService = SpringContextHolder.getBean(RemoteUserService.class);
					R<List<SysUserDto>> manageDeptUserResult = remoteUserService.getManageDeptUserByUsername(sysUserDto.getUsername(),null,null,null, SecurityConstants.FROM_IN);
					if(!manageDeptUserResult.isSuccess()){
						throw new BusinessException("获取管理用户失败:"+manageDeptUserResult.getMsg());
					}
					dataPermissionDto.setManageUserList(manageDeptUserResult.getData());
				}
			}

			DataPermissionContext.set(dataPermissionDto);
        }

        try {
            // 执行目标方法
            return joinPoint.proceed();
        } finally {
            // 方法执行完毕,清除数据权限上下文,避免内存泄露
            DataPermissionContext.clear();
        }
    }
}

package com.wss.common.mybatis.config;

import com.wss.common.core.domain.dto.SysUserDto;
import com.wss.common.mybatis.annotation.DataPermission;
import lombok.Data;

import java.util.List;

@Data
public class DataPermissionDto {

	private DataPermission dataPermission;
	private List<SysUserDto> manageUserList;

}

定义sql拼接拦截器

自定义类,并实现接口:MultiDataPermissionHandler,重写方法:getSqlSegment 该自定义类,需要放在spring context中


package com.wss.common.mybatis.config;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.handler.MultiDataPermissionHandler;
import com.wss.common.core.domain.dto.SysUserDto;
import com.wss.common.core.handler.LoginUserContextHolder;
import com.wss.common.mybatis.annotation.DataPermission;
import com.wss.common.mybatis.constant.DbPermissionTypeEnum;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Alias;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Table;
import org.apache.ibatis.exceptions.PersistenceException;

import java.util.List;
import java.util.stream.Collectors;

/**
 * Mybatis数据权限拦截器
 */
public class MybatisDataPermissionHandler implements MultiDataPermissionHandler {

	/**
	 * @param table
	 * @param where
	 * @param mappedStatementId com.wss.admin.mapper.xxxxxMapper.selectList
	 * @return
	 */
    @Override
    public Expression getSqlSegment(Table table, Expression where, String mappedStatementId) {
        try {
            // 获取当前线程中的数据权限信息
			DataPermissionDto dataPermissionDto = DataPermissionContext.get();
			if(null == dataPermissionDto){
				return null;
			}
			DataPermission dataPermission = dataPermissionDto.getDataPermission();
			if (dataPermission == null) {
                return null;
            }
			String[] tableNames = dataPermission.table_name();
			if(ArrayUtil.isEmpty(tableNames)){
				return null;
			}
			String[] columnNames = dataPermission.column_name();
			if(ArrayUtil.isEmpty(columnNames)){
				return null;
			}

			if(columnNames.length != 1 && columnNames.length != tableNames.length){
				throw new PersistenceException("注解[DataPermission]参数错误");
			}

			String tableName = table.getName();
			boolean isDoTable = false;
			String columnName = columnNames[0];
			for (int i = 0; i < tableNames.length; i++) {
				if(tableName.equals(tableNames[i])){
					isDoTable = true;
					if(columnNames.length != 1){
						columnName = columnNames[i];
					}
					break;
				}
			}
			if(!isDoTable){
				return null;
			}

			String sqlSegment = "";
			if(DbPermissionTypeEnum.manageUser.equals(dataPermission.dbPermissionTypeEnum())){
				SysUserDto sysUserDto = LoginUserContextHolder.get();
				if(null == sysUserDto){
					return null;
				}
				if(sysUserDto.isAdmin()){
					return null;
				}
				List<SysUserDto> manageUserList = dataPermissionDto.getManageUserList();
				String manageUsernameListStr = "";
				if(CollUtil.isEmpty(manageUserList)){
					manageUsernameListStr = "'@'";
				}else {
					manageUsernameListStr = manageUserList.stream().map(SysUserDto::getUsername).distinct().collect(Collectors.joining("','"));
					manageUsernameListStr = "'"+manageUsernameListStr+"'";
				}
				String alias = null;
				Alias aliasObj = table.getAlias();
				if(null != aliasObj){
					alias = aliasObj.getName();
				}else {
					alias = tableName;
				}
				// a.sys_user_name in ('user1','user2')
				if(StrUtil.isNotBlank(alias)){
					sqlSegment = alias+"."+columnName +" in (" + manageUsernameListStr + ")";
				}else {
					sqlSegment = columnName +" in (" + manageUsernameListStr + ")";
				}
			}

            return CCJSqlParserUtil.parseCondExpression(sqlSegment);
        } catch (JSQLParserException e) {
            e.printStackTrace();
            return null;
        }
    }
}

mapper层接口方法添加注解


@Mapper
public interface SysUserMapper extends BaseMapper<SysUser> {

    	@DataPermission(table_name = {"sys_user","sys_user_dept"},column_name = {"username","user_id","user_id"})
    	List<SysUser> getUser1();
}

效果:


SELECT user_id,
       xxxx,
       dept_id
FROM sys_user
WHERE del_flag = '0'
  AND (user_id = ?)
  AND sys_user.username IN ('ayy09874', 'd_test', 'ces_123', 'abcd_4423234', 'ces_1113');
  

Mybatis自带的一些方法使用

1、重写BaseMapper的自带方法

如:


@Mapper
public interface SysUserMapper extends BaseMapper<SysUser> {
        @DataPermission(table_name = {"sys_user"},column_name = {"username"})
    	List<SysUser> selectList(@Param(Constants.WRAPPER) Wrapper<SysUser> queryWrapper);
}