Files
XCDesktop/remote/.trae/documents/ring-notification-plan.md
2026-03-08 01:34:54 +08:00

368 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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. 测试远程主机调用