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.
Files changed (122) hide show
  1. aury/boot/__init__.py +2 -2
  2. aury/boot/_version.py +2 -2
  3. aury/boot/application/__init__.py +60 -36
  4. aury/boot/application/adapter/__init__.py +112 -0
  5. aury/boot/application/adapter/base.py +511 -0
  6. aury/boot/application/adapter/config.py +242 -0
  7. aury/boot/application/adapter/decorators.py +259 -0
  8. aury/boot/application/adapter/exceptions.py +202 -0
  9. aury/boot/application/adapter/http.py +325 -0
  10. aury/boot/application/app/__init__.py +12 -8
  11. aury/boot/application/app/base.py +12 -0
  12. aury/boot/application/app/components.py +137 -44
  13. aury/boot/application/app/middlewares.py +9 -4
  14. aury/boot/application/app/startup.py +249 -0
  15. aury/boot/application/config/__init__.py +36 -1
  16. aury/boot/application/config/multi_instance.py +216 -0
  17. aury/boot/application/config/settings.py +398 -149
  18. aury/boot/application/constants/components.py +6 -0
  19. aury/boot/application/errors/handlers.py +17 -3
  20. aury/boot/application/middleware/logging.py +21 -120
  21. aury/boot/application/rpc/__init__.py +2 -2
  22. aury/boot/commands/__init__.py +30 -10
  23. aury/boot/commands/app.py +131 -1
  24. aury/boot/commands/docs.py +104 -17
  25. aury/boot/commands/generate.py +22 -22
  26. aury/boot/commands/init.py +68 -17
  27. aury/boot/commands/server/app.py +2 -3
  28. aury/boot/commands/templates/project/AGENTS.md.tpl +221 -0
  29. aury/boot/commands/templates/project/README.md.tpl +2 -2
  30. aury/boot/commands/templates/project/aury_docs/00-overview.md.tpl +59 -0
  31. aury/boot/commands/templates/project/aury_docs/01-model.md.tpl +184 -0
  32. aury/boot/commands/templates/project/aury_docs/02-repository.md.tpl +206 -0
  33. aury/boot/commands/templates/project/aury_docs/03-service.md.tpl +398 -0
  34. aury/boot/commands/templates/project/aury_docs/04-schema.md.tpl +95 -0
  35. aury/boot/commands/templates/project/aury_docs/05-api.md.tpl +116 -0
  36. aury/boot/commands/templates/project/aury_docs/06-exception.md.tpl +118 -0
  37. aury/boot/commands/templates/project/aury_docs/07-cache.md.tpl +122 -0
  38. aury/boot/commands/templates/project/aury_docs/08-scheduler.md.tpl +32 -0
  39. aury/boot/commands/templates/project/aury_docs/09-tasks.md.tpl +38 -0
  40. aury/boot/commands/templates/project/aury_docs/10-storage.md.tpl +115 -0
  41. aury/boot/commands/templates/project/aury_docs/11-logging.md.tpl +131 -0
  42. aury/boot/commands/templates/project/aury_docs/12-admin.md.tpl +56 -0
  43. aury/boot/commands/templates/project/aury_docs/13-channel.md.tpl +104 -0
  44. aury/boot/commands/templates/project/aury_docs/14-mq.md.tpl +102 -0
  45. aury/boot/commands/templates/project/aury_docs/15-events.md.tpl +147 -0
  46. aury/boot/commands/templates/project/aury_docs/16-adapter.md.tpl +403 -0
  47. aury/boot/commands/templates/project/{CLI.md.tpl → aury_docs/99-cli.md.tpl} +19 -19
  48. aury/boot/commands/templates/project/config.py.tpl +10 -10
  49. aury/boot/commands/templates/project/env_templates/_header.tpl +10 -0
  50. aury/boot/commands/templates/project/env_templates/admin.tpl +49 -0
  51. aury/boot/commands/templates/project/env_templates/cache.tpl +14 -0
  52. aury/boot/commands/templates/project/env_templates/database.tpl +22 -0
  53. aury/boot/commands/templates/project/env_templates/log.tpl +18 -0
  54. aury/boot/commands/templates/project/env_templates/messaging.tpl +46 -0
  55. aury/boot/commands/templates/project/env_templates/rpc.tpl +28 -0
  56. aury/boot/commands/templates/project/env_templates/scheduler.tpl +18 -0
  57. aury/boot/commands/templates/project/env_templates/service.tpl +18 -0
  58. aury/boot/commands/templates/project/env_templates/storage.tpl +38 -0
  59. aury/boot/commands/templates/project/env_templates/third_party.tpl +43 -0
  60. aury/boot/commands/templates/project/modules/tasks.py.tpl +1 -1
  61. aury/boot/common/logging/__init__.py +26 -674
  62. aury/boot/common/logging/context.py +132 -0
  63. aury/boot/common/logging/decorators.py +118 -0
  64. aury/boot/common/logging/format.py +315 -0
  65. aury/boot/common/logging/setup.py +214 -0
  66. aury/boot/contrib/admin_console/auth.py +2 -3
  67. aury/boot/contrib/admin_console/install.py +1 -1
  68. aury/boot/domain/models/mixins.py +48 -1
  69. aury/boot/domain/pagination/__init__.py +94 -0
  70. aury/boot/domain/repository/impl.py +1 -1
  71. aury/boot/domain/repository/interface.py +1 -1
  72. aury/boot/domain/transaction/__init__.py +8 -9
  73. aury/boot/infrastructure/__init__.py +86 -29
  74. aury/boot/infrastructure/cache/backends.py +102 -18
  75. aury/boot/infrastructure/cache/base.py +12 -0
  76. aury/boot/infrastructure/cache/manager.py +153 -91
  77. aury/boot/infrastructure/channel/__init__.py +24 -0
  78. aury/boot/infrastructure/channel/backends/__init__.py +9 -0
  79. aury/boot/infrastructure/channel/backends/memory.py +83 -0
  80. aury/boot/infrastructure/channel/backends/redis.py +88 -0
  81. aury/boot/infrastructure/channel/base.py +92 -0
  82. aury/boot/infrastructure/channel/manager.py +203 -0
  83. aury/boot/infrastructure/clients/__init__.py +22 -0
  84. aury/boot/infrastructure/clients/rabbitmq/__init__.py +9 -0
  85. aury/boot/infrastructure/clients/rabbitmq/config.py +46 -0
  86. aury/boot/infrastructure/clients/rabbitmq/manager.py +288 -0
  87. aury/boot/infrastructure/clients/redis/__init__.py +28 -0
  88. aury/boot/infrastructure/clients/redis/config.py +51 -0
  89. aury/boot/infrastructure/clients/redis/manager.py +264 -0
  90. aury/boot/infrastructure/database/config.py +7 -16
  91. aury/boot/infrastructure/database/manager.py +16 -38
  92. aury/boot/infrastructure/events/__init__.py +18 -21
  93. aury/boot/infrastructure/events/backends/__init__.py +11 -0
  94. aury/boot/infrastructure/events/backends/memory.py +86 -0
  95. aury/boot/infrastructure/events/backends/rabbitmq.py +193 -0
  96. aury/boot/infrastructure/events/backends/redis.py +162 -0
  97. aury/boot/infrastructure/events/base.py +127 -0
  98. aury/boot/infrastructure/events/manager.py +224 -0
  99. aury/boot/infrastructure/mq/__init__.py +24 -0
  100. aury/boot/infrastructure/mq/backends/__init__.py +9 -0
  101. aury/boot/infrastructure/mq/backends/rabbitmq.py +179 -0
  102. aury/boot/infrastructure/mq/backends/redis.py +167 -0
  103. aury/boot/infrastructure/mq/base.py +143 -0
  104. aury/boot/infrastructure/mq/manager.py +239 -0
  105. aury/boot/infrastructure/scheduler/manager.py +7 -3
  106. aury/boot/infrastructure/storage/__init__.py +9 -9
  107. aury/boot/infrastructure/storage/base.py +17 -5
  108. aury/boot/infrastructure/storage/factory.py +0 -1
  109. aury/boot/infrastructure/tasks/__init__.py +2 -2
  110. aury/boot/infrastructure/tasks/config.py +5 -13
  111. aury/boot/infrastructure/tasks/manager.py +55 -33
  112. {aury_boot-0.0.4.dist-info → aury_boot-0.0.7.dist-info}/METADATA +20 -2
  113. aury_boot-0.0.7.dist-info/RECORD +197 -0
  114. aury/boot/commands/templates/project/DEVELOPMENT.md.tpl +0 -1397
  115. aury/boot/commands/templates/project/env.example.tpl +0 -213
  116. aury/boot/infrastructure/events/bus.py +0 -362
  117. aury/boot/infrastructure/events/config.py +0 -52
  118. aury/boot/infrastructure/events/consumer.py +0 -134
  119. aury/boot/infrastructure/events/models.py +0 -63
  120. aury_boot-0.0.4.dist-info/RECORD +0 -137
  121. {aury_boot-0.0.4.dist-info → aury_boot-0.0.7.dist-info}/WHEEL +0 -0
  122. {aury_boot-0.0.4.dist-info → aury_boot-0.0.7.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,325 @@
1
+ """HTTP 类第三方接口适配器。
2
+
3
+ 本模块提供 HTTP REST API 类第三方接口的便捷基类,适用于大部分第三方服务
4
+ (如支付接口、短信接口、地图API、云服务API等)。
5
+
6
+ 基于 toolkit.http.HttpClient 封装,核心功能:
7
+ - 自动管理 HttpClient 生命周期(连接池、重试、超时)
8
+ - 统一的请求头处理(认证、签名、trace-id)
9
+ - 请求/响应日志和链路追踪
10
+ - HTTP 错误转换为 AdapterError
11
+ - 自动根据 mode 选择 base_url(生产)或 sandbox_url(沙箱)
12
+
13
+ 如果第三方提供的是 SDK 而非 HTTP API(如微信支付 SDK、阿里云 SDK),
14
+ 或者使用 gRPC 等非 HTTP 协议,请直接继承 BaseAdapter。
15
+
16
+ 典型第三方接口举例:
17
+ - 支付:微信支付、支付宝、Stripe 等
18
+ - 短信:阿里云短信、腾讯云短信、Twilio 等
19
+ - 云存储:七牛、又拍云、AWS S3 等
20
+ - 社交:微信开放平台、企业微信、钉钉等
21
+ - 地图:高德、百度、Google Maps 等
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ from typing import Any
27
+
28
+ import httpx
29
+
30
+ from aury.boot.common.logging import get_trace_id, logger
31
+ from aury.boot.toolkit.http import HttpClient, RetryConfig
32
+
33
+ from .base import BaseAdapter
34
+ from .config import AdapterSettings
35
+ from .exceptions import AdapterError, AdapterTimeoutError
36
+
37
+
38
+ class HttpAdapter(BaseAdapter):
39
+ """HTTP 类第三方 Adapter 基类。
40
+
41
+ 封装 HttpClient,提供统一的 HTTP 请求方法和错误处理。
42
+
43
+ 核心功能:
44
+ - 自动根据 settings.mode 选择 base_url / sandbox_url
45
+ - 统一的请求头处理(认证、签名、trace-id)
46
+ - 请求/响应日志
47
+ - 超时和重试配置
48
+ - 错误转换为 AdapterError
49
+
50
+ 使用示例:
51
+ class PaymentAdapter(HttpAdapter):
52
+ @adapter_method("create")
53
+ async def create_order(self, amount: int, order_id: str) -> dict:
54
+ return await self._request(
55
+ "POST", "/v1/charges",
56
+ json={"amount": amount, "order_id": order_id}
57
+ )
58
+
59
+ @create_order.mock
60
+ async def create_order_mock(self, amount: int, order_id: str) -> dict:
61
+ return {"success": True, "mock": True, "charge_id": "ch_mock_123"}
62
+
63
+ # 自定义请求头(如签名)
64
+ def _prepare_headers(self, extra: dict | None = None) -> dict:
65
+ headers = super()._prepare_headers(extra)
66
+ headers["X-Signature"] = self._sign_request(...)
67
+ return headers
68
+
69
+ Attributes:
70
+ _client: HttpClient 实例(mode 为 real/sandbox 时可用)
71
+ """
72
+
73
+ def __init__(
74
+ self,
75
+ name: str,
76
+ settings: AdapterSettings,
77
+ *,
78
+ client: HttpClient | None = None,
79
+ ) -> None:
80
+ """初始化 HTTP Adapter。
81
+
82
+ Args:
83
+ name: Adapter 名称
84
+ settings: 集成配置
85
+ client: 自定义 HttpClient(可选,默认根据配置自动创建)
86
+ """
87
+ super().__init__(name, settings)
88
+ self._client: HttpClient | None = client
89
+ self._owns_client = client is None # 是否由本类创建(需要自己清理)
90
+
91
+ # ========== 生命周期 ==========
92
+
93
+ async def _on_initialize(self) -> None:
94
+ """初始化 HttpClient。"""
95
+ if self._client is not None:
96
+ return
97
+
98
+ # 只有 real / sandbox 模式需要真实客户端
99
+ if self.settings.mode not in ("real", "sandbox"):
100
+ logger.debug(f"HttpAdapter {self.name} 处于 {self.settings.mode} 模式,跳过 HttpClient 初始化")
101
+ return
102
+
103
+ effective_url = self.settings.get_effective_url()
104
+ if not effective_url:
105
+ logger.warning(
106
+ f"HttpAdapter {self.name} 未配置 base_url/sandbox_url,"
107
+ f"真实调用可能失败"
108
+ )
109
+ return
110
+
111
+ # 创建 HttpClient
112
+ retry_config = RetryConfig(max_retries=self.settings.retry_times)
113
+ self._client = HttpClient(
114
+ base_url=effective_url,
115
+ timeout=float(self.settings.timeout),
116
+ retry_config=retry_config,
117
+ )
118
+
119
+ logger.debug(
120
+ f"HttpAdapter {self.name} 初始化 HttpClient: "
121
+ f"url={effective_url}, timeout={self.settings.timeout}"
122
+ )
123
+
124
+ async def _on_cleanup(self) -> None:
125
+ """清理 HttpClient。"""
126
+ if self._client and self._owns_client:
127
+ await self._client.close()
128
+ self._client = None
129
+ logger.debug(f"HttpAdapter {self.name} 关闭 HttpClient")
130
+
131
+ # ========== 请求方法 ==========
132
+
133
+ def _prepare_headers(self, extra: dict[str, str] | None = None) -> dict[str, str]:
134
+ """准备请求头。
135
+
136
+ 默认包含:
137
+ - Content-Type: application/json
138
+ - X-Trace-Id: 链路追踪 ID
139
+ - Authorization: Bearer {api_key}(如果配置了 api_key)
140
+
141
+ 子类可覆盖此方法添加自定义头(如签名)。
142
+
143
+ Args:
144
+ extra: 额外的请求头
145
+
146
+ Returns:
147
+ dict: 合并后的请求头
148
+ """
149
+ headers: dict[str, str] = {
150
+ "Content-Type": "application/json",
151
+ "X-Trace-Id": get_trace_id(),
152
+ }
153
+
154
+ # 认证
155
+ if self.settings.api_key:
156
+ headers["Authorization"] = f"Bearer {self.settings.api_key}"
157
+
158
+ # 合并额外头
159
+ if extra:
160
+ headers.update(extra)
161
+
162
+ return headers
163
+
164
+ async def _request(
165
+ self,
166
+ method: str,
167
+ path: str,
168
+ *,
169
+ headers: dict[str, str] | None = None,
170
+ params: dict[str, Any] | None = None,
171
+ json: Any = None,
172
+ data: Any = None,
173
+ files: Any = None,
174
+ **kwargs: Any,
175
+ ) -> dict[str, Any]:
176
+ """发送 HTTP 请求。
177
+
178
+ 这是 HttpGateway 的核心方法,子类的 @operation 方法通常调用此方法。
179
+
180
+ Args:
181
+ method: HTTP 方法(GET/POST/PUT/DELETE 等)
182
+ path: 请求路径(相对于 base_url)
183
+ headers: 额外请求头(会与默认头合并)
184
+ params: URL 查询参数
185
+ json: JSON 请求体
186
+ data: 表单数据
187
+ files: 上传文件
188
+ **kwargs: 其他 httpx 参数
189
+
190
+ Returns:
191
+ dict: 响应 JSON
192
+
193
+ Raises:
194
+ GatewayError: 请求失败
195
+ GatewayTimeoutError: 请求超时
196
+ """
197
+ if self._client is None:
198
+ # 尝试延迟初始化
199
+ await self.initialize()
200
+ if self._client is None:
201
+ raise AdapterError(
202
+ f"HttpClient 未初始化,请检查 {self.name} 的 base_url 配置",
203
+ adapter_name=self.name,
204
+ )
205
+
206
+ merged_headers = self._prepare_headers(headers)
207
+
208
+ try:
209
+ response = await self._client.request(
210
+ method=method,
211
+ url=path,
212
+ headers=merged_headers,
213
+ params=params,
214
+ json=json,
215
+ data=data,
216
+ files=files,
217
+ **kwargs,
218
+ )
219
+
220
+ # 尝试解析 JSON
221
+ try:
222
+ return response.json()
223
+ except Exception:
224
+ # 非 JSON 响应,返回包装后的结果
225
+ return {
226
+ "success": response.is_success,
227
+ "status_code": response.status_code,
228
+ "content": response.text,
229
+ }
230
+
231
+ except httpx.TimeoutException as exc:
232
+ raise AdapterTimeoutError(
233
+ f"请求超时: {method} {path}",
234
+ adapter_name=self.name,
235
+ timeout_seconds=self.settings.timeout,
236
+ cause=exc,
237
+ ) from exc
238
+
239
+ except httpx.HTTPStatusError as exc:
240
+ # HTTP 错误状态码
241
+ response = exc.response
242
+ try:
243
+ error_data = response.json()
244
+ third_party_code = error_data.get("code") or error_data.get("error_code")
245
+ third_party_message = error_data.get("message") or error_data.get("error")
246
+ except Exception:
247
+ third_party_code = None
248
+ third_party_message = response.text
249
+
250
+ raise AdapterError(
251
+ f"HTTP 错误: {response.status_code} {method} {path}",
252
+ adapter_name=self.name,
253
+ third_party_code=third_party_code,
254
+ third_party_message=third_party_message,
255
+ cause=exc,
256
+ ) from exc
257
+
258
+ except Exception as exc:
259
+ raise AdapterError(
260
+ f"请求失败: {method} {path} - {type(exc).__name__}: {exc}",
261
+ adapter_name=self.name,
262
+ cause=exc,
263
+ ) from exc
264
+
265
+ # ========== 便捷方法 ==========
266
+
267
+ async def _get(
268
+ self,
269
+ path: str,
270
+ *,
271
+ params: dict[str, Any] | None = None,
272
+ headers: dict[str, str] | None = None,
273
+ **kwargs: Any,
274
+ ) -> dict[str, Any]:
275
+ """GET 请求。"""
276
+ return await self._request("GET", path, params=params, headers=headers, **kwargs)
277
+
278
+ async def _post(
279
+ self,
280
+ path: str,
281
+ *,
282
+ json: Any = None,
283
+ data: Any = None,
284
+ headers: dict[str, str] | None = None,
285
+ **kwargs: Any,
286
+ ) -> dict[str, Any]:
287
+ """POST 请求。"""
288
+ return await self._request("POST", path, json=json, data=data, headers=headers, **kwargs)
289
+
290
+ async def _put(
291
+ self,
292
+ path: str,
293
+ *,
294
+ json: Any = None,
295
+ headers: dict[str, str] | None = None,
296
+ **kwargs: Any,
297
+ ) -> dict[str, Any]:
298
+ """PUT 请求。"""
299
+ return await self._request("PUT", path, json=json, headers=headers, **kwargs)
300
+
301
+ async def _patch(
302
+ self,
303
+ path: str,
304
+ *,
305
+ json: Any = None,
306
+ headers: dict[str, str] | None = None,
307
+ **kwargs: Any,
308
+ ) -> dict[str, Any]:
309
+ """PATCH 请求。"""
310
+ return await self._request("PATCH", path, json=json, headers=headers, **kwargs)
311
+
312
+ async def _delete(
313
+ self,
314
+ path: str,
315
+ *,
316
+ headers: dict[str, str] | None = None,
317
+ **kwargs: Any,
318
+ ) -> dict[str, Any]:
319
+ """DELETE 请求。"""
320
+ return await self._request("DELETE", path, headers=headers, **kwargs)
321
+
322
+
323
+ __all__ = [
324
+ "HttpAdapter",
325
+ ]
@@ -8,6 +8,8 @@ from .components import (
8
8
  AdminConsoleComponent,
9
9
  CacheComponent,
10
10
  DatabaseComponent,
11
+ EventBusComponent,
12
+ MessageQueueComponent,
11
13
  MigrationComponent,
12
14
  SchedulerComponent,
13
15
  TaskComponent,
@@ -18,19 +20,21 @@ from .middlewares import (
18
20
  )
19
21
 
20
22
  __all__ = [
21
- # 应用框架
22
- "FoundationApp",
23
- # 基类
24
- "Component",
25
- "Middleware",
26
- # 中间件
27
- "CORSMiddleware",
28
- "RequestLoggingMiddleware",
29
23
  # 组件
30
24
  "AdminConsoleComponent",
25
+ # 中间件
26
+ "CORSMiddleware",
31
27
  "CacheComponent",
28
+ # 基类
29
+ "Component",
32
30
  "DatabaseComponent",
31
+ "EventBusComponent",
32
+ # 应用框架
33
+ "FoundationApp",
34
+ "MessageQueueComponent",
35
+ "Middleware",
33
36
  "MigrationComponent",
37
+ "RequestLoggingMiddleware",
34
38
  "SchedulerComponent",
35
39
  "TaskComponent",
36
40
  ]
@@ -349,6 +349,18 @@ class FoundationApp(FastAPI):
349
349
  raise
350
350
 
351
351
  logger.info("应用启动完成")
352
+
353
+ # 打印启动横幅和组件状态
354
+ from aury.boot.application.app.startup import (
355
+ collect_component_status,
356
+ print_startup_banner,
357
+ )
358
+ components = collect_component_status()
359
+ print_startup_banner(
360
+ app_name=self.title,
361
+ version=self.version,
362
+ components=components,
363
+ )
352
364
 
353
365
  except Exception as e:
354
366
  logger.error(f"应用启动异常: {e}")
@@ -16,6 +16,8 @@ from aury.boot.application.migrations import MigrationManager
16
16
  from aury.boot.common.logging import logger
17
17
  from aury.boot.infrastructure.cache import CacheManager
18
18
  from aury.boot.infrastructure.database import DatabaseManager
19
+ from aury.boot.infrastructure.events import EventBusManager
20
+ from aury.boot.infrastructure.mq import MQManager
19
21
  from aury.boot.infrastructure.scheduler import SchedulerManager
20
22
  from aury.boot.infrastructure.storage import StorageManager
21
23
  from aury.boot.infrastructure.tasks import TaskManager
@@ -74,12 +76,12 @@ class CacheComponent(Component):
74
76
  """初始化缓存。"""
75
77
  try:
76
78
  cache_manager = CacheManager.get_instance()
77
- if not cache_manager._backend:
78
- await cache_manager.init_app({
79
- "CACHE_TYPE": config.cache.cache_type,
80
- "CACHE_URL": config.cache.url,
81
- "CACHE_MAX_SIZE": config.cache.max_size,
82
- })
79
+ if not cache_manager.is_initialized:
80
+ await cache_manager.initialize(
81
+ backend=config.cache.cache_type,
82
+ url=config.cache.url,
83
+ max_size=config.cache.max_size,
84
+ )
83
85
  except Exception as e:
84
86
  logger.warning(f"缓存初始化失败(非关键): {e}")
85
87
 
@@ -87,7 +89,7 @@ class CacheComponent(Component):
87
89
  """关闭缓存。"""
88
90
  try:
89
91
  cache_manager = CacheManager.get_instance()
90
- if cache_manager._backend:
92
+ if cache_manager.is_initialized:
91
93
  await cache_manager.cleanup()
92
94
  except Exception as e:
93
95
  logger.warning(f"缓存关闭失败: {e}")
@@ -95,53 +97,53 @@ class CacheComponent(Component):
95
97
 
96
98
  class StorageComponent(Component):
97
99
  """对象存储组件。
98
-
99
- 设计要点(解耦):
100
- - 使用 aury-sdk-storage 的 StorageConfig/StorageBackend,Application 层仅做装配
101
- - 建议由应用的 `StorageSettings` 读取环境变量并构造 SDK 的 StorageConfig
102
- """
100
+
101
+ 支持多实例配置,通过环境变量 STORAGE_{INSTANCE}_{FIELD} 配置。
102
+ """
103
103
 
104
104
  name = ComponentName.STORAGE
105
105
  enabled = True
106
106
  depends_on: ClassVar[list[str]] = []
107
107
 
108
108
  def can_enable(self, config: BaseConfig) -> bool:
109
- st = getattr(config, "storage", None)
110
- return self.enabled and bool(getattr(st, "enabled", True))
109
+ """当配置了 Storage 实例时启用。"""
110
+ return self.enabled and bool(config.get_storages())
111
111
 
112
112
  async def setup(self, app: FoundationApp, config: BaseConfig) -> None:
113
- try:
114
- from aury.boot.infrastructure.storage import StorageBackend, StorageConfig
115
-
116
- storage_manager = StorageManager.get_instance()
117
- st = config.storage
118
- storage_config = StorageConfig(
119
- backend=StorageBackend(st.type),
120
- access_key_id=st.access_key_id,
121
- access_key_secret=st.access_key_secret,
122
- session_token=st.session_token,
123
- endpoint=st.endpoint,
124
- region=st.region,
125
- bucket_name=st.bucket_name,
126
- base_path=st.base_path,
127
- addressing_style=st.addressing_style,
128
- role_arn=st.role_arn,
129
- role_session_name=st.role_session_name,
130
- external_id=st.external_id,
131
- sts_endpoint=st.sts_endpoint,
132
- sts_region=st.sts_region,
133
- sts_duration_seconds=st.sts_duration_seconds,
134
- )
135
- await storage_manager.init(storage_config)
136
- except Exception as e:
137
- logger.warning(f"存储初始化失败(非关键): {e}")
113
+ """初始化存储。"""
114
+ from aury.boot.infrastructure.storage import StorageBackend, StorageConfig
115
+
116
+ storage_configs = config.get_storages()
117
+ if not storage_configs:
118
+ logger.debug("未配置 Storage 实例,跳过存储初始化")
119
+ return
120
+
121
+ for name, st_config in storage_configs.items():
122
+ try:
123
+ storage_manager = StorageManager.get_instance(name)
124
+ if not storage_manager.is_initialized:
125
+ storage_config = StorageConfig(
126
+ backend=StorageBackend(st_config.backend),
127
+ access_key_id=st_config.access_key_id,
128
+ access_key_secret=st_config.access_key_secret,
129
+ endpoint=st_config.endpoint,
130
+ region=st_config.region,
131
+ bucket_name=st_config.bucket_name,
132
+ base_path=st_config.base_path,
133
+ )
134
+ await storage_manager.initialize(storage_config)
135
+ except Exception as e:
136
+ logger.warning(f"存储 [{name}] 初始化失败(非关键): {e}")
138
137
 
139
138
  async def teardown(self, app: FoundationApp) -> None:
140
- try:
141
- storage_manager = StorageManager.get_instance()
142
- await storage_manager.cleanup()
143
- except Exception as e:
144
- logger.warning(f"存储关闭失败: {e}")
139
+ """关闭所有存储实例。"""
140
+ for name in list(StorageManager._instances.keys()):
141
+ try:
142
+ storage_manager = StorageManager.get_instance(name)
143
+ if storage_manager.is_initialized:
144
+ await storage_manager.cleanup()
145
+ except Exception as e:
146
+ logger.warning(f"存储 [{name}] 关闭失败: {e}")
145
147
 
146
148
 
147
149
  class TaskComponent(Component):
@@ -410,6 +412,93 @@ class AdminConsoleComponent(Component):
410
412
  pass
411
413
 
412
414
 
415
+ class MessageQueueComponent(Component):
416
+ """消息队列组件。
417
+
418
+ 提供统一的消息队列接口,支持多种后端(Redis、RabbitMQ)。
419
+ 与 TaskComponent(基于 Dramatiq)的区别:
420
+ - Task: 异步任务处理(API 发送,Worker 执行)
421
+ - MQ: 通用消息队列(生产者/消费者模式,服务间通信)
422
+ """
423
+
424
+ name = ComponentName.MESSAGE_QUEUE
425
+ enabled = True
426
+ depends_on: ClassVar[list[str]] = []
427
+
428
+ def can_enable(self, config: BaseConfig) -> bool:
429
+ """当配置了 MQ 实例时启用。"""
430
+ return self.enabled and bool(config.get_mqs())
431
+
432
+ async def setup(self, app: FoundationApp, config: BaseConfig) -> None:
433
+ """初始化消息队列。"""
434
+ from aury.boot.application.config import MQInstanceConfig
435
+
436
+ # 从多实例配置加载
437
+ mq_configs = config.get_mqs()
438
+ if not mq_configs:
439
+ logger.debug("未配置 MQ 实例,跳过消息队列初始化")
440
+ return
441
+
442
+ for name, mq_config in mq_configs.items():
443
+ try:
444
+ mq_manager = MQManager.get_instance(name)
445
+ if not mq_manager.is_initialized:
446
+ await mq_manager.initialize(config=mq_config)
447
+ except Exception as e:
448
+ logger.warning(f"消息队列 [{name}] 初始化失败(非关键): {e}")
449
+
450
+ async def teardown(self, app: FoundationApp) -> None:
451
+ """关闭所有消息队列实例。"""
452
+ for name in list(MQManager._instances.keys()):
453
+ try:
454
+ mq_manager = MQManager.get_instance(name)
455
+ if mq_manager.is_initialized:
456
+ await mq_manager.cleanup()
457
+ except Exception as e:
458
+ logger.warning(f"消息队列 [{name}] 关闭失败: {e}")
459
+
460
+
461
+ class EventBusComponent(Component):
462
+ """事件总线组件。
463
+
464
+ 提供发布/订阅模式的事件总线功能,支持多种后端(memory、Redis、RabbitMQ)。
465
+ """
466
+
467
+ name = ComponentName.EVENT_BUS
468
+ enabled = True
469
+ depends_on: ClassVar[list[str]] = []
470
+
471
+ def can_enable(self, config: BaseConfig) -> bool:
472
+ """当配置了 Event 实例时启用。"""
473
+ return self.enabled and bool(config.get_events())
474
+
475
+ async def setup(self, app: FoundationApp, config: BaseConfig) -> None:
476
+ """初始化事件总线。"""
477
+ # 从多实例配置加载
478
+ event_configs = config.get_events()
479
+ if not event_configs:
480
+ logger.debug("未配置 Event 实例,跳过事件总线初始化")
481
+ return
482
+
483
+ for name, event_config in event_configs.items():
484
+ try:
485
+ event_manager = EventBusManager.get_instance(name)
486
+ if not event_manager.is_initialized:
487
+ await event_manager.initialize(config=event_config)
488
+ except Exception as e:
489
+ logger.warning(f"事件总线 [{name}] 初始化失败(非关键): {e}")
490
+
491
+ async def teardown(self, app: FoundationApp) -> None:
492
+ """关闭所有事件总线实例。"""
493
+ for name in list(EventBusManager._instances.keys()):
494
+ try:
495
+ event_manager = EventBusManager.get_instance(name)
496
+ if event_manager.is_initialized:
497
+ await event_manager.cleanup()
498
+ except Exception as e:
499
+ logger.warning(f"事件总线 [{name}] 关闭失败: {e}")
500
+
501
+
413
502
  # 设置默认组件
414
503
  FoundationApp.components = [
415
504
  DatabaseComponent,
@@ -418,6 +507,8 @@ FoundationApp.components = [
418
507
  CacheComponent,
419
508
  StorageComponent,
420
509
  TaskComponent,
510
+ MessageQueueComponent,
511
+ EventBusComponent,
421
512
  SchedulerComponent,
422
513
  ]
423
514
 
@@ -426,6 +517,8 @@ __all__ = [
426
517
  "AdminConsoleComponent",
427
518
  "CacheComponent",
428
519
  "DatabaseComponent",
520
+ "EventBusComponent",
521
+ "MessageQueueComponent",
429
522
  "MigrationComponent",
430
523
  "SchedulerComponent",
431
524
  "StorageComponent",
@@ -13,6 +13,8 @@ from aury.boot.application.config import BaseConfig
13
13
  from aury.boot.application.constants import MiddlewareName
14
14
  from aury.boot.application.middleware.logging import (
15
15
  RequestLoggingMiddleware as StarletteRequestLoggingMiddleware,
16
+ )
17
+ from aury.boot.application.middleware.logging import (
16
18
  WebSocketLoggingMiddleware as StarletteWebSocketLoggingMiddleware,
17
19
  )
18
20
 
@@ -28,14 +30,17 @@ class RequestLoggingMiddleware(Middleware):
28
30
 
29
31
  自动记录所有 HTTP 请求的详细信息,包括:
30
32
  - 请求方法、路径、查询参数
31
- - 客户端 IP、User-Agent
33
+ - 客户端IP、User-Agent
32
34
  - 响应状态码、耗时
33
35
  - 链路追踪 ID(X-Trace-ID / X-Request-ID)
36
+ - 请求上下文(user_id, tenant_id 等用户注册的字段)
37
+
38
+ 注意:用户的认证中间件应设置 order < 100,以便在日志记录前设置用户信息。
34
39
  """
35
40
 
36
41
  name = MiddlewareName.REQUEST_LOGGING
37
42
  enabled = True
38
- order = 0 # 最先执行,确保日志记录所有请求
43
+ order = 100 # 用户中间件可使用 0-99 在此之前执行
39
44
 
40
45
  def build(self, config: BaseConfig) -> StarletteMiddleware:
41
46
  """构建请求日志中间件实例。"""
@@ -54,7 +59,7 @@ class CORSMiddleware(Middleware):
54
59
 
55
60
  name = MiddlewareName.CORS
56
61
  enabled = True
57
- order = 10 # 在请求日志之后执行
62
+ order = 110 # 在日志中间件之后执行
58
63
 
59
64
  def can_enable(self, config: BaseConfig) -> bool:
60
65
  """仅当配置了 origins 时启用。"""
@@ -83,7 +88,7 @@ class WebSocketLoggingMiddleware(Middleware):
83
88
 
84
89
  name = MiddlewareName.WEBSOCKET_LOGGING
85
90
  enabled = True
86
- order = 1 # 紧随 HTTP 日志中间件
91
+ order = 101 # 紧随 HTTP 日志中间件
87
92
 
88
93
  def build(self, config: BaseConfig) -> StarletteMiddleware:
89
94
  """构建 WebSocket 日志中间件实例。"""