离线告警规则引擎实战:基于CodeArts与AI智能体的小程序端侧实现
前一阵在做一个运维巡检的微信小程序,其中一个需求折腾了我很久:巡检工程师经常要下到网络环境很差、甚至完全离线的地方,但告警规则全部放在云端,手机一断网,规则判定就失效,巡检员只能凭经验拍脑袋。后来我把华为云 CodeArts 的代码托管和流水线能力用起来做工程底座,再让 AI 智能体辅助拆需求、生成核心代码和测试用例,把一套离线告警规则引擎直接搬进了小程序端。这篇文章就是这次“华为云码道 CodeArts 实战”的完整复盘,核心记录四件事:离线告警引擎怎么设计、CodeArts 和 AI 智能体在开发链路里各自承担什么角色、小程序端怎么接线,以及我踩过的几个值得一提的坑。适合正在做运维类小程序、或者想让 AI 辅助开发工具真正落地到业务里的朋友参考。
1. 为什么运维巡检最怕“断网”:离线告警引擎的立项动机
1.1 巡检现场的真实困境
我最初接到的需求其实只有一句话:“做一个巡检小程序,能根据规则自动判断设备是否异常。”听起来朴素,但到了现场走访才发现问题不在“判断”本身,而在判断发生的环境。巡检员手里的设备大多是普通手机,进入弱电机房、地下配电室、园区弱电井之后,信号衰减非常严重。部分客户现场甚至明确要求巡检终端不能连接外网,数据只能在内部网络里流转。
在这种环境下,云端告警规则引擎面临一个尴尬场景:巡检员辛辛苦苦录入设备数据,本地小程序把数据传回云端,云端再跑规则、返回告警结果。任何一环出现网络抖动,录入就卡住,提交几秒钟后超时失败。更麻烦的是,如果巡检员在无网状态下连续跑了多个机房,所有离线期间的数据都会堆在本地,等联网后再补传。此时云端一次性算出几十条告警,早已错过了现场处置的最佳时机。
所以我当时确定了一个关键原则:告警判定必须发生在数据录入的“当前位置”,也就是手机本地。这不是要把云端整个搬下来,而是把最核心的“规则匹配”能力下沉到端侧,让巡检员即使在离线环境里也能立刻得到判定结果。这篇实战的核心,就是这个“离线告警规则引擎”。
1.2 “上云”与“离线”的取舍
开始设计之前,我先跟团队成员对齐了一个共识:离线引擎不是用来取代云端规则的,它只负责云端规则里“适合在本地执行”的那一部分。两者必须分工。
我按以下标准把规则切成两类:
- 云端规则:依赖全局数据的规则,例如“同一区域内超过三台设备同时高温”“某设备一周内告警次数超过阈值”。这类规则需要跨设备、跨时段的数据统计,放在本地不具备数据基础,必须留在云端。
- 离线规则:依赖单设备、单时段数据的规则,例如“机房温度超过45摄氏度并持续60秒”“UPS电池电压低于20V”。这类规则只看“当前这台设备的数据”就能得出结论,完全可以在本地执行。
这个切分非常关键。如果试图把云端所有规则都搬到本地,会陷入两个问题:一是本地存储和计算能力吃不消,二是规则更新会变成一场噩梦。我们最终只把“阈值判断类”“组合阈值类”“短时间窗口持续类”三种规则下沉到离线引擎,其余规则仍走云端,小程序端在离线时只提示“该条规则需要联网判定”。
1.3 离线告警引擎的边界定义
为了避免开发过程中需求无限膨胀,我写下了离线引擎的四条边界:
- 规则文件必须可更新:本地引擎不能写死规则,需要通过小程序下载最新规则包。
- 只做判定和记录:引擎只负责“输入指标样本、输出告警事件”,不做设备管理、不做派单。
- 告警数据必须能回流:离线产生的告警事件在联网后要能上传云端,并和云端规则产生的告警做合并去重。
- 版本可感知:巡检员需要看到当前使用的规则版本,避免离线场景下拿着旧规则去套新设备。
这四条边界在后续开发中帮了大忙。特别是第四条,最初被我当作“锦上添花”,后来在一次规则更新事故中才发现它其实是刚需。当时云端规则已经调整了温度阈值,但因为本地没有版本提示,巡检员还在用旧的45度阈值判断,导致一批设备现场没告警、回传后却全部异常。从那之后,规则版本号就成了离线引擎的必选项。
2. 把 CodeArts 和 AI 智能体放进同一张任务表:工具链选型
2.1 CodeArts 在项目里承担什么角色
这次项目我并没有从零搭建整套研发环境,而是直接用了华为云 CodeArts 作为工程底座。CodeArts 在这个项目里主要承担四件事:
- 需求管理:把“离线告警规则引擎”拆成需求条目,关联到具体代码提交。
- 代码托管:小程序前端和规则引擎核心分成两个仓库,互不干扰。
- 流水线:规则引擎代码变更后自动触发构建,产出规则包并上传到可下载地址。
- 代码检查:AI 智能体生成的代码先过一遍平台内置的静态检查,再进测试。
很多团队一提到 CodeArts 就想到“代码仓库”,但实际上它的流水线和代码检查才是这次和 AI 智能体配合的关键。我的使用姿势是:让 AI 智能体产出草稿代码和测试用例,然后把代码推送到仓库,用流水线跑编译、跑单测、跑静态检查。AI 负责“写得快”,CodeArts 负责“把关严”,两者互补。
我分享一个实际配置经验:流水线的“代码检查”阶段放在编译之前。因为 AI 生成的代码有时会混入未使用的变量、可疑的类型断言,静态检查能提前把这些低级问题暴露出来。不要觉得跑检查浪费时间,相比让带病代码进入测试阶段,这一步省下的时间远超检查本身的耗时。
2.2 AI 智能体在开发链路中的角色定位
我在这次实战中把 AI 智能体定位为“一个能快速产出草稿的结对程序员”,而不是“自动完成功能的黑盒”。具体来说,它承担了四类工作:
- 需求拆解:我给它一段口语化的巡检需求,它输出规则清单和边界条件。
- 代码生成:我给它规则数据结构和判定逻辑描述,它生成解析器骨架。
- 测试用例生成:它补齐了我容易忽略的边界测试,例如空值、跨窗口、乱序数据。
- 代码审查:我让它专门找“高危函数调用”和“不安全表达式”,结果真的抓到了几个问题。
这里要强调一个认知:AI 智能体生成代码的质量高度依赖输入信息的完整度。我一开始直接问“帮我写个规则引擎”,它输出的是泛泛的架构图,用处不大。后来改为“我有一个 JSON 规则结构,需要按以下判定流程实现解析器,输入是指标样本数组,输出是命中的规则列表,请用 TypeScript 实现”,产出质量立刻上了一个台阶。所以,把需求写清楚,是让 AI 真正发挥价值的前提。
2.3 离线引擎的载体选型
做离线引擎时,我首先纠结的是数据存储方案。巡检数据量不大,一个巡检员一天最多录入几百个指标点,但规则本身需要灵活更新。我在三种方案之间做了对比:
| 方案 | 优点 | 缺点 | 我的选择 |
|---|---|---|---|
| 云端数据库查询 | 数据统一,规则集中在服务端 | 离线不可用 | 放弃 |
| 本地 SQLite 数据库 | 查询能力强,数据管理规范 | 小程序端集成 SQLite 会增大包体,多端编译适配成本高 | 暂缓 |
| 本地文件 + 内存索引 | 轻量,实现快,规则包可直接下载替换 | 数据量大时查询效率低 | 采用 |
最终我选择“本地文件 + 内存索引”的方案。规则包是一个 JSON 文件,小程序启动时加载到内存里,按 ruleId 建立索引。巡检指标数据用微信小程序自身的存储能力暂存,联网后统一上传。这样做的好处是引擎本身不依赖数据库,纯函数式判定逻辑,单元测试也更好写。
这里有个细节值得说:很多开发者一看到“离线引擎”就想着上 SQLite,其实引擎的判断逻辑完全可以做成“无状态函数”,输入一条指标样本和一个规则集合,输出命中的规则。数据库只是存储端,不属于引擎核心。把引擎和存储解耦后,后续想换 SQLite 或 IndexedDB 都很容易。
3. 规则引擎的“骨架”:规则模型与离线判定流程
3.1 规则模型的数据结构
离线规则引擎的核心是规则模型。我设计的规则结构如下,这也是规则包 JSON 里的每一项:
{ "ruleId": "RULE_001", "name": "机房温度高温告警", "level": "critical", "source": "temperature", "condition": { "type": "threshold", "operator": ">", "value": 45, "durationSec": 60 }, "actions": ["alert", "record"] }字段说明:
ruleId:规则唯一标识,全局不重复。level:告警级别,我定义了 critical、warning、info 三级,判定的优先级依此递减。source:指标来源,例如 temperature、humidity、upsVoltage,引擎只对该类指标做匹配。condition.type:条件类型。目前实现了 threshold 阈值、continuous 连续N次、combination 组合条件三种。durationSec:持续时间窗口,表示该条件需要持续满足多少秒才告警。actions:动作列表。alert 代表弹告警,record 代表记录事件。
组合条件的规则长这样,用一个数组表示多个子条件:
{ "ruleId": "RULE_002", "name": "高温高湿同时发生告警", "level": "warning", "condition": { "type": "combination", "logic": "all", "subConditions": [ { "source": "temperature", "type": "threshold", "operator": ">", "value": 40 }, { "source": "humidity", "type": "threshold", "operator": ">", "value": 70 } ] }, "actions": ["alert"] }我最开始把规则模型设计得非常复杂,支持正则表达式、支持自定义脚本。后来被一位有经验的师兄泼了冷水:离线引擎的规则越复杂,现场出问题的概率越大。巡检场景规则其实非常固定,阈值判断占了九成。砍掉正则和脚本,保留 threshold 和 combination,不仅实现简单,规则包的安全性也大大提升。
3.2 离线判定流程
引擎的判定流程我总结为四步:加载规则 → 样本入参 → 逐条匹配 → 生成告警事件。
第一步,规则加载。小程序启动时读取本地规则包,做 JSON 解析和 schema 校验。校验这一步不能省,我遇到过规则包字段名大小写不一致导致匹配永远失败的诡异问题。解析后的规则存入内存 Map,键是规则级别加 ruleId,保证高优先级规则先执行。
第二步,指标样本入参。巡检员每录入一个指标点,小程序会组装一条 MetricSample:
{ "source": "temperature", "value": 46.5, "unit": "celsius", "timestamp": 1712563200000, "deviceId": "DEV_01" }第三步,逐条匹配。对每一条规则,引擎先检查 source 是否匹配,再检查条件类型。阈值判断做数值比较,连续判断则把该设备最近 N 条同源样本取出来逐一比较,组合判断则要求所有子条件同时满足。
第四步,生成告警事件。命中的规则会输出一条 AlertEvent,包含 ruleId、deviceId、level、触发时间、当时的指标值快照。告警事件先落本地存储,联网后再同步。
值得特别强调的是规则执行顺序。我的做法是:先判断 critical 级规则,再判断 warning 和 info。这样当一条指标同时命中“温度超过安全阈值”和“温度略超预警线”两条规则时,只会输出最高级别的那条告警,避免同一个故障刷出三条记录。现场巡检员最反感的就是告警轰炸,这个优先级设计实测非常有效。
3.3 时间窗口与告警去重
离线引擎上线后遇到一个很典型的问题:温度持续偏高时,采集一个点触发一条告警,一个巡检任务跑下来生成了几十条重复告警。这不是判断错误,而是缺少抑制机制。
我实现的抑制机制有两个层级:
第一层,持续条件。规则里如果配置了durationSec: 60,引擎不会因为一条指标样本就立刻告警,而是检查从当前时刻往前推60秒内,该设备同源样本是否全部满足阈值条件。全部满足才产生告警。这相当于给规则加了一个“时间窗口滤波器”。
第二层,去重窗口。每条规则附带一个去重键:ruleId + deviceId + 窗口起始时间戳。同一去重键在抑制窗口内只允许生成一条告警。我用一个固定大小的 Map 记录最近生成的告警键,Map 超过容量后按时间淘汰最旧的键。
这里有个参数需要调:去重窗口设多长。我最初设成 300 秒,结果一次超过阈值的异常持续了 10 分钟,巡检员只看到一条告警,以为恢复了,实际还在异常。后来改成 60 秒,既避免刷屏,又能让巡检员感知状态的持续变化。这个参数没有标准答案,需要根据巡检频率和告警重要度调整,我的经验是从“最小巡检间隔”的1.5倍起步。
4. 用 AI 智能体把需求“翻译”成可运行代码
4.1 让智能体先写“需求澄清文档”
在做核心代码之前,我让 AI 智能体先干了一件看似不产生代码但价值极高的事:生成需求澄清文档。我把口语需求发给它,请它列出规则引擎必须包含的功能点、边界情况和未知项。
提示词的思路大概是:
我准备开发一个离线告警规则引擎,运行在微信小程序环境。输入是指标样本数组,输出是命中的告警事件列表。规则以 JSON 配置下发。请帮我列出需求澄清清单,包括必须实现的功能、可以不实现的功能、以及我还没有明确但你认为需要确认的边界点。
AI 输出的结果里有一条非常关键:“离线引擎无法获取服务器时间,请确认时间基准使用设备本地时间还是云端时间戳”。这条提醒直接让我意识到,时间窗口判断必须统一以设备本地时间为基准,否则会出现规则失效的困惑。后续我把所有时间戳统一在采集端生成,用设备本地时间,并在上传后由云端记录接收时间,两者分开存储。这个设计就是靠 AI 的反问逼出来的。
4.2 生成规则解析器核心代码
需求澄清后,我让 AI 生成解析器代码。这次我给出了足够多的约束,最终的代码骨架大致如下:
export function evaluateRules( rules: RuleDefinition[], sample: MetricSample, history: MetricSample[], suppressWindowMs: number ): AlertEvent[] { const sorted = sortByLevelPriority(rules); const events: AlertEvent[] = []; for (const rule of sorted) { if (rule.source !== sample.source && rule.condition.type !== "combination") { continue; } const matched = matchRule(rule, sample, history); if (matched && !isSuppressed(rule.ruleId, sample.deviceId, sample.timestamp, suppressWindowMs)) { events.push(buildAlertEvent(rule, sample)); recordSuppression(rule.ruleId, sample.deviceId, sample.timestamp); } } return events; }不过我必须提醒一句:AI 生成这段代码只花了几十秒,但它一开始生成的版本里有个明显问题,sortByLevelPriority函数没有处理同级别规则的顺序稳定性,导致同级别的告警事件在多次执行中顺序不一致。单测能跑通,但表现不稳定。
这类问题靠人肉 review 效率太低,我的做法是把 AI 代码提交到 CodeArts 仓库后跑一轮流水线,让平台的代码检查和单测一起兜底。AI 辅助开发的价值在“快速搭骨架”,但质量门禁必须靠工程工具来保证,这是这次实践给我最深的印象之一。
4.3 自动生成测试用例:边界值是重点
AI 智能体产出的测试用例帮我挡掉了至少三个线上才会暴露的 bug。我列出几个很有代表性的边界场景:
- 刚好等于阈值:规则是“大于45度告警”,样本值正好是45度,不应该告警。AI 生成的测试用例里有
value: 45,断言结果为 false。 - 空值:传感器上传了
null值,解析器应该跳过而不是报错。最初手写代码时完全没有考虑这个情况,AI 生成的用例直接让解析器崩溃,促使我加上了类型守卫。 - 乱序样本:巡检员偶尔会补录历史数据,样本时间戳不是递增的。窗口判断不能因为乱序就把旧数据记为“当前异常”。
- 时间窗口边界:样本时间戳恰好在窗口起点上,应视为窗口内。
这里有个使用技巧:给 AI 的测试用例要求越具体越好。不要只说“写边界测试”,而是给出你的规则类型清单和潜在风险点。我那次的提示词里明确写了“重点覆盖 durationSec 窗口边缘、空值、字符串类型混入,以及组合规则部分子条件不满足的情况”,AI 产出的测试集一下子就从泛泛的 happy path 变成了真正有价值的回归测试。
5. 小程序端的缓冲池与告警展示:离线不等于失联
5.1 巡检任务与引擎之间的数据通道
离线引擎再强,最终还是要跟小程序界面配合。我这里的架构是:巡检员在小程序首页创建巡检任务,任务下挂巡检点位,每个点位录入设备指标。每次录入,小程序会调用一个统一的数据通道函数ingestMetric(sample),这个函数完成三件事:
- 把样本写入本地存储的暂存队列。
- 把样本送入规则引擎执行匹配。
- 把新的告警事件刷新到界面上的告警列表。
这个通道设计的关键是“先落本地,再上报云端”。一旦网络恢复,ingestMetric会尝试触发一次同步,把暂存队列中的指标样本和告警事件一起打包上传。同步是幂等的,服务端根据sampleId和alertId做去重,重复上传不会产生重复数据。
我特别建议不要在小程序里用“立即发起请求、失败后重试”的思路处理巡检数据。因为离线场景下几乎所有请求都会失败,频繁失败重试既费电又容易让用户误以为系统坏了。正确做法就是本地缓冲池加后续批量同步。
5.2 告警展示的体验细节
告警展示上,我做了三个层级:
- 顶部横幅:出现 critical 级告警时弹出醒目横幅,并伴随震动提示。
- 告警列表:每个告警项展示规则名、设备、级别、触发时间,以及当时的指标快照值。
- 巡检小结:巡检任务结束时,汇总本次任务产生的全部告警,支持一键复制为文本发送到工作群。
快照值这一点很容易被遗漏。单纯显示“温度告警”没有意义,巡检员需要知道触发告警时温度到底是多少。所以我在 AlertEvent 里保留了触发时刻的原始指标值,展示时直接读取快照。这个设计在后续跟客户反馈问题交互时特别管用,巡检员直接把快照截图发出来了,处理效率高很多。
5.3 规则包更新机制
离线的代价是规则更新无法实时生效。我的方案是“版本号 + 启动检查”。
规则包 JSON 里带一个version字段,比如v20250601。小程序启动时先从本地读取当前版本,再尝试从云端获取最新版本号。如果本地版本低于最新版本,则下载新规则包并覆盖本地缓存。如果启动时没有网络,则保持旧规则包继续运行,并在首页右上角显示“规则版本:v20250601(离线)”。
这个提示不是给普通巡检员看的,而是给现场负责人看的。它解决了一个实际问题:当巡检员拿着旧规则在离线环境跑了半天,产生的告警可能和云端新规则不一致。有了版本标识,现场至少能意识到“我用的不是最新规则”,避免后续对账时产生激烈争执。
一个额外的实现细节:规则包下载后必须做完整性校验。我在规则包中放了一个 SHA-256 值,下载完成后先校验再覆盖。否则断点下载导致规则包截断,JSON 解析失败时会直接把原有规则清空,那将是灾难级的故障。
6. 实测效果与踩坑记录
6.1 模拟断网后的告警触发测试
规则引擎写完后,我在真机上做了一轮完整的离线模拟。设备开启飞行模式,手动录入一条超出温度阈值的样本,观察告警是否弹出。测试共五个用例:
| 用例 | 输入 | 预期 | 实测 |
|---|---|---|---|
| 单条超阈值 | 温度 46.5 | 立即告警 | 通过 |
| 低于阈值 | 温度 44.9 | 不告警 | 通过 |
| 持续窗口内先低后高 | 44.9 持续30秒后 46.5 | 窗口内持续满足才告警 | 通过 |
| 超阈值后快速回落 | 46.5 持续10秒后回落 | 未达到持续窗口,不告警 | 通过 |
| 空值样本 | value 为 null | 跳过,不崩溃 | 通过 |
“超阈值后快速回落”这个用例特别说明问题:规则配置的是持续60秒,样本从超阈值到回落只用了10秒,理论上不应该告警。最初实现里我的持续窗口判断逻辑有 bug,每次新样本都会重置窗口起点,导致永远达不到持续条件。后来测试驱动我把逻辑改成“只取窗口内最早一条样本作为起点”,连续判断才真正生效。
6.2 踩坑记录:规则包表达式的安全问题
这是我这次实战中最后悔没有早点意识到的问题。最初为了支持灵活规则,我尝试把规则表达式设计成一段可执行的 JavaScript 字符串,然后在本地用new Function执行。Demo 跑得很欢,但一次安全评审时被直接质疑:规则包如果被中间人篡改,这段表达式会在小程序里执行任意代码,等于把手机变成了矿机。
最终方案是彻底阉割“任意表达式”能力,只保留白名单操作符> < >= <= == !=和固定的字段名。规则配置里不允许出现函数调用、不允许出现变量。这样一个经过 JSON 解析的规则文件,无论如何构造,最后都只会被解析成纯数据的判定逻辑,没有任何代码可以被执行。这个取舍会让规则表达灵活性降低一些,但在巡检场景里完全够用。
6.3 踩坑记录:小程序启动时序对规则加载的影响
小程序启动流程里,规则包加载是异步的。一开始我把规则加载放到了onShow里触发,结果出现一个 bug:巡检员进入页面后立刻开始录入数据,但规则包还在下载过程中,此时引擎里没有任何规则,所有录入都不产生告警,等规则包到达后也无法追溯之前的样本。
我解决的方案是给规则加载加了一道“启动屏障”。小程序启动时先展示一个轻量的加载状态,规则包加载完成后才允许进入巡检录入页。如果规则包加载失败且存在本地缓存,则降级使用缓存规则并放行;如果本地根本没有缓存,则提示需要联网初始化。这个处理看起来很基础,但在真实使用中非常关键,它保证了“引擎永远有规则可用”这个基本前提。
6.4 AI 智能体生成代码的“幻觉”问题
AI 智能体在生成规则引擎代码时,两次出现了“幻觉”函数名。一次是它写了一个roundValue函数,但内部调用了一个不存在的rounded(),编译不报错,运行结果永远是undefined,导致阈值判断全部失效。另一次是它假设标准库里存在sortByLevelPriority,但我没有事先定义该函数。AI 不负责保证代码整体可运行,它只是在“最可能的正确方向上”产出文本。
我的应对方式有三个:
- 生成代码后立刻跑一次单测,成功样例和失败样例各来一组。
- 代码进入仓库前跑 CodeArts 的静态检查。
- 对 AI 生成的所有“假设性工具函数”,人工确认是否存在,不存在就让 AI 补全或自己手写。
不要迷信 AI 的代码质量,也不要因为它犯低级错误就否定它的价值。它最大的贡献是帮我把“从零到一”的时间压缩了百分之七十,但“从一到稳”这件事,仍然需要人的判断和工程手段来兜底。这次实战里,AI 智能体更像是一台能快速转动但需要良好润滑的发动机,CodeArts 流水线、静态检查和单测则充当了它的限速器和安全带。
最后再分享一个小建议:如果你也在做类似的离线规则引擎,一开始不要把规则类型设计得太复杂,先把“阈值 + 持续窗口”这一条链路跑通,再逐步加组合规则。我在这次项目中就是因为过早加入了组合条件,导致第一版引擎出现了大量对规则顺序的纠结。后来先把核心链路稳定下来,组合规则作为增量特性再接入,整个开发过程顺畅了很多。这套“离线引擎 + 本地缓冲 + 异步回传”的架构,后面还可以继续扩展成可视化规则配置后台,让运营人员在网页上拖拽规则下发到小程序端,那又是另一个有趣的项目了。