Nacos 改了一行配置飞书群里 30 秒内收到了通知自定义配置变更通知插件实战文章目录Nacos 改了一行配置飞书群里 30 秒内收到了通知自定义配置变更通知插件实战配置被改了但没人知道是谁改的、什么时候改的ConfigChangeHook 接口ConfigChangeRequest 包含的信息实现一飞书 Webhook 通知核心代码钉钉/企业微信适配实现二触发 CI/CD 流水线实现三敏感配置变更拦截打包部署一张图带走配置变更 Hook 的触发流程配置被改了但没人知道是谁改的、什么时候改的Nacos 控制台里有一个历史版本功能能看到谁在什么时间改了哪行配置。但这需要你主动去翻——没有主动通知机制。生产环境里一行配置改错了服务出问题排查了半小时才发现是配置问题。如果配置变更的时候能推一条通知到运维群里发现时间可以从 30 分钟缩短到 30 秒。Nacos 2.x 提供了 ConfigChangeHook 扩展点。配置变更前和变更后都可以触发你自定义的逻辑。你可以用它推送变更通知到飞书/钉钉/企业微信触发 CI/CD 流水线配置变更后自动重新部署写入审计日志谁改了什么、改前后的 diff这篇文章用三个例子走一遍 ConfigChangeHook 的开发全流程。⚠️ 本文基于 Nacos Server 2.3.x。ConfigChangeHook 从 2.2.0 开始引入。如果你用的是 2.1.x 及以下需要用 EventListener 机制替代通过 NacosNotifyCenter 监听 ConfigChangedEvent。ConfigChangeHook 接口// com.alibaba.nacos.plugin.control.spi.ConfigChangeHookpublicinterfaceConfigChangeHook{/** * Hook 名称 */StringgetName();/** * 配置变更前触发可用于拦截/审计 * return false 则阻止本次变更 */booleanpreChange(ConfigChangeRequestrequest);/** * 配置变更后触发可用于通知/触发流水线 */voidpostChange(ConfigChangeRequestrequest,ConfigChangeResultresult);}方法调用时机典型用途getName()插件加载时返回 hook 名称preChange()配置变更执行前拦截敏感配置变更、记录审计日志postChange()配置变更执行后推送通知、触发流水线ConfigChangeRequest 包含的信息publicclassConfigChangeRequest{privateStringdataId;// 被修改的配置 IDprivateStringgroup;// 分组privateStringnamespace;// 命名空间privateStringoldContent;// 变更前的内容privateStringnewContent;// 变更后的内容privateStringsrcIp;// 操作来源 IPprivateStringsrcUser;// 操作人如果开了认证privatelongchangeTime;// 变更时间戳privateStringtype;// 变更类型INSERT/UPDATE/DELETE}实现一飞书 Webhook 通知核心代码packagecom.example.nacos.hook;importcom.alibaba.nacos.plugin.control.spi.ConfigChangeHook;importcom.alibaba.nacos.plugin.control.model.ConfigChangeRequest;importcom.alibaba.nacos.plugin.control.model.ConfigChangeResult;importjava.net.http.HttpClient;importjava.net.http.HttpRequest;importjava.net.URI;publicclassFeishuNotifyHookimplementsConfigChangeHook{privatestaticfinalStringFEISHU_WEBHOOK_URLSystem.getProperty(nacos.hook.feishu.webhook,https://open.feishu.cn/open-apis/bot/v2/hook/YOUR_TOKEN);privatefinalHttpClienthttpClientHttpClient.newHttpClient();OverridepublicStringgetName(){returnfeishu-notify;}OverridepublicbooleanpreChange(ConfigChangeRequestrequest){// 不拦截所有变更都放行returntrue;}OverridepublicvoidpostChange(ConfigChangeRequestrequest,ConfigChangeResultresult){if(!result.isSuccess()){return;// 变更失败了不通知}StringmessagebuildMessage(request);sendToFeishu(message);}privateStringbuildMessage(ConfigChangeRequestrequest){// 构建 diff简化版实际可以用 diff 库StringdiffcomputeDiff(request.getOldContent(),request.getNewContent());returnString.format(**[Nacos 配置变更通知]**\n- 配置: %s / %s\n- 命名空间: %s\n- 操作: %s\n- 操作人: %s%s\n- 时间: %s\n- 变更内容:\n\n%s\n,request.getGroup(),request.getDataId(),request.getNamespace(),request.getType(),request.getSrcUser()!null?request.getSrcUser():unknown,request.getSrcIp(),newjava.util.Date(request.getChangeTime()),diff);}privatevoidsendToFeishu(Stringmessage){try{StringjsonString.format({\msg_type\:\text\,\content\:{\text\:\%s\}},message.replace(\,\\\).replace(\n,\\n));HttpRequesthttpRequestHttpRequest.newBuilder().uri(URI.create(FEISHU_WEBHOOK_URL)).header(Content-Type,application/json).POST(HttpRequest.BodyPublishers.ofString(json)).build();httpClient.send(httpRequest,HttpResponse.BodyHandlers.ofString());}catch(Exceptione){// 通知失败不应该影响配置变更// 记录日志即可}}privateStringcomputeDiff(StringoldContent,StringnewContent){if(oldContentnull)return[新增配置];if(newContentnull)return[删除配置];// 简单的行级 diffString[]oldLinesoldContent.split(\n);String[]newLinesnewContent.split(\n);StringBuildersbnewStringBuilder();// ... 实际的 diff 逻辑returnsb.toString();}}钉钉/企业微信适配钉钉和企业微信的 Webhook 接口格式不同只需要改sendToFeishu方法// 钉钉privatevoidsendToDingTalk(Stringmessage){StringjsonString.format({\msgtype\:\markdown\,\markdown\:{\title\:\Nacos配置变更\,\text\:\%s\}},message);// POST 到钉钉 Webhook URL}// 企业微信privatevoidsendToWeCom(Stringmessage){StringjsonString.format({\msgtype\:\markdown\,\markdown\:{\content\:\%s\}},message);// POST 到企业微信 Webhook URL}实现二触发 CI/CD 流水线配置变更后自动触发 Jenkins 流水线让应用重新加载配置适合不支持动态刷新的老项目publicclassCiCdTriggerHookimplementsConfigChangeHook{privatestaticfinalStringJENKINS_URLSystem.getProperty(nacos.hook.jenkins.url,http://jenkins.company.com/job/config-reload/build);privatestaticfinalStringJENKINS_TOKENSystem.getProperty(nacos.hook.jenkins.token,);OverridepublicStringgetName(){returncicd-trigger;}OverridepublicbooleanpreChange(ConfigChangeRequestrequest){returntrue;// 不拦截}OverridepublicvoidpostChange(ConfigChangeRequestrequest,ConfigChangeResultresult){if(!result.isSuccess())return;// 只对特定命名空间和 group 的配置触发流水线if(!prod.equals(request.getNamespace()))return;if(!DEFAULT_GROUP.equals(request.getGroup()))return;triggerJenkinsBuild(request.getDataId());}privatevoidtriggerJenkinsBuild(StringdataId){try{StringurlJENKINS_URLWithParameters?DATA_IDdataId;HttpRequestrequestHttpRequest.newBuilder().uri(URI.create(url)).header(Authorization,Basic JENKINS_TOKEN).POST(HttpRequest.BodyPublishers.noBody()).build();HttpClient.newHttpClient().send(request,HttpResponse.BodyHandlers.ofString());}catch(Exceptione){// 触发失败不影响配置变更}}}实现三敏感配置变更拦截有些配置不应该被随便改比如数据库密码、加密密钥。preChange 返回 false 可以阻止变更publicclassSensitiveConfigGuardimplementsConfigChangeHook{// 敏感配置列表privatestaticfinalSetStringSENSITIVE_DATA_IDSSet.of(db.password,redis.password,jwt.secret.key,payment.private.key,oss.access.key);// 允许修改敏感配置的角色privatestaticfinalSetStringALLOWED_ROLESSet.of(ROLE_ADMIN,ROLE_SECURITY_ADMIN);OverridepublicStringgetName(){returnsensitive-config-guard;}OverridepublicbooleanpreChange(ConfigChangeRequestrequest){if(!SENSITIVE_DATA_IDS.contains(request.getDataId())){returntrue;// 不是敏感配置放行}// 检查操作人是否有权限StringsrcUserrequest.getSrcUser();if(srcUsernull||!hasAllowedRole(srcUser)){// 记录审计日志有人试图修改敏感配置但被拦截auditLog(BLOCKED: user{} tried to modify sensitive config {},srcUser,request.getDataId());returnfalse;// 拦截}// 允许修改但记录审计日志auditLog(ALLOWED: user{} modified sensitive config {},srcUser,request.getDataId());returntrue;}OverridepublicvoidpostChange(ConfigChangeRequestrequest,ConfigChangeResultresult){// 敏感配置变更后也记录日志if(SENSITIVE_DATA_IDS.contains(request.getDataId())){auditLog(CHANGED: config{} by user{} from {},request.getDataId(),request.getSrcUser(),request.getSrcIp());}}}打包部署和前两个插件一样# 打包mvn clean package-DskipTests# 部署cptarget/nacos-hook-notify-1.0.0.jar /home/nacos/plugins/# 重启 Nacosshshutdown.shshstartup.sh-mclusterSPI 配置# META-INF/services/com.alibaba.nacos.plugin.control.spi.ConfigChangeHook com.example.nacos.hook.FeishuNotifyHook com.example.nacos.hook.CiCdTriggerHook com.example.nacos.hook.SensitiveConfigGuard多个 Hook 可以同时生效。Nacos 会按 order 排序依次执行。preChange 中任何一个返回 false整个变更被拦截。一张图带走配置变更 Hook 的触发流程truetruefalse敏感配置无权限用户修改配置点击发布Hook 1: SensitiveConfigGuardpreChange()Hook 2: FeishuNotifyHookpreChange()执行配置变更写入数据库Hook 1: SensitiveConfigGuardpostChange()Hook 2: FeishuNotifyHookpostChange()Hook 3: CiCdTriggerHookpostChange()拦截变更返回 403飞书群收到通知30 秒内Jenkins 流水线触发多个 Hook 按顺序执行。preChange 阶段任何一个返回 false 就拦截。postChange 阶段所有 Hook 都会执行不管前一个是否成功。你们的 Nacos 配置变更有通知机制吗评论区留数字1有接了飞书/钉钉 2有接了审计日志 3没有都是出了问题才去翻历史 4有这个需求先收藏
Nacos 改了一行配置,飞书群里 30 秒内收到了通知:自定义配置变更通知插件实战
发布时间:2026/7/13 11:24:38
Nacos 改了一行配置飞书群里 30 秒内收到了通知自定义配置变更通知插件实战文章目录Nacos 改了一行配置飞书群里 30 秒内收到了通知自定义配置变更通知插件实战配置被改了但没人知道是谁改的、什么时候改的ConfigChangeHook 接口ConfigChangeRequest 包含的信息实现一飞书 Webhook 通知核心代码钉钉/企业微信适配实现二触发 CI/CD 流水线实现三敏感配置变更拦截打包部署一张图带走配置变更 Hook 的触发流程配置被改了但没人知道是谁改的、什么时候改的Nacos 控制台里有一个历史版本功能能看到谁在什么时间改了哪行配置。但这需要你主动去翻——没有主动通知机制。生产环境里一行配置改错了服务出问题排查了半小时才发现是配置问题。如果配置变更的时候能推一条通知到运维群里发现时间可以从 30 分钟缩短到 30 秒。Nacos 2.x 提供了 ConfigChangeHook 扩展点。配置变更前和变更后都可以触发你自定义的逻辑。你可以用它推送变更通知到飞书/钉钉/企业微信触发 CI/CD 流水线配置变更后自动重新部署写入审计日志谁改了什么、改前后的 diff这篇文章用三个例子走一遍 ConfigChangeHook 的开发全流程。⚠️ 本文基于 Nacos Server 2.3.x。ConfigChangeHook 从 2.2.0 开始引入。如果你用的是 2.1.x 及以下需要用 EventListener 机制替代通过 NacosNotifyCenter 监听 ConfigChangedEvent。ConfigChangeHook 接口// com.alibaba.nacos.plugin.control.spi.ConfigChangeHookpublicinterfaceConfigChangeHook{/** * Hook 名称 */StringgetName();/** * 配置变更前触发可用于拦截/审计 * return false 则阻止本次变更 */booleanpreChange(ConfigChangeRequestrequest);/** * 配置变更后触发可用于通知/触发流水线 */voidpostChange(ConfigChangeRequestrequest,ConfigChangeResultresult);}方法调用时机典型用途getName()插件加载时返回 hook 名称preChange()配置变更执行前拦截敏感配置变更、记录审计日志postChange()配置变更执行后推送通知、触发流水线ConfigChangeRequest 包含的信息publicclassConfigChangeRequest{privateStringdataId;// 被修改的配置 IDprivateStringgroup;// 分组privateStringnamespace;// 命名空间privateStringoldContent;// 变更前的内容privateStringnewContent;// 变更后的内容privateStringsrcIp;// 操作来源 IPprivateStringsrcUser;// 操作人如果开了认证privatelongchangeTime;// 变更时间戳privateStringtype;// 变更类型INSERT/UPDATE/DELETE}实现一飞书 Webhook 通知核心代码packagecom.example.nacos.hook;importcom.alibaba.nacos.plugin.control.spi.ConfigChangeHook;importcom.alibaba.nacos.plugin.control.model.ConfigChangeRequest;importcom.alibaba.nacos.plugin.control.model.ConfigChangeResult;importjava.net.http.HttpClient;importjava.net.http.HttpRequest;importjava.net.URI;publicclassFeishuNotifyHookimplementsConfigChangeHook{privatestaticfinalStringFEISHU_WEBHOOK_URLSystem.getProperty(nacos.hook.feishu.webhook,https://open.feishu.cn/open-apis/bot/v2/hook/YOUR_TOKEN);privatefinalHttpClienthttpClientHttpClient.newHttpClient();OverridepublicStringgetName(){returnfeishu-notify;}OverridepublicbooleanpreChange(ConfigChangeRequestrequest){// 不拦截所有变更都放行returntrue;}OverridepublicvoidpostChange(ConfigChangeRequestrequest,ConfigChangeResultresult){if(!result.isSuccess()){return;// 变更失败了不通知}StringmessagebuildMessage(request);sendToFeishu(message);}privateStringbuildMessage(ConfigChangeRequestrequest){// 构建 diff简化版实际可以用 diff 库StringdiffcomputeDiff(request.getOldContent(),request.getNewContent());returnString.format(**[Nacos 配置变更通知]**\n- 配置: %s / %s\n- 命名空间: %s\n- 操作: %s\n- 操作人: %s%s\n- 时间: %s\n- 变更内容:\n\n%s\n,request.getGroup(),request.getDataId(),request.getNamespace(),request.getType(),request.getSrcUser()!null?request.getSrcUser():unknown,request.getSrcIp(),newjava.util.Date(request.getChangeTime()),diff);}privatevoidsendToFeishu(Stringmessage){try{StringjsonString.format({\msg_type\:\text\,\content\:{\text\:\%s\}},message.replace(\,\\\).replace(\n,\\n));HttpRequesthttpRequestHttpRequest.newBuilder().uri(URI.create(FEISHU_WEBHOOK_URL)).header(Content-Type,application/json).POST(HttpRequest.BodyPublishers.ofString(json)).build();httpClient.send(httpRequest,HttpResponse.BodyHandlers.ofString());}catch(Exceptione){// 通知失败不应该影响配置变更// 记录日志即可}}privateStringcomputeDiff(StringoldContent,StringnewContent){if(oldContentnull)return[新增配置];if(newContentnull)return[删除配置];// 简单的行级 diffString[]oldLinesoldContent.split(\n);String[]newLinesnewContent.split(\n);StringBuildersbnewStringBuilder();// ... 实际的 diff 逻辑returnsb.toString();}}钉钉/企业微信适配钉钉和企业微信的 Webhook 接口格式不同只需要改sendToFeishu方法// 钉钉privatevoidsendToDingTalk(Stringmessage){StringjsonString.format({\msgtype\:\markdown\,\markdown\:{\title\:\Nacos配置变更\,\text\:\%s\}},message);// POST 到钉钉 Webhook URL}// 企业微信privatevoidsendToWeCom(Stringmessage){StringjsonString.format({\msgtype\:\markdown\,\markdown\:{\content\:\%s\}},message);// POST 到企业微信 Webhook URL}实现二触发 CI/CD 流水线配置变更后自动触发 Jenkins 流水线让应用重新加载配置适合不支持动态刷新的老项目publicclassCiCdTriggerHookimplementsConfigChangeHook{privatestaticfinalStringJENKINS_URLSystem.getProperty(nacos.hook.jenkins.url,http://jenkins.company.com/job/config-reload/build);privatestaticfinalStringJENKINS_TOKENSystem.getProperty(nacos.hook.jenkins.token,);OverridepublicStringgetName(){returncicd-trigger;}OverridepublicbooleanpreChange(ConfigChangeRequestrequest){returntrue;// 不拦截}OverridepublicvoidpostChange(ConfigChangeRequestrequest,ConfigChangeResultresult){if(!result.isSuccess())return;// 只对特定命名空间和 group 的配置触发流水线if(!prod.equals(request.getNamespace()))return;if(!DEFAULT_GROUP.equals(request.getGroup()))return;triggerJenkinsBuild(request.getDataId());}privatevoidtriggerJenkinsBuild(StringdataId){try{StringurlJENKINS_URLWithParameters?DATA_IDdataId;HttpRequestrequestHttpRequest.newBuilder().uri(URI.create(url)).header(Authorization,Basic JENKINS_TOKEN).POST(HttpRequest.BodyPublishers.noBody()).build();HttpClient.newHttpClient().send(request,HttpResponse.BodyHandlers.ofString());}catch(Exceptione){// 触发失败不影响配置变更}}}实现三敏感配置变更拦截有些配置不应该被随便改比如数据库密码、加密密钥。preChange 返回 false 可以阻止变更publicclassSensitiveConfigGuardimplementsConfigChangeHook{// 敏感配置列表privatestaticfinalSetStringSENSITIVE_DATA_IDSSet.of(db.password,redis.password,jwt.secret.key,payment.private.key,oss.access.key);// 允许修改敏感配置的角色privatestaticfinalSetStringALLOWED_ROLESSet.of(ROLE_ADMIN,ROLE_SECURITY_ADMIN);OverridepublicStringgetName(){returnsensitive-config-guard;}OverridepublicbooleanpreChange(ConfigChangeRequestrequest){if(!SENSITIVE_DATA_IDS.contains(request.getDataId())){returntrue;// 不是敏感配置放行}// 检查操作人是否有权限StringsrcUserrequest.getSrcUser();if(srcUsernull||!hasAllowedRole(srcUser)){// 记录审计日志有人试图修改敏感配置但被拦截auditLog(BLOCKED: user{} tried to modify sensitive config {},srcUser,request.getDataId());returnfalse;// 拦截}// 允许修改但记录审计日志auditLog(ALLOWED: user{} modified sensitive config {},srcUser,request.getDataId());returntrue;}OverridepublicvoidpostChange(ConfigChangeRequestrequest,ConfigChangeResultresult){// 敏感配置变更后也记录日志if(SENSITIVE_DATA_IDS.contains(request.getDataId())){auditLog(CHANGED: config{} by user{} from {},request.getDataId(),request.getSrcUser(),request.getSrcIp());}}}打包部署和前两个插件一样# 打包mvn clean package-DskipTests# 部署cptarget/nacos-hook-notify-1.0.0.jar /home/nacos/plugins/# 重启 Nacosshshutdown.shshstartup.sh-mclusterSPI 配置# META-INF/services/com.alibaba.nacos.plugin.control.spi.ConfigChangeHook com.example.nacos.hook.FeishuNotifyHook com.example.nacos.hook.CiCdTriggerHook com.example.nacos.hook.SensitiveConfigGuard多个 Hook 可以同时生效。Nacos 会按 order 排序依次执行。preChange 中任何一个返回 false整个变更被拦截。一张图带走配置变更 Hook 的触发流程truetruefalse敏感配置无权限用户修改配置点击发布Hook 1: SensitiveConfigGuardpreChange()Hook 2: FeishuNotifyHookpreChange()执行配置变更写入数据库Hook 1: SensitiveConfigGuardpostChange()Hook 2: FeishuNotifyHookpostChange()Hook 3: CiCdTriggerHookpostChange()拦截变更返回 403飞书群收到通知30 秒内Jenkins 流水线触发多个 Hook 按顺序执行。preChange 阶段任何一个返回 false 就拦截。postChange 阶段所有 Hook 都会执行不管前一个是否成功。你们的 Nacos 配置变更有通知机制吗评论区留数字1有接了飞书/钉钉 2有接了审计日志 3没有都是出了问题才去翻历史 4有这个需求先收藏