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
aury/boot/_version.py
CHANGED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.0.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 0,
|
|
31
|
+
__version__ = version = '0.0.32'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 0, 32)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
@@ -24,6 +24,7 @@ from aury.boot.domain.transaction import (
|
|
|
24
24
|
# 依赖注入容器(从 infrastructure 导入)
|
|
25
25
|
from aury.boot.infrastructure.di import Container, Lifetime, Scope, ServiceDescriptor
|
|
26
26
|
from aury.boot.infrastructure.events import (
|
|
27
|
+
BroadcasterEventBus,
|
|
27
28
|
Event,
|
|
28
29
|
EventBackend,
|
|
29
30
|
EventBusManager,
|
|
@@ -31,9 +32,7 @@ from aury.boot.infrastructure.events import (
|
|
|
31
32
|
EventType,
|
|
32
33
|
IEventBus,
|
|
33
34
|
# 后端实现
|
|
34
|
-
MemoryEventBus,
|
|
35
35
|
RabbitMQEventBus,
|
|
36
|
-
RedisEventBus,
|
|
37
36
|
)
|
|
38
37
|
|
|
39
38
|
from . import interfaces, rpc
|
|
@@ -108,14 +107,13 @@ __all__ = [
|
|
|
108
107
|
"IEventBus",
|
|
109
108
|
"Lifetime",
|
|
110
109
|
"LogSettings",
|
|
111
|
-
"
|
|
110
|
+
"BroadcasterEventBus",
|
|
112
111
|
"Middleware",
|
|
113
112
|
"MiddlewareName",
|
|
114
113
|
"MigrationComponent",
|
|
115
114
|
# 迁移
|
|
116
115
|
"MigrationManager",
|
|
117
116
|
"RabbitMQEventBus",
|
|
118
|
-
"RedisEventBus",
|
|
119
117
|
"RequestLoggingMiddleware",
|
|
120
118
|
"SchedulerComponent",
|
|
121
119
|
"SchedulerMode",
|
|
@@ -628,6 +628,7 @@ class TelemetryPlugin(Plugin):
|
|
|
628
628
|
alert_on_slow_sql=config.alert.alert_on_slow_sql,
|
|
629
629
|
alert_on_error=config.alert.alert_on_error,
|
|
630
630
|
alert_callback=alert_callback,
|
|
631
|
+
slow_request_exclude_paths=config.alert.slow_request_exclude_paths,
|
|
631
632
|
traces_endpoint=config.telemetry.traces_endpoint,
|
|
632
633
|
traces_headers=config.telemetry.traces_headers,
|
|
633
634
|
logs_endpoint=config.telemetry.logs_endpoint,
|
|
@@ -753,6 +754,7 @@ class AlertComponent(Component):
|
|
|
753
754
|
"slow_sql_aggregate": config.alert.slow_sql_aggregate,
|
|
754
755
|
"exception_aggregate": config.alert.exception_aggregate,
|
|
755
756
|
"suppress_seconds": config.alert.suppress_seconds,
|
|
757
|
+
"slow_request_exclude_paths": config.alert.slow_request_exclude_paths,
|
|
756
758
|
},
|
|
757
759
|
notifiers=config.alert.get_notifiers(),
|
|
758
760
|
)
|
|
@@ -696,6 +696,12 @@ class AlertSettings(BaseModel):
|
|
|
696
696
|
description="是否对异常发送告警(默认只对 5xx 告警,4xx 业务异常不告警)"
|
|
697
697
|
)
|
|
698
698
|
|
|
699
|
+
# 慢请求路径排除配置
|
|
700
|
+
slow_request_exclude_paths: list[str] = Field(
|
|
701
|
+
default_factory=list,
|
|
702
|
+
description="排除慢请求告警的路径列表(支持 * 通配符),如 SSE/WebSocket 长连接接口"
|
|
703
|
+
)
|
|
704
|
+
|
|
699
705
|
# 默认累计触发配置
|
|
700
706
|
aggregate_window: int = Field(
|
|
701
707
|
default=10,
|
|
@@ -127,6 +127,60 @@ mypy {package_name}/
|
|
|
127
127
|
- **[aury_docs/99-cli.md](./aury_docs/99-cli.md)** - CLI 命令参考
|
|
128
128
|
- **[.env.example](./.env.example)** - 所有可用环境变量
|
|
129
129
|
|
|
130
|
+
## 配置结构
|
|
131
|
+
|
|
132
|
+
框架使用 `BaseConfig` 统一管理配置,环境变量通过 `__` 分隔符映射到嵌套配置:
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
# 配置结构(BaseConfig)
|
|
136
|
+
class BaseConfig(BaseSettings):
|
|
137
|
+
# 基础服务
|
|
138
|
+
server: ServerSettings # SERVER__*
|
|
139
|
+
cors: CORSSettings # CORS__*
|
|
140
|
+
log: LogSettings # LOG__*
|
|
141
|
+
health_check: HealthCheckSettings # HEALTH_CHECK__*
|
|
142
|
+
admin: AdminConsoleSettings # ADMIN__*
|
|
143
|
+
|
|
144
|
+
# 数据与缓存
|
|
145
|
+
database: DatabaseSettings # DATABASE__*
|
|
146
|
+
cache: CacheSettings # CACHE__*
|
|
147
|
+
channel: ChannelSettings # CHANNEL__*
|
|
148
|
+
storage: StorageSettings # STORAGE__*
|
|
149
|
+
migration: MigrationSettings # MIGRATION__*
|
|
150
|
+
|
|
151
|
+
# 服务编排
|
|
152
|
+
service: ServiceSettings # SERVICE__*
|
|
153
|
+
scheduler: SchedulerSettings # SCHEDULER__*
|
|
154
|
+
|
|
155
|
+
# 异步与事件
|
|
156
|
+
task: TaskSettings # TASK__*
|
|
157
|
+
event: EventSettings # EVENT__*
|
|
158
|
+
|
|
159
|
+
# 微服务通信
|
|
160
|
+
rpc_client: RPCClientSettings # RPC_CLIENT__*
|
|
161
|
+
rpc_service: RPCServiceSettings # RPC_SERVICE__*
|
|
162
|
+
|
|
163
|
+
# 监控告警
|
|
164
|
+
telemetry: TelemetrySettings # TELEMETRY__*
|
|
165
|
+
alert: AlertSettings # ALERT__*
|
|
166
|
+
|
|
167
|
+
model_config = SettingsConfigDict(
|
|
168
|
+
env_nested_delimiter="__", # 环境变量分隔符
|
|
169
|
+
)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**环境变量命名规则**:`{SECTION}__{FIELD}`
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
# 示例
|
|
176
|
+
DATABASE__URL=postgresql://...
|
|
177
|
+
DATABASE__POOL_SIZE=10
|
|
178
|
+
CACHE__CACHE_TYPE=redis
|
|
179
|
+
CACHE__URL=redis://localhost:6379
|
|
180
|
+
ALERT__ENABLED=true
|
|
181
|
+
ALERT__SLOW_REQUEST_THRESHOLD=1.0
|
|
182
|
+
```
|
|
183
|
+
|
|
130
184
|
## 代码规范
|
|
131
185
|
|
|
132
186
|
> 项目所有业务配置请通过应用 `settings`/配置对象获取,**不要**直接使用 `os.environ` 在业务代码中读环境变量。
|
|
@@ -2,14 +2,18 @@
|
|
|
2
2
|
# =============================================================================
|
|
3
3
|
# 流式通道配置 (CHANNEL__) - SSE/实时通信
|
|
4
4
|
# =============================================================================
|
|
5
|
+
# 基于 Broadcaster 库,通过 URL scheme 切换后端
|
|
6
|
+
# 支持: memory:// | redis://host:port/db | kafka://host:port | postgres://...
|
|
7
|
+
#
|
|
5
8
|
# 单实例配置:
|
|
6
|
-
# CHANNEL__BACKEND=
|
|
7
|
-
#
|
|
8
|
-
# CHANNEL__URL=redis://localhost:6379/3
|
|
9
|
+
# CHANNEL__BACKEND=broadcaster
|
|
10
|
+
# CHANNEL__URL=memory://
|
|
11
|
+
# 分布式: CHANNEL__URL=redis://localhost:6379/3
|
|
9
12
|
#
|
|
10
13
|
# 多实例配置 (格式: CHANNEL__{{INSTANCE}}__{{FIELD}}):
|
|
11
|
-
# CHANNEL__SSE__BACKEND=
|
|
12
|
-
#
|
|
14
|
+
# CHANNEL__SSE__BACKEND=broadcaster
|
|
15
|
+
# CHANNEL__SSE__URL=memory://
|
|
16
|
+
# CHANNEL__NOTIFICATION__BACKEND=broadcaster
|
|
13
17
|
# CHANNEL__NOTIFICATION__URL=redis://localhost:6379/3
|
|
14
18
|
|
|
15
19
|
# =============================================================================
|
|
@@ -32,18 +36,22 @@
|
|
|
32
36
|
# =============================================================================
|
|
33
37
|
# 事件总线配置 (EVENT__)
|
|
34
38
|
# =============================================================================
|
|
39
|
+
# 基于 Broadcaster 库,通过 URL scheme 切换后端
|
|
40
|
+
# 支持: memory:// | redis://host:port/db | kafka://host:port | postgres://...
|
|
41
|
+
# RabbitMQ 需使用 backend=rabbitmq
|
|
42
|
+
#
|
|
35
43
|
# 单实例配置:
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
|
|
44
|
+
# EVENT__BACKEND=broadcaster
|
|
45
|
+
# EVENT__URL=memory://
|
|
46
|
+
# 分布式: EVENT__URL=redis://localhost:6379/5
|
|
47
|
+
#
|
|
39
48
|
# 多实例配置 (格式: EVENT__{{INSTANCE}}__{{FIELD}}):
|
|
40
|
-
# EVENT__DEFAULT__BACKEND=
|
|
41
|
-
#
|
|
49
|
+
# EVENT__DEFAULT__BACKEND=broadcaster
|
|
50
|
+
# EVENT__DEFAULT__URL=memory://
|
|
51
|
+
# EVENT__DISTRIBUTED__BACKEND=broadcaster
|
|
42
52
|
# EVENT__DISTRIBUTED__URL=redis://localhost:6379/5
|
|
43
|
-
# EVENT__DISTRIBUTED__KEY_PREFIX=events:
|
|
44
53
|
#
|
|
45
|
-
# RabbitMQ
|
|
54
|
+
# RabbitMQ 后端 (复杂消息场景):
|
|
46
55
|
# EVENT__DOMAIN__BACKEND=rabbitmq
|
|
47
56
|
# EVENT__DOMAIN__URL=amqp://guest:guest@localhost:5672/
|
|
48
57
|
# EVENT__DOMAIN__EXCHANGE_NAME=domain.events
|
|
49
|
-
# EVENT__DOMAIN__EXCHANGE_TYPE=topic
|
|
@@ -21,6 +21,8 @@ ALERT__SLOW_SQL_THRESHOLD=0.5
|
|
|
21
21
|
ALERT__ALERT_ON_SLOW_REQUEST=true
|
|
22
22
|
ALERT__ALERT_ON_SLOW_SQL=true
|
|
23
23
|
ALERT__ALERT_ON_ERROR=true
|
|
24
|
+
# 慢请求排除路径(SSE/WebSocket 等长连接接口,支持 * 通配符)
|
|
25
|
+
# ALERT__SLOW_REQUEST_EXCLUDE_PATHS=["*/subscribe", "*/ws", "*/stream"]
|
|
24
26
|
# 聚合与抑制
|
|
25
27
|
ALERT__AGGREGATE_WINDOW=10
|
|
26
28
|
ALERT__SLOW_REQUEST_AGGREGATE=5
|
|
@@ -27,12 +27,11 @@ from .cache import (
|
|
|
27
27
|
|
|
28
28
|
# 通道 (SSE/PubSub)
|
|
29
29
|
from .channel import (
|
|
30
|
+
BroadcasterChannel,
|
|
30
31
|
ChannelBackend,
|
|
31
32
|
ChannelManager,
|
|
32
33
|
ChannelMessage,
|
|
33
34
|
IChannel,
|
|
34
|
-
MemoryChannel,
|
|
35
|
-
RedisChannel,
|
|
36
35
|
)
|
|
37
36
|
|
|
38
37
|
# RabbitMQ 客户端
|
|
@@ -47,15 +46,14 @@ from .di import Container, Lifetime, Scope, ServiceDescriptor
|
|
|
47
46
|
|
|
48
47
|
# 事件总线
|
|
49
48
|
from .events import (
|
|
49
|
+
BroadcasterEventBus,
|
|
50
50
|
Event,
|
|
51
51
|
EventBackend,
|
|
52
52
|
EventBusManager,
|
|
53
53
|
EventHandler,
|
|
54
54
|
EventType,
|
|
55
55
|
IEventBus,
|
|
56
|
-
MemoryEventBus,
|
|
57
56
|
RabbitMQEventBus,
|
|
58
|
-
RedisEventBus,
|
|
59
57
|
)
|
|
60
58
|
|
|
61
59
|
# 消息队列
|
|
@@ -103,6 +101,7 @@ __all__ = [
|
|
|
103
101
|
"CacheFactory",
|
|
104
102
|
"CacheManager",
|
|
105
103
|
# 通道
|
|
104
|
+
"BroadcasterChannel",
|
|
106
105
|
"ChannelBackend",
|
|
107
106
|
"ChannelManager",
|
|
108
107
|
"ChannelMessage",
|
|
@@ -128,19 +127,16 @@ __all__ = [
|
|
|
128
127
|
"MQMessage",
|
|
129
128
|
"MemcachedCache",
|
|
130
129
|
"MemoryCache",
|
|
131
|
-
"MemoryChannel",
|
|
132
|
-
"MemoryEventBus",
|
|
133
130
|
"RabbitMQ",
|
|
134
131
|
# RabbitMQ 客户端
|
|
135
132
|
"RabbitMQClient",
|
|
136
133
|
"RabbitMQConfig",
|
|
137
134
|
"RabbitMQEventBus",
|
|
135
|
+
"BroadcasterEventBus",
|
|
138
136
|
"RedisCache",
|
|
139
|
-
"RedisChannel",
|
|
140
137
|
# Redis 客户端
|
|
141
138
|
"RedisClient",
|
|
142
139
|
"RedisConfig",
|
|
143
|
-
"RedisEventBus",
|
|
144
140
|
"RedisMQ",
|
|
145
141
|
"S3Storage",
|
|
146
142
|
# 调度器
|
|
@@ -2,23 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
提供发布/订阅模式的通道功能,用于 SSE、WebSocket 等实时通信场景。
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
- memory
|
|
7
|
-
- redis
|
|
5
|
+
支持的后端(通过 Broadcaster 库):
|
|
6
|
+
- memory:// - 内存通道(单进程,开发/测试用)
|
|
7
|
+
- redis:// - Redis Pub/Sub(多进程/分布式)
|
|
8
|
+
- kafka:// - Apache Kafka
|
|
9
|
+
- postgres:// - PostgreSQL LISTEN/NOTIFY
|
|
8
10
|
"""
|
|
9
11
|
|
|
10
|
-
from .backends import
|
|
12
|
+
from .backends import BroadcasterChannel
|
|
11
13
|
from .base import ChannelBackend, ChannelMessage, IChannel
|
|
12
14
|
from .manager import ChannelManager
|
|
13
15
|
|
|
14
16
|
__all__ = [
|
|
15
17
|
# 接口和类型
|
|
16
18
|
"ChannelBackend",
|
|
17
|
-
# 管理器
|
|
18
|
-
"ChannelManager",
|
|
19
19
|
"ChannelMessage",
|
|
20
20
|
"IChannel",
|
|
21
|
+
# 管理器
|
|
22
|
+
"ChannelManager",
|
|
21
23
|
# 后端实现
|
|
22
|
-
"
|
|
23
|
-
"RedisChannel",
|
|
24
|
+
"BroadcasterChannel",
|
|
24
25
|
]
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""Broadcaster 通道后端。
|
|
2
|
+
|
|
3
|
+
基于 Broadcaster 库实现,支持多种后端:
|
|
4
|
+
- memory:// - 内存(单进程,开发/测试用)
|
|
5
|
+
- redis:// - Redis Pub/Sub(多进程/分布式)
|
|
6
|
+
- kafka:// - Apache Kafka
|
|
7
|
+
- postgres:// - PostgreSQL LISTEN/NOTIFY
|
|
8
|
+
|
|
9
|
+
优势:
|
|
10
|
+
- 共享连接池,支持成千上万并发订阅
|
|
11
|
+
- 自动重连机制
|
|
12
|
+
- 统一 API,通过 URL scheme 切换后端
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
from collections.abc import AsyncIterator
|
|
19
|
+
from datetime import datetime
|
|
20
|
+
|
|
21
|
+
from broadcaster import Broadcast
|
|
22
|
+
|
|
23
|
+
from aury.boot.common.logging import logger
|
|
24
|
+
|
|
25
|
+
from ..base import ChannelMessage, IChannel
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class BroadcasterChannel(IChannel):
|
|
29
|
+
"""Broadcaster 通道实现。
|
|
30
|
+
|
|
31
|
+
使用 Broadcaster 库统一处理多种后端,解决原生实现的连接池问题:
|
|
32
|
+
- 共享单个连接处理所有订阅
|
|
33
|
+
- 内部通过 asyncio.Queue 分发消息
|
|
34
|
+
- 支持成千上万并发订阅者
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, url: str) -> None:
|
|
38
|
+
"""初始化 Broadcaster 通道。
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
url: 连接 URL,支持的 scheme:
|
|
42
|
+
- memory:// - 内存后端
|
|
43
|
+
- redis://host:port/db - Redis Pub/Sub
|
|
44
|
+
- kafka://host:port - Apache Kafka
|
|
45
|
+
- postgres://user:pass@host:port/db - PostgreSQL
|
|
46
|
+
"""
|
|
47
|
+
self._url = url
|
|
48
|
+
self._broadcast = Broadcast(url)
|
|
49
|
+
self._connected = False
|
|
50
|
+
|
|
51
|
+
async def _ensure_connected(self) -> None:
|
|
52
|
+
"""确保已连接。"""
|
|
53
|
+
if not self._connected:
|
|
54
|
+
await self._broadcast.connect()
|
|
55
|
+
self._connected = True
|
|
56
|
+
logger.debug(f"Broadcaster 通道已连接: {self._mask_url(self._url)}")
|
|
57
|
+
|
|
58
|
+
def _mask_url(self, url: str) -> str:
|
|
59
|
+
"""URL 脱敏(隐藏密码)。"""
|
|
60
|
+
if "@" in url:
|
|
61
|
+
parts = url.split("@")
|
|
62
|
+
prefix = parts[0]
|
|
63
|
+
suffix = parts[1]
|
|
64
|
+
if ":" in prefix:
|
|
65
|
+
scheme_and_user = prefix.rsplit(":", 1)[0]
|
|
66
|
+
return f"{scheme_and_user}:***@{suffix}"
|
|
67
|
+
return url
|
|
68
|
+
|
|
69
|
+
async def publish(self, channel: str, message: ChannelMessage) -> None:
|
|
70
|
+
"""发布消息到通道。"""
|
|
71
|
+
await self._ensure_connected()
|
|
72
|
+
|
|
73
|
+
message.channel = channel
|
|
74
|
+
# 序列化消息
|
|
75
|
+
data = {
|
|
76
|
+
"data": message.data,
|
|
77
|
+
"event": message.event,
|
|
78
|
+
"id": message.id,
|
|
79
|
+
"channel": message.channel,
|
|
80
|
+
"timestamp": message.timestamp.isoformat(),
|
|
81
|
+
}
|
|
82
|
+
await self._broadcast.publish(channel=channel, message=json.dumps(data))
|
|
83
|
+
|
|
84
|
+
async def subscribe(self, channel: str) -> AsyncIterator[ChannelMessage]:
|
|
85
|
+
"""订阅通道。
|
|
86
|
+
|
|
87
|
+
Broadcaster 内部共享连接,每个订阅者不会创建新的连接。
|
|
88
|
+
"""
|
|
89
|
+
await self._ensure_connected()
|
|
90
|
+
|
|
91
|
+
async with self._broadcast.subscribe(channel=channel) as subscriber:
|
|
92
|
+
async for event in subscriber:
|
|
93
|
+
try:
|
|
94
|
+
data = json.loads(event.message)
|
|
95
|
+
message = ChannelMessage(
|
|
96
|
+
data=data.get("data"),
|
|
97
|
+
event=data.get("event"),
|
|
98
|
+
id=data.get("id"),
|
|
99
|
+
channel=data.get("channel") or channel,
|
|
100
|
+
timestamp=datetime.fromisoformat(data["timestamp"])
|
|
101
|
+
if data.get("timestamp")
|
|
102
|
+
else datetime.now(),
|
|
103
|
+
)
|
|
104
|
+
yield message
|
|
105
|
+
except (json.JSONDecodeError, KeyError, TypeError) as e:
|
|
106
|
+
logger.warning(f"解析通道消息失败: {e}")
|
|
107
|
+
|
|
108
|
+
async def psubscribe(self, pattern: str) -> AsyncIterator[ChannelMessage]:
|
|
109
|
+
"""模式订阅(通配符)。
|
|
110
|
+
|
|
111
|
+
注意:Broadcaster 目前不支持模式订阅,此方法会抛出 NotImplementedError。
|
|
112
|
+
如需模式订阅,请使用具体的 channel 名称。
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
pattern: 通道模式
|
|
116
|
+
|
|
117
|
+
Raises:
|
|
118
|
+
NotImplementedError: Broadcaster 不支持模式订阅
|
|
119
|
+
"""
|
|
120
|
+
raise NotImplementedError(
|
|
121
|
+
"Broadcaster 后端不支持模式订阅 (psubscribe)。"
|
|
122
|
+
"请使用具体的 channel 名称。"
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
async def unsubscribe(self, channel: str) -> None:
|
|
126
|
+
"""取消订阅通道。
|
|
127
|
+
|
|
128
|
+
注意:Broadcaster 的订阅通过上下文管理器自动处理,
|
|
129
|
+
退出 subscribe() 的 async for 循环即可取消订阅。
|
|
130
|
+
"""
|
|
131
|
+
pass
|
|
132
|
+
|
|
133
|
+
async def close(self) -> None:
|
|
134
|
+
"""关闭通道。"""
|
|
135
|
+
if self._connected:
|
|
136
|
+
await self._broadcast.disconnect()
|
|
137
|
+
self._connected = False
|
|
138
|
+
logger.debug("Broadcaster 通道已关闭")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
__all__ = ["BroadcasterChannel"]
|
|
@@ -16,8 +16,11 @@ from typing import Any
|
|
|
16
16
|
class ChannelBackend(Enum):
|
|
17
17
|
"""通道后端类型。"""
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
# Broadcaster 统一后端(支持 memory/redis/kafka/postgres,通过 URL scheme 区分)
|
|
20
|
+
BROADCASTER = "broadcaster"
|
|
21
|
+
# 未来扩展
|
|
22
|
+
RABBITMQ = "rabbitmq"
|
|
23
|
+
ROCKETMQ = "rocketmq"
|
|
21
24
|
|
|
22
25
|
|
|
23
26
|
@dataclass
|
|
@@ -9,8 +9,7 @@ from collections.abc import AsyncIterator
|
|
|
9
9
|
|
|
10
10
|
from aury.boot.common.logging import logger
|
|
11
11
|
|
|
12
|
-
from .backends.
|
|
13
|
-
from .backends.redis import RedisChannel
|
|
12
|
+
from .backends.broadcaster import BroadcasterChannel
|
|
14
13
|
from .base import ChannelBackend, ChannelMessage, IChannel
|
|
15
14
|
|
|
16
15
|
|
|
@@ -51,7 +50,6 @@ class ChannelManager:
|
|
|
51
50
|
self._backend: IChannel | None = None
|
|
52
51
|
self._backend_type: ChannelBackend | None = None
|
|
53
52
|
self._initialized: bool = False
|
|
54
|
-
self._redis_client = None
|
|
55
53
|
self._url: str | None = None
|
|
56
54
|
|
|
57
55
|
@classmethod
|
|
@@ -84,17 +82,19 @@ class ChannelManager:
|
|
|
84
82
|
|
|
85
83
|
async def initialize(
|
|
86
84
|
self,
|
|
87
|
-
backend: ChannelBackend | str = ChannelBackend.
|
|
85
|
+
backend: ChannelBackend | str = ChannelBackend.BROADCASTER,
|
|
88
86
|
*,
|
|
89
|
-
url: str
|
|
90
|
-
max_subscribers: int = 1000,
|
|
87
|
+
url: str = "memory://",
|
|
91
88
|
) -> ChannelManager:
|
|
92
89
|
"""初始化通道(链式调用)。
|
|
93
90
|
|
|
94
91
|
Args:
|
|
95
|
-
backend:
|
|
96
|
-
url:
|
|
97
|
-
|
|
92
|
+
backend: 后端类型,默认 broadcaster
|
|
93
|
+
url: 连接 URL,支持:
|
|
94
|
+
- memory:// - 内存后端(单进程,默认)
|
|
95
|
+
- redis://host:port/db - Redis Pub/Sub
|
|
96
|
+
- kafka://host:port - Apache Kafka
|
|
97
|
+
- postgres://user:pass@host/db - PostgreSQL
|
|
98
98
|
|
|
99
99
|
Returns:
|
|
100
100
|
self: 支持链式调用
|
|
@@ -110,24 +110,28 @@ class ChannelManager:
|
|
|
110
110
|
self._backend_type = backend
|
|
111
111
|
self._url = url
|
|
112
112
|
|
|
113
|
-
if backend == ChannelBackend.
|
|
114
|
-
self._backend =
|
|
115
|
-
elif backend
|
|
116
|
-
|
|
117
|
-
raise ValueError("Redis 通道需要提供 url 参数")
|
|
118
|
-
# 内部创建 RedisClient
|
|
119
|
-
from aury.boot.infrastructure.clients.redis import RedisClient
|
|
120
|
-
|
|
121
|
-
self._redis_client = RedisClient()
|
|
122
|
-
await self._redis_client.configure(url=url).initialize()
|
|
123
|
-
self._backend = RedisChannel(self._redis_client)
|
|
113
|
+
if backend == ChannelBackend.BROADCASTER:
|
|
114
|
+
self._backend = BroadcasterChannel(url)
|
|
115
|
+
elif backend in (ChannelBackend.RABBITMQ, ChannelBackend.ROCKETMQ):
|
|
116
|
+
raise NotImplementedError(f"{backend.value} 后端暂未实现")
|
|
124
117
|
else:
|
|
125
118
|
raise ValueError(f"不支持的通道后端: {backend}")
|
|
126
119
|
|
|
127
120
|
self._initialized = True
|
|
128
|
-
logger.info(f"通道管理器 [{self.name}] 初始化完成: {backend.value}")
|
|
121
|
+
logger.info(f"通道管理器 [{self.name}] 初始化完成: {backend.value}, url={self._mask_url(url)}")
|
|
129
122
|
return self
|
|
130
123
|
|
|
124
|
+
def _mask_url(self, url: str) -> str:
|
|
125
|
+
"""URL 脱敏(隐藏密码)。"""
|
|
126
|
+
if "@" in url:
|
|
127
|
+
parts = url.split("@")
|
|
128
|
+
prefix = parts[0]
|
|
129
|
+
suffix = parts[1]
|
|
130
|
+
if ":" in prefix:
|
|
131
|
+
scheme_and_user = prefix.rsplit(":", 1)[0]
|
|
132
|
+
return f"{scheme_and_user}:***@{suffix}"
|
|
133
|
+
return url
|
|
134
|
+
|
|
131
135
|
@property
|
|
132
136
|
def backend(self) -> IChannel:
|
|
133
137
|
"""获取通道后端。"""
|
|
@@ -218,9 +222,6 @@ class ChannelManager:
|
|
|
218
222
|
if self._backend:
|
|
219
223
|
await self._backend.close()
|
|
220
224
|
self._backend = None
|
|
221
|
-
if self._redis_client:
|
|
222
|
-
await self._redis_client.cleanup()
|
|
223
|
-
self._redis_client = None
|
|
224
225
|
self._initialized = False
|
|
225
226
|
logger.info(f"通道管理器 [{self.name}] 已关闭")
|
|
226
227
|
|
|
@@ -3,12 +3,11 @@
|
|
|
3
3
|
提供发布/订阅模式的事件总线功能,用于模块间解耦通信。
|
|
4
4
|
|
|
5
5
|
支持的后端:
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
- rabbitmq: RabbitMQ Exchange(分布式)
|
|
6
|
+
- broadcaster: 基于 broadcaster 库(推荐,支持 memory/redis/kafka/postgres)
|
|
7
|
+
- rabbitmq: RabbitMQ Exchange(复杂消息场景)
|
|
9
8
|
"""
|
|
10
9
|
|
|
11
|
-
from .backends import
|
|
10
|
+
from .backends import BroadcasterEventBus, RabbitMQEventBus
|
|
12
11
|
from .base import Event, EventBackend, EventHandler, EventType, IEventBus
|
|
13
12
|
from .manager import EventBusManager
|
|
14
13
|
|
|
@@ -22,9 +21,8 @@ __all__ = [
|
|
|
22
21
|
"EventType",
|
|
23
22
|
"IEventBus",
|
|
24
23
|
# 后端实现
|
|
25
|
-
"
|
|
24
|
+
"BroadcasterEventBus",
|
|
26
25
|
"RabbitMQEventBus",
|
|
27
|
-
"RedisEventBus",
|
|
28
26
|
]
|
|
29
27
|
|
|
30
28
|
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
"""事件总线后端实现。"""
|
|
2
2
|
|
|
3
|
-
from .
|
|
3
|
+
from .broadcaster import BroadcasterEventBus
|
|
4
4
|
from .rabbitmq import RabbitMQEventBus
|
|
5
|
-
from .redis import RedisEventBus
|
|
6
5
|
|
|
7
6
|
__all__ = [
|
|
8
|
-
"
|
|
7
|
+
"BroadcasterEventBus",
|
|
9
8
|
"RabbitMQEventBus",
|
|
10
|
-
"RedisEventBus",
|
|
11
9
|
]
|