
前一阵在做一个运维巡检的微信小程序其中一个需求折腾了我很久巡检工程师经常要下到网络环境很差、甚至完全离线的地方但告警规则全部放在云端手机一断网规则判定就失效巡检员只能凭经验拍脑袋。后来我把华为云 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 流水线、静态检查和单测则充当了它的限速器和安全带。最后再分享一个小建议如果你也在做类似的离线规则引擎一开始不要把规则类型设计得太复杂先把“阈值 持续窗口”这一条链路跑通再逐步加组合规则。我在这次项目中就是因为过早加入了组合条件导致第一版引擎出现了大量对规则顺序的纠结。后来先把核心链路稳定下来组合规则作为增量特性再接入整个开发过程顺畅了很多。这套“离线引擎 本地缓冲 异步回传”的架构后面还可以继续扩展成可视化规则配置后台让运营人员在网页上拖拽规则下发到小程序端那又是另一个有趣的项目了。