aury-boot 0.0.5__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 (49) hide show
  1. aury/boot/_version.py +2 -2
  2. aury/boot/application/__init__.py +15 -0
  3. aury/boot/application/adapter/__init__.py +112 -0
  4. aury/boot/application/adapter/base.py +511 -0
  5. aury/boot/application/adapter/config.py +242 -0
  6. aury/boot/application/adapter/decorators.py +259 -0
  7. aury/boot/application/adapter/exceptions.py +202 -0
  8. aury/boot/application/adapter/http.py +325 -0
  9. aury/boot/application/app/middlewares.py +7 -4
  10. aury/boot/application/config/multi_instance.py +42 -26
  11. aury/boot/application/config/settings.py +111 -191
  12. aury/boot/application/middleware/logging.py +14 -1
  13. aury/boot/commands/generate.py +22 -22
  14. aury/boot/commands/init.py +41 -9
  15. aury/boot/commands/templates/project/AGENTS.md.tpl +8 -4
  16. aury/boot/commands/templates/project/aury_docs/01-model.md.tpl +17 -16
  17. aury/boot/commands/templates/project/aury_docs/11-logging.md.tpl +82 -43
  18. aury/boot/commands/templates/project/aury_docs/12-admin.md.tpl +14 -14
  19. aury/boot/commands/templates/project/aury_docs/13-channel.md.tpl +40 -28
  20. aury/boot/commands/templates/project/aury_docs/14-mq.md.tpl +9 -9
  21. aury/boot/commands/templates/project/aury_docs/15-events.md.tpl +8 -8
  22. aury/boot/commands/templates/project/aury_docs/16-adapter.md.tpl +403 -0
  23. aury/boot/commands/templates/project/aury_docs/99-cli.md.tpl +19 -19
  24. aury/boot/commands/templates/project/config.py.tpl +10 -10
  25. aury/boot/commands/templates/project/env_templates/_header.tpl +10 -0
  26. aury/boot/commands/templates/project/env_templates/admin.tpl +49 -0
  27. aury/boot/commands/templates/project/env_templates/cache.tpl +14 -0
  28. aury/boot/commands/templates/project/env_templates/database.tpl +22 -0
  29. aury/boot/commands/templates/project/env_templates/log.tpl +18 -0
  30. aury/boot/commands/templates/project/env_templates/messaging.tpl +46 -0
  31. aury/boot/commands/templates/project/env_templates/rpc.tpl +28 -0
  32. aury/boot/commands/templates/project/env_templates/scheduler.tpl +18 -0
  33. aury/boot/commands/templates/project/env_templates/service.tpl +18 -0
  34. aury/boot/commands/templates/project/env_templates/storage.tpl +38 -0
  35. aury/boot/commands/templates/project/env_templates/third_party.tpl +43 -0
  36. aury/boot/common/logging/__init__.py +26 -674
  37. aury/boot/common/logging/context.py +132 -0
  38. aury/boot/common/logging/decorators.py +118 -0
  39. aury/boot/common/logging/format.py +315 -0
  40. aury/boot/common/logging/setup.py +214 -0
  41. aury/boot/infrastructure/database/config.py +6 -14
  42. aury/boot/infrastructure/tasks/config.py +5 -13
  43. aury/boot/infrastructure/tasks/manager.py +8 -4
  44. aury/boot/testing/base.py +2 -2
  45. {aury_boot-0.0.5.dist-info → aury_boot-0.0.7.dist-info}/METADATA +2 -1
  46. {aury_boot-0.0.5.dist-info → aury_boot-0.0.7.dist-info}/RECORD +48 -27
  47. aury/boot/commands/templates/project/env.example.tpl +0 -281
  48. {aury_boot-0.0.5.dist-info → aury_boot-0.0.7.dist-info}/WHEEL +0 -0
  49. {aury_boot-0.0.5.dist-info → aury_boot-0.0.7.dist-info}/entry_points.txt +0 -0
@@ -2,33 +2,40 @@
2
2
 
3
3
  用于 SSE(Server-Sent Events)和实时通信。支持 memory 和 redis 后端。
4
4
 
5
+ ## 核心概念
6
+
7
+ - **ChannelManager**:管理器,支持命名多实例(如 sse、notification 独立管理)
8
+ - **channel 参数**:通道名/Topic,用于区分不同的消息流(如 `user:123`、`order:456`)
9
+
5
10
  ## 13.1 基本用法
