diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/config/DifyConfig.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/config/DifyConfig.java index 74b4daa4..41f980f4 100644 --- a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/config/DifyConfig.java +++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/config/DifyConfig.java @@ -4,7 +4,6 @@ import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.apache.dubbo.config.annotation.DubboReference; -import org.apache.dubbo.config.annotation.DubboService; import org.springframework.context.annotation.Configuration; import org.xyzh.api.system.service.SysConfigService; @@ -159,6 +158,15 @@ public class DifyConfig { return apiBaseUrl != null && !apiBaseUrl.trim().isEmpty(); } + /** + * 刷新配置(从数据库重新加载) + * 由Redis事件监听器调用 + */ + public void refresh() { + log.info("收到配置刷新请求,重新加载Dify配置..."); + init(); + } + /** * 获取完整的API URL */ diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/config/RedisSubscriberConfig.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/config/RedisSubscriberConfig.java new file mode 100644 index 00000000..2e12b7c8 --- /dev/null +++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/config/RedisSubscriberConfig.java @@ -0,0 +1,34 @@ +package org.xyzh.ai.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.listener.PatternTopic; +import org.springframework.data.redis.listener.RedisMessageListenerContainer; +import org.xyzh.ai.listener.DifyConfigListener; + +/** + * AI模块Redis订阅配置 + * + * @author cascade + * @since 2026-01-01 + */ +@Configuration +public class RedisSubscriberConfig { + + @Autowired + private DifyConfigListener difyConfigListener; + + @Bean + public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory connectionFactory) { + RedisMessageListenerContainer container = new RedisMessageListenerContainer(); + container.setConnectionFactory(connectionFactory); + + // 订阅Dify配置变更频道 + container.addMessageListener(difyConfigListener, + new PatternTopic(difyConfigListener.getChannelPattern())); + + return container; + } +} diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/listener/DifyConfigListener.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/listener/DifyConfigListener.java new file mode 100644 index 00000000..585bb0cd --- /dev/null +++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/listener/DifyConfigListener.java @@ -0,0 +1,31 @@ +package org.xyzh.ai.listener; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.xyzh.api.system.constance.SysConfigRedisPrefix; +import org.xyzh.ai.config.DifyConfig; +import org.xyzh.common.redis.listener.AbstractSysConfigListener; + +/** + * Dify配置变更监听器 + * 监听sys:config:dify频道,接收到事件后延时2秒刷新配置 + * + * @author cascade + * @since 2026-01-01 + */ +@Component +public class DifyConfigListener extends AbstractSysConfigListener { + + @Autowired + private DifyConfig difyConfig; + + @Override + protected void doRefresh(String channel, String body) { + difyConfig.refresh(); + } + + @Override + public String getChannelPattern() { + return SysConfigRedisPrefix.SYS_CONFIG_DIFY; + } +} diff --git a/urbanLifelineServ/apis/api-system/src/main/java/org/xyzh/api/system/constance/SysConfigRedisPrefix.java b/urbanLifelineServ/apis/api-system/src/main/java/org/xyzh/api/system/constance/SysConfigRedisPrefix.java index 39c014b1..f3f5990f 100644 --- a/urbanLifelineServ/apis/api-system/src/main/java/org/xyzh/api/system/constance/SysConfigRedisPrefix.java +++ b/urbanLifelineServ/apis/api-system/src/main/java/org/xyzh/api/system/constance/SysConfigRedisPrefix.java @@ -1,8 +1,81 @@ package org.xyzh.api.system.constance; +import java.util.HashMap; +import java.util.Map; + /** * 通过redis事件,实现数据库更新配置,更新其他服务的bean数据 + * 按配置分组定义Redis前缀 + * + * 注意:其他服务接收到事件后需延时2秒再查询数据库,等待发布方事务提交 + * + * @see org.xyzh.common.redis.listener.AbstractSysConfigListener */ public class SysConfigRedisPrefix { - public static final String SYS_CONFIG_DIFY="sys:config:dify"; + + /** 配置变更频道前缀(用于订阅所有配置变更) */ + public static final String SYS_CONFIG_PREFIX = "sys:config:"; + + /** 站点与品牌配置 */ + public static final String SYS_CONFIG_SITE = "sys:config:site"; + + /** 国际化与时区配置 */ + public static final String SYS_CONFIG_I18N = "sys:config:i18n"; + + /** 安全与认证配置 */ + public static final String SYS_CONFIG_SECURITY = "sys:config:security"; + + /** 存储与上传配置(含MinIO、文件管理) */ + public static final String SYS_CONFIG_STORAGE = "sys:config:storage"; + + /** 通知配置(邮件/短信) */ + public static final String SYS_CONFIG_NOTIFY = "sys:config:notify"; + + /** Dify AI配置 */ + public static final String SYS_CONFIG_DIFY = "sys:config:dify"; + + /** 日志与审计配置 */ + public static final String SYS_CONFIG_LOG = "sys:config:log"; + + /** 平台特性配置 */ + public static final String SYS_CONFIG_PLATFORM = "sys:config:platform"; + + /** 微信客服配置 */ + public static final String SYS_CONFIG_WECHAT = "sys:config:wechat"; + + /** group到channel的映射 */ + private static final Map GROUP_CHANNEL_MAP = new HashMap<>(); + + static { + GROUP_CHANNEL_MAP.put("site", SYS_CONFIG_SITE); + GROUP_CHANNEL_MAP.put("i18n", SYS_CONFIG_I18N); + GROUP_CHANNEL_MAP.put("security", SYS_CONFIG_SECURITY); + GROUP_CHANNEL_MAP.put("storage", SYS_CONFIG_STORAGE); + GROUP_CHANNEL_MAP.put("notify", SYS_CONFIG_NOTIFY); + GROUP_CHANNEL_MAP.put("dify", SYS_CONFIG_DIFY); + GROUP_CHANNEL_MAP.put("log", SYS_CONFIG_LOG); + GROUP_CHANNEL_MAP.put("platform", SYS_CONFIG_PLATFORM); + GROUP_CHANNEL_MAP.put("wechat", SYS_CONFIG_WECHAT); + } + + /** + * 根据group获取对应的Redis channel + * @param group 配置分组(对应数据库中的group字段) + * @return channel名称,未匹配则返回 sys:config:{group} + */ + public static String getChannelByGroup(String group) { + if (group == null || group.isEmpty()) { + return null; + } + return GROUP_CHANNEL_MAP.getOrDefault(group, SYS_CONFIG_PREFIX + group); + } + + /** + * 判断group是否有效 + * @param group 配置分组 + * @return 是否为已知的分组 + */ + public static boolean isValidGroup(String group) { + return group != null && GROUP_CHANNEL_MAP.containsKey(group); + } } diff --git a/urbanLifelineServ/common/common-redis/src/main/java/org/xyzh/common/redis/listener/AbstractSysConfigListener.java b/urbanLifelineServ/common/common-redis/src/main/java/org/xyzh/common/redis/listener/AbstractSysConfigListener.java new file mode 100644 index 00000000..46b5f380 --- /dev/null +++ b/urbanLifelineServ/common/common-redis/src/main/java/org/xyzh/common/redis/listener/AbstractSysConfigListener.java @@ -0,0 +1,66 @@ +package org.xyzh.common.redis.listener; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.redis.connection.Message; +import org.springframework.data.redis.connection.MessageListener; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * 系统配置变更监听器抽象基类 + * 通过Redis Pub/Sub接收配置变更事件,延时2秒后执行刷新(等待事务提交) + * + * @author cascade + * @since 2026-01-01 + */ +public abstract class AbstractSysConfigListener implements MessageListener { + + private static final Logger logger = LoggerFactory.getLogger(AbstractSysConfigListener.class); + + /** 延时时间(毫秒),等待事务提交 */ + private static final long DELAY_MILLIS = 2000L; + + /** 调度线程池 */ + private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(r -> { + Thread t = new Thread(r, "sys-config-refresh"); + t.setDaemon(true); + return t; + }); + + @Override + public void onMessage(Message message, byte[] pattern) { + String channel = new String(message.getChannel()); + String body = new String(message.getBody()); + + logger.info("收到配置变更事件: channel={}, body={}", channel, body); + + // 延时2秒执行,等待发布方事务提交 + scheduler.schedule(() -> { + try { + logger.info("开始刷新配置: channel={}", channel); + doRefresh(channel, body); + logger.info("配置刷新完成: channel={}", channel); + } catch (Exception e) { + logger.error("配置刷新失败: channel={}", channel, e); + } + }, DELAY_MILLIS, TimeUnit.MILLISECONDS); + } + + /** + * 执行配置刷新,子类实现具体逻辑 + * + * @param channel 频道名称(对应配置分组) + * @param body 消息体(可选,可传递额外信息) + */ + protected abstract void doRefresh(String channel, String body); + + /** + * 获取监听的频道前缀 + * + * @return 频道前缀 + */ + public abstract String getChannelPattern(); +} diff --git a/urbanLifelineServ/system/src/main/java/org/xyzh/system/service/impl/SysConfigServiceImpl.java b/urbanLifelineServ/system/src/main/java/org/xyzh/system/service/impl/SysConfigServiceImpl.java index 57c8031e..4f871c9c 100644 --- a/urbanLifelineServ/system/src/main/java/org/xyzh/system/service/impl/SysConfigServiceImpl.java +++ b/urbanLifelineServ/system/src/main/java/org/xyzh/system/service/impl/SysConfigServiceImpl.java @@ -17,8 +17,13 @@ import org.xyzh.common.core.page.PageDomain; import org.xyzh.common.utils.id.IdUtil; import org.xyzh.common.utils.StringUtils; +import org.xyzh.common.redis.service.RedisService; +import org.xyzh.api.system.constance.SysConfigRedisPrefix; import org.xyzh.system.mapper.config.TbSysConfigMapper; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + /** * @description 系统配置服务实现类 * @filename SysConfigServiceImpl.java @@ -41,6 +46,37 @@ public class SysConfigServiceImpl implements SysConfigService { @Resource private TbSysConfigMapper configMapper; + @Resource + private RedisService redisService; + + /** 异步发送事件的线程池 */ + private final ExecutorService eventExecutor = Executors.newSingleThreadExecutor(r -> { + Thread t = new Thread(r, "sys-config-event"); + t.setDaemon(true); + return t; + }); + + /** + * 异步发布配置变更事件 + * @param group 配置分组 + */ + private void publishConfigChangeEvent(String group) { + if (StringUtils.isBlank(group)) { + return; + } + eventExecutor.execute(() -> { + try { + String channel = SysConfigRedisPrefix.getChannelByGroup(group); + if (channel != null) { + redisService.publish(channel, System.currentTimeMillis()); + logger.info("配置变更事件发布成功: channel={}", channel); + } + } catch (Exception e) { + logger.error("配置变更事件发布失败: group={}", group, e); + } + }); + } + /** * 根据key查询配置 */ @@ -242,6 +278,8 @@ public class SysConfigServiceImpl implements SysConfigService { int rows = configMapper.insertConfig(configDTO); if (rows > 0) { logger.info("新增配置成功, configId={}", configDTO.getConfigId()); + // 异步发布配置变更事件 + publishConfigChangeEvent(configDTO.getGroup()); return ResultDomain.success("新增配置成功", configDTO); } logger.warn("新增配置失败, configId={}", configDTO.getConfigId()); @@ -257,6 +295,8 @@ public class SysConfigServiceImpl implements SysConfigService { int rows = configMapper.updateConfig(configDTO); if (rows > 0) { logger.info("更新配置成功, configId={}", configDTO.getConfigId()); + // 异步发布配置变更事件 + publishConfigChangeEvent(configDTO.getGroup()); return ResultDomain.success("更新配置成功", configDTO); } logger.warn("更新配置失败, configId={}", configDTO.getConfigId()); @@ -271,6 +311,8 @@ public class SysConfigServiceImpl implements SysConfigService { int rows = configMapper.deleteConfig(configDTO); if (rows > 0) { logger.info("删除配置成功, configId={}", configDTO.getConfigId()); + // 异步发布配置变更事件 + publishConfigChangeEvent(configDTO.getGroup()); return ResultDomain.success("删除配置成功", Boolean.TRUE); } logger.warn("删除配置失败, configId={}", configDTO.getConfigId());