houzhongyi
2024-07-11 e7c1260db32209a078a962aaa0ad5492c35774fb
提交 | 用户 | 时间
e7c126 1 package com.iailab.framework.datapermission.core.rule.dept;
H 2
3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.util.ObjectUtil;
5 import cn.hutool.core.util.StrUtil;
6 import com.iailab.framework.common.enums.UserTypeEnum;
7 import com.iailab.framework.common.util.collection.CollectionUtils;
8 import com.iailab.framework.common.util.json.JsonUtils;
9 import com.iailab.framework.datapermission.core.rule.DataPermissionRule;
10 import com.iailab.framework.mybatis.core.dataobject.BaseDO;
11 import com.iailab.framework.mybatis.core.util.MyBatisUtils;
12 import com.iailab.framework.security.core.LoginUser;
13 import com.iailab.framework.security.core.util.SecurityFrameworkUtils;
14 import com.iailab.module.system.api.permission.PermissionApi;
15 import com.iailab.module.system.api.permission.dto.DeptDataPermissionRespDTO;
16 import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
17 import lombok.AllArgsConstructor;
18 import lombok.extern.slf4j.Slf4j;
19 import net.sf.jsqlparser.expression.*;
20 import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
21 import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
22 import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
23 import net.sf.jsqlparser.expression.operators.relational.InExpression;
24
25 import java.util.HashMap;
26 import java.util.HashSet;
27 import java.util.Map;
28 import java.util.Set;
29
30 /**
31  * 基于部门的 {@link DataPermissionRule} 数据权限规则实现
32  *
33  * 注意,使用 DeptDataPermissionRule 时,需要保证表中有 dept_id 部门编号的字段,可自定义。
34  *
35  * 实际业务场景下,会存在一个经典的问题?当用户修改部门时,冗余的 dept_id 是否需要修改?
36  * 1. 一般情况下,dept_id 不进行修改,则会导致用户看不到之前的数据。【iailab-server 采用该方案】
37  * 2. 部分情况下,希望该用户还是能看到之前的数据,则有两种方式解决:【需要你改造该 DeptDataPermissionRule 的实现代码】
38  *  1)编写洗数据的脚本,将 dept_id 修改成新部门的编号;【建议】
39  *      最终过滤条件是 WHERE dept_id = ?
40  *  2)洗数据的话,可能涉及的数据量较大,也可以采用 user_id 进行过滤的方式,此时需要获取到 dept_id 对应的所有 user_id 用户编号;
41  *      最终过滤条件是 WHERE user_id IN (?, ?, ? ...)
42  *  3)想要保证原 dept_id 和 user_id 都可以看的到,此时使用 dept_id 和 user_id 一起过滤;
43  *      最终过滤条件是 WHERE dept_id = ? OR user_id IN (?, ?, ? ...)
44  *
45  * @author iailab
46  */
47 @AllArgsConstructor
48 @Slf4j
49 public class DeptDataPermissionRule implements DataPermissionRule {
50
51     /**
52      * LoginUser 的 Context 缓存 Key
53      */
54     protected static final String CONTEXT_KEY = DeptDataPermissionRule.class.getSimpleName();
55
56     private static final String DEPT_COLUMN_NAME = "dept_id";
57     private static final String USER_COLUMN_NAME = "user_id";
58
59     static final Expression EXPRESSION_NULL = new NullValue();
60
61     private final PermissionApi permissionApi;
62
63     /**
64      * 基于部门的表字段配置
65      * 一般情况下,每个表的部门编号字段是 dept_id,通过该配置自定义。
66      *
67      * key:表名
68      * value:字段名
69      */
70     private final Map<String, String> deptColumns = new HashMap<>();
71     /**
72      * 基于用户的表字段配置
73      * 一般情况下,每个表的部门编号字段是 dept_id,通过该配置自定义。
74      *
75      * key:表名
76      * value:字段名
77      */
78     private final Map<String, String> userColumns = new HashMap<>();
79     /**
80      * 所有表名,是 {@link #deptColumns} 和 {@link #userColumns} 的合集
81      */
82     private final Set<String> TABLE_NAMES = new HashSet<>();
83
84     @Override
85     public Set<String> getTableNames() {
86         return TABLE_NAMES;
87     }
88
89     @Override
90     public Expression getExpression(String tableName, Alias tableAlias) {
91         // 只有有登陆用户的情况下,才进行数据权限的处理
92         LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
93         if (loginUser == null) {
94             return null;
95         }
96         // 只有管理员类型的用户,才进行数据权限的处理
97         if (ObjectUtil.notEqual(loginUser.getUserType(), UserTypeEnum.ADMIN.getValue())) {
98             return null;
99         }
100
101         // 获得数据权限
102         DeptDataPermissionRespDTO deptDataPermission = loginUser.getContext(CONTEXT_KEY, DeptDataPermissionRespDTO.class);
103         // 从上下文中拿不到,则调用逻辑进行获取
104         if (deptDataPermission == null) {
105             deptDataPermission = permissionApi.getDeptDataPermission(loginUser.getId()).getCheckedData();
106             if (deptDataPermission == null) {
107                 log.error("[getExpression][LoginUser({}) 获取数据权限为 null]", JsonUtils.toJsonString(loginUser));
108                 throw new NullPointerException(String.format("LoginUser(%d) Table(%s/%s) 未返回数据权限",
109                         loginUser.getId(), tableName, tableAlias.getName()));
110             }
111             // 添加到上下文中,避免重复计算
112             loginUser.setContext(CONTEXT_KEY, deptDataPermission);
113         }
114
115         // 情况一,如果是 ALL 可查看全部,则无需拼接条件
116         if (deptDataPermission.getAll()) {
117             return null;
118         }
119
120         // 情况二,即不能查看部门,又不能查看自己,则说明 100% 无权限
121         if (CollUtil.isEmpty(deptDataPermission.getDeptIds())
122             && Boolean.FALSE.equals(deptDataPermission.getSelf())) {
123             return new EqualsTo(null, null); // WHERE null = null,可以保证返回的数据为空
124         }
125
126         // 情况三,拼接 Dept 和 User 的条件,最后组合
127         Expression deptExpression = buildDeptExpression(tableName,tableAlias, deptDataPermission.getDeptIds());
128         Expression userExpression = buildUserExpression(tableName, tableAlias, deptDataPermission.getSelf(), loginUser.getId());
129         if (deptExpression == null && userExpression == null) {
130             // TODO iailab:获得不到条件的时候,暂时不抛出异常,而是不返回数据
131             log.warn("[getExpression][LoginUser({}) Table({}/{}) DeptDataPermission({}) 构建的条件为空]",
132                     JsonUtils.toJsonString(loginUser), tableName, tableAlias, JsonUtils.toJsonString(deptDataPermission));
133 //            throw new NullPointerException(String.format("LoginUser(%d) Table(%s/%s) 构建的条件为空",
134 //                    loginUser.getId(), tableName, tableAlias.getName()));
135             return EXPRESSION_NULL;
136         }
137         if (deptExpression == null) {
138             return userExpression;
139         }
140         if (userExpression == null) {
141             return deptExpression;
142         }
143         // 目前,如果有指定部门 + 可查看自己,采用 OR 条件。即,WHERE (dept_id IN ? OR user_id = ?)
144         return new Parenthesis(new OrExpression(deptExpression, userExpression));
145     }
146
147     private Expression buildDeptExpression(String tableName, Alias tableAlias, Set<Long> deptIds) {
148         // 如果不存在配置,则无需作为条件
149         String columnName = deptColumns.get(tableName);
150         if (StrUtil.isEmpty(columnName)) {
151             return null;
152         }
153         // 如果为空,则无条件
154         if (CollUtil.isEmpty(deptIds)) {
155             return null;
156         }
157         // 拼接条件
158         return new InExpression(MyBatisUtils.buildColumn(tableName, tableAlias, columnName),
159                 new ExpressionList(CollectionUtils.convertList(deptIds, LongValue::new)));
160     }
161
162     private Expression buildUserExpression(String tableName, Alias tableAlias, Boolean self, Long userId) {
163         // 如果不查看自己,则无需作为条件
164         if (Boolean.FALSE.equals(self)) {
165             return null;
166         }
167         String columnName = userColumns.get(tableName);
168         if (StrUtil.isEmpty(columnName)) {
169             return null;
170         }
171         // 拼接条件
172         return new EqualsTo(MyBatisUtils.buildColumn(tableName, tableAlias, columnName), new LongValue(userId));
173     }
174
175     // ==================== 添加配置 ====================
176
177     public void addDeptColumn(Class<? extends BaseDO> entityClass) {
178         addDeptColumn(entityClass, DEPT_COLUMN_NAME);
179     }
180
181     public void addDeptColumn(Class<? extends BaseDO> entityClass, String columnName) {
182         String tableName = TableInfoHelper.getTableInfo(entityClass).getTableName();
183        addDeptColumn(tableName, columnName);
184     }
185
186     public void addDeptColumn(String tableName, String columnName) {
187         deptColumns.put(tableName, columnName);
188         TABLE_NAMES.add(tableName);
189     }
190
191     public void addUserColumn(Class<? extends BaseDO> entityClass) {
192         addUserColumn(entityClass, USER_COLUMN_NAME);
193     }
194
195     public void addUserColumn(Class<? extends BaseDO> entityClass, String columnName) {
196         String tableName = TableInfoHelper.getTableInfo(entityClass).getTableName();
197         addUserColumn(tableName, columnName);
198     }
199
200     public void addUserColumn(String tableName, String columnName) {
201         userColumns.put(tableName, columnName);
202         TABLE_NAMES.add(tableName);
203     }
204
205 }