feat(XCamera): 实现异步抓图功能并优化图像处理
fix(nginx): 调整企业微信回调代理路径 feat(gateway): 添加企业微信消息处理功能 docs: 更新项目规划文档和企业微信配置详情 refactor(XCamera): 重构LED检测和图像处理逻辑 test: 添加ONVIF抓图测试功能
This commit is contained in:
@@ -3,6 +3,20 @@ use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
use serde_json::json;
|
||||
use uuid::Uuid;
|
||||
use actix_ws::Session;
|
||||
|
||||
/// 获取当前本地时间的格式化字符串
|
||||
fn get_current_time() -> String {
|
||||
let now = chrono::Local::now();
|
||||
now.format("%m-%d %H:%M:%S%.3f").to_string()
|
||||
}
|
||||
|
||||
/// 带时间前缀的打印宏
|
||||
macro_rules! log {
|
||||
($($arg:tt)*) => {
|
||||
println!("[{}] {}", get_current_time(), format!($($arg)*));
|
||||
};
|
||||
}
|
||||
|
||||
/// 连接信息
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -32,13 +46,39 @@ impl ConnectionManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// 添加连接
|
||||
pub fn add_connection(&mut self, info: ConnectionInfo) {
|
||||
let id = info.id.clone();
|
||||
self.connections.insert(id.clone(), info);
|
||||
log!("🔌 添加新连接: {}", id);
|
||||
}
|
||||
|
||||
/// 移除连接
|
||||
pub fn remove_connection(&mut self, id: &str) -> Option<ConnectionInfo> {
|
||||
let removed = self.connections.remove(id);
|
||||
if removed.is_some() {
|
||||
log!("🔌 移除连接: {}", id);
|
||||
}
|
||||
removed
|
||||
}
|
||||
|
||||
/// 更新连接心跳时间
|
||||
pub fn update_heartbeat(&mut self, id: &str) -> bool {
|
||||
if let Some(info) = self.connections.get_mut(id) {
|
||||
info.last_heartbeat = Instant::now();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取连接信息
|
||||
pub fn get_connection(&self, id: &str) -> Option<&ConnectionInfo> {
|
||||
let conn = self.connections.get(id);
|
||||
if let Some(info) = conn {
|
||||
println!("🔍 获取连接信息: {} (连接时长: {:?})", id, info.connected_at.elapsed());
|
||||
log!("🔍 获取连接信息: {} (连接时长: {:?})", id, info.connected_at.elapsed());
|
||||
} else {
|
||||
println!("⚠️ 连接不存在: {}", id);
|
||||
log!("⚠️ 连接不存在: {}", id);
|
||||
}
|
||||
conn
|
||||
}
|
||||
@@ -56,9 +96,9 @@ impl ConnectionManager {
|
||||
/// 获取所有连接信息
|
||||
pub fn get_all_connections(&self) -> Vec<&ConnectionInfo> {
|
||||
let connections: Vec<&ConnectionInfo> = self.connections.values().collect();
|
||||
println!("📋 获取所有连接信息: {}个连接", connections.len());
|
||||
log!("📋 获取所有连接信息: {}个连接", connections.len());
|
||||
for conn in &connections {
|
||||
println!(" - 连接: {} (连接时长: {:?})", conn.id, conn.connected_at.elapsed());
|
||||
log!(" - 连接: {} (连接时长: {:?})", conn.id, conn.connected_at.elapsed());
|
||||
}
|
||||
connections
|
||||
}
|
||||
@@ -71,7 +111,7 @@ impl ConnectionManager {
|
||||
self.connections.retain(|id, info| {
|
||||
let elapsed = now.duration_since(info.last_heartbeat);
|
||||
if elapsed > timeout {
|
||||
println!("🧹 清理超时连接: {} (最后心跳: {:?}前)", id, elapsed);
|
||||
log!("🧹 清理超时连接: {} (最后心跳: {:?}前)", id, elapsed);
|
||||
removed_ids.push(id.clone());
|
||||
false
|
||||
} else {
|
||||
@@ -80,7 +120,7 @@ impl ConnectionManager {
|
||||
});
|
||||
|
||||
if !removed_ids.is_empty() {
|
||||
println!("🧹 总共清理超时连接: {}个 (剩余: {}个)", removed_ids.len(), self.connections.len());
|
||||
log!("🧹 总共清理超时连接: {}个 (剩余: {}个)", removed_ids.len(), self.connections.len());
|
||||
}
|
||||
|
||||
removed_ids
|
||||
@@ -90,19 +130,21 @@ impl ConnectionManager {
|
||||
/// WebSocket连接池
|
||||
pub struct WebSocketPool {
|
||||
manager: Arc<RwLock<ConnectionManager>>,
|
||||
sessions: Arc<RwLock<std::collections::HashMap<String, Arc<RwLock<Session>>>>>,
|
||||
}
|
||||
|
||||
impl WebSocketPool {
|
||||
pub fn new() -> Self {
|
||||
println!("🚀 创建WebSocket连接池");
|
||||
pub fn new(manager: Arc<RwLock<ConnectionManager>>) -> Self {
|
||||
log!("🚀 创建WebSocket连接池");
|
||||
Self {
|
||||
manager: Arc::new(RwLock::new(ConnectionManager::new())),
|
||||
manager,
|
||||
sessions: Arc::new(RwLock::new(std::collections::HashMap::new())),
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取连接管理器
|
||||
pub fn get_manager(&self) -> Arc<RwLock<ConnectionManager>> {
|
||||
println!("📋 获取WebSocket连接管理器 (引用计数: {:?})", Arc::strong_count(&self.manager));
|
||||
log!("📋 获取WebSocket连接管理器 (引用计数: {:?})", Arc::strong_count(&self.manager));
|
||||
self.manager.clone()
|
||||
}
|
||||
|
||||
@@ -111,34 +153,85 @@ impl WebSocketPool {
|
||||
&self.manager
|
||||
}
|
||||
|
||||
/// 广播消息到所有连接
|
||||
pub async fn broadcast(&self, _message: serde_json::Value) -> Result<(), String> {
|
||||
/// 添加WebSocket会话
|
||||
pub async fn add_session(&self, connection_id: &str, session: Session) {
|
||||
let mut sessions = self.sessions.write().await;
|
||||
sessions.insert(connection_id.to_string(), Arc::new(RwLock::new(session)));
|
||||
log!("🔌 添加WebSocket会话: {}", connection_id);
|
||||
}
|
||||
|
||||
/// 移除WebSocket会话
|
||||
pub async fn remove_session(&self, connection_id: &str) {
|
||||
let mut sessions = self.sessions.write().await;
|
||||
if sessions.remove(connection_id).is_some() {
|
||||
log!("🔌 移除WebSocket会话: {}", connection_id);
|
||||
}
|
||||
}
|
||||
|
||||
/// 广播消息到所有控制通道连接(SmartClaw)
|
||||
pub async fn broadcast_to_control(&self, message: serde_json::Value) -> Result<(), String> {
|
||||
let manager = self.manager.read().await;
|
||||
let connections = manager.get_all_connections();
|
||||
|
||||
if connections.is_empty() {
|
||||
return Err("没有可用的WebSocket连接".to_string());
|
||||
// 过滤出控制通道连接(SmartClaw)
|
||||
let control_connections: Vec<&ConnectionInfo> = connections.into_iter()
|
||||
.filter(|conn| conn.client_info.as_ref().map(|info| info == "SmartClaw").unwrap_or(false))
|
||||
.collect();
|
||||
|
||||
if control_connections.is_empty() {
|
||||
return Err("没有可用的SmartClaw连接".to_string());
|
||||
}
|
||||
|
||||
println!("📢 WebSocket连接池广播消息到 {} 个连接", connections.len());
|
||||
log!("📢 WebSocket连接池广播消息到 {} 个SmartClaw连接", control_connections.len());
|
||||
|
||||
// 实现具体的消息发送逻辑
|
||||
let sessions = self.sessions.read().await;
|
||||
let message_str = message.to_string();
|
||||
|
||||
for conn in &control_connections {
|
||||
if let Some(session) = sessions.get(&conn.id) {
|
||||
let mut session = session.write().await;
|
||||
if let Err(e) = session.text(message_str.clone()).await {
|
||||
log!("❌ 发送消息到SmartClaw连接 {} 失败: {}", conn.id, e);
|
||||
} else {
|
||||
log!("✅ 发送消息到SmartClaw连接: {}", conn.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 这里需要实现具体的消息发送逻辑
|
||||
// 暂时返回成功
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 广播消息到所有连接(兼容旧接口)
|
||||
pub async fn broadcast(&self, message: serde_json::Value) -> Result<(), String> {
|
||||
self.broadcast_to_control(message).await
|
||||
}
|
||||
|
||||
/// 发送消息到指定连接(用于测试和调试)
|
||||
pub async fn send_to_connection(&self, connection_id: &str, _message: serde_json::Value) -> Result<(), String> {
|
||||
pub async fn send_to_connection(&self, connection_id: &str, message: serde_json::Value) -> Result<(), String> {
|
||||
let manager = self.manager.read().await;
|
||||
|
||||
if manager.get_connection(connection_id).is_none() {
|
||||
return Err(format!("连接不存在: {}", connection_id));
|
||||
}
|
||||
|
||||
println!("📨 WebSocket连接池发送消息到连接: {}", connection_id);
|
||||
log!("📨 WebSocket连接池发送消息到连接: {}", connection_id);
|
||||
|
||||
// 实现具体的消息发送逻辑
|
||||
let sessions = self.sessions.read().await;
|
||||
let message_str = message.to_string();
|
||||
|
||||
if let Some(session) = sessions.get(connection_id) {
|
||||
let mut session = session.write().await;
|
||||
if let Err(e) = session.text(message_str).await {
|
||||
return Err(format!("发送消息失败: {}", e));
|
||||
} else {
|
||||
log!("✅ 发送消息到连接: {}", connection_id);
|
||||
}
|
||||
} else {
|
||||
return Err(format!("会话不存在: {}", connection_id));
|
||||
}
|
||||
|
||||
// 这里需要实现具体的消息发送逻辑
|
||||
// 暂时返回成功
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -173,7 +266,7 @@ pub struct CommunicationConfig {
|
||||
|
||||
impl Default for CommunicationConfig {
|
||||
fn default() -> Self {
|
||||
println!("⚙️ 创建默认通信配置");
|
||||
log!("⚙️ 创建默认通信配置");
|
||||
Self {
|
||||
websocket_url: "ws://localhost:8000/api/v1/ws/control".to_string(),
|
||||
api_key: "claw_secret_key".to_string(),
|
||||
@@ -187,7 +280,7 @@ impl Default for CommunicationConfig {
|
||||
impl CommunicationConfig {
|
||||
/// 创建生产环境配置
|
||||
pub fn production() -> Self {
|
||||
println!("🏭 创建生产环境通信配置 (心跳: {:?}, 超时: {:?})", Duration::from_secs(30), Duration::from_secs(60));
|
||||
log!("🏭 创建生产环境通信配置 (心跳: {:?}, 超时: {:?})", Duration::from_secs(30), Duration::from_secs(60));
|
||||
Self {
|
||||
websocket_url: "ws://pactgo.cn/api/v1/ws/control".to_string(),
|
||||
api_key: std::env::var("API_KEY").unwrap_or_else(|_| "claw_secret_key".to_string()),
|
||||
@@ -199,7 +292,7 @@ impl CommunicationConfig {
|
||||
|
||||
/// 验证配置
|
||||
pub fn validate(&self) -> Result<(), String> {
|
||||
println!("🔍 验证通信配置...");
|
||||
log!("🔍 验证通信配置...");
|
||||
if self.websocket_url.is_empty() {
|
||||
return Err("WebSocket URL不能为空".to_string());
|
||||
}
|
||||
@@ -209,7 +302,7 @@ impl CommunicationConfig {
|
||||
if self.max_connections == 0 {
|
||||
return Err("最大连接数不能为0".to_string());
|
||||
}
|
||||
println!("✅ 通信配置验证通过 (URL: {}, 最大连接数: {})", self.websocket_url, self.max_connections);
|
||||
log!("✅ 通信配置验证通过 (URL: {}, 最大连接数: {})", self.websocket_url, self.max_connections);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -222,7 +315,7 @@ pub struct WebSocketClient {
|
||||
|
||||
impl WebSocketClient {
|
||||
pub fn new(config: CommunicationConfig) -> Self {
|
||||
println!("🚀 创建WebSocket客户端,配置URL: {}", config.websocket_url);
|
||||
log!("🚀 创建WebSocket客户端,配置URL: {}", config.websocket_url);
|
||||
Self {
|
||||
config,
|
||||
connected: false,
|
||||
@@ -231,7 +324,7 @@ impl WebSocketClient {
|
||||
|
||||
/// 连接到网关服务
|
||||
pub async fn connect(&mut self) -> Result<(), String> {
|
||||
println!("🔗 正在连接到网关WebSocket: {}", self.config.websocket_url);
|
||||
log!("🔗 正在连接到网关WebSocket: {}", self.config.websocket_url);
|
||||
|
||||
// 验证配置
|
||||
if let Err(e) = self.config.validate() {
|
||||
@@ -240,7 +333,7 @@ impl WebSocketClient {
|
||||
|
||||
// 这里需要实现具体的WebSocket连接逻辑
|
||||
// 暂时返回模拟连接成功
|
||||
println!("✅ WebSocket连接成功 (模拟)");
|
||||
log!("✅ WebSocket连接成功 (模拟)");
|
||||
self.connected = true;
|
||||
Ok(())
|
||||
}
|
||||
@@ -248,10 +341,10 @@ impl WebSocketClient {
|
||||
/// 断开连接
|
||||
pub async fn disconnect(&mut self) -> Result<(), String> {
|
||||
if self.connected {
|
||||
println!("🔌 断开WebSocket连接: {}", self.config.websocket_url);
|
||||
log!("🔌 断开WebSocket连接: {}", self.config.websocket_url);
|
||||
self.connected = false;
|
||||
} else {
|
||||
println!("⚠️ 没有活动的WebSocket连接需要断开");
|
||||
log!("⚠️ 没有活动的WebSocket连接需要断开");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -263,7 +356,7 @@ impl WebSocketClient {
|
||||
"task": _task
|
||||
});
|
||||
|
||||
println!("📤 WebSocket客户端发送任务: {:?} (心跳: {:?}, 超时: {:?})", _task.task_type, self.config.heartbeat_interval, self.config.connection_timeout);
|
||||
log!("📤 WebSocket客户端发送任务: {:?} (心跳: {:?}, 超时: {:?})", _task.task_type, self.config.heartbeat_interval, self.config.connection_timeout);
|
||||
|
||||
// 这里需要实现具体的发送逻辑
|
||||
// 暂时返回模拟响应
|
||||
@@ -287,7 +380,7 @@ impl WebSocketClient {
|
||||
|
||||
/// 检查连接状态
|
||||
pub fn is_connected(&self) -> bool {
|
||||
println!("🔗 WebSocket客户端连接状态: {}", if self.connected { "已连接" } else { "未连接" });
|
||||
log!("🔗 WebSocket客户端连接状态: {}", if self.connected { "已连接" } else { "未连接" });
|
||||
self.connected
|
||||
}
|
||||
}
|
||||
140
Claw/Server/gateway/src/log.txt
Normal file
140
Claw/Server/gateway/src/log.txt
Normal file
@@ -0,0 +1,140 @@
|
||||
C:\Disk\Gateway>gateway.exe
|
||||
🚀 网关服务启动中...
|
||||
📍 绑定地址: 127.0.0.1:8000 (通过nginx代理)
|
||||
📝 日志级别: info
|
||||
🔧 版本: 0.1.0
|
||||
🎯 环境: development
|
||||
🌐 外部访问: https://pactgo.cn (nginx代理)
|
||||
[03-23 13:08:18.414] 🚀 创建WebSocket连接池
|
||||
[03-23 13:08:18.415] ⚙️ 创建默认通信配置
|
||||
[03-23 13:08:18.415] 🚀 初始化任务处理服务
|
||||
[03-23 13:08:18.415] 📋 WebSocket连接池已创建
|
||||
[03-23 13:08:18.415] ⚙️ 通信配置已加载: "ws://localhost:8000/api/v1/ws/control"
|
||||
[03-23 13:08:18.415] 🏭 创建生产环境通信配置 (心跳: 30s, 超时: 60s)
|
||||
[03-23 13:08:18.415] 🚀 创建WebSocket客户端,配置URL: ws://pactgo.cn/api/v1/ws/control
|
||||
[2026-03-23T05:08:18Z INFO actix_server::builder] starting 2 workers
|
||||
✅ 网关服务已启动在 127.0.0.1:8000 (通过nginx代理)
|
||||
🔍 可用接口:
|
||||
🎯 企业微信回调 - 直接匹配企业微信配置
|
||||
POST /wecom - 企业微信回调(必须直接匹配企业微信配置)
|
||||
|
||||
📋 API接口(通过 /api/v1 前缀):
|
||||
GET /api/v1/health - 健康检查
|
||||
GET /api/v1/system - 系统信息
|
||||
POST /api/v1/task - 处理任务
|
||||
GET /api/v1/task/<task_id> - 查询任务状态
|
||||
GET /api/v1/tasks - 查询任务列表
|
||||
POST /api/v1/wechat/miniprogram/callback - 微信小程序回调
|
||||
GET /api/v1/ws/control - WebSocket控制通道
|
||||
GET /api/v1/ws/task - WebSocket任务通道
|
||||
POST /api/v1/test/websocket/send - WebSocket发送测试
|
||||
POST /api/v1/test/websocket/send_and_wait - WebSocket发送并等待测试
|
||||
GET /api/v1/test/websocket/get_manager - WebSocket管理器测试
|
||||
GET /api/v1/test/websocket/connection_send - WebSocket连接发送测试
|
||||
POST /api/v1/test/websocket/direct_send - WebSocket直接发送测试
|
||||
POST /api/v1/test/websocket/direct_send_and_wait - WebSocket直接发送并等待测试
|
||||
🌐 外部访问: https://pactgo.cn (nginx代理)
|
||||
🔗 WebSocket连接: wss://pactgo.cn/api/v1/ws/control
|
||||
[2026-03-23T05:08:18Z INFO actix_server::server] Actix runtime found; starting in Actix runtime
|
||||
[2026-03-23T05:08:18Z INFO actix_server::server] starting service: "actix-web-service-127.0.0.1:8000", workers: 2, listening on: 127.0.0.1:8000
|
||||
🔄 启动WebSocket客户端连接测试...
|
||||
[03-23 13:08:18.425] 🔗 正在连接到网关WebSocket: ws://pactgo.cn/api/v1/ws/control
|
||||
[03-23 13:08:18.425] 🔍 验证通信配置...
|
||||
[03-23 13:08:18.425] ✅ 通信配置验证通过 (URL: ws://pactgo.cn/api/v1/ws/control, 最大连接数: 100)
|
||||
[03-23 13:08:18.425] ✅ WebSocket连接成功 (模拟)
|
||||
✅ WebSocket客户端连接成功
|
||||
[03-23 13:08:18.426] 🔗 WebSocket客户端连接状态: 已连接
|
||||
🔗 WebSocket客户端已连接
|
||||
[03-23 13:08:18.426] 📤 WebSocket客户端发送任务: TextProcessing (心跳: 30s, 超时: 60s)
|
||||
✅ 测试任务发送成功: "任务处理成功(WebSocket模拟)"
|
||||
[03-23 13:08:18.428] 📋 获取所有连接信息: 0个连接
|
||||
[03-23 13:08:18.556] 🔗 收到WebSocket连接请求:
|
||||
[03-23 13:08:18.556] HttpRequest GET HTTP/1.1:/api/v1/ws/control
|
||||
[03-23 13:08:18.557] headers:
|
||||
[03-23 13:08:18.557] "upgrade": "websocket"
|
||||
[03-23 13:08:18.557] "x-api-key": "claw_secret_key"
|
||||
[03-23 13:08:18.557] "sec-websocket-version": "13"
|
||||
[03-23 13:08:18.558] "x-real-ip": "222.211.215.207"
|
||||
[03-23 13:08:18.558] "connection": "upgrade"
|
||||
[03-23 13:08:18.559] "host": "pactgo.cn"
|
||||
[03-23 13:08:18.560] "x-forwarded-for": "222.211.215.207"
|
||||
[03-23 13:08:18.560] "x-forwarded-proto": "https"
|
||||
[03-23 13:08:18.560] "sec-websocket-key": "xV8B16meVWRnAZI8FHb1ig=="
|
||||
[03-23 13:08:18.560] ✅ WebSocket连接认证通过
|
||||
[03-23 13:08:18.560] 🎯 检测到SmartClaw服务连接 (控制通道)
|
||||
[03-23 13:08:18.560] 🔗 开始WebSocket握手...
|
||||
[03-23 13:08:18.560] ✅ WebSocket握手成功
|
||||
[03-23 13:08:18.561] 🔌 添加新连接: 26dd6cb5-c305-421d-a8a3-753d0455b1c6
|
||||
[03-23 13:08:18.561] 🔌 添加WebSocket会话: 26dd6cb5-c305-421d-a8a3-753d0455b1c6
|
||||
[03-23 13:08:18.561] 🔌 创建新的WebSocket连接: id=26dd6cb5-c305-421d-a8a3-753d0455b1c6, type=SmartClaw
|
||||
[03-23 13:08:18.561] 🔄 启动WebSocket消息处理循环...
|
||||
[03-23 13:08:18.561] ✅ WebSocket连接已建立
|
||||
[03-23 13:08:18.561] ✅ WebSocket消息处理循环已启动
|
||||
[03-23 13:08:18.570] 📨 收到消息: {"service":"smartclaw","timestamp":1774242498,"type":"connect","version":"0.1.0"}
|
||||
[03-23 13:08:18.570] 🔗 收到连接消息
|
||||
[03-23 13:08:18.594] 📨 收到消息: {"service":"smartclaw","timestamp":1774242498,"type":"heartbeat"}
|
||||
[03-23 13:08:18.594] 💓 收到心跳消息
|
||||
[03-23 13:08:22.292] 📨 收到消息: {"service":"smartclaw","timestamp":1774242502,"type":"heartbeat"}
|
||||
[03-23 13:08:22.293] 💓 收到心跳消息
|
||||
[03-23 13:08:23.435] 🔌 断开WebSocket连接: ws://pactgo.cn/api/v1/ws/control
|
||||
🔌 WebSocket客户端已断开
|
||||
[03-23 13:08:37.412] 📱 收到企业微信回调
|
||||
[03-23 13:08:37.412] 请求方法: POST
|
||||
[03-23 13:08:37.414] 查询参数: msg_signature=d3c4aaed9b7bfdb3b7ac17119762e639bcaa3e2a×tamp=1774242517&nonce=1774420742
|
||||
[03-23 13:08:37.414] 📥 开始消息推送处理流程
|
||||
[03-23 13:08:37.415] 消息内容: <xml><ToUserName><![CDATA[wwa7bb7aec981103b4]]></ToUserName><Encrypt><![CDATA[sTK12sXLTGbDIHop8a/0+8lehw6CBjCdfXBdR1DwpyFWmt8fttKjChwIzocwgWXaPDPaB68Cs7+MHVg6VINthu4bLHS/lNgKGjYYsQHe5vrdEQ2OGUBOWIQqNNX3bb0e7Ls/zBecAw7blSQNKi4akxGF1anvMVuVfv8ZspdkkongM3jXFiQ0HJJJDv68ejWRpW9iY3XR5F3FsQgYB0UbJguKtO+JuBdxlLQ30PdI0VrNtWL6XbMZN1xwXWTqxwy/A7aXFv/7nc2w/HnPJZW21/2AX0K/1MIdQpOamtZ3LaV5/tu/bSqUaSEF8n2PHN3tpmynYQKtcq0+KZWEnUXmxqvTWotLkV8f6MPlkPrQ5cEmXZ+EcV8jS4v2y9gZvhQ7IUYJ3H78eoqKm0uGlKCnRM9Ac22F961lRKuT9Y+ggsCtlKwU3eDuKLNJ2rdAQ0oO4OxcvPVguuy9c5m6CepL2g==]]></Encrypt><AgentID><![CDATA[1000002]]></AgentID></xml>
|
||||
[03-23 13:08:37.415] 🔐 开始验证企业微信签名
|
||||
[03-23 13:08:37.415] 🔐 验证企业微信签名:
|
||||
[03-23 13:08:37.415] msg_signature: d3c4aaed9b7bfdb3b7ac17119762e639bcaa3e2a
|
||||
[03-23 13:08:37.415] timestamp: 1774242517
|
||||
[03-23 13:08:37.415] nonce: 1774420742
|
||||
[03-23 13:08:37.415] data: sTK12sXLTGbDIHop8a/0+8lehw6CBjCdfXBdR1DwpyFWmt8fttKjChwIzocwgWXaPDPaB68Cs7+MHVg6VINthu4bLHS/lNgKGjYYsQHe5vrdEQ2OGUBOWIQqNNX3bb0e7Ls/zBecAw7blSQNKi4akxGF1anvMVuVfv8ZspdkkongM3jXFiQ0HJJJDv68ejWRpW9iY3XR5F3FsQgYB0UbJguKtO+JuBdxlLQ30PdI0VrNtWL6XbMZN1xwXWTqxwy/A7aXFv/7nc2w/HnPJZW21/2AX0K/1MIdQpOamtZ3LaV5/tu/bSqUaSEF8n2PHN3tpmynYQKtcq0+KZWEnUXmxqvTWotLkV8f6MPlkPrQ5cEmXZ+EcV8jS4v2y9gZvhQ7IUYJ3H78eoqKm0uGlKCnRM9Ac22F961lRKuT9Y+ggsCtlKwU3eDuKLNJ2rdAQ0oO4OxcvPVguuy9c5m6CepL2g==
|
||||
[03-23 13:08:37.416] token: mytoken123456
|
||||
[03-23 13:08:37.416] 排序后拼接字符串: 17742425171774420742mytoken123456sTK12sXLTGbDIHop8a/0+8lehw6CBjCdfXBdR1DwpyFWmt8fttKjChwIzocwgWXaPDPaB68Cs7+MHVg6VINthu4bLHS/lNgKGjYYsQHe5vrdEQ2OGUBOWIQqNNX3bb0e7Ls/zBecAw7blSQNKi4akxGF1anvMVuVfv8ZspdkkongM3jXFiQ0HJJJDv68ejWRpW9iY3XR5F3FsQgYB0UbJguKtO+JuBdxlLQ30PdI0VrNtWL6XbMZN1xwXWTqxwy/A7aXFv/7nc2w/HnPJZW21/2AX0K/1MIdQpOamtZ3LaV5/tu/bSqUaSEF8n2PHN3tpmynYQKtcq0+KZWEnUXmxqvTWotLkV8f6MPlkPrQ5cEmXZ+EcV8jS4v2y9gZvhQ7IUYJ3H78eoqKm0uGlKCnRM9Ac22F961lRKuT9Y+ggsCtlKwU3eDuKLNJ2rdAQ0oO4OxcvPVguuy9c5m6CepL2g==
|
||||
[03-23 13:08:37.416] 计算签名: d3c4aaed9b7bfdb3b7ac17119762e639bcaa3e2a
|
||||
[03-23 13:08:37.416] 验证结果: ✅ 通过
|
||||
[03-23 13:08:37.416] ✅ 签名验证通过,开始处理消息
|
||||
[03-23 13:08:37.416] 📄 开始解析企业微信XML消息
|
||||
[03-23 13:08:37.417] 🔒 发现加密消息,开始解密
|
||||
[03-23 13:08:37.417] 提取到加密内容
|
||||
[03-23 13:08:37.417] ✅ 消息解密成功
|
||||
[03-23 13:08:37.417] 解密后内容: <xml><ToUserName><![CDATA[wwa7bb7aec981103b4]]></ToUserName><FromUserName><![CDATA[ZengQingMing]]></FromUserName><CreateTime>1774242517</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[好 像通了]]></Content><MsgId>7620313586460205140</MsgId><AgentID>1000002</AgentID></xml>
|
||||
[03-23 13:08:37.417] 发送者: ZengQingMing
|
||||
[03-23 13:08:37.417] 消息内容: 好像通了
|
||||
[03-23 13:08:37.417] 消息类型: text
|
||||
[03-23 13:08:37.418] 📋 获取所有连接信息: 1个连接
|
||||
[03-23 13:08:37.418] - 连接: 26dd6cb5-c305-421d-a8a3-753d0455b1c6 (连接时长: 18.8562652s)
|
||||
[03-23 13:08:37.418] 📤 准备发送回复消息给用户: ZengQingMing
|
||||
[03-23 13:08:37.418] ✅ 已开始发送回复消息: 思考中...
|
||||
[03-23 13:08:37.418] 🔄 开始转发消息到SmartClaw
|
||||
[03-23 13:08:37.418] 📤 发送消息到SmartClaw: Object {"data": Object {"content": String("好像通了"), "event": Null, "from_user_name": String("ZengQingMing"), "msg_signature": String("d3c4aaed9b7bfdb3b7ac17119762e639bcaa3e2a"), "msg_type": String("text"), "nonce": String("1774420742"), "raw_body": String("<xml><ToUserName><![CDATA[wwa7bb7aec981103b4]]></ToUserName><Encrypt><![CDATA[sTK12sXLTGbDIHop8a/0+8lehw6CBjCdfXBdR1DwpyFWmt8fttKjChwIzocwgWXaPDPaB68Cs7+MHVg6VINthu4bLHS/lNgKGjYYsQHe5vrdEQ2OGUBOWIQqNNX3bb0e7Ls/zBecAw7blSQNKi4akxGF1anvMVuVfv8ZspdkkongM3jXFiQ0HJJJDv68ejWRpW9iY3XR5F3FsQgYB0UbJguKtO+JuBdxlLQ30PdI0VrNtWL6XbMZN1xwXWTqxwy/A7aXFv/7nc2w/HnPJZW21/2AX0K/1MIdQpOamtZ3LaV5/tu/bSqUaSEF8n2PHN3tpmynYQKtcq0+KZWEnUXmxqvTWotLkV8f6MPlkPrQ5cEmXZ+EcV8jS4v2y9gZvhQ7IUYJ3H78eoqKm0uGlKCnRM9Ac22F961lRKuT9Y+ggsCtlKwU3eDuKLNJ2rdAQ0oO4OxcvPVguuy9c5m6CepL2g==]]></Encrypt><AgentID><![CDATA[1000002]]></AgentID></xml>"), "timestamp": String("1774242517")}, "type": String("wechat_message")}
|
||||
[03-23 13:08:37.418] ✅ 企业微信消息处理完成,返回 success
|
||||
[03-23 13:08:37.419] 📤 开始发送企业微信消息
|
||||
[03-23 13:08:37.420] 📋 获取所有连接信息: 1个连接
|
||||
[03-23 13:08:37.420] - 连接: 26dd6cb5-c305-421d-a8a3-753d0455b1c6 (连接时长: 18.858536s)
|
||||
[03-23 13:08:37.420] 📢 WebSocket连接池广播消息到 1 个SmartClaw连接
|
||||
[03-23 13:08:37.420] ✅ 发送消息到SmartClaw连接: 26dd6cb5-c305-421d-a8a3-753d0455b1c6
|
||||
[03-23 13:08:37.421] ✅ 消息已成功转发到SmartClaw
|
||||
[03-23 13:08:37.432] 📨 收到消息: {"data":{"content":"我收到了你的消息,正在处理...","from_user_name":"ZengQingMing","msg_type":"text","timestamp":1774242517},"type":"wechat_message_response"}
|
||||
[03-23 13:08:37.438] 📱 收到SmartClaw的微信消息回复
|
||||
[03-23 13:08:37.438] 回复发送者: ZengQingMing
|
||||
[03-23 13:08:37.438] 回复内容: 我收到了你的消息,正在处理...
|
||||
[03-23 13:08:37.438] 📤 开始发送企业微信消息
|
||||
[03-23 13:08:37.756] 获取到访问令牌: mUca6bQOJQ3fPnLdoZU9__QOLXUnBU9amY9yl-y1QJCb3oumKzRhKS_poGcXkOzaA58q221dGtVGISkQANsamvXYpM-q5bCQs-ok_WpzqZwBCiBuiVkZpzyQoBxPE_OKUCLvMKQW0Rx381LOBMK8G34ngBvUodnWo9hlvosPHe48qLpunLdQJjXtekpJe0HHOaAAoWfWWJLY1G60IMPIvA
|
||||
[03-23 13:08:37.811] 获取到访问令牌: mUca6bQOJQ3fPnLdoZU9__QOLXUnBU9amY9yl-y1QJCb3oumKzRhKS_poGcXkOzaA58q221dGtVGISkQANsamvXYpM-q5bCQs-ok_WpzqZwBCiBuiVkZpzyQoBxPE_OKUCLvMKQW0Rx381LOBMK8G34ngBvUodnWo9hlvosPHe48qLpunLdQJjXtekpJe0HHOaAAoWfWWJLY1G60IMPIvA
|
||||
[03-23 13:08:38.187] ✅ 消息发送成功
|
||||
[03-23 13:08:38.211] ✅ 消息发送成功
|
||||
[03-23 13:08:38.211] ✅ 企业微信回复消息发送成功
|
||||
[03-23 13:08:48.436] 📋 获取所有连接信息: 1个连接
|
||||
[03-23 13:08:48.436] - 连接: 26dd6cb5-c305-421d-a8a3-753d0455b1c6 (连接时长: 29.8742601s)
|
||||
🔍 测试WebSocket连接 - 发现 1 个连接
|
||||
📤 准备向连接 26dd6cb5-c305-421d-a8a3-753d0455b1c6 发送测试消息
|
||||
✅ 模拟发送测试消息: {"message":"连接健康检查","timestamp":1774242528,"type":"health_check"}
|
||||
🧪 测试WebSocketConnection的send方法
|
||||
📤 send方法测试结果: {"connection_id":"26dd6cb5-c305-421d-a8a3-753d0455b1c6","test_message":"这是send方法的测试消息","type":"test_send"}
|
||||
🧪 测试WebSocketConnection的send_and_wait方法
|
||||
⏱️ send_and_wait方法测试结果: {"connection_id":"26dd6cb5-c305-421d-a8a3-753d0455b1c6","request":"这是send_and_wait方法的测试请求","response":"模拟响应数据","timeout":5000,"type":"test_send_and_wait"}
|
||||
[03-23 13:08:48.572] 📨 收到消息: {"service":"smartclaw","timestamp":1774242528,"type":"heartbeat"}
|
||||
[03-23 13:08:48.572] 💓 收到心跳消息
|
||||
[03-23 13:08:52.293] 📨 收到消息: {"service":"smartclaw","timestamp":1774242532,"type":"heartbeat"}
|
||||
[03-23 13:08:52.293] 💓 收到心跳消息
|
||||
@@ -4,11 +4,26 @@ use actix_ws::{Message};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::env;
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
use tokio::sync::RwLock;
|
||||
use base64;
|
||||
use shared::{TaskRequest, TaskResponse, HealthResponse, utils};
|
||||
use sha1::{Sha1, Digest};
|
||||
use futures::StreamExt;
|
||||
use crate::communication::ConnectionInfo;
|
||||
|
||||
/// 获取当前本地时间的格式化字符串
|
||||
fn get_current_time() -> String {
|
||||
let now = chrono::Local::now();
|
||||
now.format("%m-%d %H:%M:%S%.3f").to_string()
|
||||
}
|
||||
|
||||
/// 带时间前缀的打印宏
|
||||
macro_rules! log {
|
||||
($($arg:tt)*) => {
|
||||
println!("[{}] {}", get_current_time(), format!($($arg)*));
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
mod communication;
|
||||
@@ -56,13 +71,13 @@ async fn get_wechat_access_token() -> Result<String, Box<dyn std::error::Error>>
|
||||
/// 发送企业微信消息
|
||||
async fn send_wechat_message(touser: &str, content: &str, debug: bool) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if debug {
|
||||
println!("📤 开始发送企业微信消息");
|
||||
log!("📤 开始发送企业微信消息");
|
||||
}
|
||||
|
||||
// 获取访问令牌
|
||||
let access_token = get_wechat_access_token().await?;
|
||||
if debug {
|
||||
println!(" 获取到访问令牌: {}", access_token);
|
||||
log!(" 获取到访问令牌: {}", access_token);
|
||||
}
|
||||
|
||||
// 构建消息
|
||||
@@ -88,12 +103,12 @@ async fn send_wechat_message(touser: &str, content: &str, debug: bool) -> Result
|
||||
|
||||
if result.errcode == 0 {
|
||||
if debug {
|
||||
println!("✅ 消息发送成功");
|
||||
log!("✅ 消息发送成功");
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
if debug {
|
||||
println!("❌ 消息发送失败: {} - {}", result.errcode, result.errmsg);
|
||||
log!("❌ 消息发送失败: {} - {}", result.errcode, result.errmsg);
|
||||
}
|
||||
Err(format!("消息发送失败: {} - {}", result.errcode, result.errmsg).into())
|
||||
}
|
||||
@@ -217,27 +232,27 @@ fn get_wechat_config() -> (String, String, String, String, bool, bool, bool) {
|
||||
/// 解析企业微信XML消息
|
||||
fn parse_wechat_xml_message(xml_content: &str, debug: bool) -> (Option<String>, Option<String>, Option<String>, Option<String>) {
|
||||
if debug {
|
||||
println!("📄 开始解析企业微信XML消息");
|
||||
log!("📄 开始解析企业微信XML消息");
|
||||
}
|
||||
|
||||
// 检查是否为加密消息
|
||||
if xml_content.contains("<Encrypt>") {
|
||||
if debug {
|
||||
println!("🔒 发现加密消息,开始解密");
|
||||
log!("🔒 发现加密消息,开始解密");
|
||||
}
|
||||
|
||||
// 提取Encrypt标签内容
|
||||
if let Some(encrypt_content) = extract_xml_tag(xml_content, "Encrypt") {
|
||||
if debug {
|
||||
println!(" 提取到加密内容");
|
||||
log!(" 提取到加密内容");
|
||||
}
|
||||
|
||||
// 解密消息
|
||||
match decrypt_wechat_message(&encrypt_content) {
|
||||
Ok(decrypted_xml) => {
|
||||
if debug {
|
||||
println!("✅ 消息解密成功");
|
||||
println!(" 解密后内容: {}", decrypted_xml);
|
||||
log!("✅ 消息解密成功");
|
||||
log!(" 解密后内容: {}", decrypted_xml);
|
||||
}
|
||||
|
||||
// 从解密后的XML中提取发送者、内容、消息类型和事件类型
|
||||
@@ -248,16 +263,16 @@ fn parse_wechat_xml_message(xml_content: &str, debug: bool) -> (Option<String>,
|
||||
|
||||
if debug {
|
||||
if let Some(from_user) = &from_user_name {
|
||||
println!(" 发送者: {}", from_user);
|
||||
log!(" 发送者: {}", from_user);
|
||||
}
|
||||
if let Some(msg_content) = &content {
|
||||
println!(" 消息内容: {}", msg_content);
|
||||
log!(" 消息内容: {}", msg_content);
|
||||
}
|
||||
if let Some(msg_type_val) = &msg_type {
|
||||
println!(" 消息类型: {}", msg_type_val);
|
||||
log!(" 消息类型: {}", msg_type_val);
|
||||
}
|
||||
if let Some(event_val) = &event {
|
||||
println!(" 事件类型: {}", event_val);
|
||||
log!(" 事件类型: {}", event_val);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,21 +280,21 @@ fn parse_wechat_xml_message(xml_content: &str, debug: bool) -> (Option<String>,
|
||||
}
|
||||
Err(e) => {
|
||||
if debug {
|
||||
println!("❌ 消息解密失败: {}", e);
|
||||
log!("❌ 消息解密失败: {}", e);
|
||||
}
|
||||
return (None, None, None, None);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if debug {
|
||||
println!("❌ 无法提取Encrypt标签");
|
||||
log!("❌ 无法提取Encrypt标签");
|
||||
}
|
||||
return (None, None, None, None);
|
||||
}
|
||||
} else {
|
||||
// 非加密消息,直接解析
|
||||
if debug {
|
||||
println!("🔓 非加密消息,直接解析");
|
||||
log!("🔓 非加密消息,直接解析");
|
||||
}
|
||||
let from_user_name = extract_xml_tag(xml_content, "FromUserName");
|
||||
let content = extract_xml_tag(xml_content, "Content");
|
||||
@@ -288,16 +303,16 @@ fn parse_wechat_xml_message(xml_content: &str, debug: bool) -> (Option<String>,
|
||||
|
||||
if debug {
|
||||
if let Some(from_user) = &from_user_name {
|
||||
println!(" 发送者: {}", from_user);
|
||||
log!(" 发送者: {}", from_user);
|
||||
}
|
||||
if let Some(msg_content) = &content {
|
||||
println!(" 消息内容: {}", msg_content);
|
||||
log!(" 消息内容: {}", msg_content);
|
||||
}
|
||||
if let Some(msg_type_val) = &msg_type {
|
||||
println!(" 消息类型: {}", msg_type_val);
|
||||
log!(" 消息类型: {}", msg_type_val);
|
||||
}
|
||||
if let Some(event_val) = &event {
|
||||
println!(" 事件类型: {}", event_val);
|
||||
log!(" 事件类型: {}", event_val);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -408,12 +423,12 @@ impl TaskService {
|
||||
/// 创建新的任务处理服务
|
||||
fn new() -> Self {
|
||||
let connection_manager = Arc::new(RwLock::new(ConnectionManager::new()));
|
||||
let websocket_pool = WebSocketPool::new();
|
||||
let websocket_pool = WebSocketPool::new(connection_manager.clone());
|
||||
let communication_config = CommunicationConfig::default();
|
||||
|
||||
println!("🚀 初始化任务处理服务");
|
||||
println!("📋 WebSocket连接池已创建");
|
||||
println!("⚙️ 通信配置已加载: {:?}", communication_config.websocket_url);
|
||||
log!("🚀 初始化任务处理服务");
|
||||
log!("📋 WebSocket连接池已创建");
|
||||
log!("⚙️ 通信配置已加载: {:?}", communication_config.websocket_url);
|
||||
|
||||
Self {
|
||||
connection_manager,
|
||||
@@ -424,10 +439,10 @@ impl TaskService {
|
||||
|
||||
/// 处理任务请求 - 现在通过WebSocket发送给内网服务器
|
||||
async fn process_task(&self, task: TaskRequest) -> TaskResponse {
|
||||
println!("📝 收到任务请求:");
|
||||
println!(" 用户ID: {}", task.user_id);
|
||||
println!(" 任务类型: {}", task.task_type);
|
||||
println!(" 内容长度: {} 字符", task.content.len());
|
||||
log!("📝 收到任务请求:");
|
||||
log!(" 用户ID: {}", task.user_id);
|
||||
log!(" 任务类型: {}", task.task_type);
|
||||
log!(" 内容长度: {} 字符", task.content.len());
|
||||
|
||||
// 验证任务参数
|
||||
if task.content.is_empty() {
|
||||
@@ -442,15 +457,15 @@ impl TaskService {
|
||||
let task_id = utils::generate_task_id(&task.user_id);
|
||||
|
||||
// 通过WebSocket连接发送任务到内网服务器
|
||||
println!("🚀 通过WebSocket发送任务到内网服务器...");
|
||||
log!("🚀 通过WebSocket发送任务到内网服务器...");
|
||||
match self.send_task_via_websocket(task.clone()).await {
|
||||
Ok(response) => {
|
||||
println!("✅ 任务处理成功");
|
||||
log!("✅ 任务处理成功");
|
||||
response
|
||||
},
|
||||
Err(e) => {
|
||||
println!("❌ WebSocket任务发送失败: {}", e);
|
||||
println!("🎭 使用模拟响应");
|
||||
log!("❌ WebSocket任务发送失败: {}", e);
|
||||
log!("🎭 使用模拟响应");
|
||||
self.create_mock_response(task_id, task)
|
||||
}
|
||||
}
|
||||
@@ -459,7 +474,7 @@ impl TaskService {
|
||||
/// 通过WebSocket发送任务到内网服务器
|
||||
async fn send_task_via_websocket(&self, task: TaskRequest) -> Result<TaskResponse, String> {
|
||||
// 使用通信配置
|
||||
println!("⚙️ 使用通信配置 - 心跳间隔: {:?}, 连接超时: {:?}",
|
||||
log!("⚙️ 使用通信配置 - 心跳间隔: {:?}, 连接超时: {:?}",
|
||||
self.communication_config.heartbeat_interval,
|
||||
self.communication_config.connection_timeout);
|
||||
|
||||
@@ -477,11 +492,11 @@ impl TaskService {
|
||||
// 使用WebSocket池广播任务消息
|
||||
match self.websocket_pool.broadcast(task_message.clone()).await {
|
||||
Ok(_) => {
|
||||
println!("📤 任务已通过WebSocket广播到所有连接");
|
||||
log!("📤 任务已通过WebSocket广播到所有连接");
|
||||
|
||||
// 获取连接池统计信息
|
||||
let pool_stats = self.websocket_pool.get_pool_stats();
|
||||
println!("📊 WebSocket连接池统计: {}", pool_stats);
|
||||
log!("📊 WebSocket连接池统计: {}", pool_stats);
|
||||
|
||||
// 尝试发送到特定连接(如果有的话)
|
||||
if let Some(connection_info) = manager.get_all_connections().first() {
|
||||
@@ -493,16 +508,16 @@ impl TaskService {
|
||||
|
||||
match self.websocket_pool.send_to_connection(&connection_info.id, specific_message).await {
|
||||
Ok(_) => {
|
||||
println!("📨 任务已发送到特定连接: {}", connection_info.id);
|
||||
log!("📨 任务已发送到特定连接: {}", connection_info.id);
|
||||
},
|
||||
Err(e) => {
|
||||
println!("⚠️ 发送到特定连接失败: {}", e);
|
||||
log!("⚠️ 发送到特定连接失败: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
println!("⚠️ WebSocket广播失败: {}", e);
|
||||
log!("⚠️ WebSocket广播失败: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -529,7 +544,7 @@ impl TaskService {
|
||||
|
||||
/// 创建模拟响应(当WebSocket不可用时)
|
||||
fn create_mock_response(&self, task_id: String, task: TaskRequest) -> TaskResponse {
|
||||
println!("🎭 创建模拟响应");
|
||||
log!("🎭 创建模拟响应");
|
||||
|
||||
let result = match task.task_type {
|
||||
shared::TaskType::TextProcessing => {
|
||||
@@ -592,17 +607,17 @@ impl TaskService {
|
||||
/// 验证企业微信签名
|
||||
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);
|
||||
log!("🔐 验证企业微信签名:");
|
||||
log!(" msg_signature: {}", msg_signature);
|
||||
log!(" timestamp: {}", timestamp);
|
||||
log!(" nonce: {}", nonce);
|
||||
log!(" data: {}", data);
|
||||
}
|
||||
|
||||
// 获取企业微信配置
|
||||
let (token, _corp_id, _encoding_aes_key, _corp_secret, _, _, _) = get_wechat_config();
|
||||
if debug {
|
||||
println!(" token: {}", token);
|
||||
log!(" token: {}", token);
|
||||
}
|
||||
|
||||
// 企业微信签名验证算法(严格按照官方要求)
|
||||
@@ -613,7 +628,7 @@ impl TaskService {
|
||||
// 2. 将排序后的参数拼接成一个字符串
|
||||
let combined = params.join("");
|
||||
if debug {
|
||||
println!(" 排序后拼接字符串: {}", combined);
|
||||
log!(" 排序后拼接字符串: {}", combined);
|
||||
}
|
||||
|
||||
// 3. 进行sha1加密
|
||||
@@ -625,8 +640,8 @@ impl TaskService {
|
||||
// 4. 与msg_signature对比
|
||||
let is_valid = computed_signature == msg_signature;
|
||||
if debug {
|
||||
println!(" 计算签名: {}", computed_signature);
|
||||
println!(" 验证结果: {}", if is_valid { "✅ 通过" } else { "❌ 失败" });
|
||||
log!(" 计算签名: {}", computed_signature);
|
||||
log!(" 验证结果: {}", if is_valid { "✅ 通过" } else { "❌ 失败" });
|
||||
}
|
||||
|
||||
is_valid
|
||||
@@ -634,10 +649,10 @@ impl TaskService {
|
||||
|
||||
/// 验证微信小程序签名
|
||||
fn validate_miniprogram_signature(signature: &str, data: &str, session_key: &str) -> bool {
|
||||
println!("🔐 验证微信小程序签名:");
|
||||
println!(" signature: {}", signature);
|
||||
println!(" data: {}", data);
|
||||
println!(" session_key: {}", session_key);
|
||||
log!("🔐 验证微信小程序签名:");
|
||||
log!(" signature: {}", signature);
|
||||
log!(" data: {}", data);
|
||||
log!(" session_key: {}", session_key);
|
||||
|
||||
// 微信小程序签名验证算法
|
||||
// 1. 将session_key和data拼接
|
||||
@@ -652,8 +667,8 @@ impl TaskService {
|
||||
|
||||
// 3. 与signature对比
|
||||
let is_valid = computed_signature == signature;
|
||||
println!(" 计算签名: {}", computed_signature);
|
||||
println!(" 验证结果: {}", if is_valid { "✅ 通过" } else { "❌ 失败" });
|
||||
log!(" 计算签名: {}", computed_signature);
|
||||
log!(" 验证结果: {}", if is_valid { "✅ 通过" } else { "❌ 失败" });
|
||||
|
||||
is_valid
|
||||
}
|
||||
@@ -663,13 +678,13 @@ impl TaskService {
|
||||
async fn websocket_handler(
|
||||
req: HttpRequest,
|
||||
body: web::Payload,
|
||||
_app_data: web::Data<TaskService>,
|
||||
app_data: web::Data<TaskService>,
|
||||
) -> Result<HttpResponse, actix_web::Error> {
|
||||
println!("🔗 收到WebSocket连接请求: ");
|
||||
println!(" HttpRequest {:?} {:?}:{}", req.method(), req.version(), req.path());
|
||||
println!(" headers: ");
|
||||
log!("🔗 收到WebSocket连接请求: ");
|
||||
log!(" HttpRequest {:?} {:?}:{}", req.method(), req.version(), req.path());
|
||||
log!(" headers: ");
|
||||
for (name, value) in req.headers() {
|
||||
println!(" {:?}: {:?}", name, value);
|
||||
log!(" {:?}: {:?}", name, value);
|
||||
}
|
||||
|
||||
// 验证连接来源(可以添加API密钥验证)
|
||||
@@ -680,58 +695,129 @@ async fn websocket_handler(
|
||||
let expected_key = env::var("WEBSOCKET_API_KEY").unwrap_or_else(|_| "claw_secret_key".to_string());
|
||||
|
||||
if api_key != expected_key {
|
||||
println!("❌ WebSocket连接认证失败");
|
||||
log!("❌ WebSocket连接认证失败");
|
||||
return Err(actix_web::error::ErrorUnauthorized("Invalid API key"));
|
||||
}
|
||||
|
||||
println!("✅ WebSocket连接认证通过");
|
||||
log!("✅ WebSocket连接认证通过");
|
||||
|
||||
// 获取请求路径,区分连接类型
|
||||
let path = req.path();
|
||||
let is_control_connection = path == "/api/v1/ws/control";
|
||||
|
||||
if is_control_connection {
|
||||
println!("🎯 检测到SmartClaw服务连接 (控制通道)");
|
||||
log!("🎯 检测到SmartClaw服务连接 (控制通道)");
|
||||
} else {
|
||||
println!("📱 检测到设备连接 (任务通道)");
|
||||
log!("📱 检测到设备连接 (任务通道)");
|
||||
}
|
||||
|
||||
println!("🔗 开始WebSocket握手...");
|
||||
log!("🔗 开始WebSocket握手...");
|
||||
// 使用actix-ws处理WebSocket连接
|
||||
let (response, mut session, msg_stream) = match actix_ws::handle(&req, body) {
|
||||
Ok(result) => {
|
||||
println!("✅ WebSocket握手成功");
|
||||
log!("✅ WebSocket握手成功");
|
||||
result
|
||||
},
|
||||
Err(e) => {
|
||||
println!("❌ WebSocket握手失败: {}", e);
|
||||
log!("❌ WebSocket握手失败: {}", e);
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
|
||||
println!("🔄 启动WebSocket消息处理循环...");
|
||||
// 生成连接ID
|
||||
let connection_id = uuid::Uuid::new_v4().to_string();
|
||||
|
||||
// 添加连接到连接管理器
|
||||
{{
|
||||
let mut manager = app_data.connection_manager.write().await;
|
||||
let connection_info = ConnectionInfo {
|
||||
id: connection_id.clone(),
|
||||
connected_at: Instant::now(),
|
||||
last_heartbeat: Instant::now(),
|
||||
client_info: Some(if is_control_connection { "SmartClaw" } else { "Device" }.to_string()),
|
||||
};
|
||||
manager.add_connection(connection_info);
|
||||
// 保存会话到WebSocketPool
|
||||
app_data.websocket_pool.add_session(&connection_id, session.clone()).await;
|
||||
log!("🔌 创建新的WebSocket连接: id={}, type={}", connection_id, if is_control_connection { "SmartClaw" } else { "Device" });
|
||||
}}
|
||||
|
||||
log!("🔄 启动WebSocket消息处理循环...");
|
||||
// 启动WebSocket消息处理循环
|
||||
actix_web::rt::spawn(async move {
|
||||
println!("✅ WebSocket消息处理循环已启动");
|
||||
log!("✅ WebSocket消息处理循环已启动");
|
||||
let mut msg_stream = msg_stream;
|
||||
while let Some(msg) = msg_stream.next().await {
|
||||
match msg {
|
||||
Ok(Message::Text(text)) => {
|
||||
println!("📨 收到消息: {}", text);
|
||||
log!("📨 收到消息: {}", text);
|
||||
// 处理消息
|
||||
if let Ok(parsed) = serde_json::from_str::<serde_json::Value>(&text) {
|
||||
match parsed.get("type").and_then(|t| t.as_str()) {
|
||||
Some("wechat_message_response") => {
|
||||
// 处理SmartClaw的微信消息回复
|
||||
log!("📱 收到SmartClaw的微信消息回复");
|
||||
if let Some(data) = parsed.get("data") {
|
||||
if let Some(from_user_name) = data.get("from_user_name").and_then(|v| v.as_str()) {
|
||||
if let Some(content) = data.get("content").and_then(|v| v.as_str()) {
|
||||
log!(" 回复发送者: {}", from_user_name);
|
||||
log!(" 回复内容: {}", content);
|
||||
|
||||
// 发送回复到企业微信
|
||||
let from_user_name_clone = from_user_name.to_string();
|
||||
let content_clone = content.to_string();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = send_wechat_message(&from_user_name_clone, &content_clone, true).await {
|
||||
log!("❌ 发送企业微信回复消息失败: {}", e);
|
||||
} else {
|
||||
log!("✅ 企业微信回复消息发送成功");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some("heartbeat") => {
|
||||
// 处理心跳消息
|
||||
log!("💓 收到心跳消息");
|
||||
// 更新心跳时间
|
||||
let mut manager = app_data.connection_manager.write().await;
|
||||
manager.update_heartbeat(&connection_id);
|
||||
}
|
||||
Some("connect") => {
|
||||
// 处理连接消息
|
||||
log!("🔗 收到连接消息");
|
||||
}
|
||||
Some(msg_type) => {
|
||||
log!("❓ 收到未知消息类型: {}", msg_type);
|
||||
}
|
||||
None => {
|
||||
log!("❓ 收到无类型消息");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Message::Binary(bin)) => {
|
||||
println!("📨 收到二进制消息: {} bytes", bin.len());
|
||||
log!("📨 收到二进制消息: {} bytes", bin.len());
|
||||
}
|
||||
Ok(Message::Ping(msg)) => {
|
||||
println!("📨 收到Ping");
|
||||
log!("📨 收到Ping");
|
||||
let _ = session.pong(&msg).await;
|
||||
// 更新心跳时间
|
||||
let mut manager = app_data.connection_manager.write().await;
|
||||
manager.update_heartbeat(&connection_id);
|
||||
}
|
||||
Ok(Message::Pong(_)) => {
|
||||
println!("📨 收到Pong");
|
||||
log!("📨 收到Pong");
|
||||
}
|
||||
Ok(Message::Close(reason)) => {
|
||||
println!("📨 收到关闭消息: {:?}", reason);
|
||||
log!("📨 收到关闭消息: {:?}", reason);
|
||||
// 从连接管理器中移除连接
|
||||
let mut manager = app_data.connection_manager.write().await;
|
||||
manager.remove_connection(&connection_id);
|
||||
// 从WebSocketPool中移除会话
|
||||
app_data.websocket_pool.remove_session(&connection_id).await;
|
||||
log!("🔌 移除WebSocket连接: {}", connection_id);
|
||||
break;
|
||||
}
|
||||
Ok(Message::Continuation(_)) => {
|
||||
@@ -741,16 +827,22 @@ async fn websocket_handler(
|
||||
// 处理 nop 消息
|
||||
}
|
||||
Err(e) => {
|
||||
println!("❌ WebSocket错误: {}", e);
|
||||
log!("❌ WebSocket错误: {}", e);
|
||||
// 从连接管理器中移除连接
|
||||
let mut manager = app_data.connection_manager.write().await;
|
||||
manager.remove_connection(&connection_id);
|
||||
// 从WebSocketPool中移除会话
|
||||
app_data.websocket_pool.remove_session(&connection_id).await;
|
||||
log!("🔌 移除WebSocket连接: {}", connection_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("🔚 WebSocket连接已关闭");
|
||||
log!("🔚 WebSocket连接已关闭");
|
||||
});
|
||||
|
||||
println!("✅ WebSocket连接已建立");
|
||||
log!("✅ WebSocket连接已建立");
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
@@ -761,7 +853,7 @@ async fn health_check(app_data: web::Data<TaskService>) -> impl Responder {
|
||||
|
||||
// 获取连接管理器引用(用于测试)
|
||||
let manager_ref = app_data.websocket_pool.get_manager_ref();
|
||||
println!("📋 健康检查 - 连接管理器引用: {:?}", manager_ref.as_ref() as *const _);
|
||||
log!("📋 健康检查 - 连接管理器引用: {:?}", manager_ref.as_ref() as *const _);
|
||||
|
||||
let response = HealthResponse {
|
||||
status: "healthy".to_string(),
|
||||
@@ -836,7 +928,7 @@ async fn test_websocket_connection_send(app_data: web::Data<TaskService>) -> imp
|
||||
}
|
||||
|
||||
/// 企业微信回调处理器
|
||||
async fn handle_wechat_callback(req: HttpRequest, body: web::Bytes) -> impl Responder {
|
||||
async fn handle_wechat_callback(req: HttpRequest, body: web::Bytes, app_data: web::Data<TaskService>) -> impl Responder {
|
||||
// 获取企业微信配置,包括debug配置
|
||||
let (_, _, _, _, debug_wechat, _debug_config, _http) = get_wechat_config();
|
||||
|
||||
@@ -847,11 +939,11 @@ async fn handle_wechat_callback(req: HttpRequest, body: web::Bytes) -> impl Resp
|
||||
let query_string = req.query_string();
|
||||
|
||||
// 根据debug配置控制日志输出
|
||||
if debug_wechat {
|
||||
println!("📱 收到企业微信回调");
|
||||
println!(" 请求方法: {}", method);
|
||||
println!(" 查询参数: {}", query_string);
|
||||
}
|
||||
if debug_wechat {
|
||||
log!("📱 收到企业微信回调");
|
||||
log!(" 请求方法: {}", method);
|
||||
log!(" 查询参数: {}", query_string);
|
||||
}
|
||||
|
||||
// 解析查询参数
|
||||
#[derive(Deserialize)]
|
||||
@@ -865,18 +957,18 @@ async fn handle_wechat_callback(req: HttpRequest, body: web::Bytes) -> impl Resp
|
||||
let query: WeChatQuery = match web::Query::<WeChatQuery>::from_query(query_string) {
|
||||
Ok(q) => q.into_inner(),
|
||||
Err(e) => {
|
||||
if debug_wechat {
|
||||
println!("❌ 解析查询参数失败: {}", e);
|
||||
if debug_wechat {
|
||||
log!("❌ 解析查询参数失败: {}", e);
|
||||
}
|
||||
return HttpResponse::BadRequest().body("error");
|
||||
}
|
||||
return HttpResponse::BadRequest().body("error");
|
||||
}
|
||||
};
|
||||
|
||||
// 核心判断:GET 和 POST 必须分开处理
|
||||
if method == &Method::GET {
|
||||
// 1. GET请求 = URL 验证
|
||||
if debug_wechat {
|
||||
println!("🔐 开始URL验证流程");
|
||||
log!("🔐 开始URL验证流程");
|
||||
}
|
||||
|
||||
// 验证签名
|
||||
@@ -889,32 +981,32 @@ async fn handle_wechat_callback(req: HttpRequest, body: web::Bytes) -> impl Resp
|
||||
);
|
||||
|
||||
if !is_valid {
|
||||
if debug_wechat {
|
||||
println!("❌ URL验证签名失败");
|
||||
if debug_wechat {
|
||||
log!("❌ URL验证签名失败");
|
||||
}
|
||||
return HttpResponse::Unauthorized().body("invalid signature");
|
||||
}
|
||||
return HttpResponse::Unauthorized().body("invalid signature");
|
||||
}
|
||||
|
||||
// 验证通过,返回echostr
|
||||
if let Some(echostr) = query.echostr {
|
||||
if debug_wechat {
|
||||
println!("✅ URL验证成功,返回 echostr: {}", echostr);
|
||||
log!("✅ URL验证成功,返回 echostr: {}", echostr);
|
||||
}
|
||||
return HttpResponse::Ok().body(echostr);
|
||||
} else {
|
||||
if debug_wechat {
|
||||
println!("❌ URL验证失败:缺少echostr参数");
|
||||
log!("❌ URL验证失败:缺少echostr参数");
|
||||
}
|
||||
return HttpResponse::BadRequest().body("missing echostr");
|
||||
}
|
||||
} else if method == &Method::POST {
|
||||
// 2. POST请求 = 消息推送
|
||||
if debug_wechat {
|
||||
println!("📥 开始消息推送处理流程");
|
||||
log!("📥 开始消息推送处理流程");
|
||||
|
||||
// 处理实际的消息回调
|
||||
let body_str = String::from_utf8_lossy(&body);
|
||||
println!(" 消息内容: {}", body_str);
|
||||
log!(" 消息内容: {}", body_str);
|
||||
}
|
||||
|
||||
// 提取Encrypt字段内容
|
||||
@@ -923,7 +1015,7 @@ async fn handle_wechat_callback(req: HttpRequest, body: web::Bytes) -> impl Resp
|
||||
|
||||
// 验证签名(POST请求需要包含Encrypt字段)
|
||||
if debug_wechat {
|
||||
println!("🔐 开始验证企业微信签名");
|
||||
log!("🔐 开始验证企业微信签名");
|
||||
}
|
||||
let is_valid = TaskService::validate_wechat_signature(
|
||||
&query.msg_signature,
|
||||
@@ -934,78 +1026,131 @@ async fn handle_wechat_callback(req: HttpRequest, body: web::Bytes) -> impl Resp
|
||||
);
|
||||
|
||||
if !is_valid {
|
||||
if debug_wechat {
|
||||
println!("❌ 消息推送签名验证失败");
|
||||
if debug_wechat {
|
||||
log!("❌ 消息推送签名验证失败");
|
||||
}
|
||||
return HttpResponse::Unauthorized().body("invalid signature");
|
||||
}
|
||||
return HttpResponse::Unauthorized().body("invalid signature");
|
||||
}
|
||||
|
||||
if debug_wechat {
|
||||
println!("✅ 签名验证通过,开始处理消息");
|
||||
log!("✅ 签名验证通过,开始处理消息");
|
||||
}
|
||||
|
||||
// 解析XML消息
|
||||
let (from_user_name, content, msg_type, event) = parse_wechat_xml_message(&body_str, debug_wechat);
|
||||
|
||||
// 检查是否有可用的SmartClaw连接
|
||||
let manager = app_data.connection_manager.read().await;
|
||||
let connections = manager.get_all_connections();
|
||||
let has_smartclaw_connections = connections.iter()
|
||||
.any(|conn| conn.client_info.as_ref().map(|info| info == "SmartClaw").unwrap_or(false));
|
||||
let has_connections = has_smartclaw_connections;
|
||||
|
||||
// 尝试发送回复消息
|
||||
if let Some(user_id) = from_user_name {
|
||||
// 判断是否需要回复:只回复文本消息和非位置上报事件
|
||||
if let Some(ref user_id) = from_user_name {
|
||||
// 判断是否需要回复:只回复文本消息
|
||||
let should_reply = match msg_type.as_deref() {
|
||||
Some("text") => true, // 文本消息需要回复
|
||||
Some("event") => {
|
||||
// 事件消息中,除了位置上报事件外都回复
|
||||
event.as_deref() != Some("LOCATION")
|
||||
}
|
||||
Some("text") => true, // 只有文本消息需要回复
|
||||
_ => false, // 其他类型消息不回复
|
||||
};
|
||||
|
||||
if should_reply {
|
||||
if debug_wechat {
|
||||
println!("📤 准备发送回复消息给用户: {}", user_id);
|
||||
}
|
||||
if debug_wechat {
|
||||
log!("📤 准备发送回复消息给用户: {}", user_id);
|
||||
}
|
||||
|
||||
let reply_content = if let Some(msg_content) = content {
|
||||
format!("我收到你的消息啦!你说: {}", msg_content)
|
||||
// 根据连接状态选择回复内容
|
||||
let reply_content = if has_connections {
|
||||
"思考中..."
|
||||
} 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 let Err(e) = send_wechat_message(user_id.as_str(), reply_content, debug_wechat).await {
|
||||
if debug_wechat {
|
||||
log!("❌ 发送回复消息失败: {}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if debug_wechat {
|
||||
println!("✅ 已开始发送回复消息");
|
||||
}
|
||||
log!("✅ 已开始发送回复消息: {}", reply_content);
|
||||
}
|
||||
} else {
|
||||
if debug_wechat {
|
||||
println!("⚠️ 消息类型不需要回复,跳过回复");
|
||||
log!("⚠️ 消息类型不需要回复,跳过回复");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if debug_wechat {
|
||||
println!("⚠️ 无法获取发送者信息,跳过回复");
|
||||
log!("⚠️ 无法获取发送者信息,跳过回复");
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有连接且是文本消息,转发消息到SmartClaw
|
||||
if has_connections && msg_type.as_deref() == Some("text") {
|
||||
// 转发消息到SmartClaw
|
||||
if debug_wechat {
|
||||
log!("🔄 开始转发消息到SmartClaw");
|
||||
}
|
||||
|
||||
// 构建转发消息
|
||||
let wechat_message = serde_json::json!({
|
||||
"type": "wechat_message",
|
||||
"data": {
|
||||
"from_user_name": from_user_name,
|
||||
"content": content,
|
||||
"msg_type": msg_type,
|
||||
"event": event,
|
||||
"raw_body": body_str.to_string(),
|
||||
"timestamp": query.timestamp,
|
||||
"nonce": query.nonce,
|
||||
"msg_signature": query.msg_signature
|
||||
}
|
||||
});
|
||||
|
||||
// 通过WebSocket发送消息到SmartClaw
|
||||
if debug_wechat {
|
||||
log!("📤 发送消息到SmartClaw: {:?}", wechat_message);
|
||||
}
|
||||
|
||||
// 异步发送消息到SmartClaw
|
||||
let app_data_clone = app_data.clone();
|
||||
let debug_wechat_clone = debug_wechat;
|
||||
|
||||
tokio::spawn(async move {
|
||||
match app_data_clone.websocket_pool.broadcast(wechat_message).await {
|
||||
Ok(_) => {
|
||||
if debug_wechat_clone {
|
||||
log!("✅ 消息已成功转发到SmartClaw");
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
if debug_wechat_clone {
|
||||
log!("❌ 转发消息到SmartClaw失败: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if !has_connections {
|
||||
if debug_wechat {
|
||||
log!("⚠️ 没有可用的SmartClaw连接,跳过消息转发");
|
||||
}
|
||||
} else if msg_type.as_deref() != Some("text") {
|
||||
if debug_wechat {
|
||||
log!("⚠️ 非文本消息,跳过消息转发");
|
||||
}
|
||||
}
|
||||
|
||||
// 企业微信要求返回纯文本 "success"
|
||||
if debug_wechat {
|
||||
println!("✅ 企业微信消息处理完成,返回 success");
|
||||
log!("✅ 企业微信消息处理完成,返回 success");
|
||||
}
|
||||
HttpResponse::Ok().body("success")
|
||||
} else {
|
||||
// 其他请求方法
|
||||
if debug_wechat {
|
||||
println!("❌ 不支持的请求方法: {}", method);
|
||||
log!("❌ 不支持的请求方法: {}", method);
|
||||
}
|
||||
HttpResponse::MethodNotAllowed().body("method not allowed")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user