refactor: 重构trustlog-sdk目录结构到trustlog/go-trustlog
- 将所有trustlog-sdk文件移动到trustlog/go-trustlog/目录 - 更新README中所有import路径从trustlog-sdk改为go-trustlog - 更新cookiecutter配置文件中的项目名称 - 更新根目录.lefthook.yml以引用新位置的配置 - 添加go.sum文件到版本控制 - 删除过时的示例文件 这次重构与trustlog-server保持一致的目录结构, 为未来支持多语言SDK(Python、Java等)预留空间。
This commit is contained in:
195
api/adapter/tcp_publisher.go
Normal file
195
api/adapter/tcp_publisher.go
Normal file
@@ -0,0 +1,195 @@
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ThreeDotsLabs/watermill/message"
|
||||
|
||||
"go.yandata.net/iod/iod/trustlog-sdk/api/logger"
|
||||
)
|
||||
|
||||
// 默认配置常量.
|
||||
const (
|
||||
defaultConnectTimeout = 10 * time.Second
|
||||
defaultMaxRetries = 3
|
||||
)
|
||||
|
||||
// 预定义错误.
|
||||
var (
|
||||
ErrServerAddrRequired = errors.New("server address is required")
|
||||
ErrPublisherClosed = errors.New("publisher is closed")
|
||||
)
|
||||
|
||||
// TCPPublisherConfig TCP 发布者配置
|
||||
type TCPPublisherConfig struct {
|
||||
// ServerAddr TCP 服务器地址,格式: "host:port"
|
||||
ServerAddr string
|
||||
// ConnectTimeout 连接超时时间
|
||||
ConnectTimeout time.Duration
|
||||
// MaxRetries 最大重试次数
|
||||
MaxRetries int
|
||||
}
|
||||
|
||||
// TCPPublisher 实现基于 TCP 的 watermill Publisher
|
||||
type TCPPublisher struct {
|
||||
config TCPPublisherConfig
|
||||
conn net.Conn
|
||||
logger logger.Logger
|
||||
|
||||
closed bool
|
||||
closedMu sync.RWMutex
|
||||
closeChan chan struct{}
|
||||
}
|
||||
|
||||
// NewTCPPublisher 创建一个新的 TCP Publisher.
|
||||
func NewTCPPublisher(config TCPPublisherConfig, logger logger.Logger) (*TCPPublisher, error) {
|
||||
if config.ServerAddr == "" {
|
||||
return nil, ErrServerAddrRequired
|
||||
}
|
||||
|
||||
if config.ConnectTimeout == 0 {
|
||||
config.ConnectTimeout = defaultConnectTimeout
|
||||
}
|
||||
|
||||
if config.MaxRetries == 0 {
|
||||
config.MaxRetries = defaultMaxRetries
|
||||
}
|
||||
|
||||
p := &TCPPublisher{
|
||||
config: config,
|
||||
logger: logger,
|
||||
closeChan: make(chan struct{}),
|
||||
}
|
||||
|
||||
// 连接到服务器
|
||||
if err := p.connect(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 不再接收 ACK/NACK,发送即成功模式
|
||||
// go p.receiveAcks() // 已移除
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// connect 连接到 TCP 服务器
|
||||
func (p *TCPPublisher) connect() error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), p.config.ConnectTimeout)
|
||||
defer cancel()
|
||||
|
||||
var d net.Dialer
|
||||
conn, err := d.DialContext(ctx, "tcp", p.config.ServerAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to connect to %s: %w", p.config.ServerAddr, err)
|
||||
}
|
||||
|
||||
p.conn = conn
|
||||
p.logger.InfoContext(context.Background(), "Connected to TCP server", "addr", p.config.ServerAddr)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Publish 发布消息.
|
||||
func (p *TCPPublisher) Publish(topic string, messages ...*message.Message) error {
|
||||
p.closedMu.RLock()
|
||||
if p.closed {
|
||||
p.closedMu.RUnlock()
|
||||
return ErrPublisherClosed
|
||||
}
|
||||
p.closedMu.RUnlock()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// 使用 WaitGroup 和 errChan 来并发发送消息并收集错误
|
||||
var wg sync.WaitGroup
|
||||
errs := make([]error, 0, len(messages))
|
||||
var errMu sync.Mutex
|
||||
errChan := make(chan error, len(messages))
|
||||
|
||||
for _, msg := range messages {
|
||||
if msg == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func(m *message.Message) {
|
||||
defer wg.Done()
|
||||
|
||||
if err := p.publishSingle(ctx, topic, m); err != nil {
|
||||
errChan <- err
|
||||
}
|
||||
}(msg)
|
||||
}
|
||||
|
||||
// 等待所有消息发送完成
|
||||
wg.Wait()
|
||||
close(errChan)
|
||||
|
||||
// 检查是否有错误
|
||||
for err := range errChan {
|
||||
errMu.Lock()
|
||||
errs = append(errs, err)
|
||||
errMu.Unlock()
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return fmt.Errorf("failed to publish %d messages: %w", len(errs), errors.Join(errs...))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// publishSingle 发送单条消息,不等待 ACK
|
||||
func (p *TCPPublisher) publishSingle(ctx context.Context, topic string, msg *message.Message) error {
|
||||
tcpMsg := &TCPMessage{
|
||||
Type: MessageTypeData,
|
||||
Topic: topic,
|
||||
UUID: msg.UUID,
|
||||
Payload: msg.Payload,
|
||||
}
|
||||
|
||||
// 编码消息
|
||||
data, err := EncodeTCPMessage(tcpMsg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encode message: %w", err)
|
||||
}
|
||||
|
||||
p.logger.DebugContext(ctx, "Sending message", "uuid", msg.UUID, "topic", topic)
|
||||
|
||||
// 发送消息
|
||||
if _, err := p.conn.Write(data); err != nil {
|
||||
return fmt.Errorf("failed to write message: %w", err)
|
||||
}
|
||||
|
||||
p.logger.DebugContext(ctx, "Message sent successfully", "uuid", msg.UUID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// receiveAcks, shouldStopReceiving, handleDecodeError 方法已移除
|
||||
// 不再接收 ACK/NACK,采用发送即成功模式以提高性能
|
||||
|
||||
// Close 关闭发布者
|
||||
func (p *TCPPublisher) Close() error {
|
||||
p.closedMu.Lock()
|
||||
if p.closed {
|
||||
p.closedMu.Unlock()
|
||||
return nil
|
||||
}
|
||||
p.closed = true
|
||||
p.closedMu.Unlock()
|
||||
|
||||
close(p.closeChan)
|
||||
|
||||
if p.conn != nil {
|
||||
if err := p.conn.Close(); err != nil {
|
||||
return fmt.Errorf("failed to close connection: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
p.logger.InfoContext(context.Background(), "TCP Publisher closed")
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user