6
11
 
7
12
  ```python
8
13
  from aury.boot.infrastructure.channel import ChannelManager
9
14
 
10
- # 获取实例
11
- channel = ChannelManager.get_instance()
15
+ # 命名多实例(推荐)- 不同业务场景使用不同实例
16
+ sse_channel = ChannelManager.get_instance("sse")
17
+ notification_channel = ChannelManager.get_instance("notification")
12
18
 
13
- # 初始化(Memory 后端 - 单进程)
14
- await channel.initialize(backend="memory")
19
+ # Memory 后端(单进程)
20
+ await sse_channel.initialize(backend="memory")
15
21
 
16
- # 初始化(Redis 后端 - 多进程/分布式)
22
+ # Redis 后端(多进程/分布式)
17
23
  from aury.boot.infrastructure.clients.redis import RedisClient
18
24
  redis_client = RedisClient.get_instance()
19
25
  await redis_client.initialize(url="redis://localhost:6379/0")
20
- await channel.initialize(backend="redis", redis_client=redis_client)
26
+ await notification_channel.initialize(backend="redis", redis_client=redis_client)
21
27
  ```
22
28
 
23
- ## 13.2 发布消息
29
+ ## 13.2 发布和订阅(Topic 管理)
24
30
 
25
31
  ```python
26
- # 发布消息到频道
27
- await channel.publish("user:123", {{"event": "message", "data": "hello"}})
32
+ # 发布消息到指定 Topic
33
+ await sse_channel.publish("user:123", {{"event": "message", "data": "hello"}})
34
+ await sse_channel.publish("order:456", {{"status": "shipped"}})
28
35
 
29
36
  # 发布到多个用户
30
37
  for user_id in user_ids:
31
- await channel.publish(f"user:{{user_id}}", notification)
38
+ await sse_channel.publish(f"user:{{user_id}}", notification)
32
39
  ```
33
40
 
34
41
  ## 13.3 SSE 端点示例
@@ -42,14 +49,17 @@ from fastapi.responses import StreamingResponse
42
49
  from aury.boot.infrastructure.channel import ChannelManager
43
50
 
44
51
  router = APIRouter(tags=["SSE"])
45
- channel = ChannelManager.get_instance()
52
+
53
+ # 获取命名实例(在 app 启动时已初始化)
54
+ sse_channel = ChannelManager.get_instance("sse")
46
55
 
47
56
 
48
57
  @router.get("/sse/{{user_id}}")
49
58
  async def sse_stream(user_id: str):
50
59
  \"\"\"SSE 实时消息流。\"\"\"
51
60
  async def event_generator():
52
- async for message in channel.subscribe(f"user:{{user_id}}"):
61
+ # user_id 作为 Topic,区分不同用户的消息流
62
+ async for message in sse_channel.subscribe(f"user:{{user_id}}"):
53
63
  yield f"data: {{json.dumps(message)}}\n\n"
54
64
 
