用户多部门切换部门,MySQL根据多个部门id递归获取所有上级(祖级)、获取部门的全路径(全结构名称)

背景

之前做过的项目,都是一个用户就一个部门的,现在碰到个一个用户在多个部门的需求,而且需要可以切换不同部门查看不同数据

就比如说一个大公司下面有多个子公司,每个子公司有好多部门、子部门等等,然后有部分用户就和多个子公司/部门都有关联,有些数据就需要根据当前用户选择的部门进行过滤。

实现

1、用户部门关联

首先就是用户和多部门关联,数据库要怎么保存。之前不是单部门的嘛,用户表和部门表关联就是用的一个dept_id字段;现在多部门我也不打算新建表存储关联关系,而是在原有的dept_id字段上,改个数据类型,比如由varchar改为longtext,然后多个部门之间用逗号隔开。

2、切换部门

其次就是切换部门,用户登录的时候查询用户信息,拿到了用户的部门id,根据部门id去递归获取所有上级。

因为我们切换部门的时候每次只能选中一个部门,而且这个部门需要显示全路径(比如:A公司/子公司1/开发部A公司/子公司1/办公室),所以为了后续好获取全路径,我们需要加一个checked属性,用户的部门id checked值为1,也就是true,其他的上级则为0,也就是false。

递归上级

根据多个部门id,递归所有上级,sql查询如下:

