Initial commit
This commit is contained in:
367
remote/.trae/documents/ring-notification-plan.md
Normal file
367
remote/.trae/documents/ring-notification-plan.md
Normal file
@@ -0,0 +1,367 @@
|
||||
# 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. 测试远程主机调用
|
||||
Reference in New Issue
Block a user