mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-10-31 18:28:43 +08:00 
			
		
		
		
	完成部分权限的认证操作的迁移
This commit is contained in:
		| @@ -233,11 +233,6 @@ public class OperateLogAspect { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private static void fillContentFields(SysOperateLogCreateReqVO operateLogVO) { | ||||
|         operateLogVO.setContent(CONTENT.get()); | ||||
|         operateLogVO.setExts(EXTS.get()); | ||||
|     } | ||||
|  | ||||
|     private static boolean isLogEnable(ProceedingJoinPoint joinPoint, OperateLog operateLog) { | ||||
|         // 有 @OperateLog 注解的情况下 | ||||
|         if (operateLog != null) { | ||||
|   | ||||
| @@ -2,7 +2,7 @@ package cn.iocoder.dashboard.framework.security.core.handler; | ||||
|  | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import cn.iocoder.dashboard.framework.security.config.SecurityProperties; | ||||
| import cn.iocoder.dashboard.framework.security.core.service.SecurityFrameworkService; | ||||
| import cn.iocoder.dashboard.framework.security.core.service.SecurityAuthFrameworkService; | ||||
| import cn.iocoder.dashboard.framework.security.core.util.SecurityUtils; | ||||
| import cn.iocoder.dashboard.util.servlet.ServletUtils; | ||||
| import org.springframework.security.core.Authentication; | ||||
| @@ -26,7 +26,7 @@ public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler { | ||||
|     private SecurityProperties securityProperties; | ||||
|  | ||||
|     @Resource | ||||
|     private SecurityFrameworkService securityFrameworkService; | ||||
|     private SecurityAuthFrameworkService securityFrameworkService; | ||||
|  | ||||
|     @Override | ||||
|     public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { | ||||
|   | ||||
| @@ -4,16 +4,11 @@ import cn.iocoder.dashboard.framework.security.core.LoginUser; | ||||
| import org.springframework.security.core.userdetails.UserDetailsService; | ||||
| 
 | ||||
| /** | ||||
|  * Security 框架 Service 接口,定义 security 组件需要的功能 | ||||
|  * Security 框架 Auth Service 接口,定义 security 组件需要的功能 | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| public interface SecurityFrameworkService extends UserDetailsService { | ||||
| 
 | ||||
|     /** | ||||
|      * 基于 token 退出登录 | ||||
|      * | ||||
|      * @param token token | ||||
|      */ | ||||
|     void logout(String token); | ||||
| public interface SecurityAuthFrameworkService extends UserDetailsService { | ||||
| 
 | ||||
|     /** | ||||
|      * 校验 token 的有效性,并获取用户信息 | ||||
| @@ -32,4 +27,11 @@ public interface SecurityFrameworkService extends UserDetailsService { | ||||
|      */ | ||||
|     LoginUser mockLogin(Long userId); | ||||
| 
 | ||||
|     /** | ||||
|      * 基于 token 退出登录 | ||||
|      * | ||||
|      * @param token token | ||||
|      */ | ||||
|     void logout(String token); | ||||
| 
 | ||||
| } | ||||
| @@ -0,0 +1,26 @@ | ||||
| package cn.iocoder.dashboard.framework.security.core.service; | ||||
|  | ||||
| /** | ||||
|  * Security 框架 Permission Service 接口,定义 security 组件需要的功能 | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| public interface SecurityPermissionFrameworkService { | ||||
|  | ||||
|     /** | ||||
|      * 判断是否有权限 | ||||
|      * | ||||
|      * @param permission 权限 | ||||
|      * @return 是否 | ||||
|      */ | ||||
|     boolean hasPermission(String permission); | ||||
|  | ||||
|     /** | ||||
|      * 判断是否有权限,任一一个即可 | ||||
|      * | ||||
|      * @param permissions 权限 | ||||
|      * @return 是否 | ||||
|      */ | ||||
|     boolean hasAnyPermissions(String... permissions); | ||||
|  | ||||
| } | ||||
| @@ -3,7 +3,9 @@ package cn.iocoder.dashboard.framework.web.core.handler; | ||||
| import cn.iocoder.dashboard.common.exception.GlobalException; | ||||
| import cn.iocoder.dashboard.common.exception.ServiceException; | ||||
| import cn.iocoder.dashboard.common.pojo.CommonResult; | ||||
| import cn.iocoder.dashboard.framework.security.core.util.SecurityUtils; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.security.access.AccessDeniedException; | ||||
| import org.springframework.validation.BindException; | ||||
| import org.springframework.validation.FieldError; | ||||
| import org.springframework.web.HttpRequestMethodNotSupportedException; | ||||
| @@ -64,6 +66,9 @@ public class GlobalExceptionHandler { | ||||
|         if (ex instanceof ServiceException) { | ||||
|             return serviceExceptionHandler((ServiceException) ex); | ||||
|         } | ||||
|         if (ex instanceof AccessDeniedException) { | ||||
|             return accessDeniedExceptionHandler(request, (AccessDeniedException) ex); | ||||
|         } | ||||
|         if (ex instanceof GlobalException) { | ||||
|             return globalExceptionHandler(request, (GlobalException) ex); | ||||
|         } | ||||
| @@ -131,7 +136,7 @@ public class GlobalExceptionHandler { | ||||
|     public CommonResult<?> validationException(ValidationException ex) { | ||||
|         log.warn("[constraintViolationExceptionHandler]", ex); | ||||
|         // 无法拼接明细的错误信息,因为 Dubbo Consumer 抛出 ValidationException 异常时,是直接的字符串信息,且人类不可读 | ||||
|         return CommonResult.error(BAD_REQUEST.getCode(), "请求参数不正确"); | ||||
|         return CommonResult.error(BAD_REQUEST); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -158,6 +163,18 @@ public class GlobalExceptionHandler { | ||||
|         return CommonResult.error(METHOD_NOT_ALLOWED.getCode(), String.format("请求方法不正确:%s", ex.getMessage())); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 处理 Spring Security 权限不足的异常 | ||||
|      * | ||||
|      * 来源是,使用 @PreAuthorize 注解,AOP 进行权限拦截 | ||||
|      */ | ||||
|     @ExceptionHandler(value = AccessDeniedException.class) | ||||
|     public CommonResult<?> accessDeniedExceptionHandler(HttpServletRequest req, AccessDeniedException ex) { | ||||
|         log.warn("[accessDeniedExceptionHandler][userId({}) 无法访问 url({})]", SecurityUtils.getLoginUserId(), | ||||
|                 req.getRequestURL(), ex); | ||||
|         return CommonResult.error(FORBIDDEN); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 处理业务异常 ServiceException | ||||
|      * | ||||
|   | ||||
| @@ -7,9 +7,7 @@ import lombok.Builder; | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
| import org.hibernate.validator.constraints.Length; | ||||
| import org.springframework.validation.annotation.Validated; | ||||
|  | ||||
| import javax.validation.Valid; | ||||
| import javax.validation.constraints.NotEmpty; | ||||
| import javax.validation.constraints.Pattern; | ||||
|  | ||||
| @@ -22,7 +20,7 @@ public class SysAuthLoginReqVO { | ||||
|  | ||||
|     @ApiModelProperty(value = "账号", required = true, example = "yudaoyuanma") | ||||
|     @NotEmpty(message = "登陆账号不能为空") | ||||
|     @Length(min = 5, max = 16, message = "账号长度为 5-16 位") | ||||
|     @Length(min = 4, max = 16, message = "账号长度为 4-16 位") | ||||
|     @Pattern(regexp = "^[A-Za-z0-9]+$", message = "账号格式为数字以及字母") | ||||
|     private String username; | ||||
|  | ||||
|   | ||||
| @@ -0,0 +1,3 @@ | ||||
| ### 请求 /system/user/page 接口 => 没有权限 | ||||
| GET {{baseUrl}}/system/user/page?pageNo=1&pageSize=10 | ||||
| Authorization: Bearer test104 # 使用测试账号 | ||||
| @@ -18,6 +18,7 @@ import io.swagger.annotations.Api; | ||||
| import io.swagger.annotations.ApiImplicitParam; | ||||
| import io.swagger.annotations.ApiImplicitParams; | ||||
| import io.swagger.annotations.ApiOperation; | ||||
| import org.springframework.security.access.prepost.PreAuthorize; | ||||
| import org.springframework.validation.annotation.Validated; | ||||
| import org.springframework.web.bind.annotation.*; | ||||
| import org.springframework.web.multipart.MultipartFile; | ||||
| @@ -41,7 +42,7 @@ public class SysUserController { | ||||
|  | ||||
|     @ApiOperation("获得用户分页列表") | ||||
|     @GetMapping("/page") | ||||
| //    @PreAuthorize("@ss.hasPermi('system:user:list')") | ||||
|     @PreAuthorize("@ss.hasPermission('system:user:list')") | ||||
|     public CommonResult<PageResult<SysUserPageItemRespVO>> pageUsers(@Validated SysUserPageReqVO reqVO) { | ||||
|         // 获得用户分页列表 | ||||
|         PageResult<SysUserDO> pageResult = userService.pageUsers(reqVO); | ||||
|   | ||||
| @@ -1,16 +1,17 @@ | ||||
| package cn.iocoder.dashboard.modules.system.dal.mysql.dao.permission; | ||||
|  | ||||
| import cn.iocoder.dashboard.framework.mybatis.core.mapper.BaseMapperX; | ||||
| import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysRoleMenuDO; | ||||
| import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; | ||||
| import com.baomidou.mybatisplus.core.mapper.BaseMapper; | ||||
| import org.apache.ibatis.annotations.Mapper; | ||||
|  | ||||
| import java.util.Collection; | ||||
| import java.util.Date; | ||||
| import java.util.List; | ||||
| import java.util.stream.Collectors; | ||||
|  | ||||
| @Mapper | ||||
| public interface SysRoleMenuMapper extends BaseMapper<SysRoleMenuDO> { | ||||
| public interface SysRoleMenuMapper extends BaseMapperX<SysRoleMenuDO> { | ||||
|  | ||||
|     default List<SysRoleMenuDO> selectListByRoleId(Long roleId) { | ||||
|         return selectList(new QueryWrapper<SysRoleMenuDO>().eq("role_id", roleId)); | ||||
| @@ -32,4 +33,9 @@ public interface SysRoleMenuMapper extends BaseMapper<SysRoleMenuDO> { | ||||
|                 .in("menu_id", menuIds)); | ||||
|     } | ||||
|  | ||||
|     default boolean selectExistsByUpdateTimeAfter(Date maxUpdateTime) { | ||||
|         return selectOne(new QueryWrapper<SysRoleMenuDO>().select("id") | ||||
|                 .gt("update_time", maxUpdateTime).last("LIMIT 1")) != null; | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,29 @@ | ||||
| package cn.iocoder.dashboard.modules.system.mq.consumer.permission; | ||||
|  | ||||
| import cn.iocoder.dashboard.framework.redis.core.pubsub.AbstractChannelMessageListener; | ||||
| import cn.iocoder.dashboard.modules.system.mq.message.permission.SysRoleMenuRefreshMessage; | ||||
| import cn.iocoder.dashboard.modules.system.service.permission.SysPermissionService; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.stereotype.Component; | ||||
|  | ||||
| import javax.annotation.Resource; | ||||
|  | ||||
| /** | ||||
|  * 针对 {@link SysRoleMenuRefreshMessage} 的消费者 | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| @Component | ||||
| @Slf4j | ||||
| public class SysRoleMenuRefreshConsumer extends AbstractChannelMessageListener<SysRoleMenuRefreshMessage> { | ||||
|  | ||||
|     @Resource | ||||
|     private SysPermissionService permissionService; | ||||
|  | ||||
|     @Override | ||||
|     public void onMessage(SysRoleMenuRefreshMessage message) { | ||||
|         log.info("[onMessage][收到 Role 与 Menu 的关联刷新消息]"); | ||||
|         permissionService.initLocalCache(); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,17 @@ | ||||
| package cn.iocoder.dashboard.modules.system.mq.message.permission; | ||||
|  | ||||
| import cn.iocoder.dashboard.framework.redis.core.pubsub.ChannelMessage; | ||||
| import lombok.Data; | ||||
|  | ||||
| /** | ||||
|  * 角色与菜单数据刷新 Message | ||||
|  */ | ||||
| @Data | ||||
| public class SysRoleMenuRefreshMessage implements ChannelMessage { | ||||
|  | ||||
|     @Override | ||||
|     public String getChannel() { | ||||
|         return "system.role-menu.refresh"; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,27 @@ | ||||
| package cn.iocoder.dashboard.modules.system.mq.producer.permission; | ||||
|  | ||||
| import cn.iocoder.dashboard.framework.redis.core.util.RedisMessageUtils; | ||||
| import cn.iocoder.dashboard.modules.system.mq.message.permission.SysRoleMenuRefreshMessage; | ||||
| import org.springframework.data.redis.core.StringRedisTemplate; | ||||
| import org.springframework.stereotype.Component; | ||||
|  | ||||
| import javax.annotation.Resource; | ||||
|  | ||||
| /** | ||||
|  * Permission 权限相关消息的 Producer | ||||
|  */ | ||||
| @Component | ||||
| public class SysPermissionProducer { | ||||
|  | ||||
|     @Resource | ||||
|     private StringRedisTemplate stringRedisTemplate; | ||||
|  | ||||
|     /** | ||||
|      * 发送 {@link SysRoleMenuRefreshMessage} 消息 | ||||
|      */ | ||||
|     public void sendRoleMenuRefreshMessage() { | ||||
|         SysRoleMenuRefreshMessage message = new SysRoleMenuRefreshMessage(); | ||||
|         RedisMessageUtils.sendChannelMessage(stringRedisTemplate, message); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,6 +1,6 @@ | ||||
| package cn.iocoder.dashboard.modules.system.service.auth; | ||||
|  | ||||
| import cn.iocoder.dashboard.framework.security.core.service.SecurityFrameworkService; | ||||
| import cn.iocoder.dashboard.framework.security.core.service.SecurityAuthFrameworkService; | ||||
|  | ||||
| /** | ||||
|  * 认证 Service 接口 | ||||
| @@ -9,7 +9,7 @@ import cn.iocoder.dashboard.framework.security.core.service.SecurityFrameworkSer | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| public interface SysAuthService extends SecurityFrameworkService { | ||||
| public interface SysAuthService extends SecurityAuthFrameworkService { | ||||
|  | ||||
|     String login(String username, String password, String captchaUUID, String captchaCode); | ||||
|  | ||||
|   | ||||
| @@ -59,6 +59,14 @@ public interface SysMenuService { | ||||
|     List<SysMenuDO> listMenusFromCache(Collection<Long> menuIds, Collection<Integer> menuTypes, | ||||
|                                        Collection<Integer> menusStatuses); | ||||
|  | ||||
|     /** | ||||
|      * 获得权限对应的菜单数组 | ||||
|      * | ||||
|      * @param permission 权限标识 | ||||
|      * @return 数组 | ||||
|      */ | ||||
|     List<SysMenuDO> getMenuListByPermissionFromCache(String permission); | ||||
|  | ||||
|     /* | ||||
|      * 创建菜单 | ||||
|      * | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| package cn.iocoder.dashboard.modules.system.service.permission; | ||||
|  | ||||
| import cn.iocoder.dashboard.framework.security.core.service.SecurityPermissionFrameworkService; | ||||
| import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysMenuDO; | ||||
| import org.springframework.lang.Nullable; | ||||
|  | ||||
| @@ -14,12 +15,12 @@ import java.util.Set; | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| public interface SysPermissionService { | ||||
| public interface SysPermissionService extends SecurityPermissionFrameworkService { | ||||
|  | ||||
|     /** | ||||
|      * 初始化 | ||||
|      */ | ||||
|     void init(); | ||||
|     void initLocalCache(); | ||||
|  | ||||
|     /** | ||||
|      * 获得角色们拥有的菜单列表,从缓存中获取 | ||||
|   | ||||
| @@ -56,6 +56,16 @@ public interface SysRoleService { | ||||
|      */ | ||||
|     boolean hasAnyAdmin(Collection<SysRoleDO> roleList); | ||||
|  | ||||
|     /** | ||||
|      * 判断角色编号数组中,是否有管理员 | ||||
|      * | ||||
|      * @param ids 角色编号数组 | ||||
|      * @return 是否有管理员 | ||||
|      */ | ||||
|     default boolean hasAnyAdmin(Set<Long> ids) { | ||||
|         return hasAnyAdmin(listRolesFromCache(ids)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 创建角色 | ||||
|      * | ||||
|   | ||||
| @@ -61,7 +61,7 @@ public class SysMenuServiceImpl implements SysMenuService { | ||||
|      * | ||||
|      * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向 | ||||
|      */ | ||||
|     private volatile Multimap<String, SysMenuDO> permMenuCache; | ||||
|     private volatile Multimap<String, SysMenuDO> permissionMenuCache; | ||||
|     /** | ||||
|      * 缓存菜单的最大更新时间,用于后续的增量轮询,判断是否有更新 | ||||
|      */ | ||||
| @@ -76,7 +76,7 @@ public class SysMenuServiceImpl implements SysMenuService { | ||||
|     private SysMenuProducer menuProducer; | ||||
|  | ||||
|     /** | ||||
|      * 初始化 {@link #menuCache} 和 {@link #permMenuCache} 缓存 | ||||
|      * 初始化 {@link #menuCache} 和 {@link #permissionMenuCache} 缓存 | ||||
|      */ | ||||
|     @Override | ||||
|     @PostConstruct | ||||
| @@ -95,7 +95,7 @@ public class SysMenuServiceImpl implements SysMenuService { | ||||
|             permMenuCacheBuilder.put(menuDO.getPermission(), menuDO); | ||||
|         }); | ||||
|         menuCache = menuCacheBuilder.build(); | ||||
|         permMenuCache = permMenuCacheBuilder.build(); | ||||
|         permissionMenuCache = permMenuCacheBuilder.build(); | ||||
|         assert menuList.size() > 0; // 断言,避免告警 | ||||
|         maxUpdateTime = menuList.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime(); | ||||
|         log.info("[initLocalCache][缓存菜单,数量为:{}]", menuList.size()); | ||||
| @@ -162,6 +162,11 @@ public class SysMenuServiceImpl implements SysMenuService { | ||||
|                 .collect(Collectors.toList()); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public List<SysMenuDO> getMenuListByPermissionFromCache(String permission) { | ||||
|         return new ArrayList<>(permissionMenuCache.get(permission)); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Long createMenu(SysMenuCreateReqVO reqVO) { | ||||
|         // 校验父菜单存在 | ||||
|   | ||||
| @@ -2,12 +2,16 @@ package cn.iocoder.dashboard.modules.system.service.permission.impl; | ||||
|  | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import cn.hutool.core.collection.CollectionUtil; | ||||
| import cn.hutool.core.util.ArrayUtil; | ||||
| import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO; | ||||
| import cn.iocoder.dashboard.framework.security.core.util.SecurityUtils; | ||||
| import cn.iocoder.dashboard.modules.system.dal.mysql.dao.permission.SysRoleMenuMapper; | ||||
| import cn.iocoder.dashboard.modules.system.dal.mysql.dao.permission.SysUserRoleMapper; | ||||
| import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysMenuDO; | ||||
| import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysRoleDO; | ||||
| import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysRoleMenuDO; | ||||
| import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.permission.SysUserRoleDO; | ||||
| import cn.iocoder.dashboard.modules.system.mq.producer.permission.SysPermissionProducer; | ||||
| import cn.iocoder.dashboard.modules.system.service.permission.SysMenuService; | ||||
| import cn.iocoder.dashboard.modules.system.service.permission.SysPermissionService; | ||||
| import cn.iocoder.dashboard.modules.system.service.permission.SysRoleService; | ||||
| @@ -18,23 +22,28 @@ import com.google.common.collect.Multimap; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.stereotype.Service; | ||||
| import org.springframework.transaction.annotation.Transactional; | ||||
| import org.springframework.transaction.support.TransactionSynchronization; | ||||
| import org.springframework.transaction.support.TransactionSynchronizationManager; | ||||
|  | ||||
| import javax.annotation.PostConstruct; | ||||
| import javax.annotation.Resource; | ||||
| import java.util.Collection; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| import java.util.Set; | ||||
| import java.util.*; | ||||
|  | ||||
| /** | ||||
|  * 权限 Service 实现类 | ||||
|  * | ||||
|  * @author 芋道源码 | ||||
|  */ | ||||
| @Service | ||||
| @Service("ss") // 使用 Spring Security 的缩写,方便食用 | ||||
| @Slf4j | ||||
| public class SysPermissionServiceImpl implements SysPermissionService { | ||||
|  | ||||
|     /** | ||||
|      * 定时执行 {@link #schedulePeriodicRefresh()} 的周期 | ||||
|      * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高 | ||||
|      */ | ||||
|     private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L; | ||||
|  | ||||
|     /** | ||||
|      * 角色编号与菜单编号的缓存映射 | ||||
|      * key:角色编号 | ||||
| @@ -51,6 +60,10 @@ public class SysPermissionServiceImpl implements SysPermissionService { | ||||
|      * 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向 | ||||
|      */ | ||||
|     private volatile Multimap<Long, Long> menuRoleCache; | ||||
|     /** | ||||
|      * 缓存菜单的最大更新时间,用于后续的增量轮询,判断是否有更新 | ||||
|      */ | ||||
|     private volatile Date maxUpdateTime; | ||||
|  | ||||
|     @Resource | ||||
|     private SysRoleMenuMapper roleMenuMapper; | ||||
| @@ -62,14 +75,22 @@ public class SysPermissionServiceImpl implements SysPermissionService { | ||||
|     @Resource | ||||
|     private SysMenuService menuService; | ||||
|  | ||||
|     @Resource | ||||
|     private SysPermissionProducer permissionProducer; | ||||
|  | ||||
|     /** | ||||
|      * 初始化 {@link #roleMenuCache} 和 {@link #menuRoleCache} 缓存 | ||||
|      */ | ||||
|     @Override | ||||
|     @PostConstruct | ||||
|     public void init() { | ||||
|     public void initLocalCache() { | ||||
|         // 获取角色与菜单的关联列表,如果有更新 | ||||
|         List<SysRoleMenuDO> roleMenuList = this.loadRoleMenuIfUpdate(maxUpdateTime); | ||||
|         if (CollUtil.isEmpty(roleMenuList)) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         // 初始化 roleMenuCache 和 menuRoleCache 缓存 | ||||
|         List<SysRoleMenuDO> roleMenuList = roleMenuMapper.selectList(null); | ||||
|         ImmutableMultimap.Builder<Long, Long> roleMenuCacheBuilder = ImmutableMultimap.builder(); | ||||
|         ImmutableMultimap.Builder<Long, Long> menuRoleCacheBuilder = ImmutableMultimap.builder(); | ||||
|         roleMenuList.forEach(roleMenuDO -> { | ||||
| @@ -78,9 +99,32 @@ public class SysPermissionServiceImpl implements SysPermissionService { | ||||
|         }); | ||||
|         roleMenuCache = roleMenuCacheBuilder.build(); | ||||
|         menuRoleCache = menuRoleCacheBuilder.build(); | ||||
|         assert roleMenuList.size() > 0; // 断言,避免告警 | ||||
|         maxUpdateTime = roleMenuList.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime(); | ||||
|         log.info("[initLocalCache][初始化角色与菜单的关联数量为 {}]", roleMenuList.size()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 如果角色与菜单的关联发生变化,从数据库中获取最新的全量角色与菜单的关联。 | ||||
|      * 如果未发生变化,则返回空 | ||||
|      * | ||||
|      * @param maxUpdateTime 当前角色与菜单的关联的最大更新时间 | ||||
|      * @return 角色与菜单的关联列表 | ||||
|      */ | ||||
|     private List<SysRoleMenuDO> loadRoleMenuIfUpdate(Date maxUpdateTime) { | ||||
|         // 第一步,判断是否要更新。 | ||||
|         if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据 | ||||
|             log.info("[loadRoleMenuIfUpdate][首次加载全量角色与菜单的关联]"); | ||||
|         } else { // 判断数据库中是否有更新的角色与菜单的关联 | ||||
|             if (!roleMenuMapper.selectExistsByUpdateTimeAfter(maxUpdateTime)) { | ||||
|                 return null; | ||||
|             } | ||||
|             log.info("[loadRoleMenuIfUpdate][增量加载全量角色与菜单的关联]"); | ||||
|         } | ||||
|         // 第二步,如果有更新,则从数据库加载所有角色与菜单的关联 | ||||
|         return roleMenuMapper.selectList(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public List<SysMenuDO> listRoleMenusFromCache(Collection<Long> roleIds, Collection<Integer> menuTypes, | ||||
|                                                   Collection<Integer> menusStatuses) { | ||||
| @@ -140,6 +184,15 @@ public class SysPermissionServiceImpl implements SysPermissionService { | ||||
|         if (!CollectionUtil.isEmpty(deleteMenuIds)) { | ||||
|             roleMenuMapper.deleteListByRoleIdAndMenuIds(roleId, deleteMenuIds); | ||||
|         } | ||||
|         // 发送刷新消息. 注意,需要事务提交后,在进行发送刷新消息。不然 db 还未提交,结果缓存先刷新了 | ||||
|         TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { | ||||
|  | ||||
|             @Override | ||||
|             public void afterCommit() { | ||||
|                 permissionProducer.sendRoleMenuRefreshMessage(); | ||||
|             } | ||||
|  | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
| @@ -189,4 +242,39 @@ public class SysPermissionServiceImpl implements SysPermissionService { | ||||
|         // TODO 实现我 | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean hasPermission(String permission) { | ||||
|         return hasAnyPermissions(permission); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean hasAnyPermissions(String... permissions) { | ||||
|         // 如果为空,说明已经有权限 | ||||
|         if (ArrayUtil.isEmpty(permissions)) { | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         // 获得当前登陆的角色。如果为空,说明没有权限 | ||||
|         Set<Long> roleIds = SecurityUtils.getLoginUserRoleIds(); | ||||
|         if (CollUtil.isEmpty(roleIds)) { | ||||
|             return false; | ||||
|         } | ||||
|         // 判断是否是超管。如果是,当然符合条件 | ||||
|         if (roleService.hasAnyAdmin(roleIds)) { | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         // 遍历权限,判断是否有一个满足 | ||||
|         return Arrays.stream(permissions).anyMatch(permission -> { | ||||
|             List<SysMenuDO> menuList = menuService.getMenuListByPermissionFromCache(permission); | ||||
|             // 采用严格模式,如果权限找不到对应的 Menu 的话,认为 | ||||
|             if (CollUtil.isEmpty(menuList)) { | ||||
|                 return false; | ||||
|             } | ||||
|             // 获得是否拥有该权限,任一一个 | ||||
|             return menuList.stream().anyMatch(menu -> CollUtil.containsAny(roleIds, | ||||
|                     menuRoleCache.get(menu.getId()))); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -217,6 +217,8 @@ public class SysRoleServiceImpl implements SysRoleService { | ||||
|         updateObject.setId(id); | ||||
|         updateObject.setStatus(status); | ||||
|         roleMapper.updateById(updateObject); | ||||
|         // 发送刷新消息 | ||||
|         roleProducer.sendRoleRefreshMessage(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
| @@ -229,6 +231,8 @@ public class SysRoleServiceImpl implements SysRoleService { | ||||
|         updateObject.setDataScope(dataScope); | ||||
|         updateObject.setDataScopeDeptIds(dataScopeDeptIds); | ||||
|         roleMapper.updateById(updateObject); | ||||
|         // 发送刷新消息 | ||||
|         roleProducer.sendRoleRefreshMessage(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -34,7 +34,7 @@ yudao: | ||||
|     token-timeout: 1d | ||||
|     session-timeout: 30m | ||||
|     mock-enable: true | ||||
|     mock-secret: yudaoyuanma | ||||
|     mock-secret: test | ||||
|   swagger: | ||||
|     title: 管理后台 | ||||
|     description: 提供管理员管理的所有功能 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 YunaiV
					YunaiV