背景
之前做过的项目,都是一个用户就一个部门的,现在碰到个一个用户在多个部门的需求,而且需要可以切换不同部门查看不同数据
。
就比如说一个大公司下面有多个子公司,每个子公司有好多部门、子部门等等,然后有部分用户就和多个子公司/部门都有关联,有些数据就需要根据当前用户选择的部门进行过滤。
实现
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) 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>
查询结果用一个实体类接收:
@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也好,都可以很方便的截取出来。(我后面选择的是第二种方式。)
最后
大家如果有类似的需求要做,可以参考一下,我这里也是记录一下自己的实现方案,方便以后再碰到可以直接把代码拿过来用。