sql 复制代码
<select id="parentDeptList" resultType="com.entity.sys.vo.DeptVo">
    SELECT id,GROUP_CONCAT(DISTINCT `name`) `name`,GROUP_CONCAT(DISTINCT parent_id) parent_id,MAX(checked) checked
    FROM(
        WITH RECURSIVE temp_dept(id,name,parent_id,checked) AS (
            <!--查询指定部门-->
            SELECT id,name,parent_id,1 checked
            FROM sys_dept
            WHERE FIND_IN_SET(id,#{deptId})
            UNION ALL
            <!--递归查询父部门-->
            SELECT d.id,d.name,d.parent_id,0 checked
            FROM sys_dept d
            JOIN temp_dept c ON d.id = c.parent_id
        )
        SELECT id,name,parent_id,checked
        FROM temp_dept
        <!--不加条件则获取它的所有父节点,包括它本身-->
        <!--WHERE parent_id IN (SELECT id FROM sys_dept WHERE parent_id = 'ROOT')-->
    ) a
    GROUP BY id
</select>

查询结果用一个实体类接收:

java 复制代码
@Data
public class DeptVo {
    private String id;
    private String parentId;
    private String name;

    /**
     * 部门id全称:1/2/5/13
     */
    private String fullId;

    /**
     * 部门名称全称:A公司/子公司1/开发部/开发一组
     */
    private String fullName;

    /**
     * 是否选中
     */
    private Boolean checked;

    public DeptVo() {}

    public DeptVo(String id, String name, String fullId, String fullName,Boolean checked) {
        this.id = id;
        this.name = name;
        this.fullId = fullId;
        this.fullName = fullName;
        this.checked = checked;
    }
}

获取部门的全路径名称

拿到了查询结果,现在我们就可以来处理和获取部门的全路径名称了:

java 复制代码
/**
 * 递归查询,根据当前部门id(可能有多个)获取所有上级
 */
public List<DeptVo> parentDeptList(String id){
    List<DeptVo> deptList = baseMapper.parentDeptList(id);
    List<DeptVo> result = new ArrayList<>();
    int count = 0;
    for (DeptVo node : deptList) {
        if (node.getChecked()) {
            generateFullIdAndFullName(deptList, node);
            // 默认选中第一个部门
            if (count == 0) node.setChecked(true);
            else node.setChecked(false);
            result.add(node);
            count++;
        }
    }
    return result;
}

private static void generateFullIdAndFullName(List<DeptVo> nodeList, DeptVo currentNode) {
    StringBuilder fullIdBuilder = new StringBuilder();
    StringBuilder fullNameBuilder = new StringBuilder();
    generateFullIdAndFullNameRecursive(nodeList, currentNode, fullIdBuilder, fullNameBuilder);
    currentNode.setFullId(fullIdBuilder.toString());
    currentNode.setFullName(fullNameBuilder.toString());
}

private static void generateFullIdAndFullNameRecursive(List<DeptVo> nodeList, DeptVo currentNode, StringBuilder fullIdBuilder, StringBuilder fullNameBuilder) {
    fullIdBuilder.insert(0, currentNode.getId());
    fullNameBuilder.insert(0, currentNode.getName());
    if (!"ROOT".equals(currentNode.getParentId())) {
        fullIdBuilder.insert(0, "/");
        fullNameBuilder.insert(0, "/");
        for (DeptVo node : nodeList) {
            if (node.getId().equals(currentNode.getParentId())) {
                generateFullIdAndFullNameRecursive(nodeList, node, fullIdBuilder, fullNameBuilder);
                break;
            }
        }
    }
}

比如说用户A有id为4、5、6、7、8这几个部门,像这种的话就是新增/编辑用户时,用户选择部门时任意一级的部门都可以选,所以就会出现下面图片中的示例:单独选了营销部,但是又选了营销部下面的营销一部。这种情况就不做:切换部门时,查询所在部门及子部门数据。也就是只查询所在部门数据,子部门数据不查询,不然切换部门这个功能的意义不大。

然后上面的数据的输出结果如下:

将结果设置到用户信息中

能正确拿到数据后,我们在用户信息中,增加一个字段:

java 复制代码
@TableField(exist = false)
private List<DeptVo> deptArr;

parentDeptList 方法返回的数据设置到 deptArr 中,最后将用户信息存储到缓存中。用户登录成功,获取登录用户信息,就可以拿到 deptArr 了,切换时也是在这个列表进行切换选哪一个。

切换部门

切换部门时,前端传选中的部门的id,后端拿到当前登录用户的 deptArr 列表,匹配前端传过来的id,将匹配到的那一条数据的 checked 设置为true,其他的则设置为false:

java 复制代码
/**
 * 切换部门
 * @param id 部门id
 */
@GetMapping(value = "/qhbmsctk")
public ResultUtil qhbmsctk(String id,HttpServletRequest request){
    // 获取当前登录用户
    SysUser loginUser = LoginUtil.getLoginUser();
    List<DeptVo> deptArr = loginUser.getDeptArr();
    List<DeptVo> collect = deptArr.stream().peek(obj -> {
        if (obj.getId().equals(id)) {
            obj.setChecked(true); // 设置checked为true如果id等于前端传过来的deptId
        }else {
            obj.setChecked(false);
        }
    }).collect(Collectors.toList());
    loginUser.setDeptArr(collect);
    // 切换成功后,可以重新生成token重新登录(静默登录),也可以将更新后的用户信息重新更新到缓存中
    // 看自己实际需求选哪一种方式
    return ResultUtil.success();
}

切换成功后,可以重新生成token重新登录(静默登录),也可以将更新后的用户信息重新更新到缓存中,看自己实际需求选哪一种方式。

java 复制代码
/**
 * 获取当前用户选中的部门
 */
public DeptVo getCheckedDept(SysUser loginUser){
    loginUser = loginUser == null ? LoginUtil.getLoginUser() : loginUser;
    List<DeptVo> list = loginUser.getDeptArr();
    DeptVo vo = list.stream().filter(t -> t.getChecked()).findFirst().orElse(null);
    if (vo == null){
        throw new ExceptionVo(-1,"获取当前选中部门失败");
    }
    return vo;
}

3、数据过滤

数据过滤有两种过滤方式:1、根据部门id过滤(根据当前用户选中的部门id去过滤数据);2、根据当前用户id过滤(只查询当前用户创建的数据)。

第一种的话,没什么好说的,拿到当前用户选中的部门的id就行。

第二种呢就不能只根据用户id去过滤,因为用户是多部门的,所以需要用 用户id+选中的部门id 去过滤数据。这里的话要过滤数据的表在刚开始设计字段的时候,有两种方式:1、可以设计两个字段:create_id(创建人id)、create_dept(创建人部门id),通过这两个字段去过滤数据;2、也可以只设计一个字段,但是这个字段要包含创建人id创建人部门idcreate_id(由创建人id和创建人部门id组合而成),比如:userid#deptid,两个id之间用某个分隔符隔开,后续哪怕需要单独用的用户id也好、部门id也好,都可以很方便的截取出来。(我后面选择的是第二种方式。)

最后

大家如果有类似的需求要做,可以参考一下,我这里也是记录一下自己的实现方案,方便以后再碰到可以直接把代码拿过来用。

相关推荐
yanjiaweiya26 分钟前
云原生-集群管理
java·开发语言·云原生
gadiaola34 分钟前
【JavaSE面试篇】Java集合部分高频八股汇总
java·面试
艾迪的技术之路1 小时前
redisson使用lock导致死锁问题
java·后端·面试
今天背单词了吗9801 小时前
算法学习笔记:8.Bellman-Ford 算法——从原理到实战,涵盖 LeetCode 与考研 408 例题
java·开发语言·后端·算法·最短路径问题
天天摸鱼的java工程师1 小时前
使用 Spring Boot 整合高德地图实现路线规划功能
java·后端
东阳马生架构2 小时前
订单初版—2.生单链路中的技术问题说明文档
java
咖啡啡不加糖2 小时前
暴力破解漏洞与命令执行漏洞
java·后端·web安全
风象南2 小时前
SpringBoot敏感配置项加密与解密实战
java·spring boot·后端
DKPT2 小时前
Java享元模式实现方式与应用场景分析
java·笔记·学习·设计模式·享元模式