初出茅庐.开胃菜·代码思路
大约 5 分钟
初出茅庐.开胃菜·代码思路
数据权限技术调研
什么是数据权限
数据权限无非就是某人只能看到某些数据,这些数据是可能是属于自己直接操作的,也可能是间接操作的。
场景1、 业务员A在业务员B某个订单上查看了某某客户对某某产品的销售单价,在某某搜搜框中搜出其他业务员的订单信息,可以随意的查看其他人的订单信息,其他人的业务员客户信息,其他人负责的产品信息,这无疑不是对系统健全的挑战。
场景2、 某业务主管看到公司某产品的平均毛利率,某客户的平均毛利率,重则带着下面一群小弟出去创业,称为公司的竞争对手,这无疑会对公司造成损失。
某业务员A想知道自己有哪些客户,于是打开了客户管理界面,看到的客户全是自己相关的,假如没有数据权限的设定 ,会看到有部分不属于自己负责客户。如何统计出某业务主管自己部门销售量多的销售呢?
有哪些数据权限
数据权限可以为后期操作做出等级的分级。CRM系统中数据权限有五种:
一、全部,当选择全部时,该员工会看到所有的数据信息,多用于超级管理员或总裁级别;
二、本部门及下属部门,该员工会看到本部门的及所有子部门的数据信息,多用于总监级别;
三、仅本部门,该员工只能看到本部门的数据信息,多用于部门经理级别;
四、自己发布或参与的,该员工只能看到和自己有关的数据信息,不会看到别人的数据信息,多用于普通员工;
五、自定义部门,后期该员工可以看到自定义的部门有关的数据信息。
如何实现数据权限
1.数据权限是和角色进行绑定的
对应的页面是:系统管理->权限管理->角色管理
2.后端使用AOP+自定义注解实现数据权限
2.1 利用sql实现数据权限
2.1.1 举例:假如我要查询线索表里的数据
SELECT
clue.*,
r.create_by AS assign_by,
r.user_name AS OWNER,
r.create_time AS owner_time
FROM
tb_clue clue
LEFT JOIN tb_assign_record r ON clue.id = r.assign_id
WHERE
r.latest = '1'
AND r.type = '0'
AND clue. STATUS IN ('1', '2')
ORDER BY
clue.create_time DESC
2.1.2 全部数据权限的SQL
SELECT
clue.*,
r.create_by AS assign_by,
r.user_name AS OWNER,
r.create_time AS owner_time
FROM
tb_clue clue
LEFT JOIN tb_assign_record r ON clue.id = r.assign_id
WHERE
r.latest = '1'
AND r.type = '0'
AND clue. STATUS IN ('1', '2')
ORDER BY
clue.create_time DESC
在全部数据权限下,什么都不用修改
2.1.3仅本人数据权限的SQL
SELECT
clue.*,
r.create_by AS assign_by,
r.user_name AS OWNER,
r.create_time AS owner_time
FROM
tb_clue clue
LEFT JOIN tb_assign_record r ON clue.id = r.assign_id
WHERE
r.latest = '1'
AND r.type = '0'
AND clue. STATUS IN ('1', '2')
AND (r.user_id = 1014)
ORDER BY
clue.create_time DESC
可见,对于本人数据权限的sql来说多了 AND (r.user_id = 1014)
2.1.4具有本部门下所有数据权限:
SELECT
clue.*,
r.create_by AS assign_by,
r.user_name AS OWNER,
r.create_time AS owner_time
FROM
tb_clue clue
LEFT JOIN tb_assign_record r ON clue.id = r.assign_id
WHERE
r.latest = '1'
AND r.type = '0'
AND clue. STATUS IN ('1', '2')
AND (r.dept_id = 213)
ORDER BY
clue.create_time DESC
可见,对于本部门数据权限的sql来说多了 AND (r.dept_id = 213)
2.1.5具有本部门及以下数据权限SQL
SELECT
clue.*,
r.create_by AS assign_by,
r.user_name AS OWNER,
r.create_time AS owner_time
FROM
tb_clue clue
LEFT JOIN tb_assign_record r ON clue.id = r.assign_id
WHERE
r.latest = '1'
AND r.type = '0'
AND clue. STATUS IN ('1', '2')
AND (
r.dept_id IN (
SELECT
dept_id
FROM
sys_dept
WHERE
dept_id = 213
OR find_in_set(213, ancestors)
)
)
ORDER BY
clue.create_time DESC
由于查询的是部门及以下,需要关联到部门表
相比与全部数据权限多了
AND (
r.dept_id IN (
SELECT
dept_id
FROM
sys_dept
WHERE
dept_id = 213
OR find_in_set(213, ancestors)
)
)
2.1.6具有自定义数据权限SQL
SELECT
clue.*,
r.create_by AS assign_by,
r.user_name AS OWNER,
r.create_time AS owner_time
FROM
tb_clue clue
LEFT JOIN tb_assign_record r ON clue.id = r.assign_id
WHERE
r.latest = '1'
AND r.type = '0'
AND clue. STATUS IN ('1', '2')
AND (
r.dept_id IN (
SELECT
dept_id
FROM
sys_role_dept
WHERE
role_id = 113
)
)
ORDER BY
clue.create_time DESC
相比与全部数据权限多了
AND (
r.dept_id IN (
SELECT
dept_id
FROM
sys_role_dept
WHERE
role_id = 113
)
)
2.1.7 总结
可以看出,数据权限就是在原有的sql上拼接关于数据权限的sql
2.2自定义数据权限注解@DataScope
package com.huike.common.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.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataScope
{
/**
* 部门表的别名
*/
public String deptAlias() default "";
/**
* 用户表的别名
*/
public String userAlias() default "";
}
2.3利用aop实现该注解
2.3.1 确定切入点
// 配置织入点
@Pointcut("@annotation(com.huike.common.annotation.DataScope)")
public void dataScopePointCut(){
}
2.3.2 利用通知类型拼接sql
在方法执行前拼接sql,由于是方法执行前,使用@before
@Before("dataScopePointCut()")
public void doBefore(JoinPoint point) throws Throwable
{
handleDataScope(point);
}
protected void handleDataScope(final JoinPoint joinPoint)
{
// 获得注解
DataScope controllerDataScope = getAnnotationLog(joinPoint);
if (controllerDataScope == null)
{
return;
}
// 获取当前的用户
LoginUser loginUser = SpringUtils.getBean(TokenService.class).getLoginUser(ServletUtils.getRequest());
if (StringUtils.isNotNull(loginUser))
{
SysUser currentUser = loginUser.getUser();
// 如果是超级管理员,则不过滤数据
if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin())
{
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)
{
System.out.println("过滤数据---------------");
StringBuilder sqlString = new StringBuilder();
for (SysRole role : user.getRoles())
{
String dataScope = role.getDataScope();
if (DATA_SCOPE_ALL.equals(dataScope))
{
sqlString = new StringBuilder();
break;
}
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()));
}
else if (DATA_SCOPE_DEPT.equals(dataScope))
{
sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));
}
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()));
}
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];
if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
{
BaseEntity baseEntity = (BaseEntity) params;
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;
}
2.4 业务代码例子
业务层方法,加上@DataScope
/**
* 查询线索管理列表
*
* @param tbClue 线索管理
* @return 线索管理
*/
@Override
@DataScope(deptAlias = "r", userAlias = "r")
public List<TbClue> selectTbClueList(TbClue tbClue) {
return tbClueMapper.selectTbClueList(tbClue);
}
数据访问层XML文件例子
<select id="selectTbClueList" parameterType="TbClue" resultMap="TbClueAssignResult">
<include refid="selectTbClueAssignVo"/>
<where>
<if test="id != null and id != ''"> and clue.id like concat('%', #{id}, '%')</if>
<if test="name != null and name != ''"> and clue.name like concat('%', #{name}, '%')</if>
</where>
<!-- 数据范围过滤 -->
${params.dataScope}
order by clue.create_time desc
</select>