本文从本人博客搬运,原文格式更加美观,可以移步原文阅读:若依系统用户权限模型分析

用户-角色-菜单

1.基本使用

这是一个经典的用户-角色-权限的模型,其中菜单就代表了权限(因为权限就代表能否访问某个资源,菜单可以代表资源),它们互为多对多关系

新增菜单,需要选择上级菜单。菜单分为三类:目录、菜单、按钮

目录表示外层,有一个下拉箭头,点击可以列出子菜单

目录存储到数据库中有如下特点:

  • parent_id为0
  • component组件路径为空
  • perms权限标识为空

菜单表示目录下可点击的模块。其中组件路径代表点击菜单后访问的路径(前端路由使用),权限标识代表访问菜单需要的权限字符串。默认情况下菜单的权限都会包含list,因为点击菜单后默认来到数据列表页面,需要调用后端数据列表查询接口

按钮表示菜单下的资源,没有路由地址和组件路径(因为按钮在页面内部,不涉及前端路由),权限标识代表点击按钮生效需要的权限字符串

用默认用户登录会拥有所有权限,我们尝试新增一个角色,让其只拥有部分菜单权限

此时数据库sys_role表会保存这个角色的基本信息

同时sys_role_menu会保存这个角色与菜单的关联关系,可以发现只要有至少一个子项被选中,父项就会被添加到角色可用菜单中。比如角色管理下只选中了角色查询,那么其父菜单角色管理也会被附带添加

然后再新增一个用户,赋予其测试角色

此时除了在sys_user表保存用户信息,还会在sys_user_role表保存用户所关联的角色。这样通过用户->角色->菜单这样的关系,就可以定义用户所有的权限

然后用新创建的用户登录,可以发现只能显示系统管理下的用户管理和角色管理

并且角色管理页面中只有查询相关的按钮,没有增删改相关的按钮

2.原理分析

当用户登录成功后,redis中会保存用户的信息,其中就包含了角色和权限字符串的信息

{
    "@type": "com.ruoyi.common.core.domain.model.LoginUser",
    "accountNonExpired": true,
    "accountNonLocked": true,
    "browser": "Chrome 8",
    "credentialsNonExpired": true,
    "enabled": true,
    "expireTime": 1610099630922,
    "ipaddr": "127.0.0.1",
    "loginLocation": "内网IP",
    "loginTime": 1610097830922,
    "os": "Windows 10",
    "password": "$2a$10$QJAQWU5OYLWE609iM5Tr5O1KXjbLMX8TqU6wp5kqf0/UjE58HWpQ6",
    "permissions": [  // 用户关联的所有菜单的权限字符串
        "system:user:resetPwd",
        "system:user:export",
        "system:user:list",
        "system:user:remove",
        "system:role:list",
        "system:user:import",
        "system:user:edit",
        "system:role:query",
        "system:user:query",
        "system:user:add"
    ],
    "token": "08c70ed8-3283-4bcb-8e14-d239ae93f7d2",
    "user": {
        "admin": false,
        "avatar": "",
        "createBy": "admin",
        "createTime": 1599702165000,
        "delFlag": "0",
        "dept": {
            "children": [],
            "deptId": 103,
            "deptName": "研发部门",
            "leader": "若依",
            "orderNum": "1",
            "params": {},
            "parentId": 101,
            "status": "0"
        },
        "deptId": 103,
        "email": "",
        "loginIp": "",
        "nickName": "baobao",
        "params": {},
        "password": "$2a$10$QJAQWU5OYLWE609iM5Tr5O1KXjbLMX8TqU6wp5kqf0/UjE58HWpQ6",
        "phonenumber": "",
        "roles": [  // 用户所拥有的角色
            {
                "admin": false,
                "dataScope": "2",
                "flag": false,
                "params": {},
                "roleId": 3,
                "roleKey": "test",
                "roleName": "测试",
                "roleSort": "2",
                "status": "0"
            }
        ],
        "sex": "0",
        "status": "0",
        "userId": 3,
        "userName": "包包"
    },
    "username": "包包"
}

登录成功来到首页时,会发起/getInfo请求,获取用户信息,包含了权限和角色信息

/**
     * 获取用户信息
     * 
     * @return 用户信息
     */
