提交 | 用户 | 时间
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 }