初出茅庐.开胃菜·代码思路

YangeIT大约 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>