10 KiB
10 KiB
Ring 通知功能实现计划
一、功能概述
1.1 使用场景
远程电脑执行长链任务(如 opencode、trae 等工具)时,通过 ring.py 脚本向主控电脑发送通知,提醒用户任务完成。
1.2 调用方式
python ring.py "任务完成"
python ring.py "编译成功" --title "Build"
python ring.py "下载完成" --sound
1.3 架构设计
┌─────────────────────────────────────────────────────────────────┐
│ 主控电脑 (被控端) │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ Ring Server (端口 3002) ││
│ │ - HTTP POST /ring ││
│ │ - 接收通知请求 ││
│ │ - 触发系统通知 (Windows Toast) ││
│ │ - 可选:播放提示音 ││
│ └─────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────┘
▲
│ HTTP POST
│
┌─────────────────────────────┴───────────────────────────────────┐
│ 远程电脑 (控制端) │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ ring.py ││
│ │ - 发送 HTTP 请求到主控电脑 ││
│ │ - 支持自定义消息、标题、提示音 ││
│ └─────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────┘
二、实现步骤
步骤 1:创建 RingService 服务
文件: src/services/ring/RingService.js
功能:
- 使用 Windows Toast 通知 API
- 支持自定义标题和消息
- 可选播放提示音
实现方式:
- 使用 PowerShell 调用 Windows Toast 通知
- 或使用
node-notifier库
步骤 2:创建 Ring Server
文件: src/server/RingServer.js
功能:
- 独立的 HTTP 服务器,监听端口 3002
- 提供
/ringPOST 接口 - 调用 RingService 发送通知
API 设计:
POST /ring
Content-Type: application/json
{
"message": "任务完成",
"title": "Ring", // 可选,默认 "Ring"
"sound": true // 可选,默认 false
}
步骤 3:集成到 App.js
修改文件: src/core/App.js
修改内容:
- 注册 RingService
- 注册 RingServer
- 启动时同时启动 Ring Server
步骤 4:更新配置
修改文件: src/config/schema.js
新增配置:
ring: {
port: { type: 'number', default: 3002 },
enabled: { type: 'boolean', default: true }
}
步骤 5:创建 ring.py 客户端脚本
文件: scripts/ring.py
功能:
- 命令行参数解析
- 发送 HTTP 请求到主控电脑
- 支持配置目标地址
使用方式:
python ring.py "消息内容"
python ring.py "消息" --title "标题" --sound
python ring.py "消息" --host 192.168.1.100 --port 3002
三、文件清单
新增文件
| 文件路径 | 说明 |
|---|---|
src/services/ring/RingService.js |
通知服务,调用系统通知 |
src/services/ring/index.js |
服务导出 |
src/server/RingServer.js |
独立 HTTP 服务器 |
scripts/ring.py |
Python 客户端脚本 |
修改文件
| 文件路径 | 修改内容 |
|---|---|
src/core/App.js |
注册并启动 Ring 服务 |
src/config/schema.js |
添加 ring 配置项 |
config/default.json |
添加 ring 默认配置 |
四、详细实现
4.1 RingService.js
const { spawn } = require('child_process');
const logger = require('../../utils/logger');
class RingService {
async notify({ message, title = 'Ring', sound = false }) {
// 使用 PowerShell 发送 Windows Toast 通知
const psScript = `
[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
[Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] | Out-Null
$template = @"
<toast duration="short">
<visual>
<binding template="ToastText02">
<text id="1">${title}</text>
<text id="2">${message}</text>
</binding>
</visual>
${sound ? '<audio src="ms-winsoundevent:Notification.Default" loop="false"/>' : '<audio silent="true"/>'}
</toast>
"@
$xml = New-Object Windows.Data.Xml.Dom.XmlDocument
$xml.LoadXml($template)
$toast = [Windows.UI.Notifications.ToastNotification]::new($xml)
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier("Ring").Show($toast)
`;
return new Promise((resolve, reject) => {
const ps = spawn('powershell', ['-NoProfile', '-Command', psScript]);
ps.on('close', (code) => {
if (code === 0) {
logger.info('Ring notification sent', { title, message });
resolve(true);
} else {
resolve(false);
}
});
ps.on('error', (err) => {
logger.error('Ring notification failed', { error: err.message });
resolve(false);
});
});
}
}
module.exports = RingService;
4.2 RingServer.js
const http = require('http');
const logger = require('../utils/logger');
class RingServer {
constructor(config = {}) {
this.port = config.port || 3002;
this.host = config.host || '0.0.0.0';
this.server = null;
this.ringService = config.ringService;
}
start() {
this.server = http.createServer((req, res) => {
if (req.method === 'POST' && req.url === '/ring') {
this.handleRing(req, res);
} else {
res.writeHead(404);
res.end('Not Found');
}
});
return new Promise((resolve, reject) => {
this.server.listen(this.port, this.host, () => {
logger.info('Ring server started', { port: this.port });
resolve({ port: this.port, host: this.host });
});
this.server.on('error', reject);
});
}
async handleRing(req, res) {
let body = '';
req.on('data', chunk => body += chunk);
req.on('end', async () => {
try {
const data = JSON.parse(body);
await this.ringService.notify(data);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ success: true }));
} catch (err) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: err.message }));
}
});
}
stop() {
return new Promise((resolve) => {
if (this.server) {
this.server.close(() => {
logger.info('Ring server stopped');
resolve();
});
} else {
resolve();
}
});
}
}
module.exports = RingServer;
4.3 ring.py
#!/usr/bin/env python3
import argparse
import requests
import sys
DEFAULT_HOST = "localhost"
DEFAULT_PORT = 3002
def main():
parser = argparse.ArgumentParser(description='Send notification to remote computer')
parser.add_argument('message', help='Notification message')
parser.add_argument('--title', '-t', default='Ring', help='Notification title')
parser.add_argument('--sound', '-s', action='store_true', help='Play notification sound')
parser.add_argument('--host', default=DEFAULT_HOST, help='Target host')
parser.add_argument('--port', type=int, default=DEFAULT_PORT, help='Target port')
args = parser.parse_args()
try:
response = requests.post(
f"http://{args.host}:{args.port}/ring",
json={
"message": args.message,
"title": args.title,
"sound": args.sound
},
timeout=5
)
if response.status_code == 200:
print("Notification sent successfully")
else:
print(f"Failed to send notification: {response.text}")
sys.exit(1)
except Exception as e:
print(f"Error: {e}")
sys.exit(1)
if __name__ == "__main__":
main()
五、使用示例
5.1 基本使用
# 发送简单通知
python ring.py "任务完成"
# 自定义标题
python ring.py "编译成功" --title "Build"
# 带提示音
python ring.py "下载完成" --sound
# 指定目标主机
python ring.py "远程任务完成" --host 192.168.1.100 --port 3002
5.2 在脚本中使用
# 长时间任务完成后通知
npm run build && python ring.py "Build completed" --sound
# 或者在脚本中
long_running_command
python ring.py "Command finished: $?"
六、配置说明
config/default.json
{
"ring": {
"enabled": true,
"port": 3002
}
}
七、安全考虑
- 内网使用 - Ring Server 默认只监听内网,不暴露到公网
- 可选认证 - 后续可添加简单 token 认证
- 频率限制 - 防止通知轰炸
八、测试计划
- 启动应用,验证 Ring Server 在 3002 端口启动
- 使用 curl 测试:
curl -X POST http://localhost:3002/ring -H "Content-Type: application/json" -d '{"message":"test"}' - 验证 Windows 通知弹出
- 测试 ring.py 脚本
- 测试远程主机调用