在线热更新配置
This commit is contained in:
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<String, String> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user