aury-boot 0.0.4__py3-none-any.whl → 0.0.7__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/__init__.py +2 -2
- aury/boot/_version.py +2 -2
- aury/boot/application/__init__.py +60 -36
- aury/boot/application/adapter/__init__.py +112 -0
- aury/boot/application/adapter/base.py +511 -0
- aury/boot/application/adapter/config.py +242 -0
- aury/boot/application/adapter/decorators.py +259 -0
- aury/boot/application/adapter/exceptions.py +202 -0
- aury/boot/application/adapter/http.py +325 -0
- aury/boot/application/app/__init__.py +12 -8
- aury/boot/application/app/base.py +12 -0
- aury/boot/application/app/components.py +137 -44
- aury/boot/application/app/middlewares.py +9 -4
- aury/boot/application/app/startup.py +249 -0
- aury/boot/application/config/__init__.py +36 -1
- aury/boot/application/config/multi_instance.py +216 -0
- aury/boot/application/config/settings.py +398 -149
- aury/boot/application/constants/components.py +6 -0
- aury/boot/application/errors/handlers.py +17 -3
- aury/boot/application/middleware/logging.py +21 -120
- aury/boot/application/rpc/__init__.py +2 -2
- aury/boot/commands/__init__.py +30 -10
- aury/boot/commands/app.py +131 -1
- aury/boot/commands/docs.py +104 -17
- aury/boot/commands/generate.py +22 -22
- aury/boot/commands/init.py +68 -17
- aury/boot/commands/server/app.py +2 -3
- aury/boot/commands/templates/project/AGENTS.md.tpl +221 -0
- aury/boot/commands/templates/project/README.md.tpl +2 -2
- aury/boot/commands/templates/project/aury_docs/00-overview.md.tpl +59 -0
- aury/boot/commands/templates/project/aury_docs/01-model.md.tpl +184 -0
- aury/boot/commands/templates/project/aury_docs/02-repository.md.tpl +206 -0
- aury/boot/commands/templates/project/aury_docs/03-service.md.tpl +398 -0
- aury/boot/commands/templates/project/aury_docs/04-schema.md.tpl +95 -0
- aury/boot/commands/templates/project/aury_docs/05-api.md.tpl +116 -0
- aury/boot/commands/templates/project/aury_docs/06-exception.md.tpl +118 -0
- aury/boot/commands/templates/project/aury_docs/07-cache.md.tpl +122 -0
- aury/boot/commands/templates/project/aury_docs/08-scheduler.md.tpl +32 -0
- aury/boot/commands/templates/project/aury_docs/09-tasks.md.tpl +38 -0
- aury/boot/commands/templates/project/aury_docs/10-storage.md.tpl +115 -0
- aury/boot/commands/templates/project/aury_docs/11-logging.md.tpl +131 -0
- aury/boot/commands/templates/project/aury_docs/12-admin.md.tpl +56 -0
- aury/boot/commands/templates/project/aury_docs/13-channel.md.tpl +104 -0
- aury/boot/commands/templates/project/aury_docs/14-mq.md.tpl +102 -0
- aury/boot/commands/templates/project/aury_docs/15-events.md.tpl +147 -0
- aury/boot/commands/templates/project/aury_docs/16-adapter.md.tpl +403 -0
- aury/boot/commands/templates/project/{CLI.md.tpl → aury_docs/99-cli.md.tpl} +19 -19
- aury/boot/commands/templates/project/config.py.tpl +10 -10
- aury/boot/commands/templates/project/env_templates/_header.tpl +10 -0
- aury/boot/commands/templates/project/env_templates/admin.tpl +49 -0
- aury/boot/commands/templates/project/env_templates/cache.tpl +14 -0
- aury/boot/commands/templates/project/env_templates/database.tpl +22 -0
- aury/boot/commands/templates/project/env_templates/log.tpl +18 -0
- aury/boot/commands/templates/project/env_templates/messaging.tpl +46 -0
- aury/boot/commands/templates/project/env_templates/rpc.tpl +28 -0
- aury/boot/commands/templates/project/env_templates/scheduler.tpl +18 -0
- aury/boot/commands/templates/project/env_templates/service.tpl +18 -0
- aury/boot/commands/templates/project/env_templates/storage.tpl +38 -0
- aury/boot/commands/templates/project/env_templates/third_party.tpl +43 -0
- aury/boot/commands/templates/project/modules/tasks.py.tpl +1 -1
- aury/boot/common/logging/__init__.py +26 -674
- aury/boot/common/logging/context.py +132 -0
- aury/boot/common/logging/decorators.py +118 -0
- aury/boot/common/logging/format.py +315 -0
- aury/boot/common/logging/setup.py +214 -0
- aury/boot/contrib/admin_console/auth.py +2 -3
- aury/boot/contrib/admin_console/install.py +1 -1
- aury/boot/domain/models/mixins.py +48 -1
- aury/boot/domain/pagination/__init__.py +94 -0
- aury/boot/domain/repository/impl.py +1 -1
- aury/boot/domain/repository/interface.py +1 -1
- aury/boot/domain/transaction/__init__.py +8 -9
- aury/boot/infrastructure/__init__.py +86 -29
- aury/boot/infrastructure/cache/backends.py +102 -18
- aury/boot/infrastructure/cache/base.py +12 -0
- aury/boot/infrastructure/cache/manager.py +153 -91
- aury/boot/infrastructure/channel/__init__.py +24 -0
- aury/boot/infrastructure/channel/backends/__init__.py +9 -0
- aury/boot/infrastructure/channel/backends/memory.py +83 -0
- aury/boot/infrastructure/channel/backends/redis.py +88 -0
- aury/boot/infrastructure/channel/base.py +92 -0
- aury/boot/infrastructure/channel/manager.py +203 -0
- aury/boot/infrastructure/clients/__init__.py +22 -0
- aury/boot/infrastructure/clients/rabbitmq/__init__.py +9 -0
- aury/boot/infrastructure/clients/rabbitmq/config.py +46 -0
- aury/boot/infrastructure/clients/rabbitmq/manager.py +288 -0
- aury/boot/infrastructure/clients/redis/__init__.py +28 -0
- aury/boot/infrastructure/clients/redis/config.py +51 -0
- aury/boot/infrastructure/clients/redis/manager.py +264 -0
- aury/boot/infrastructure/database/config.py +7 -16
- aury/boot/infrastructure/database/manager.py +16 -38
- aury/boot/infrastructure/events/__init__.py +18 -21
- aury/boot/infrastructure/events/backends/__init__.py +11 -0
- aury/boot/infrastructure/events/backends/memory.py +86 -0
- aury/boot/infrastructure/events/backends/rabbitmq.py +193 -0
- aury/boot/infrastructure/events/backends/redis.py +162 -0
- aury/boot/infrastructure/events/base.py +127 -0
- aury/boot/infrastructure/events/manager.py +224 -0
- aury/boot/infrastructure/mq/__init__.py +24 -0
- aury/boot/infrastructure/mq/backends/__init__.py +9 -0
- aury/boot/infrastructure/mq/backends/rabbitmq.py +179 -0
- aury/boot/infrastructure/mq/backends/redis.py +167 -0
- aury/boot/infrastructure/mq/base.py +143 -0
- aury/boot/infrastructure/mq/manager.py +239 -0
- aury/boot/infrastructure/scheduler/manager.py +7 -3
- aury/boot/infrastructure/storage/__init__.py +9 -9
- aury/boot/infrastructure/storage/base.py +17 -5
- aury/boot/infrastructure/storage/factory.py +0 -1
- aury/boot/infrastructure/tasks/__init__.py +2 -2
- aury/boot/infrastructure/tasks/config.py +5 -13
- aury/boot/infrastructure/tasks/manager.py +55 -33
- {aury_boot-0.0.4.dist-info → aury_boot-0.0.7.dist-info}/METADATA +20 -2
- aury_boot-0.0.7.dist-info/RECORD +197 -0
- aury/boot/commands/templates/project/DEVELOPMENT.md.tpl +0 -1397
- aury/boot/commands/templates/project/env.example.tpl +0 -213
- aury/boot/infrastructure/events/bus.py +0 -362
- aury/boot/infrastructure/events/config.py +0 -52
- aury/boot/infrastructure/events/consumer.py +0 -134
- aury/boot/infrastructure/events/models.py +0 -63
- aury_boot-0.0.4.dist-info/RECORD +0 -137
- {aury_boot-0.0.4.dist-info → aury_boot-0.0.7.dist-info}/WHEEL +0 -0
- {aury_boot-0.0.4.dist-info → aury_boot-0.0.7.dist-info}/entry_points.txt +0 -0
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
# =============================================================================
|
|
2
|
-
# {project_name} 环境变量配置
|
|
3
|
-
# =============================================================================
|
|
4
|
-
# 复制此文件为 .env 并根据实际情况修改
|
|
5
|
-
# 所有配置项均有默认值,只需取消注释并修改需要覆盖的项
|
|
6
|
-
|
|
7
|
-
# =============================================================================
|
|
8
|
-
# 服务配置 (SERVICE_)
|
|
9
|
-
# =============================================================================
|
|
10
|
-
# 服务名称,用于日志目录区分
|
|
11
|
-
SERVICE_NAME={project_name_snake}
|
|
12
|
-
# 服务类型: api / worker
|
|
13
|
-
# SERVICE_TYPE=api
|
|
14
|
-
|
|
15
|
-
# =============================================================================
|
|
16
|
-
# 服务器配置 (SERVER_)
|
|
17
|
-
# =============================================================================
|
|
18
|
-
# SERVER_HOST=127.0.0.1
|
|
19
|
-
# SERVER_PORT=8000
|
|
20
|
-
# 工作进程数(生产环境建议设为 CPU 核心数)
|
|
21
|
-
# SERVER_WORKERS=1
|
|
22
|
-
# 是否启用热重载(生产环境应设为 false)
|
|
23
|
-
# SERVER_RELOAD=true
|
|
24
|
-
|
|
25
|
-
# =============================================================================
|
|
26
|
-
# 数据库配置 (DATABASE_)
|
|
27
|
-
# =============================================================================
|
|
28
|
-
# 数据库连接字符串(默认 SQLite)
|
|
29
|
-
# DATABASE_URL=sqlite+aiosqlite:///./dev.db
|
|
30
|
-
# PostgreSQL: postgresql+asyncpg://user:pass@localhost:5432/{project_name_snake}
|
|
31
|
-
# MySQL: mysql+aiomysql://user:pass@localhost:3306/{project_name_snake}
|
|
32
|
-
|
|
33
|
-
# 连接池大小
|
|
34
|
-
# DATABASE_POOL_SIZE=5
|
|
35
|
-
# 连接池最大溢出连接数
|
|
36
|
-
# DATABASE_MAX_OVERFLOW=10
|
|
37
|
-
# 连接回收时间(秒)
|
|
38
|
-
# DATABASE_POOL_RECYCLE=3600
|
|
39
|
-
# 获取连接超时时间(秒)
|
|
40
|
-
# DATABASE_POOL_TIMEOUT=30
|
|
41
|
-
# 是否在获取连接前 PING
|
|
42
|
-
# DATABASE_POOL_PRE_PING=true
|
|
43
|
-
# 是否输出 SQL 语句(调试用)
|
|
44
|
-
# DATABASE_ECHO=false
|
|
45
|
-
|
|
46
|
-
# =============================================================================
|
|
47
|
-
# 缓存配置 (CACHE_)
|
|
48
|
-
# =============================================================================
|
|
49
|
-
# 缓存类型: memory / redis / memcached
|
|
50
|
-
# CACHE_TYPE=memory
|
|
51
|
-
# Redis/Memcached URL
|
|
52
|
-
# CACHE_URL=redis://localhost:6379/0
|
|
53
|
-
# 内存缓存最大大小
|
|
54
|
-
# CACHE_MAX_SIZE=1000
|
|
55
|
-
|
|
56
|
-
# =============================================================================
|
|
57
|
-
# 日志配置 (LOG_)
|
|
58
|
-
# =============================================================================
|
|
59
|
-
# 日志级别: DEBUG / INFO / WARNING / ERROR / CRITICAL
|
|
60
|
-
# LOG_LEVEL=INFO
|
|
61
|
-
# 日志文件目录
|
|
62
|
-
# LOG_DIR=logs
|
|
63
|
-
# 日志文件轮转时间 (HH:MM 格式)
|
|
64
|
-
# LOG_ROTATION_TIME=00:00
|
|
65
|
-
# 日志文件轮转大小阈值
|
|
66
|
-
# LOG_ROTATION_SIZE=100 MB
|
|
67
|
-
# 日志文件保留天数
|
|
68
|
-
# LOG_RETENTION_DAYS=7
|
|
69
|
-
# 是否启用日志文件轮转
|
|
70
|
-
# LOG_ENABLE_FILE_ROTATION=true
|
|
71
|
-
# 是否输出日志到控制台
|
|
72
|
-
# LOG_ENABLE_CONSOLE=true
|
|
73
|
-
|
|
74
|
-
# =============================================================================
|
|
75
|
-
# 健康检查配置 (HEALTH_CHECK_)
|
|
76
|
-
# =============================================================================
|
|
77
|
-
# 健康检查端点路径
|
|
78
|
-
# HEALTH_CHECK_PATH=/api/health
|
|
79
|
-
# 是否启用健康检查端点
|
|
80
|
-
# HEALTH_CHECK_ENABLED=true
|
|
81
|
-
|
|
82
|
-
# =============================================================================
|
|
83
|
-
# 管理后台配置 (ADMIN_) - SQLAdmin Admin Console
|
|
84
|
-
# =============================================================================
|
|
85
|
-
# 是否启用管理后台(生产建议仅内网或配合反向代理)
|
|
86
|
-
# ADMIN_ENABLED=false
|
|
87
|
-
# 管理后台路径(默认 /api/admin-console,避免与业务 URL 冲突)
|
|
88
|
-
# ADMIN_PATH=/api/admin-console
|
|
89
|
-
#
|
|
90
|
-
# SQLAdmin 通常要求同步 SQLAlchemy Engine:
|
|
91
|
-
# - 若 DATABASE_URL 使用的是异步驱动(如 postgresql+asyncpg),建议显式提供同步 URL 覆盖
|
|
92
|
-
# ADMIN_DATABASE_URL=postgresql+psycopg://user:pass@localhost:5432/{project_name_snake}
|
|
93
|
-
#
|
|
94
|
-
# 可选:显式指定项目侧模块(用于注册 views/auth)
|
|
95
|
-
# ADMIN_VIEWS_MODULE={project_name_snake}.admin_console
|
|
96
|
-
#
|
|
97
|
-
# 认证(默认建议 basic / bearer;jwt/custom 通常需要自定义 backend)
|
|
98
|
-
# ADMIN_AUTH_MODE=basic
|
|
99
|
-
# ADMIN_AUTH_SECRET_KEY=CHANGE_ME_TO_A_RANDOM_SECRET
|
|
100
|
-
#
|
|
101
|
-
# basic:登录页用户名/密码
|
|
102
|
-
# ADMIN_AUTH_BASIC_USERNAME=admin
|
|
103
|
-
# ADMIN_AUTH_BASIC_PASSWORD=change_me
|
|
104
|
-
#
|
|
105
|
-
# bearer:token 白名单(也支持在登录页输入 token)
|
|
106
|
-
# ADMIN_AUTH_BEARER_TOKENS=["change_me_token"]
|
|
107
|
-
#
|
|
108
|
-
# custom/jwt:自定义认证后端(动态导入)
|
|
109
|
-
# ADMIN_AUTH_BACKEND=yourpkg.admin_auth:backend
|
|
110
|
-
|
|
111
|
-
# =============================================================================
|
|
112
|
-
# CORS 配置 (CORS_)
|
|
113
|
-
# =============================================================================
|
|
114
|
-
# 允许的 CORS 源(生产环境应设置具体域名)
|
|
115
|
-
# CORS_ORIGINS=["*"]
|
|
116
|
-
# 是否允许 CORS 凭据
|
|
117
|
-
# CORS_ALLOW_CREDENTIALS=true
|
|
118
|
-
# 允许的 CORS 方法
|
|
119
|
-
# CORS_ALLOW_METHODS=["*"]
|
|
120
|
-
# 允许的 CORS 头
|
|
121
|
-
# CORS_ALLOW_HEADERS=["*"]
|
|
122
|
-
|
|
123
|
-
# =============================================================================
|
|
124
|
-
# 调度器配置 (SCHEDULER_)
|
|
125
|
-
# =============================================================================
|
|
126
|
-
# 是否在 API 服务中启用内嵌调度器
|
|
127
|
-
# SCHEDULER_ENABLED=true
|
|
128
|
-
# 定时任务模块列表(为空时自动发现 schedules 模块)
|
|
129
|
-
# SCHEDULER_SCHEDULE_MODULES=[]
|
|
130
|
-
|
|
131
|
-
# =============================================================================
|
|
132
|
-
# 任务队列配置 (TASK_)
|
|
133
|
-
# =============================================================================
|
|
134
|
-
# 任务队列代理 URL(如 Redis 或 RabbitMQ)
|
|
135
|
-
# TASK_BROKER_URL=redis://localhost:6379/1
|
|
136
|
-
# 最大重试次数
|
|
137
|
-
# TASK_MAX_RETRIES=3
|
|
138
|
-
# 任务超时时间(秒)
|
|
139
|
-
# TASK_TIMEOUT=3600
|
|
140
|
-
|
|
141
|
-
# =============================================================================
|
|
142
|
-
# 事件总线配置 (EVENT_)
|
|
143
|
-
# =============================================================================
|
|
144
|
-
# 事件总线代理 URL(如 RabbitMQ)
|
|
145
|
-
# EVENT_BROKER_URL=amqp://guest:guest@localhost:5672/
|
|
146
|
-
# 事件交换机名称
|
|
147
|
-
# EVENT_EXCHANGE_NAME=aury.events
|
|
148
|
-
|
|
149
|
-
# =============================================================================
|
|
150
|
-
# 数据库迁移配置 (MIGRATION_)
|
|
151
|
-
# =============================================================================
|
|
152
|
-
# Alembic 配置文件路径
|
|
153
|
-
# MIGRATION_CONFIG_PATH=alembic.ini
|
|
154
|
-
# Alembic 迁移脚本目录
|
|
155
|
-
# MIGRATION_SCRIPT_LOCATION=migrations
|
|
156
|
-
# 是否自动创建迁移配置和目录
|
|
157
|
-
# MIGRATION_AUTO_CREATE=true
|
|
158
|
-
|
|
159
|
-
# =============================================================================
|
|
160
|
-
# 对象存储配置 (STORAGE_) - 基于 aury-sdk-storage
|
|
161
|
-
# =============================================================================
|
|
162
|
-
# 是否启用存储组件
|
|
163
|
-
# STORAGE_ENABLED=true
|
|
164
|
-
# 存储类型: local / s3 / cos / oss
|
|
165
|
-
# STORAGE_TYPE=local
|
|
166
|
-
#
|
|
167
|
-
# 本地存储(开发环境)
|
|
168
|
-
# STORAGE_BASE_PATH=./storage
|
|
169
|
-
#
|
|
170
|
-
# S3/COS/OSS 通用配置
|
|
171
|
-
# STORAGE_ACCESS_KEY_ID=AKIDxxxxx
|
|
172
|
-
# STORAGE_ACCESS_KEY_SECRET=xxxxx
|
|
173
|
-
# STORAGE_SESSION_TOKEN=
|
|
174
|
-
# STORAGE_ENDPOINT=https://cos.ap-guangzhou.myqcloud.com
|
|
175
|
-
# STORAGE_REGION=ap-guangzhou
|
|
176
|
-
# STORAGE_BUCKET_NAME=my-bucket-1250000000
|
|
177
|
-
# STORAGE_ADDRESSING_STYLE=virtual
|
|
178
|
-
#
|
|
179
|
-
# STS AssumeRole(可选,服务端自动刷新凭证)
|
|
180
|
-
# STORAGE_ROLE_ARN=
|
|
181
|
-
# STORAGE_ROLE_SESSION_NAME=aury-storage
|
|
182
|
-
# STORAGE_EXTERNAL_ID=
|
|
183
|
-
# STORAGE_STS_ENDPOINT=
|
|
184
|
-
# STORAGE_STS_REGION=
|
|
185
|
-
# STORAGE_STS_DURATION_SECONDS=3600
|
|
186
|
-
|
|
187
|
-
# =============================================================================
|
|
188
|
-
# RPC 客户端配置 (RPC_CLIENT_)
|
|
189
|
-
# =============================================================================
|
|
190
|
-
# 服务地址映射 {{service_name: url}}
|
|
191
|
-
# RPC_CLIENT_SERVICES={{"user-service": "http://localhost:8001"}}
|
|
192
|
-
# 默认超时时间(秒)
|
|
193
|
-
# RPC_CLIENT_DEFAULT_TIMEOUT=30
|
|
194
|
-
# 默认重试次数
|
|
195
|
-
# RPC_CLIENT_DEFAULT_RETRY_TIMES=3
|
|
196
|
-
# DNS 解析使用的协议
|
|
197
|
-
# RPC_CLIENT_DNS_SCHEME=http
|
|
198
|
-
# DNS 解析默认端口
|
|
199
|
-
# RPC_CLIENT_DNS_PORT=80
|
|
200
|
-
# 是否使用 DNS 回退(K8s/Docker Compose 自动 DNS)
|
|
201
|
-
# RPC_CLIENT_USE_DNS_FALLBACK=true
|
|
202
|
-
|
|
203
|
-
# =============================================================================
|
|
204
|
-
# RPC 服务注册配置 (RPC_SERVICE_)
|
|
205
|
-
# =============================================================================
|
|
206
|
-
# 服务名称(用于注册)
|
|
207
|
-
# RPC_SERVICE_NAME={project_name_snake}
|
|
208
|
-
# 服务地址(用于注册)
|
|
209
|
-
# RPC_SERVICE_URL=http://localhost:8000
|
|
210
|
-
# 健康检查 URL(用于注册)
|
|
211
|
-
# RPC_SERVICE_HEALTH_CHECK_URL=http://localhost:8000/api/health
|
|
212
|
-
# 是否自动注册到服务注册中心
|
|
213
|
-
# RPC_SERVICE_AUTO_REGISTER=false
|
|
@@ -1,362 +0,0 @@
|
|
|
1
|
-
"""事件总线实现。
|
|
2
|
-
|
|
3
|
-
提供事件发布/订阅机制,支持本地和分布式模式。
|
|
4
|
-
|
|
5
|
-
**架构说明**:
|
|
6
|
-
本模块不依赖具体的 domain.events 实现,而是通过接口和类型变量来保持通用性。
|
|
7
|
-
事件总线作为 infrastructure 组件只需要事件对象能够被序列化。
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
from __future__ import annotations
|
|
11
|
-
|
|
12
|
-
import asyncio
|
|
13
|
-
from collections.abc import Callable
|
|
14
|
-
from typing import Any
|
|
15
|
-
|
|
16
|
-
from kombu import Connection, Exchange
|
|
17
|
-
|
|
18
|
-
from aury.boot.common.logging import logger
|
|
19
|
-
|
|
20
|
-
from .config import EventConfig
|
|
21
|
-
from .consumer import EventConsumer
|
|
22
|
-
from .models import Event, EventHandler, EventType
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class EventBus:
|
|
26
|
-
"""事件总线(单例模式)。
|
|
27
|
-
|
|
28
|
-
职责:
|
|
29
|
-
1. 管理事件订阅者
|
|
30
|
-
2. 分发事件(支持本地和分布式模式)
|
|
31
|
-
3. 异步事件处理
|
|
32
|
-
|
|
33
|
-
模式:
|
|
34
|
-
- 本地模式:内存中的观察者模式(默认,无需消息队列)
|
|
35
|
-
- 分布式模式:使用 Kombu 消息队列(需要配置 broker_url)
|
|
36
|
-
|
|
37
|
-
使用示例:
|
|
38
|
-
# 本地模式(内存)
|
|
39
|
-
event_bus = EventBus.get_instance()
|
|
40
|
-
|
|
41
|
-
@event_bus.subscribe(MyEvent)
|
|
42
|
-
async def on_my_event(event: MyEvent):
|
|
43
|
-
print(f"Event {event.event_name} received!")
|
|
44
|
-
|
|
45
|
-
await event_bus.publish(MyEvent(...))
|
|
46
|
-
|
|
47
|
-
# 分布式模式(Kombu)
|
|
48
|
-
await event_bus.initialize(broker_url="redis://localhost:6379/0")
|
|
49
|
-
await event_bus.publish(MyEvent(...)) # 通过消息队列发布
|
|
50
|
-
"""
|
|
51
|
-
|
|
52
|
-
_instance: EventBus | None = None
|
|
53
|
-
|
|
54
|
-
def __init__(self, settings: EventConfig | None = None) -> None:
|
|
55
|
-
"""私有构造函数,使用 get_instance() 获取实例。
|
|
56
|
-
|
|
57
|
-
Args:
|
|
58
|
-
settings: 事件总线配置(如果为 None 则使用默认配置)
|
|
59
|
-
"""
|
|
60
|
-
if EventBus._instance is not None:
|
|
61
|
-
raise RuntimeError("EventBus 是单例类,请使用 get_instance() 获取实例")
|
|
62
|
-
|
|
63
|
-
self._settings = settings or EventConfig()
|
|
64
|
-
self._handlers: dict[type[Event], list[EventHandler]] = {}
|
|
65
|
-
self._event_history: list[Event] = []
|
|
66
|
-
|
|
67
|
-
# Kombu 相关
|
|
68
|
-
self._connection: Any = None # Connection | None
|
|
69
|
-
self._exchange: Any = None # Exchange | None
|
|
70
|
-
self._producer: Any = None # Producer | None
|
|
71
|
-
self._consumer: Any = None # EventConsumer | None
|
|
72
|
-
self._consumer_task: Any = None # asyncio.Task | None
|
|
73
|
-
self._initialized: bool = False
|
|
74
|
-
self._use_distributed: bool = False
|
|
75
|
-
self._broker_url: str | None = None
|
|
76
|
-
|
|
77
|
-
logger.debug("事件总线已创建(本地模式)")
|
|
78
|
-
|
|
79
|
-
@classmethod
|
|
80
|
-
def get_instance(cls) -> EventBus:
|
|
81
|
-
"""获取单例实例。"""
|
|
82
|
-
if cls._instance is None:
|
|
83
|
-
cls._instance = cls()
|
|
84
|
-
return cls._instance
|
|
85
|
-
|
|
86
|
-
async def initialize(
|
|
87
|
-
self,
|
|
88
|
-
broker_url: str | None = None,
|
|
89
|
-
*,
|
|
90
|
-
exchange_name: str | None = None,
|
|
91
|
-
queue_prefix: str | None = None,
|
|
92
|
-
) -> None:
|
|
93
|
-
"""初始化分布式事件总线(使用 Kombu)。
|
|
94
|
-
|
|
95
|
-
Args:
|
|
96
|
-
broker_url: 消息队列 URL(如 "redis://localhost:6379/0" 或 "amqp://guest:guest@localhost:5672//")
|
|
97
|
-
如果不指定则使用配置中的值
|
|
98
|
-
exchange_name: 交换机名称,如果不指定则使用配置中的值
|
|
99
|
-
queue_prefix: 队列名称前缀,如果不指定则使用配置中的值
|
|
100
|
-
"""
|
|
101
|
-
if self._initialized:
|
|
102
|
-
logger.warning("事件总线已初始化,跳过")
|
|
103
|
-
return
|
|
104
|
-
|
|
105
|
-
# 使用提供的参数或配置中的值
|
|
106
|
-
final_broker_url = broker_url or self._settings.broker_url
|
|
107
|
-
final_exchange_name = exchange_name or self._settings.exchange_name
|
|
108
|
-
final_queue_prefix = queue_prefix or self._settings.queue_prefix
|
|
109
|
-
|
|
110
|
-
if not final_broker_url:
|
|
111
|
-
logger.info("未提供 broker_url,使用本地模式(内存)")
|
|
112
|
-
self._use_distributed = False
|
|
113
|
-
self._initialized = True
|
|
114
|
-
return
|
|
115
|
-
|
|
116
|
-
try:
|
|
117
|
-
self._broker_url = final_broker_url
|
|
118
|
-
self._connection = Connection(final_broker_url)
|
|
119
|
-
self._exchange = Exchange(final_exchange_name, type="topic", durable=True)
|
|
120
|
-
|
|
121
|
-
# 创建生产者
|
|
122
|
-
self._producer = self._connection.Producer(
|
|
123
|
-
exchange=self._exchange,
|
|
124
|
-
serializer="json",
|
|
125
|
-
)
|
|
126
|
-
|
|
127
|
-
# 启动消费者(在后台任务中)
|
|
128
|
-
self._consumer = EventConsumer(
|
|
129
|
-
connection=self._connection,
|
|
130
|
-
exchange=self._exchange,
|
|
131
|
-
queue_prefix=final_queue_prefix,
|
|
132
|
-
handlers=self._handlers,
|
|
133
|
-
)
|
|
134
|
-
|
|
135
|
-
# 在后台启动消费者
|
|
136
|
-
self._consumer_task = asyncio.create_task(self._start_consumer())
|
|
137
|
-
|
|
138
|
-
self._use_distributed = True
|
|
139
|
-
self._initialized = True
|
|
140
|
-
logger.info(f"事件总线已初始化(分布式模式,broker: {broker_url})")
|
|
141
|
-
except Exception as exc:
|
|
142
|
-
logger.error(f"事件总线初始化失败: {exc}")
|
|
143
|
-
raise
|
|
144
|
-
|
|
145
|
-
async def _start_consumer(self) -> None:
|
|
146
|
-
"""启动消费者(在后台运行)。"""
|
|
147
|
-
if self._consumer:
|
|
148
|
-
try:
|
|
149
|
-
# 在事件循环中运行消费者
|
|
150
|
-
loop = asyncio.get_event_loop()
|
|
151
|
-
await loop.run_in_executor(None, self._consumer.run)
|
|
152
|
-
except Exception as exc:
|
|
153
|
-
logger.error(f"事件消费者运行失败: {exc}")
|
|
154
|
-
|
|
155
|
-
def subscribe(
|
|
156
|
-
self,
|
|
157
|
-
event_type: type[EventType],
|
|
158
|
-
handler: EventHandler | None = None,
|
|
159
|
-
) -> EventHandler | Callable[[EventHandler], EventHandler]:
|
|
160
|
-
"""订阅事件。
|
|
161
|
-
|
|
162
|
-
可以作为装饰器使用:
|
|
163
|
-
@event_bus.subscribe(MyEvent)
|
|
164
|
-
async def handler(event):
|
|
165
|
-
pass
|
|
166
|
-
|
|
167
|
-
或直接调用:
|
|
168
|
-
event_bus.subscribe(MyEvent, handler)
|
|
169
|
-
|
|
170
|
-
Args:
|
|
171
|
-
event_type: 事件类型
|
|
172
|
-
handler: 事件处理器
|
|
173
|
-
|
|
174
|
-
Returns:
|
|
175
|
-
EventHandler | Callable: 处理器或装饰器
|
|
176
|
-
"""
|
|
177
|
-
def decorator(fn: EventHandler) -> EventHandler:
|
|
178
|
-
if event_type not in self._handlers:
|
|
179
|
-
self._handlers[event_type] = []
|
|
180
|
-
|
|
181
|
-
self._handlers[event_type].append(fn)
|
|
182
|
-
logger.debug(f"订阅事件: {event_type.__name__} -> {fn.__name__}")
|
|
183
|
-
|
|
184
|
-
# 如果使用分布式模式,需要注册队列
|
|
185
|
-
if self._use_distributed and self._consumer:
|
|
186
|
-
self._consumer.register_event_type(event_type)
|
|
187
|
-
|
|
188
|
-
return fn
|
|
189
|
-
|
|
190
|
-
if handler is not None:
|
|
191
|
-
return decorator(handler)
|
|
192
|
-
return decorator
|
|
193
|
-
|
|
194
|
-
def unsubscribe(self, event_type: type[EventType], handler: EventHandler) -> None:
|
|
195
|
-
"""取消订阅事件。
|
|
196
|
-
|
|
197
|
-
Args:
|
|
198
|
-
event_type: 事件类型
|
|
199
|
-
handler: 事件处理器
|
|
200
|
-
"""
|
|
201
|
-
if event_type in self._handlers:
|
|
202
|
-
try:
|
|
203
|
-
self._handlers[event_type].remove(handler)
|
|
204
|
-
logger.debug(f"取消订阅事件: {event_type.__name__} -> {handler.__name__}")
|
|
205
|
-
except ValueError:
|
|
206
|
-
pass
|
|
207
|
-
|
|
208
|
-
async def publish(self, event: EventType) -> None:
|
|
209
|
-
"""发布事件。
|
|
210
|
-
|
|
211
|
-
Args:
|
|
212
|
-
event: 事件对象
|
|
213
|
-
"""
|
|
214
|
-
logger.info(f"发布事件: {event}")
|
|
215
|
-
|
|
216
|
-
# 记录事件历史
|
|
217
|
-
self._add_to_history(event)
|
|
218
|
-
|
|
219
|
-
if self._use_distributed:
|
|
220
|
-
# 分布式模式:通过消息队列发布
|
|
221
|
-
await self._publish_distributed(event)
|
|
222
|
-
else:
|
|
223
|
-
# 本地模式:直接调用处理器
|
|
224
|
-
await self._publish_local(event)
|
|
225
|
-
|
|
226
|
-
async def _publish_local(self, event: EventType) -> None:
|
|
227
|
-
"""本地模式:直接调用处理器。"""
|
|
228
|
-
handlers = self._handlers.get(type(event), [])
|
|
229
|
-
if not handlers:
|
|
230
|
-
logger.debug(f"事件 {event.event_name} 没有订阅者")
|
|
231
|
-
return
|
|
232
|
-
|
|
233
|
-
# 执行处理器
|
|
234
|
-
for handler in handlers:
|
|
235
|
-
try:
|
|
236
|
-
if asyncio.iscoroutinefunction(handler):
|
|
237
|
-
await handler(event)
|
|
238
|
-
else:
|
|
239
|
-
handler(event)
|
|
240
|
-
logger.debug(f"事件处理成功: {handler.__name__}")
|
|
241
|
-
except Exception as exc:
|
|
242
|
-
logger.error(f"事件处理失败: {handler.__name__}, 错误: {exc}")
|
|
243
|
-
|
|
244
|
-
async def _publish_distributed(self, event: EventType) -> None:
|
|
245
|
-
"""分布式模式:通过消息队列发布。"""
|
|
246
|
-
if not self._producer:
|
|
247
|
-
logger.warning("生产者未初始化,回退到本地模式")
|
|
248
|
-
await self._publish_local(event)
|
|
249
|
-
return
|
|
250
|
-
|
|
251
|
-
try:
|
|
252
|
-
# 序列化事件(使用 model_dump 而不是 model_dump_json,因为 kombu 会自动序列化)
|
|
253
|
-
event_data = event.model_dump()
|
|
254
|
-
routing_key = event.event_name.lower().replace("event", "")
|
|
255
|
-
|
|
256
|
-
# 发布到消息队列
|
|
257
|
-
self._producer.publish(
|
|
258
|
-
event_data,
|
|
259
|
-
routing_key=routing_key,
|
|
260
|
-
declare=[self._exchange],
|
|
261
|
-
)
|
|
262
|
-
logger.debug(f"事件已发布到消息队列: {routing_key}")
|
|
263
|
-
except Exception as exc:
|
|
264
|
-
logger.error(f"发布事件到消息队列失败: {exc}")
|
|
265
|
-
# 失败时回退到本地模式
|
|
266
|
-
await self._publish_local(event)
|
|
267
|
-
|
|
268
|
-
async def publish_many(self, events: list[Event]) -> None:
|
|
269
|
-
"""批量发布事件。
|
|
270
|
-
|
|
271
|
-
Args:
|
|
272
|
-
events: 事件列表
|
|
273
|
-
"""
|
|
274
|
-
for event in events:
|
|
275
|
-
await self.publish(event)
|
|
276
|
-
|
|
277
|
-
def _add_to_history(self, event: Event) -> None:
|
|
278
|
-
"""添加事件到历史记录。
|
|
279
|
-
|
|
280
|
-
Args:
|
|
281
|
-
event: 事件对象
|
|
282
|
-
"""
|
|
283
|
-
# 检查是否启用了历史记录
|
|
284
|
-
if not self._settings.enable_history:
|
|
285
|
-
return
|
|
286
|
-
|
|
287
|
-
self._event_history.append(event)
|
|
288
|
-
|
|
289
|
-
# 限制历史记录大小
|
|
290
|
-
if (
|
|
291
|
-
self._settings.max_history_size is not None
|
|
292
|
-
and len(self._event_history) > self._settings.max_history_size
|
|
293
|
-
):
|
|
294
|
-
self._event_history = self._event_history[-self._settings.max_history_size:]
|
|
295
|
-
|
|
296
|
-
def get_history(
|
|
297
|
-
self,
|
|
298
|
-
event_type: type[EventType] | None = None,
|
|
299
|
-
limit: int = 100,
|
|
300
|
-
) -> list[Event]:
|
|
301
|
-
"""获取事件历史。
|
|
302
|
-
|
|
303
|
-
Args:
|
|
304
|
-
event_type: 事件类型(可选,不指定则返回所有事件)
|
|
305
|
-
limit: 返回数量限制
|
|
306
|
-
|
|
307
|
-
Returns:
|
|
308
|
-
list[Event]: 事件列表
|
|
309
|
-
"""
|
|
310
|
-
if event_type is None:
|
|
311
|
-
return self._event_history[-limit:]
|
|
312
|
-
|
|
313
|
-
filtered = [e for e in self._event_history if isinstance(e, event_type)]
|
|
314
|
-
return filtered[-limit:]
|
|
315
|
-
|
|
316
|
-
def clear_history(self) -> None:
|
|
317
|
-
"""清空事件历史。"""
|
|
318
|
-
self._event_history.clear()
|
|
319
|
-
logger.debug("事件历史已清空")
|
|
320
|
-
|
|
321
|
-
def clear_handlers(self) -> None:
|
|
322
|
-
"""清空所有事件处理器。"""
|
|
323
|
-
self._handlers.clear()
|
|
324
|
-
logger.debug("事件处理器已清空")
|
|
325
|
-
|
|
326
|
-
def get_handler_count(self, event_type: type[EventType] | None = None) -> int:
|
|
327
|
-
"""获取处理器数量。
|
|
328
|
-
|
|
329
|
-
Args:
|
|
330
|
-
event_type: 事件类型(可选)
|
|
331
|
-
|
|
332
|
-
Returns:
|
|
333
|
-
int: 处理器数量
|
|
334
|
-
"""
|
|
335
|
-
if event_type is None:
|
|
336
|
-
return sum(len(handlers) for handlers in self._handlers.values())
|
|
337
|
-
return len(self._handlers.get(event_type, []))
|
|
338
|
-
|
|
339
|
-
async def cleanup(self) -> None:
|
|
340
|
-
"""清理资源。"""
|
|
341
|
-
if self._consumer:
|
|
342
|
-
self._consumer.should_stop = True
|
|
343
|
-
|
|
344
|
-
if self._connection:
|
|
345
|
-
self._connection.close()
|
|
346
|
-
|
|
347
|
-
self._initialized = False
|
|
348
|
-
self._use_distributed = False
|
|
349
|
-
logger.info("事件总线已清理")
|
|
350
|
-
|
|
351
|
-
def __repr__(self) -> str:
|
|
352
|
-
"""字符串表示。"""
|
|
353
|
-
event_count = len(self._handlers)
|
|
354
|
-
history_size = len(self._event_history)
|
|
355
|
-
mode = "distributed" if self._use_distributed else "local"
|
|
356
|
-
return f"<EventBus mode={mode} events={event_count} history={history_size}>"
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
__all__ = [
|
|
360
|
-
"EventBus",
|
|
361
|
-
]
|
|
362
|
-
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
"""事件总线配置。
|
|
2
|
-
|
|
3
|
-
提供事件系统的配置管理。
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
from __future__ import annotations
|
|
7
|
-
|
|
8
|
-
from pydantic import Field
|
|
9
|
-
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class EventConfig(BaseSettings):
|
|
13
|
-
"""事件总线基础设施配置。
|
|
14
|
-
|
|
15
|
-
Infrastructure 层直接使用的事件总线配置。
|
|
16
|
-
|
|
17
|
-
环境变量前缀: EVENT_
|
|
18
|
-
示例: EVENT_BROKER_URL, EVENT_EXCHANGE_NAME, EVENT_QUEUE_PREFIX, EVENT_MAX_HISTORY_SIZE
|
|
19
|
-
"""
|
|
20
|
-
|
|
21
|
-
broker_url: str | None = Field(
|
|
22
|
-
default=None,
|
|
23
|
-
description="消息队列 URL(如 redis://localhost:6379/0 或 amqp://guest:guest@localhost:5672//),为空时使用本地模式"
|
|
24
|
-
)
|
|
25
|
-
exchange_name: str = Field(
|
|
26
|
-
default="events",
|
|
27
|
-
description="交换机名称"
|
|
28
|
-
)
|
|
29
|
-
queue_prefix: str = Field(
|
|
30
|
-
default="event",
|
|
31
|
-
description="队列名称前缀"
|
|
32
|
-
)
|
|
33
|
-
max_history_size: int | None = Field(
|
|
34
|
-
default=1000,
|
|
35
|
-
description="事件历史记录最大条数(None 表示不限制,生产环境建议设置或禁用历史记录)"
|
|
36
|
-
)
|
|
37
|
-
enable_history: bool = Field(
|
|
38
|
-
default=True,
|
|
39
|
-
description="是否启用事件历史记录(生产环境建议关闭以节省内存)"
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
model_config = SettingsConfigDict(
|
|
43
|
-
env_prefix="EVENT_",
|
|
44
|
-
case_sensitive=False,
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
__all__ = [
|
|
49
|
-
"EventConfig",
|
|
50
|
-
]
|
|
51
|
-
|
|
52
|
-
|