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
|
@@ -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
|
|
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
|
|
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
|
-
| `
|
|
69
|
-
| `
|
|
70
|
-
| `
|
|
71
|
-
| `
|
|
72
|
-
| `
|
|
73
|
-
| `
|
|
74
|
-
| `
|
|
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
|
-
| `
|
|
85
|
-
| `
|
|
86
|
-
| `
|
|
87
|
-
| `
|
|
88
|
-
| `
|
|
89
|
-
| `
|
|
90
|
-
| `
|
|
91
|
-
| `
|
|
92
|
-
| `
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# 健康检查配置 (HEALTH_CHECK__)
|
|
4
|
+
# =============================================================================
|
|
5
|
+
# 健康检查端点路径
|
|
6
|
+
# HEALTH_CHECK__PATH=/api/health
|
|
7
|
+
# 是否启用健康检查端点
|
|
8
|
+
# HEALTH_CHECK__ENABLED=true
|
|
9
|
+
|
|
10
|
+
# =============================================================================
|
|
11
|
+
# CORS 配置 (CORS__)
|
|
12
|
+
# =============================================================================
|
|
13
|
+
# 允许的 CORS 源(生产环境应设置具体域名)
|
|
14
|
+
# CORS__ORIGINS=["*"]
|
|
15
|
+
# 是否允许 CORS 凭据
|
|
16
|
+
# CORS__ALLOW_CREDENTIALS=true
|
|
17
|
+
# 允许的 CORS 方法
|
|
18
|
+
# CORS__ALLOW_METHODS=["*"]
|
|
19
|
+
# 允许的 CORS 头
|
|
20
|
+
# CORS__ALLOW_HEADERS=["*"]
|
|
21
|
+
|
|
22
|
+
# =============================================================================
|
|
23
|
+
# 管理后台配置 (ADMIN__) - SQLAdmin Admin Console
|
|
24
|
+
# =============================================================================
|
|
25
|
+
# 是否启用管理后台(生产建议仅内网或配合反向代理)
|
|
26
|
+
# ADMIN__ENABLED=false
|
|
27
|
+
# 管理后台路径(默认 /api/admin-console,避免与业务 URL 冲突)
|
|
28
|
+
# ADMIN__PATH=/api/admin-console
|
|
29
|
+
#
|
|
30
|
+
# SQLAdmin 通常要求同步 SQLAlchemy Engine:
|
|
31
|
+
# - 若 DATABASE__URL 使用的是异步驱动(如 postgresql+asyncpg),建议显式提供同步 URL 覆盖
|
|
32
|
+
# ADMIN__DATABASE_URL=postgresql+psycopg://user:pass@localhost:5432/{project_name_snake}
|
|
33
|
+
#
|
|
34
|
+
# 可选:显式指定项目侧模块(用于注册 views/auth)
|
|
35
|
+
# ADMIN__VIEWS_MODULE={project_name_snake}.admin_console
|
|
36
|
+
#
|
|
37
|
+
# 认证配置 (嵌套格式: ADMIN__AUTH__{{FIELD}})
|
|
38
|
+
# ADMIN__AUTH__MODE=basic
|
|
39
|
+
# ADMIN__AUTH__SECRET_KEY=CHANGE_ME_TO_A_RANDOM_SECRET
|
|
40
|
+
#
|
|
41
|
+
# basic:登录页用户名/密码
|
|
42
|
+
# ADMIN__AUTH__BASIC_USERNAME=admin
|
|
43
|
+
# ADMIN__AUTH__BASIC_PASSWORD=change_me
|
|
44
|
+
#
|
|
45
|
+
# bearer:token 白名单(也支持在登录页输入 token)
|
|
46
|
+
# ADMIN__AUTH__BEARER_TOKENS=["change_me_token"]
|
|
47
|
+
#
|
|
48
|
+
# custom/jwt:自定义认证后端(动态导入)
|
|
49
|
+
# ADMIN__AUTH__BACKEND=yourpkg.admin_auth:backend
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# 缓存配置 (CACHE__)
|
|
4
|
+
# =============================================================================
|
|
5
|
+
# 单实例配置:
|
|
6
|
+
# CACHE__CACHE_TYPE=memory
|
|
7
|
+
# CACHE__URL=redis://localhost:6379/0
|
|
8
|
+
# CACHE__MAX_SIZE=1000
|
|
9
|
+
|
|
10
|
+
# 多实例配置 (格式: CACHE__{{INSTANCE}}__{{FIELD}}):
|
|
11
|
+
# CACHE__DEFAULT__BACKEND=memory
|
|
12
|
+
# CACHE__DEFAULT__MAX_SIZE=1000
|
|
13
|
+
# CACHE__SESSION__BACKEND=redis
|
|
14
|
+
# CACHE__SESSION__URL=redis://localhost:6379/2
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# 数据库配置 (DATABASE__)
|
|
4
|
+
# =============================================================================
|
|
5
|
+
# 单实例配置:
|
|
6
|
+
# DATABASE__URL=sqlite+aiosqlite:///./dev.db
|
|
7
|
+
# PostgreSQL: postgresql+asyncpg://user:pass@localhost:5432/{project_name_snake}
|
|
8
|
+
# MySQL: mysql+aiomysql://user:pass@localhost:3306/{project_name_snake}
|
|
9
|
+
|
|
10
|
+
# 连接池配置
|
|
11
|
+
# DATABASE__POOL_SIZE=5
|
|
12
|
+
# DATABASE__MAX_OVERFLOW=10
|
|
13
|
+
# DATABASE__POOL_RECYCLE=3600
|
|
14
|
+
# DATABASE__POOL_TIMEOUT=30
|
|
15
|
+
# DATABASE__POOL_PRE_PING=true
|
|
16
|
+
# DATABASE__ECHO=false
|
|
17
|
+
|
|
18
|
+
# 多实例配置 (格式: DATABASE__{{INSTANCE}}__{{FIELD}}):
|
|
19
|
+
# DATABASE__DEFAULT__URL=postgresql+asyncpg://user:pass@localhost:5432/{project_name_snake}
|
|
20
|
+
# DATABASE__DEFAULT__POOL_SIZE=5
|
|
21
|
+
# DATABASE__READONLY__URL=postgresql+asyncpg://user:pass@replica:5432/{project_name_snake}
|
|
22
|
+
# DATABASE__READONLY__POOL_SIZE=10
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# 日志配置 (LOG__)
|
|
4
|
+
# =============================================================================
|
|
5
|
+
# 日志级别: DEBUG / INFO / WARNING / ERROR / CRITICAL
|
|
6
|
+
# LOG__LEVEL=INFO
|
|
7
|
+
# 日志文件目录
|
|
8
|
+
# LOG__DIR=logs
|
|
9
|
+
# 日志文件轮转时间 (HH:MM 格式)
|
|
10
|
+
# LOG__ROTATION_TIME=00:00
|
|
11
|
+
# 日志文件轮转大小阈值
|
|
12
|
+
# LOG__ROTATION_SIZE=100 MB
|
|
13
|
+
# 日志文件保留天数
|
|
14
|
+
# LOG__RETENTION_DAYS=7
|
|
15
|
+
# 是否启用日志文件轮转
|
|
16
|
+
# LOG__ENABLE_FILE_ROTATION=true
|
|
17
|
+
# 是否输出日志到控制台
|
|
18
|
+
# LOG__ENABLE_CONSOLE=true
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# 流式通道配置 (CHANNEL__) - SSE/实时通信
|
|
4
|
+
# =============================================================================
|
|
5
|
+
# 多实例配置 (格式: CHANNEL__{{INSTANCE}}__{{FIELD}}):
|
|
6
|
+
# CHANNEL__DEFAULT__BACKEND=memory
|
|
7
|
+
# CHANNEL__SHARED__BACKEND=redis
|
|
8
|
+
# CHANNEL__SHARED__URL=redis://localhost:6379/3
|
|
9
|
+
# CHANNEL__SHARED__KEY_PREFIX=channel:
|
|
10
|
+
# CHANNEL__SHARED__TTL=86400
|
|
11
|
+
|
|
12
|
+
# =============================================================================
|
|
13
|
+
# 消息队列配置 (MQ__)
|
|
14
|
+
# =============================================================================
|
|
15
|
+
# 单实例配置:
|
|
16
|
+
# MQ__ENABLED=false
|
|
17
|
+
# MQ__BROKER_URL=redis://localhost:6379/4
|
|
18
|
+
|
|
19
|
+
# 多实例配置 (格式: MQ__{{INSTANCE}}__{{FIELD}}):
|
|
20
|
+
# MQ__DEFAULT__BACKEND=redis
|
|
21
|
+
# MQ__DEFAULT__URL=redis://localhost:6379/4
|
|
22
|
+
# MQ__DEFAULT__MAX_CONNECTIONS=10
|
|
23
|
+
#
|
|
24
|
+
# RabbitMQ 后端:
|
|
25
|
+
# MQ__ORDERS__BACKEND=rabbitmq
|
|
26
|
+
# MQ__ORDERS__URL=amqp://guest:guest@localhost:5672/orders
|
|
27
|
+
# MQ__ORDERS__PREFETCH_COUNT=10
|
|
28
|
+
|
|
29
|
+
# =============================================================================
|
|
30
|
+
# 事件总线配置 (EVENT__)
|
|
31
|
+
# =============================================================================
|
|
32
|
+
# 单实例配置:
|
|
33
|
+
# EVENT__BROKER_URL=
|
|
34
|
+
# EVENT__EXCHANGE_NAME=aury.events
|
|
35
|
+
|
|
36
|
+
# 多实例配置 (格式: EVENT__{{INSTANCE}}__{{FIELD}}):
|
|
37
|
+
# EVENT__DEFAULT__BACKEND=memory
|
|
38
|
+
# EVENT__DISTRIBUTED__BACKEND=redis
|
|
39
|
+
# EVENT__DISTRIBUTED__URL=redis://localhost:6379/5
|
|
40
|
+
# EVENT__DISTRIBUTED__KEY_PREFIX=events:
|
|
41
|
+
#
|
|
42
|
+
# RabbitMQ 后端:
|
|
43
|
+
# EVENT__DOMAIN__BACKEND=rabbitmq
|
|
44
|
+
# EVENT__DOMAIN__URL=amqp://guest:guest@localhost:5672/
|
|
45
|
+
# EVENT__DOMAIN__EXCHANGE_NAME=domain.events
|
|
46
|
+
# EVENT__DOMAIN__EXCHANGE_TYPE=topic
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# RPC 客户端配置 (RPC_CLIENT__)
|
|
4
|
+
# =============================================================================
|
|
5
|
+
# 服务地址映射 {service_name: url}
|
|
6
|
+
# RPC_CLIENT__SERVICES={"user-service": "http://localhost:8001"}
|
|
7
|
+
# 默认超时时间(秒)
|
|
8
|
+
# RPC_CLIENT__DEFAULT_TIMEOUT=30
|
|
9
|
+
# 默认重试次数
|
|
10
|
+
# RPC_CLIENT__DEFAULT_RETRY_TIMES=3
|
|
11
|
+
# DNS 解析使用的协议
|
|
12
|
+
# RPC_CLIENT__DNS_SCHEME=http
|
|
13
|
+
# DNS 解析默认端口
|
|
14
|
+
# RPC_CLIENT__DNS_PORT=80
|
|
15
|
+
# 是否使用 DNS 回退(K8s/Docker Compose 自动 DNS)
|
|
16
|
+
# RPC_CLIENT__USE_DNS_FALLBACK=true
|
|
17
|
+
|
|
18
|
+
# =============================================================================
|
|
19
|
+
# RPC 服务注册配置 (RPC_SERVICE__)
|
|
20
|
+
# =============================================================================
|
|
21
|
+
# 服务名称(用于注册)
|
|
22
|
+
# RPC_SERVICE__NAME={project_name_snake}
|
|
23
|
+
# 服务地址(用于注册)
|
|
24
|
+
# RPC_SERVICE__URL=http://localhost:8000
|
|
25
|
+
# 健康检查 URL(用于注册)
|
|
26
|
+
# RPC_SERVICE__HEALTH_CHECK_URL=http://localhost:8000/api/health
|
|
27
|
+
# 是否自动注册到服务注册中心
|
|
28
|
+
# RPC_SERVICE__AUTO_REGISTER=false
|