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