github.com/wfusion/gofusion@v1.1.14/common/infra/watermill/pubsub/rocketmq/publisher.go (about) 1 package rocketmq 2 3 import ( 4 "context" 5 "log" 6 "time" 7 8 "github.com/apache/rocketmq-client-go/v2" 9 "github.com/apache/rocketmq-client-go/v2/primitive" 10 "github.com/apache/rocketmq-client-go/v2/producer" 11 "github.com/pkg/errors" 12 13 "github.com/wfusion/gofusion/common/infra/watermill" 14 "github.com/wfusion/gofusion/common/infra/watermill/message" 15 ) 16 17 // Publisher the rocketmq publisher 18 type Publisher struct { 19 config PublisherConfig 20 producer rocketmq.Producer 21 logger watermill.LoggerAdapter 22 23 closed bool 24 } 25 26 // NewPublisher creates a new RocketMQ Publisher. 27 func NewPublisher( 28 config PublisherConfig, 29 logger watermill.LoggerAdapter, 30 ) (*Publisher, error) { 31 if err := config.Validate(); err != nil { 32 return nil, err 33 } 34 if logger == nil { 35 logger = watermill.NopLogger{} 36 } 37 pub, err := rocketmq.NewProducer(config.Options()...) 38 if err != nil { 39 return nil, errors.Wrap(err, "cannot create RocketMQ producer") 40 } 41 if config.SendMode == "" { 42 config.SendMode = Sync 43 } 44 if config.SendAsyncCallback == nil { 45 config.SendAsyncCallback = DefaultSendAsyncCallback 46 } 47 return &Publisher{ 48 config: config, 49 producer: pub, 50 logger: logger, 51 }, nil 52 } 53 54 // PublisherConfig the rocketmq publisher config 55 type PublisherConfig struct { 56 GroupName string 57 InstanceName string 58 Namespace string 59 SendMsgTimeout time.Duration 60 VIPChannelEnabled bool 61 RetryTimes int 62 Interceptors []primitive.Interceptor 63 Selector producer.QueueSelector 64 Credentials *primitive.Credentials 65 DefaultTopicQueueNums int 66 CreateTopicKey string 67 // NsResolver primitive.NsResolver 68 // NameServer primitive.NamesrvAddr 69 // NameServerDomain string 70 71 SendMode SendMode // ["sync", "async", "oneway"] 72 SendAsyncCallback 73 74 // Marshaler is used to marshal messages from Watermill format into Rocketmq format. 75 Marshaler Marshaler 76 } 77 78 // Options generate options 79 func (c *PublisherConfig) Options() []producer.Option { 80 var opts []producer.Option 81 if c.GroupName != "" { 82 opts = append(opts, producer.WithGroupName(c.GroupName)) 83 } 84 if c.InstanceName != "" { 85 opts = append(opts, producer.WithInstanceName(c.InstanceName)) 86 } 87 if c.Namespace != "" { 88 opts = append(opts, producer.WithNamespace(c.Namespace)) 89 } 90 if c.SendMsgTimeout > 0 { 91 opts = append(opts, producer.WithSendMsgTimeout(c.SendMsgTimeout)) 92 } 93 if c.VIPChannelEnabled { 94 opts = append(opts, producer.WithVIPChannel(c.VIPChannelEnabled)) 95 } 96 if c.RetryTimes > 0 { 97 opts = append(opts, producer.WithRetry(c.RetryTimes)) 98 } 99 if len(c.Interceptors) > 0 { 100 opts = append(opts, producer.WithInterceptor(c.Interceptors...)) 101 } 102 if c.Selector != nil { 103 opts = append(opts, producer.WithQueueSelector(c.Selector)) 104 } 105 if c.Credentials != nil { 106 opts = append(opts, producer.WithCredentials(*c.Credentials)) 107 } 108 if c.DefaultTopicQueueNums > 0 { 109 opts = append(opts, producer.WithDefaultTopicQueueNums(c.DefaultTopicQueueNums)) 110 } 111 if c.CreateTopicKey != "" { 112 opts = append(opts, producer.WithCreateTopicKey(c.CreateTopicKey)) 113 } 114 return nil 115 } 116 117 // Validate validate publisher config 118 func (c PublisherConfig) Validate() error { 119 if c.SendMode != "" && c.SendMode != "sync" && c.SendMode != "async" && c.SendMode != "one_way" { 120 return errors.Errorf("invalid send mode: %s", c.SendMode) 121 } 122 return nil 123 } 124 125 // Publish publishes message to RocketMQ. 126 // 127 // Publish is blocking and wait for ack from RocketMQ. 128 // When one of messages delivery fails - function is interrupted. 129 func (p *Publisher) Publish(ctx context.Context, topic string, msgs ...*message.Message) error { 130 if p.closed { 131 return errors.New("publisher closed") 132 } 133 logFields := make(watermill.LogFields, 4) 134 logFields["topic"] = topic 135 for _, msg := range msgs { 136 logFields["message_uuid"] = msg.UUID 137 p.logger.Trace("Sending message to RocketMQ", logFields) 138 rocketmqMsgs, err := p.config.Marshaler.Marshal(topic, msg) 139 if err != nil { 140 return errors.Wrapf(err, "cannot marshal message %s", msg.UUID) 141 } 142 switch p.config.SendMode { 143 case Async: 144 err = p.sendAsync(ctx, msg, logFields, rocketmqMsgs...) 145 case OneWay: 146 err = p.producer.SendOneWay(ctx) 147 default: 148 err = p.sendSync(ctx, msg, logFields, rocketmqMsgs...) 149 } 150 if err != nil { 151 return err 152 } 153 } 154 return nil 155 } 156 157 func (p *Publisher) sendSync(ctx context.Context, wmsg *message.Message, 158 fields map[string]any, rmsg ...*primitive.Message) error { 159 result, err := p.producer.SendSync(ctx, rmsg...) 160 if err != nil { 161 return errors.WithMessagef(err, "send sync msg %s failed", wmsg.UUID) 162 } 163 fields["send_status"] = result.Status 164 fields["msg_id"] = result.MsgID 165 fields["offset_msg_id"] = result.OffsetMsgID 166 fields["queue_offset"] = result.QueueOffset 167 fields["message_queue"] = result.MessageQueue.String() 168 fields["transaction_id"] = result.TransactionID 169 fields["region_id"] = result.RegionID 170 fields["trace_on"] = result.TraceOn 171 p.logger.Trace("Message sent to RocketMQ", fields) 172 return nil 173 } 174 175 func (p *Publisher) sendAsync(ctx context.Context, wmsg *message.Message, 176 fields map[string]any, rmsg ...*primitive.Message) error { 177 err := p.producer.SendAsync(ctx, p.config.SendAsyncCallback, rmsg...) 178 if err != nil { 179 return errors.WithMessagef(err, "send sync msg %s failed", wmsg.UUID) 180 } 181 return nil 182 } 183 184 // Close closes the publisher 185 func (p *Publisher) Close() error { 186 if p.closed { 187 return nil 188 } 189 p.closed = true 190 191 if err := p.producer.Shutdown(); err != nil { 192 return errors.Wrap(err, "cannot close Kafka producer") 193 } 194 195 return nil 196 } 197 198 // SendMode send mode 199 type SendMode string 200 201 const ( 202 // Sync the syns mode 203 Sync SendMode = "sync" 204 // Async the async mode 205 Async SendMode = "async" 206 // OneWay the one way mode, no resule 207 OneWay SendMode = "one_way" 208 ) 209 210 // SendAsyncCallback callback for each message send aysnc result 211 type SendAsyncCallback func(ctx context.Context, result *primitive.SendResult, err error) 212 213 // DefaultSendAsyncCallback default SendAsyncCallback 214 func DefaultSendAsyncCallback(ctx context.Context, result *primitive.SendResult, err error) { 215 if err != nil { 216 log.Printf("receive message error: %v\n", err) 217 } else { 218 log.Printf("send message success: result=%s\n", result.String()) 219 } 220 }