aury-boot 0.0.30__py3-none-any.whl → 0.0.32__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aury/boot/_version.py +2 -2
- aury/boot/application/__init__.py +2 -4
- aury/boot/application/app/components.py +2 -0
- aury/boot/application/config/settings.py +6 -0
- aury/boot/commands/templates/project/AGENTS.md.tpl +54 -0
- aury/boot/commands/templates/project/env_templates/messaging.tpl +21 -13
- aury/boot/commands/templates/project/env_templates/monitoring.tpl +2 -0
- aury/boot/infrastructure/__init__.py +4 -8
- aury/boot/infrastructure/channel/__init__.py +9 -8
- aury/boot/infrastructure/channel/backends/__init__.py +2 -6
- aury/boot/infrastructure/channel/backends/broadcaster.py +141 -0
- aury/boot/infrastructure/channel/base.py +5 -2
- aury/boot/infrastructure/channel/manager.py +25 -24
- aury/boot/infrastructure/events/__init__.py +4 -6
- aury/boot/infrastructure/events/backends/__init__.py +2 -4
- aury/boot/infrastructure/events/backends/broadcaster.py +189 -0
- aury/boot/infrastructure/events/base.py +9 -4
- aury/boot/infrastructure/events/manager.py +24 -20
- aury/boot/infrastructure/monitoring/alerting/manager.py +2 -0
- aury/boot/infrastructure/monitoring/alerting/rules.py +16 -0
- aury/boot/infrastructure/monitoring/tracing/processor.py +31 -1
- aury/boot/infrastructure/monitoring/tracing/provider.py +2 -0
- {aury_boot-0.0.30.dist-info → aury_boot-0.0.32.dist-info}/METADATA +4 -1
- {aury_boot-0.0.30.dist-info → aury_boot-0.0.32.dist-info}/RECORD +26 -28
- aury/boot/infrastructure/channel/backends/memory.py +0 -126
- aury/boot/infrastructure/channel/backends/redis.py +0 -130
- aury/boot/infrastructure/events/backends/memory.py +0 -86
- aury/boot/infrastructure/events/backends/redis.py +0 -169
- {aury_boot-0.0.30.dist-info → aury_boot-0.0.32.dist-info}/WHEEL +0 -0
- {aury_boot-0.0.30.dist-info → aury_boot-0.0.32.dist-info}/entry_points.txt +0 -0
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
"""内存通道后端。
|
|
2
|
-
|
|
3
|
-
适用于单进程场景,如开发环境或简单应用。
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
from __future__ import annotations
|
|
7
|
-
|
|
8
|
-
import asyncio
|
|
9
|
-
from collections.abc import AsyncIterator
|
|
10
|
-
import contextlib
|
|
11
|
-
import fnmatch
|
|
12
|
-
|
|
13
|
-
from aury.boot.common.logging import logger
|
|
14
|
-
|
|
15
|
-
from ..base import ChannelMessage, IChannel
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class MemoryChannel(IChannel):
|
|
19
|
-
"""内存通道实现。
|
|
20
|
-
|
|
21
|
-
使用 asyncio.Queue 实现进程内的发布/订阅。
|
|
22
|
-
|
|
23
|
-
注意:仅适用于单进程,不支持跨进程通信。
|
|
24
|
-
"""
|
|
25
|
-
|
|
26
|
-
def __init__(self, max_subscribers: int = 1000) -> None:
|
|
27
|
-
"""初始化内存通道。
|
|
28
|
-
|
|
29
|
-
Args:
|
|
30
|
-
max_subscribers: 每个通道最大订阅者数量
|
|
31
|
-
"""
|
|
32
|
-
self._max_subscribers = max_subscribers
|
|
33
|
-
# channel -> list of queues
|
|
34
|
-
self._subscribers: dict[str, list[asyncio.Queue[ChannelMessage]]] = {}
|
|
35
|
-
# pattern -> list of queues (用于 psubscribe)
|
|
36
|
-
self._pattern_subscribers: dict[str, list[asyncio.Queue[ChannelMessage]]] = {}
|
|
37
|
-
self._lock = asyncio.Lock()
|
|
38
|
-
|
|
39
|
-
async def publish(self, channel: str, message: ChannelMessage) -> None:
|
|
40
|
-
"""发布消息到通道。"""
|
|
41
|
-
message.channel = channel
|
|
42
|
-
async with self._lock:
|
|
43
|
-
# 精确匹配订阅者
|
|
44
|
-
subscribers = self._subscribers.get(channel, [])
|
|
45
|
-
for queue in subscribers:
|
|
46
|
-
try:
|
|
47
|
-
queue.put_nowait(message)
|
|
48
|
-
except asyncio.QueueFull:
|
|
49
|
-
logger.warning(f"通道 [{channel}] 订阅者队列已满,消息被丢弃")
|
|
50
|
-
|
|
51
|
-
# 模式匹配订阅者
|
|
52
|
-
for pattern, queues in self._pattern_subscribers.items():
|
|
53
|
-
if fnmatch.fnmatch(channel, pattern):
|
|
54
|
-
for queue in queues:
|
|
55
|
-
try:
|
|
56
|
-
queue.put_nowait(message)
|
|
57
|
-
except asyncio.QueueFull:
|
|
58
|
-
logger.warning(f"模式 [{pattern}] 订阅者队列已满,消息被丢弃")
|
|
59
|
-
|
|
60
|
-
async def subscribe(self, channel: str) -> AsyncIterator[ChannelMessage]:
|
|
61
|
-
"""订阅通道。"""
|
|
62
|
-
queue: asyncio.Queue[ChannelMessage] = asyncio.Queue(maxsize=100)
|
|
63
|
-
|
|
64
|
-
async with self._lock:
|
|
65
|
-
if channel not in self._subscribers:
|
|
66
|
-
self._subscribers[channel] = []
|
|
67
|
-
if len(self._subscribers[channel]) >= self._max_subscribers:
|
|
68
|
-
raise RuntimeError(f"通道 [{channel}] 订阅者数量已达上限")
|
|
69
|
-
self._subscribers[channel].append(queue)
|
|
70
|
-
|
|
71
|
-
try:
|
|
72
|
-
while True:
|
|
73
|
-
message = await queue.get()
|
|
74
|
-
yield message
|
|
75
|
-
finally:
|
|
76
|
-
async with self._lock:
|
|
77
|
-
if channel in self._subscribers:
|
|
78
|
-
with contextlib.suppress(ValueError):
|
|
79
|
-
self._subscribers[channel].remove(queue)
|
|
80
|
-
if not self._subscribers[channel]:
|
|
81
|
-
del self._subscribers[channel]
|
|
82
|
-
|
|
83
|
-
async def psubscribe(self, pattern: str) -> AsyncIterator[ChannelMessage]:
|
|
84
|
-
"""模式订阅通道。
|
|
85
|
-
|
|
86
|
-
使用 fnmatch 风格的通配符:
|
|
87
|
-
- `*` 匹配任意字符
|
|
88
|
-
- `?` 匹配单个字符
|
|
89
|
-
- `[seq]` 匹配 seq 中的任意字符
|
|
90
|
-
"""
|
|
91
|
-
queue: asyncio.Queue[ChannelMessage] = asyncio.Queue(maxsize=100)
|
|
92
|
-
|
|
93
|
-
async with self._lock:
|
|
94
|
-
if pattern not in self._pattern_subscribers:
|
|
95
|
-
self._pattern_subscribers[pattern] = []
|
|
96
|
-
if len(self._pattern_subscribers[pattern]) >= self._max_subscribers:
|
|
97
|
-
raise RuntimeError(f"模式 [{pattern}] 订阅者数量已达上限")
|
|
98
|
-
self._pattern_subscribers[pattern].append(queue)
|
|
99
|
-
|
|
100
|
-
try:
|
|
101
|
-
while True:
|
|
102
|
-
message = await queue.get()
|
|
103
|
-
yield message
|
|
104
|
-
finally:
|
|
105
|
-
async with self._lock:
|
|
106
|
-
if pattern in self._pattern_subscribers:
|
|
107
|
-
with contextlib.suppress(ValueError):
|
|
108
|
-
self._pattern_subscribers[pattern].remove(queue)
|
|
109
|
-
if not self._pattern_subscribers[pattern]:
|
|
110
|
-
del self._pattern_subscribers[pattern]
|
|
111
|
-
|
|
112
|
-
async def unsubscribe(self, channel: str) -> None:
|
|
113
|
-
"""取消订阅通道(清除所有订阅者)。"""
|
|
114
|
-
async with self._lock:
|
|
115
|
-
if channel in self._subscribers:
|
|
116
|
-
del self._subscribers[channel]
|
|
117
|
-
|
|
118
|
-
async def close(self) -> None:
|
|
119
|
-
"""关闭通道,清理所有订阅。"""
|
|
120
|
-
async with self._lock:
|
|
121
|
-
self._subscribers.clear()
|
|
122
|
-
self._pattern_subscribers.clear()
|
|
123
|
-
logger.debug("内存通道已关闭")
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
__all__ = ["MemoryChannel"]
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
"""Redis 通道后端。
|
|
2
|
-
|
|
3
|
-
适用于多进程/多实例场景,支持跨进程通信。
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
from __future__ import annotations
|
|
7
|
-
|
|
8
|
-
from collections.abc import AsyncIterator
|
|
9
|
-
from datetime import datetime
|
|
10
|
-
import json
|
|
11
|
-
from typing import TYPE_CHECKING
|
|
12
|
-
|
|
13
|
-
from aury.boot.common.logging import logger
|
|
14
|
-
|
|
15
|
-
from ..base import ChannelMessage, IChannel
|
|
16
|
-
|
|
17
|
-
if TYPE_CHECKING:
|
|
18
|
-
from aury.boot.infrastructure.clients.redis import RedisClient
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class RedisChannel(IChannel):
|
|
22
|
-
"""Redis 通道实现。
|
|
23
|
-
|
|
24
|
-
使用 Redis Pub/Sub 实现跨进程的发布/订阅。
|
|
25
|
-
"""
|
|
26
|
-
|
|
27
|
-
def __init__(self, redis_client: RedisClient) -> None:
|
|
28
|
-
"""初始化 Redis 通道。
|
|
29
|
-
|
|
30
|
-
Args:
|
|
31
|
-
redis_client: RedisClient 实例
|
|
32
|
-
"""
|
|
33
|
-
self._client = redis_client
|
|
34
|
-
self._pubsub = None
|
|
35
|
-
|
|
36
|
-
async def publish(self, channel: str, message: ChannelMessage) -> None:
|
|
37
|
-
"""发布消息到通道。"""
|
|
38
|
-
message.channel = channel
|
|
39
|
-
# 序列化消息
|
|
40
|
-
data = {
|
|
41
|
-
"data": message.data,
|
|
42
|
-
"event": message.event,
|
|
43
|
-
"id": message.id,
|
|
44
|
-
"channel": message.channel,
|
|
45
|
-
"timestamp": message.timestamp.isoformat(),
|
|
46
|
-
}
|
|
47
|
-
await self._client.connection.publish(channel, json.dumps(data))
|
|
48
|
-
|
|
49
|
-
async def subscribe(self, channel: str) -> AsyncIterator[ChannelMessage]:
|
|
50
|
-
"""订阅通道。"""
|
|
51
|
-
pubsub = self._client.connection.pubsub()
|
|
52
|
-
await pubsub.subscribe(channel)
|
|
53
|
-
|
|
54
|
-
try:
|
|
55
|
-
async for raw_message in pubsub.listen():
|
|
56
|
-
if raw_message["type"] == "message":
|
|
57
|
-
try:
|
|
58
|
-
data = json.loads(raw_message["data"])
|
|
59
|
-
message = ChannelMessage(
|
|
60
|
-
data=data.get("data"),
|
|
61
|
-
event=data.get("event"),
|
|
62
|
-
id=data.get("id"),
|
|
63
|
-
channel=data.get("channel"),
|
|
64
|
-
timestamp=datetime.fromisoformat(data["timestamp"])
|
|
65
|
-
if data.get("timestamp")
|
|
66
|
-
else datetime.now(),
|
|
67
|
-
)
|
|
68
|
-
yield message
|
|
69
|
-
except (json.JSONDecodeError, KeyError) as e:
|
|
70
|
-
logger.warning(f"解析通道消息失败: {e}")
|
|
71
|
-
finally:
|
|
72
|
-
await pubsub.unsubscribe(channel)
|
|
73
|
-
await pubsub.close()
|
|
74
|
-
|
|
75
|
-
async def psubscribe(self, pattern: str) -> AsyncIterator[ChannelMessage]:
|
|
76
|
-
"""模式订阅(通配符)。
|
|
77
|
-
|
|
78
|
-
Args:
|
|
79
|
-
pattern: 通道模式,支持 * 和 ? 通配符
|
|
80
|
-
- * 匹配任意字符
|
|
81
|
-
- ? 匹配单个字符
|
|
82
|
-
- 示例: "space:123:*" 订阅 space:123 下所有事件
|
|
83
|
-
|
|
84
|
-
Yields:
|
|
85
|
-
ChannelMessage: 接收到的消息
|
|
86
|
-
"""
|
|
87
|
-
pubsub = self._client.connection.pubsub()
|
|
88
|
-
await pubsub.psubscribe(pattern)
|
|
89
|
-
|
|
90
|
-
try:
|
|
91
|
-
async for raw_message in pubsub.listen():
|
|
92
|
-
# psubscribe 的消息类型是 "pmessage"
|
|
93
|
-
if raw_message["type"] == "pmessage":
|
|
94
|
-
try:
|
|
95
|
-
data = json.loads(raw_message["data"])
|
|
96
|
-
# pmessage 包含实际匹配的通道名
|
|
97
|
-
actual_channel = raw_message.get("channel")
|
|
98
|
-
if isinstance(actual_channel, bytes):
|
|
99
|
-
actual_channel = actual_channel.decode("utf-8")
|
|
100
|
-
|
|
101
|
-
message = ChannelMessage(
|
|
102
|
-
data=data.get("data"),
|
|
103
|
-
event=data.get("event"),
|
|
104
|
-
id=data.get("id"),
|
|
105
|
-
channel=actual_channel or data.get("channel"),
|
|
106
|
-
timestamp=datetime.fromisoformat(data["timestamp"])
|
|
107
|
-
if data.get("timestamp")
|
|
108
|
-
else datetime.now(),
|
|
109
|
-
)
|
|
110
|
-
yield message
|
|
111
|
-
except (json.JSONDecodeError, KeyError) as e:
|
|
112
|
-
logger.warning(f"解析通道消息失败: {e}")
|
|
113
|
-
finally:
|
|
114
|
-
await pubsub.punsubscribe(pattern)
|
|
115
|
-
await pubsub.close()
|
|
116
|
-
|
|
117
|
-
async def unsubscribe(self, channel: str) -> None:
|
|
118
|
-
"""取消订阅通道。"""
|
|
119
|
-
if self._pubsub:
|
|
120
|
-
await self._pubsub.unsubscribe(channel)
|
|
121
|
-
|
|
122
|
-
async def close(self) -> None:
|
|
123
|
-
"""关闭通道。"""
|
|
124
|
-
if self._pubsub:
|
|
125
|
-
await self._pubsub.close()
|
|
126
|
-
self._pubsub = None
|
|
127
|
-
logger.debug("Redis 通道已关闭")
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
__all__ = ["RedisChannel"]
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
"""内存事件总线后端。
|
|
2
|
-
|
|
3
|
-
适用于单进程场景,如开发环境或简单应用。
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
from __future__ import annotations
|
|
7
|
-
|
|
8
|
-
import asyncio
|
|
9
|
-
from collections.abc import Callable
|
|
10
|
-
from typing import Any
|
|
11
|
-
|
|
12
|
-
from aury.boot.common.logging import logger
|
|
13
|
-
|
|
14
|
-
from ..base import Event, EventHandler, IEventBus
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class MemoryEventBus(IEventBus):
|
|
18
|
-
"""内存事件总线实现。
|
|
19
|
-
|
|
20
|
-
使用内存中的字典存储订阅关系,支持同步和异步处理器。
|
|
21
|
-
|
|
22
|
-
注意:仅适用于单进程,不支持跨进程事件传递。
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
def __init__(self) -> None:
|
|
26
|
-
"""初始化内存事件总线。"""
|
|
27
|
-
# event_name -> list of handlers
|
|
28
|
-
self._handlers: dict[str, list[EventHandler]] = {}
|
|
29
|
-
|
|
30
|
-
def _get_event_name(self, event_type: type[Event] | str) -> str:
|
|
31
|
-
"""获取事件名称。"""
|
|
32
|
-
if isinstance(event_type, str):
|
|
33
|
-
return event_type
|
|
34
|
-
return event_type.__name__
|
|
35
|
-
|
|
36
|
-
def subscribe(
|
|
37
|
-
self,
|
|
38
|
-
event_type: type[Event] | str,
|
|
39
|
-
handler: EventHandler,
|
|
40
|
-
) -> None:
|
|
41
|
-
"""订阅事件。"""
|
|
42
|
-
event_name = self._get_event_name(event_type)
|
|
43
|
-
if event_name not in self._handlers:
|
|
44
|
-
self._handlers[event_name] = []
|
|
45
|
-
if handler not in self._handlers[event_name]:
|
|
46
|
-
self._handlers[event_name].append(handler)
|
|
47
|
-
logger.debug(f"订阅事件: {event_name} -> {handler.__name__}")
|
|
48
|
-
|
|
49
|
-
def unsubscribe(
|
|
50
|
-
self,
|
|
51
|
-
event_type: type[Event] | str,
|
|
52
|
-
handler: EventHandler,
|
|
53
|
-
) -> None:
|
|
54
|
-
"""取消订阅事件。"""
|
|
55
|
-
event_name = self._get_event_name(event_type)
|
|
56
|
-
if event_name in self._handlers:
|
|
57
|
-
try:
|
|
58
|
-
self._handlers[event_name].remove(handler)
|
|
59
|
-
logger.debug(f"取消订阅事件: {event_name} -> {handler.__name__}")
|
|
60
|
-
except ValueError:
|
|
61
|
-
pass
|
|
62
|
-
|
|
63
|
-
async def publish(self, event: Event) -> None:
|
|
64
|
-
"""发布事件。"""
|
|
65
|
-
event_name = event.event_name
|
|
66
|
-
handlers = self._handlers.get(event_name, [])
|
|
67
|
-
|
|
68
|
-
if not handlers:
|
|
69
|
-
logger.debug(f"事件 {event_name} 没有订阅者")
|
|
70
|
-
return
|
|
71
|
-
|
|
72
|
-
for handler in handlers:
|
|
73
|
-
try:
|
|
74
|
-
result = handler(event)
|
|
75
|
-
if asyncio.iscoroutine(result):
|
|
76
|
-
await result
|
|
77
|
-
except Exception as e:
|
|
78
|
-
logger.error(f"处理事件 {event_name} 失败: {e}")
|
|
79
|
-
|
|
80
|
-
async def close(self) -> None:
|
|
81
|
-
"""关闭事件总线。"""
|
|
82
|
-
self._handlers.clear()
|
|
83
|
-
logger.debug("内存事件总线已关闭")
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
__all__ = ["MemoryEventBus"]
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
"""Redis 事件总线后端。
|
|
2
|
-
|
|
3
|
-
适用于多进程/多实例场景,支持跨进程事件传递。
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
from __future__ import annotations
|
|
7
|
-
|
|
8
|
-
import asyncio
|
|
9
|
-
import json
|
|
10
|
-
from typing import TYPE_CHECKING
|
|
11
|
-
|
|
12
|
-
from aury.boot.common.logging import logger
|
|
13
|
-
|
|
14
|
-
from ..base import Event, EventHandler, IEventBus
|
|
15
|
-
|
|
16
|
-
if TYPE_CHECKING:
|
|
17
|
-
from aury.boot.infrastructure.clients.redis import RedisClient
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
# 框架默认前缀
|
|
21
|
-
DEFAULT_CHANNEL_PREFIX = "aury:event:"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class RedisEventBus(IEventBus):
|
|
25
|
-
"""Redis 事件总线实现。
|
|
26
|
-
|
|
27
|
-
使用 Redis Pub/Sub 实现跨进程的事件发布/订阅。
|
|
28
|
-
|
|
29
|
-
频道命名格式:{channel_prefix}{event_name}
|
|
30
|
-
默认:aury:event:user.created
|
|
31
|
-
"""
|
|
32
|
-
|
|
33
|
-
def __init__(
|
|
34
|
-
self,
|
|
35
|
-
url: str | None = None,
|
|
36
|
-
*,
|
|
37
|
-
redis_client: RedisClient | None = None,
|
|
38
|
-
channel_prefix: str = DEFAULT_CHANNEL_PREFIX,
|
|
39
|
-
) -> None:
|
|
40
|
-
"""初始化 Redis 事件总线。
|
|
41
|
-
|
|
42
|
-
Args:
|
|
43
|
-
url: Redis 连接 URL(当 redis_client 为 None 时必须提供)
|
|
44
|
-
redis_client: RedisClient 实例(可选,优先使用)
|
|
45
|
-
channel_prefix: 频道名称前缀,默认 "aury:event:"
|
|
46
|
-
|
|
47
|
-
Raises:
|
|
48
|
-
ValueError: 当 url 和 redis_client 都为 None 时
|
|
49
|
-
"""
|
|
50
|
-
if redis_client is None and url is None:
|
|
51
|
-
raise ValueError("Redis 事件总线需要提供 url 或 redis_client 参数")
|
|
52
|
-
|
|
53
|
-
self._url = url
|
|
54
|
-
self._client = redis_client
|
|
55
|
-
self._channel_prefix = channel_prefix
|
|
56
|
-
# event_name -> list of handlers (本地订阅)
|
|
57
|
-
self._handlers: dict[str, list[EventHandler]] = {}
|
|
58
|
-
self._pubsub = None
|
|
59
|
-
self._listener_task: asyncio.Task | None = None
|
|
60
|
-
self._running = False
|
|
61
|
-
self._owns_client = False # 是否自己创建的客户端
|
|
62
|
-
|
|
63
|
-
async def _ensure_client(self) -> None:
|
|
64
|
-
"""确保 Redis 客户端已初始化。"""
|
|
65
|
-
if self._client is None and self._url:
|
|
66
|
-
from aury.boot.infrastructure.clients.redis import RedisClient
|
|
67
|
-
self._client = RedisClient()
|
|
68
|
-
await self._client.initialize(url=self._url)
|
|
69
|
-
self._owns_client = True
|
|
70
|
-
|
|
71
|
-
def _get_event_name(self, event_type: type[Event] | str) -> str:
|
|
72
|
-
"""获取事件名称。"""
|
|
73
|
-
if isinstance(event_type, str):
|
|
74
|
-
return event_type
|
|
75
|
-
return event_type.__name__
|
|
76
|
-
|
|
77
|
-
def _get_channel(self, event_name: str) -> str:
|
|
78
|
-
"""获取 Redis 频道名称。"""
|
|
79
|
-
return f"{self._channel_prefix}{event_name}"
|
|
80
|
-
|
|
81
|
-
def subscribe(
|
|
82
|
-
self,
|
|
83
|
-
event_type: type[Event] | str,
|
|
84
|
-
handler: EventHandler,
|
|
85
|
-
) -> None:
|
|
86
|
-
"""订阅事件。"""
|
|
87
|
-
event_name = self._get_event_name(event_type)
|
|
88
|
-
if event_name not in self._handlers:
|
|
89
|
-
self._handlers[event_name] = []
|
|
90
|
-
if handler not in self._handlers[event_name]:
|
|
91
|
-
self._handlers[event_name].append(handler)
|
|
92
|
-
logger.debug(f"订阅事件: {event_name} -> {handler.__name__}")
|
|
93
|
-
|
|
94
|
-
def unsubscribe(
|
|
95
|
-
self,
|
|
96
|
-
event_type: type[Event] | str,
|
|
97
|
-
handler: EventHandler,
|
|
98
|
-
) -> None:
|
|
99
|
-
"""取消订阅事件。"""
|
|
100
|
-
event_name = self._get_event_name(event_type)
|
|
101
|
-
if event_name in self._handlers:
|
|
102
|
-
try:
|
|
103
|
-
self._handlers[event_name].remove(handler)
|
|
104
|
-
logger.debug(f"取消订阅事件: {event_name} -> {handler.__name__}")
|
|
105
|
-
except ValueError:
|
|
106
|
-
pass
|
|
107
|
-
|
|
108
|
-
async def publish(self, event: Event) -> None:
|
|
109
|
-
"""发布事件。"""
|
|
110
|
-
await self._ensure_client()
|
|
111
|
-
event_name = event.event_name
|
|
112
|
-
channel = self._get_channel(event_name)
|
|
113
|
-
data = json.dumps(event.to_dict())
|
|
114
|
-
await self._client.connection.publish(channel, data)
|
|
115
|
-
|
|
116
|
-
async def start_listening(self) -> None:
|
|
117
|
-
"""开始监听事件(需要在后台任务中运行)。"""
|
|
118
|
-
if self._running:
|
|
119
|
-
return
|
|
120
|
-
|
|
121
|
-
await self._ensure_client()
|
|
122
|
-
self._pubsub = self._client.connection.pubsub()
|
|
123
|
-
self._running = True
|
|
124
|
-
|
|
125
|
-
# 订阅所有已注册事件的频道
|
|
126
|
-
channels = [self._get_channel(name) for name in self._handlers]
|
|
127
|
-
if channels:
|
|
128
|
-
await self._pubsub.subscribe(*channels)
|
|
129
|
-
|
|
130
|
-
# 监听消息
|
|
131
|
-
async for message in self._pubsub.listen():
|
|
132
|
-
if not self._running:
|
|
133
|
-
break
|
|
134
|
-
|
|
135
|
-
if message["type"] == "message":
|
|
136
|
-
try:
|
|
137
|
-
data = json.loads(message["data"])
|
|
138
|
-
event_name = data.get("event_name")
|
|
139
|
-
handlers = self._handlers.get(event_name, [])
|
|
140
|
-
|
|
141
|
-
for handler in handlers:
|
|
142
|
-
try:
|
|
143
|
-
# 创建事件对象
|
|
144
|
-
event = Event.from_dict(data)
|
|
145
|
-
result = handler(event)
|
|
146
|
-
if asyncio.iscoroutine(result):
|
|
147
|
-
await result
|
|
148
|
-
except Exception as e:
|
|
149
|
-
logger.error(f"处理事件 {event_name} 失败: {e}")
|
|
150
|
-
except (json.JSONDecodeError, KeyError) as e:
|
|
151
|
-
logger.warning(f"解析事件消息失败: {e}")
|
|
152
|
-
|
|
153
|
-
async def close(self) -> None:
|
|
154
|
-
"""关闭事件总线。"""
|
|
155
|
-
self._running = False
|
|
156
|
-
if self._pubsub:
|
|
157
|
-
await self._pubsub.close()
|
|
158
|
-
self._pubsub = None
|
|
159
|
-
if self._listener_task:
|
|
160
|
-
self._listener_task.cancel()
|
|
161
|
-
self._listener_task = None
|
|
162
|
-
if self._owns_client and self._client:
|
|
163
|
-
await self._client.close()
|
|
164
|
-
self._client = None
|
|
165
|
-
self._handlers.clear()
|
|
166
|
-
logger.debug("Redis 事件总线已关闭")
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
__all__ = ["RedisEventBus"]
|
|
File without changes
|
|
File without changes
|