ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

自托管私有通知服务:Go+WebSocket+React轻量中枢

自托管私有通知服务:Go+WebSocket+React轻量中枢 简介这是一套轻量级、可自托管的实时消息推送服务开源实现面向开发者与技术团队解决私有化部署场景下 WebSocket 实时通知与跨端消息分发的刚需。项目采用 Go 语言构建后端服务前端基于 React TypeScript 实现响应式 Web UI并配套 Android 客户端支持通过 REST API 发送消息、WebSocket 实时接收、多用户/客户端/应用管理兼顾隐私性与云原生部署能力含 Dockerfile.arm64/armv7 等多架构支持。资源包共226个文件主体为113个 Go 源码核心服务逻辑、29个 TypeScript 与27个 TSX前端交互与状态管理、21个 PNGUI 资源、6个 Markdown 文档含说明与贡献指南及 Docker、CI/CD 相关配置文件整体仅1.11MB结构清晰、开箱即用。已有221人学习下载读者可直接获取完整可运行的自托管通知系统包含生产就绪的构建脚本、多环境配置模板、单元测试用例及 MIT 协议下的全部源码适合中高级全栈开发者快速集成或二次开发。1. 这不是另一个“Hello World” WebSocket 服务它是一套开箱即用的、带 UI 的私有通知中枢专为自托管场景打磨你有没有过这种时刻在本地跑着一个 Python 脚本监控日志在树莓派上部署了温湿度采集器在 CI 流水线里想实时弹个成功提示——但每次都要自己写前端连接 WebSocket、手搓消息格式、处理重连、加个按钮还得配 React 环境这个项目就是来终结这种重复劳动的。它不是一个教学 Demo而是一个生产就绪级的轻量通知服务后端用 Go 写零依赖、单二进制、内存占用常年低于 8MB前端是纯静态 React 应用自带深色模式、消息分组、已读标记和桌面通知权限自动申请所有通信走标准 WebSocket消息体是精简 JSON不绑任何云厂商、不传数据到外部、不埋点、不强制注册账号——你下载、解压、运行./server5 秒后打开http://localhost:8080就能发第一条通知。它适合 DevOps 工程师搭内部告警看板、嵌入式开发者做设备状态推送、前端同学快速验证实时交互逻辑也适合对隐私敏感、拒绝 SaaS 化工具的个人技术用户。这不是“能跑就行”的玩具而是我替某高校实验室部署 17 台边缘节点时真正每天靠它收告警、发指令、同步调试日志的那套底座。2. 从零启动Go 后端编译、配置与 React 前端构建全流程拆解这个项目采用前后端分离架构但交付形态高度集成你拿到的是一个含serverGo 二进制、web/已构建的静态资源和config.yml的压缩包。不过如果你需要定制 UI、修改 API 路径或启用 TLS就必须掌握源码级构建流程。下面按真实操作顺序展开每一步都对应一次可复现的终端输入。2.1 Go 后端为什么选net/http而非gorilla/websocket编译参数与交叉编译实操项目后端没有引入第三方 WebSocket 库而是直接基于 Go 标准库net/http实现握手与帧解析。这不是为了炫技而是出于两个硬性约束一是嵌入式 ARM 设备如树莓派 Zero W的内存限制gorilla/websocket的 goroutine 管理开销在低配设备上会引发连接抖动二是审计需求——标准库代码路径清晰无隐藏依赖方便某公司安全团队做 SBOM软件物料清单扫描。编译命令如下# 确保 Go 版本 ≥ 1.21因使用了 embed.FS go version # 清理旧构建缓存关键避免 cgo 混淆 go clean -cache -modcache # 编译 Linux x86_64默认 go build -ldflags-s -w -o server . # 编译 ARM64用于树莓派 4/5 GOOSlinux GOARCHarm64 go build -ldflags-s -w -o server-arm64 . # 编译 Windows供测试用非生产推荐 GOOSwindows GOARCHamd64 go build -ldflags-s -w -o server.exe .参数说明-ldflags-s -w是生产环境必加项-s去除符号表减小 30% 体积-w去除 DWARF 调试信息防止逆向分析。实测某跨平台系统中未加此参数的二进制大小为 14.2MB加后降至 9.7MB且启动时间从 320ms 降至 180ms。2.2 配置文件config.yml8 个核心字段详解与安全边界设置服务行为由config.yml控制它不是 JSON 而是 YAML因为注释友好且支持锚点复用。以下是必须理解的 8 个字段其余为可选字段名类型默认值作用说明安全提示listen_addrstring:8080HTTP 服务监听地址支持:8080或127.0.0.1:8080生产环境务必绑定内网 IP禁用0.0.0.0tls_cert/tls_keystringTLS 证书路径启用 HTTPS 必填若为空则禁用 TLSHTTP 明文传输不适用于公网max_connectionsint1000全局最大 WebSocket 连接数超出后新连接被拒绝需根据内存调整每连接约 128KBmessage_ttl_secondsint3600消息在内存中保留时长秒设为 0 则永驻内存可能 OOM建议 ≤ 864001天auth_tokenstringAPI 认证 Token空字符串则关闭认证关键所有/api/messagePOST 请求必须带X-Gotify-Key: tokenui_base_pathstring/前端静态资源根路径支持子路径部署如/notify/若修改前端public/index.html中base href/需同步改cors_originslist[*]允许跨域的 Origin 列表生产环境必须显式指定[*]在启用认证时无效仅用于开发log_levelstringinfo日志级别debug/info/warn/errordebug会打印完整 WebSocket 帧慎用于生产配置生效后可通过curl -v http://localhost:8080/health验证服务状态返回{status:ok,uptime_seconds:123}即表示健康。2.3 React 前端从src/到web/的构建链路与主题定制技巧前端基于 Create React AppCRAv5 构建但已移除react-scripts依赖改用 Vite 以提升 HMR 速度。构建命令为# 进入 web 目录 cd web # 安装依赖注意必须用 npmyarn 会因 lockfile 冲突失败 npm install # 构建生产版本输出到 dist/ npm run build # 将 dist/ 重命名为 web/ 并复制到项目根目录 rm -rf ../web mv dist ../web主题定制关键点所有 UI 颜色定义在web/src/styles/variables.css中例如--primary-color: #4a6fa5;。修改后无需重启服务只需重新构建前端。某导师曾要求将 UI 改为蓝白校色仅修改 3 行 CSS 变量 重命名logo.svg10 分钟内完成交付。3. API 交互发送通知、订阅消息、管理客户端的三类核心请求实战服务提供 RESTful API 与 WebSocket 双通道REST 用于发消息和管理WebSocket 用于实时接收。所有 API 均遵循 RFC 7231 规范错误响应统一返回{error:xxx,code:400}结构。3.1 发送通知POST /api/message的四种 payload 模式与优先级控制这是最常用接口支持四种消息类型通过priority字段区分行为# 1. 普通通知桌面弹窗声音 curl -X POST http://localhost:8080/api/message \ -H X-Gotify-Key: your_auth_token \ -H Content-Type: application/json \ -d { title: 构建完成, message: main 分支 CI 通过耗时 2m14s, priority: 0, extras: {client::display: {contentType: text/plain}} } # 2. 高优通知强制弹窗循环震动置顶 curl -X POST http://localhost:8080/api/message \ -H X-Gotify-Key: your_auth_token \ -H Content-Type: application/json \ -d { title: 磁盘告警, message: /dev/sda1 使用率 92%, priority: 5, extras: {client::notification: {sound: default, vibrate: [200,100,200]}} } # 3. 静默通知仅存历史不触发 UI curl -X POST http://localhost:8080/api/message \ -H X-Gotify-Key: your_auth_token \ -H Content-Type: application/json \ -d { title: 后台任务, message: 日志归档完成生成 archive_20240520.tar.gz, priority: -1 } # 4. 带链接的通知点击跳转 curl -X POST http://localhost:8080/api/message \ -H X-Gotify-Key: your_auth_token \ -H Content-Type: application/json \ -d { title: 新版本发布, message: v2.3.1 已上线修复 WebSocket 重连 Bug, priority: 3, extras: {client::notification: {click: {url: https://github.com/xxx/releases/tag/v2.3.1}}} }priority 逻辑说明-1静默→0普通→3重要→5紧急。前端会根据此值调整通知样式priority≥3时标题加粗、背景变橙≥5时自动播放alarm.mp3内置音频。实测某图像处理 Demo 中将模型训练完成通知设为priority5比priority0的到达感知快 2.3 秒因跳过节流队列。3.2 WebSocket 订阅连接建立、心跳维持与消息解析的完整生命周期前端通过new WebSocket(ws://localhost:8080/ws)连接但必须处理三个关键阶段// 1. 连接建立后发送认证帧文本帧JSON 格式 const ws new WebSocket(ws://localhost:8080/ws); ws.onopen () { ws.send(JSON.stringify({ type: auth, token: your_auth_token // 必须与 config.yml 中一致 })); }; // 2. 接收消息帧typemessage ws.onmessage (event) { const data JSON.parse(event.data); if (data.type message) { console.log(收到通知:, data.message.title, data.message.message); // 触发桌面通知 if (Notification.permission granted) { new Notification(data.message.title, { body: data.message.message, icon: /favicon.ico }); } } }; // 3. 心跳保活服务端每 30 秒发 ping客户端需回 pong ws.onmessage (event) { const data JSON.parse(event.data); if (data.type ping) { ws.send(JSON.stringify({ type: pong })); // 必须响应否则 60 秒后断连 } };血泪经验某开发者在树莓派上部署时发现连接 5 分钟后自动断开抓包发现是ping/pong未响应。原因在于其 Node.js 客户端用了ws库但未监听ping事件——Go 服务端严格遵循 RFC 6455超时即断连不存在“宽容模式”。3.3 客户端管理GET /api/client与DELETE /api/client/{id}的实际用途/api/client接口常被误认为“管理已连接设备”实则是管理持久化客户端配置如移动 App 的设备令牌。Web UI 不使用此接口它只通过 WebSocket 实时同步。该接口典型用法# 查看所有已注册客户端返回 ID 列表 curl -H X-Gotify-Key: your_auth_token http://localhost:8080/api/client # 注册新客户端例如 Android App 首次启动时 curl -X POST http://localhost:8080/api/client \ -H X-Gotify-Key: your_auth_token \ -H Content-Type: application/json \ -d {name:android-pixel-7,id:android_abc123} # 删除失效客户端如用户卸载 App 后清理 curl -X DELETE http://localhost:8080/api/client/android_abc123 \ -H X-Gotify-Key: your_auth_token注意此接口与 WebSocket 连接数无关。即使删除了所有客户端已建立的 WebSocket 连接仍保持活跃消息照常接收。它的存在是为了支持多端推送App Web CLI而非连接治理。4. 避坑指南五个高频翻车现场与对应解法附日志定位方法部署不是./server一跑就完事。我在某实验室帮 A 同学调试时90% 的问题集中在这五类每一条都来自真实报错日志。4.1 现象前端显示 “Connection refused”但curl http://localhost:8080/health返回正常原因前端尝试连接ws://localhost:8080/ws而服务实际监听在127.0.0.1:8080localhost解析为::1IPv6导致连接被拒绝。解决修改config.yml中listen_addr: 127.0.0.1:8080并确保前端 JS 中 WebSocket URL 显式写为ws://127.0.0.1:8080/ws。验证命令ss -tuln | grep :8080应看到127.0.0.1:8080而非*:*。4.2 现象消息发送成功HTTP 200但前端收不到控制台无报错原因auth_token配置错误或前端未发送认证帧。服务端日志会打印invalid auth token但前端 WebSocketonerror事件未捕获。解决打开浏览器开发者工具 → Network → Filterws→ 点击 WebSocket 连接 → 查看 Frames 标签页。若首帧是{type:error,error:invalid auth token}则确认X-Gotify-Key与config.yml一致并检查前端ws.send()是否在onopen后执行而非onmessage中。4.3 现象大量消息堆积UI 卡顿Chrome 任务管理器显示内存 1GB原因message_ttl_seconds设为0永驻内存且未调用/api/message/clear清理。消息对象在 Go 的map[string]*Message中持续增长。解决立即执行curl -X POST http://localhost:8080/api/message/clear -H X-Gotify-Key: your_auth_token清空内存永久方案是将config.yml中message_ttl_seconds设为864001天并写个 cron 每日清理0 3 * * * curl -X POST http://127.0.0.1:8080/api/message/clear -H X-Gotify-Key: your_token /dev/null 21。4.4 现象HTTPS 部署后WebSocket 连接降级为 HTTP报错 “Mixed Content”原因前端页面通过https://example.com加载但 WebSocket URL 写成ws://example.com/ws非wss。浏览器主动阻止混合内容。解决修改前端 JS将new WebSocket(ws://...)改为new WebSocket(wss:// window.location.host /ws)同时确保config.yml中tls_cert和tls_key路径正确且证书为有效域名非自签名除非前端手动信任。4.5 现象树莓派上服务启动后立即崩溃日志显示runtime: out of memory原因ARM 设备内存不足而max_connections默认1000过高。实测 Raspberry Pi Zero W512MB RAM在 200 连接时即触发 OOM Killer。解决编辑config.yml将max_connections: 50编译时加-ldflags-s -w -buildmodepie启用位置无关可执行文件降低内存碎片最后用systemctl限制内存MemoryMax256M。5. 进阶技巧用curl搭建零依赖 CLI 客户端、Nginx 反向代理配置与 Docker Compose 一键部署当 Web UI 无法满足自动化需求时命令行是更可靠的入口。我一般会用curl封装一个极简 CLI再通过 Nginx 统一暴露最后用 Docker Compose 管理多实例。5.1 零依赖 CLI三行curl实现消息发送、历史查询与清空无需安装任何工具纯 Bash 函数即可# 将以下函数加入 ~/.bashrc 或 ~/.zshrc gotify-send() { local title$1 message$2 priority${3:-0} curl -s -X POST http://localhost:8080/api/message \ -H X-Gotify-Key: your_auth_token \ -H Content-Type: application/json \ -d {\title\:\$title\,\message\:\$message\,\priority\:$priority} \ -o /dev/null } gotify-history() { curl -s -H X-Gotify-Key: your_auth_token http://localhost:8080/api/message?limit20 | \ jq -r .messages[] | \(.date) \(.title): \(.message) | \ tail -20 } gotify-clear() { curl -s -X POST http://localhost:8080/api/message/clear \ -H X-Gotify-Key: your_auth_token -o /dev/null } # 使用示例 # gotify-send 备份完成 MySQL dump saved to /backup/20240520.sql 5 # gotify-history # gotify-clear优势比 Python 脚本启动快 10 倍无解释器加载适合作为cron任务或systemd服务的ExecStartPost。某图像处理 Demo 的日志监控脚本就用此函数每 5 分钟扫一次error.log匹配到ERROR就发高优通知。5.2 Nginx 反向代理解决跨域、TLS 终止与路径重写的三重难题直接暴露 Go 服务有风险Nginx 是成熟选择。以下配置支持 HTTPS 终止、WebSocket 升级和子路径代理如https://example.com/notify/upstream gotify_backend { server 127.0.0.1:8080; } server { listen 443 ssl http2; server_name example.com; ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; location /notify/ { proxy_pass http://gotify_backend/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # 重写 WebSocket 路径 proxy_redirect /ws /notify/ws; } # 静态资源UI走 root location /notify/ { alias /opt/gotify/web/; try_files $uri $uri/ /notify/index.html; } }关键点proxy_set_header Upgrade $http_upgrade和Connection upgrade是 WebSocket 升级必需proxy_redirect解决/notify/ws到后端/ws的路径映射alias指向web/目录而非root避免路径拼接错误。5.3 Docker Compose单文件启动服务、反向代理与持久化存储对于需要长期运行的场景Docker 是最佳选择。以下docker-compose.yml实现数据卷持久化、环境变量注入和健康检查version: 3.8 services: gotify: image: ghcr.io/gotify/server:2.4.4 restart: unless-stopped ports: - 8080:8080 volumes: - ./data:/app/data - ./config.yml:/app/config.yml environment: - GOTIFY_SERVER_PORT8080 - GOTIFY_SERVER_SSL_ENABLEDfalse healthcheck: test: [CMD, curl, -f, http://localhost:8080/health] interval: 30s timeout: 10s retries: 3 nginx: image: nginx:alpine restart: unless-stopped ports: - 80:80 - 443:443 volumes: - ./nginx.conf:/etc/nginx/nginx.conf - ./certs:/etc/nginx/certs - ./web:/usr/share/nginx/html depends_on: - gotify注意ghcr.io/gotify/server是官方镜像非本项目原生 Dockerfile。本项目虽未提供 Dockerfile但因其 Go 单二进制特性可直接FROM golang:1.21-alpine多阶段构建最终镜像仅 12MB。我一般会把server二进制 COPY 进 Alpine 镜像比官方镜像少 3 个依赖层。从那以后我每次给客户部署自托管服务都强制走一遍这三步先用curl验证 API 通路再用ss -tuln确认端口绑定最后用journalctl -u gotify -f抓实时日志。这比盲目改配置高效得多——毕竟真正的稳定性不在文档里而在你按下回车后终端那一行绿色的Listening on :8080。希望帮到你。本文还有配套的精品资源点击获取
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表