@GetMapping("getInfo")
public AjaxResult getInfo()
{
    // 从请求头中获取token,解析token后获得uuid,根据uuid从redis中查询关联的用户信息
    LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
    SysUser user = loginUser.getUser();
    // 跳转1:获取用户角色集合
    Set<String> roles = permissionService.getRolePermission(user);
    // 跳转2:获取用户权限集合
    Set<String> permissions = permissionService.getMenuPermission(user);
    AjaxResult ajax = AjaxResult.success();
    ajax.put("user", user);
    ajax.put("roles", roles);
    ajax.put("permissions", permissions);
    return ajax;
}

// 跳转1:获取用户角色集合
public Set<String> getRolePermission(SysUser user)
{
    Set<String> roles = new HashSet<String>();
    // 管理员拥有所有权限
    if (user.isAdmin())
    {
        roles.add("admin");
    }
    else
    {
        // 跳转3:从数据库查询该用户的所有角色
        roles.addAll(roleService.selectRolePermissionByUserId(user.getUserId()));
    }
    return roles;
}

// 跳转3:从数据库查询该用户的所有角色
@Override
public Set<String> selectRolePermissionByUserId(Long userId)
{
    // 查询用户关联的所有角色
    List<SysRole> perms = roleMapper.selectRolePermissionByUserId(userId);
    Set<String> permsSet = new HashSet<>();
    // 遍历角色,将所有角色字符串收集成一个Set返回
    for (SysRole perm : perms)
    {
        if (StringUtils.isNotNull(perm))
        {
            permsSet.addAll(Arrays.asList(perm.getRoleKey().trim().split(",")));
        }
    }
    return permsSet;
}

// 跳转2:获取用户权限集合
public Set<String> getMenuPermission(SysUser user)
{
    Set<String> perms = new HashSet<String>();
    // 管理员拥有所有权限
    if (user.isAdmin())
    {
        perms.add("*:*:*");
    }
    else
    {
        // 跳转4:查询该用户所有权限
        perms.addAll(menuService.selectMenuPermsByUserId(user.getUserId()));
    }
    return perms;
}

// 跳转4:查询该用户所有权限
@Override
public Set<String> selectMenuPermsByUserId(Long userId)
{
    // 查询该用户的所有权限字符串
    List<String> perms = menuMapper.selectMenuPermsByUserId(userId);
    Set<String> permsSet = new HashSet<>();
    for (String perm : perms)
    {
        if (StringUtils.isNotEmpty(perm))
        {
            permsSet.addAll(Arrays.asList(perm.trim().split(",")));
        }
    }
    return permsSet;
}

然后会调用/getRouters查询该用户有访问权限的目录菜单的路由数据(不包含按钮,因为按钮只有点击进入具体的菜单后才会决定显示或不显示),根据获取的路由数据动态展示该用户有权限的目录与菜单

/**
     * 获取路由信息
     * 
     * @return 路由信息
     */
@GetMapping("getRouters")
public AjaxResult getRouters()
{
    // 解析请求头的token,从redis中获取用户信息
    LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
    // 用户信息
    SysUser user = loginUser.getUser();
    // 跳转1:查询用户关联的所有菜单
    List<SysMenu> menus = menuService.selectMenuTreeByUserId(user.getUserId());
    // 跳转5:根据菜单树型结构构建返回给前端的路由
    return AjaxResult.success(menuService.buildMenus(menus));
}

// 跳转1:查询用户关联的所有菜单
@Override
public List<SysMenu> selectMenuTreeByUserId(Long userId)
{
    List<SysMenu> menus = null;
    // 如果是admin,直接获取所有菜单
    if (SecurityUtils.isAdmin(userId))
    {
        // 获取所有目录和菜单的集合(不包含按钮)
        /*select distinct m.menu_id, m.parent_id, m.menu_name, m.path, m.component, m.visible, m.status, ifnull(m.perms,'') as perms, 				m.is_frame, m.menu_type, m.icon, m.order_num, m.create_time
		from sys_menu m where m.menu_type in ('M', 'C') and m.status = 0
		order by m.parent_id, m.order_num*/
        menus = menuMapper.selectMenuTreeAll();
    }
    else
    {
        // 获取指定用户拥有的目录和菜单的集合
        /*select distinct m.menu_id, m.parent_id, m.menu_name, m.path, m.component, m.visible, m.status, ifnull(m.perms,'') as perms, 				m.is_frame, m.menu_type, m.icon, m.order_num, m.create_time
		from sys_menu m
			 left join sys_role_menu rm on m.menu_id = rm.menu_id
			 left join sys_user_role ur on rm.role_id = ur.role_id
			 left join sys_role ro on ur.role_id = ro.role_id
			 left join sys_user u on ur.user_id = u.user_id
		where u.user_id = #{userId} and m.menu_type in ('M', 'C') and m.status = 0  AND ro.status = 0
		order by m.parent_id, m.order_num*/
        menus = menuMapper.selectMenuTreeByUserId(userId);
    }
    // 跳转2:获取每个目录或菜单的子项,从一级目录开始构建树型结构
    return getChildPerms(menus, 0);
}

