Workerman不可在onMessage中直接调用微信/短信SDK,因其基于异步事件循环,同步HTTP请求会阻塞整个进程;正确做法是将报警任务投递至Redis队列,由独立Worker异步处理,并遵循企业微信/服务号合规接口及云通信短信API规范。

Workerman 本身不直接发微信或短信,必须对接第三方服务接口;强行在 Workerman 进程里调用阻塞式 HTTP 请求(比如用 file_get_contents 发短信)会卡死整个事件循环,导致数据采集和 MQTT 订阅全部停滞。
为什么不能直接在 onMessage 里调用微信/短信 SDK
Workerman 是基于 ReactPHP 模型的异步 I/O 框架,所有回调(包括 onMessage)都在单线程 EventLoop 中执行。一旦你在里面调用同步 HTTP 客户端(如 cURL 默认模式、file_get_contents),整个进程就会挂起等待响应,传感器心跳断连、MQTT 心跳超时、新连接无法接入——不是“报警没发出去”,而是“整个系统假死”。
常见错误现象:
- 温湿度数据还在上报,但 Web 管理端曲线突然停住
- 设备在线状态变成离线,但实际硬件仍在运行
- 报警触发后,只发了一次,后续再超限就完全静默
正确做法是把报警动作“投递出去”,交给独立进程或队列处理:
- 用
Redis的LPUSH+BRPOP做轻量级任务队列 - 启动一个单独的
Worker子进程,持续BRPOP报警任务并异步发信 - 避免在主 Worker 中做任何
sleep、file_get_contents、curl_exec等阻塞操作
微信报警必须走企业微信/公众号模板消息,不能用个人号
个人微信号没有开放 API 接口,所谓“用 PHP 调微信发消息”基本是调用非官方协议(如 WeChatPY、itchat),这类方案在 2025 年后已大面积失效,且违反微信平台规则,账号会被封禁。
合规路径只有两条:
-
企业微信应用:需管理员在后台创建应用,获取
corpid和corpsecret,调用/cgi-bin/message/send接口发模板消息(需用户提前关注该应用) - 微信服务号:需认证,开通模板消息能力,用户必须已关注且产生过交互(如点击菜单),否则无法主动推送
示例关键参数(企业微信):
{
"touser": "@all",
"msgtype": "textcard",
"agentid": 100001,
"textcard": {
"title": "⚠️ 温室温湿度越限",
"description": "时间:2026-05-23 22:15<br>位置:A区大棚<br>温度:38.2℃(>35℃)<br>湿度:89%RH(>85%RH)",
"url": "https://your-platform.com/alarm/detail/abc123"
}
}
注意:agentid 必须和获取 access_token 时用的 corpid/corpsecret 匹配,否则返回 {"errcode":40014,"errmsg":"invalid access_token"}。
一站式排查小程序隐私授权报错(errno 104 / buttonId is wrong 等)+ 标准修改模式。适用:隐私协议配置、隐私弹窗不弹、授权报错、隐私接口调用失败。已抽离具体项目,适用于任何微信小程序。
短信报警优先选云通信平台的 HTTP API,别碰 SMPP
SMPP 协议需要维护长连接、处理重连、序列号管理、状态报告解析,对 Workerman 来说负担过重,且国内三大运营商基本不直接受理中小开发者接入。
推荐方案是调用成熟云服务商的 HTTPS 接口,例如:
- 阿里云:调用
/green/text/openScan(内容审核)+/singleSendSms(发送),需先申请签名与模板,模板中必须含变量如{name}、{temp} - 腾讯云:用
SmsClientSDK,注意TemplateID和PhoneNumberSet格式必须为+8613800138000 - 容联云、亿美软通等也提供简单 JSON 接口,返回
"Code":"OK"即表示已入运营商通道
关键避坑点:
- 短信内容里不能出现“微信”“红包”“投资”等敏感词,否则审核不通过
- 同一手机号 60 秒内只能发 1 条,高频报警需加
Redis限流(如SETNX alarm:phone:138xxxxxx 1 EX 60) - 不要在报警逻辑里拼接完整短信文案,应从配置中心读取模板,方便多语言和运营调整
报警触发判断必须带“恢复通知”和“去抖动”
单纯“超限即发”会导致一分钟内狂轰滥炸。真实农棚环境存在传感器毛刺、短时通风扰动,连续 3 次采样(间隔 10 秒)都超限才真正触发,且恢复时也要发一次“已恢复正常”通知,否则管理员不敢关手机声音。
典型判断逻辑(伪代码):
// $cacheKey = 'alarm:temp:zone_a'
// 上报时存当前值 + 时间戳
$redis->hMset($cacheKey, ['value' => $temp, 'ts' => time()]);
// 每 10 秒检查一次
if ($temp > 35.0 && $redis->hGet($cacheKey, 'count') === false) {
$redis->hIncrBy($cacheKey, 'count', 1);
} else if ($temp hDel($cacheKey, 'count');
}
// 连续 3 次超限才投递任务
if ((int)$redis->hGet($cacheKey, 'count') >= 3) {
$redis->lPush('alarm_queue', json_encode([...]));
}
最容易被忽略的是:报警任务投递后,必须记录“已触发时间戳”,否则网络抖动重连时可能重复消费同一条任务,造成重复通知。这个时间戳建议和报警 ID 一起存进 Redis 的 Hash 结构,而不是仅靠队列顺序。










