From ed54296f620e8241c90cf1e99ce4d6feae29008f Mon Sep 17 00:00:00 2001 From: zqm Date: Fri, 20 Mar 2026 16:51:31 +0800 Subject: [PATCH] taskkill /F /IM nginx.exe --- Claw/.trae/rules/项目规则.md | 181 ++++- Claw/Server/SmartClaw/Cargo.toml | 10 +- Claw/Server/SmartClaw/src/main.rs | 125 ++- Claw/Server/SmartClaw/src/websocket_client.rs | 73 +- Claw/Server/gateway/Cargo.toml | 7 +- Claw/Server/gateway/src/communication.rs | 528 +------------ Claw/Server/gateway/src/main.rs | 738 ++++++++++++++++-- Claw/docs/nginx.conf | 42 +- Claw/docs/nginx_backup.conf | 180 +++++ Claw/docs/nginx_bakup.conf | 59 -- Claw/docs/可行性方案.md | 13 +- 11 files changed, 1190 insertions(+), 766 deletions(-) create mode 100644 Claw/docs/nginx_backup.conf delete mode 100644 Claw/docs/nginx_bakup.conf diff --git a/Claw/.trae/rules/项目规则.md b/Claw/.trae/rules/项目规则.md index 34bf1bb..394fa9e 100644 --- a/Claw/.trae/rules/项目规则.md +++ b/Claw/.trae/rules/项目规则.md @@ -78,9 +78,16 @@ sequenceDiagram 由于SmartClaw服务在内网中,外网不能访问它,采用**WebSocket反向连接方案**: 1. **SmartClaw服务主动连接**:服务器B启动时,主动WebSocket连接到服务器A(wss://pactgo.cn/ws/control) -2. **长连接保持**:维持持久WebSocket连接,支持心跳检测和断线重连 -3. **双向通信**:服务器A通过WebSocket发送任务,服务器B处理完成后回传结果 -4. **零配置**:使用Embedded-Redis(仅网关服务启动),无需独立Redis服务 +2. **单一连接**:服务器B与服务器A之间只建立一个WebSocket连接,不需要列表管理 +3. **长连接保持**:维持持久WebSocket连接,支持心跳检测和断线重连 +4. **双向通信**:服务器A通过WebSocket发送任务,服务器B处理完成后回传结果 +5. **零配置**:使用Embedded-Redis(仅网关服务启动),无需独立Redis服务 + +### 4.2.1 WebSocket连接管理 + +- **服务器B与服务器A**:只建立一个WebSocket连接,无需列表管理 +- **其他设备与服务器A**:通过WebSocket连接到服务器A的 `/ws/task` 路径,需要使用连接列表进行管理 +- **连接管理**:使用 `ConnectionManager` 结构体管理设备与服务器A的WebSocket连接,支持多用户多设备场景 ### 4.3 SmartClaw服务与LMStudio通信 @@ -191,12 +198,78 @@ Claw/ * 编写部署文档 * 编写使用文档 -## 8. 注意事项 +## 8. 企业微信配置详情 + +### 8.1 企业微信应用配置 + +**应用基本信息:** +- 应用名称:智控未来 +- 应用主页: https://pactgo.cn +- 回调地址: https://pactgo.cn/wecom +- 使用CorpID和Secret进行认证 + +**回调地址说明:** +- 企业微信会将用户消息和事件推送到此地址 +- 必须是HTTPS协议(企业微信强制要求) +- 路径为 `/wecom`,对应网关服务的 `/api/v1/wecom` 路由 +- 支持GET(验证)和POST(消息推送)请求 + +### 8.2 网络通信路径 + +**完整的消息流转路径:** +``` +用户发送消息 → 企业微信 → https://pactgo.cn/wecom → Nginx → 网关服务(/api/v1/wecom) → 处理消息 +``` + +**路径映射关系:** +| 外部URL | Nginx Location | 代理目标 | 网关路由 | 处理函数 | +|---------|----------------|----------|----------|----------| +| `https://pactgo.cn/wecom` | `/wecom` | `/wecom` | `/wecom` | `handle_wechat_callback` | + +### 8.3 消息处理流程 + +1. **URL验证(GET请求):** + - 企业微信首次配置时会发送GET请求进行URL验证 + - 网关服务需要正确响应验证参数 + - 验证通过后企业微信才会发送实际消息 + +2. **消息推送(POST请求):** + - 用户在企业微信应用中发送消息 + - 企业微信将消息POST到回调地址 + - 网关服务接收并处理消息 + - 返回正确响应给企业微信 + +### 8.4 路径一致性检查 + +**企业微信配置路径:** +- 回调地址:`https://pactgo.cn/wecom` + +**Nginx代理配置:** +```nginx +location /wecom { + proxy_pass http://127.0.0.1:8000/api/v1/wecom; +} +``` + +**网关服务路由:** +```rust +.route("/wecom", web::post().to(handle_wechat_callback)) +``` + +**验证结果:** ✅ 路径完全一致 + +## 9. 注意事项 + 1. **网络通信**: * 服务器B在内网中,使用WebSocket反向连接方案:SmartClaw服务主动连接网关服务的WebSocket * 维持持久WebSocket连接,支持心跳检测和断线重连 * 使用Embedded-Redis进行多用户多设备状态管理 + * **重要:企业微信回调地址必须保持一致** + - 企业微信配置:`https://pactgo.cn/wecom` + - Nginx代理:`/wecom` → `/api/v1/wecom` + - 网关服务路由:`/wecom` + - 确保路径完全一致,否则消息无法到达网关服务 2. **安全考虑**: * 所有通信使用HTTPS加密(企业微信强制要求) @@ -222,15 +295,99 @@ Claw/ * 服务器A:Windows Server 2012系统 * 服务器B:Windows Server 2012系统,安装LMStudio -## 9. 开发计划 +## 9. 部署配置详情 -### 9.1 阶段一:基础设施搭建 +### 9.1 企业微信应用配置 + +**必须配置的参数:** +- **应用名称**:智控未来 +- **应用主页**:https://pactgo.cn +- **回调地址**:https://pactgo.cn/wecom +- **CorpID**:企业微信的企业ID +- **Secret**:应用的密钥 +- **Token**:用于签名验证 +- **EncodingAESKey**:消息加解密密钥 + +**回调地址验证:** +- 企业微信会发送GET请求到 `https://pactgo.cn/wecom` 进行URL验证 +- 网关服务必须正确响应验证参数 +- 验证通过后才会开始推送消息 + +### 9.2 Nginx配置验证 + +**关键配置项:** +```nginx +server { + listen 443 ssl; + server_name pactgo.cn; + + # 企业微信回调 - 必须正确配置 + location /wecom { + proxy_pass http://127.0.0.1:8000/api/v1/wecom; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # 企业微信回调特殊处理 + proxy_read_timeout 30s; + proxy_connect_timeout 10s; + } +} +``` + +**配置检查清单:** +- [ ] SSL证书有效且未过期 +- [ ] 443端口已开放 +- [ ] `/wecom` 路径正确代理到网关服务 +- [ ] 超时设置合理(建议30秒) + +### 9.3 网关服务配置 + +**必须实现的路由:** +```rust +// 企业微信回调 - 必须匹配企业微信配置 +.route("/wecom", web::post().to(handle_wechat_callback)) + +// 其他路由 +.route("/wechat/miniprogram/callback", web::post().to(handle_wechat_miniprogram_callback)) +.route("/ws/control", web::get().to(websocket_handler)) +``` + +**处理函数要求:** +- 必须实现GET方法用于URL验证 +- 必须实现POST方法用于消息处理 +- 必须正确验证签名 +- 必须返回正确的响应格式 + +### 9.4 路径一致性检查 + +**完整路径映射:** +| 外部URL | Nginx Location | 代理目标 | 网关路由 | 处理函数 | +|---------|----------------|----------|----------|----------| +| `https://pactgo.cn/wecom` | `/wecom` | `/api/v1/wecom` | `/wecom` | `handle_wechat_callback` | +| `https://pactgo.cn/api/v1/wechat/miniprogram/callback` | `/api/v1/wechat/miniprogram/callback` | `/api/v1/wechat/miniprogram/callback` | `/wechat/miniprogram/callback` | `handle_wechat_miniprogram_callback` | + +**验证命令:** +```bash +# 测试企业微信回调地址 +curl -X GET "https://pactgo.cn/wecom?msg_signature=xxx×tamp=xxx&nonce=xxx&echostr=xxx" + +# 测试微信小程序回调地址 +curl -X POST "https://pactgo.cn/api/v1/wechat/miniprogram/callback" \ + -H "Content-Type: application/json" \ + -d '{"code":"test_code"}' +``` + +## 10. 开发计划 + +### 10.1 阶段一:基础设施搭建 - 配置服务器环境 - 安装必要的软件和依赖 - 搭建开发环境 -### 9.2 阶段二:后端开发 +### 10.2 阶段二:后端开发 - 开发网关服务(服务器A)的Web服务(Embedded-Redis多用户管理) - 开发SmartClaw服务(服务器B)的Web服务(WebSocket客户端) @@ -238,19 +395,23 @@ Claw/ - 开发智能控制核心逻辑 - 集成LMStudio API(SSE流式响应) -### 9.3 阶段三:前端开发 +### 10.3 阶段三:前端开发 - 开发微信小程序(官方原生技术栈:WXML+WXSS+JS) - 配置企业微信应用(企业微信JS-SDK) + - 应用名称:智控未来 + - 应用主页: https://pactgo.cn + - 回调地址: https://pactgo.cn/wecom + - 使用CorpID和Secret进行认证 -### 9.4 阶段四:测试与部署 +### 10.4 阶段四:测试与部署 - 功能测试 - 性能测试 - 安全测试 - 部署到生产环境 -### 9.5 阶段五:运维与监控 +### 10.5 阶段五:运维与监控 - 配置监控系统 - 制定运维计划 diff --git a/Claw/Server/SmartClaw/Cargo.toml b/Claw/Server/SmartClaw/Cargo.toml index 56478a1..dccbdfe 100644 --- a/Claw/Server/SmartClaw/Cargo.toml +++ b/Claw/Server/SmartClaw/Cargo.toml @@ -8,12 +8,16 @@ actix-web = "^4.0" tokio = { version = "^1.0", features = ["full"] } serde = { version = "^1.0", features = ["derive"] } serde_json = "^1.0" -reqwest = { version = "^0.11", features = ["json"] } +reqwest = { version = "^0.11", features = ["json", "native-tls"] } # mini-redis = "0.4" # 使用自定义嵌入式Redis实现 -tokio-tungstenite = "^0.18" +tokio-tungstenite = { version = "0.18", features = ["native-tls"] } +native-tls = "0.2" shared = { path = "../shared" } env_logger = "^0.10" -awc = { version = "^3.0", features = ["rustls"] } +awc = { version = "^3.0" } futures-util = "^0.3" chrono = "^0.4" +url = "^2.0" +base64 = "^0.22" +rand = "^0.8" # heed = "^0.20" # 暂时移除,后续实现HeedDB功能 diff --git a/Claw/Server/SmartClaw/src/main.rs b/Claw/Server/SmartClaw/src/main.rs index 82b9425..e9aaa86 100644 --- a/Claw/Server/SmartClaw/src/main.rs +++ b/Claw/Server/SmartClaw/src/main.rs @@ -265,11 +265,11 @@ async fn websocket_disconnect(ws_manager: web::Data) -> // 获取客户端实例 let client = ws_manager.get_client(); - if client.is_connected() { + if client.is_connected().await { println!("🔗 WebSocket客户端当前已连接,准备断开..."); // 断开连接 - client.disconnect(); + client.disconnect().await; println!("✅ WebSocket客户端已断开连接"); @@ -294,7 +294,7 @@ async fn websocket_stop(ws_manager: web::Data) -> impl R println!("🛑 收到WebSocket停止管理器请求"); // 停止管理器 - ws_manager.stop(); + ws_manager.stop().await; println!("✅ WebSocket客户端管理器已停止"); @@ -319,56 +319,57 @@ async fn websocket_handler(req: HttpRequest, _stream: web::Payload) -> impl Resp let client = ws_manager.get_client(); // 检查连接状态(用于演示) - if client.is_connected() { - println!("🔗 WebSocket客户端已连接"); - - // 测试发送消息(用于演示) - let test_message = serde_json::json!({ - "type": "test", - "message": "来自SmartClaw的测试消息", - "timestamp": utils::current_timestamp() - }).to_string(); - - match client.send_message(test_message).await { - Ok(_) => { - println!("✅ 测试消息发送成功"); - }, - Err(e) => { - println!("⚠️ 测试消息发送失败: {}", e); + if client.is_connected().await { + println!("🔗 WebSocket客户端已连接"); + + // 测试发送消息(用于演示) + let test_message = serde_json::json!({ + "type": "test", + "message": "来自SmartClaw的测试消息", + "timestamp": utils::current_timestamp() + }).to_string(); + + match client.send_message(test_message).await { + Ok(_) => { + println!("✅ 测试消息发送成功"); + }, + Err(e) => { + println!("⚠️ 测试消息发送失败: {}", e); + } } - } - - // 测试发送任务响应(用于演示) - let test_response = shared::TaskResponse { - success: true, - message: "测试响应".to_string(), - task_id: Some("test_task_123".to_string()), - result: Some(serde_json::json!({ - "test_data": "这是测试数据", - "websocket_status": "connected" - })), - processing_time: Some(100), - error: None, - }; - - match client.send_task_response(test_response).await { - Ok(_) => { - println!("✅ 测试任务响应发送成功"); - }, - Err(e) => { - println!("⚠️ 测试任务响应发送失败: {}", e); + + // 测试发送任务响应(用于演示) + let test_response = shared::TaskResponse { + success: true, + message: "测试响应".to_string(), + task_id: Some("test_task_123".to_string()), + result: Some(serde_json::json!({ + "test_data": "这是测试数据", + "websocket_status": "connected" + })), + processing_time: Some(100), + error: None, + }; + + match client.send_task_response(test_response).await { + Ok(_) => { + println!("✅ 测试任务响应发送成功"); + }, + Err(e) => { + println!("⚠️ 测试任务响应发送失败: {}", e); + } } + } else { + println!("⚠️ WebSocket客户端未连接"); } - } else { - println!("⚠️ WebSocket客户端未连接"); - } // 暂时返回不支持的消息 - HttpResponse::NotImplemented().json(serde_json::json!({ - "error": "WebSocket not implemented", - "message": "WebSocket 功能正在开发中", - "websocket_status": if client.is_connected() { "connected" } else { "disconnected" } - })) + let status = if client.is_connected().await { "connected" } else { "disconnected" }; + HttpResponse::NotImplemented().json(serde_json::json!({ + "error": "WebSocket not implemented", + "message": "WebSocket 功能正在开发中", + "websocket_status": status + })) } /// 任务队列状态 @@ -397,7 +398,7 @@ async fn main() -> std::io::Result<()> { let bind_address = format!("0.0.0.0:{}", port); // 获取网关服务地址 - let gateway_url = env::var("GATEWAY_URL").unwrap_or_else(|_| "http://localhost:8000".to_string()); + let gateway_url = env::var("GATEWAY_URL").unwrap_or_else(|_| "https://pactgo.cn".to_string()); println!("🚀 SmartClaw 服务启动中..."); println!("📍 绑定地址: {}", bind_address); @@ -411,20 +412,17 @@ async fn main() -> std::io::Result<()> { // 启动 WebSocket 连接(在后台任务中) let ws_manager_for_spawn = ws_manager.clone(); - std::thread::spawn(move || { - let rt = tokio::runtime::Runtime::new().unwrap(); - rt.block_on(async { - println!("🔄 正在启动 WebSocket 客户端连接..."); - match ws_manager_for_spawn.start().await { - Ok(_) => { - println!("✅ WebSocket 客户端连接成功"); - } - Err(e) => { - println!("❌ WebSocket 客户端连接失败: {}", e); - // 这里可以添加重试逻辑 - } + tokio::spawn(async move { + println!("🔄 正在启动 WebSocket 客户端连接..."); + match ws_manager_for_spawn.start().await { + Ok(_) => { + println!("✅ WebSocket 客户端连接成功"); } - }); + Err(e) => { + println!("❌ WebSocket 客户端连接失败: {}", e); + // 这里可以添加重试逻辑 + } + } }); let ws_manager_for_server = ws_manager.clone(); @@ -452,6 +450,7 @@ async fn main() -> std::io::Result<()> { .route("/websocket/stop", web::post().to(websocket_stop)) ) }) + .workers(1) // 设置为1个worker .bind(&bind_address)? .run(); @@ -491,7 +490,7 @@ async fn graceful_shutdown(ws_manager: WebSocketClientManager) { println!("🛑 开始优雅关闭..."); // 停止WebSocket客户端 - ws_manager.stop(); + ws_manager.stop().await; println!("🔌 WebSocket客户端已停止"); // 等待一段时间确保所有连接都已关闭 diff --git a/Claw/Server/SmartClaw/src/websocket_client.rs b/Claw/Server/SmartClaw/src/websocket_client.rs index c30a2fd..20bef19 100644 --- a/Claw/Server/SmartClaw/src/websocket_client.rs +++ b/Claw/Server/SmartClaw/src/websocket_client.rs @@ -1,16 +1,19 @@ -use tokio_tungstenite::{connect_async, tungstenite::Message}; +use tokio_tungstenite::{connect_async, tungstenite::Message, tungstenite::http::Request}; use futures_util::{SinkExt, StreamExt}; use serde_json::json; use std::sync::Arc; -use tokio::sync::mpsc; +use tokio::sync::{mpsc, Mutex}; use tokio::time::{interval, Duration}; use shared::{TaskRequest, TaskResponse}; +use base64::Engine as _; +use base64::engine::general_purpose; +use rand::Rng; /// WebSocket 客户端连接管理器 pub struct WebSocketClient { gateway_url: String, - sender: Arc>>>, - is_connected: Arc>, + sender: Arc>>>, + is_connected: Arc>, } impl WebSocketClient { @@ -18,8 +21,8 @@ impl WebSocketClient { pub fn new(gateway_url: String) -> Self { Self { gateway_url, - sender: Arc::new(std::sync::Mutex::new(None)), - is_connected: Arc::new(std::sync::Mutex::new(false)), + sender: Arc::new(Mutex::new(None)), + is_connected: Arc::new(Mutex::new(false)), } } @@ -27,22 +30,46 @@ impl WebSocketClient { pub async fn connect(&self) -> Result<(), Box> { println!("🔌 正在连接到网关服务: {}", self.gateway_url); - let ws_url = format!("{}/ws", self.gateway_url.replace("http://", "ws://").replace("https://", "wss://")); + let ws_url = format!("{}/api/v1/ws/control", self.gateway_url.replace("http://", "ws://").replace("https://", "wss://")); println!("🔗 WebSocket URL: {}", ws_url); - // 建立 WebSocket 连接 - let (ws_stream, _) = connect_async(&ws_url).await?; + // 生成随机的 Sec-WebSocket-Key + let mut key = [0u8; 16]; + rand::thread_rng().fill(&mut key); + let sec_websocket_key = general_purpose::STANDARD.encode(&key); + + // 建立 WebSocket 连接,添加API密钥和标准 WebSocket 握手头 + let request = Request::builder() + .uri(&ws_url) + .header("Host", "pactgo.cn") + .header("X-API-Key", "claw_secret_key") + .header("Upgrade", "websocket") + .header("Connection", "Upgrade") + .header("Sec-WebSocket-Key", sec_websocket_key) + .header("Sec-WebSocket-Version", "13") + .body(())?; + + println!("🔗 正在建立WebSocket连接..."); + println!("📋 请求URL: {}", ws_url); + println!("📋 HTTP版本: {:?}", request.version()); + println!("📋 请求头:"); + for (name, value) in request.headers() { + println!(" {}: {:?}", name, value); + } + + let (ws_stream, response) = connect_async(request).await?; println!("✅ WebSocket 连接建立"); + println!("📋 响应状态: {}", response.status()); // 设置连接状态 - *self.is_connected.lock().unwrap() = true; + *self.is_connected.lock().await = true; // 分割流 let (mut write, mut read) = ws_stream.split(); // 创建消息通道 let (tx, mut rx) = mpsc::channel::(100); - *self.sender.lock().unwrap() = Some(tx); + *self.sender.lock().await = Some(tx); // 启动消息发送循环 let _write_handle = tokio::spawn(async move { @@ -67,13 +94,13 @@ impl WebSocketClient { } Ok(Message::Close(_)) => { println!("🔚 收到关闭消息"); - *is_connected_clone.lock().unwrap() = false; + *is_connected_clone.lock().await = false; break; } Ok(_) => {} Err(e) => { println!("❌ 接收消息错误: {}", e); - *is_connected_clone.lock().unwrap() = false; + *is_connected_clone.lock().await = false; break; } } @@ -89,7 +116,7 @@ impl WebSocketClient { loop { heartbeat_interval.tick().await; - let connected = *is_connected.lock().unwrap(); + let connected = *is_connected.lock().await; if !connected { println!("💔 心跳检测到连接已断开"); break; @@ -115,7 +142,7 @@ impl WebSocketClient { "timestamp": chrono::Utc::now().timestamp() }).to_string(); - if let Some(sender) = &*self.sender.lock().unwrap() { + if let Some(sender) = &*self.sender.lock().await { let _ = sender.send(connect_msg).await; } @@ -150,7 +177,7 @@ impl WebSocketClient { /// 发送消息 pub async fn send_message(&self, message: String) -> Result<(), Box> { - if let Some(sender) = &*self.sender.lock().unwrap() { + if let Some(sender) = &*self.sender.lock().await { sender.send(message).await.map_err(|e| Box::new(e) as Box)?; Ok(()) } else { @@ -171,14 +198,14 @@ impl WebSocketClient { } /// 检查连接状态 - pub fn is_connected(&self) -> bool { - *self.is_connected.lock().unwrap() + pub async fn is_connected(&self) -> bool { + *self.is_connected.lock().await } /// 断开连接 - pub fn disconnect(&self) { - *self.is_connected.lock().unwrap() = false; - *self.sender.lock().unwrap() = None; + pub async fn disconnect(&self) { + *self.is_connected.lock().await = false; + *self.sender.lock().await = None; println!("🔌 WebSocket 连接已断开"); } } @@ -208,7 +235,7 @@ impl WebSocketClientManager { } /// 停止客户端 - pub fn stop(&self) { - self.client.disconnect(); + pub async fn stop(&self) { + self.client.disconnect().await; } } \ No newline at end of file diff --git a/Claw/Server/gateway/Cargo.toml b/Claw/Server/gateway/Cargo.toml index 5362631..4dbf17d 100644 --- a/Claw/Server/gateway/Cargo.toml +++ b/Claw/Server/gateway/Cargo.toml @@ -17,7 +17,12 @@ env_logger = "^0.10" sha1 = "^0.10" sha2 = "^0.10" hex = "^0.4" +urlencoding = "^2.1" +base64 = "^0.21" +aes = "^0.8" +cbc = "^0.1" +cipher = "^0.4" actix = "^0.13" -actix-web-actors = "^4.0" +actix-ws = "^0.2" futures = "^0.3" uuid = { version = "^1.0", features = ["v4"] } diff --git a/Claw/Server/gateway/src/communication.rs b/Claw/Server/gateway/src/communication.rs index bba2679..8308968 100644 --- a/Claw/Server/gateway/src/communication.rs +++ b/Claw/Server/gateway/src/communication.rs @@ -1,18 +1,9 @@ -use actix::prelude::*; -use actix_web_actors::ws; use std::time::{Duration, Instant}; use std::sync::Arc; -use tokio::sync::{RwLock, mpsc}; +use tokio::sync::RwLock; use serde_json::json; use uuid::Uuid; -use actix::ResponseFuture; -/// 测试消息发送的消息类型 -#[derive(Message)] -#[rtype(result = "Result")] -struct TestSendMessage; - -/// WebSocket连接状态 /// 连接信息 #[derive(Debug, Clone)] pub struct ConnectionInfo { @@ -23,500 +14,10 @@ pub struct ConnectionInfo { } impl ConnectionInfo { - /// 创建新的连接信息 - pub fn new(id: String) -> Self { - let now = Instant::now(); - Self { - id, - connected_at: now, - last_heartbeat: now, - client_info: None, - } - } - - /// 设置客户端信息 - pub fn set_client_info(&mut self, info: String) { - self.client_info = Some(info); - } - /// 获取客户端信息 pub fn get_client_info(&self) -> &Option { &self.client_info } - - /// 更新心跳时间 - pub fn update_heartbeat(&mut self) { - self.last_heartbeat = Instant::now(); - } -} - -/// WebSocket连接 -#[allow(dead_code)] -pub struct WebSocketConnection { - /// 连接ID - id: String, - /// 连接信息 - info: ConnectionInfo, - /// 连接管理器 - manager: Arc>, - /// 心跳间隔 - heartbeat_interval: Duration, - /// 客户端超时时间 - client_timeout: Duration, - /// 心跳定时器 - hb: Instant, - /// 响应通道 - response_sender: Option>, - /// 响应接收器 - response_receiver: Option>, -} - -impl WebSocketConnection { - pub fn new(manager: Arc>) -> Self { - let id = Uuid::new_v4().to_string(); - let info = ConnectionInfo::new(id.clone()); - - let (tx, rx) = mpsc::channel(100); - - println!("🔌 创建新的WebSocket连接: id={}, connected_at={:?}", info.id, info.connected_at); - - Self { - id, - info, - manager, - heartbeat_interval: Duration::from_secs(30), - client_timeout: Duration::from_secs(60), - hb: Instant::now(), - response_sender: Some(tx), - response_receiver: Some(rx), - } - } - - /// 获取连接信息(用于调试和监控) - pub fn get_info(&self) -> &ConnectionInfo { - println!("ℹ️ 获取连接信息: id={}, 连接时长: {:?}", self.info.id, self.info.connected_at.elapsed()); - &self.info - } - - /// 获取响应发送器(用于测试和调试) - pub fn get_response_sender(&self) -> &Option> { - println!("📤 获取响应发送器: {:?}", self.response_sender.is_some()); - &self.response_sender - } - - /// 获取响应接收器(用于测试和调试) - pub fn get_response_receiver(&self) -> &Option> { - println!("📥 获取响应接收器: {:?}", self.response_receiver.is_some()); - &self.response_receiver - } - - /// 发送消息 - /// - /// 这个方法用于向WebSocket连接发送消息。 - /// 在实际使用中,当WebSocket连接建立后,可以通过这个方法发送各种类型的消息。 - /// - /// # 示例 - /// ``` - /// let message = json!({ - /// "type": "notification", - /// "data": "Hello World" - /// }); - /// connection.send(message).await?; - /// ``` - #[allow(dead_code)] - pub async fn send(&self, message: serde_json::Value) -> Result<(), String> { - if let Some(_sender) = &self.response_sender { - // 这里应该通过WebSocket连接发送消息 - println!("📤 通过WebSocket发送消息到连接 {}: {}", self.id, message); - Ok(()) - } else { - Err("发送器不可用".to_string()) - } - } - - /// 发送消息并等待响应 - /// - /// 这个方法用于向WebSocket连接发送请求消息并等待响应。 - /// 适用于需要确认响应的场景,如RPC调用。 - /// - /// # 参数 - /// * `message` - 要发送的消息 - /// * `timeout_ms` - 超时时间(毫秒) - /// - /// # 示例 - /// ``` - /// let request = json!({ - /// "type": "get_status", - /// "request_id": "123" - /// }); - /// let response = connection.send_and_wait(request, 5000).await?; - /// ``` - #[allow(dead_code)] - pub async fn send_and_wait(&self, message: serde_json::Value, timeout_ms: u64) -> Result { - let (_response_tx, mut _response_rx) = tokio::sync::mpsc::channel::(1); - let request_id = Uuid::new_v4().to_string(); - - let _msg = json!({ - "type": "request", - "id": request_id, - "data": message - }); - - println!("📤 发送WebSocket请求到连接 {},请求ID: {},超时: {}ms", self.id, request_id, timeout_ms); - - // 首先发送消息 - match self.send(message.clone()).await { - Ok(_) => { - println!("✅ 消息发送成功,等待响应..."); - }, - Err(e) => { - println!("❌ 消息发送失败: {}", e); - return Err(format!("发送失败: {}", e)); - } - } - - // 这里需要实现具体的发送逻辑 - // 暂时返回模拟响应 - Ok(json!({ - "success": true, - "message": "WebSocket消息已发送", - "data": message, - "request_id": request_id, - "timeout": timeout_ms - })) - } - - /// 内部测试方法 - 测试消息发送功能 - /// - /// 这个方法用于测试WebSocketConnection的消息发送功能。 - /// 它会依次调用send和send_and_wait方法来验证功能是否正常。 - /// - /// # 返回值 - /// 返回测试结果,包含send和send_and_wait的测试状态 - #[allow(dead_code)] - pub async fn test_send_functionality(&self) -> Result { - println!("🧪 测试WebSocket连接的消息发送功能"); - - let test_message = json!({ - "type": "test", - "connection_id": self.id, - "timestamp": chrono::Utc::now().timestamp(), - "message": "这是内部测试消息" - }); - - // 测试发送消息 - match self.send(test_message.clone()).await { - Ok(_) => { - println!("✅ 测试消息发送成功"); - - // 测试发送并等待响应 - match self.send_and_wait(test_message.clone(), 5000).await { - Ok(response) => { - println!("✅ 测试发送并等待响应成功"); - Ok(json!({ - "test_send": "success", - "test_send_and_wait": "success", - "response": response, - "connection_id": self.id - })) - }, - Err(e) => { - println!("⚠️ 测试发送并等待响应失败: {}", e); - Ok(json!({ - "test_send": "success", - "test_send_and_wait": "failed", - "error": e.to_string(), - "connection_id": self.id - })) - } - } - }, - Err(e) => { - println!("❌ 测试消息发送失败: {}", e); - Err(format!("测试发送失败: {}", e)) - } - } - } - - /// 心跳处理 - fn heartbeat(&mut self, ctx: &mut ws::WebsocketContext) { - ctx.run_interval(self.heartbeat_interval, |act, ctx| { - // 检查客户端超时 - if Instant::now().duration_since(act.hb) > act.client_timeout { - println!("❌ WebSocket连接超时: {} (最后心跳: {:?}前)", act.id, act.info.last_heartbeat.elapsed()); - ctx.stop(); - return; - } - - // 更新心跳时间 - act.info.update_heartbeat(); - - // 发送心跳消息 - let heartbeat_msg = json!({ - "type": "heartbeat", - "timestamp": chrono::Utc::now().timestamp(), - "connection_id": act.id, - "client_info": act.info.get_client_info() - }); - - ctx.text(serde_json::to_string(&heartbeat_msg).unwrap()); - }); - } -} - -impl Actor for WebSocketConnection { - type Context = ws::WebsocketContext; - - fn started(&mut self, ctx: &mut Self::Context) { - println!("✅ WebSocket连接已建立: {}", self.id); - - // 注册连接到管理器 - let manager = self.manager.clone(); - let connection_id = self.id.clone(); - let mut connection_info = self.info.clone(); - - // 设置客户端信息 - connection_info.set_client_info("SmartClaw-Service".to_string()); - - actix::spawn(async move { - let mut manager = manager.write().await; - manager.add_connection(connection_id, connection_info); - }); - - // 启动心跳机制 - self.heartbeat(ctx); - - // 发送欢迎消息 - let welcome_msg = json!({ - "type": "welcome", - "connection_id": self.id, - "timestamp": chrono::Utc::now().timestamp(), - "message": "连接到Claw网关服务", - "client_info": "SmartClaw-Service" - }); - - ctx.text(serde_json::to_string(&welcome_msg).unwrap()); - - // 在后台测试消息发送功能 - let connection_clone = ctx.address(); - actix::spawn(async move { - // 延迟2秒后测试发送功能 - tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; - - if let Ok(result) = connection_clone.send(TestSendMessage).await { - match result { - Ok(test_result) => { - println!("✅ WebSocket连接测试完成: {:?}", test_result); - }, - Err(e) => { - println!("⚠️ WebSocket连接测试失败: {}", e); - } - } - } - }); - } - - fn stopped(&mut self, _ctx: &mut Self::Context) { - println!("🔌 WebSocket连接已断开: {} (连接时长: {:?})", self.id, self.info.connected_at.elapsed()); - - // 从管理器中移除连接 - let manager = self.manager.clone(); - let connection_id = self.id.clone(); - - actix::spawn(async move { - let mut manager = manager.write().await; - manager.remove_connection(&connection_id); - }); - } -} - -/// WebSocket消息处理 -impl StreamHandler> for WebSocketConnection { - fn handle(&mut self, msg: Result, ctx: &mut Self::Context) { - match msg { - Ok(ws::Message::Ping(msg)) => { - self.hb = Instant::now(); - ctx.pong(&msg); - } - Ok(ws::Message::Pong(_)) => { - self.hb = Instant::now(); - } - Ok(ws::Message::Text(text)) => { - self.hb = Instant::now(); - self.info.update_heartbeat(); // 更新最后心跳时间 - - // 解析消息 - match serde_json::from_str::(&text) { - Ok(json_msg) => { - self.handle_message(json_msg, ctx); - } - Err(e) => { - println!("❌ 解析WebSocket消息失败: {}", e); - let error_msg = json!({ - "type": "error", - "message": "消息格式错误", - "error": e.to_string() - }); - ctx.text(serde_json::to_string(&error_msg).unwrap()); - } - } - } - Ok(ws::Message::Binary(bin)) => { - // 处理二进制消息(如果需要) - println!("📦 收到二进制消息: {} bytes", bin.len()); - } - Ok(ws::Message::Close(reason)) => { - println!("🔌 WebSocket连接关闭: {:?} (连接时长: {:?})", reason, self.info.connected_at.elapsed()); - ctx.stop(); - } - Err(e) => { - println!("❌ WebSocket协议错误: {}", e); - ctx.stop(); - } - _ => {} - } - } -} - -impl WebSocketConnection { - /// 处理接收到的消息 - fn handle_message(&mut self, msg: serde_json::Value, ctx: &mut ws::WebsocketContext) { - let msg_type = msg.get("type").and_then(|v| v.as_str()).unwrap_or("unknown"); - - match msg_type { - "heartbeat" => { - // 心跳响应 - let response = json!({ - "type": "heartbeat_response", - "timestamp": chrono::Utc::now().timestamp(), - "connection_id": self.id - }); - ctx.text(serde_json::to_string(&response).unwrap()); - } - "task_response" => { - // 任务响应 - println!("✅ 收到任务响应: {:?}", msg); - // 这里可以处理任务响应,更新状态等 - - // 测试使用send方法发送确认消息 - let ack_message = json!({ - "type": "task_ack", - "original_response": msg, - "timestamp": chrono::Utc::now().timestamp(), - "connection_id": self.id - }); - - // 由于send是异步方法,我们在这里模拟使用 - println!("📤 模拟使用send方法发送确认: {}", ack_message); - - // 测试使用send_and_wait方法(模拟) - let test_request = json!({ - "type": "test_request", - "data": "测试数据", - "connection_id": self.id - }); - - println!("⏱️ 模拟使用send_and_wait方法发送请求: {},超时: 3000ms", test_request); - } - "status_update" => { - // 状态更新 - println!("📊 收到状态更新: {:?}", msg); - } - "test_send_direct" => { - // 直接测试send方法 - println!("🧪 收到直接测试send方法的消息"); - - // 模拟使用send方法 - let test_message = json!({ - "type": "test_send_response", - "original_message": msg, - "connection_id": self.id, - "test_result": "send方法测试成功" - }); - - println!("📤 模拟send方法调用: {}", test_message); - - // 发送响应 - let response = json!({ - "type": "test_send_completed", - "test_message": test_message, - "connection_id": self.id - }); - ctx.text(serde_json::to_string(&response).unwrap()); - } - "test_send_and_wait_direct" => { - // 直接测试send_and_wait方法 - println!("🧪 收到直接测试send_and_wait方法的消息"); - - // 模拟使用send_and_wait方法 - let test_request = json!({ - "type": "test_send_and_wait_request", - "original_message": msg, - "connection_id": self.id, - "timeout": 5000 - }); - - println!("⏱️ 模拟send_and_wait方法调用: {}", test_request); - - // 模拟响应 - let test_response = json!({ - "success": true, - "message": "send_and_wait方法测试成功", - "data": "这是模拟的响应数据", - "connection_id": self.id - }); - - println!("✅ 模拟send_and_wait方法响应: {}", test_response); - - // 发送响应 - let response = json!({ - "type": "test_send_and_wait_completed", - "request": test_request, - "response": test_response, - "connection_id": self.id - }); - ctx.text(serde_json::to_string(&response).unwrap()); - } - _ => { - println!("📨 收到未知类型消息: {}", msg_type); - let response = json!({ - "type": "unknown_message_type", - "original_type": msg_type, - "message": "收到未知消息类型" - }); - ctx.text(serde_json::to_string(&response).unwrap()); - } - } - } -} - -/// 处理测试消息发送的消息 -impl Handler for WebSocketConnection { - type Result = ResponseFuture>; - - fn handle(&mut self, _msg: TestSendMessage, _ctx: &mut Self::Context) -> Self::Result { - let connection_id = self.id.clone(); - - Box::pin(async move { - println!("🧪 在Handler中测试WebSocket连接的消息发送功能"); - - let test_message = json!({ - "type": "test", - "connection_id": connection_id, - "timestamp": chrono::Utc::now().timestamp(), - "message": "这是Handler中的测试消息" - }); - - // 模拟测试结果 - Ok(json!({ - "test_send": "simulated_success", - "test_send_and_wait": "simulated_success", - "connection_id": connection_id, - "test_message": test_message, - "note": "这是在Handler中模拟的测试结果" - })) - }) - } } /// 连接管理器 @@ -531,18 +32,6 @@ impl ConnectionManager { } } - /// 添加连接 - pub fn add_connection(&mut self, id: String, info: ConnectionInfo) { - self.connections.insert(id.clone(), info); - println!("📥 连接已注册: {} (总数: {})", id, self.connections.len()); - } - - /// 移除连接 - pub fn remove_connection(&mut self, id: &str) { - self.connections.remove(id); - println!("📤 连接已移除: {} (剩余: {})", id, self.connections.len()); - } - /// 获取连接信息 pub fn get_connection(&self, id: &str) -> Option<&ConnectionInfo> { let conn = self.connections.get(id); @@ -728,7 +217,7 @@ impl CommunicationConfig { /// WebSocket客户端(用于SmartClaw服务连接网关) pub struct WebSocketClient { config: CommunicationConfig, - connection: Option, + connected: bool, } impl WebSocketClient { @@ -736,7 +225,7 @@ impl WebSocketClient { println!("🚀 创建WebSocket客户端,配置URL: {}", config.websocket_url); Self { config, - connection: None, + connected: false, } } @@ -752,15 +241,15 @@ impl WebSocketClient { // 这里需要实现具体的WebSocket连接逻辑 // 暂时返回模拟连接成功 println!("✅ WebSocket连接成功 (模拟)"); - self.connection = Some(WebSocketConnection::new(Arc::new(RwLock::new(ConnectionManager::new())))); + self.connected = true; Ok(()) } /// 断开连接 pub async fn disconnect(&mut self) -> Result<(), String> { - if self.connection.is_some() { + if self.connected { println!("🔌 断开WebSocket连接: {}", self.config.websocket_url); - self.connection = None; + self.connected = false; } else { println!("⚠️ 没有活动的WebSocket连接需要断开"); } @@ -798,8 +287,7 @@ impl WebSocketClient { /// 检查连接状态 pub fn is_connected(&self) -> bool { - let connected = self.connection.is_some(); - println!("🔗 WebSocket客户端连接状态: {}", if connected { "已连接" } else { "未连接" }); - connected + println!("🔗 WebSocket客户端连接状态: {}", if self.connected { "已连接" } else { "未连接" }); + self.connected } } \ No newline at end of file diff --git a/Claw/Server/gateway/src/main.rs b/Claw/Server/gateway/src/main.rs index 3b9d4d5..f51131a 100644 --- a/Claw/Server/gateway/src/main.rs +++ b/Claw/Server/gateway/src/main.rs @@ -1,15 +1,401 @@ use actix_web::{web, App, HttpRequest, HttpResponse, HttpServer, Responder, middleware::Logger}; -use actix_web_actors::ws; -use serde::Deserialize; +use actix_web::http::Method; +use actix_ws::{Message}; +use serde::{Deserialize, Serialize}; use std::env; use std::sync::Arc; use tokio::sync::RwLock; +use base64; use shared::{TaskRequest, TaskResponse, HealthResponse, utils}; use sha1::{Sha1, Digest}; +use futures::StreamExt; mod communication; -use communication::{WebSocketConnection, ConnectionManager, WebSocketPool, CommunicationConfig, WebSocketClient}; +use communication::{ConnectionManager, WebSocketPool, CommunicationConfig, WebSocketClient}; + +/// 企业微信消息发送结构体 +#[derive(Serialize)] +struct WeChatMessage { + touser: String, + msgtype: String, + agentid: i32, + text: WeChatTextContent, +} + +#[derive(Serialize)] +struct WeChatTextContent { + content: String, +} + +#[derive(Deserialize)] +struct WeChatAccessTokenResponse { + access_token: String, + // expires_in: i32, // 未使用,暂时注释 +} + +#[derive(Deserialize)] +struct WeChatSendMessageResponse { + errcode: i32, + errmsg: String, +} + +/// 获取企业微信访问令牌 +async fn get_wechat_access_token() -> Result> { + let (_token, corp_id, _encoding_aes_key, corp_secret, _debug, _debug_config, _http) = get_wechat_config(); + + let url = format!("https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={}&corpsecret={}", corp_id, corp_secret); + + let client = reqwest::Client::new(); + let response = client.get(&url).send().await?; + let result: WeChatAccessTokenResponse = response.json().await?; + + Ok(result.access_token) +} + +/// 发送企业微信消息 +async fn send_wechat_message(touser: &str, content: &str, debug: bool) -> Result<(), Box> { + if debug { + println!("📤 开始发送企业微信消息"); + } + + // 获取访问令牌 + let access_token = get_wechat_access_token().await?; + if debug { + println!(" 获取到访问令牌: {}", access_token); + } + + // 构建消息 + let message = WeChatMessage { + touser: touser.to_string(), + msgtype: "text".to_string(), + agentid: 1000002, // 企业微信应用ID + text: WeChatTextContent { + content: content.to_string(), + }, + }; + + // 发送消息 + let url = format!("https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token={}", access_token); + + let client = reqwest::Client::new(); + let response = client.post(&url) + .json(&message) + .send() + .await?; + + let result: WeChatSendMessageResponse = response.json().await?; + + if result.errcode == 0 { + if debug { + println!("✅ 消息发送成功"); + } + Ok(()) + } else { + if debug { + println!("❌ 消息发送失败: {} - {}", result.errcode, result.errmsg); + } + Err(format!("消息发送失败: {} - {}", result.errcode, result.errmsg).into()) + } +} + +#[derive(Deserialize, Serialize, Debug)] +struct WeChatConfig { + wechat: WeChatSettings, + debug: DebugSettings, +} + +#[derive(Deserialize, Serialize, Debug)] +struct WeChatSettings { + token: String, + corp_id: String, + encoding_aes_key: String, + corp_secret: String, +} + +#[derive(Deserialize, Serialize, Debug)] +struct DebugSettings { + wechat: bool, + config: bool, + http: bool, +} + +impl Default for WeChatConfig { + fn default() -> Self { + Self { + wechat: WeChatSettings { + token: "mytoken123456".to_string(), + corp_id: "wwa7bb7aec981103b4".to_string(), + encoding_aes_key: "PXP7FjoinIPc9WscGymDlf1VwMyBLh1cKJJSJFx2SO8".to_string(), + corp_secret: "your_corp_secret_here".to_string(), + }, + debug: DebugSettings { + wechat: false, + config: false, + http: true, + }, + } + } +} + +/// 企业微信配置(从config.json文件读取,便于配置管理) +fn get_wechat_config() -> (String, String, String, String, bool, bool, bool) { + use std::fs::File; + use std::io::{Read, Write}; + use std::path::Path; + + // 配置文件路径 + let config_path = Path::new("./config.json"); + + // 尝试读取配置文件 + if config_path.exists() { + // 先创建默认配置,用于获取debug.config的值 + let default_config = WeChatConfig::default(); + let debug_config = default_config.debug.config; + + if debug_config { + println!("📁 读取配置文件: {:?}", config_path); + } + + match File::open(config_path) { + Ok(mut file) => { + let mut contents = String::new(); + if file.read_to_string(&mut contents).is_ok() { + match serde_json::from_str::(&contents) { + Ok(config) => { + if config.debug.config { + println!("✅ 配置文件读取成功"); + } + return (config.wechat.token, config.wechat.corp_id, config.wechat.encoding_aes_key, config.wechat.corp_secret, config.debug.wechat, config.debug.config, config.debug.http); + } + Err(e) => { + if debug_config { + println!("❌ 配置文件解析失败: {}, 使用默认配置", e); + } + } + } + } else { + if debug_config { + println!("❌ 配置文件读取失败,使用默认配置"); + } + } + } + Err(e) => { + let default_config = WeChatConfig::default(); + if default_config.debug.config { + println!("❌ 打开配置文件失败: {}, 使用默认配置", e); + } + } + } + } else { + let default_config = WeChatConfig::default(); + if default_config.debug.config { + println!("📁 配置文件不存在,生成默认配置"); + } + // 生成默认配置文件 + let default_config = WeChatConfig::default(); + if let Ok(mut file) = File::create(config_path) { + if let Ok(contents) = serde_json::to_string_pretty(&default_config) { + if file.write_all(contents.as_bytes()).is_ok() { + if default_config.debug.config { + println!("✅ 默认配置文件生成成功: {:?}", config_path); + } + } else { + if default_config.debug.config { + println!("❌ 默认配置文件生成失败"); + } + } + } + } + } + + // 使用默认值 + let default_config = WeChatConfig::default(); + (default_config.wechat.token, default_config.wechat.corp_id, default_config.wechat.encoding_aes_key, default_config.wechat.corp_secret, default_config.debug.wechat, default_config.debug.config, default_config.debug.http) +} + +/// 解析企业微信XML消息 +fn parse_wechat_xml_message(xml_content: &str, debug: bool) -> (Option, Option, Option, Option) { + if debug { + println!("📄 开始解析企业微信XML消息"); + } + + // 检查是否为加密消息 + if xml_content.contains("") { + if debug { + println!("🔒 发现加密消息,开始解密"); + } + + // 提取Encrypt标签内容 + if let Some(encrypt_content) = extract_xml_tag(xml_content, "Encrypt") { + if debug { + println!(" 提取到加密内容"); + } + + // 解密消息 + match decrypt_wechat_message(&encrypt_content) { + Ok(decrypted_xml) => { + if debug { + println!("✅ 消息解密成功"); + println!(" 解密后内容: {}", decrypted_xml); + } + + // 从解密后的XML中提取发送者、内容、消息类型和事件类型 + let from_user_name = extract_xml_tag(&decrypted_xml, "FromUserName"); + let content = extract_xml_tag(&decrypted_xml, "Content"); + let msg_type = extract_xml_tag(&decrypted_xml, "MsgType"); + let event = extract_xml_tag(&decrypted_xml, "Event"); + + if debug { + if let Some(from_user) = &from_user_name { + println!(" 发送者: {}", from_user); + } + if let Some(msg_content) = &content { + println!(" 消息内容: {}", msg_content); + } + if let Some(msg_type_val) = &msg_type { + println!(" 消息类型: {}", msg_type_val); + } + if let Some(event_val) = &event { + println!(" 事件类型: {}", event_val); + } + } + + return (from_user_name, content, msg_type, event); + } + Err(e) => { + if debug { + println!("❌ 消息解密失败: {}", e); + } + return (None, None, None, None); + } + } + } else { + if debug { + println!("❌ 无法提取Encrypt标签"); + } + return (None, None, None, None); + } + } else { + // 非加密消息,直接解析 + if debug { + println!("🔓 非加密消息,直接解析"); + } + let from_user_name = extract_xml_tag(xml_content, "FromUserName"); + let content = extract_xml_tag(xml_content, "Content"); + let msg_type = extract_xml_tag(xml_content, "MsgType"); + let event = extract_xml_tag(xml_content, "Event"); + + if debug { + if let Some(from_user) = &from_user_name { + println!(" 发送者: {}", from_user); + } + if let Some(msg_content) = &content { + println!(" 消息内容: {}", msg_content); + } + if let Some(msg_type_val) = &msg_type { + println!(" 消息类型: {}", msg_type_val); + } + if let Some(event_val) = &event { + println!(" 事件类型: {}", event_val); + } + } + + return (from_user_name, content, msg_type, event); + } +} + +/// 解密企业微信消息 +fn decrypt_wechat_message(encrypted: &str) -> Result> { + // 获取企业微信配置 + let (_, _corp_id, encoding_aes_key, _corp_secret, _debug, _debug_config, _http) = get_wechat_config(); + + // 企业微信官方要求:EncodingAESKey 使用URL_SAFE Base64解码,且需要补全=号 + let mut aes_key_str = encoding_aes_key.to_string(); + while aes_key_str.len() % 4 != 0 { + aes_key_str.push('='); + } + let key = base64::Engine::decode(&base64::engine::general_purpose::URL_SAFE, &aes_key_str)?; + + if key.len() != 32 { + return Err("Invalid key length".into()); + } + + // 企业微信官方规定:IV 必须是 EncodingAESKey 解码后的前 16 字节 + let iv = &key[0..16]; + let mut iv_array = [0u8; 16]; + iv_array.copy_from_slice(iv); + + // 企业微信官方要求:加密消息内容(Encrypt)使用STANDARD Base64解码 + let ciphertext = base64::Engine::decode(&base64::engine::general_purpose::STANDARD, &encrypted)?; + + // 使用AES-CBC-256解密 + use aes::cipher::{KeyIvInit, BlockDecryptMut}; + use aes::Aes256; + use cbc::Decryptor; + use cipher::block_padding::NoPadding; + + let decryptor = Decryptor::::new_from_slices(&key, &iv_array)?; + let mut buffer = ciphertext.to_vec(); + let plaintext = decryptor.decrypt_padded_mut::(&mut buffer) + .map_err(|e| format!("Decryption error: {:?}", e))?; + + // 企业微信官方格式:16字节随机串 + 4字节长度(网络序) + 消息内容 + CorpID + if plaintext.len() < 20 { + return Err("Decrypted data too short".into()); + } + + // 解析4字节长度(网络序,大端) + let msg_len = u32::from_be_bytes([plaintext[16], plaintext[17], plaintext[18], plaintext[19]]); + let msg_start = 20; + let msg_end = msg_start + msg_len as usize; + + if msg_end > plaintext.len() { + return Err("Invalid message length".into()); + } + + // 截取消息内容 + let msg = &plaintext[msg_start..msg_end]; + let result = String::from_utf8_lossy(msg).to_string(); + + Ok(result) +} + +/// 提取XML标签内容 +fn extract_xml_tag(xml: &str, tag: &str) -> Option { + // 尝试匹配带CDATA的格式 + let start_tag_cdata = format!("<{}>"; + let end_tag = format!("").map(|i| start_idx + i + 1) { + if let Some(end_idx) = xml[tag_end_idx..].find(&end_tag) { + let content = &xml[tag_end_idx..tag_end_idx + end_idx]; + // 去除前后空白 + let trimmed_content = content.trim(); + if !trimmed_content.is_empty() { + return Some(trimmed_content.to_string()); + } + } + } + } + + None +} /// 任务处理服务 struct TaskService { @@ -204,20 +590,31 @@ impl TaskService { } /// 验证企业微信签名 - fn validate_wechat_signature(signature: &str, timestamp: &str, nonce: &str, token: &str) -> bool { - println!("🔐 验证企业微信签名:"); - println!(" signature: {}", signature); - println!(" timestamp: {}", timestamp); - println!(" nonce: {}", nonce); - println!(" token: {}", token); + fn validate_wechat_signature(msg_signature: &str, timestamp: &str, nonce: &str, data: &str, debug: bool) -> bool { + if debug { + println!("🔐 验证企业微信签名:"); + println!(" msg_signature: {}", msg_signature); + println!(" timestamp: {}", timestamp); + println!(" nonce: {}", nonce); + println!(" data: {}", data); + } - // 企业微信签名验证算法 - // 1. 将token、timestamp、nonce三个参数进行字典序排序 - let mut params = vec![token, timestamp, nonce]; + // 获取企业微信配置 + let (token, _corp_id, _encoding_aes_key, _corp_secret, _, _, _) = get_wechat_config(); + if debug { + println!(" token: {}", token); + } + + // 企业微信签名验证算法(严格按照官方要求) + // 1. 将token、timestamp、nonce、data四个参数进行字典序排序 + let mut params = vec![token.as_str(), timestamp, nonce, data]; params.sort(); - // 2. 将三个参数字符串拼接成一个字符串 + // 2. 将排序后的参数拼接成一个字符串 let combined = params.join(""); + if debug { + println!(" 排序后拼接字符串: {}", combined); + } // 3. 进行sha1加密 let mut hasher = Sha1::new(); @@ -225,10 +622,12 @@ impl TaskService { let result = hasher.finalize(); let computed_signature = hex::encode(result); - // 4. 与signature对比 - let is_valid = computed_signature == signature; - println!(" 计算签名: {}", computed_signature); - println!(" 验证结果: {}", if is_valid { "✅ 通过" } else { "❌ 失败" }); + // 4. 与msg_signature对比 + let is_valid = computed_signature == msg_signature; + if debug { + println!(" 计算签名: {}", computed_signature); + println!(" 验证结果: {}", if is_valid { "✅ 通过" } else { "❌ 失败" }); + } is_valid } @@ -263,10 +662,15 @@ impl TaskService { /// WebSocket连接处理器 async fn websocket_handler( req: HttpRequest, - stream: web::Payload, - app_data: web::Data, + body: web::Payload, + _app_data: web::Data, ) -> Result { - println!("🔗 收到WebSocket连接请求: {:?}", req); + println!("🔗 收到WebSocket连接请求: "); + println!(" HttpRequest {:?} {:?}:{}", req.method(), req.version(), req.path()); + println!(" headers: "); + for (name, value) in req.headers() { + println!(" {:?}: {:?}", name, value); + } // 验证连接来源(可以添加API密钥验证) let api_key = req.headers().get("X-API-Key") @@ -277,30 +681,77 @@ async fn websocket_handler( if api_key != expected_key { println!("❌ WebSocket连接认证失败"); - return Ok(HttpResponse::Unauthorized().json(serde_json::json!({ - "error": "Invalid API key", - "message": "WebSocket连接认证失败" - }))); + return Err(actix_web::error::ErrorUnauthorized("Invalid API key")); } println!("✅ WebSocket连接认证通过"); - // 创建WebSocket连接 - let connection = WebSocketConnection::new(app_data.connection_manager.clone()); + // 获取请求路径,区分连接类型 + let path = req.path(); + let is_control_connection = path == "/api/v1/ws/control"; - // 获取连接信息(用于调试) - let connection_info = connection.get_info(); - println!("ℹ️ WebSocket连接信息: id={}, 连接时间: {:?}", connection_info.id, connection_info.connected_at.elapsed()); + if is_control_connection { + println!("🎯 检测到SmartClaw服务连接 (控制通道)"); + } else { + println!("📱 检测到设备连接 (任务通道)"); + } - // 获取响应通道信息(用于调试) - let sender_info = connection.get_response_sender(); - let receiver_info = connection.get_response_receiver(); - println!("📤 响应发送器: {:?}, 📥 响应接收器: {:?}", sender_info.is_some(), receiver_info.is_some()); + println!("🔗 开始WebSocket握手..."); + // 使用actix-ws处理WebSocket连接 + let (response, mut session, msg_stream) = match actix_ws::handle(&req, body) { + Ok(result) => { + println!("✅ WebSocket握手成功"); + result + }, + Err(e) => { + println!("❌ WebSocket握手失败: {}", e); + return Err(e); + } + }; - let resp = ws::start(connection, &req, stream)?; + println!("🔄 启动WebSocket消息处理循环..."); + // 启动WebSocket消息处理循环 + actix_web::rt::spawn(async move { + println!("✅ WebSocket消息处理循环已启动"); + let mut msg_stream = msg_stream; + while let Some(msg) = msg_stream.next().await { + match msg { + Ok(Message::Text(text)) => { + println!("📨 收到消息: {}", text); + // 处理消息 + } + Ok(Message::Binary(bin)) => { + println!("📨 收到二进制消息: {} bytes", bin.len()); + } + Ok(Message::Ping(msg)) => { + println!("📨 收到Ping"); + let _ = session.pong(&msg).await; + } + Ok(Message::Pong(_)) => { + println!("📨 收到Pong"); + } + Ok(Message::Close(reason)) => { + println!("📨 收到关闭消息: {:?}", reason); + break; + } + Ok(Message::Continuation(_)) => { + // 处理 continuation 消息 + } + Ok(Message::Nop) => { + // 处理 nop 消息 + } + Err(e) => { + println!("❌ WebSocket错误: {}", e); + break; + } + } + } + + println!("🔚 WebSocket连接已关闭"); + }); println!("✅ WebSocket连接已建立"); - Ok(resp) + Ok(response) } /// 健康检查处理器 @@ -386,16 +837,26 @@ async fn test_websocket_connection_send(app_data: web::Data) -> imp /// 企业微信回调处理器 async fn handle_wechat_callback(req: HttpRequest, body: web::Bytes) -> impl Responder { - println!("📱 收到企业微信回调"); + // 获取企业微信配置,包括debug配置 + let (_, _, _, _, debug_wechat, _debug_config, _http) = get_wechat_config(); + + // 获取请求方法 + let method = req.method(); // 获取查询参数 let query_string = req.query_string(); - println!(" 查询参数: {}", query_string); + + // 根据debug配置控制日志输出 + if debug_wechat { + println!("📱 收到企业微信回调"); + println!(" 请求方法: {}", method); + println!(" 查询参数: {}", query_string); + } // 解析查询参数 #[derive(Deserialize)] struct WeChatQuery { - signature: String, + msg_signature: String, timestamp: String, nonce: String, echostr: Option, @@ -404,49 +865,150 @@ async fn handle_wechat_callback(req: HttpRequest, body: web::Bytes) -> impl Resp let query: WeChatQuery = match web::Query::::from_query(query_string) { Ok(q) => q.into_inner(), Err(e) => { - println!("❌ 解析查询参数失败: {}", e); - return HttpResponse::BadRequest().json(serde_json::json!({ - "error": "Invalid query parameters", - "message": e.to_string() - })); + if debug_wechat { + println!("❌ 解析查询参数失败: {}", e); + } + return HttpResponse::BadRequest().body("error"); } }; - // 获取企业微信配置 - let token = env::var("WECHAT_TOKEN").unwrap_or_else(|_| "your_token_here".to_string()); - - // 验证签名 - let is_valid = TaskService::validate_wechat_signature( - &query.signature, - &query.timestamp, - &query.nonce, - &token - ); - - if !is_valid { - return HttpResponse::Unauthorized().json(serde_json::json!({ - "error": "Invalid signature", - "message": "签名验证失败" - })); + // 核心判断:GET 和 POST 必须分开处理 + if method == &Method::GET { + // 1. GET请求 = URL 验证 + if debug_wechat { + println!("🔐 开始URL验证流程"); + } + + // 验证签名 + let is_valid = TaskService::validate_wechat_signature( + &query.msg_signature, + &query.timestamp, + &query.nonce, + &query.echostr.as_ref().unwrap_or(&String::new()), + debug_wechat + ); + + if !is_valid { + if debug_wechat { + println!("❌ URL验证签名失败"); + } + return HttpResponse::Unauthorized().body("invalid signature"); + } + + // 验证通过,返回echostr + if let Some(echostr) = query.echostr { + if debug_wechat { + println!("✅ URL验证成功,返回 echostr: {}", echostr); + } + return HttpResponse::Ok().body(echostr); + } else { + if debug_wechat { + println!("❌ URL验证失败:缺少echostr参数"); + } + return HttpResponse::BadRequest().body("missing echostr"); + } + } else if method == &Method::POST { + // 2. POST请求 = 消息推送 + if debug_wechat { + println!("📥 开始消息推送处理流程"); + + // 处理实际的消息回调 + let body_str = String::from_utf8_lossy(&body); + println!(" 消息内容: {}", body_str); + } + + // 提取Encrypt字段内容 + let body_str = String::from_utf8_lossy(&body); + let encrypt_content = extract_xml_tag(&body_str, "Encrypt").unwrap_or_default(); + + // 验证签名(POST请求需要包含Encrypt字段) + if debug_wechat { + println!("🔐 开始验证企业微信签名"); + } + let is_valid = TaskService::validate_wechat_signature( + &query.msg_signature, + &query.timestamp, + &query.nonce, + &encrypt_content, + debug_wechat + ); + + if !is_valid { + if debug_wechat { + println!("❌ 消息推送签名验证失败"); + } + return HttpResponse::Unauthorized().body("invalid signature"); + } + + if debug_wechat { + println!("✅ 签名验证通过,开始处理消息"); + } + + // 解析XML消息 + let (from_user_name, content, msg_type, event) = parse_wechat_xml_message(&body_str, debug_wechat); + + // 尝试发送回复消息 + if let Some(user_id) = from_user_name { + // 判断是否需要回复:只回复文本消息和非位置上报事件 + let should_reply = match msg_type.as_deref() { + Some("text") => true, // 文本消息需要回复 + Some("event") => { + // 事件消息中,除了位置上报事件外都回复 + event.as_deref() != Some("LOCATION") + } + _ => false, // 其他类型消息不回复 + }; + + if should_reply { + if debug_wechat { + println!("📤 准备发送回复消息给用户: {}", user_id); + } + + let reply_content = if let Some(msg_content) = content { + format!("我收到你的消息啦!你说: {}", msg_content) + } else { + "我收到你的消息啦!".to_string() + }; + + // 异步发送消息(不阻塞主线程) + let user_id_clone = user_id.clone(); + let reply_content_clone = reply_content.clone(); + let debug_wechat_clone = debug_wechat; + + tokio::spawn(async move { + if let Err(e) = send_wechat_message(&user_id_clone, &reply_content_clone, debug_wechat_clone).await { + if debug_wechat_clone { + println!("❌ 发送回复消息失败: {}", e); + } + } + }); + + if debug_wechat { + println!("✅ 已开始发送回复消息"); + } + } else { + if debug_wechat { + println!("⚠️ 消息类型不需要回复,跳过回复"); + } + } + } else { + if debug_wechat { + println!("⚠️ 无法获取发送者信息,跳过回复"); + } + } + + // 企业微信要求返回纯文本 "success" + if debug_wechat { + println!("✅ 企业微信消息处理完成,返回 success"); + } + HttpResponse::Ok().body("success") + } else { + // 其他请求方法 + if debug_wechat { + println!("❌ 不支持的请求方法: {}", method); + } + HttpResponse::MethodNotAllowed().body("method not allowed") } - - // 如果是验证请求(首次配置时需要) - if let Some(echostr) = query.echostr { - println!("✅ 企业微信验证请求,返回 echostr: {}", echostr); - return HttpResponse::Ok().body(echostr); - } - - // 处理实际的消息回调 - let body_str = String::from_utf8_lossy(&body); - println!(" 消息内容: {}", body_str); - - // TODO: 解析XML消息并处理 - - HttpResponse::Ok().json(serde_json::json!({ - "status": "success", - "message": "企业微信回调已接收", - "timestamp": utils::current_timestamp() - })) } /// 微信小程序回调处理器 @@ -771,8 +1333,15 @@ async fn system_info(app_data: web::Data) -> impl Responder { #[actix_web::main] async fn main() -> std::io::Result<()> { // 初始化日志 + // 获取企业微信配置,包括http日志配置 + let (_token, _corp_id, _encoding_aes_key, _corp_secret, _debug, _debug_config, http_log) = get_wechat_config(); + + // 根据配置决定Actix Web的日志级别 + let actix_web_log_level = if http_log { "info" } else { "warn" }; + let log_filter = format!("info,actix_web={}", actix_web_log_level); + env_logger::init_from_env( - env_logger::Env::new().default_filter_or("info,actix_web=info") + env_logger::Env::new().default_filter_or(&log_filter) ); // 由于nginx代理,网关服务监听在8000端口 @@ -913,6 +1482,9 @@ async fn main() -> std::io::Result<()> { App::new() .app_data(task_service.clone()) .wrap(Logger::default()) + // 企业微信回调 - 直接匹配企业微信配置路径 /wecom + .route("/wecom", web::post().to(handle_wechat_callback)) + // 其他API路由 - 通过 /api/v1 前缀 .service( web::scope("/api/v1") // 健康检查 @@ -923,8 +1495,7 @@ async fn main() -> std::io::Result<()> { .route("/task", web::post().to(handle_task)) .route("/task/{task_id}", web::get().to(get_task_status)) .route("/tasks", web::get().to(list_tasks)) - // 微信集成 - .route("/wechat/callback", web::post().to(handle_wechat_callback)) + // 微信小程序集成 .route("/wechat/miniprogram/callback", web::post().to(handle_wechat_miniprogram_callback)) // WebSocket连接(内网服务器连接) .route("/ws/control", web::get().to(websocket_handler)) @@ -944,12 +1515,15 @@ async fn main() -> std::io::Result<()> { println!("✅ 网关服务已启动在 {} (通过nginx代理)", bind_address); println!("🔍 可用接口:"); + println!(" 🎯 企业微信回调 - 直接匹配企业微信配置"); + println!(" POST /wecom - 企业微信回调(必须直接匹配企业微信配置)"); + println!(""); + println!(" 📋 API接口(通过 /api/v1 前缀):"); println!(" GET /api/v1/health - 健康检查"); println!(" GET /api/v1/system - 系统信息"); println!(" POST /api/v1/task - 处理任务"); - println!(" GET /api/v1/task/{{task_id}} - 查询任务状态"); + println!(" GET /api/v1/task/ - 查询任务状态"); println!(" GET /api/v1/tasks - 查询任务列表"); - println!(" POST /api/v1/wechat/callback - 企业微信回调"); println!(" POST /api/v1/wechat/miniprogram/callback - 微信小程序回调"); println!(" GET /api/v1/ws/control - WebSocket控制通道"); println!(" GET /api/v1/ws/task - WebSocket任务通道"); diff --git a/Claw/docs/nginx.conf b/Claw/docs/nginx.conf index d2e4ce9..a31ea85 100644 --- a/Claw/docs/nginx.conf +++ b/Claw/docs/nginx.conf @@ -40,9 +40,9 @@ http { ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; - # 企业微信回调 - 网关服务API + # 企业微信回调 - 直接代理到网关服务 location /wecom { - proxy_pass http://127.0.0.1:8000/api/v1/wechat/callback; + proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -94,6 +94,25 @@ http { proxy_cache off; } + # WebSocket控制通道 - SmartClaw服务连接(API路径) + location /api/v1/ws/control { + proxy_pass http://127.0.0.1:8000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # WebSocket特殊超时设置 + proxy_read_timeout 86400s; # 24小时,支持长连接 + proxy_connect_timeout 10s; + + # 禁用缓存 + proxy_cache off; + } + # WebSocket任务通道 location /ws/task { proxy_pass http://127.0.0.1:8000; @@ -113,6 +132,25 @@ http { proxy_cache off; } + # WebSocket任务通道(API路径) + location /api/v1/ws/task { + proxy_pass http://127.0.0.1:8000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # WebSocket特殊超时设置 + proxy_read_timeout 86400s; # 24小时,支持长连接 + proxy_connect_timeout 10s; + + # 禁用缓存 + proxy_cache off; + } + # Web前端静态文件 location / { root C:/Claw/web; # 指向Web前端构建目录 diff --git a/Claw/docs/nginx_backup.conf b/Claw/docs/nginx_backup.conf new file mode 100644 index 0000000..a31ea85 --- /dev/null +++ b/Claw/docs/nginx_backup.conf @@ -0,0 +1,180 @@ +worker_processes 1; + +events { + worker_connections 1024; +} + +http { + include mime.types; + default_type application/octet-stream; + + sendfile on; + keepalive_timeout 65; + + # 日志配置 + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log logs/access.log main; + error_log logs/error.log; + + # HTTP 自动跳 HTTPS + server { + listen 80; + server_name pactgo.cn; + return 301 https://$host$request_uri; + } + + # HTTPS 服务器 - 网关服务 + server { + listen 443 ssl; + server_name pactgo.cn; + + # SSL证书配置 + ssl_certificate C:/nginx/ssl/pactgo.cn-chain.pem; + ssl_certificate_key C:/nginx/ssl/pactgo.cn-key.pem; + + ssl_session_cache shared:SSL:1m; + ssl_session_timeout 5m; + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; + + # 企业微信回调 - 直接代理到网关服务 + location /wecom { + proxy_pass http://127.0.0.1:8000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # 企业微信回调特殊处理 + proxy_read_timeout 30s; + proxy_connect_timeout 10s; + } + + # 微信小程序回调 - 网关服务API + location /api/v1/wechat/miniprogram/callback { + proxy_pass http://127.0.0.1:8000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # 网关服务其他API + location /api/ { + proxy_pass http://127.0.0.1:8000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # API超时设置 + proxy_read_timeout 60s; + proxy_connect_timeout 10s; + } + + # WebSocket控制通道 - SmartClaw服务连接 + location /ws/control { + proxy_pass http://127.0.0.1:8000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # WebSocket特殊超时设置 + proxy_read_timeout 86400s; # 24小时,支持长连接 + proxy_connect_timeout 10s; + + # 禁用缓存 + proxy_cache off; + } + + # WebSocket控制通道 - SmartClaw服务连接(API路径) + location /api/v1/ws/control { + proxy_pass http://127.0.0.1:8000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # WebSocket特殊超时设置 + proxy_read_timeout 86400s; # 24小时,支持长连接 + proxy_connect_timeout 10s; + + # 禁用缓存 + proxy_cache off; + } + + # WebSocket任务通道 + location /ws/task { + proxy_pass http://127.0.0.1:8000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # WebSocket特殊超时设置 + proxy_read_timeout 86400s; # 24小时,支持长连接 + proxy_connect_timeout 10s; + + # 禁用缓存 + proxy_cache off; + } + + # WebSocket任务通道(API路径) + location /api/v1/ws/task { + proxy_pass http://127.0.0.1:8000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # WebSocket特殊超时设置 + proxy_read_timeout 86400s; # 24小时,支持长连接 + proxy_connect_timeout 10s; + + # 禁用缓存 + proxy_cache off; + } + + # Web前端静态文件 + location / { + root C:/Claw/web; # 指向Web前端构建目录 + index index.html index.htm; + try_files $uri $uri/ /index.html; + + # 静态文件缓存 + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + } + + # 健康检查端点 + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } + } + + # 上游服务器配置 + upstream gateway_backend { + server 127.0.0.1:8000 max_fails=3 fail_timeout=30s; + keepalive 32; + } +} \ No newline at end of file diff --git a/Claw/docs/nginx_bakup.conf b/Claw/docs/nginx_bakup.conf deleted file mode 100644 index 0cfeffd..0000000 --- a/Claw/docs/nginx_bakup.conf +++ /dev/null @@ -1,59 +0,0 @@ -worker_processes 1; - -events { - worker_connections 1024; -} - -http { - include mime.types; - default_type application/octet-stream; - - sendfile on; - keepalive_timeout 65; - - # HTTP 自动跳 HTTPS - server { - listen 80; - server_name pactgo.cn; - return 301 https://$host$request_uri; - } - - # HTTPS 服务器 - server { - listen 443 ssl; - server_name pactgo.cn; - - ssl_certificate C:/nginx/ssl/pactgo.cn-chain.pem; - ssl_certificate_key C:/nginx/ssl/pactgo.cn-key.pem; - - ssl_session_cache shared:SSL:1m; - ssl_session_timeout 5m; - ssl_ciphers HIGH:!aNULL:!MD5; - ssl_prefer_server_ciphers on; - - location /wecom { - proxy_pass http://127.0.0.1:8000/api/v1/wechat/callback; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - - # 企业微信回调特殊处理 - proxy_read_timeout 30s; - proxy_connect_timeout 10s; - } - - # WebSocket 支持 - location /ws { - proxy_pass http://127.0.0.1:8000; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - } - - location / { - root html; - index index.html index.htm; - } - } -} \ No newline at end of file diff --git a/Claw/docs/可行性方案.md b/Claw/docs/可行性方案.md index 24f7306..21fe4f3 100644 --- a/Claw/docs/可行性方案.md +++ b/Claw/docs/可行性方案.md @@ -93,9 +93,16 @@ sequenceDiagram **WebSocket长连接方案**(推荐): 1. **SmartClaw服务主动连接**:服务器B启动时,主动WebSocket连接到服务器A -2. **长连接保持**:维持持久WebSocket连接,支持心跳检测 -3. **双向通信**:服务器A通过WebSocket发送任务,服务器B处理完成后回传结果 -4. **断线重连**:自动重连机制,保证连接稳定性 +2. **单一连接**:服务器B与服务器A之间只建立一个WebSocket连接,不需要列表管理 +3. **长连接保持**:维持持久WebSocket连接,支持心跳检测 +4. **双向通信**:服务器A通过WebSocket发送任务,服务器B处理完成后回传结果 +5. **断线重连**:自动重连机制,保证连接稳定性 + +### 4.2.1 WebSocket连接管理 + +- **服务器B与服务器A**:只建立一个WebSocket连接,无需列表管理 +- **其他设备与服务器A**:通过WebSocket连接到服务器A的 `/ws/task` 路径,需要使用连接列表进行管理 +- **连接管理**:使用 `ConnectionManager` 结构体管理设备与服务器A的WebSocket连接,支持多用户多设备场景 **优点**: