209 lines
5.7 KiB
Go
209 lines
5.7 KiB
Go
package message
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/gogf/gf/v2/frame/g"
|
|
"github.com/nats-io/nats.go/jetstream"
|
|
)
|
|
|
|
// natsMessageClient NATS 实现
|
|
type natsMessageClient struct {
|
|
clientType messageClientType
|
|
}
|
|
|
|
// StreamGroup 创建消费组(支持单个或批量)
|
|
func (q *natsMessageClient) streamGroup(ctx context.Context, configs ...interface{}) error {
|
|
if len(configs) == 0 {
|
|
return fmt.Errorf("配置不能为空")
|
|
}
|
|
for _, config := range configs {
|
|
cfg, ok := config.(*NATSConfig)
|
|
if !ok {
|
|
return fmt.Errorf("无效的 NATS 配置类型")
|
|
}
|
|
if err := q.createStreamGroup(ctx, cfg); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// createStreamGroup 内部单个创建消费组
|
|
func (q *natsMessageClient) createStreamGroup(ctx context.Context, cfg *NATSConfig) error {
|
|
// Stream 不存在,创建新的
|
|
storage := jetstream.FileStorage
|
|
if !cfg.Durable {
|
|
storage = jetstream.MemoryStorage
|
|
}
|
|
if g.IsEmpty(cfg.Replicas) {
|
|
cfg.Replicas = 1
|
|
}
|
|
// 构建流配置
|
|
jsConfig := jetstream.StreamConfig{
|
|
Name: cfg.Stream,
|
|
Subjects: []string{fmt.Sprintf("%s.>", cfg.Stream)},
|
|
Replicas: cfg.Replicas,
|
|
NoAck: cfg.AutoAck,
|
|
AllowMsgSchedules: cfg.DelayMessage, // 延迟消息核心开关
|
|
Storage: storage,
|
|
Discard: jetstream.DiscardOld, // 达到上限删除旧消息
|
|
}
|
|
// 检查流是否已存在
|
|
stream, err := js.Stream(ctx, cfg.Stream)
|
|
if err == nil {
|
|
// 流已存在,更新配置
|
|
_, err = js.UpdateStream(ctx, jsConfig)
|
|
if err != nil {
|
|
return fmt.Errorf("更新任务流失败: %w", err)
|
|
}
|
|
g.Log().Infof(ctx, "任务流已更新: %s", stream.CachedInfo().Config.Name)
|
|
return nil
|
|
}
|
|
// 创建新流
|
|
stream, err = js.CreateStream(ctx, jsConfig)
|
|
if err != nil {
|
|
return fmt.Errorf("创建任务流失败: %w", err)
|
|
}
|
|
|
|
g.Log().Infof(ctx, "✅ NATS 队列初始化成功: stream=%s, consumer=%s", cfg.Stream, cfg.Consumer)
|
|
return nil
|
|
}
|
|
|
|
// Publish 发布消息(支持单个或批量)
|
|
func (q *natsMessageClient) publish(ctx context.Context, config interface{}, data interface{}) error {
|
|
cfg, ok := config.(*NATSConfig)
|
|
if !ok {
|
|
return fmt.Errorf("无效的 NATS 配置类型")
|
|
}
|
|
err := q.createStreamGroup(ctx, cfg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
payload, err := json.Marshal(data)
|
|
if err != nil {
|
|
return fmt.Errorf("序列化数据失败: %w", err)
|
|
}
|
|
|
|
// 发布消息到 JetStream
|
|
subject := fmt.Sprintf("%s.>", cfg.Stream)
|
|
_, err = js.Publish(ctx, subject, payload)
|
|
if err != nil {
|
|
g.Log().Errorf(ctx, "❌ NATS 发布消息失败: topic=%s, err=%v", cfg.Stream, err)
|
|
return err
|
|
}
|
|
|
|
g.Log().Infof(ctx, "✅ NATS 发布消息成功: topic=%s", cfg.Stream)
|
|
return nil
|
|
}
|
|
|
|
// PublishDelayed 发布延迟消息(支持单个或批量)
|
|
func (q *natsMessageClient) publishDelayed(ctx context.Context, config interface{}, data interface{}, delay int) error {
|
|
|
|
cfg, ok := config.(*NATSConfig)
|
|
if !ok {
|
|
return fmt.Errorf("无效的 NATS 配置类型")
|
|
}
|
|
|
|
payload, err := json.Marshal(data)
|
|
if err != nil {
|
|
return fmt.Errorf("序列化数据失败: %w", err)
|
|
}
|
|
|
|
// 使用 goroutine 实现简单的延迟发布
|
|
go func() {
|
|
time.Sleep(time.Duration(delay))
|
|
subject := fmt.Sprintf("%s.>", cfg.Stream)
|
|
if err := q.publishInternal(ctx, subject, payload); err != nil {
|
|
g.Log().Errorf(ctx, "❌ NATS 延迟消息发布失败: topic=%s, delay=%v, err=%v", cfg.Stream, delay, err)
|
|
}
|
|
}()
|
|
|
|
g.Log().Infof(ctx, "✅ NATS 延迟消息已提交: topic=%s, delay=%v", cfg.Stream, delay)
|
|
|
|
return nil
|
|
}
|
|
|
|
// publishInternal 内部发布消息
|
|
func (q *natsMessageClient) publishInternal(ctx context.Context, subject string, payload []byte) error {
|
|
_, err := js.Publish(ctx, subject, payload)
|
|
return err
|
|
}
|
|
|
|
// Subscribe 订阅消息(支持单个或批量)
|
|
func (q *natsMessageClient) subscribe(ctx context.Context, configs ...interface{}) error {
|
|
if len(configs) == 0 {
|
|
return fmt.Errorf("配置不能为空")
|
|
}
|
|
|
|
for _, config := range configs {
|
|
cfg, ok := config.(*NATSConfig)
|
|
if !ok {
|
|
return fmt.Errorf("无效的 NATS 配置类型")
|
|
}
|
|
handler := cfg.HandleFunc
|
|
if handler == nil {
|
|
return fmt.Errorf("必须提供处理函数")
|
|
}
|
|
if err := q.createSubscribe(ctx, cfg, handler); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// subscribe 内部单个订阅消息
|
|
func (q *natsMessageClient) createSubscribe(ctx context.Context, cfg *NATSConfig, handler func(ctx context.Context, message map[string]interface{}) error) error {
|
|
g.Log().Infof(ctx, "🔔 NATS 开始订阅: stream=%s, consumer=%s", cfg.Stream, cfg.Consumer)
|
|
// Stream 不存在,创建新的
|
|
ackPolicy := jetstream.AckExplicitPolicy
|
|
if cfg.AutoAck {
|
|
ackPolicy = jetstream.AckNonePolicy
|
|
}
|
|
jsConfig := jetstream.ConsumerConfig{
|
|
Name: cfg.Consumer,
|
|
Durable: cfg.Consumer,
|
|
AckPolicy: ackPolicy,
|
|
MaxDeliver: 3,
|
|
MaxAckPending: cfg.PrefetchCount,
|
|
}
|
|
// 创建新消费者
|
|
consumer, err := js.CreateOrUpdateConsumer(ctx, cfg.Stream, jsConfig)
|
|
if err != nil {
|
|
return fmt.Errorf("创建消费者失败: %w", err)
|
|
}
|
|
// 创建消息处理函数
|
|
msgHandler := func(msg jetstream.Msg) {
|
|
// 解析消息
|
|
var data map[string]any
|
|
if err := json.Unmarshal(msg.Data(), &data); err != nil {
|
|
g.Log().Errorf(ctx, "解析消息失败: %v", err)
|
|
msg.Nak()
|
|
return
|
|
}
|
|
// 处理业务逻辑
|
|
if err := handler(ctx, data); err != nil {
|
|
g.Log().Errorf(ctx, "处理消息失败: %v", err)
|
|
msg.Nak()
|
|
return
|
|
}
|
|
g.Log().Infof(ctx, "处理消息成功")
|
|
if !cfg.AutoAck {
|
|
msg.Ack()
|
|
}
|
|
}
|
|
|
|
// 开始消费
|
|
_, err = consumer.Consume(msgHandler)
|
|
if err != nil {
|
|
return fmt.Errorf("开始消费失败: %w", err)
|
|
}
|
|
|
|
g.Log().Infof(ctx, "✅ 开始消费消息: %s/%s", cfg.Stream, cfg.Consumer)
|
|
|
|
return nil
|
|
}
|