Files
XCDesktop/remote/.trae/documents/ring-notification-plan.md

368 lines
10 KiB
Markdown
Raw Normal View History

2026-03-08 01:34:54 +08:00
# Ring 通知功能实现计划
## 一、功能概述
### 1.1 使用场景
远程电脑执行长链任务(如 opencode、trae 等工具)时,通过 `ring.py` 脚本向主控电脑发送通知,提醒用户任务完成。
### 1.2 调用方式
```bash
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
- 提供 `/ring` POST 接口
- 调用 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`
**新增配置:**
```javascript
ring: {
port: { type: 'number', default: 3002 },
enabled: { type: 'boolean', default: true }
}
```
### 步骤 5创建 ring.py 客户端脚本
**文件:** `scripts/ring.py`
**功能:**
- 命令行参数解析
- 发送 HTTP 请求到主控电脑
- 支持配置目标地址
**使用方式:**
```bash
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
```javascript
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
```javascript
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
```python
#!/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 基本使用
```bash
# 发送简单通知
python ring.py "任务完成"
# 自定义标题
python ring.py "编译成功" --title "Build"
# 带提示音
python ring.py "下载完成" --sound
# 指定目标主机
python ring.py "远程任务完成" --host 192.168.1.100 --port 3002
```
### 5.2 在脚本中使用
```bash
# 长时间任务完成后通知
npm run build && python ring.py "Build completed" --sound
# 或者在脚本中
long_running_command
python ring.py "Command finished: $?"
```
---
## 六、配置说明
### config/default.json
```json
{
"ring": {
"enabled": true,
"port": 3002
}
}
```
---
## 七、安全考虑
1. **内网使用** - Ring Server 默认只监听内网,不暴露到公网
2. **可选认证** - 后续可添加简单 token 认证
3. **频率限制** - 防止通知轰炸
---
## 八、测试计划
1. 启动应用,验证 Ring Server 在 3002 端口启动
2. 使用 curl 测试:`curl -X POST http://localhost:3002/ring -H "Content-Type: application/json" -d '{"message":"test"}'`
3. 验证 Windows 通知弹出
4. 测试 ring.py 脚本
5. 测试远程主机调用