背景
之前做过的项目,都是一个用户就一个部门的,现在碰到个一个用户在多个部门的需求,而且需要可以切换不同部门查看不同数据
。
就比如说一个大公司下面有多个子公司,每个子公司有好多部门、子部门等等,然后有部分用户就和多个子公司/部门都有关联,有些数据就需要根据当前用户选择的部门进行过滤。
实现
1、用户部门关联
首先就是用户和多部门关联,数据库要怎么保存。之前不是单部门的嘛,用户表和部门表关联就是用的一个dept_id
字段;现在多部门我也不打算新建表存储关联关系,而是在原有的dept_id
字段上,改个数据类型,比如由varchar
改为longtext
,然后多个部门之间用逗号隔开。
2、切换部门
其次就是切换部门,用户登录的时候查询用户信息,拿到了用户的部门id,根据部门id去递归获取所有上级。
因为我们切换部门的时候每次只能选中一个部门,而且这个部门需要显示全路径(比如:A公司/子公司1/开发部
、A公司/子公司1/办公室
),所以为了后续好获取全路径,我们需要加一个checked
属性,用户的部门id checked
值为1,也就是true,其他的上级则为0,也就是false。
递归上级
根据多个部门id,递归所有上级,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) checkedFROM(WITH RECURSIVE temp_dept(id,name,parent_id,checked) AS (<!--查询指定部门-->SELECT id,name,parent_id,1 checkedFROM sys_deptWHERE FIND_IN_SET(id,#{deptId})UNION ALL<!--递归查询父部门-->SELECT d.id,d.name,d.parent_id,0 checkedFROM sys_dept dJOIN temp_dept c ON d.id = c.parent_id)SELECT id,name,parent_id,checkedFROM temp_dept<!--不加条件则获取它的所有父节点,包括它本身--><!--WHERE parent_id IN (SELECT id FROM sys_dept WHERE parent_id = 'ROOT')-->) aGROUP BY id
</select>
查询结果用一个实体类接收:
@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;}
}
获取部门的全路径名称
拿到了查询结果,现在我们就可以来处理和获取部门的全路径名称了:
/*** 递归查询,根据当前部门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这几个部门,像这种的话就是新增/编辑用户时,用户选择部门时任意一级的部门都可以选,所以就会出现下面图片中的示例:单独选了营销部,但是又选了营销部下面的营销一部。这种情况就不做:切换部门时,查询所在部门及子部门数据
。也就是只查询所在部门数据,子部门数据不查询
,不然切换部门这个功能的意义不大。
然后上面的数据的输出结果如下:
将结果设置到用户信息中
能正确拿到数据后,我们在用户信息中,增加一个字段:
@TableField(exist = false)
private List<DeptVo> deptArr;
将 parentDeptList
方法返回的数据设置到 deptArr
中,最后将用户信息存储到缓存中。用户登录成功,获取登录用户信息,就可以拿到 deptArr
了,切换时也是在这个列表进行切换选哪一个。
切换部门
切换部门时,前端传选中的部门的id,后端拿到当前登录用户的 deptArr
列表,匹配前端传过来的id,将匹配到的那一条数据的 checked
设置为true,其他的则设置为false:
/*** 切换部门* @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重新登录(静默登录),也可以将更新后的用户信息重新更新到缓存中,看自己实际需求选哪一种方式。
/*** 获取当前用户选中的部门*/
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
和创建人部门id
:create_id(由创建人id和创建人部门id组合而成)
,比如:userid#deptid
,两个id之间用某个分隔符隔开,后续哪怕需要单独用的用户id也好、部门id也好,都可以很方便的截取出来。(我后面选择的是第二种方式。)
最后
大家如果有类似的需求要做,可以参考一下,我这里也是记录一下自己的实现方案,方便以后再碰到可以直接把代码拿过来用。