use crate::error::MqError;
use crate::queue::{Message, MessageQueue};
use async_trait::async_trait;
use pulsar::{producer, Consumer, DeserializeMessage, MessageId, Pulsar, SubType, TokioExecutor};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::{Mutex, RwLock};
pub struct RealPulsarQueue {
url: String,
pulsar: Option<Arc<Pulsar<TokioExecutor>>>,
producer: Option<Arc<producer::Producer<TokioExecutor>>>,
consumers: Arc<RwLock<HashMap<String, Arc<Mutex<Consumer<BytesMessage, TokioExecutor>>>>>>,
pending_acks: Arc<RwLock<HashMap<String, (String, MessageId)>>>,
}
impl RealPulsarQueue {
pub fn new(url: impl Into<String>) -> Self {
Self {
url: url.into(),
pulsar: None,
producer: None,
consumers: Arc::new(RwLock::new(HashMap::new())),
pending_acks: Arc::new(RwLock::new(HashMap::new())),
}
}
pub async fn connect(&mut self) -> Result<(), MqError> {
let pulsar = Pulsar::builder(&self.url, TokioExecutor)
.build()
.await
.map_err(|e| MqError::Connection(format!("Pulsar connect failed: {e}")))?;
self.pulsar = Some(Arc::new(pulsar));
Ok(())
}
pub async fn reconnect(&mut self) -> Result<(), MqError> {
self.pulsar = None;
self.producer = None;
self.consumers.write().await.clear();
self.pending_acks.write().await.clear();
self.connect().await
}
}
impl Default for RealPulsarQueue {
fn default() -> Self {
Self::new("pulsar://localhost:6650")
}
}
struct BytesMessage(Vec<u8>);
impl DeserializeMessage for BytesMessage {
type Output = Result<BytesMessage, pulsar::Error>;
fn deserialize_message(payload: &pulsar::proto::Message) -> Self::Output {
Ok(BytesMessage(payload.payload.clone()))
}
}
#[async_trait]
impl MessageQueue for RealPulsarQueue {
async fn publish(&self, topic: &str, message: &[u8]) -> Result<(), MqError> {
let pulsar = self
.pulsar
.as_ref()
.ok_or_else(|| MqError::Connection("Pulsar not connected".into()))?;
let mut producer = pulsar
.producer()
.with_topic(topic)
.build()
.await
.map_err(|e| MqError::Publish(format!("Pulsar producer failed: {e}")))?;
producer
.send_non_blocking(producer::Message {
payload: message.to_vec(),
..Default::default()
})
.await
.map_err(|e| MqError::Publish(format!("Pulsar send failed: {e}")))?;
producer
.close()
.await
.map_err(|e| MqError::Publish(format!("Pulsar close failed: {e}")))?;
Ok(())
}
async fn consume(&self, topic: &str) -> Result<Option<Message>, MqError> {
let pulsar = self
.pulsar
.as_ref()
.ok_or_else(|| MqError::Connection("Pulsar not connected".into()))?;
let consumer_arc = {
let subs = self.consumers.read().await;
subs.get(topic).cloned()
};
let consumer_arc = match consumer_arc {
Some(arc) => arc,
None => {
let consumer: Consumer<BytesMessage, TokioExecutor> = pulsar
.consumer()
.with_topic(topic)
.with_consumer_name("sz-orm-queue")
.with_subscription_type(SubType::Exclusive)
.with_subscription("sz-orm-subscription")
.build()
.await
.map_err(|e| {
MqError::Subscribe(format!("Pulsar consumer build failed: {e}"))
})?;
let arc = Arc::new(Mutex::new(consumer));
self.consumers
.write()
.await
.insert(topic.to_string(), arc.clone());
arc
}
};
let mut consumer = consumer_arc.lock().await;
use futures::StreamExt;
match tokio::time::timeout(std::time::Duration::from_millis(100), consumer.next()).await {
Ok(Some(Ok(msg))) => {
let payload = msg.payload.0.clone();
let pulsar_msg_id = msg.message_id();
let msg_id_str = format!("{:?}", pulsar_msg_id);
self.pending_acks
.write()
.await
.insert(msg_id_str.clone(), (topic.to_string(), pulsar_msg_id));
let message = Message {
topic: topic.to_string(),
payload,
key: None,
timestamp: current_timestamp_millis(),
headers: HashMap::new(),
id: msg_id_str,
};
Ok(Some(message))
}
Ok(Some(Err(_))) | Ok(None) => Ok(None),
Err(_) => Ok(None),
}
}
async fn ack(&self, message_id: &str) -> Result<(), MqError> {
let (topic, pulsar_msg_id) = {
let mut pending = self.pending_acks.write().await;
pending.remove(message_id).ok_or_else(|| {
MqError::NotSupported(format!(
"Pulsar message_id not found for ack: {}",
message_id
))
})?
};
let consumer_arc = {
let subs = self.consumers.read().await;
subs.get(&topic).cloned().ok_or_else(|| {
MqError::Connection(format!("Pulsar consumer not found for topic: {}", topic))
})?
};
let mut consumer = consumer_arc.lock().await;
consumer
.ack_with(pulsar_msg_id)
.await
.map_err(|e| MqError::Publish(format!("Pulsar ack failed: {e}")))?;
Ok(())
}
async fn subscribe(&self, topic: &str) -> Result<(), MqError> {
let pulsar = self
.pulsar
.as_ref()
.ok_or_else(|| MqError::Connection("Pulsar not connected".into()))?;
let consumer: Consumer<BytesMessage, TokioExecutor> = pulsar
.consumer()
.with_topic(topic)
.with_consumer_name("sz-orm-queue")
.with_subscription_type(SubType::Exclusive)
.with_subscription("sz-orm-subscription")
.build()
.await
.map_err(|e| MqError::Subscribe(format!("Pulsar consumer build failed: {e}")))?;
self.consumers
.write()
.await
.insert(topic.to_string(), Arc::new(Mutex::new(consumer)));
Ok(())
}
}
fn current_timestamp_millis() -> i64 {
use std::time::{SystemTime, UNIX_EPOCH};
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as i64
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_real_pulsar_queue_new() {
let queue = RealPulsarQueue::new("pulsar://localhost:6650");
assert_eq!(queue.url, "pulsar://localhost:6650");
assert!(queue.pulsar.is_none());
}
#[test]
fn test_real_pulsar_queue_default() {
let queue = RealPulsarQueue::default();
assert_eq!(queue.url, "pulsar://localhost:6650");
}
#[tokio::test]
async fn test_real_pulsar_not_connected_publish() {
let queue = RealPulsarQueue::new("pulsar://localhost:6650");
let result = queue.publish("topic", b"msg").await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_real_pulsar_not_connected_consume() {
let queue = RealPulsarQueue::new("pulsar://localhost:6650");
let result = queue.consume("topic").await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_h5_pulsar_ack_unknown_message_id_fails() {
let queue = RealPulsarQueue::new("pulsar://localhost:6650");
let result = queue.ack("unknown-msg-id").await;
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(err.contains("not found"), "err: {err}");
}
#[tokio::test]
#[ignore = "需真实 Pulsar 服务器"]
async fn test_real_pulsar_publish_and_consume() {
let mut queue = RealPulsarQueue::new("pulsar://localhost:6650");
queue.connect().await.unwrap();
queue.subscribe("test-topic").await.unwrap();
queue.publish("test-topic", b"hello pulsar").await.unwrap();
let msg = queue
.consume("test-topic")
.await
.unwrap()
.expect("message should exist");
assert_eq!(msg.payload, b"hello pulsar");
assert_eq!(msg.topic, "test-topic");
queue.ack(&msg.id).await.unwrap();
}
}