// 跳转2:获取每个目录或菜单的子项,构建树型结构
public List<SysMenu> getChildPerms(List<SysMenu> list, int parentId)
{
    List<SysMenu> returnList = new ArrayList<SysMenu>();
    for (Iterator<SysMenu> iterator = list.iterator(); iterator.hasNext();)
    {
        SysMenu t = (SysMenu) iterator.next();
        // 一、根据传入的某个父节点ID,遍历该父节点的所有子节点
        if (t.getParentId() == parentId)
        {
            // 跳转3:递归构建每个节点的子节点
            recursionFn(list, t);
            returnList.add(t);
        }
    }
    return returnList;
}

// 跳转3:递归构建每个节点的子节点
private void recursionFn(List<SysMenu> list, SysMenu t)
{
    // 跳转4:得到t的子节点列表
    List<SysMenu> childList = getChildList(list, t);
    // 设置t的子节点列表
    t.setChildren(childList);
    // 遍历子节点,递归构建子节点的子节点
    for (SysMenu tChild : childList)
    {
        // 如果有子节点
        if (hasChild(list, tChild))
        {
            recursionFn(list, tChild);
        }
    }
}

// 跳转4:得到t的子节点列表
private List<SysMenu> getChildList(List<SysMenu> list, SysMenu t)
{
    List<SysMenu> tlist = new ArrayList<SysMenu>();
    Iterator<SysMenu> it = list.iterator();
    while (it.hasNext())
    {
        SysMenu n = (SysMenu) it.next();
        if (n.getParentId().longValue() == t.getMenuId().longValue())
        {
            tlist.add(n);
        }
    }
    return tlist;
}

// 跳转5:根据菜单树型结构构建返回给前端的路由
@Override
public List<RouterVo> buildMenus(List<SysMenu> menus)
{
    List<RouterVo> routers = new LinkedList<RouterVo>();
    for (SysMenu menu : menus)
    {
        RouterVo router = new RouterVo();
        // 设置是否可见
        router.setHidden("1".equals(menu.getVisible()));
        // 跳转6:设置路由名称
        router.setName(getRouteName(menu));
        // 跳转7:设置路径
        router.setPath(getRouterPath(menu));
        // 跳转8:设置组件路径
        router.setComponent(getComponent(menu));
        // 设置路由元数据
        router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon()));
        List<SysMenu> cMenus = menu.getChildren();
        // 如果菜单类型是目录M,并且子节点大于0
        if (!cMenus.isEmpty() && cMenus.size() > 0 && UserConstants.TYPE_DIR.equals(menu.getMenuType()))
        {
            router.setAlwaysShow(true);
            router.setRedirect("noRedirect");
            // 设置路由子节点
            router.setChildren(buildMenus(cMenus));
        }
        // 如果是最外层,并且类型是菜单C,说明它不应该包含子节点
        else if (isMeunFrame(menu))
        {
            List<RouterVo> childrenList = new ArrayList<RouterVo>();
            RouterVo children = new RouterVo();
            children.setPath(menu.getPath());
            children.setComponent(menu.getComponent());
            children.setName(StringUtils.capitalize(menu.getPath()));
            children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon()));
            childrenList.add(children);
            // 直接将该节点作为路由子节点
            router.setChildren(childrenList);
        }
        routers.add(router);
    }
    return routers;
}

// 跳转6:设置路由名称
public String getRouteName(SysMenu menu)
{
    // 获取菜单的path字段
    String routerName = StringUtils.capitalize(menu.getPath());
    // 非外链并且是一级菜单(类型为菜单)
    if (isMeunFrame(menu))
    {
        routerName = StringUtils.EMPTY;
    }
    return routerName;
}

