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

10 KiB
Raw Permalink Blame History

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
  • 提供 /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

新增配置:

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
  }
}

七、安全考虑

  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. 测试远程主机调用