提交 | 用户 | 时间
|
e7c126
|
1 |
package com.iailab.module.system.service.permission; |
H |
2 |
|
|
3 |
import cn.hutool.core.collection.CollUtil; |
|
4 |
import cn.hutool.core.collection.CollectionUtil; |
|
5 |
import cn.hutool.core.util.ArrayUtil; |
|
6 |
import cn.hutool.extra.spring.SpringUtil; |
|
7 |
import com.iailab.framework.common.enums.CommonStatusEnum; |
|
8 |
import com.iailab.framework.common.util.collection.CollectionUtils; |
|
9 |
import com.iailab.framework.datapermission.core.annotation.DataPermission; |
|
10 |
import com.iailab.module.system.api.permission.dto.DeptDataPermissionRespDTO; |
818a01
|
11 |
import com.iailab.module.system.controller.admin.permission.vo.menu.MenuListReqVO; |
H |
12 |
import com.iailab.module.system.dal.dataobject.app.AppDO; |
e7c126
|
13 |
import com.iailab.module.system.dal.dataobject.permission.MenuDO; |
H |
14 |
import com.iailab.module.system.dal.dataobject.permission.RoleDO; |
|
15 |
import com.iailab.module.system.dal.dataobject.permission.RoleMenuDO; |
|
16 |
import com.iailab.module.system.dal.dataobject.permission.UserRoleDO; |
818a01
|
17 |
import com.iailab.module.system.dal.dataobject.tenant.TenantDO; |
H |
18 |
import com.iailab.module.system.dal.dataobject.tenant.TenantPackageDO; |
e7c126
|
19 |
import com.iailab.module.system.dal.mysql.permission.RoleMenuMapper; |
H |
20 |
import com.iailab.module.system.dal.mysql.permission.UserRoleMapper; |
|
21 |
import com.iailab.module.system.dal.redis.RedisKeyConstants; |
|
22 |
import com.iailab.module.system.enums.permission.DataScopeEnum; |
818a01
|
23 |
import com.iailab.module.system.service.app.AppService; |
e7c126
|
24 |
import com.iailab.module.system.service.dept.DeptService; |
818a01
|
25 |
import com.iailab.module.system.service.tenant.TenantPackageService; |
H |
26 |
import com.iailab.module.system.service.tenant.TenantService; |
e7c126
|
27 |
import com.iailab.module.system.service.user.AdminUserService; |
H |
28 |
import com.baomidou.dynamic.datasource.annotation.DSTransactional; |
|
29 |
import com.google.common.annotations.VisibleForTesting; |
|
30 |
import com.google.common.base.Suppliers; |
|
31 |
import com.google.common.collect.Sets; |
|
32 |
import lombok.extern.slf4j.Slf4j; |
|
33 |
import org.springframework.cache.annotation.CacheEvict; |
|
34 |
import org.springframework.cache.annotation.Cacheable; |
|
35 |
import org.springframework.cache.annotation.Caching; |
|
36 |
import org.springframework.stereotype.Service; |
|
37 |
import org.springframework.transaction.annotation.Transactional; |
|
38 |
|
|
39 |
import javax.annotation.Resource; |
|
40 |
import java.util.*; |
|
41 |
import java.util.function.Supplier; |
|
42 |
|
|
43 |
import static com.iailab.framework.common.util.collection.CollectionUtils.convertSet; |
|
44 |
import static com.iailab.framework.common.util.json.JsonUtils.toJsonString; |
818a01
|
45 |
import static com.iailab.framework.tenant.core.context.TenantContextHolder.getTenantId; |
e7c126
|
46 |
|
H |
47 |
/** |
|
48 |
* 权限 Service 实现类 |
|
49 |
* |
|
50 |
* @author iailab |
|
51 |
*/ |
|
52 |
@Service |
|
53 |
@Slf4j |
|
54 |
public class PermissionServiceImpl implements PermissionService { |
|
55 |
|
|
56 |
@Resource |
|
57 |
private RoleMenuMapper roleMenuMapper; |
|
58 |
@Resource |
|
59 |
private UserRoleMapper userRoleMapper; |
|
60 |
|
|
61 |
@Resource |
|
62 |
private RoleService roleService; |
|
63 |
@Resource |
|
64 |
private MenuService menuService; |
|
65 |
@Resource |
|
66 |
private DeptService deptService; |
|
67 |
@Resource |
|
68 |
private AdminUserService userService; |
818a01
|
69 |
@Resource |
H |
70 |
private TenantService tenantService; |
|
71 |
@Resource |
|
72 |
private TenantPackageService tenantPackageService; |
|
73 |
@Resource |
|
74 |
private AppService appService; |
|
75 |
|
e7c126
|
76 |
|
H |
77 |
@Override |
|
78 |
public boolean hasAnyPermissions(Long userId, String... permissions) { |
|
79 |
// 如果为空,说明已经有权限 |
|
80 |
if (ArrayUtil.isEmpty(permissions)) { |
|
81 |
return true; |
|
82 |
} |
|
83 |
|
|
84 |
// 获得当前登录的角色。如果为空,说明没有权限 |
|
85 |
List<RoleDO> roles = getEnableUserRoleListByUserIdFromCache(userId); |
|
86 |
if (CollUtil.isEmpty(roles)) { |
|
87 |
return false; |
|
88 |
} |
|
89 |
|
|
90 |
// 情况一:遍历判断每个权限,如果有一满足,说明有权限 |
|
91 |
for (String permission : permissions) { |
|
92 |
if (hasAnyPermission(roles, permission)) { |
|
93 |
return true; |
|
94 |
} |
|
95 |
} |
|
96 |
|
|
97 |
// 情况二:如果是超管,也说明有权限 |
|
98 |
return roleService.hasAnySuperAdmin(convertSet(roles, RoleDO::getId)); |
|
99 |
} |
|
100 |
|
|
101 |
/** |
|
102 |
* 判断指定角色,是否拥有该 permission 权限 |
|
103 |
* |
|
104 |
* @param roles 指定角色数组 |
|
105 |
* @param permission 权限标识 |
|
106 |
* @return 是否拥有 |
|
107 |
*/ |
|
108 |
private boolean hasAnyPermission(List<RoleDO> roles, String permission) { |
|
109 |
List<Long> menuIds = menuService.getMenuIdListByPermissionFromCache(permission); |
|
110 |
// 采用严格模式,如果权限找不到对应的 Menu 的话,也认为没有权限 |
|
111 |
if (CollUtil.isEmpty(menuIds)) { |
|
112 |
return false; |
|
113 |
} |
|
114 |
|
|
115 |
// 判断是否有权限 |
|
116 |
Set<Long> roleIds = convertSet(roles, RoleDO::getId); |
|
117 |
for (Long menuId : menuIds) { |
|
118 |
// 获得拥有该菜单的角色编号集合 |
|
119 |
Set<Long> menuRoleIds = getSelf().getMenuRoleIdListByMenuIdFromCache(menuId); |
|
120 |
// 如果有交集,说明有权限 |
|
121 |
if (CollUtil.containsAny(menuRoleIds, roleIds)) { |
|
122 |
return true; |
|
123 |
} |
|
124 |
} |
|
125 |
return false; |
|
126 |
} |
|
127 |
|
|
128 |
@Override |
|
129 |
public boolean hasAnyRoles(Long userId, String... roles) { |
|
130 |
// 如果为空,说明已经有权限 |
|
131 |
if (ArrayUtil.isEmpty(roles)) { |
|
132 |
return true; |
|
133 |
} |
|
134 |
|
|
135 |
// 获得当前登录的角色。如果为空,说明没有权限 |
|
136 |
List<RoleDO> roleList = getEnableUserRoleListByUserIdFromCache(userId); |
|
137 |
if (CollUtil.isEmpty(roleList)) { |
|
138 |
return false; |
|
139 |
} |
|
140 |
|
|
141 |
// 判断是否有角色 |
|
142 |
Set<String> userRoles = convertSet(roleList, RoleDO::getCode); |
|
143 |
return CollUtil.containsAny(userRoles, Sets.newHashSet(roles)); |
|
144 |
} |
|
145 |
|
|
146 |
// ========== 角色-菜单的相关方法 ========== |
|
147 |
|
|
148 |
@Override |
|
149 |
@DSTransactional // 多数据源,使用 @DSTransactional 保证本地事务,以及数据源的切换 |
|
150 |
@CacheEvict(value = RedisKeyConstants.MENU_ROLE_ID_LIST, |
|
151 |
allEntries = true) // allEntries 清空所有缓存,主要一次更新涉及到的 menuIds 较多,反倒批量会更快 |
|
152 |
public void assignRoleMenu(Long roleId, Set<Long> menuIds) { |
|
153 |
// 获得角色拥有菜单编号 |
|
154 |
Set<Long> dbMenuIds = convertSet(roleMenuMapper.selectListByRoleId(roleId), RoleMenuDO::getMenuId); |
|
155 |
// 计算新增和删除的菜单编号 |
|
156 |
Set<Long> menuIdList = CollUtil.emptyIfNull(menuIds); |
|
157 |
Collection<Long> createMenuIds = CollUtil.subtract(menuIdList, dbMenuIds); |
|
158 |
Collection<Long> deleteMenuIds = CollUtil.subtract(dbMenuIds, menuIdList); |
|
159 |
// 执行新增和删除。对于已经授权的菜单,不用做任何处理 |
|
160 |
if (CollUtil.isNotEmpty(createMenuIds)) { |
|
161 |
roleMenuMapper.insertBatch(CollectionUtils.convertList(createMenuIds, menuId -> { |
|
162 |
RoleMenuDO entity = new RoleMenuDO(); |
|
163 |
entity.setRoleId(roleId); |
|
164 |
entity.setMenuId(menuId); |
|
165 |
return entity; |
|
166 |
})); |
|
167 |
} |
|
168 |
if (CollUtil.isNotEmpty(deleteMenuIds)) { |
|
169 |
roleMenuMapper.deleteListByRoleIdAndMenuIds(roleId, deleteMenuIds); |
|
170 |
} |
|
171 |
} |
|
172 |
|
818a01
|
173 |
// ========== 角色-菜单的相关方法 ========== |
H |
174 |
|
7da8f1
|
175 |
// @Override |
H |
176 |
// @DSTransactional // 多数据源,使用 @DSTransactional 保证本地事务,以及数据源的切换 |
|
177 |
// @CacheEvict(value = RedisKeyConstants.MENU_ROLE_ID_LIST, |
|
178 |
// allEntries = true) // allEntries 清空所有缓存,主要一次更新涉及到的 menuIds 较多,反倒批量会更快 |
|
179 |
// public void assignRoleAppMenu(Long roleId, Set<Long> menuIds) { |
|
180 |
// // 获得角色拥有应用菜单编号 |
|
181 |
// MenuListReqVO reqVO = new MenuListReqVO(); |
|
182 |
// List<MenuDO> appMenuList = menuService.getAppMenuList(reqVO); |
|
183 |
// Set<Long> appMenuIds = convertSet(appMenuList, MenuDO::getId); |
|
184 |
// Set<Long> dbMenuIds = convertSet(roleMenuMapper.selectListByRoleId(roleId), RoleMenuDO::getMenuId); |
|
185 |
// dbMenuIds.retainAll(appMenuIds); |
|
186 |
// // 计算新增和删除的菜单编号 |
|
187 |
// Set<Long> menuIdList = CollUtil.emptyIfNull(menuIds); |
|
188 |
// Collection<Long> createMenuIds = CollUtil.subtract(menuIdList, dbMenuIds); |
|
189 |
// Collection<Long> deleteMenuIds = CollUtil.subtract(dbMenuIds, menuIdList); |
|
190 |
// // 执行新增和删除。对于已经授权的菜单,不用做任何处理 |
|
191 |
// if (CollUtil.isNotEmpty(createMenuIds)) { |
|
192 |
// roleMenuMapper.insertBatch(CollectionUtils.convertList(createMenuIds, menuId -> { |
|
193 |
// RoleMenuDO entity = new RoleMenuDO(); |
|
194 |
// entity.setRoleId(roleId); |
|
195 |
// entity.setMenuId(menuId); |
|
196 |
// return entity; |
|
197 |
// })); |
|
198 |
// } |
|
199 |
// if (CollUtil.isNotEmpty(deleteMenuIds)) { |
|
200 |
// roleMenuMapper.deleteListByRoleIdAndMenuIds(roleId, deleteMenuIds); |
|
201 |
// } |
|
202 |
// } |
818a01
|
203 |
|
e7c126
|
204 |
@Override |
H |
205 |
@Transactional(rollbackFor = Exception.class) |
|
206 |
@Caching(evict = { |
|
207 |
@CacheEvict(value = RedisKeyConstants.MENU_ROLE_ID_LIST, |
|
208 |
allEntries = true), // allEntries 清空所有缓存,此处无法方便获得 roleId 对应的 menu 缓存们 |
|
209 |
@CacheEvict(value = RedisKeyConstants.USER_ROLE_ID_LIST, |
|
210 |
allEntries = true) // allEntries 清空所有缓存,此处无法方便获得 roleId 对应的 user 缓存们 |
|
211 |
}) |
|
212 |
public void processRoleDeleted(Long roleId) { |
|
213 |
// 标记删除 UserRole |
|
214 |
userRoleMapper.deleteListByRoleId(roleId); |
|
215 |
// 标记删除 RoleMenu |
|
216 |
roleMenuMapper.deleteListByRoleId(roleId); |
|
217 |
} |
|
218 |
|
|
219 |
@Override |
|
220 |
@CacheEvict(value = RedisKeyConstants.MENU_ROLE_ID_LIST, key = "#menuId") |
|
221 |
public void processMenuDeleted(Long menuId) { |
|
222 |
roleMenuMapper.deleteListByMenuId(menuId); |
|
223 |
} |
|
224 |
|
|
225 |
@Override |
|
226 |
public Set<Long> getRoleMenuListByRoleId(Collection<Long> roleIds) { |
|
227 |
if (CollUtil.isEmpty(roleIds)) { |
|
228 |
return Collections.emptySet(); |
|
229 |
} |
7da8f1
|
230 |
// 如果是管理员的情况下,获取全部菜单编号 |
H |
231 |
if (roleService.hasAnySuperAdmin(roleIds)) { |
|
232 |
return convertSet(menuService.getMenuList(), MenuDO::getId); |
|
233 |
} |
e7c126
|
234 |
return convertSet(roleMenuMapper.selectListByRoleId(roleIds), RoleMenuDO::getMenuId); |
H |
235 |
} |
|
236 |
|
|
237 |
@Override |
818a01
|
238 |
public Set<Long> getRoleAppMenuListByRoleId(Collection<Long> roleIds) { |
H |
239 |
if (CollUtil.isEmpty(roleIds)) { |
|
240 |
return Collections.emptySet(); |
|
241 |
} |
ce910c
|
242 |
// 获取 tenantId |
H |
243 |
Long tenantId = getTenantId(); |
818a01
|
244 |
// 如果是管理员的情况下,获取全部应用菜单编号 |
H |
245 |
if (roleService.hasAnySuperAdmin(roleIds)) { |
|
246 |
MenuListReqVO reqVO = new MenuListReqVO(); |
ce910c
|
247 |
return convertSet(menuService.getAppMenuList(tenantId, reqVO), MenuDO::getId); |
818a01
|
248 |
} |
H |
249 |
// 如果是非管理员的情况下,获得拥有的应用菜单编号 |
|
250 |
TenantDO tenant = tenantService.getTenant(tenantId); |
|
251 |
TenantPackageDO tenantPackage = tenantPackageService.getTenantPackage(tenant.getPackageId()); |
|
252 |
Set<Long> menuIds = tenantPackage.getMenuIds(); |
|
253 |
Set<Long> longs = convertSet(roleMenuMapper.selectListByRoleId(roleIds), RoleMenuDO::getMenuId); |
|
254 |
longs.retainAll(menuIds); |
|
255 |
return longs; |
|
256 |
} |
|
257 |
|
|
258 |
@Override |
e7c126
|
259 |
@Cacheable(value = RedisKeyConstants.MENU_ROLE_ID_LIST, key = "#menuId") |
H |
260 |
public Set<Long> getMenuRoleIdListByMenuIdFromCache(Long menuId) { |
|
261 |
return convertSet(roleMenuMapper.selectListByMenuId(menuId), RoleMenuDO::getRoleId); |
|
262 |
} |
|
263 |
|
|
264 |
// ========== 用户-角色的相关方法 ========== |
|
265 |
|
|
266 |
@Override |
|
267 |
@DSTransactional // 多数据源,使用 @DSTransactional 保证本地事务,以及数据源的切换 |
|
268 |
@CacheEvict(value = RedisKeyConstants.USER_ROLE_ID_LIST, key = "#userId") |
|
269 |
public void assignUserRole(Long userId, Set<Long> roleIds) { |
|
270 |
// 获得角色拥有角色编号 |
|
271 |
Set<Long> dbRoleIds = convertSet(userRoleMapper.selectListByUserId(userId), |
|
272 |
UserRoleDO::getRoleId); |
|
273 |
// 计算新增和删除的角色编号 |
|
274 |
Set<Long> roleIdList = CollUtil.emptyIfNull(roleIds); |
|
275 |
Collection<Long> createRoleIds = CollUtil.subtract(roleIdList, dbRoleIds); |
|
276 |
Collection<Long> deleteMenuIds = CollUtil.subtract(dbRoleIds, roleIdList); |
|
277 |
// 执行新增和删除。对于已经授权的角色,不用做任何处理 |
|
278 |
if (!CollectionUtil.isEmpty(createRoleIds)) { |
|
279 |
userRoleMapper.insertBatch(CollectionUtils.convertList(createRoleIds, roleId -> { |
|
280 |
UserRoleDO entity = new UserRoleDO(); |
|
281 |
entity.setUserId(userId); |
|
282 |
entity.setRoleId(roleId); |
|
283 |
return entity; |
|
284 |
})); |
|
285 |
} |
|
286 |
if (!CollectionUtil.isEmpty(deleteMenuIds)) { |
|
287 |
userRoleMapper.deleteListByUserIdAndRoleIdIds(userId, deleteMenuIds); |
|
288 |
} |
|
289 |
} |
|
290 |
|
|
291 |
@Override |
|
292 |
@CacheEvict(value = RedisKeyConstants.USER_ROLE_ID_LIST, key = "#userId") |
|
293 |
public void processUserDeleted(Long userId) { |
|
294 |
userRoleMapper.deleteListByUserId(userId); |
|
295 |
} |
|
296 |
|
|
297 |
@Override |
|
298 |
public Set<Long> getUserRoleIdListByUserId(Long userId) { |
|
299 |
return convertSet(userRoleMapper.selectListByUserId(userId), UserRoleDO::getRoleId); |
|
300 |
} |
|
301 |
|
|
302 |
@Override |
|
303 |
@Cacheable(value = RedisKeyConstants.USER_ROLE_ID_LIST, key = "#userId") |
|
304 |
public Set<Long> getUserRoleIdListByUserIdFromCache(Long userId) { |
|
305 |
return getUserRoleIdListByUserId(userId); |
|
306 |
} |
|
307 |
|
|
308 |
@Override |
|
309 |
public Set<Long> getUserRoleIdListByRoleId(Collection<Long> roleIds) { |
|
310 |
return convertSet(userRoleMapper.selectListByRoleIds(roleIds), UserRoleDO::getUserId); |
|
311 |
} |
|
312 |
|
|
313 |
/** |
|
314 |
* 获得用户拥有的角色,并且这些角色是开启状态的 |
|
315 |
* |
|
316 |
* @param userId 用户编号 |
|
317 |
* @return 用户拥有的角色 |
|
318 |
*/ |
|
319 |
@VisibleForTesting |
|
320 |
List<RoleDO> getEnableUserRoleListByUserIdFromCache(Long userId) { |
|
321 |
// 获得用户拥有的角色编号 |
|
322 |
Set<Long> roleIds = getSelf().getUserRoleIdListByUserIdFromCache(userId); |
|
323 |
// 获得角色数组,并移除被禁用的 |
|
324 |
List<RoleDO> roles = roleService.getRoleListFromCache(roleIds); |
|
325 |
roles.removeIf(role -> !CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus())); |
|
326 |
return roles; |
|
327 |
} |
|
328 |
|
|
329 |
// ========== 用户-部门的相关方法 ========== |
|
330 |
|
|
331 |
@Override |
|
332 |
public void assignRoleDataScope(Long roleId, Integer dataScope, Set<Long> dataScopeDeptIds) { |
|
333 |
roleService.updateRoleDataScope(roleId, dataScope, dataScopeDeptIds); |
|
334 |
} |
|
335 |
|
|
336 |
@Override |
|
337 |
@DataPermission(enable = false) // 关闭数据权限,不然就会出现递归获取数据权限的问题 |
|
338 |
public DeptDataPermissionRespDTO getDeptDataPermission(Long userId) { |
|
339 |
// 获得用户的角色 |
|
340 |
List<RoleDO> roles = getEnableUserRoleListByUserIdFromCache(userId); |
|
341 |
|
|
342 |
// 如果角色为空,则只能查看自己 |
|
343 |
DeptDataPermissionRespDTO result = new DeptDataPermissionRespDTO(); |
|
344 |
if (CollUtil.isEmpty(roles)) { |
|
345 |
result.setSelf(true); |
|
346 |
return result; |
|
347 |
} |
|
348 |
|
|
349 |
// 获得用户的部门编号的缓存,通过 Guava 的 Suppliers 惰性求值,即有且仅有第一次发起 DB 的查询 |
|
350 |
Supplier<Long> userDeptId = Suppliers.memoize(() -> userService.getUser(userId).getDeptId()); |
|
351 |
// 遍历每个角色,计算 |
|
352 |
for (RoleDO role : roles) { |
|
353 |
// 为空时,跳过 |
|
354 |
if (role.getDataScope() == null) { |
|
355 |
continue; |
|
356 |
} |
|
357 |
// 情况一,ALL |
|
358 |
if (Objects.equals(role.getDataScope(), DataScopeEnum.ALL.getScope())) { |
|
359 |
result.setAll(true); |
|
360 |
continue; |
|
361 |
} |
|
362 |
// 情况二,DEPT_CUSTOM |
|
363 |
if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_CUSTOM.getScope())) { |
|
364 |
CollUtil.addAll(result.getDeptIds(), role.getDataScopeDeptIds()); |
|
365 |
// 自定义可见部门时,保证可以看到自己所在的部门。否则,一些场景下可能会有问题。 |
|
366 |
// 例如说,登录时,基于 t_user 的 username 查询会可能被 dept_id 过滤掉 |
|
367 |
CollUtil.addAll(result.getDeptIds(), userDeptId.get()); |
|
368 |
continue; |
|
369 |
} |
|
370 |
// 情况三,DEPT_ONLY |
|
371 |
if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_ONLY.getScope())) { |
|
372 |
CollectionUtils.addIfNotNull(result.getDeptIds(), userDeptId.get()); |
|
373 |
continue; |
|
374 |
} |
|
375 |
// 情况四,DEPT_DEPT_AND_CHILD |
|
376 |
if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_AND_CHILD.getScope())) { |
|
377 |
CollUtil.addAll(result.getDeptIds(), deptService.getChildDeptIdListFromCache(userDeptId.get())); |
|
378 |
// 添加本身部门编号 |
|
379 |
CollUtil.addAll(result.getDeptIds(), userDeptId.get()); |
|
380 |
continue; |
|
381 |
} |
|
382 |
// 情况五,SELF |
|
383 |
if (Objects.equals(role.getDataScope(), DataScopeEnum.SELF.getScope())) { |
|
384 |
result.setSelf(true); |
|
385 |
continue; |
|
386 |
} |
|
387 |
// 未知情况,error log 即可 |
|
388 |
log.error("[getDeptDataPermission][LoginUser({}) role({}) 无法处理]", userId, toJsonString(result)); |
|
389 |
} |
|
390 |
return result; |
|
391 |
} |
|
392 |
|
|
393 |
/** |
|
394 |
* 获得自身的代理对象,解决 AOP 生效问题 |
|
395 |
* |
|
396 |
* @return 自己 |
|
397 |
*/ |
|
398 |
private PermissionServiceImpl getSelf() { |
|
399 |
return SpringUtil.getBean(getClass()); |
|
400 |
} |
|
401 |
|
|
402 |
} |