潘志宝
2024-11-11 aa1aa68141e3ee33f98cdd785ddc5c244fedc592
提交 | 用户 | 时间
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),
d9f9ba 159 //                new Parenthesis(new ExpressionList<>(CollectionUtils.convertList(deptIds, LongValue::new))));
e7c126 160                 new ExpressionList(CollectionUtils.convertList(deptIds, LongValue::new)));
H 161     }
162
163     private Expression buildUserExpression(String tableName, Alias tableAlias, Boolean self, Long userId) {
164         // 如果不查看自己,则无需作为条件
165         if (Boolean.FALSE.equals(self)) {
166             return null;
167         }
168         String columnName = userColumns.get(tableName);
169         if (StrUtil.isEmpty(columnName)) {
170             return null;
171         }
172         // 拼接条件
173         return new EqualsTo(MyBatisUtils.buildColumn(tableName, tableAlias, columnName), new LongValue(userId));
174     }
175
176     // ==================== 添加配置 ====================
177
178     public void addDeptColumn(Class<? extends BaseDO> entityClass) {
179         addDeptColumn(entityClass, DEPT_COLUMN_NAME);
180     }
181
182     public void addDeptColumn(Class<? extends BaseDO> entityClass, String columnName) {
183         String tableName = TableInfoHelper.getTableInfo(entityClass).getTableName();
184        addDeptColumn(tableName, columnName);
185     }
186
187     public void addDeptColumn(String tableName, String columnName) {
188         deptColumns.put(tableName, columnName);
189         TABLE_NAMES.add(tableName);
190     }
191
192     public void addUserColumn(Class<? extends BaseDO> entityClass) {
193         addUserColumn(entityClass, USER_COLUMN_NAME);
194     }
195
196     public void addUserColumn(Class<? extends BaseDO> entityClass, String columnName) {
197         String tableName = TableInfoHelper.getTableInfo(entityClass).getTableName();
198         addUserColumn(tableName, columnName);
199     }
200
201     public void addUserColumn(String tableName, String columnName) {
202         userColumns.put(tableName, columnName);
203         TABLE_NAMES.add(tableName);
204     }
205
206 }