use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::{mpsc, Mutex, RwLock};
use serde_json::Value;
use log::{debug, info, warn};
use crate::config::Credentials;
use crate::error::Error;
use super::auto_reconnect_client::{AutoReconnectWebsocketClient, ConnectionState, ReconnectConfig};
use super::channel::{Args, ChannelType};
#[derive(Debug, Clone)]
pub struct ManagerConfig {
pub reconnect_interval: u64,
pub max_reconnect_attempts: u32,
pub heartbeat_interval: u64,
pub message_timeout: u64,
pub auto_reconnect: bool,
}
impl Default for ManagerConfig {
fn default() -> Self {
Self {
reconnect_interval: 5,
max_reconnect_attempts: 10,
heartbeat_interval: 30,
message_timeout: 60,
auto_reconnect: true,
}
}
}
#[derive(Debug, Clone)]
pub struct SubscriptionInfo {
pub channel: ChannelType,
pub args: Args,
pub is_active: bool,
}
pub struct OkxWebsocketManager {
config: ManagerConfig,
public_client: Arc<Mutex<AutoReconnectWebsocketClient>>,
private_client: Option<Arc<Mutex<AutoReconnectWebsocketClient>>>,
business_client: Option<Arc<Mutex<AutoReconnectWebsocketClient>>>,
subscriptions: Arc<RwLock<HashMap<String, SubscriptionInfo>>>,
message_sender: Arc<Mutex<Option<mpsc::UnboundedSender<Value>>>>,
is_running: Arc<Mutex<bool>>,
}
impl OkxWebsocketManager {
pub fn new_public() -> Self {
Self::new_with_config(None, ManagerConfig::default())
}
pub fn new_with_credentials(credentials: Credentials) -> Self {
Self::new_with_config(Some(credentials), ManagerConfig::default())
}
pub fn new_with_config(credentials: Option<Credentials>, config: ManagerConfig) -> Self {
let reconnect_config = ReconnectConfig {
enabled: config.auto_reconnect,
interval: config.reconnect_interval,
max_attempts: config.max_reconnect_attempts,
backoff_factor: 1.5,
max_backoff: 60,
heartbeat_interval: config.heartbeat_interval,
message_timeout: config.message_timeout,
};
let public_client = Arc::new(Mutex::new(
AutoReconnectWebsocketClient::new_public()
));
let private_client = credentials.clone().map(|creds| {
Arc::new(Mutex::new(
AutoReconnectWebsocketClient::new_private(creds)
))
});
let business_client = credentials.clone().map(|creds| {
Arc::new(Mutex::new(
AutoReconnectWebsocketClient::new_business(creds)
))
});
Self {
config,
public_client,
private_client,
business_client,
subscriptions: Arc::new(RwLock::new(HashMap::new())),
message_sender: Arc::new(Mutex::new(None)),
is_running: Arc::new(Mutex::new(false)),
}
}
pub async fn start(&self) -> Result<mpsc::UnboundedReceiver<Value>, Error> {
let mut is_running = self.is_running.lock().await;
if *is_running {
return Err(Error::WebSocketError("Manager is already running".to_string()));
}
let (tx, rx) = mpsc::unbounded_channel();
*self.message_sender.lock().await = Some(tx.clone());
*is_running = true;
let public_rx = self.public_client.lock().await.start().await?;
self.start_message_forwarder(public_rx, tx.clone()).await;
if let Some(private_client) = &self.private_client {
let private_rx = private_client.lock().await.start().await?;
self.start_message_forwarder(private_rx, tx.clone()).await;
}
info!("WebSocket管理器已启动");
Ok(rx)
}
pub async fn stop(&self) {
*self.is_running.lock().await = false;
*self.message_sender.lock().await = None;
self.public_client.lock().await.stop().await;
if let Some(private_client) = &self.private_client {
private_client.lock().await.stop().await;
}
info!("WebSocket管理器已停止");
}
pub async fn subscribe(&self, channel: ChannelType, args: Args) -> Result<(), Error> {
let subscription_key = format!("{:?}_{}", channel, args.inst_id.as_ref().unwrap_or(&"".to_string()));
{
let mut subscriptions = self.subscriptions.write().await;
subscriptions.insert(subscription_key.clone(), SubscriptionInfo {
channel: channel.clone(),
args: args.clone(),
is_active: false,
});
}
self.execute_subscription(&channel, &args).await?;
{
let mut subscriptions = self.subscriptions.write().await;
if let Some(sub) = subscriptions.get_mut(&subscription_key) {
sub.is_active = true;
}
}
debug!("已订阅频道: {:?}", channel);
Ok(())
}
pub async fn unsubscribe(&self, channel: ChannelType, args: Args) -> Result<(), Error> {
let subscription_key = format!("{:?}_{}", channel, args.inst_id.as_ref().unwrap_or(&"".to_string()));
self.execute_unsubscription(&channel, &args).await?;
{
let mut subscriptions = self.subscriptions.write().await;
subscriptions.remove(&subscription_key);
}
debug!("已取消订阅频道: {:?}", channel);
Ok(())
}
pub async fn get_connection_status(&self) -> (ConnectionState, Option<ConnectionState>) {
let public_state = self.public_client.lock().await.get_connection_state();
let private_state = if let Some(private_client) = &self.private_client {
Some(private_client.lock().await.get_connection_state())
} else {
None
};
(public_state, private_state)
}
pub async fn get_active_subscriptions_count(&self) -> usize {
let subscriptions = self.subscriptions.read().await;
subscriptions.values().filter(|sub| sub.is_active).count()
}
async fn execute_subscription(&self, channel: &ChannelType, args: &Args) -> Result<(), Error> {
match channel {
ChannelType::Tickers | ChannelType::Books | ChannelType::Trades |
ChannelType::BooksLite | ChannelType::Books50L | ChannelType::BlockTickers |
ChannelType::IndexTickers | ChannelType::MarkPrice | ChannelType::PriceLimit |
ChannelType::EstimatedPrice | ChannelType::FundingRate | ChannelType::Status |
ChannelType::IndexCandle(_) | ChannelType::MarkPriceCandle(_) => {
self.public_client.lock().await.subscribe(channel.clone(), args.clone()).await
}
ChannelType::Candle(_) | ChannelType::Account | ChannelType::Orders |
ChannelType::Positions | ChannelType::AlgoOrders | ChannelType::AdvancedAlgoOrders |
ChannelType::OrdersAlgo | ChannelType::Balance | ChannelType::PositionRisk |
ChannelType::BalanceAndPosition | ChannelType::Greeks | ChannelType::DepositInfo => {
if let Some(private_client) = &self.private_client {
private_client.lock().await.subscribe(channel.clone(), args.clone()).await
} else {
Err(Error::WebSocketError("Private client not available".to_string()))
}
}
ChannelType::Custom(_) => {
self.public_client.lock().await.subscribe(channel.clone(), args.clone()).await
}
}
}
async fn execute_unsubscription(&self, channel: &ChannelType, args: &Args) -> Result<(), Error> {
match channel {
ChannelType::Tickers | ChannelType::Books | ChannelType::Trades |
ChannelType::BooksLite | ChannelType::Books50L | ChannelType::BlockTickers |
ChannelType::IndexTickers | ChannelType::MarkPrice | ChannelType::PriceLimit |
ChannelType::EstimatedPrice | ChannelType::FundingRate | ChannelType::Status |
ChannelType::IndexCandle(_) | ChannelType::MarkPriceCandle(_) => {
self.public_client.lock().await.unsubscribe(channel.clone(), args.clone()).await
}
ChannelType::Candle(_) | ChannelType::Account | ChannelType::Orders |
ChannelType::Positions | ChannelType::AlgoOrders | ChannelType::AdvancedAlgoOrders |
ChannelType::OrdersAlgo | ChannelType::Balance | ChannelType::PositionRisk |
ChannelType::BalanceAndPosition | ChannelType::Greeks | ChannelType::DepositInfo => {
if let Some(private_client) = &self.private_client {
private_client.lock().await.unsubscribe(channel.clone(), args.clone()).await
} else {
Err(Error::WebSocketError("Private client not available".to_string()))
}
}
ChannelType::Custom(_) => {
self.public_client.lock().await.unsubscribe(channel.clone(), args.clone()).await
}
}
}
async fn start_message_forwarder(
&self,
mut rx: mpsc::UnboundedReceiver<Value>,
tx: mpsc::UnboundedSender<Value>,
) {
tokio::spawn(async move {
while let Some(message) = rx.recv().await {
if tx.send(message).is_err() {
warn!("消息转发失败,接收器可能已关闭");
break;
}
}
debug!("消息转发器结束");
});
}
}