diff --git a/cf-framework/cf-common/src/main/java/com/cf/imes/framework/common/util/cache/CacheUtils.java b/cf-framework/cf-common/src/main/java/com/cf/imes/framework/common/util/cache/CacheUtils.java index 7a180960f..40351bd65 100644 --- a/cf-framework/cf-common/src/main/java/com/cf/imes/framework/common/util/cache/CacheUtils.java +++ b/cf-framework/cf-common/src/main/java/com/cf/imes/framework/common/util/cache/CacheUtils.java @@ -17,13 +17,21 @@ import java.util.concurrent.Executors; public class CacheUtils { public static LoadingCache buildAsyncReloadingCache(Duration duration, CacheLoader loader) { - Executor executor = Executors.newCachedThreadPool( // TODO 晨丰:可能要思考下,未来要不要做成可配置 - TtlExecutors.getDefaultDisableInheritableThreadFactory()); // TTL 保证 ThreadLocal 可以透传 return CacheBuilder.newBuilder() // 只阻塞当前数据加载线程,其他线程返回旧值 .refreshAfterWrite(duration) // 通过 asyncReloading 实现全异步加载,包括 refreshAfterWrite 被阻塞的加载线程 - .build(CacheLoader.asyncReloading(loader, executor)); + .build(CacheLoader.asyncReloading(loader, Executors.newCachedThreadPool())); // TODO 芋艿:可能要思考下,未来要不要做成可配置 } + /** + * 构建同步刷新的 LoadingCache 对象 + * + * @param duration 过期时间 + * @param loader CacheLoader 对象 + * @return LoadingCache 对象 + */ + public static LoadingCache buildCache(Duration duration, CacheLoader loader) { + return CacheBuilder.newBuilder().refreshAfterWrite(duration).build(loader); + } } diff --git a/cf-framework/cf-spring-boot-starter-biz-organ/src/main/java/com/cf/imes/framework/organ/core/context/OrganContextHolder.java b/cf-framework/cf-spring-boot-starter-biz-organ/src/main/java/com/cf/imes/framework/organ/core/context/OrganContextHolder.java index 1912f9a3f..62df76118 100644 --- a/cf-framework/cf-spring-boot-starter-biz-organ/src/main/java/com/cf/imes/framework/organ/core/context/OrganContextHolder.java +++ b/cf-framework/cf-spring-boot-starter-biz-organ/src/main/java/com/cf/imes/framework/organ/core/context/OrganContextHolder.java @@ -21,6 +21,11 @@ public class OrganContextHolder { */ private static final ThreadLocal IGNORE = new TransmittableThreadLocal<>(); + /** + * 上下文获取不到organid异常提示 + */ + public static final String ORGANID_NOT_EXIST_EXCEPTION = "OrganContextHolder 不存在组织编号!"; + /** * 获得组织编号 * @@ -48,8 +53,7 @@ public class OrganContextHolder { public static Long getRequiredOrganId() { Long organId = getOrganId(); if (organId == null) { - throw new NullPointerException("OrganContextHolder 不存在组织编号!可参考文档:" - + DocumentEnum.ORGAN.getUrl()); + throw new NullPointerException(ORGANID_NOT_EXIST_EXCEPTION); } return organId; } diff --git a/cf-framework/cf-spring-boot-starter-biz-organ/src/main/java/com/cf/imes/framework/organ/core/security/OrganSecurityWebFilter.java b/cf-framework/cf-spring-boot-starter-biz-organ/src/main/java/com/cf/imes/framework/organ/core/security/OrganSecurityWebFilter.java index fc54c41d5..64fd51034 100644 --- a/cf-framework/cf-spring-boot-starter-biz-organ/src/main/java/com/cf/imes/framework/organ/core/security/OrganSecurityWebFilter.java +++ b/cf-framework/cf-spring-boot-starter-biz-organ/src/main/java/com/cf/imes/framework/organ/core/security/OrganSecurityWebFilter.java @@ -1,8 +1,6 @@ package com.cf.imes.framework.organ.core.security; import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.util.StrUtil; -import com.cf.imes.framework.common.enums.RpcConstants; import com.cf.imes.framework.common.exception.enums.GlobalErrorCodeConstants; import com.cf.imes.framework.common.pojo.CommonResult; import com.cf.imes.framework.common.util.servlet.ServletUtils; @@ -56,18 +54,10 @@ public class OrganSecurityWebFilter extends ApiRequestFilter { this.organFrameworkService = organFrameworkService; } - @Override - protected boolean shouldNotFilter(HttpServletRequest request) { - return super.shouldNotFilter(request) && - !StrUtil.startWithAny(request.getRequestURI(), RpcConstants.RPC_API_PREFIX); // 因为 RPC API 也会透传组织编号 - } - @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { Long organId = WebFrameworkUtils.getOrganId(request); - //Long organId = OrganContextHolder.getOrganId(); - boolean isRpcRequest = WebFrameworkUtils.isRpcRequest(request); // 1. 登陆的用户,校验是否有权限访问该组织,避免越权问题。 LoginUser user = SecurityFrameworkUtils.getLoginUser(); if (user != null) { @@ -76,8 +66,7 @@ public class OrganSecurityWebFilter extends ApiRequestFilter { organId = user.getOrganId(); OrganContextHolder.setOrganId(organId); // 如果传递了组织编号,则进行比对组织编号,避免越权问题 - } else if (!Objects.equals(user.getOrganId(), OrganContextHolder.getOrganId()) - && !isRpcRequest) { // Cloud 特殊逻辑:如果是 RPC 请求,就不校验了。主要考虑,一些场景下,会调用 OrganUtils 去切换组织 + } else if (!Objects.equals(user.getOrganId(), OrganContextHolder.getOrganId())) { // Cloud 特殊逻辑:如果是 RPC 请求,就不校验了。主要考虑,一些场景下,会调用 OrganUtils 去切换组织 log.error("[doFilterInternal][组织({}) User({}/{}) 越权访问组织({}) URL({}/{})]", user.getOrganId(), user.getId(), user.getUserType(), OrganContextHolder.getOrganId(), request.getRequestURI(), request.getMethod()); diff --git a/cf-framework/cf-spring-boot-starter-mq/src/main/java/com/cf/imes/framework/mq/redis/config/ChenfengRedisMQConsumerAutoConfiguration.java b/cf-framework/cf-spring-boot-starter-mq/src/main/java/com/cf/imes/framework/mq/redis/config/ChenfengRedisMQConsumerAutoConfiguration.java index e6d7d5229..9e4239c29 100644 --- a/cf-framework/cf-spring-boot-starter-mq/src/main/java/com/cf/imes/framework/mq/redis/config/ChenfengRedisMQConsumerAutoConfiguration.java +++ b/cf-framework/cf-spring-boot-starter-mq/src/main/java/com/cf/imes/framework/mq/redis/config/ChenfengRedisMQConsumerAutoConfiguration.java @@ -7,6 +7,7 @@ import com.cf.imes.framework.common.enums.DocumentEnum; import com.cf.imes.framework.mq.redis.core.RedisMQTemplate; import com.cf.imes.framework.mq.redis.core.job.RedisPendingMessageResendJob; import com.cf.imes.framework.mq.redis.core.pubsub.AbstractRedisChannelMessageListener; +import com.cf.imes.framework.mq.redis.core.pubsub.AbstractRedisSimpleMessageListener; import com.cf.imes.framework.mq.redis.core.stream.AbstractRedisStreamMessageListener; import com.cf.imes.framework.redis.config.ChenfengRedisAutoConfiguration; import lombok.extern.slf4j.Slf4j; @@ -61,6 +62,26 @@ public class ChenfengRedisMQConsumerAutoConfiguration { return container; } + /** + * 创建 Redis Pub/Sub 广播消费的容器 + */ + @Bean + @ConditionalOnBean(AbstractRedisSimpleMessageListener.class) + public RedisMessageListenerContainer redisDelListenerContainer( + RedisTemplate redisTemplate, List listeners) { + // 创建 RedisMessageListenerContainer 对象 + RedisMessageListenerContainer container = new RedisMessageListenerContainer(); + // 设置 RedisConnection 工厂。 + container.setConnectionFactory(redisTemplate.getRequiredConnectionFactory()); + // 添加监听器 + listeners.forEach(listener -> { + container.addMessageListener(listener, listener.getTopic()); + log.info("[redisMessageListenerContainer][注册 ChannelTopic({}) 对应的监听器({})]", + listener.getTopic(), listener.getClass().getName()); + }); + return container; + } + /** * 创建 Redis Stream 重新消费的任务 */ diff --git a/cf-framework/cf-spring-boot-starter-mq/src/main/java/com/cf/imes/framework/mq/redis/core/pubsub/AbstractRedisSimpleMessageListener.java b/cf-framework/cf-spring-boot-starter-mq/src/main/java/com/cf/imes/framework/mq/redis/core/pubsub/AbstractRedisSimpleMessageListener.java new file mode 100644 index 000000000..1f03d3d46 --- /dev/null +++ b/cf-framework/cf-spring-boot-starter-mq/src/main/java/com/cf/imes/framework/mq/redis/core/pubsub/AbstractRedisSimpleMessageListener.java @@ -0,0 +1,22 @@ +package com.cf.imes.framework.mq.redis.core.pubsub; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.data.redis.connection.MessageListener; +import org.springframework.data.redis.listener.ChannelTopic; + +/** + * Redis监听器 + * + * + * @author 晨丰科技 + */ +public abstract class AbstractRedisSimpleMessageListener implements MessageListener { + + /** + * 通道主题 + */ + @Getter + @Setter + private ChannelTopic topic; +} diff --git a/cf-framework/cf-spring-boot-starter-security/src/main/java/com/cf/imes/framework/security/core/filter/TokenAuthenticationFilter.java b/cf-framework/cf-spring-boot-starter-security/src/main/java/com/cf/imes/framework/security/core/filter/TokenAuthenticationFilter.java index e11ef1707..fe6a24e5b 100644 --- a/cf-framework/cf-spring-boot-starter-security/src/main/java/com/cf/imes/framework/security/core/filter/TokenAuthenticationFilter.java +++ b/cf-framework/cf-spring-boot-starter-security/src/main/java/com/cf/imes/framework/security/core/filter/TokenAuthenticationFilter.java @@ -15,6 +15,8 @@ import com.cf.imes.module.system.api.oauth2.OAuth2TokenApi; import com.cf.imes.module.system.api.oauth2.dto.OAuth2AccessTokenCheckRespDTO; import com.cf.imes.module.system.api.permission.PermissionApi; import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; import org.springframework.security.access.AccessDeniedException; import org.springframework.web.filter.OncePerRequestFilter; @@ -33,6 +35,7 @@ import java.nio.charset.StandardCharsets; * @author 晨丰科技 */ @RequiredArgsConstructor +@Slf4j public class TokenAuthenticationFilter extends OncePerRequestFilter { private final SecurityProperties securityProperties; @@ -129,14 +132,17 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter { .setOrganId(WebFrameworkUtils.getOrganId(request)); } + @SneakyThrows private LoginUser buildLoginUserByHeader(HttpServletRequest request) { String loginUserStr = request.getHeader(SecurityFrameworkUtils.LOGIN_USER_HEADER); if(StrUtil.isNotEmpty(loginUserStr)) { - LoginUser loginUser = JsonUtils.parseObject(loginUserStr, LoginUser.class); - //CommonResult superAdmin = permissionApi.hasAnyRoles(loginUser.getId(), "super_admin"); - //loginUser.setIsSupAdmin(superAdmin.getCheckedData()); - loginUser.setNickname(URLDecoder.decode(loginUser.getNickname(),StandardCharsets.UTF_8)); - return loginUser; + try { + loginUserStr = URLDecoder.decode(loginUserStr, StandardCharsets.UTF_8.name()); // 解码,解决中文乱码问题 + return JsonUtils.parseObject(loginUserStr, LoginUser.class); + } catch (Exception ex) { + log.error("[buildLoginUserByHeader][解析 LoginUser({}) 发生异常]", loginUserStr, ex); ; + throw ex; + } } return null; } diff --git a/cf-framework/cf-spring-boot-starter-security/src/main/java/com/cf/imes/framework/security/core/rpc/LoginUserRequestInterceptor.java b/cf-framework/cf-spring-boot-starter-security/src/main/java/com/cf/imes/framework/security/core/rpc/LoginUserRequestInterceptor.java index 4737ec895..c77b94564 100644 --- a/cf-framework/cf-spring-boot-starter-security/src/main/java/com/cf/imes/framework/security/core/rpc/LoginUserRequestInterceptor.java +++ b/cf-framework/cf-spring-boot-starter-security/src/main/java/com/cf/imes/framework/security/core/rpc/LoginUserRequestInterceptor.java @@ -1,23 +1,37 @@ package com.cf.imes.framework.security.core.rpc; -import com.cf.imes.framework.rpc.core.util.FeignUtils; +import com.cf.imes.framework.common.util.json.JsonUtils; import com.cf.imes.framework.security.core.LoginUser; import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils; import feign.RequestInterceptor; import feign.RequestTemplate; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; /** * LoginUser 的 RequestInterceptor 实现类:Feign 请求时,将 {@link LoginUser} 设置到 header 中,继续透传给被调用的服务 * * @author 晨丰科技 */ +@Slf4j public class LoginUserRequestInterceptor implements RequestInterceptor { @Override + @SneakyThrows public void apply(RequestTemplate requestTemplate) { LoginUser user = SecurityFrameworkUtils.getLoginUser(); if (user != null) { - FeignUtils.createJsonHeader(requestTemplate, SecurityFrameworkUtils.LOGIN_USER_HEADER, user); + try { + String userStr = JsonUtils.toJsonString(user); + userStr = URLEncoder.encode(userStr, StandardCharsets.UTF_8.name()); // 编码,避免中文乱码 + requestTemplate.header(SecurityFrameworkUtils.LOGIN_USER_HEADER, userStr); + } catch (Exception ex) { + log.error("[apply][序列化 LoginUser({}) 发生异常]", user, ex); + throw ex; + } } } diff --git a/cf-framework/cf-spring-boot-starter-security/src/main/java/com/cf/imes/framework/security/core/service/SecurityFrameworkService.java b/cf-framework/cf-spring-boot-starter-security/src/main/java/com/cf/imes/framework/security/core/service/SecurityFrameworkService.java index 7072b583b..13b1613cb 100644 --- a/cf-framework/cf-spring-boot-starter-security/src/main/java/com/cf/imes/framework/security/core/service/SecurityFrameworkService.java +++ b/cf-framework/cf-spring-boot-starter-security/src/main/java/com/cf/imes/framework/security/core/service/SecurityFrameworkService.java @@ -23,6 +23,13 @@ public interface SecurityFrameworkService { */ boolean hasAnyPermissions(String... permissions); + /** + * 清空本地缓存 + * + * @param + */ + void invalidateAll(); + /** * 判断是否有角色 * diff --git a/cf-framework/cf-spring-boot-starter-security/src/main/java/com/cf/imes/framework/security/core/service/SecurityFrameworkServiceImpl.java b/cf-framework/cf-spring-boot-starter-security/src/main/java/com/cf/imes/framework/security/core/service/SecurityFrameworkServiceImpl.java index c438c9efe..ec0885c00 100644 --- a/cf-framework/cf-spring-boot-starter-security/src/main/java/com/cf/imes/framework/security/core/service/SecurityFrameworkServiceImpl.java +++ b/cf-framework/cf-spring-boot-starter-security/src/main/java/com/cf/imes/framework/security/core/service/SecurityFrameworkServiceImpl.java @@ -28,7 +28,7 @@ public class SecurityFrameworkServiceImpl implements SecurityFrameworkService { /** * 针对 {@link #hasAnyRoles(String...)} 的缓存 */ - private final LoadingCache>, Boolean> hasAnyRolesCache = CacheUtils.buildAsyncReloadingCache( + private final LoadingCache>, Boolean> hasAnyRolesCache = CacheUtils.buildCache( Duration.ofMinutes(1L), // 过期时间 1 分钟 new CacheLoader>, Boolean>() { @@ -42,7 +42,7 @@ public class SecurityFrameworkServiceImpl implements SecurityFrameworkService { /** * 针对 {@link #hasAnyPermissions(String...)} 的缓存 */ - private final LoadingCache>, Boolean> hasAnyPermissionsCache = CacheUtils.buildAsyncReloadingCache( + private final LoadingCache>, Boolean> hasAnyPermissionsCache = CacheUtils.buildCache( Duration.ofMinutes(1L), // 过期时间 1 分钟 new CacheLoader>, Boolean>() { @@ -65,6 +65,11 @@ public class SecurityFrameworkServiceImpl implements SecurityFrameworkService { return hasAnyPermissionsCache.get(new KeyValue<>(SecurityFrameworkUtils.getLoginUserId(), Arrays.asList(permissions))); } + @Override + public void invalidateAll() { + hasAnyPermissionsCache.invalidateAll(); + } + @Override public boolean hasRole(String role) { return hasAnyRoles(role); diff --git a/cf-gateway/src/main/java/com/cf/imes/gateway/filter/security/TokenAuthenticationFilter.java b/cf-gateway/src/main/java/com/cf/imes/gateway/filter/security/TokenAuthenticationFilter.java index 65fe68b7a..8da358fce 100644 --- a/cf-gateway/src/main/java/com/cf/imes/gateway/filter/security/TokenAuthenticationFilter.java +++ b/cf-gateway/src/main/java/com/cf/imes/gateway/filter/security/TokenAuthenticationFilter.java @@ -22,11 +22,7 @@ import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; -import java.net.URLEncoder; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; import java.time.Duration; -import java.util.List; import java.util.Objects; import java.util.function.Function; @@ -74,14 +70,6 @@ public class TokenAuthenticationFilter implements GlobalFilter, Ordered { }); - private final LoadingCache organIdCache = CacheUtils.buildAsyncReloadingCache(Duration.ofMinutes(1L), // 过期时间 1 分钟 - new CacheLoader<>() { - @Override - public Long load(String token) { - return getOrganIdByToken(token).block(); - } - }); - public TokenAuthenticationFilter(ReactorLoadBalancerExchangeFilterFunction lbFunction) { // Q:为什么不使用 OAuth2TokenApi 进行调用? // A1:Spring Cloud OpenFeign 官方未内置 Reactive 的支持 https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/#reactive-support @@ -144,17 +132,9 @@ public class TokenAuthenticationFilter implements GlobalFilter, Ordered { private Mono checkAccessToken(Long organId, String token) { return webClient.get() .uri(OAuth2TokenApi.URL_CHECK, uriBuilder -> uriBuilder.queryParam("accessToken", token).build()) - // .headers(httpHeaders -> WebFrameworkUtils.setOrganIdHeader(organId, httpHeaders)) // 设置组织的 Header .retrieve().bodyToMono(String.class); } - private Mono getOrganIdByToken(String token) { - return webClient.get() - .uri(OAuth2TokenApi.URL_ORGAN, uriBuilder -> uriBuilder.queryParam("accessToken", token).build()) - .retrieve() - .bodyToMono(Long.class); - } - private LoginUser buildUser(String body) { // 处理结果,结果不正确 CommonResult result = JsonUtils.parseObject(body, CHECK_RESULT_TYPE_REFERENCE); @@ -175,7 +155,7 @@ public class TokenAuthenticationFilter implements GlobalFilter, Ordered { .setOrganId(tokenInfo.getOrganId()).setScopes(tokenInfo.getScopes()) .setLarge(tokenInfo.getLarge()).setDbNo(tokenInfo.getDbNo()).setTableNo(tokenInfo.getTableNo()) .setDataCode(tokenInfo.getDataCode()).setIsSupAdmin(tokenInfo.getIsSupAdmin()) - .setNickname(URLEncoder.encode(tokenInfo.getNickname(), StandardCharsets.UTF_8)); + .setNickname(tokenInfo.getNickname()); } @Override diff --git a/cf-gateway/src/main/java/com/cf/imes/gateway/util/SecurityFrameworkUtils.java b/cf-gateway/src/main/java/com/cf/imes/gateway/util/SecurityFrameworkUtils.java index aeea82db1..897c0a4c6 100644 --- a/cf-gateway/src/main/java/com/cf/imes/gateway/util/SecurityFrameworkUtils.java +++ b/cf-gateway/src/main/java/com/cf/imes/gateway/util/SecurityFrameworkUtils.java @@ -3,10 +3,15 @@ package com.cf.imes.gateway.util; import cn.hutool.core.map.MapUtil; import com.cf.imes.framework.common.util.json.JsonUtils; import com.cf.imes.gateway.filter.security.LoginUser; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.util.StringUtils; import org.springframework.web.server.ServerWebExchange; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + /** * 安全服务工具类 * @@ -14,6 +19,7 @@ import org.springframework.web.server.ServerWebExchange; * * @author 晨丰科技 */ +@Slf4j public class SecurityFrameworkUtils { private static final String AUTHORIZATION_HEADER = "Authorization"; @@ -101,8 +107,16 @@ public class SecurityFrameworkUtils { * @param builder 请求 * @param user 用户 */ + @SneakyThrows public static void setLoginUserHeader(ServerHttpRequest.Builder builder, LoginUser user) { - builder.header(LOGIN_USER_HEADER, JsonUtils.toJsonString(user)); + try { + String userStr = JsonUtils.toJsonString(user); + userStr = URLEncoder.encode(userStr, StandardCharsets.UTF_8.name()); // 编码,避免中文乱码 + builder.header(LOGIN_USER_HEADER, userStr); + } catch (Exception ex) { + log.error("[setLoginUserHeader][序列化 user({}) 发生异常]", user, ex); + throw ex; + } builder.header(DATA_CODE, user.getDataCode()); builder.header(ORGAN_ID, user.getOrganId().toString()); } diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/mysql/permission/RoleMapper.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/mysql/permission/RoleMapper.java index 6bb522ceb..486b92c2c 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/mysql/permission/RoleMapper.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/mysql/permission/RoleMapper.java @@ -1,7 +1,7 @@ package com.cf.imes.module.system.dal.mysql.permission; +import cn.hutool.core.util.ObjectUtil; import com.cf.imes.framework.common.pojo.PageResult; -import com.cf.imes.framework.mybatis.core.dataobject.BaseDO; import com.cf.imes.framework.mybatis.core.mapper.BaseMapperX; import com.cf.imes.framework.mybatis.core.query.LambdaQueryWrapperX; import com.cf.imes.framework.security.core.LoginUser; @@ -12,9 +12,7 @@ import org.apache.ibatis.annotations.Mapper; import org.springframework.lang.Nullable; import java.util.Collection; -import java.util.HashSet; import java.util.List; -import java.util.Objects; @Mapper public interface RoleMapper extends BaseMapperX { @@ -28,7 +26,7 @@ public interface RoleMapper extends BaseMapperX { //如果是超级管理员并且未传organId,就查看自己的的 lambdaQueryWrapperX.inIfPresent(RoleDO::getOrganId,0L,loginUser.getOrganId()); }*/ - if(isSupAdmin && !Objects.isNull(reqVO.getOrganId())) { + if(isSupAdmin && ObjectUtil.isNotNull(reqVO.getOrganId())) { //如果是超级管理员并且传入organId,就查看传入的组织 lambdaQueryWrapperX.eqIfPresent(RoleDO::getOrganId, reqVO.getOrganId()); } else { @@ -41,7 +39,7 @@ public interface RoleMapper extends BaseMapperX { .eqIfPresent(RoleDO::getStatus, reqVO.getStatus()) .betweenIfPresent(RoleDO::getCreateTime, reqVO.getCreateTime()) .ne(RoleDO::getId, 1) - .orderByDesc(RoleDO::getId); + .orderByAsc(RoleDO::getSort); return selectPage(reqVO, lambdaQueryWrapperX); } diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/redis/RedisRefreshChannelTopicConstants.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/redis/RedisRefreshChannelTopicConstants.java new file mode 100644 index 000000000..89b2dd743 --- /dev/null +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/redis/RedisRefreshChannelTopicConstants.java @@ -0,0 +1,15 @@ +package com.cf.imes.module.system.dal.redis; + +/** + * system redis监听刷新通道名称 常量 + * + * @author Gqr + * @since 2024/6/13 14:55 + */ +public interface RedisRefreshChannelTopicConstants { + + /** + * 刷新权限 + */ + String PREMISSION_REFRESH = "PREMISSION_REFRESH"; +} diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/redis/listener/SystemPremissionRefreshRedisListener.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/redis/listener/SystemPremissionRefreshRedisListener.java new file mode 100644 index 000000000..a16cb6023 --- /dev/null +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/redis/listener/SystemPremissionRefreshRedisListener.java @@ -0,0 +1,31 @@ +package com.cf.imes.module.system.dal.redis.listener; + +import com.cf.imes.framework.mq.redis.core.pubsub.AbstractRedisSimpleMessageListener; +import com.cf.imes.framework.security.core.service.SecurityFrameworkService; +import com.cf.imes.module.system.dal.redis.RedisRefreshChannelTopicConstants; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.connection.Message; +import org.springframework.data.redis.listener.ChannelTopic; +import org.springframework.stereotype.Component; + +/** + * system PREMISSION_REFRESH redis监听器 + * + * @author Gqr + * @since 2024/6/13 10:32 + */ +@Component +public class SystemPremissionRefreshRedisListener extends AbstractRedisSimpleMessageListener { + + @Autowired + private SecurityFrameworkService securityFrameworkService; + + public SystemPremissionRefreshRedisListener() { + super.setTopic(new ChannelTopic(RedisRefreshChannelTopicConstants.PREMISSION_REFRESH)); + } + + @Override + public void onMessage(Message message, byte[] bytes) { + securityFrameworkService.invalidateAll(); + } +} diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/permission/PermissionServiceImpl.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/permission/PermissionServiceImpl.java index 13713855c..f58486c80 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/permission/PermissionServiceImpl.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/permission/PermissionServiceImpl.java @@ -4,14 +4,12 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.extra.spring.SpringUtil; -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.cf.imes.framework.common.enums.CommonStatusEnum; import com.cf.imes.framework.common.util.collection.CollectionUtils; import com.cf.imes.framework.datapermission.core.annotation.DataPermission; import com.cf.imes.framework.mybatis.core.query.LambdaQueryWrapperX; import com.cf.imes.framework.organ.core.aop.OrganIgnore; import com.cf.imes.framework.organ.core.context.OrganContextHolder; -import com.cf.imes.framework.organ.core.db.OrganBaseDO; import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils; import com.cf.imes.module.system.api.permission.dto.DeptDataPermissionRespDTO; import com.cf.imes.module.system.controller.admin.permission.vo.permission.PermissionAssignUserRoleReqVO; @@ -22,6 +20,7 @@ import com.cf.imes.module.system.dal.dataobject.permission.UserRoleDO; import com.cf.imes.module.system.dal.mysql.permission.RoleMenuMapper; import com.cf.imes.module.system.dal.mysql.permission.UserRoleMapper; import com.cf.imes.module.system.dal.redis.RedisKeyConstants; +import com.cf.imes.module.system.dal.redis.RedisRefreshChannelTopicConstants; import com.cf.imes.module.system.enums.permission.DataScopeEnum; import com.cf.imes.module.system.service.dept.DeptService; import com.cf.imes.module.system.service.user.AdminUserService; @@ -30,14 +29,17 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Suppliers; import com.google.common.collect.Sets; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Caching; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.util.*; +import java.util.concurrent.CompletableFuture; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -72,6 +74,9 @@ public class PermissionServiceImpl implements PermissionService { @Resource private AdminUserService userService; + @Autowired + private RedisTemplate redisTemplate; + @Override public boolean hasAnyPermissions(Long userId, String... permissions) { // 如果为空,说明已经有权限 @@ -179,6 +184,16 @@ public class PermissionServiceImpl implements PermissionService { if (CollUtil.isNotEmpty(deleteMenuIds)) { roleMenuMapper.deleteListByRoleIdAndMenuIds(roleId, deleteMenuIds); } + + // 角色菜单权限发生修改,通知刷新本地缓存 + if (CollectionUtil.isNotEmpty(createMenuIds) || CollectionUtil.isNotEmpty(deleteMenuIds)) { + CompletableFuture.runAsync(() -> { + redisTemplate.convertAndSend(RedisRefreshChannelTopicConstants.PREMISSION_REFRESH, ""); + }).exceptionally(e -> { + log.error("redis message发送失败, topic:{}, 异常:{}", RedisRefreshChannelTopicConstants.PREMISSION_REFRESH, e.getMessage()); + return null; + }); + } } @Override