123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639 |
- package comm
- import (
- "context"
- "encoding/json"
- "errors"
- "fmt"
- "os"
- "os/signal"
- "sync"
- "syscall"
- "time"
- "github.com/nats-io/nats.go"
- )
- const (
- ConnStateDisconned = 1
- ConnStateReconned = 2
- )
- // NATSQueue queue for work
- type NatsBus struct {
- mu sync.Mutex
- nc *nats.Conn
- reconnChan chan int
- streamWacther []*NatsStreamWather
- topicWatcher []*NatsTopicWather
- replyers []*NatsMsgReplyer
- runtimeStartStreamWatcher StartStreamWatcherHandle
- }
- type StartStreamWatcherHandle func(conf *NatsStreamWather) error
- type NatsStreamWather struct {
- Stream string
- Topic string
- Queue string
- AckWaitMinute int64
- AckWaitMinuteFloat float64
- Entity CreateEnity
- Cb WatcherCallback
- Cb2 WatcherCallback2
- Exported bool //在其他部署包内,是否可以被调用
- }
- // 消息回复者
- type NatsMsgReplyer struct {
- Subject string
- Entity CreateEnity
- Exported bool //在其他部署包内,是否可以被调用
- Timeout time.Duration //调用的最大时间
- Cb ReplyerHandle
- Cb2 ReplyerHandle2
- }
- type NatsTopicWather struct {
- Topic string
- Cb WatcherCallback
- }
- type Stream func(streamName string, topic string, group string)
- type ListenTopic func(topic string)
- type WatcherCallback func(msg *nats.Msg, entity interface{})
- type WatcherCallback2 func(msg *nats.Msg)
- type ReplyerHandle func(msg *nats.Msg, entity interface{}) interface{}
- type ReplyerHandle2 func(msg *nats.Msg, entity interface{}) (interface{}, error)
- type ReplyerHandle3 func(msg *nats.Msg) (interface{}, error)
- type CreateEnity func() interface{}
- type PackMessageCallback func(entity interface{}) (*RespPackMessage, error)
- type RespPackMessage struct {
- CanAck bool
- }
- // 请求数据
- type NatsResponse struct {
- ErrorNo int
- ErrorDesc string
- Result string //json字符串
- }
- type RequestOptions struct {
- Timeout time.Duration
- PubOpts []nats.PubOpt
- DeployPack string
- }
- type ReqPayload struct {
- Payload interface{}
- DeployPack string
- }
- type ReqPackApi struct {
- Payload interface{}
- Subject string
- Timeout time.Duration
- }
- // req params
- type ReqStreamApi struct {
- Subject string
- Payload interface{}
- PubOpts []nats.PubOpt
- }
- type CancelSubscribe func()
- type SubscribeCallback func(obj interface{}, msg *nats.Msg) interface{}
- type SubOption struct {
- Sub string
- Obj func() interface{}
- Call SubscribeCallback
- }
- func (q *NatsBus) Subscribe(topic string, msg nats.MsgHandler) (*nats.Subscription, error) {
- return q.nc.Subscribe(topic, msg)
- }
- func (q *NatsBus) SubscribeOnce(Option *SubOption) (CancelSubscribe, error) {
- var sub *nats.Subscription
- var cancel = func() {
- if sub != nil {
- sub.Unsubscribe()
- sub = nil
- }
- }
- s, err := q.nc.Subscribe(Option.Sub, func(msg *nats.Msg) {
- var p interface{}
- if Option.Obj != nil {
- p = Option.Obj()
- err := json.Unmarshal(msg.Data, p)
- if err != nil {
- msg.Term()
- fmt.Println(Option.Sub, " payload parse msg err", err)
- return
- }
- }
- cancel()
- out := Option.Call(p, msg)
- if out != nil {
- payload, _ := json.Marshal(out)
- msg.Respond(payload)
- return
- }
- })
- if err != nil {
- return nil, err
- }
- sub = s
- return cancel, nil
- }
- func (q *NatsBus) QueueSubscribe(Option *SubOption) (CancelSubscribe, error) {
- var sub *nats.Subscription
- var cancel = func() {
- if sub != nil {
- sub.Unsubscribe()
- sub = nil
- }
- }
- s, err := q.nc.QueueSubscribe(Option.Sub, Option.Sub+".queue", func(msg *nats.Msg) {
- var p interface{}
- if Option.Obj != nil {
- p = Option.Obj()
- err := json.Unmarshal(msg.Data, p)
- if err != nil {
- msg.Term()
- fmt.Println(Option.Sub, " payload parse msg err", err)
- return
- }
- }
- out := Option.Call(p, msg)
- if out != nil {
- payload, _ := json.Marshal(out)
- msg.Respond(payload)
- return
- }
- })
- if err != nil {
- return nil, err
- }
- sub = s
- return cancel, nil
- }
- func (q *NatsBus) Publish(topic string, data []byte) error {
- return q.nc.Publish(topic, data)
- }
- func (q *NatsBus) PublishObj(topic string, obj interface{}) error {
- data, _ := json.Marshal(obj)
- return q.nc.Publish(topic, data)
- }
- func (q *NatsBus) AddReplyers(w ...*NatsMsgReplyer) {
- q.replyers = append(q.replyers, w...)
- }
- func (q *NatsBus) AddStreamWacher(w ...*NatsStreamWather) {
- q.streamWacther = append(q.streamWacther, w...)
- }
- // 请求代理api
- func (q *NatsBus) RequestPackApi(subject string, Payload interface{}, out interface{}, options *RequestOptions) error {
- pack := "comm"
- timeout := time.Second * 5
- if options != nil {
- if len(options.DeployPack) > 0 {
- pack = options.DeployPack
- }
- if options.Timeout > 0 {
- timeout = options.Timeout
- }
- }
- payload, _ := json.Marshal(&ReqPackApi{Payload: Payload, Subject: subject, Timeout: timeout})
- msg, err := q.nc.Request(fmt.Sprintf("%s.%s", pack, PackPublicApi), payload, timeout)
- if err != nil {
- return err
- }
- result := &NatsResponse{}
- err = json.Unmarshal(msg.Data, result)
- if err != nil {
- return err
- }
- if result.ErrorNo != 200 {
- return errors.New(result.ErrorDesc)
- }
- if out != nil {
- return json.Unmarshal([]byte(result.Result), out)
- }
- return nil
- }
- // 请求本地api
- func (q *NatsBus) RequestApi(subject string, data interface{}, timeout time.Duration, out interface{}) error {
- // Lock so only one goroutine at a time can access the map c.v.
- q.mu.Lock()
- defer q.mu.Unlock()
- payload := []byte{}
- if data != nil {
- payload, _ = json.Marshal(data)
- }
- msg, err := q.nc.Request(subject, payload, timeout)
- if err != nil {
- return err
- }
- result := &NatsResponse{}
- err = json.Unmarshal(msg.Data, result)
- if err != nil {
- return err
- }
- if result.ErrorNo != 200 {
- return errors.New(result.ErrorDesc)
- }
- if out != nil {
- return json.Unmarshal([]byte(result.Result), out)
- }
- return nil
- }
- func (q *NatsBus) Request(subject string, data interface{}, timeout time.Duration, out interface{}) error {
- payload := []byte{}
- if data != nil {
- payload, _ = json.Marshal(data)
- }
- msg, err := q.nc.Request(subject, payload, timeout)
- if err != nil {
- return err
- }
- if out != nil {
- return json.Unmarshal(msg.Data, out)
- }
- return nil
- }
- // 退出
- func (q *NatsBus) Quit() {
- q.nc.Drain()
- q.nc.Close()
- }
- func NewNatsBus2(natsUri string, MaxReconnect int, ReconnDelaySecond int, streamWacther []*NatsStreamWather, replyers []*NatsMsgReplyer) (*NatsBus, error) {
- bus, err := NewNatsBus(natsUri, MaxReconnect, ReconnDelaySecond, streamWacther)
- if err != nil {
- return nil, err
- }
- bus.replyers = replyers
- return bus, nil
- }
- // 可变参数
- func NewNatsBus3(natsUri string, MaxReconnect int, ReconnDelaySecond int, streamWacther []*NatsStreamWather, replyers ...*NatsMsgReplyer) (*NatsBus, error) {
- bus, err := NewNatsBus(natsUri, MaxReconnect, ReconnDelaySecond, streamWacther)
- if err != nil {
- return nil, err
- }
- bus.replyers = replyers
- return bus, nil
- }
- func NewNatsBusWithName(name string, natsUri string, MaxReconnect int, ReconnDelaySecond int, streamWacther []*NatsStreamWather) (*NatsBus, error) {
- url := natsUri
- if len(url) < 1 {
- url = nats.DefaultURL
- }
- fmt.Println("conning to nats ====> ", url)
- bus := &NatsBus{
- reconnChan: make(chan int),
- streamWacther: streamWacther,
- topicWatcher: []*NatsTopicWather{},
- }
- nc, err := nats.Connect(url,
- nats.Name(name),
- nats.MaxReconnects(MaxReconnect),
- nats.ReconnectWait(time.Duration(ReconnDelaySecond*int(time.Second))),
- nats.DisconnectErrHandler(func(nc *nats.Conn, err error) {
- fmt.Printf("%s Got disconnected! Reason: %q\n", time.Now().Format("2006-01-02 15:04:05"), err)
- bus.reconnChan <- ConnStateDisconned
- }),
- nats.ReconnectHandler(func(nc *nats.Conn) {
- fmt.Printf("%s Got reconnected to %v!\n", time.Now().Format("2006-01-02 15:04:05"), nc.ConnectedUrl())
- bus.reconnChan <- ConnStateReconned
- }),
- nats.ClosedHandler(func(nc *nats.Conn) {
- tip := fmt.Sprintf("%s Connection closed. Reason: %q\n", time.Now().Format("2006-01-02 15:04:05"), nc.LastError())
- fmt.Println(tip)
- // panic(tip)
- }),
- )
- if err != nil {
- fmt.Println("conn to nats failed ====> ", url, err)
- return nil, err
- }
- bus.nc = nc
- fmt.Printf("%s nats bus Connected: %s\n", time.Now().Format("2006-01-02 15:04:05"), url)
- return bus, nil
- }
- // 可变参数
- func NewNatsBus4(name string, natsUri string, MaxReconnect int, ReconnDelaySecond int, streamWacther []*NatsStreamWather, replyers ...*NatsMsgReplyer) (*NatsBus, error) {
- bus, err := NewNatsBusWithName(name, natsUri, MaxReconnect, ReconnDelaySecond, streamWacther)
- if err != nil {
- return nil, err
- }
- bus.replyers = replyers
- return bus, nil
- }
- func NewNatsBus(natsUri string, MaxReconnect int, ReconnDelaySecond int, streamWacther []*NatsStreamWather) (*NatsBus, error) {
- url := natsUri
- if len(url) < 1 {
- url = nats.DefaultURL
- }
- fmt.Println("conning to nats ====> ", url)
- bus := &NatsBus{
- reconnChan: make(chan int),
- streamWacther: streamWacther,
- topicWatcher: []*NatsTopicWather{},
- }
- nc, err := nats.Connect(url,
- // nats.Name("render1"),
- nats.MaxReconnects(MaxReconnect),
- nats.ReconnectWait(time.Duration(ReconnDelaySecond*int(time.Second))),
- nats.DisconnectErrHandler(func(nc *nats.Conn, err error) {
- fmt.Printf("%s Got disconnected! Reason: %q\n", time.Now().Format("2006-01-02 15:04:05"), err)
- bus.reconnChan <- ConnStateDisconned
- }),
- nats.ReconnectHandler(func(nc *nats.Conn) {
- fmt.Printf("%s Got reconnected to %v!\n", time.Now().Format("2006-01-02 15:04:05"), nc.ConnectedUrl())
- bus.reconnChan <- ConnStateReconned
- }),
- nats.ClosedHandler(func(nc *nats.Conn) {
- tip := fmt.Sprintf("%s Connection closed. Reason: %q\n", time.Now().Format("2006-01-02 15:04:05"), nc.LastError())
- fmt.Println(tip)
- // panic(tip)
- }),
- )
- if err != nil {
- fmt.Println("conn to nats failed ====> ", url, err)
- return nil, err
- }
- bus.nc = nc
- fmt.Printf("%s nats bus Connected: %s\n", time.Now().Format("2006-01-02 15:04:05"), url)
- return bus, nil
- }
- // 创建jetStream
- func (q *NatsBus) JetStream() (nats.JetStreamContext, error) {
- return q.nc.JetStream()
- }
- // 创建jetStream
- func (q *NatsBus) CreateStream(streamName string, topic string) (nats.JetStreamContext, error) {
- js, err := q.nc.JetStream()
- if err != nil {
- return nil, err
- }
- stream, _ := js.StreamInfo(streamName)
- if stream != nil {
- return js, nil
- }
- _, err = js.AddStream(&nats.StreamConfig{
- Name: streamName,
- Subjects: []string{topic},
- Retention: nats.WorkQueuePolicy,
- })
- if err != nil {
- fmt.Println("bus CreateStream err=>", err.Error())
- return nil, err
- }
- return js, nil
- }
- // 监听流数据
- func (q *NatsBus) WatchStream(ctxTerm, ctxConn context.Context, conf *NatsStreamWather) error {
- streamName := conf.Stream
- topic := conf.Topic
- queue := conf.Queue
- fmt.Println("Watching Stream ", conf.Stream, conf.Topic, conf.Queue)
- //创建对应的jet stream 以便模型创建消息不回丢失
- js, err := q.CreateStream(streamName, topic)
- if err != nil {
- fmt.Println("WatchStream error=>", err.Error())
- return err
- }
- opts := []nats.SubOpt{nats.BindStream(streamName)} //nats.MaxDeliver(3)
- if conf.AckWaitMinute > 0 {
- opts = append(opts, nats.AckWait(time.Duration(conf.AckWaitMinute*int64(time.Minute))))
- }
- if conf.AckWaitMinuteFloat > 0 {
- opts = append(opts, nats.AckWait(time.Duration(int64(conf.AckWaitMinuteFloat*float64(time.Minute)))))
- }
- sub, err := js.PullSubscribe(topic, queue, opts...)
- if err != nil {
- fmt.Println("WatchStream QueueSubscribeSync err ", conf.Stream, conf.Topic, err)
- return err
- }
- fmt.Println("Watching Stream succ=>", conf.Stream, conf.Topic, conf.Queue)
- var currHandingMsg *nats.Msg = nil
- for {
- select {
- case <-ctxTerm.Done():
- fmt.Println("Watching Stream Termed", streamName, topic)
- if currHandingMsg != nil {
- err := currHandingMsg.Nak()
- fmt.Println("terminate currMsg ", err)
- }
- //中断链接
- q.Quit()
- return nil
- case <-ctxConn.Done():
- fmt.Println("Watching Stream Conn closed", streamName, topic)
- if currHandingMsg != nil {
- err := currHandingMsg.Nak()
- fmt.Println("terminate currMsg ", err)
- }
- return nil
- default:
- currHandingMsg = nil
- msgs, err := sub.Fetch(1)
- if err == nil && len(msgs) > 0 {
- msg := msgs[0]
- currHandingMsg = msg
- meta, _ := msg.Metadata()
- fmt.Println("meta for ", meta.Stream, "#", topic, "#", meta.Consumer, "#")
- fmt.Println("meta NumDelivered", meta.NumDelivered, "NumPending ", meta.NumPending)
- fmt.Println("meta Sequence.Consumer", meta.Sequence.Consumer, "Sequence.Stream ", meta.Sequence.Stream)
- if conf.Cb2 != nil {
- conf.Cb2(msg)
- } else if conf.Cb != nil {
- var req interface{}
- if conf.Entity != nil {
- req = conf.Entity()
- }
- err = json.Unmarshal(msg.Data, req)
- if err != nil {
- msg.Term()
- fmt.Println("work msg err=>", err.Error())
- continue
- }
- conf.Cb(msg, req)
- } else {
- fmt.Println("error no stream wather callback!!!!")
- }
- currHandingMsg = nil
- }
- }
- }
- }
- func (q *NatsBus) GetNatsConn() *nats.Conn {
- return q.nc
- }
- func (q *NatsBus) Run(cb func()) {
- //ctx, cancel := context.WithCancel(context.Background())
- //ctx, stop := context.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
- ctxTerm, cancelTerm := context.WithCancel(context.Background())
- ctxConn, cancelCnn := context.WithCancel(context.Background())
- defer cancelTerm()
- defer cancelCnn()
- go func() {
- exit := make(chan os.Signal, 1)
- signal.Notify(exit, os.Interrupt, syscall.SIGTERM)
- <-exit
- cancelTerm()
- time.Sleep(1 * time.Second)
- os.Exit(1)
- }()
- var QueueSubscribe = func(rplyer *NatsMsgReplyer) {
- r1 := rplyer
- queue := fmt.Sprintf("%s.queue", r1.Subject)
- q.nc.QueueSubscribe(r1.Subject, queue, func(msg *nats.Msg) {
- defer func() {
- if err := recover(); err != nil {
- fmt.Println("catch error ", err)
- }
- }()
- fmt.Println("replay for ", r1.Subject)
- var req interface{}
- if r1.Entity != nil {
- req = r1.Entity()
- err := json.Unmarshal(msg.Data, req)
- if err != nil {
- msg.Term()
- fmt.Println("rplyer work msg err", err.Error())
- return
- }
- }
- if r1.Cb != nil {
- out := r1.Cb(msg, req)
- if out != nil {
- payload, err := json.Marshal(out)
- if err != nil {
- fmt.Println("error reply for", r1.Subject, err)
- }
- err = msg.Respond(payload)
- if err != nil {
- fmt.Println("error reply for", r1.Subject, out)
- }
- }
- return
- }
- if r1.Cb2 != nil {
- out, err := r1.Cb2(msg, req)
- resp := &NatsResponse{ErrorNo: 200}
- if err != nil {
- fmt.Println("error reply2 for", r1.Subject, err)
- resp.ErrorDesc = err.Error()
- resp.ErrorNo = 400
- } else {
- payload, _ := json.Marshal(out)
- resp.Result = string(payload)
- }
- payload, _ := json.Marshal(resp)
- err = msg.Respond(payload)
- if err != nil {
- fmt.Println("error reply2 for", r1.Subject, out, err.Error())
- }
- return
- }
- })
- }
- var startSubscribe = func() {
- //启动所有watcher
- for _, item := range q.streamWacther {
- go q.WatchStream(ctxTerm, ctxConn, item)
- }
- for _, rplyer := range q.replyers {
- QueueSubscribe(rplyer)
- }
- }
- q.runtimeStartStreamWatcher = func(conf *NatsStreamWather) error {
- go q.WatchStream(ctxTerm, ctxConn, conf)
- return nil
- }
- startSubscribe()
- if cb != nil {
- cb()
- }
- for {
- select {
- case state := <-q.reconnChan:
- if state == ConnStateDisconned {
- cancelCnn()
- } else if state == ConnStateReconned {
- ctxConn, cancelCnn = context.WithCancel(context.Background())
- startSubscribe()
- }
- }
- }
- }
|