| @@ -0,0 +1,8 @@ | |||||
| package sysevent | |||||
| type Config struct { | |||||
| Address string `json:"address"` | |||||
| Account string `json:"account"` | |||||
| Password string `json:"password"` | |||||
| VHost string `json:"vhost"` | |||||
| } | |||||
| @@ -0,0 +1,119 @@ | |||||
| package sysevent | |||||
| import ( | |||||
| "fmt" | |||||
| "github.com/streadway/amqp" | |||||
| "gitlink.org.cn/cloudream/common/pkgs/async" | |||||
| "gitlink.org.cn/cloudream/common/utils/serder" | |||||
| ) | |||||
| type PublisherEvent interface{} | |||||
| type PublisherExited struct { | |||||
| Err error | |||||
| } | |||||
| type PublishError struct { | |||||
| Err error | |||||
| } | |||||
| type OtherError struct { | |||||
| Err error | |||||
| } | |||||
| type Publisher struct { | |||||
| connection *amqp.Connection | |||||
| channel *amqp.Channel | |||||
| eventChan *async.UnboundChannel[SysEvent] | |||||
| thisSource Source | |||||
| } | |||||
| func NewPublisher(cfg Config, thisSource Source) (*Publisher, error) { | |||||
| config := amqp.Config{ | |||||
| Vhost: cfg.VHost, | |||||
| } | |||||
| url := fmt.Sprintf("amqp://%s:%s@%s", cfg.Account, cfg.Password, cfg.Address) | |||||
| connection, err := amqp.DialConfig(url, config) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| channel, err := connection.Channel() | |||||
| if err != nil { | |||||
| connection.Close() | |||||
| return nil, fmt.Errorf("openning channel on connection: %w", err) | |||||
| } | |||||
| _, err = channel.QueueDeclare( | |||||
| SysEventQueueName, | |||||
| false, | |||||
| true, | |||||
| false, | |||||
| false, | |||||
| nil, | |||||
| ) | |||||
| if err != nil { | |||||
| return nil, fmt.Errorf("declare queue: %w", err) | |||||
| } | |||||
| pub := &Publisher{ | |||||
| connection: connection, | |||||
| channel: channel, | |||||
| eventChan: async.NewUnboundChannel[SysEvent](), | |||||
| thisSource: thisSource, | |||||
| } | |||||
| return pub, nil | |||||
| } | |||||
| func (p *Publisher) Start() *async.UnboundChannel[PublisherEvent] { | |||||
| ch := async.NewUnboundChannel[PublisherEvent]() | |||||
| go func() { | |||||
| defer ch.Close() | |||||
| defer p.channel.Close() | |||||
| defer p.connection.Close() | |||||
| for { | |||||
| event := <-p.eventChan.Receive().Chan() | |||||
| if event.Err != nil { | |||||
| if event.Err == async.ErrChannelClosed { | |||||
| ch.Send(PublisherExited{Err: nil}) | |||||
| } else { | |||||
| ch.Send(PublisherExited{Err: event.Err}) | |||||
| } | |||||
| return | |||||
| } | |||||
| eventData, err := serder.ObjectToJSONEx(event.Value) | |||||
| if err != nil { | |||||
| ch.Send(OtherError{Err: fmt.Errorf("serialize event data: %w", err)}) | |||||
| continue | |||||
| } | |||||
| err = p.channel.Publish("", SysEventQueueName, false, false, amqp.Publishing{ | |||||
| ContentType: "text/plain", | |||||
| Body: eventData, | |||||
| Expiration: "60000", // 消息超时时间默认1分钟 | |||||
| }) | |||||
| if err != nil { | |||||
| ch.Send(PublishError{Err: err}) | |||||
| continue | |||||
| } | |||||
| } | |||||
| }() | |||||
| return ch | |||||
| } | |||||
| // Publish 发布事件,自动补齐时间戳和源信息 | |||||
| func (p *Publisher) Publish(evt SysEvent) { | |||||
| // TODO 补齐时间戳和源信息 | |||||
| p.eventChan.Send(evt) | |||||
| } | |||||
| // PublishRaw 完全原样发布事件,不补齐任何信息 | |||||
| func (p *Publisher) PublishRaw(evt SysEvent) { | |||||
| p.eventChan.Send(evt) | |||||
| } | |||||
| @@ -0,0 +1,9 @@ | |||||
| package sysevent | |||||
| const ( | |||||
| SysEventQueueName = "SysEventQueue" | |||||
| ) | |||||
| type SysEvent = any // TODO 换成具体的类型 | |||||
| type Source = any // TODO 换成具体的类型 | |||||
| @@ -0,0 +1,121 @@ | |||||
| package sysevent | |||||
| import ( | |||||
| "fmt" | |||||
| "sync" | |||||
| "github.com/streadway/amqp" | |||||
| "gitlink.org.cn/cloudream/common/pkgs/async" | |||||
| "gitlink.org.cn/cloudream/common/utils/lo2" | |||||
| "gitlink.org.cn/cloudream/common/utils/serder" | |||||
| ) | |||||
| type Watcher interface { | |||||
| OnEvent(event SysEvent) | |||||
| } | |||||
| type WatcherEvent interface{} | |||||
| type WatcherExited struct { | |||||
| Err error | |||||
| } | |||||
| type WatcherHost struct { | |||||
| watchers []Watcher | |||||
| lock sync.Mutex | |||||
| connection *amqp.Connection | |||||
| channel *amqp.Channel | |||||
| recvChan <-chan amqp.Delivery | |||||
| } | |||||
| func NewWatcherHost(cfg Config) (*WatcherHost, error) { | |||||
| config := amqp.Config{ | |||||
| Vhost: cfg.VHost, | |||||
| } | |||||
| url := fmt.Sprintf("amqp://%s:%s@%s", cfg.Account, cfg.Password, cfg.Address) | |||||
| connection, err := amqp.DialConfig(url, config) | |||||
| if err != nil { | |||||
| return nil, err | |||||
| } | |||||
| channel, err := connection.Channel() | |||||
| if err != nil { | |||||
| connection.Close() | |||||
| return nil, fmt.Errorf("openning channel on connection: %w", err) | |||||
| } | |||||
| _, err = channel.QueueDeclare( | |||||
| SysEventQueueName, | |||||
| false, | |||||
| true, | |||||
| false, | |||||
| false, | |||||
| nil, | |||||
| ) | |||||
| if err != nil { | |||||
| channel.Close() | |||||
| connection.Close() | |||||
| return nil, fmt.Errorf("declare queue: %w", err) | |||||
| } | |||||
| recvChan, err := channel.Consume(SysEventQueueName, "", true, false, true, false, nil) | |||||
| if err != nil { | |||||
| channel.Close() | |||||
| connection.Close() | |||||
| return nil, fmt.Errorf("consume queue: %w", err) | |||||
| } | |||||
| wat := &WatcherHost{ | |||||
| connection: connection, | |||||
| channel: channel, | |||||
| recvChan: recvChan, | |||||
| } | |||||
| return wat, nil | |||||
| } | |||||
| func (w *WatcherHost) Start() *async.UnboundChannel[WatcherEvent] { | |||||
| ch := async.NewUnboundChannel[WatcherEvent]() | |||||
| go func() { | |||||
| defer ch.Close() | |||||
| defer w.channel.Close() | |||||
| defer w.connection.Close() | |||||
| for m := range w.recvChan { | |||||
| evt, err := serder.JSONToObjectEx[SysEvent](m.Body) | |||||
| if err != nil { | |||||
| ch.Send(OtherError{Err: fmt.Errorf("deserialize event: %w", err)}) | |||||
| continue | |||||
| } | |||||
| w.lock.Lock() | |||||
| ws := make([]Watcher, 0, len(w.watchers)) | |||||
| ws = append(ws, w.watchers...) | |||||
| w.lock.Unlock() | |||||
| for _, w := range ws { | |||||
| w.OnEvent(evt) | |||||
| } | |||||
| } | |||||
| ch.Send(WatcherExited{Err: nil}) | |||||
| }() | |||||
| return ch | |||||
| } | |||||
| func (w *WatcherHost) AddWatcher(watcher Watcher) { | |||||
| w.lock.Lock() | |||||
| defer w.lock.Unlock() | |||||
| w.watchers = append(w.watchers, watcher) | |||||
| } | |||||
| func (w *WatcherHost) RemoveWatcher(watcher Watcher) { | |||||
| w.lock.Lock() | |||||
| defer w.lock.Unlock() | |||||
| w.watchers = lo2.Remove(w.watchers, watcher) | |||||
| } | |||||