EdgexAgent/device-ble-go/pkg/messagebus/messagebus.go

277 lines
7.4 KiB
Go
Raw Normal View History

2025-07-10 20:40:32 +08:00
// Package messagebus 提供简化版的 EdgeX MessageBus 客户端封装,支持请求-响应通信
package messagebus
import (
"fmt"
"sync"
"time"
"github.com/edgexfoundry/go-mod-core-contracts/v4/clients/logger"
commonDTO "github.com/edgexfoundry/go-mod-core-contracts/v4/dtos/common"
"github.com/edgexfoundry/go-mod-messaging/v4/messaging"
"github.com/edgexfoundry/go-mod-messaging/v4/pkg/types"
"github.com/google/uuid"
)
// Client 表示一个简化版的 EdgeX MessageBus 客户端,支持基本的发布、订阅和基于 RequestID 的请求-响应机制。
type Client struct {
client messaging.MessageClient // 底层消息客户端
lc logger.LoggingClient // 日志客户端
isConnected bool // 连接状态标志
mutex sync.RWMutex // 并发读写锁
messageChan chan types.MessageEnvelope // 主动订阅消息通道
errorChan chan error // 错误信息通道
stopChan chan struct{} // 停止所有处理器的控制通道
wg sync.WaitGroup // 等待所有 goroutine 正常退出
responseChMap sync.Map // RequestID -> 响应通道,用于匹配响应
subscribedMu sync.Mutex // 订阅响应的互斥锁
subscribedRes bool // 是否已订阅响应标志
handledReqID sync.Map // string -> struct{}{},标记已处理
timeout time.Duration // 请求-响应超时时间
}
// Config 表示 MessageBus 配置参数
type Config struct {
Host string // 主机地址
Port int // 端口号
Protocol string // 协议mqtt/nats
Type string // 消息总线类型
ClientID string // 客户端 ID
Username string // 用户名(可选)
Password string // 密码(可选)
QoS int // QoS 级别(可选)
Timeout time.Duration // 默认请求超时时间(可选)
}
// MessageHandler 定义处理订阅消息的回调函数签名
type MessageHandler func(topic string, message types.MessageEnvelope) error
// NewClient 创建一个新的 MessageBus 客户端实例。
func NewClient(config Config, lc logger.LoggingClient) (*Client, error) {
messageBusConfig := types.MessageBusConfig{
Broker: types.HostInfo{
Host: config.Host,
Port: config.Port,
Protocol: config.Protocol,
},
Type: config.Type,
Optional: map[string]string{
"ClientId": config.ClientID,
},
}
if config.Username != "" {
messageBusConfig.Optional["Username"] = config.Username
}
if config.Password != "" {
messageBusConfig.Optional["Password"] = config.Password
}
if config.QoS > 0 {
messageBusConfig.Optional["Qos"] = fmt.Sprintf("%d", config.QoS)
}
client, err := messaging.NewMessageClient(messageBusConfig)
if err != nil {
return nil, err
}
return &Client{
client: client,
lc: lc,
messageChan: make(chan types.MessageEnvelope, 100),
errorChan: make(chan error, 10),
stopChan: make(chan struct{}),
timeout: config.Timeout,
}, nil
}
func (c *Client) SetTimeout(timeout time.Duration) {
c.timeout = timeout
}
func (c *Client) Connect() error {
c.mutex.Lock()
defer c.mutex.Unlock()
if c.isConnected {
return nil
}
if err := c.client.Connect(); err != nil {
return err
}
c.isConnected = true
return nil
}
func (c *Client) Disconnect() error {
c.mutex.Lock()
defer c.mutex.Unlock()
if !c.isConnected || c.client == nil {
return nil
}
close(c.stopChan)
c.wg.Wait()
if err := c.client.Disconnect(); err != nil {
return err
}
c.isConnected = false
return nil
}
func (c *Client) IsConnected() bool {
c.mutex.RLock()
defer c.mutex.RUnlock()
return c.isConnected
}
func (c *Client) Publish(topic string, data interface{}) error {
if !c.IsConnected() {
return fmt.Errorf("MessageBus 未连接")
}
message := types.MessageEnvelope{
Versionable: commonDTO.NewVersionable(),
RequestID: uuid.NewString(),
CorrelationID: uuid.NewString(),
Payload: data,
ContentType: "application/json",
}
return c.client.Publish(message, topic)
}
func (c *Client) Request(topic string, data interface{}) (types.MessageEnvelope, error) {
if !c.IsConnected() {
return types.MessageEnvelope{}, fmt.Errorf("MessageBus 未连接")
}
reqID := uuid.NewString()
respCh := make(chan types.MessageEnvelope, 1)
c.responseChMap.Store(reqID, respCh)
message := types.MessageEnvelope{
Versionable: commonDTO.NewVersionable(),
RequestID: reqID,
CorrelationID: uuid.NewString(),
Payload: data,
ContentType: "application/json",
}
if err := c.client.Publish(message, topic); err != nil {
c.responseChMap.Delete(reqID)
return types.MessageEnvelope{}, err
}
select {
case resp := <-respCh:
return resp, nil
case <-time.After(c.timeout):
c.lc.Warnf("请求超时: %s", reqID)
go func(id string) {
time.Sleep(2 * time.Second)
c.responseChMap.Delete(id)
}(reqID)
return types.MessageEnvelope{}, fmt.Errorf("请求超时: %s", reqID)
}
}
func (c *Client) Subscribe(topic string, handler MessageHandler) error {
if !c.IsConnected() {
return fmt.Errorf("MessageBus 未连接")
}
topicChannel := types.TopicChannel{
Topic: topic,
Messages: make(chan types.MessageEnvelope, 100),
}
if err := c.client.Subscribe([]types.TopicChannel{topicChannel}, c.errorChan); err != nil {
return err
}
c.wg.Add(1)
go c.handleMessages(topic, topicChannel.Messages, handler)
return nil
}
func (c *Client) SubscribeResponse(topic string) error {
if !c.IsConnected() {
return fmt.Errorf("MessageBus 未连接")
}
c.subscribedMu.Lock()
defer c.subscribedMu.Unlock()
if c.subscribedRes {
c.lc.Infof("响应主题 %s 已订阅,跳过重复订阅", topic)
return nil
}
topicChannel := types.TopicChannel{
Topic: topic,
Messages: make(chan types.MessageEnvelope, 100),
}
if err := c.client.Subscribe([]types.TopicChannel{topicChannel}, c.errorChan); err != nil {
return err
}
c.subscribedRes = true
c.wg.Add(1)
go c.handleResponseMessages(topic, topicChannel.Messages)
return nil
}
func (c *Client) handleMessages(topic string, ch chan types.MessageEnvelope, handler MessageHandler) {
defer c.wg.Done()
for {
select {
case msg, ok := <-ch:
if !ok {
return
}
actualTopic := msg.ReceivedTopic
if actualTopic == "" {
actualTopic = topic
}
msg.ReceivedTopic = actualTopic
if err := handler(actualTopic, msg); err != nil {
c.lc.Errorf("处理主题 %s 的消息时出错: %v", actualTopic, err)
}
case <-c.stopChan:
return
}
}
}
func (c *Client) handleResponseMessages(topic string, ch chan types.MessageEnvelope) {
defer c.wg.Done()
for {
select {
case msg, ok := <-ch:
if !ok {
return
}
if _, handled := c.handledReqID.LoadOrStore(msg.RequestID, struct{}{}); handled {
c.lc.Debugf("重复响应已处理: %s", msg.RequestID)
continue
}
val, exists := c.responseChMap.Load(msg.RequestID)
if !exists {
c.lc.Debugf("未匹配的响应: %s可能已超时", msg.RequestID)
continue
}
respCh, ok := val.(chan types.MessageEnvelope)
if !ok {
c.lc.Errorf("响应通道类型错误: %s", msg.RequestID)
c.responseChMap.Delete(msg.RequestID)
continue
}
select {
case respCh <- msg:
// 成功发送,不立即删除
default:
c.lc.Warnf("响应通道已满: %s", msg.RequestID)
c.responseChMap.Delete(msg.RequestID)
}
case <-c.stopChan:
return
}
}
}