mirror of
https://github.com/gookit/event
synced 2025-09-27 03:15:58 +08:00
up: update name match logic and add more tests
This commit is contained in:
12
README.md
12
README.md
@@ -10,10 +10,12 @@ Lightweight event management, dispatch tool library implemented by Go
|
||||
|
||||
- Support for custom definition event objects
|
||||
- Support for adding multiple listeners to an event
|
||||
- Supports setting the priority of the event listener. The higher the priority, the higher the trigger.
|
||||
- Support setting the priority of the event listener, the higher the priority, the first to trigger
|
||||
- Support for a set of event listeners based on the event name prefix `PREFIX.*`.
|
||||
- add `app.*` event listen, trigger `app.run` `app.end`, Both will trigger the `app.*` event at the same time
|
||||
- `ModeSimple` - `app.*` event listen, trigger `app.run` `app.end`, Both will trigger the `app.*` event at the same time
|
||||
- `ModePath` - `db.**` event listen, trigger `db.run` `db.end`, Only trigger the `db.**` event once
|
||||
- Support for using the wildcard `*` to listen for triggers for all events
|
||||
- Support async trigger event by `go` channel consumers. use `Async(), FireAsync()`
|
||||
- Complete unit testing, unit coverage `> 95%`
|
||||
|
||||
## [中文说明](README.zh-CN.md)
|
||||
@@ -22,7 +24,7 @@ Lightweight event management, dispatch tool library implemented by Go
|
||||
|
||||
## GoDoc
|
||||
|
||||
- [Godoc for github](https://pkg.go.dev/github.com/gookit/event)
|
||||
- [Godoc for GitHub](https://pkg.go.dev/github.com/gookit/event)
|
||||
|
||||
## Install
|
||||
|
||||
@@ -38,7 +40,9 @@ go get github.com/gookit/event
|
||||
- `MustTrigger/MustFire(name string, params M) Event` Trigger event, there will be panic if there is an error
|
||||
- `FireEvent(e Event) (err error)` Trigger an event based on a given event instance
|
||||
- `FireBatch(es ...interface{}) (ers []error)` Trigger multiple events at once
|
||||
- `AsyncFire(e Event)` Async fire event by 'go' keywords
|
||||
- `Async/FireC(name string, params M)` Push event to `chan`, asynchronous consumption processing
|
||||
- `FireAsync(e Event)` Push event to `chan`, asynchronous consumption processing
|
||||
- `AsyncFire(e Event)` Async fire event by 'go' keywords
|
||||
|
||||
## Quick start
|
||||
|
||||
|
@@ -6,14 +6,16 @@
|
||||
[](https://coveralls.io/github/gookit/event?branch=master)
|
||||
[](https://goreportcard.com/report/github.com/gookit/event)
|
||||
|
||||
Go 实现的轻量级的事件管理、调度工具库
|
||||
`event` Go 实现的轻量级的事件管理、调度工具库
|
||||
|
||||
- 支持自定义定义事件对象
|
||||
- 支持自定义创建预定义的事件对象
|
||||
- 支持对一个事件添加多个监听器
|
||||
- 支持设置事件监听器的优先级,优先级越高越先触发
|
||||
- 支持根据事件名称前缀 `PREFIX.*` 来进行一组事件监听.
|
||||
- 注册`app.*` 事件的监听,触发 `app.run` `app.end` 时,都将同时会触发 `app.*` 事件
|
||||
- 支持使用通配符 `*` 来监听全部事件的触发
|
||||
- 支持通过通配符 `*` 来进行一组事件的匹配监听.
|
||||
- `ModeSimple` - 注册 `app.*` 事件的监听,触发 `app.run` `app.end` 时,都将同时会触发 `app.*` 事件
|
||||
- `ModePath` - **NEW** `*` 只匹配一段非 `.` 的字符,可以进行更精细的监听; `**` 匹配任意多个字符,只能用于开头或结尾
|
||||
- 支持直接使用通配符 `*` 来监听全部事件的触发
|
||||
- 支持触发事件时投递到 `chan`, 异步进行消费处理. 触发: `Async(), FireAsync()`
|
||||
- 完善的单元测试,单元覆盖率 `> 95%`
|
||||
|
||||
## [English](README.md)
|
||||
@@ -22,7 +24,7 @@ English introduction, please see **[EN README](README.md)**
|
||||
|
||||
## GoDoc
|
||||
|
||||
- [Godoc for github](https://pkg.go.dev/github.com/gookit/event)
|
||||
- [Godoc for GitHub](https://pkg.go.dev/github.com/gookit/event)
|
||||
|
||||
## 安装
|
||||
|
||||
@@ -35,10 +37,12 @@ go get github.com/gookit/event
|
||||
- `On/Listen(name string, listener Listener, priority ...int)` 注册事件监听
|
||||
- `Subscribe/AddSubscriber(sbr Subscriber)` 订阅,支持注册多个事件监听
|
||||
- `Trigger/Fire(name string, params M) (error, Event)` 触发事件
|
||||
- `MustTrigger/MustFire(name string, params M) Event` 触发事件,有错误则会panic
|
||||
- `FireEvent(e Event) (err error)` 根据给定的事件实例,触发事件
|
||||
- `MustTrigger/MustFire(name string, params M) Event` 触发事件,有错误则会panic
|
||||
- `FireEvent(e Event) (err error)` 根据给定的事件实例,触发事件
|
||||
- `FireBatch(es ...interface{}) (ers []error)` 一次触发多个事件
|
||||
- `AsyncFire(e Event)` Async fire event by 'go' keywords
|
||||
- `Async/FireC(name string, params M)` 投递触事件到 `chan`,异步消费处理
|
||||
- `FireAsync(e Event)` 投递触事件到 `chan`,异步消费处理
|
||||
- `AsyncFire(e Event)` 简单的通过 `go` 异步触发事件
|
||||
|
||||
## 快速使用
|
||||
|
||||
|
14
event.go
14
event.go
@@ -1,10 +1,12 @@
|
||||
// Package event is lightweight event manager and dispatcher implements by Go.
|
||||
package event
|
||||
|
||||
// Wildcard event name
|
||||
const Wildcard = "*"
|
||||
const AnyNode = "*"
|
||||
const AllNode = "**"
|
||||
// wildcard event name
|
||||
const (
|
||||
Wildcard = "*"
|
||||
AnyNode = "*"
|
||||
AllNode = "**"
|
||||
)
|
||||
|
||||
const (
|
||||
// ModeSimple old mode, simple match group listener.
|
||||
@@ -44,8 +46,8 @@ type ManagerFace interface {
|
||||
type Options struct {
|
||||
// EnableLock enable lock on fire event.
|
||||
EnableLock bool
|
||||
// ChanLength for fire events by goroutine
|
||||
ChanLength int
|
||||
// ChannelSize for fire events by goroutine
|
||||
ChannelSize int
|
||||
ConsumerNum int
|
||||
// MatchMode event name match mode. default is ModeSimple
|
||||
MatchMode uint8
|
||||
|
99
manager.go
99
manager.go
@@ -6,6 +6,11 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultChannelSize = 100
|
||||
defaultConsumerNum = 5
|
||||
)
|
||||
|
||||
// Manager event manager definition. for manage events and listeners
|
||||
type Manager struct {
|
||||
Options
|
||||
@@ -44,8 +49,8 @@ func NewManager(name string, fns ...OptionFn) *Manager {
|
||||
}
|
||||
|
||||
// for async fire by goroutine
|
||||
em.ConsumerNum = 5
|
||||
em.ChanLength = 10
|
||||
em.ConsumerNum = defaultConsumerNum
|
||||
em.ChannelSize = defaultChannelSize
|
||||
|
||||
// apply options
|
||||
return em.WithOptions(fns...)
|
||||
@@ -154,6 +159,29 @@ func (em *Manager) Trigger(name string, params M) (error, Event) {
|
||||
|
||||
// Fire trigger event by name. if not found listener, will return (nil, nil)
|
||||
func (em *Manager) Fire(name string, params M) (err error, e Event) {
|
||||
// call listeners handle event
|
||||
e, err = em.fireByName(name, params, false)
|
||||
return
|
||||
}
|
||||
|
||||
// Async fire event by go channel.
|
||||
func (em *Manager) Async(name string, params M) {
|
||||
_, _ = em.fireByName(name, params, true)
|
||||
}
|
||||
|
||||
// FireC async fire event by go channel. alias of the method Async()
|
||||
func (em *Manager) FireC(name string, params M) {
|
||||
_, _ = em.fireByName(name, params, true)
|
||||
}
|
||||
|
||||
// fire event by name.
|
||||
//
|
||||
// if useCh is true, will async fire by channel. always return (nil, nil)
|
||||
//
|
||||
// On useCh=false:
|
||||
// - will call listeners handle event.
|
||||
// - if not found listener, will return (nil, nil)
|
||||
func (em *Manager) fireByName(name string, params M, useCh bool) (e Event, err error) {
|
||||
name = goodName(name, false)
|
||||
|
||||
// use pre-defined Event
|
||||
@@ -163,10 +191,16 @@ func (em *Manager) Fire(name string, params M) (err error, e Event) {
|
||||
e.SetData(params)
|
||||
}
|
||||
} else {
|
||||
// create a basic event instance
|
||||
// create new basic event instance
|
||||
e = em.newBasicEvent(name, params)
|
||||
}
|
||||
|
||||
// fire by channel
|
||||
if useCh {
|
||||
em.FireAsync(e)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// call listeners handle event
|
||||
err = em.FireEvent(e)
|
||||
return
|
||||
@@ -183,17 +217,6 @@ func (em *Manager) FireEvent(e Event) (err error) {
|
||||
e.Abort(false)
|
||||
name := e.Name()
|
||||
|
||||
// fire direct matched listeners. eg: db.user.add
|
||||
if lq, ok := em.listeners[name]; ok {
|
||||
// sort by priority before call.
|
||||
for _, li := range lq.Sort().Items() {
|
||||
err = li.Listener.Handle(e)
|
||||
if err != nil || e.IsAborted() {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fire group listeners by wildcard. eg "db.user.*"
|
||||
if em.MatchMode == ModePath {
|
||||
err = em.firePathMode(name, e)
|
||||
@@ -223,6 +246,17 @@ func (em *Manager) FireEvent(e Event) (err error) {
|
||||
// Example:
|
||||
// - event "db.user.add" will trigger listeners on the "db.user.*"
|
||||
func (em *Manager) fireSimpleMode(name string, e Event) (err error) {
|
||||
// fire direct matched listeners. eg: db.user.add
|
||||
if lq, ok := em.listeners[name]; ok {
|
||||
// sort by priority before call.
|
||||
for _, li := range lq.Sort().Items() {
|
||||
err = li.Listener.Handle(e)
|
||||
if err != nil || e.IsAborted() {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pos := strings.LastIndexByte(name, '.')
|
||||
|
||||
if pos > 0 && pos < len(name) {
|
||||
@@ -247,12 +281,8 @@ func (em *Manager) fireSimpleMode(name string, e Event) (err error) {
|
||||
// - event "db.user.add" will trigger listeners on the "db.**"
|
||||
// - event "db.user.add" will trigger listeners on the "db.user.*"
|
||||
func (em *Manager) firePathMode(name string, e Event) (err error) {
|
||||
if strings.IndexByte(name, '.') < 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for pattern, lq := range em.listeners {
|
||||
if pattern != name && matchNodePath(pattern, name, ".") {
|
||||
if pattern == name || matchNodePath(pattern, name, ".") {
|
||||
for _, li := range lq.Sort().Items() {
|
||||
err = li.Listener.Handle(e)
|
||||
if err != nil || e.IsAborted() {
|
||||
@@ -265,14 +295,18 @@ func (em *Manager) firePathMode(name string, e Event) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Fire2 alias of the method Fire()
|
||||
func (em *Manager) Fire2(name string, params M) (error, Event) {
|
||||
return em.Fire(name, params)
|
||||
}
|
||||
// FireAsync async fire event by go channel.
|
||||
//
|
||||
// Note: if you want to use this method, you should call the method Close() after all events are fired.
|
||||
func (em *Manager) FireAsync(e Event) {
|
||||
if em.ConsumerNum <= 0 {
|
||||
em.ConsumerNum = defaultConsumerNum
|
||||
}
|
||||
if em.ChannelSize <= 0 {
|
||||
em.ChannelSize = defaultChannelSize
|
||||
}
|
||||
|
||||
// FireByChan async fire event by go channel
|
||||
func (em *Manager) FireByChan(e Event) {
|
||||
// once make
|
||||
// once make consumers
|
||||
em.once.Do(func() {
|
||||
em.makeConsumers()
|
||||
})
|
||||
@@ -283,11 +317,12 @@ func (em *Manager) FireByChan(e Event) {
|
||||
|
||||
// async fire event by 'go' keywords
|
||||
func (em *Manager) makeConsumers() {
|
||||
em.ch = make(chan Event, em.ChanLength)
|
||||
em.ch = make(chan Event, em.ChannelSize)
|
||||
|
||||
// make event consumers
|
||||
for i := 0; i < em.ConsumerNum; i++ {
|
||||
go func() {
|
||||
// keep running until channel closed
|
||||
for e := range em.ch {
|
||||
_ = em.FireEvent(e) // ignore async fire error
|
||||
}
|
||||
@@ -295,6 +330,14 @@ func (em *Manager) makeConsumers() {
|
||||
}
|
||||
}
|
||||
|
||||
// Close manager and event channel
|
||||
func (em *Manager) Close() error {
|
||||
if em.ch != nil {
|
||||
close(em.ch)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FireBatch fire multi event at once.
|
||||
//
|
||||
// Usage:
|
||||
@@ -316,7 +359,7 @@ func (em *Manager) FireBatch(es ...any) (ers []error) {
|
||||
return
|
||||
}
|
||||
|
||||
// AsyncFire async fire event by 'go' keywords
|
||||
// AsyncFire simple async fire event by 'go' keywords
|
||||
func (em *Manager) AsyncFire(e Event) {
|
||||
go func(e Event) {
|
||||
_ = em.FireEvent(e)
|
||||
|
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gookit/event"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -77,4 +78,73 @@ func TestManager_Fire_usePathMode(t *testing.T) {
|
||||
assert.Contains(t, str, "db.*.update|")
|
||||
assert.True(t, strings.Count(str, "|") == 3)
|
||||
})
|
||||
buf.Reset()
|
||||
|
||||
t.Run("not-exist", func(t *testing.T) {
|
||||
err, e := em.Fire("not-exist", event.M{"user": "inhere"})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "inhere", e.Get("user"))
|
||||
str := buf.String()
|
||||
fmt.Println(str)
|
||||
assert.Equal(t, "*|", str)
|
||||
})
|
||||
}
|
||||
|
||||
func TestManager_Fire_AllNode(t *testing.T) {
|
||||
em := event.NewManager("test", event.UsePathMode, event.EnableLock)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
em.Listen("**.add", event.ListenerFunc(func(e event.Event) error {
|
||||
_, _ = buf.WriteString("**.add|")
|
||||
return nil
|
||||
}))
|
||||
|
||||
err, e := em.Trigger("db.user.add", event.M{"user": "inhere"})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "inhere", e.Get("user"))
|
||||
str := buf.String()
|
||||
assert.Equal(t, "**.add|", str)
|
||||
}
|
||||
|
||||
func TestManager_FireC(t *testing.T) {
|
||||
em := event.NewManager("test", event.UsePathMode, event.EnableLock)
|
||||
defer func(em *event.Manager) {
|
||||
_ = em.Close()
|
||||
}(em)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
em.Listen("db.user.*", event.ListenerFunc(func(e event.Event) error {
|
||||
_, _ = buf.WriteString("db.user.*|")
|
||||
return nil
|
||||
}))
|
||||
em.Listen("db.**", event.ListenerFunc(func(e event.Event) error {
|
||||
_, _ = buf.WriteString("db.**|")
|
||||
return nil
|
||||
}), 1)
|
||||
|
||||
em.Listen("db.user.add", event.ListenerFunc(func(e event.Event) error {
|
||||
_, _ = buf.WriteString("db.user.add|")
|
||||
return nil
|
||||
}), 2)
|
||||
|
||||
assert.True(t, em.HasListeners("db.user.*"))
|
||||
|
||||
em.FireC("db.user.add", event.M{"user": "inhere"})
|
||||
time.Sleep(time.Millisecond * 100) // wait for async
|
||||
|
||||
str := buf.String()
|
||||
fmt.Println(str)
|
||||
assert.Contains(t, str, "db.**|")
|
||||
assert.Contains(t, str, "db.user.*|")
|
||||
assert.Contains(t, str, "db.user.add|")
|
||||
assert.True(t, strings.Count(str, "|") == 3)
|
||||
buf.Reset()
|
||||
|
||||
em.WithOptions(func(o *event.Options) {
|
||||
o.ChannelSize = 0
|
||||
o.ConsumerNum = 0
|
||||
})
|
||||
em.Async("not-exist", event.M{"user": "inhere"})
|
||||
time.Sleep(time.Millisecond * 100) // wait for async
|
||||
assert.Empty(t, buf.String())
|
||||
}
|
||||
|
10
util.go
10
util.go
@@ -46,8 +46,14 @@ func goodName(name string, isReg bool) string {
|
||||
panic("event: the event name cannot be empty")
|
||||
}
|
||||
|
||||
if isReg && (name == AllNode || name == Wildcard) {
|
||||
return Wildcard
|
||||
// on add listener
|
||||
if isReg {
|
||||
if name == AllNode || name == Wildcard {
|
||||
return Wildcard
|
||||
}
|
||||
if strings.HasPrefix(name, AllNode) {
|
||||
return name
|
||||
}
|
||||
}
|
||||
|
||||
if !goodNameReg.MatchString(name) {
|
||||
|
Reference in New Issue
Block a user