公司动态

消息推送方案详解:用HTTP请求把脚本结果发到手机

📅 2026/9/3 2:10:23
消息推送方案详解:用HTTP请求把脚本结果发到手机
如果你手里有一台服务器、一个 NAS或者只是每天定时跑脚本的人大概率会遇到同一个需求任务跑完了怎么第一时间把结果推到自己手机上在本地看日志固然可行但更多时候你人不在电脑前关机睡觉时一个凌晨跑完的批处理任务根本不会有人看到。所以这次我们来看“推送消息到手机”这件事把它拆成一个可以直接落地的技术方案。先说结论这不需要非得上 App 推送平台也不需要维护一套 APNs / FCM 开发环境。对个人开发者来说最实用的路线是自建或接入现成的消息推送服务然后通过 HTTP 请求让通知中心把消息直接发到手机通知栏。整个过程在 10 分钟内可以跑通而且能覆盖定时任务、系统监控、脚本卡住、批量任务完成提醒这些常见场景。这篇内容会给你几套方案自托管型服务 ntfy、iOS 生态里轻量的 Bark、不用服务器的企业微信机器人推送以及公共推送渠道的调用方式。每套方案都会说清楚部署条件、适合谁以及核心的 API 调用示例。文章后部会补上批量任务怎么接、自动化怎么触发、哪些坑最常见。1. 消息推送方案先看清单再选边“推送消息到手机”的方式并不少但真正适合个人脚本接入的主要有几类。下面把方案按部署难度、平台、是否需要服务器、能否批量通知列成一张对比表方便先做减法。方案平台是否需自建服务部署难度推送方式适合场景ntfyAndroid / iOS / Web可选低HTTP POST / GET自托管优先、脚本监控、图片通知BarkiOS推荐自建低HTTP GET / POSTiPhone 用户、轻量提醒GotifyAndroid需要中HTTP POST想要自托管、不打算用公共服务器PushPlus微信服务号不需要极低HTTP GET国内快速接入、微信触达Server酱微信服务号不需要极低HTTP GET和 PushPlus 定位类似企业微信群机器人企业微信不需要低HTTP POST平时用企业微信、需要群内接收这几种方案里ntfy 是目前个人项目里性价比比较高的一类它支持 Android / iOS也支持网页端协议简单到一条 curl 就能发消息还支持图片、按钮交互、优先级、Markdown 内容。如果你本身就有一台长期运行的 Linux 机器完全可以把 ntfy 装上去后续所有脚本统一往一个 HTTP 接口丢通知。Bark 则比较特殊它是把 Apple 的 APNs 推送能力封装成了一个自建服务端。iOS 装上 Bark App 后会给一个推送地址后续请求这个地址就是走系统级通知中心。缺点也是明显的只适合 iPhone 用户。如果不想自己维护任何服务那么企业微信机器人或者 PushPlus 这类无需服务器的方案会更加省心。它们不需要公网 IP不需要装 Docker拿到 token 后直接 HTTP 调用就行。坏处是消息最终落在微信或企业微信内部对数据敏感项目要慎重。2. 适用场景与安全边界推送通道说白了就是把“事件”转成“手机通知”。能用的场景比大多数人想象得广服务器定时备份完成后给一个“备份成功/失败”的消息。训练脚本、OCR 批量处理、ComfyUI 出图任务跑完后通知。NAS 下载完成、磁盘空间不足、系统服务异常退出。自己写的爬虫结束、每日签到脚本成功、API 服务被频繁访问告警。家里路由器、Docker 容器状态异常时发一个紧急消息。不适合的场景也很明确真正的生产系统告警、大规模运维报警、需要保证 SLA 的消息中继不要完全依赖个人维护的推送服务。你自建服务宕机时恰好是系统最需要告警的时候这会变成一个单点。专业告警该用专业的监控平台。另一个必须提到的点是安全边界。把消息推到自己手机的前提是消息本身不能泄露给不该看到的人。公共 ntfy 服务器上 topic 本质是“可猜的 URL”一旦被他人扫描到就能直接往你的手机推送垃圾消息使用任何推送服务时也不要明文携带口令、Token、密钥等敏感内容因为你无法保证中继链路一定安全。尤其是用微信类的第三方服务时数据会经过别人服务器。凡是内部系统、涉及用户隐私、敏感会话的内容都不建议往公共推送服务里发。不管用哪一套至少要加鉴权、限制收发范围、定期换 token并且明确只是“提醒通知”不能让通知消息成为唯一的敏感信息存储点。3. 最简路线一条 curl 把消息发到手机这一节先跑通最简单的一条路径不涉及任何服务器搭建。我推荐先用 ntfy 官方公共服务器的免费 topic 做验证整个流程能让你的手机在两分钟内开始收通知。第一步手机装 ntfy App。ntfy 官方客户端支持 Android 和 iOS。装完之后不需要注册账号直接在 App 里添加一个订阅地址格式如下https://ntfy.sh/你的自定义主题名这里的主题名就是你的“频道”自己取一个不容易被猜到的随机字符串即可。比如https://ntfy.sh/hunter2-test-task-7f9a第二步在电脑或本地调试终端里跑一条 curl 命令curl -d 脚本执行完成备份文件已生成 https://ntfy.sh/hunter2-test-task-7f9a如果网络通畅手机上几秒内就会收到标题为 “ntfy.sh/hunter2-test-task-7f9a”、内容为“脚本执行完成备份文件已生成”的通知。第三步带标题、优先级和标签再发一条curl -H Title: 备份任务 \ -H Priority: high \ -H Tags: white_check_mark \ -d 系统备份已成功完成耗时 3 分 20 秒 \ https://ntfy.sh/hunter2-test-task-7f9aTags 参数里可以填 emoji 短码或其他标签会让通知栏更醒目。Priority 字段可以控制声音、震动和通知是否显示在锁屏上。这里的white_check_mark对应的是绿底对勾样式这是 ntfy 比较常用的做法。这套流程验证完成后你可以得出两个结论一台完全没有公网 IP 的电脑也能把消息推给手机前提是它能访问ntfy.sh。后续要把消息接进自己的 Python / Shell / Node 脚本只需要替换这个 URL 即可。但公共服务器的问题在于主题名是共享通道流量不可控也不能保证长期可用。所以真正要用起来更推荐自建 ntfy。4. 自建 ntfy本地部署一台消息推送服务自建 ntfy 的核心价值不是省那几块钱而是你拥有独立的消息通道主题可控、访问可控、不会受公共服务器策略影响。ntfy 服务端是一个 Go 写的单一二进制也提供 Docker 镜像。我建议用 Docker 起服务升级和回滚都方便。以下是 Docker Compose 配置示例假设服务跑在当前机器的 8080 端口version: 3 services: ntfy: image: binwiederhier/ntfy:latest container_name: ntfy command: serve environment: - TZAsia/Shanghai volumes: - /var/lib/ntfy:/var/lib/ntfy - /etc/ntfy:/etc/ntfy ports: - 8080:80 restart: unless-stopped启动命令docker-compose up -d启动后先测试本机访问是否正常curl http://127.0.0.1:8080如果一切正常这条 curl 会返回一个欢迎页面或者相应正文内容。接下来用一条不带鉴权的消息测试curl -d 本地自建 ntfy 服务已启动 http://127.0.0.1:8080/test_topic这个/test_topic就是当前服务下的一个主题。此时手机 App 里要订阅的地址不再是https://ntfy.sh/xxx而是你自建服务的地址http://你的服务器IP:8080/test_topic接着问题会暴露出来自建服务默认不限制任何人订阅和发消息只要别人知道你服务器的 IP 和端口就可以发消息进来。所以在正式使用前建议立刻把权限控制打开。先进入容器执行用户创建和访问控制命令docker exec -it ntfy ntfy user add --roleadmin admin这里会让你输入两次 admin 密码。再执行授权和生成访问令牌docker exec -it ntfy ntfy access admin * docker exec -it ntfy ntfy token add adminntfy token add admin会输出一串形如tk_xxxxxxxxxxxx的令牌这就是后续调用 API 要用的密钥。拿到 token 后给需要推送消息的脚本使用。发布消息时不再裸调而是带 Bearer Tokencurl -H Authorization: Bearer tk_xxxxxxxxxxxx \ -d 带鉴权的自建推送测试 \ http://127.0.0.1:8080/hunter2-secret-topic如果不想用 token也可以在 ntfy server 配置里启用auth-file然后为每个用户单独授权某个主题的读写权限。官方文档里对ntfy access的用法说明得很详细。这里只需记住一点自建服务只要暴露到网络就必须开鉴权。5. iOS 用户的自建方案Bark 轻量推送Bark 的思路和 ntfy 不太一样。它没有 Web 端也不做复杂的标签系统核心定位就是给 iPhone 做一个私有推送通道。Bark 的客户端会注册 iOS 的系统通知服务端拿到推送后再经过苹果的 APNs 把消息送到手机。服务端是开源项目finab/bark-server部署同样可以用 Dockerdocker run -d --name bark \ -p 8080:8080 \ -v /data/bark-data:/data \ finab/bark-serverBark 客户端在 App Store 的正式名称是 “Bark - 推送通知”。安装后第一次打开会要求输入你自己的服务器地址。如果是 Docker 部署在本机或局域网服务器可以填http://你的服务器IP:8080Bark 会给每个设备分配一个 device key。之后推送请求使用如下格式curl http://127.0.0.1:8080/你的deviceKey/标题/内容中文内容如果直接拼在路径里很容易出现编码问题。因此更推荐用--data-urlencode方式传递参数curl -G http://127.0.0.1:8080/push \ --data-urlencode device_key你的deviceKey \ --data-urlencode title脚本执行结果 \ --data-urlencode body备份成功共处理 208 个文件 \ --data-urlencode soundalarm服务端也支持/push这个统一入口设备 key 从客户端设置页面复制即可。参数sound可以指定铃声类型group参数可以把多条消息归组避免通知栏被刷屏。Bark 的体验在 iOS 上非常干脆通知延迟很低界面也够简单。但有两件事要提前想清楚第一Bark 服务端必须能被手机访问到否则推送请求过不去。如果你只在局域网内测试手机关闭 Wi-Fi 就收不到想在外面也能收到就需要给服务端配反向代理和 HTTPS或者使用带公网 IP 的服务器。第二Bark 消息体中尽量别放敏感内容。虽然服务端是你自己的但 APNs 推送链路本身并不由你控制。6. 不想装服务用企业微信群机器人接收消息如果你是用 Windows 笔记本或者不想维护 Docker也未必需要 Linux 服务器。这时可以直接用企业微信群机器人把消息推到企业微信群里手机会收到企业微信的 App 推送。操作流程分三步第一步在手机上安装企业微信登录一个能建群的企业账号。第二步创建一个只有你自己在的群然后在群设置里添加“群机器人”。添加成功后你会得到一个 webhook 地址形如https://qyapi.weixin.qq.com/cgi-bin/webhook/send?keyxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx第三步用 curl 发文本消息。例如curl https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key你的key \ -H Content-Type: application/json \ -d {msgtype:text,text:{content:定时任务执行完成}}手机上企业微信会收到一条来自“群机器人”的消息。这个流程无需公网 IP也不用下载额外的推送客户端只要企业微信能够联网即可。企业微信机器人还支持 Markdown 消息。发送时把msgtype改为markdown即可curl https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key你的key \ -H Content-Type: application/json \ -d { msgtype: markdown, markdown: { content: 任务完成 font color\info\成功/font详情见[日志](https://example.com/log) } }使用企业微信机器人的时候有几点限制需要心里有数。它的 webhook 地址固定且不带过期时间但一旦泄露群里其他人也可以用所以不要在公开仓库或公共聊天里贴 webhook。更要紧的是企业微信是组织协作工具把个人脚本产生的通知发到企业微信群属于跨场景使用建议只用于个人可控的群并遵守所在企业的使用规范。同样的思路也适用于 PushPlus 或 Server酱。它们是把消息推到微信个人号或服务号接入更傻瓜化PushPlus 的调用格式大致为curl http://www.pushplus.plus/send?token你的tokentitle通知标题content通知内容Server酱的调用格式大致为curl https://sctapi.ftqq.com/SCT你的Key.send?title通知标题desp通知内容这类服务的特点就是开箱即用注册后拿到 token 直接调用。缺点也比较一致这类第三方公众号/服务号接口并不保证长期稳定也不适合承载需要高可靠性的告警场景。可以把它们作为备选通道或者日常通知使用但不能把核心业务全押在单一免费服务上。7. 把推送接进脚本Python、Shell、Node 调用示例推送验证通过后就要考虑把它封装成一个项目内可复用的模块。与其每次粘贴 curl不如在脚本里直接调用。先看 Python 版本依赖 requests 库import requests NTFY_URL http://127.0.0.1:8080/hunter2-secret-topic def send_message(title: str, message: str, priority: str default): resp requests.post( NTFY_URL, datamessage.encode(utf-8), headers{ Title: title, Priority: priority, Authorization: Bearer tk_xxxxxxxxxxxx, }, timeout10, ) if resp.status_code not in (200, 201): print(fsend message failed, status: {resp.status_code}, body: {resp.text}) return False return True if __name__ __main__: send_message(备份任务, 今天的备份已完成, prioritydefault)这里的超时一定要设置。推送服务如果挂掉不能让它把主流程卡死。另外requests.post的data参数传的是字节串所以中文不会出问题。Shell 脚本里可以直接写一个函数#!/bin/bash NTFY_URLhttp://127.0.0.1:8080/backup-topic NTFY_TOKENtk_xxxxxxxxxxxx function send_notify() { local title$1 local message$2 curl -s -o /dev/null \ -H Authorization: Bearer ${NTFY_TOKEN} \ -H Title: ${title} \ --data-urlencode ${message} \ ${NTFY_URL} } send_notify 数据库备份完成 导出文件大小 1.2GBcurl -s -o /dev/null的作用是静默输出并丢弃 HTTP 返回体这样日志不会被一堆响应内容污染。Node.js 环境也类似不需要额外依赖const NTFY_URL http://127.0.0.1:8080/hunter2-secret-topic; const NTFY_TOKEN tk_xxxxxxxxxxxx; async function sendMessage(title, body) { const res await fetch(NTFY_URL, { method: POST, headers: { Title: title, Authorization: Bearer ${NTFY_TOKEN}, }, body, }); console.log(res.status); } sendMessage(任务完成, 生成报告完成耗时 8 秒);无论选择哪种语言核心逻辑都是一样的拼接推送地址、带鉴权头、发送标题 内容、检查返回状态。这样的封装放到项目里后以后任何任务只需要一行调用就能把消息推出来。8. 批量任务、定时任务和自动化触发方式推送最大的价值其实在“无人值守”的时候体现。比如你有一个每天晚上处理 5000 张图片的批处理任务执行完了想看一眼结果大半夜不可能蹲在电脑前。这时候只要在任务的末尾加一段推送逻辑就行。如果是脚本内部在 try/except 的 finally 或者异常分支里调用推送。拿 Python 来说import traceback def process_images(): # 业务逻辑 pass try: process_images() send_message(图片批量处理, 处理完成共生成 5000 张缩略图) except Exception: traceback.print_exc() send_message(图片批量处理, f任务失败请检查日志, priorityhigh)这样任务无论成功失败都能收到通知。失败时用 high 优先级手机会立刻弹窗提醒。如果是定时的周期任务用 cron 加推送更省心。编辑 crontabcrontab -e加一行0 2 * * * /opt/scripts/backup.sh /dev/null 21只要 backup.sh 内部已经在结尾调用推送函数凌晨两点跑完就会自动发消息到手机上。如果你要处理的是“一批批次任务”建议不要每个 task 都单独发一条通知。一个合理的模式是任务成功后先静默记录结果到结束节点时再汇总发一条。你可以写一个计数器或者维护一个队列。下面是一个简单示例import time from queue import Queue queue Queue() def push_if_idle(q): if q.get() batch_done: send_message(批量任务, f共 {count} 条任务完成耗时 {elapsed}s) # 实际用消息积压时可以攒到一定数量批量推送这里主要强调的是通知要“克制”不是每条日志都发。每天凌晨一批任务跑完如果 100 条子任务都往手机推你的手机基本会被刷屏。更好的做法是把结果写到日志文件只在整批任务的开始、成功、失败三个阶段发通知或者只在你关心的关键节点发通知。9. 富消息扩展图片、按钮与点击跳转发一条纯文字是基础能力。真正实用起来还可能需要把一张结果图、一个报表链接、或者一个“立即查看”按钮发到手机上。ntfy 支持直接推送图片。只要用 curl 上传文件并把文件内容作为请求体curl -H Authorization: Bearer tk_xxxxxxxxxxxx \ -H Filename: result.png \ --data-binary result.png \ http://127.0.0.1:8080/report-topic这条命令跑完后手机会收到一张图。对自动化截图、出图任务、看 OCR 结果都很有用。如果想发可点击的 URLntfy 也支持设置通知按钮。比如curl -H Actions: view, 打开后台, https://example.com/admin \ -d 系统异常请点击查看详情 \ http://127.0.0.1:8080/watch-topic通知到达后手机会出现一个“打开后台”的按钮点击后将跳转到对应 URL。这类交互很适合运维告警看到告警后直接在手机上点开地址处理问题不用记 IP。企业微信机器人的 Markdown 内容也自带超链接转发到手机上后点击链接能直接打开网页。把内部系统的审计日志地址或 Grafana 面板地址放进消息即可。对 Bark / PushPlus 这类简单通道来说它们虽然支持 URL 点击但能力相对弱一些。Bark 的设备 key 后接的 URL 可以用于点击跳转不过服务端对 payload 类型的支持不如 ntfy 丰富。选型时有复杂富文本和按钮交互需求优先 ntfy。10. 推送失败的常见原因与排查方法消息发不出去、手机一直收不到是最常见的一类坑。很多问题不在推送服务本身而是网络、鉴权或消息格式。这里给一个可执行的排查思路。问题现象可能原因排查方式解决方案手机收不到任何通知App 包名订阅地址填错对比订阅地址与发送地址是否完全一致保证发送路径中的 topic 与订阅地址完全一致自建服务能访问手机无法访问服务端口没对手机开放手机浏览器访问服务器 IP:端口反向代理或放行防火墙端口局域网能收4G/5G 收不到没有公网 IP 或未配置内网穿透手机断开 Wi-Fi 测试一次部署到公网服务器或加路由映射并配置 HTTPS发送返回 401token 无效或过期服务端执行ntfy token list查看重新生成 token 并更新脚本发送返回 403用户对主题没有 push 权限执行ntfy access查看权限授权用户读写对应主题企业微信机器人发送失败JSON 格式不正确或 key 错误复制响应内容检查错误码用官方文档对照 JSON 结构推送到了但没声音手机系统通知权限关闭在系统设置里检查通知、声音权限重新授权并检查 ntfy 优先级延迟非常高服务端在海外或网络链路慢看接收延迟和发送端网络延迟尽量挑选国内可达的服务渠道批量任务发完消息后就卡住推送调用没有设置超时抓包看请求是否长时间未返回给 HTTP 请求加 timeout排查时有一个很实用的原则把问题拆成“发送端能否调到服务”和“订阅端能否收到 App 通知”两段。先确认 curl 返回 200再确认 App 已订阅这个主题。哪怕没有任何日志系统一条curl -v打出完整请求就能快速定位大部分问题。11. 最佳实践哪些配置值得长期坚持推送通道用顺了以后很容易陷入一个误区“多套服务全发一遍”。实际上真正的稳定方案不是渠道多而是有主通道、有降级通道。我建议这样设计通知体系主通道选自建 ntfy推荐长期运行在 Docker 中。备选通道配置一个企业微信机器人或者 PushPlus。每个脚本都设置超时推送失败不影响主流程。所有 token 放到单独的配置文件或环境变量不要写死在代码里。发布脚本之前用真实消息在目标手机上自测一次。关于公网暴露的问题要单独再强调一次。自建 ntfy 裸奔到公网可能被扫描、刷消息甚至消耗流量。至少要做到接入层加反向代理开启auth-file给服务配好管理员密码。如果你的服务只在内网测试就不要把端口无条件映射到公网。消息内容方面建议明确区分“通知”和“日志”。通知里写能看懂的一句话详细拼接过程留给日志文件。你可以在脚本里这样设计send_message( 批量任务完成, f共 {success} 成功{failed} 失败耗时 {elapsed_seconds}s详见 /var/log/batch.log )这里没有暴露敏感信息也没有让日志主体挤在通知里。手机收到的是一句摘要有事了再去找日志。还有一个很容易被忽略的点推送服务本身的监控。ntfy 自己也会挂所以比较稳的做法是写一个看门狗脚本定期向 ntfy 发一条心跳超过三分钟没有正常响应就通过备用通道告警或者干脆直接发邮件。这种双通道设计才够稳。如果任务很关键在推送成功但实际业务执行失败的情况下也要做好区分。有的项目只在send_message返回异常时打印却不关心任务本身的 exit code结果消息每次都发成功但任务根本没有产出。所以推送函数只负责传达结果判断成功与失败还要看业务日志和返回码不要把推送结果当成执行结果。12. 小结下一步从哪里开始不管你的手机是 Android 还是 iPhone第一件值得做的事是用 ntfy 的公共服务器跑通一条 curl。这一条命令能同时验证手机端 App 是否正常、消息通道是否可用、你的网络是否能访问服务。三分钟内跑通这一步后面换自建服务器、加鉴权、加富文本都只是配置层面的问题。接下来最值得投入的是把推送封装成一个脚本模块。把 Python / Shell / Node 里的发送逻辑抽出来加好超时和返回码判断然后挂到一个真实任务末尾观察一两天。最容易踩的坑基本都是主题权限和网络可达性自建服务忘记加鉴权、公网端口未做安全限制或者手机和服务器不在同一网络却以为能直连。如果你打算照着部署建议先用内网局域网跑通再考虑加公网访问和 HTTPS。最后补充一句这篇文章选的都是相对通用的推送服务方案服务版本和接口细节会随时间变化真正接入前以对应项目官方文档为准不要在公网直接暴露无鉴权的推送订阅地址。