55
65
  return StreamingResponse(
@@ -61,32 +71,34 @@ async def sse_stream(user_id: str):
61
71
 
62
72
  @router.post("/notify/{{user_id}}")
63
73
  async def send_notification(user_id: str, message: str):
64
- \"\"\"发送通知。\"\"\"
65
- await channel.publish(f"user:{{user_id}}", {{"message": message}})
74
+ \"\"\"\u53d1\u9001\u901a\u77e5\u3002\"\"\"
75
+ await sse_channel.publish(f"user:{{user_id}}", {{"message": message}})
66
76
  return {{"status": "sent"}}
67
77
  ```
68
78
 
69
- ## 13.4 多实例
79
+ ## 13.4 应用场景示例
70
80
 
71
81
  ```python
72
- # 不同用途的通道实例
73
- notifications = ChannelManager.get_instance("notifications")
74
- chat = ChannelManager.get_instance("chat")
75
-
76
- # 分别初始化
77
- await notifications.initialize(backend="redis", redis_client=redis_client)
78
- await chat.initialize(backend="redis", redis_client=redis_client)
82
+ # 不同业务场景使用不同的命名实例
83
+ sse_channel = ChannelManager.get_instance("sse") # 前端 SSE 推送
84
+ chat_channel = ChannelManager.get_instance("chat") # 聊天消息
85
+ notify_channel = ChannelManager.get_instance("notify") # 系统通知
86
+
87
+ # 分别初始化(可使用不同后端)
88
+ await sse_channel.initialize(backend="memory") # 单进程即可
89
+ await chat_channel.initialize(backend="redis", redis_client=redis_client) # 需要跨进程
90
+ await notify_channel.initialize(backend="redis", redis_client=redis_client)
79
91
  ```
80
92
 
81
93
  ## 13.5 环境变量
82
94
 
83
95
  ```bash
84
96
  # 默认实例
85
- CHANNEL_BACKEND=memory
97
+ CHANNEL__BACKEND=memory
86
98
 
87
- # 多实例(格式:CHANNEL_{{INSTANCE}}_{{FIELD}})
88
- CHANNEL_DEFAULT_BACKEND=redis
89
- CHANNEL_DEFAULT_URL=redis://localhost:6379/3
90
- CHANNEL_NOTIFICATIONS_BACKEND=redis
91
- CHANNEL_NOTIFICATIONS_URL=redis://localhost:6379/4
99
+ # 多实例(格式:CHANNEL__{{INSTANCE}}__{{FIELD}})
100
+ CHANNEL__DEFAULT__BACKEND=redis
101
+ CHANNEL__DEFAULT__URL=redis://localhost:6379/3
102
+ CHANNEL__NOTIFICATIONS__BACKEND=redis
103
+ CHANNEL__NOTIFICATIONS__URL=redis://localhost:6379/4
92
104
  ```
@@ -85,15 +85,15 @@ await notifications_mq.initialize(backend="redis", url="redis://localhost:6379/5
85
85
 
86
86
  ```bash
87
87
  # 默认实例
88
- MQ_BACKEND=redis
89
- MQ_URL=redis://localhost:6379/0
90
-
91
- # 多实例(格式:MQ_{{INSTANCE}}_{{FIELD}})
92
- MQ_DEFAULT_BACKEND=redis
93
- MQ_DEFAULT_URL=redis://localhost:6379/4
94
- MQ_ORDERS_BACKEND=rabbitmq
95
- MQ_ORDERS_URL=amqp://guest:guest@localhost:5672/
96
- MQ_ORDERS_PREFETCH_COUNT=10
88
+ MQ__BACKEND=redis
89
+ MQ__URL=redis://localhost:6379/0
90
+
91
+ # 多实例(格式:MQ__{{INSTANCE}}__{{FIELD}})
92
+ MQ__DEFAULT__BACKEND=redis
93
+ MQ__DEFAULT__URL=redis://localhost:6379/4
94
+ MQ__ORDERS__BACKEND=rabbitmq
95
+ MQ__ORDERS__URL=amqp://guest:guest@localhost:5672/
96
+ MQ__ORDERS__PREFETCH_COUNT=10
97
97
  ```
98
98
 
99
99
  ## 14.6 与异步任务(Dramatiq)的区别
@@ -130,14 +130,14 @@ await bus.initialize(
130
130
 
131
131
  ```bash
132
132
  # 默认实例
133
- EVENT_BACKEND=memory
134
-
135
- # 多实例(格式:EVENT_{{INSTANCE}}_{{FIELD}})
136
- EVENT_DEFAULT_BACKEND=redis
137
- EVENT_DEFAULT_URL=redis://localhost:6379/5
138
- EVENT_DOMAIN_BACKEND=rabbitmq
139
- EVENT_DOMAIN_URL=amqp://guest:guest@localhost:5672/
140
- EVENT_DOMAIN_EXCHANGE_NAME=domain.events
133
+ EVENT__BACKEND=memory
134
+
135
+ # 多实例(格式:EVENT__{{INSTANCE}}__{{FIELD}})
136
+ EVENT__DEFAULT__BACKEND=redis
137
+ EVENT__DEFAULT__URL=redis://localhost:6379/5
138
+ EVENT__DOMAIN__BACKEND=rabbitmq
139
+ EVENT__DOMAIN__URL=amqp://guest:guest@localhost:5672/
140
+ EVENT__DOMAIN__EXCHANGE_NAME=domain.events
141
141
  ```
142
142
 
143
143
  ## 15.6 最佳实践
@@ -0,0 +1,403 @@
1
+ # Adapter(第三方接口适配器)
2
+
3
+ ## 16.1 概述
4
+
5
+ Adapter 模块用于封装第三方接口(如支付、短信、微信等外部服务)的调用,支持在不同环境(开发、测试、生产)中灵活切换真实调用与 Mock 实现。
6
+
7
+ **核心特性**:
8
+ - 多模式支持:`real`(真实调用)、`sandbox`(沙箱环境)、`mock`(本地 Mock)、`disabled`(禁用)
9
+ - 方法级模式覆盖:同一 Adapter 的不同方法可以使用不同模式
10
+ - 装饰器式 Mock:使用 `.mock` 链式方法定义 Mock 逻辑
11
+ - 调用记录:测试时可追踪所有调用历史
12
+
13
+ ## 16.2 基础用法
14
+
15
+ **文件**: `{package_name}/adapters/payment_adapter.py`
16
+
17
+ ```python
18
+ """支付适配器。"""
19
+
20
+ from aury.boot.application.adapter import (
21
+ BaseAdapter,
22
+ AdapterSettings,
23
+ adapter_method,
24
+ )
25
+
26
+
27
+ class PaymentAdapter(BaseAdapter):
28
+ """支付第三方适配器。"""
29
+
30
+ @adapter_method("create_order")
31
+ async def create_order(self, amount: int, order_id: str) -> dict:
32
+ """创建支付订单(真实实现)。"""
33
+ # 真实调用第三方 API
34
+ response = await self.http_client.post(
35
+ "https://api.payment.com/orders",
36
+ json={"amount": amount, "order_id": order_id},
37
+ )
38
+ return response.json()
39
+
40
+ @create_order.mock
41
+ async def create_order_mock(self, amount: int, order_id: str) -> dict:
42
+ """创建支付订单(Mock 实现)。"""
43
+ if amount > 100000:
44
+ return {"success": False, "error": "金额超限"}
45
+ return {
46
+ "success": True,
47
+ "transaction_id": f"mock_tx_{order_id}",
48
+ "amount": amount,
49
+ }
50
+
51
+ @adapter_method("query_order")
52
+ async def query_order(self, transaction_id: str) -> dict:
53
+ """查询支付订单。"""
54
+ response = await self.http_client.get(
55
+ f"https://api.payment.com/orders/{transaction_id}"
56
+ )
57
+ return response.json()
58
+
59
+ @query_order.mock
60
+ async def query_order_mock(self, transaction_id: str) -> dict:
61
+ """查询支付订单(Mock)。"""
62
+ return {
63
+ "transaction_id": transaction_id,
64
+ "status": "paid",
65
+ "mock": True,
66
+ }
67
+ ```
68
+
69
+ ## 16.3 Adapter 配置
70
+
71
+ ### 环境变量配置
72
+
73
+ ```bash
74
+ # .env
75
+
76
+ # 全局模式:real / sandbox / mock / disabled
77
+ THIRD_PARTY__GATEWAY_MODE=mock
78
+
79
+ # 方法级模式覆盖(JSON 格式)
80
+ # 例如:query 方法使用 real,其他方法使用全局配置
81
+ THIRD_PARTY__METHOD_MODES={"query_order": "real"}
82
+
83
+ # Mock 策略:decorator(装饰器)/ auto(自动生成)
84
+ THIRD_PARTY__MOCK_STRATEGY=decorator
85
+
86
+ # 调试模式
87
+ THIRD_PARTY__DEBUG=true
88
+ ```
89
+
90
+ ### 代码配置
91
+
92
+ ```python
93
+ from aury.boot.application.adapter import AdapterSettings
94
+
95
+ # 方式 1:从环境变量加载
96
+ settings = AdapterSettings()
97
+
98
+ # 方式 2:代码显式配置
99
+ settings = AdapterSettings(
100
+ mode="mock",
101
+ method_modes={
102
+ "query_order": "real", # query_order 使用真实调用
103
+ "create_order": "mock", # create_order 使用 Mock
104
+ },
105
+ debug=True,
106
+ )
107
+
108
+ # 创建 Adapter 实例
109
+ adapter = PaymentAdapter("payment", settings)
110
+ ```
111
+
112
+ ## 16.4 使用 HttpAdapter
113
+
114
+ 对于 HTTP 类第三方 API,推荐使用 `HttpAdapter`:
115
+
116
+ ```python
117
+ from aury.boot.application.adapter import HttpAdapter, AdapterSettings, adapter_method
118
+
119
+
120
+ class WechatAdapter(HttpAdapter):
121
+ """微信 API 适配器。"""
122
+
123
+ def __init__(self, settings: AdapterSettings | None = None):
124
+ super().__init__(
125
+ name="wechat",
126
+ settings=settings,
127
+ base_url="https://api.weixin.qq.com",
128
+ timeout=30.0,
129
+ )
130
+
131
+ async def _prepare_headers(self, headers: dict | None) -> dict:
132
+ """添加认证头。"""
133
+ headers = await super()._prepare_headers(headers)
134
+ headers["Authorization"] = f"Bearer {await self._get_access_token()}"
135
+ return headers
136
+
137
+ @adapter_method("send_message")
138
+ async def send_message(self, openid: str, content: str) -> dict:
139
+ """发送消息。"""
140
+ return await self._request(
141
+ "POST",
142
+ "/cgi-bin/message/send",
143
+ json={"touser": openid, "content": content},
144
+ )
145
+
146
+ @send_message.mock
147
+ async def send_message_mock(self, openid: str, content: str) -> dict:
148
+ """发送消息(Mock)。"""
149
+ return {"errcode": 0, "errmsg": "ok", "mock": True}
150
+ ```
151
+
152
+ ## 16.5 方法级模式覆盖
153
+
154
+ 同一 Adapter 的不同方法可以使用不同模式:
155
+
156
+ ```python
157
+ settings = AdapterSettings(
158
+ mode="mock", # 默认 Mock
159
+ method_modes={
160
+ "query_order": "real", # 查询走真实接口
161
+ "create_order": "mock", # 创建走 Mock
162
+ "refund": "disabled", # 退款禁用
163
+ },
164
+ )
165
+
166
+ adapter = PaymentAdapter("payment", settings)
167
+
168
+ # query_order 会调用真实 API
169
+ result = await adapter.query_order("tx_123")
170
+
171
+ # create_order 会调用 Mock 方法
172
+ result = await adapter.create_order(100, "order_123")
173
+
174
+ # refund 会抛出 AdapterDisabledError
175
+ result = await adapter.refund("tx_123") # 抛出异常
176
+ ```
177
+
178
+ ## 16.6 测试中使用
179
+
180
+ ### 调用记录追踪
181
+
182
+ ```python
183
+ import pytest
184
+ from {package_name}.adapters.payment_adapter import PaymentAdapter
185
+ from aury.boot.application.adapter import AdapterSettings
186
+
187
+
188
+ @pytest.fixture
189
+ def payment_adapter():
190
+ settings = AdapterSettings(mode="mock")
191
+ return PaymentAdapter("payment", settings)
192
+
193
+
194
+ async def test_create_order(payment_adapter):
195
+ """测试创建订单。"""
196
+ result = await payment_adapter.create_order(100, "order_001")
197
+
198
+ assert result["success"] is True
199
+ assert result["mock"] is True
200
+
201
+ # 检查调用记录
202
+ history = payment_adapter.call_history
203
+ assert len(history) == 1
204
+ assert history[0].method == "create_order"
205
+ assert history[0].args == (100, "order_001")
206
+ assert history[0].result == result
207
+
208
+
209
+ async def test_amount_limit(payment_adapter):
210
+ """测试金额超限。"""
211
+ result = await payment_adapter.create_order(200000, "order_002")
212
+
213
+ assert result["success"] is False
214
+ assert "超限" in result["error"]
215
+
216
+
217
+ async def test_clear_history(payment_adapter):
218
+ """测试清除调用记录。"""
219
+ await payment_adapter.create_order(100, "order_001")
220
+ await payment_adapter.query_order("tx_001")
221
+
222
+ assert len(payment_adapter.call_history) == 2
223
+
224
+ payment_adapter.clear_history()
225
+ assert len(payment_adapter.call_history) == 0
226
+ ```
227
+
228
+ ## 16.7 高级用法
229
+
230
+ ### 钩子方法
231
+
232
+ ```python
233
+ class PaymentAdapter(HttpAdapter):
234
+ """带钩子的支付适配器。"""
235
+
236
+ async def _on_before_call(
237
+ self, method: str, args: tuple, kwargs: dict
238
+ ) -> None:
239
+ """调用前钩子。"""
240
+ logger.info(f"调用 {method},参数: {args}")
241
+
242
+ async def _on_after_call(
243
+ self, method: str, args: tuple, kwargs: dict, result: Any
244
+ ) -> None:
245
+ """调用后钩子。"""
246
+ logger.info(f"{method} 返回: {result}")
247
+
248
+ async def _on_call_error(
249
+ self, method: str, args: tuple, kwargs: dict, error: Exception
250
+ ) -> None:
251
+ """调用异常钩子。"""
252
+ logger.error(f"{method} 异常: {error}")
253
+ # 可以在这里发送告警
254
+ ```
255
+
256
+ ### 复合适配器
257
+
258
+ ```python
259
+ class CompositePaymentAdapter(BaseAdapter):
260
+ """组合多个支付渠道。"""
261
+
262
+ def __init__(self, settings: AdapterSettings | None = None):
263
+ super().__init__("composite_payment", settings)
264
+ self.alipay = AlipayAdapter(settings)
265
+ self.wechat = WechatAdapter(settings)
266
+
267
+ @adapter_method("pay")
268
+ async def pay(self, channel: str, amount: int, order_id: str) -> dict:
269
+ """根据渠道选择支付方式。"""
270
+ if channel == "alipay":
271
+ return await self.alipay.create_order(amount, order_id)
272
+ elif channel == "wechat":
273
+ return await self.wechat.create_order(amount, order_id)
274
+ else:
275
+ raise ValueError(f"不支持的支付渠道: {channel}")
276
+
277
+ @pay.mock
278
+ async def pay_mock(self, channel: str, amount: int, order_id: str) -> dict:
279
+ """统一 Mock 实现。"""
280
+ return {
281
+ "success": True,
282
+ "channel": channel,
283
+ "transaction_id": f"mock_{channel}_{order_id}",
284
+ }
285
+ ```
286
+
287
+ ## 16.8 异常处理
288
+
289
+ ```python
290
+ from aury.boot.application.adapter import (
291
+ AdapterError,
292
+ AdapterDisabledError,
293
+ AdapterTimeoutError,
294
+ AdapterValidationError,
295
+ )
296
+
297
+ try:
298
+ result = await adapter.create_order(100, "order_001")
299
+ except AdapterDisabledError:
300
+ logger.warning("支付适配器已禁用")
301
+ # 降级处理
302
+ except AdapterTimeoutError:
303
+ logger.error("支付适配器超时")
304
+ # 重试或告警
305
+ except AdapterValidationError as e:
306
+ logger.error(f"参数校验失败: {e}")
307
+ except AdapterError as e:
308
+ logger.error(f"适配器错误: {e}")
309
+ ```
310
+
311
+ ## 16.9 最佳实践
312
+
313
+ ### 1. Adapter 放置位置
314
+
315
+ ```
316
+ {package_name}/
317
+ ├── adapters/ # 第三方适配器
318
+ │ ├── __init__.py
319
+ │ ├── payment_adapter.py # 支付适配器
320
+ │ ├── sms_adapter.py # 短信适配器
321
+ │ └── wechat_adapter.py # 微信适配器
322
+ ```
323
+
324
+ ### 2. Mock 逻辑应覆盖边界情况
325
+
326
+ ```python
327
+ @create_order.mock
328
+ async def create_order_mock(self, amount: int, order_id: str) -> dict:
329
+ """Mock 应模拟各种场景。"""
330
+ # 模拟金额校验
331
+ if amount <= 0:
332
+ return {"success": False, "error": "金额必须大于0"}
333
+ if amount > 100000:
334
+ return {"success": False, "error": "金额超限"}
335
+
336
+ # 模拟偶发失败(可选)
337
+ import random
338
+ if random.random() < 0.01:
339
+ return {"success": False, "error": "系统繁忙"}
340
+
341
+ return {"success": True, "transaction_id": f"mock_{order_id}"}
342
+ ```
343
+
344
+ ### 3. 环境配置建议
345
+
346
+ ```bash
347
+ # 开发环境 (.env.development)
348
+ THIRD_PARTY__GATEWAY_MODE=mock
349
+ THIRD_PARTY__DEBUG=true
350
+
351
+ # 测试环境 (.env.testing)
352
+ THIRD_PARTY__GATEWAY_MODE=mock
353
+ THIRD_PARTY__METHOD_MODES={"query": "sandbox"}
354
+
355
+ # 生产环境 (.env.production)
356
+ THIRD_PARTY__GATEWAY_MODE=real
357
+ THIRD_PARTY__DEBUG=false
358
+ ```
359
+
360
+ ### 4. 在 Service 中使用
361
+
362
+ ```python
363
+ from sqlalchemy.ext.asyncio import AsyncSession
364
+
365
+ from aury.boot.domain.service.base import BaseService
366
+ from aury.boot.domain.transaction import transactional
367
+
368
+ from {package_name}.adapters.payment_adapter import PaymentAdapter
369
+ from {package_name}.repositories.order_repository import OrderRepository
370
+
371
+
372
+ class OrderService(BaseService):
373
+ """订单服务。"""
374
+
375
+ def __init__(self, session: AsyncSession, payment: PaymentAdapter):
376
+ super().__init__(session)
377
+ self.order_repo = OrderRepository(session)
378
+ self.payment = payment
379
+
380
+ @transactional
381
+ async def create_order(self, user_id: str, amount: int) -> Order:
382
+ """创建订单并发起支付。"""
383
+ # 1. 创建订单记录
384
+ order = await self.order_repo.create({
385
+ "user_id": user_id,
386
+ "amount": amount,
387
+ "status": "pending",
388
+ })
389
+
390
+ # 2. 调用支付适配器
391
+ pay_result = await self.payment.create_order(amount, str(order.id))
392
+
393
+ if not pay_result["success"]:
394
+ raise PaymentError(pay_result["error"])
395
+
396
+ # 3. 更新订单状态
397
+ await self.order_repo.update(order, {
398
+ "transaction_id": pay_result["transaction_id"],
399
+ "status": "paid",
400
+ })
401
+
402
+ return order
403
+ ```
@@ -36,10 +36,10 @@ aury generate schema user # Pydantic Schema
36
36
  aury generate model user --fields "name:str,email:str,age:int"
37
37
 
38
38
  # 指定模型基类
39
- aury generate model user --base UUIDAuditableStateModel # UUID主键 + 软删除(推荐)
40
- aury generate model user --base UUIDModel # UUID主键 + 时间戳
39
+ aury generate model user --base AuditableStateModel # int主键 + 软删除(推荐)
41
40
  aury generate model user --base Model # int主键 + 时间戳
42
- aury generate model user --base VersionedUUIDModel # UUID + 乐观锁 + 时间戳
41
+ aury generate model user --base FullFeaturedModel # int主键 + 全功能
42
+ aury generate model user --base UUIDAuditableStateModel # UUID主键(如需要)
43
43
  ```
44
44
 
45
45
  ## 数据库迁移
@@ -65,13 +65,13 @@ aury worker # 运行 Dramatiq Worker
65
65
 
66
66
  | 变量 | 说明 | 默认值 |
67
67
  |------|------|--------|
68
- | `DATABASE_URL` | 数据库连接 URL | `sqlite+aiosqlite:///./dev.db` |
69
- | `CACHE_TYPE` | 缓存类型 (memory/redis) | `memory` |
70
- | `CACHE_URL` | Redis URL | - |
71
- | `LOG_LEVEL` | 日志级别 | `INFO` |
72
- | `LOG_DIR` | 日志目录 | `logs` |
73
- | `SCHEDULER_ENABLED` | 启用内嵌调度器 | `true` |
74
- | `TASK_BROKER_URL` | 任务队列 Broker URL | - |
68
+ | `DATABASE__URL` | 数据库连接 URL | `sqlite+aiosqlite:///./dev.db` |
69
+ | `CACHE__CACHE_TYPE` | 缓存类型 (memory/redis) | `memory` |
70
+ | `CACHE__URL` | Redis URL | - |
71
+ | `LOG__LEVEL` | 日志级别 | `INFO` |
72
+ | `LOG__DIR` | 日志目录 | `logs` |
73
+ | `SCHEDULER__ENABLED` | 启用内嵌调度器 | `true` |
74
+ | `TASK__BROKER_URL` | 任务队列 Broker URL | - |
75
75
 
76
76
  ## 管理后台(Admin Console)
77
77
 
@@ -81,12 +81,12 @@ aury worker # 运行 Dramatiq Worker
81
81
 
82
82
  | 变量 | 说明 | 默认值 |
83
83
  |------|------|--------|
84
- | `ADMIN_ENABLED` | 是否启用管理后台 | `false` |
85
- | `ADMIN_PATH` | 管理后台路径 | `/api/admin-console` |
86
- | `ADMIN_DATABASE_URL` | 管理后台同步数据库 URL(可覆盖自动推导) | - |
87
- | `ADMIN_AUTH_MODE` | 认证模式(basic/bearer/none/custom/jwt) | `basic` |
88
- | `ADMIN_AUTH_SECRET_KEY` | session 签名密钥(生产必配) | - |
89
- | `ADMIN_AUTH_BASIC_USERNAME` | basic 用户名 | - |
90
- | `ADMIN_AUTH_BASIC_PASSWORD` | basic 密码 | - |
91
- | `ADMIN_AUTH_BEARER_TOKENS` | bearer token 白名单 | `[]` |
92
- | `ADMIN_AUTH_BACKEND` | 自定义认证后端导入路径(module:attr) | - |
84
+ | `ADMIN__ENABLED` | 是否启用管理后台 | `false` |
85
+ | `ADMIN__PATH` | 管理后台路径 | `/api/admin-console` |
86
+ | `ADMIN__DATABASE_URL` | 管理后台同步数据库 URL(可覆盖自动推导) | - |
87
+ | `ADMIN__AUTH_MODE` | 认证模式(basic/bearer/none/custom/jwt) | `basic` |
88
+ | `ADMIN__AUTH_SECRET_KEY` | session 签名密钥(生产必配) | - |
89
+ | `ADMIN__AUTH_BASIC_USERNAME` | basic 用户名 | - |
90
+ | `ADMIN__AUTH_BASIC_PASSWORD` | basic 密码 | - |
91
+ | `ADMIN__AUTH_BEARER_TOKENS` | bearer token 白名单 | `[]` |
92
+ | `ADMIN__AUTH_BACKEND` | 自定义认证后端导入路径(module:attr) | - |
@@ -2,11 +2,11 @@
2
2
 
3
3
  配置优先级:命令行参数 > 环境变量 > .env 文件 > 默认值
4
4
 
5
- 环境变量示例:
6
- DATABASE_URL=postgresql+asyncpg://user:pass@localhost:5432/mydb
7
- CACHE_TYPE=redis
8
- CACHE_URL=redis://localhost:6379/0
9
- LOG_LEVEL=INFO
5
+ 环境变量格式(使用双下划线分层):
6
+ DATABASE__URL=postgresql+asyncpg://user:pass@localhost:5432/mydb
7
+ CACHE__CACHE_TYPE=redis
8
+ CACHE__URL=redis://localhost:6379/0
9
+ LOG__LEVEL=INFO
10
10
  """
11
11
 
12
12
  from aury.boot.application.config import BaseConfig
@@ -16,11 +16,11 @@ class AppConfig(BaseConfig):
16
16
  """{project_name} 配置。
17
17
 
18
18
  继承 BaseConfig 获得所有默认配置项:
19
- - server: 服务器配置
20
- - database: 数据库配置
21
- - cache: 缓存配置
22
- - log: 日志配置
23
- - migration: 迁移配置
19
+ - server: 服务器配置 (SERVER__*)
20
+ - database: 数据库配置 (DATABASE__*)
21
+ - cache: 缓存配置 (CACHE__*)
22
+ - log: 日志配置 (LOG__*)
23
+ - migration: 迁移配置 (MIGRATION__*)
24
24
 
25
25
  可以在这里添加自定义配置项。
26
26
  """
@@ -0,0 +1,10 @@
1
+ # =============================================================================
2
+ # {project_name} 环境变量配置
3
+ # =============================================================================
4
+ # 复制此文件为 .env 并根据实际情况修改
5
+ # 所有配置项均有默认值,只需取消注释并修改需要覆盖的项
6
+ #
7
+ # 环境变量格式说明:
8
+ # - 使用双下划线 (__) 作为层级分隔符
9
+ # - 单实例: {{SECTION}}__{{FIELD}}=value
10
+ # - 多实例: {{SECTION}}__{{INSTANCE}}__{{FIELD}}=value