// 跳转7:设置路径
public String getRouterPath(SysMenu menu)
{
    String routerPath = menu.getPath();
    // 非外链并且是一级目录(类型为目录)
    if (0 == menu.getParentId().intValue() && UserConstants.TYPE_DIR.equals(menu.getMenuType())
        && UserConstants.NO_FRAME.equals(menu.getIsFrame()))
    {
        routerPath = "/" + menu.getPath();
    }
    // 非外链并且是一级目录(类型为菜单)
    else if (isMeunFrame(menu))
    {
        routerPath = "/";
    }
    return routerPath;
}

// 跳转8:设置组件路径
public String getComponent(SysMenu menu)
{
    String component = UserConstants.LAYOUT;
    if (StringUtils.isNotEmpty(menu.getComponent()) && !isMeunFrame(menu))
    {
        component = menu.getComponent();
    }
    return component;
}

点击具体的目录下的菜单时,前端会根据已经获取的用户权限信息,动态判断菜单中的按钮是否要显示

当然,以上只是做了前端的权限功能,无法防止绕过前端调用无权限的接口。后端的权限校验在每个接口上用@PreAuthorize实现

@PreAuthorize中用了自定义类来校验权限。原理是获取redis中已登录用户信息,判断用户是否有该接口上标注的权限或者角色

/**
 * RuoYi首创 自定义权限实现,ss取自SpringSecurity首字母
 * 
 * @author ruoyi
 */
@Service("ss")
public class PermissionService
{
    /** 所有权限标识 */
    private static final String ALL_PERMISSION = "*:*:*";

    /** 管理员角色权限标识 */
    private static final String SUPER_ADMIN = "admin";

    private static final String ROLE_DELIMETER = ",";

    private static final String PERMISSION_DELIMETER = ",";

    @Autowired
    private TokenService tokenService;

    /**
     * 验证用户是否具备某权限
     * 
     * @param permission 权限字符串
     * @return 用户是否具备某权限
     */
    public boolean hasPermi(String permission)
    {
        if (StringUtils.isEmpty(permission))
        {
            return false;
        }
        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
        if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions()))
        {
            return false;
        }
        return hasPermissions(loginUser.getPermissions(), permission);
    }

    /**
     * 验证用户是否不具备某权限,与 hasPermi逻辑相反
     *
     * @param permission 权限字符串
     * @return 用户是否不具备某权限
     */
    public boolean lacksPermi(String permission)
    {
        return hasPermi(permission) != true;
    }

    /**
     * 验证用户是否具有以下任意一个权限
     *
     * @param permissions 以 PERMISSION_NAMES_DELIMETER 为分隔符的权限列表
     * @return 用户是否具有以下任意一个权限
     */
    public boolean hasAnyPermi(String permissions)
    {
        if (StringUtils.isEmpty(permissions))
        {
            return false;
        }
        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
        if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions()))
        {
            return false;
        }
        Set<String> authorities = loginUser.getPermissions();
        for (String permission : permissions.split(PERMISSION_DELIMETER))
        {
            if (permission != null && hasPermissions(authorities, permission))
            {
                return true;
            }
        }
        return false;
    }

    /**
     * 判断用户是否拥有某个角色
     * 
     * @param role 角色字符串
     * @return 用户是否具备某角色
     */
    public boolean hasRole(String role)
    {
        if (StringUtils.isEmpty(role))
        {
            return false;
        }
        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
        if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles()))
        {
            return false;
        }
        for (SysRole sysRole : loginUser.getUser().getRoles())
        {
            String roleKey = sysRole.getRoleKey();
            if (SUPER_ADMIN.contains(roleKey) || roleKey.contains(StringUtils.trim(role)))
            {
                return true;
            }
        }
        return false;
    }

    /**
     * 验证用户是否不具备某角色,与 isRole逻辑相反。
     *
     * @param role 角色名称
     * @return 用户是否不具备某角色
     */
    public boolean lacksRole(String role)
    {
        return hasRole(role) != true;
    }

    /**
     * 验证用户是否具有以下任意一个角色
     *
     * @param roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表
     * @return 用户是否具有以下任意一个角色
     */
    public boolean hasAnyRoles(String roles)
    {
        if (StringUtils.isEmpty(roles))
        {
            return false;
        }
        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
        if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles()))
        {
            return false;
        }
        for (String role : roles.split(ROLE_DELIMETER))
        {
            if (hasRole(role))
            {
                return true;
            }
        }
        return false;
    }

    /**
     * 判断是否包含权限
     * 
     * @param permissions 权限列表
     * @param permission 权限字符串
     * @return 用户是否具备某权限
     */
    private boolean hasPermissions(Set<String> permissions, String permission)
    {
        return permissions.contains(ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission));
    }
}

