本文从本人博客搬运,原文格式更加美观,可以移步原文阅读:若依系统用户权限模型分析
这是一个经典的用户-角色-权限的模型,其中菜单就代表了权限(因为权限就代表能否访问某个资源,菜单可以代表资源),它们互为多对多关系
新增菜单,需要选择上级菜单。菜单分为三类:目录、菜单、按钮
目录表示外层,有一个下拉箭头,点击可以列出子菜单
目录存储到数据库中有如下特点:
parent_id
为0component
组件路径为空perms
权限标识为空菜单表示目录下可点击的模块。其中组件路径代表点击菜单后访问的路径(前端路由使用),权限标识代表访问菜单需要的权限字符串。默认情况下菜单的权限都会包含list
,因为点击菜单后默认来到数据列表页面,需要调用后端数据列表查询接口
按钮表示菜单下的资源,没有路由地址和组件路径(因为按钮在页面内部,不涉及前端路由),权限标识代表点击按钮生效需要的权限字符串
用默认用户登录会拥有所有权限,我们尝试新增一个角色,让其只拥有部分菜单权限
此时数据库sys_role
表会保存这个角色的基本信息
同时sys_role_menu
会保存这个角色与菜单的关联关系,可以发现只要有至少一个子项被选中,父项就会被添加到角色可用菜单中。比如角色管理下只选中了角色查询,那么其父菜单角色管理也会被附带添加
然后再新增一个用户,赋予其测试角色
此时除了在sys_user
表保存用户信息,还会在sys_user_role
表保存用户所关联的角色。这样通过用户->角色->菜单这样的关系,就可以定义用户所有的权限
然后用新创建的用户登录,可以发现只能显示系统管理下的用户管理和角色管理
并且角色管理页面中只有查询相关的按钮,没有增删改相关的按钮
当用户登录成功后,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字段指向部门
新增用户的时候需要选择部门
多对多关系
新增用户时可以选择多个岗位
这里的部门不是指某个用户所属的部门,而是说角色有能访问到哪些部门的数据的权限,它们是多对多对多关系
在角色管理中,可以对角色关联的部门做配置
可以看到,已经预先定义好了一些部门的数据权限范围,如果想精细化配置,可以选择自定义数据权限,这样可以自由选择角色拥有哪些部门数据权限
我们给测试角色选择研发部门权限,那么再用测试用户登录,就只能看到研发部门的数据了
在用户修改角色对应的数据权限后,角色表中会保存对应的数据权限枚举标识
并且角色与部门的中间表中也会保存对应数据
然后在用户管理页面,查询用户列表或者部门树型结构时,会根据用户对应的角色的数据权限过滤查询的结果。以查询用户列表为例,首先在Service中会添加@DataScope
表示需要根据数据权限对数据进行过滤,注解支持的参数如下:
参数 | 类型 | 默认值 | 描述 |
---|---|---|---|
deptAlias | String | 空 | 部门表的别名 |
userAlias | String | 空 | 用户表的别名 |
/**
* 获取用户列表
*/
@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
中会添加部门数据过滤逻辑:
获取注解@DataScope
信息
获取当前登录用户的角色信息
根据角色信息的数据权限类型来判断需要如何过滤,支持以下5种权限类型:
将权限过滤的条件sql语句拼接好,然后获取切入方法的参数,将其强转为BaseEntity
,然后将权限过滤的sql语句赋值给BaseEntity
的params
参数
@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') >= date_format(#{beginTime},'%y%m%d')
if>
<if test="endTime != null and endTime != ''">
AND date_format(u.create_time,'%y%m%d') <= 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
获取