Commit dc42f0f1bb6378a48468bd74d7f4a89a060179fb
1 parent
c10ab175
1. 进一步封装 Redis pub/sub 的能力,简化编程难度
2. 数据字典的本地缓存刷新
Showing
18 changed files
with
232 additions
and
35 deletions
src/main/java/cn/iocoder/dashboard/framework/redis/config/RedisConfig.java
| 1 | package cn.iocoder.dashboard.framework.redis.config; | 1 | package cn.iocoder.dashboard.framework.redis.config; |
| 2 | 2 | ||
| 3 | -import cn.iocoder.dashboard.framework.redis.core.listener.AbstractMessageListener; | 3 | +import cn.iocoder.dashboard.framework.redis.core.pubsub.AbstractChannelMessageListener; |
| 4 | import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer; | 4 | import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer; |
| 5 | import lombok.extern.slf4j.Slf4j; | 5 | import lombok.extern.slf4j.Slf4j; |
| 6 | import org.springframework.context.annotation.Bean; | 6 | import org.springframework.context.annotation.Bean; |
| @@ -35,7 +35,7 @@ public class RedisConfig { | @@ -35,7 +35,7 @@ public class RedisConfig { | ||
| 35 | 35 | ||
| 36 | @Bean | 36 | @Bean |
| 37 | public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory factory, | 37 | public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory factory, |
| 38 | - List<AbstractMessageListener<?>> listeners) { | 38 | + List<AbstractChannelMessageListener<?>> listeners) { |
| 39 | // 创建 RedisMessageListenerContainer 对象 | 39 | // 创建 RedisMessageListenerContainer 对象 |
| 40 | RedisMessageListenerContainer container = new RedisMessageListenerContainer(); | 40 | RedisMessageListenerContainer container = new RedisMessageListenerContainer(); |
| 41 | // 设置 RedisConnection 工厂。 | 41 | // 设置 RedisConnection 工厂。 |
src/main/java/cn/iocoder/dashboard/framework/redis/core/listener/AbstractMessageListener.java renamed to src/main/java/cn/iocoder/dashboard/framework/redis/core/pubsub/AbstractChannelMessageListener.java
| 1 | -package cn.iocoder.dashboard.framework.redis.core.listener; | 1 | +package cn.iocoder.dashboard.framework.redis.core.pubsub; |
| 2 | 2 | ||
| 3 | import cn.hutool.core.util.ArrayUtil; | 3 | import cn.hutool.core.util.ArrayUtil; |
| 4 | import cn.iocoder.dashboard.util.json.JSONUtils; | 4 | import cn.iocoder.dashboard.util.json.JSONUtils; |
| 5 | +import lombok.SneakyThrows; | ||
| 5 | import org.springframework.data.redis.connection.Message; | 6 | import org.springframework.data.redis.connection.Message; |
| 6 | import org.springframework.data.redis.connection.MessageListener; | 7 | import org.springframework.data.redis.connection.MessageListener; |
| 7 | import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl; | 8 | import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl; |
| @@ -15,15 +16,21 @@ import java.lang.reflect.Type; | @@ -15,15 +16,21 @@ import java.lang.reflect.Type; | ||
| 15 | * | 16 | * |
| 16 | * @author 芋道源码 | 17 | * @author 芋道源码 |
| 17 | */ | 18 | */ |
| 18 | -public abstract class AbstractMessageListener<T> implements MessageListener { | 19 | +public abstract class AbstractChannelMessageListener<T extends ChannelMessage> implements MessageListener { |
| 19 | 20 | ||
| 20 | /** | 21 | /** |
| 21 | * 消息类型 | 22 | * 消息类型 |
| 22 | */ | 23 | */ |
| 23 | private final Class<T> messageType; | 24 | private final Class<T> messageType; |
| 25 | + /** | ||
| 26 | + * Redis Channel | ||
| 27 | + */ | ||
| 28 | + private final String channel; | ||
| 24 | 29 | ||
| 25 | - protected AbstractMessageListener() { | 30 | + @SneakyThrows |
| 31 | + protected AbstractChannelMessageListener() { | ||
| 26 | this.messageType = getMessageClass(); | 32 | this.messageType = getMessageClass(); |
| 33 | + this.channel = messageType.newInstance().getChannel(); | ||
| 27 | } | 34 | } |
| 28 | 35 | ||
| 29 | /** | 36 | /** |
| @@ -31,7 +38,9 @@ public abstract class AbstractMessageListener<T> implements MessageListener { | @@ -31,7 +38,9 @@ public abstract class AbstractMessageListener<T> implements MessageListener { | ||
| 31 | * | 38 | * |
| 32 | * @return channel | 39 | * @return channel |
| 33 | */ | 40 | */ |
| 34 | - public abstract String getChannel(); | 41 | + public final String getChannel() { |
| 42 | + return channel; | ||
| 43 | + } | ||
| 35 | 44 | ||
| 36 | @Override | 45 | @Override |
| 37 | public final void onMessage(Message message, byte[] bytes) { | 46 | public final void onMessage(Message message, byte[] bytes) { |
| @@ -56,7 +65,7 @@ public abstract class AbstractMessageListener<T> implements MessageListener { | @@ -56,7 +65,7 @@ public abstract class AbstractMessageListener<T> implements MessageListener { | ||
| 56 | Class<?> targetClass = getClass(); | 65 | Class<?> targetClass = getClass(); |
| 57 | while (targetClass.getSuperclass() != null) { | 66 | while (targetClass.getSuperclass() != null) { |
| 58 | // 如果不是 AbstractMessageListener 父类,继续向上查找 | 67 | // 如果不是 AbstractMessageListener 父类,继续向上查找 |
| 59 | - if (targetClass.getSuperclass() != AbstractMessageListener.class) { | 68 | + if (targetClass.getSuperclass() != AbstractChannelMessageListener.class) { |
| 60 | targetClass = targetClass.getSuperclass(); | 69 | targetClass = targetClass.getSuperclass(); |
| 61 | continue; | 70 | continue; |
| 62 | } | 71 | } |
src/main/java/cn/iocoder/dashboard/framework/redis/core/pubsub/ChannelMessage.java
0 → 100644
| 1 | +package cn.iocoder.dashboard.framework.redis.core.pubsub; | ||
| 2 | + | ||
| 3 | +import com.alibaba.fastjson.annotation.JSONField; | ||
| 4 | + | ||
| 5 | +/** | ||
| 6 | + * Redis Channel Message 接口 | ||
| 7 | + */ | ||
| 8 | +public interface ChannelMessage { | ||
| 9 | + | ||
| 10 | + /** | ||
| 11 | + * 获得 Redis Channel | ||
| 12 | + * | ||
| 13 | + * @return Channel | ||
| 14 | + */ | ||
| 15 | + @JSONField(serialize = false) // 必须序列化 | ||
| 16 | + String getChannel(); | ||
| 17 | + | ||
| 18 | +} |
src/main/java/cn/iocoder/dashboard/framework/redis/core/util/RedisMessageUtils.java
0 → 100644
| 1 | +package cn.iocoder.dashboard.framework.redis.core.util; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.dashboard.framework.redis.core.pubsub.ChannelMessage; | ||
| 4 | +import cn.iocoder.dashboard.util.json.JSONUtils; | ||
| 5 | +import org.springframework.data.redis.core.RedisTemplate; | ||
| 6 | + | ||
| 7 | +/** | ||
| 8 | + * Redis 消息工具类 | ||
| 9 | + * | ||
| 10 | + * @author 芋道源码 | ||
| 11 | + */ | ||
| 12 | +public class RedisMessageUtils { | ||
| 13 | + | ||
| 14 | + /** | ||
| 15 | + * 发送 Redis 消息,基于 Redis pub/sub 实现 | ||
| 16 | + * | ||
| 17 | + * @param redisTemplate Redis 操作模板 | ||
| 18 | + * @param message 消息 | ||
| 19 | + */ | ||
| 20 | + public static <T extends ChannelMessage> void sendChannelMessage(RedisTemplate<?, ?> redisTemplate, T message) { | ||
| 21 | + redisTemplate.convertAndSend(message.getChannel(), JSONUtils.toJSONString(message)); | ||
| 22 | + } | ||
| 23 | + | ||
| 24 | +} |
src/main/java/cn/iocoder/dashboard/modules/system/controller/auth/SysAuthController.java
| @@ -63,7 +63,8 @@ public class SysAuthController { | @@ -63,7 +63,8 @@ public class SysAuthController { | ||
| 63 | // 获得角色列表 | 63 | // 获得角色列表 |
| 64 | List<SysRoleDO> roleList = roleService.listRolesFromCache(getLoginUserRoleIds()); | 64 | List<SysRoleDO> roleList = roleService.listRolesFromCache(getLoginUserRoleIds()); |
| 65 | // 获得菜单列表 | 65 | // 获得菜单列表 |
| 66 | - List<SysMenuDO> menuList = permissionService.listRoleMenusFromCache(getLoginUserRoleIds(), | 66 | + List<SysMenuDO> menuList = permissionService.listRoleMenusFromCache( |
| 67 | + getLoginUserRoleIds(), // 注意,基于登陆的角色,因为后续的权限判断也是基于它 | ||
| 67 | SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType(), MenuTypeEnum.BUTTON.getType()), | 68 | SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType(), MenuTypeEnum.BUTTON.getType()), |
| 68 | SetUtils.asSet(CommonStatusEnum.ENABLE.getStatus())); | 69 | SetUtils.asSet(CommonStatusEnum.ENABLE.getStatus())); |
| 69 | // 拼接结果返回 | 70 | // 拼接结果返回 |
| @@ -74,7 +75,8 @@ public class SysAuthController { | @@ -74,7 +75,8 @@ public class SysAuthController { | ||
| 74 | @GetMapping("list-menus") | 75 | @GetMapping("list-menus") |
| 75 | public CommonResult<List<SysAuthMenuRespVO>> listMenus() { | 76 | public CommonResult<List<SysAuthMenuRespVO>> listMenus() { |
| 76 | // 获得用户拥有的菜单列表 | 77 | // 获得用户拥有的菜单列表 |
| 77 | - List<SysMenuDO> menuList = permissionService.listRoleMenusFromCache(getLoginUserRoleIds(), | 78 | + List<SysMenuDO> menuList = permissionService.listRoleMenusFromCache( |
| 79 | + getLoginUserRoleIds(), // 注意,基于登陆的角色,因为后续的权限判断也是基于它 | ||
| 78 | SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType()), // 只要目录和菜单类型 | 80 | SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType()), // 只要目录和菜单类型 |
| 79 | SetUtils.asSet(CommonStatusEnum.ENABLE.getStatus())); // 只要开启的 | 81 | SetUtils.asSet(CommonStatusEnum.ENABLE.getStatus())); // 只要开启的 |
| 80 | // 转换成 Tree 结构返回 | 82 | // 转换成 Tree 结构返回 |
src/main/java/cn/iocoder/dashboard/modules/system/dal/mysql/dao/dict/SysDictDataMapper.java
| @@ -10,6 +10,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper; | @@ -10,6 +10,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper; | ||
| 10 | import com.baomidou.mybatisplus.core.metadata.IPage; | 10 | import com.baomidou.mybatisplus.core.metadata.IPage; |
| 11 | import org.apache.ibatis.annotations.Mapper; | 11 | import org.apache.ibatis.annotations.Mapper; |
| 12 | 12 | ||
| 13 | +import java.util.Date; | ||
| 13 | import java.util.List; | 14 | import java.util.List; |
| 14 | 15 | ||
| 15 | import static com.baomidou.mybatisplus.core.metadata.OrderItem.asc; | 16 | import static com.baomidou.mybatisplus.core.metadata.OrderItem.asc; |
| @@ -42,4 +43,10 @@ public interface SysDictDataMapper extends BaseMapper<SysDictDataDO> { | @@ -42,4 +43,10 @@ public interface SysDictDataMapper extends BaseMapper<SysDictDataDO> { | ||
| 42 | .likeIfPresent("dict_type", reqVO.getDictType()) | 43 | .likeIfPresent("dict_type", reqVO.getDictType()) |
| 43 | .eqIfPresent("status", reqVO.getStatus())); | 44 | .eqIfPresent("status", reqVO.getStatus())); |
| 44 | } | 45 | } |
| 46 | + | ||
| 47 | + default boolean selectExistsByUpdateTimeAfter(Date maxUpdateTime) { | ||
| 48 | + return selectOne(new QueryWrapper<SysDictDataDO>().select("id") | ||
| 49 | + .gt("update_time", maxUpdateTime).last("LIMIT 1")) != null; | ||
| 50 | + } | ||
| 51 | + | ||
| 45 | } | 52 | } |
src/main/java/cn/iocoder/dashboard/modules/system/mq/consumer/dict/SysDictDataRefreshConsumer.java
0 → 100644
| 1 | +package cn.iocoder.dashboard.modules.system.mq.consumer.dict; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.dashboard.framework.redis.core.pubsub.AbstractChannelMessageListener; | ||
| 4 | +import cn.iocoder.dashboard.modules.system.mq.message.dict.SysDictDataRefreshMessage; | ||
| 5 | +import cn.iocoder.dashboard.modules.system.service.dict.SysDictDataService; | ||
| 6 | +import lombok.extern.slf4j.Slf4j; | ||
| 7 | +import org.springframework.stereotype.Component; | ||
| 8 | + | ||
| 9 | +import javax.annotation.Resource; | ||
| 10 | + | ||
| 11 | +/** | ||
| 12 | + * 针对 {@link SysDictDataRefreshMessage} 的消费者 | ||
| 13 | + * | ||
| 14 | + * @author 芋道源码 | ||
| 15 | + */ | ||
| 16 | +@Component | ||
| 17 | +@Slf4j | ||
| 18 | +public class SysDictDataRefreshConsumer extends AbstractChannelMessageListener<SysDictDataRefreshMessage> { | ||
| 19 | + | ||
| 20 | + @Resource | ||
| 21 | + private SysDictDataService dictDataService; | ||
| 22 | + | ||
| 23 | + @Override | ||
| 24 | + public void onMessage(SysDictDataRefreshMessage message) { | ||
| 25 | + log.info("[onMessage][收到 DictData 刷新消息]"); | ||
| 26 | + dictDataService.initLocalCache(); | ||
| 27 | + } | ||
| 28 | + | ||
| 29 | +} |
src/main/java/cn/iocoder/dashboard/modules/system/mq/consumer/SysMenuRefreshConsumer.java renamed to src/main/java/cn/iocoder/dashboard/modules/system/mq/consumer/permission/SysMenuRefreshConsumer.java
| 1 | -package cn.iocoder.dashboard.modules.system.mq.consumer; | 1 | +package cn.iocoder.dashboard.modules.system.mq.consumer.permission; |
| 2 | 2 | ||
| 3 | -import cn.iocoder.dashboard.framework.redis.core.listener.AbstractMessageListener; | 3 | +import cn.iocoder.dashboard.framework.redis.core.pubsub.AbstractChannelMessageListener; |
| 4 | import cn.iocoder.dashboard.modules.system.mq.message.permission.SysMenuRefreshMessage; | 4 | import cn.iocoder.dashboard.modules.system.mq.message.permission.SysMenuRefreshMessage; |
| 5 | import cn.iocoder.dashboard.modules.system.service.permission.SysMenuService; | 5 | import cn.iocoder.dashboard.modules.system.service.permission.SysMenuService; |
| 6 | import lombok.extern.slf4j.Slf4j; | 6 | import lombok.extern.slf4j.Slf4j; |
| @@ -8,9 +8,14 @@ import org.springframework.stereotype.Component; | @@ -8,9 +8,14 @@ import org.springframework.stereotype.Component; | ||
| 8 | 8 | ||
| 9 | import javax.annotation.Resource; | 9 | import javax.annotation.Resource; |
| 10 | 10 | ||
| 11 | +/** | ||
| 12 | + * 针对 {@link SysMenuRefreshMessage} 的消费者 | ||
| 13 | + * | ||
| 14 | + * @author 芋道源码 | ||
| 15 | + */ | ||
| 11 | @Component | 16 | @Component |
| 12 | @Slf4j | 17 | @Slf4j |
| 13 | -public class SysMenuRefreshConsumer extends AbstractMessageListener<SysMenuRefreshMessage> { | 18 | +public class SysMenuRefreshConsumer extends AbstractChannelMessageListener<SysMenuRefreshMessage> { |
| 14 | 19 | ||
| 15 | @Resource | 20 | @Resource |
| 16 | private SysMenuService menuService; | 21 | private SysMenuService menuService; |
| @@ -18,12 +23,7 @@ public class SysMenuRefreshConsumer extends AbstractMessageListener<SysMenuRefre | @@ -18,12 +23,7 @@ public class SysMenuRefreshConsumer extends AbstractMessageListener<SysMenuRefre | ||
| 18 | @Override | 23 | @Override |
| 19 | public void onMessage(SysMenuRefreshMessage message) { | 24 | public void onMessage(SysMenuRefreshMessage message) { |
| 20 | log.info("[onMessage][收到 Menu 刷新消息]"); | 25 | log.info("[onMessage][收到 Menu 刷新消息]"); |
| 21 | - menuService.init(); | ||
| 22 | - } | ||
| 23 | - | ||
| 24 | - @Override | ||
| 25 | - public String getChannel() { | ||
| 26 | - return SysMenuRefreshMessage.TOPIC; | 26 | + menuService.initLocalCache(); |
| 27 | } | 27 | } |
| 28 | 28 | ||
| 29 | } | 29 | } |
src/main/java/cn/iocoder/dashboard/modules/system/mq/message/dict/SysDictDataRefreshMessage.java
0 → 100644
| 1 | +package cn.iocoder.dashboard.modules.system.mq.message.dict; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.dashboard.framework.redis.core.pubsub.ChannelMessage; | ||
| 4 | +import lombok.Data; | ||
| 5 | + | ||
| 6 | +/** | ||
| 7 | + * 字典数据数据刷新 Message | ||
| 8 | + */ | ||
| 9 | +@Data | ||
| 10 | +public class SysDictDataRefreshMessage implements ChannelMessage { | ||
| 11 | + | ||
| 12 | + @Override | ||
| 13 | + public String getChannel() { | ||
| 14 | + return "system.dict-data.refresh"; | ||
| 15 | + } | ||
| 16 | + | ||
| 17 | +} |
src/main/java/cn/iocoder/dashboard/modules/system/mq/message/package-info.java deleted
| 1 | -package cn.iocoder.dashboard.modules.system.mq.message; |
src/main/java/cn/iocoder/dashboard/modules/system/mq/message/permission/SysMenuRefreshMessage.java
| 1 | package cn.iocoder.dashboard.modules.system.mq.message.permission; | 1 | package cn.iocoder.dashboard.modules.system.mq.message.permission; |
| 2 | 2 | ||
| 3 | +import cn.iocoder.dashboard.framework.redis.core.pubsub.ChannelMessage; | ||
| 3 | import lombok.Data; | 4 | import lombok.Data; |
| 4 | 5 | ||
| 5 | /** | 6 | /** |
| 6 | * 菜单数据刷新 Message | 7 | * 菜单数据刷新 Message |
| 7 | */ | 8 | */ |
| 8 | @Data | 9 | @Data |
| 9 | -public class SysMenuRefreshMessage { | 10 | +public class SysMenuRefreshMessage implements ChannelMessage { |
| 10 | 11 | ||
| 11 | - public static final String TOPIC = "system.menu.refresh"; | 12 | + @Override |
| 13 | + public String getChannel() { | ||
| 14 | + return "system.menu.refresh"; | ||
| 15 | + } | ||
| 12 | 16 | ||
| 13 | } | 17 | } |
src/main/java/cn/iocoder/dashboard/modules/system/mq/producer/dict/SysDictDataProducer.java
0 → 100644
| 1 | +package cn.iocoder.dashboard.modules.system.mq.producer.dict; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.dashboard.framework.redis.core.util.RedisMessageUtils; | ||
| 4 | +import cn.iocoder.dashboard.modules.system.mq.message.dict.SysDictDataRefreshMessage; | ||
| 5 | +import org.springframework.data.redis.core.StringRedisTemplate; | ||
| 6 | +import org.springframework.stereotype.Component; | ||
| 7 | + | ||
| 8 | +import javax.annotation.Resource; | ||
| 9 | + | ||
| 10 | +/** | ||
| 11 | + * DictData 字典数据相关消息的 Producer | ||
| 12 | + */ | ||
| 13 | +@Component | ||
| 14 | +public class SysDictDataProducer { | ||
| 15 | + | ||
| 16 | + @Resource | ||
| 17 | + private StringRedisTemplate stringRedisTemplate; | ||
| 18 | + | ||
| 19 | + /** | ||
| 20 | + * 发送 {@link SysDictDataRefreshMessage} 消息 | ||
| 21 | + */ | ||
| 22 | + public void sendMenuRefreshMessage() { | ||
| 23 | + SysDictDataRefreshMessage message = new SysDictDataRefreshMessage(); | ||
| 24 | + RedisMessageUtils.sendChannelMessage(stringRedisTemplate, message); | ||
| 25 | + } | ||
| 26 | + | ||
| 27 | +} |
src/main/java/cn/iocoder/dashboard/modules/system/mq/producer/package-info.java deleted
| 1 | -package cn.iocoder.dashboard.modules.system.mq.producer; |
src/main/java/cn/iocoder/dashboard/modules/system/mq/producer/permission/SysMenuProducer.java
| 1 | package cn.iocoder.dashboard.modules.system.mq.producer.permission; | 1 | package cn.iocoder.dashboard.modules.system.mq.producer.permission; |
| 2 | 2 | ||
| 3 | +import cn.iocoder.dashboard.framework.redis.core.util.RedisMessageUtils; | ||
| 3 | import cn.iocoder.dashboard.modules.system.mq.message.permission.SysMenuRefreshMessage; | 4 | import cn.iocoder.dashboard.modules.system.mq.message.permission.SysMenuRefreshMessage; |
| 4 | -import cn.iocoder.dashboard.util.json.JSONUtils; | ||
| 5 | import org.springframework.data.redis.core.StringRedisTemplate; | 5 | import org.springframework.data.redis.core.StringRedisTemplate; |
| 6 | import org.springframework.stereotype.Component; | 6 | import org.springframework.stereotype.Component; |
| 7 | 7 | ||
| @@ -21,7 +21,7 @@ public class SysMenuProducer { | @@ -21,7 +21,7 @@ public class SysMenuProducer { | ||
| 21 | */ | 21 | */ |
| 22 | public void sendMenuRefreshMessage() { | 22 | public void sendMenuRefreshMessage() { |
| 23 | SysMenuRefreshMessage message = new SysMenuRefreshMessage(); | 23 | SysMenuRefreshMessage message = new SysMenuRefreshMessage(); |
| 24 | - stringRedisTemplate.convertAndSend(SysMenuRefreshMessage.TOPIC, JSONUtils.toJSONString(message)); | 24 | + RedisMessageUtils.sendChannelMessage(stringRedisTemplate, message); |
| 25 | } | 25 | } |
| 26 | 26 | ||
| 27 | } | 27 | } |
src/main/java/cn/iocoder/dashboard/modules/system/service/dict/SysDictDataService.java
| @@ -18,9 +18,9 @@ import java.util.List; | @@ -18,9 +18,9 @@ import java.util.List; | ||
| 18 | public interface SysDictDataService extends DictDataFrameworkService { | 18 | public interface SysDictDataService extends DictDataFrameworkService { |
| 19 | 19 | ||
| 20 | /** | 20 | /** |
| 21 | - * 初始化,主要是初始化缓存 | 21 | + * 初始化字典数据的本地缓存 |
| 22 | */ | 22 | */ |
| 23 | - void init(); | 23 | + void initLocalCache(); |
| 24 | 24 | ||
| 25 | /** | 25 | /** |
| 26 | * 获得字典数据列表 | 26 | * 获得字典数据列表 |
src/main/java/cn/iocoder/dashboard/modules/system/service/dict/impl/SysDictDataServiceImpl.java
| 1 | package cn.iocoder.dashboard.modules.system.service.dict.impl; | 1 | package cn.iocoder.dashboard.modules.system.service.dict.impl; |
| 2 | 2 | ||
| 3 | +import cn.hutool.core.collection.CollUtil; | ||
| 3 | import cn.iocoder.dashboard.common.enums.CommonStatusEnum; | 4 | import cn.iocoder.dashboard.common.enums.CommonStatusEnum; |
| 4 | import cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil; | 5 | import cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil; |
| 5 | import cn.iocoder.dashboard.common.pojo.PageResult; | 6 | import cn.iocoder.dashboard.common.pojo.PageResult; |
| 7 | +import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO; | ||
| 6 | import cn.iocoder.dashboard.modules.system.controller.dict.vo.data.SysDictDataCreateReqVO; | 8 | import cn.iocoder.dashboard.modules.system.controller.dict.vo.data.SysDictDataCreateReqVO; |
| 7 | import cn.iocoder.dashboard.modules.system.controller.dict.vo.data.SysDictDataExportReqVO; | 9 | import cn.iocoder.dashboard.modules.system.controller.dict.vo.data.SysDictDataExportReqVO; |
| 8 | import cn.iocoder.dashboard.modules.system.controller.dict.vo.data.SysDictDataPageReqVO; | 10 | import cn.iocoder.dashboard.modules.system.controller.dict.vo.data.SysDictDataPageReqVO; |
| @@ -11,15 +13,19 @@ import cn.iocoder.dashboard.modules.system.convert.dict.SysDictDataConvert; | @@ -11,15 +13,19 @@ import cn.iocoder.dashboard.modules.system.convert.dict.SysDictDataConvert; | ||
| 11 | import cn.iocoder.dashboard.modules.system.dal.mysql.dao.dict.SysDictDataMapper; | 13 | import cn.iocoder.dashboard.modules.system.dal.mysql.dao.dict.SysDictDataMapper; |
| 12 | import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.dict.SysDictDataDO; | 14 | import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.dict.SysDictDataDO; |
| 13 | import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.dict.SysDictTypeDO; | 15 | import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.dict.SysDictTypeDO; |
| 16 | +import cn.iocoder.dashboard.modules.system.mq.producer.dict.SysDictDataProducer; | ||
| 14 | import cn.iocoder.dashboard.modules.system.service.dict.SysDictDataService; | 17 | import cn.iocoder.dashboard.modules.system.service.dict.SysDictDataService; |
| 15 | import cn.iocoder.dashboard.modules.system.service.dict.SysDictTypeService; | 18 | import cn.iocoder.dashboard.modules.system.service.dict.SysDictTypeService; |
| 16 | import com.google.common.collect.ImmutableTable; | 19 | import com.google.common.collect.ImmutableTable; |
| 20 | +import lombok.extern.slf4j.Slf4j; | ||
| 21 | +import org.springframework.scheduling.annotation.Scheduled; | ||
| 17 | import org.springframework.stereotype.Service; | 22 | import org.springframework.stereotype.Service; |
| 18 | 23 | ||
| 19 | import javax.annotation.PostConstruct; | 24 | import javax.annotation.PostConstruct; |
| 20 | import javax.annotation.Resource; | 25 | import javax.annotation.Resource; |
| 21 | import java.util.ArrayList; | 26 | import java.util.ArrayList; |
| 22 | import java.util.Comparator; | 27 | import java.util.Comparator; |
| 28 | +import java.util.Date; | ||
| 23 | import java.util.List; | 29 | import java.util.List; |
| 24 | 30 | ||
| 25 | import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*; | 31 | import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*; |
| @@ -30,13 +36,23 @@ import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*; | @@ -30,13 +36,23 @@ import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*; | ||
| 30 | * @author ruoyi | 36 | * @author ruoyi |
| 31 | */ | 37 | */ |
| 32 | @Service | 38 | @Service |
| 39 | +@Slf4j | ||
| 33 | public class SysDictDataServiceImpl implements SysDictDataService { | 40 | public class SysDictDataServiceImpl implements SysDictDataService { |
| 34 | 41 | ||
| 42 | + /** | ||
| 43 | + * 排序 dictType > sort | ||
| 44 | + */ | ||
| 35 | private static final Comparator<SysDictDataDO> COMPARATOR_TYPE_AND_SORT = Comparator | 45 | private static final Comparator<SysDictDataDO> COMPARATOR_TYPE_AND_SORT = Comparator |
| 36 | .comparing(SysDictDataDO::getDictType) | 46 | .comparing(SysDictDataDO::getDictType) |
| 37 | .thenComparingInt(SysDictDataDO::getSort); | 47 | .thenComparingInt(SysDictDataDO::getSort); |
| 38 | 48 | ||
| 39 | /** | 49 | /** |
| 50 | + * 定时执行 {@link #schedulePeriodicRefresh()} 的周期 | ||
| 51 | + * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高 | ||
| 52 | + */ | ||
| 53 | + private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L; | ||
| 54 | + | ||
| 55 | + /** | ||
| 40 | * 字典数据缓存,第二个 key 使用 label | 56 | * 字典数据缓存,第二个 key 使用 label |
| 41 | * | 57 | * |
| 42 | * key1:字典类型 dictType | 58 | * key1:字典类型 dictType |
| @@ -50,6 +66,10 @@ public class SysDictDataServiceImpl implements SysDictDataService { | @@ -50,6 +66,10 @@ public class SysDictDataServiceImpl implements SysDictDataService { | ||
| 50 | * key2:字典值 value | 66 | * key2:字典值 value |
| 51 | */ | 67 | */ |
| 52 | private ImmutableTable<String, String, SysDictDataDO> valueDictDataCache; | 68 | private ImmutableTable<String, String, SysDictDataDO> valueDictDataCache; |
| 69 | + /** | ||
| 70 | + * 缓存字典数据的最大更新时间,用于后续的增量轮询,判断是否有更新 | ||
| 71 | + */ | ||
| 72 | + private volatile Date maxUpdateTime; | ||
| 53 | 73 | ||
| 54 | @Resource | 74 | @Resource |
| 55 | private SysDictTypeService dictTypeService; | 75 | private SysDictTypeService dictTypeService; |
| @@ -57,20 +77,56 @@ public class SysDictDataServiceImpl implements SysDictDataService { | @@ -57,20 +77,56 @@ public class SysDictDataServiceImpl implements SysDictDataService { | ||
| 57 | @Resource | 77 | @Resource |
| 58 | private SysDictDataMapper dictDataMapper; | 78 | private SysDictDataMapper dictDataMapper; |
| 59 | 79 | ||
| 80 | + @Resource | ||
| 81 | + private SysDictDataProducer dictDataProducer; | ||
| 82 | + | ||
| 60 | @Override | 83 | @Override |
| 61 | @PostConstruct | 84 | @PostConstruct |
| 62 | - public void init() { | ||
| 63 | - // 获得字典数据 | ||
| 64 | - List<SysDictDataDO> list = this.listDictDatas(); | 85 | + public void initLocalCache() { |
| 86 | + // 获取字典数据列表,如果有更新 | ||
| 87 | + List<SysDictDataDO> dataList = this.loadDictDataIfUpdate(maxUpdateTime); | ||
| 88 | + if (CollUtil.isEmpty(dataList)) { | ||
| 89 | + return; | ||
| 90 | + } | ||
| 91 | + | ||
| 65 | // 构建缓存 | 92 | // 构建缓存 |
| 66 | ImmutableTable.Builder<String, String, SysDictDataDO> labelDictDataBuilder = ImmutableTable.builder(); | 93 | ImmutableTable.Builder<String, String, SysDictDataDO> labelDictDataBuilder = ImmutableTable.builder(); |
| 67 | ImmutableTable.Builder<String, String, SysDictDataDO> valueDictDataBuilder = ImmutableTable.builder(); | 94 | ImmutableTable.Builder<String, String, SysDictDataDO> valueDictDataBuilder = ImmutableTable.builder(); |
| 68 | - list.forEach(dictData -> { | 95 | + dataList.forEach(dictData -> { |
| 69 | labelDictDataBuilder.put(dictData.getDictType(), dictData.getLabel(), dictData); | 96 | labelDictDataBuilder.put(dictData.getDictType(), dictData.getLabel(), dictData); |
| 70 | valueDictDataBuilder.put(dictData.getDictType(), dictData.getValue(), dictData); | 97 | valueDictDataBuilder.put(dictData.getDictType(), dictData.getValue(), dictData); |
| 71 | }); | 98 | }); |
| 72 | labelDictDataCache = labelDictDataBuilder.build(); | 99 | labelDictDataCache = labelDictDataBuilder.build(); |
| 73 | valueDictDataCache = valueDictDataBuilder.build(); | 100 | valueDictDataCache = valueDictDataBuilder.build(); |
| 101 | + assert dataList.size() > 0; // 断言,避免告警 | ||
| 102 | + maxUpdateTime = dataList.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime(); | ||
| 103 | + log.info("[init][缓存字典数据,数量为:{}]", dataList.size()); | ||
| 104 | + } | ||
| 105 | + | ||
| 106 | + @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD) | ||
| 107 | + public void schedulePeriodicRefresh() { | ||
| 108 | + initLocalCache(); | ||
| 109 | + } | ||
| 110 | + | ||
| 111 | + /** | ||
| 112 | + * 如果字典数据发生变化,从数据库中获取最新的全量字典数据。 | ||
| 113 | + * 如果未发生变化,则返回空 | ||
| 114 | + * | ||
| 115 | + * @param maxUpdateTime 当前字典数据的最大更新时间 | ||
| 116 | + * @return 字典数据列表 | ||
| 117 | + */ | ||
| 118 | + private List<SysDictDataDO> loadDictDataIfUpdate(Date maxUpdateTime) { | ||
| 119 | + // 第一步,判断是否要更新。 | ||
| 120 | + if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据 | ||
| 121 | + log.info("[loadDictDataIfUpdate][首次加载全量字典数据]"); | ||
| 122 | + } else { // 判断数据库中是否有更新的字典数据 | ||
| 123 | + if (!dictDataMapper.selectExistsByUpdateTimeAfter(maxUpdateTime)) { | ||
| 124 | + return null; | ||
| 125 | + } | ||
| 126 | + log.info("[loadDictDataIfUpdate][增量加载全量字典数据]"); | ||
| 127 | + } | ||
| 128 | + // 第二步,如果有更新,则从数据库加载所有字典数据 | ||
| 129 | + return dictDataMapper.selectList(); | ||
| 74 | } | 130 | } |
| 75 | 131 | ||
| 76 | @Override | 132 | @Override |
| @@ -104,6 +160,8 @@ public class SysDictDataServiceImpl implements SysDictDataService { | @@ -104,6 +160,8 @@ public class SysDictDataServiceImpl implements SysDictDataService { | ||
| 104 | // 插入字典类型 | 160 | // 插入字典类型 |
| 105 | SysDictDataDO dictData = SysDictDataConvert.INSTANCE.convert(reqVO); | 161 | SysDictDataDO dictData = SysDictDataConvert.INSTANCE.convert(reqVO); |
| 106 | dictDataMapper.insert(dictData); | 162 | dictDataMapper.insert(dictData); |
| 163 | + // 发送消息 | ||
| 164 | + dictDataProducer.sendMenuRefreshMessage(); | ||
| 107 | return dictData.getId(); | 165 | return dictData.getId(); |
| 108 | } | 166 | } |
| 109 | 167 | ||
| @@ -114,6 +172,8 @@ public class SysDictDataServiceImpl implements SysDictDataService { | @@ -114,6 +172,8 @@ public class SysDictDataServiceImpl implements SysDictDataService { | ||
| 114 | // 更新字典类型 | 172 | // 更新字典类型 |
| 115 | SysDictDataDO updateObj = SysDictDataConvert.INSTANCE.convert(reqVO); | 173 | SysDictDataDO updateObj = SysDictDataConvert.INSTANCE.convert(reqVO); |
| 116 | dictDataMapper.updateById(updateObj); | 174 | dictDataMapper.updateById(updateObj); |
| 175 | + // 发送消息 | ||
| 176 | + dictDataProducer.sendMenuRefreshMessage(); | ||
| 117 | } | 177 | } |
| 118 | 178 | ||
| 119 | @Override | 179 | @Override |
| @@ -122,6 +182,8 @@ public class SysDictDataServiceImpl implements SysDictDataService { | @@ -122,6 +182,8 @@ public class SysDictDataServiceImpl implements SysDictDataService { | ||
| 122 | this.checkDictDataExists(id); | 182 | this.checkDictDataExists(id); |
| 123 | // 删除字典数据 | 183 | // 删除字典数据 |
| 124 | dictDataMapper.deleteById(id); | 184 | dictDataMapper.deleteById(id); |
| 185 | + // 发送消息 | ||
| 186 | + dictDataProducer.sendMenuRefreshMessage(); | ||
| 125 | } | 187 | } |
| 126 | 188 | ||
| 127 | @Override | 189 | @Override |
src/main/java/cn/iocoder/dashboard/modules/system/service/permission/SysMenuService.java
| @@ -16,9 +16,9 @@ import java.util.List; | @@ -16,9 +16,9 @@ import java.util.List; | ||
| 16 | public interface SysMenuService { | 16 | public interface SysMenuService { |
| 17 | 17 | ||
| 18 | /** | 18 | /** |
| 19 | - * 初始化菜单 | 19 | + * 初始化菜单的本地缓存 |
| 20 | */ | 20 | */ |
| 21 | - void init(); | 21 | + void initLocalCache(); |
| 22 | 22 | ||
| 23 | /** | 23 | /** |
| 24 | * 获得所有菜单列表 | 24 | * 获得所有菜单列表 |
src/main/java/cn/iocoder/dashboard/modules/system/service/permission/impl/SysMenuServiceImpl.java
| @@ -77,8 +77,8 @@ public class SysMenuServiceImpl implements SysMenuService { | @@ -77,8 +77,8 @@ public class SysMenuServiceImpl implements SysMenuService { | ||
| 77 | */ | 77 | */ |
| 78 | @Override | 78 | @Override |
| 79 | @PostConstruct | 79 | @PostConstruct |
| 80 | - public synchronized void init() { | ||
| 81 | - // 获取 | 80 | + public synchronized void initLocalCache() { |
| 81 | + // 获取菜单列表,如果有更新 | ||
| 82 | List<SysMenuDO> menuList = this.loadMenuIfUpdate(maxUpdateTime); | 82 | List<SysMenuDO> menuList = this.loadMenuIfUpdate(maxUpdateTime); |
| 83 | if (CollUtil.isEmpty(menuList)) { | 83 | if (CollUtil.isEmpty(menuList)) { |
| 84 | return; | 84 | return; |
| @@ -100,7 +100,7 @@ public class SysMenuServiceImpl implements SysMenuService { | @@ -100,7 +100,7 @@ public class SysMenuServiceImpl implements SysMenuService { | ||
| 100 | 100 | ||
| 101 | @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD) | 101 | @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD) |
| 102 | public void schedulePeriodicRefresh() { | 102 | public void schedulePeriodicRefresh() { |
| 103 | - init(); | 103 | + initLocalCache(); |
| 104 | } | 104 | } |
| 105 | 105 | ||
| 106 | /** | 106 | /** |