用户-部门

多对1关系,用户表中有dept_id字段指向部门

新增用户的时候需要选择部门

用户-岗位

多对多关系

新增用户时可以选择多个岗位

用户-角色-部门

1.使用演示

这里的部门不是指某个用户所属的部门,而是说角色有能访问到哪些部门的数据的权限,它们是多对多对多关系

在角色管理中,可以对角色关联的部门做配置

可以看到,已经预先定义好了一些部门的数据权限范围,如果想精细化配置,可以选择自定义数据权限,这样可以自由选择角色拥有哪些部门数据权限

我们给测试角色选择研发部门权限,那么再用测试用户登录,就只能看到研发部门的数据了

2.原理分析

在用户修改角色对应的数据权限后,角色表中会保存对应的数据权限枚举标识

并且角色与部门的中间表中也会保存对应数据

然后在用户管理页面,查询用户列表或者部门树型结构时,会根据用户对应的角色的数据权限过滤查询的结果。以查询用户列表为例,首先在Service中会添加@DataScope表示需要根据数据权限对数据进行过滤,注解支持的参数如下:

参数类型默认值描述
deptAliasString部门表的别名
userAliasString用户表的别名
/**
     * 获取用户列表
     */
@PreAuthorize("@ss.hasPermi('system:user:list')")
@GetMapping("/list")
public TableDataInfo list(SysUser user)
{
    startPage();
    List<SysUser> list = userService.selectUserList(user);
    return getDataTable(list);
}

/**
     * 根据条件分页查询用户列表
     * 
     * @param user 用户信息
     * @return 用户信息集合信息
     */
@Override
@DataScope(deptAlias = "d", userAlias = "u") // 部门及用户权限注解,其中d和u用来表示表的别名
public List<SysUser> selectUserList(SysUser user)
{
    return userMapper.selectUserList(user);
}

然后在对应的数据权限切面类DataScopeAspect中会添加部门数据过滤逻辑:

  1. 获取注解@DataScope信息

  2. 获取当前登录用户的角色信息

  3. 根据角色信息的数据权限类型来判断需要如何过滤,支持以下5种权限类型:

    • 全部数据权限
    • 自定数据权限
    • 部门数据权限
    • 部门及以下数据权限
    • 仅本人数据权限
  4. 将权限过滤的条件sql语句拼接好,然后获取切入方法的参数,将其强转为BaseEntity,然后将权限过滤的sql语句赋值给BaseEntityparams参数

@Aspect
@Component
public class DataScopeAspect
{
    /**
     * 全部数据权限
     */
    public static final String DATA_SCOPE_ALL = "1";

    /**
     * 自定数据权限
     */
    public static final String DATA_SCOPE_CUSTOM = "2";

    /**
     * 部门数据权限
     */
    public static final String DATA_SCOPE_DEPT = "3";

    /**
     * 部门及以下数据权限
     */
    public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";

    /**
     * 仅本人数据权限
     */
    public static final String DATA_SCOPE_SELF = "5";

    /**
     * 数据权限过滤关键字
     */
    public static final String DATA_SCOPE = "dataScope";

    // 配置织入点:切入到所有标有@DataScope注解的方法
    @Pointcut("@annotation(com.ruoyi.common.annotation.DataScope)")
    public void dataScopePointCut()
    {
    }

    // 方法执行前进行增强
    @Before("dataScopePointCut()")
    public void doBefore(JoinPoint point) throws Throwable
    {
        // 处理增强逻辑
        handleDataScope(point);
    }

