Files
go-trustlog/api/adapter/tcp_publisher.go
ryan d313449c5c 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等)预留空间。
2025-12-22 13:37:57 +08:00

196 lines
4.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}