    protected void handleDataScope(final JoinPoint joinPoint)
    {
        // 获得@DataScope注解
        DataScope controllerDataScope = getAnnotationLog(joinPoint);
        if (controllerDataScope == null)
        {
            return;
        }
        // 获取当前的用户
        LoginUser loginUser = SpringUtils.getBean(TokenService.class).getLoginUser(ServletUtils.getRequest());
        SysUser currentUser = loginUser.getUser();
        if (currentUser != null)
        {
            // 如果是超级管理员,则不过滤数据
            if (!currentUser.isAdmin())
            {
                // 生成过滤部门的条件sql语句,传入@DataScope中定义的用户、部门表别名
                dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),
                        controllerDataScope.userAlias());
            }
        }
    }

    /**
     * 数据范围过滤
     *
     * @param joinPoint 切点
     * @param user 用户
     * @param userAlias 别名
     */
    public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias)
    {
        StringBuilder sqlString = new StringBuilder();
		// 遍历用户的所有角色
        for (SysRole role : user.getRoles())
        {
            // 获取角色的数据权限
            String dataScope = role.getDataScope();
            // 1.全部数据权限
            if (DATA_SCOPE_ALL.equals(dataScope))
            {
                sqlString = new StringBuilder();
                break;
            }
            // 2.自定数据权限
            else if (DATA_SCOPE_CUSTOM.equals(dataScope))
            {
                // 从角色部门中间表查询要过滤的部门
                sqlString.append(StringUtils.format(
                        " OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias,
                        role.getRoleId()));
            }
            // 3.部门数据权限
            else if (DATA_SCOPE_DEPT.equals(dataScope))
            {
                sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));
            }
            // 4.部门及以下数据权限
            else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope))
            {
                sqlString.append(StringUtils.format(
                        " OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )",
                        deptAlias, user.getDeptId(), user.getDeptId()));
            }
            // 5.仅本人数据权限
            else if (DATA_SCOPE_SELF.equals(dataScope))
            {
                if (StringUtils.isNotBlank(userAlias))
                {
                    sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));
                }
                else
                {
                    // 数据权限为仅本人且没有userAlias别名不查询任何数据
                    sqlString.append(" OR 1=0 ");
                }
            }
        }

        if (StringUtils.isNotBlank(sqlString.toString()))
        {
            // 获取切入方法的参数
            Object params = joinPoint.getArgs()[0];
            // 如果参数不为空,并且是BaseEntity的实例
            if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
            {
                // 强转为BaseEntity
                BaseEntity baseEntity = (BaseEntity) params;
                // 将过滤部门的条件sql语句存入BaseEntity的Map类型的属性params中,key为dataScope
                baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
            }
        }
    }

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

        if (method != null)
        {
            return method.getAnnotation(DataScope.class);
        }
        return null;
    }
}

经过上述切面处理后,Service中方法的参数中就保存了过滤部门的sql语句,然后在mapper中实际进行查询时,查询语句的最后取出参数中的sql语句拼接上即可:${params.dataScope}

<select id="selectUserList" parameterType="SysUser" resultMap="SysUserResult">
    select u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, d.dept_name, d.leader from sys_user u
    left join sys_dept d on u.dept_id = d.dept_id
    where u.del_flag = '0'
    <if test="userName != null and userName != ''">
        AND u.user_name like concat('%', #{userName}, '%')
    </if>
    <if test="status != null and status != ''">
        AND u.status = #{status}
    </if>
    <if test="phonenumber != null and phonenumber != ''">
        AND u.phonenumber like concat('%', #{phonenumber}, '%')
    </if>
    <if test="beginTime != null and beginTime != ''"><!-- 开始时间检索 -->
        AND date_format(u.create_time,'%y%m%d') &gt;= date_format(#{beginTime},'%y%m%d')
    </if>
    <if test="endTime != null and endTime != ''"><!-- 结束时间检索 -->
        AND date_format(u.create_time,'%y%m%d') &lt;= date_format(#{endTime},'%y%m%d')
    </if>
    <if test="deptId != null and deptId != 0">
        AND (u.dept_id = #{deptId} OR u.dept_id IN ( SELECT t.dept_id FROM sys_dept t WHERE FIND_IN_SET (#{deptId},ancestors) ))
    </if>
    <!-- 数据范围过滤 -->
    ${params.dataScope}
</select>

从上面的原理分析中可以得出结论:

仅实体继承BaseEntity才会进行处理,SQL语句会存放到BaseEntity对象中的params属性中供xml参数params.dataScope获取

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