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,118 @@
|
|
|
1
|
+
# 异常处理
|
|
2
|
+
|
|
3
|
+
框架提供了统一的异常处理机制,所有异常都会被全局异常处理中间件捕获并转换为标准的 HTTP 响应。
|
|
4
|
+
|
|
5
|
+
## 6.1 异常处理流程
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
请求 → API 路由 → Service → Repository → 抛出异常
|
|
9
|
+
↓
|
|
10
|
+
全局异常处理中间件(Middleware)
|
|
11
|
+
↓
|
|
12
|
+
转换为 ErrorResponse Schema
|
|
13
|
+
↓
|
|
14
|
+
JSON 响应返回客户端
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**流程说明**:
|
|
18
|
+
1. 业务代码抛出异常(如 `NotFoundError`)
|
|
19
|
+
2. 框架的全局异常处理中间件自动捕获
|
|
20
|
+
3. 根据异常类型转换为对应的 HTTP 状态码和错误响应
|
|
21
|
+
4. 返回统一格式的 JSON 错误响应
|
|
22
|
+
|
|
23
|
+
**响应格式**:
|
|
24
|
+
```json
|
|
25
|
+
{{
|
|
26
|
+
"code": "NOT_FOUND",
|
|
27
|
+
"message": "用户不存在",
|
|
28
|
+
"data": null
|
|
29
|
+
}}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## 6.2 内置异常
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from aury.boot.application.errors import (
|
|
36
|
+
BaseError,
|
|
37
|
+
NotFoundError, # 404
|
|
38
|
+
AlreadyExistsError, # 409
|
|
39
|
+
ValidationError, # 422
|
|
40
|
+
UnauthorizedError, # 401
|
|
41
|
+
ForbiddenError, # 403
|
|
42
|
+
BusinessError, # 400
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# 使用示例
|
|
46
|
+
raise NotFoundError("用户不存在", resource=user_id)
|
|
47
|
+
raise AlreadyExistsError(f"邮箱 {{email}} 已被注册")
|
|
48
|
+
raise UnauthorizedError("未登录或登录已过期")
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## 6.3 自定义异常
|
|
52
|
+
|
|
53
|
+
**文件**: `{package_name}/exceptions/order.py`
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from enum import Enum
|
|
57
|
+
from fastapi import status
|
|
58
|
+
from aury.boot.application.errors import BaseError
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# 推荐:定义错误码枚举
|
|
62
|
+
class OrderErrorCode(str, Enum):
|
|
63
|
+
ORDER_ERROR = "5000"
|
|
64
|
+
ORDER_NOT_FOUND = "5001"
|
|
65
|
+
INSUFFICIENT_STOCK = "5002"
|
|
66
|
+
PAYMENT_FAILED = "5003"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class OrderError(BaseError):
|
|
70
|
+
default_message = "订单错误"
|
|
71
|
+
default_code = OrderErrorCode.ORDER_ERROR
|
|
72
|
+
default_status_code = status.HTTP_400_BAD_REQUEST
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class OrderNotFoundError(OrderError):
|
|
76
|
+
default_message = "订单不存在"
|
|
77
|
+
default_code = OrderErrorCode.ORDER_NOT_FOUND
|
|
78
|
+
default_status_code = status.HTTP_404_NOT_FOUND
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class InsufficientStockError(OrderError):
|
|
82
|
+
default_message = "库存不足"
|
|
83
|
+
default_code = OrderErrorCode.INSUFFICIENT_STOCK
|
|
84
|
+
default_status_code = status.HTTP_400_BAD_REQUEST
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# 使用
|
|
88
|
+
raise OrderNotFoundError() # 使用默认值
|
|
89
|
+
raise OrderError(message="订单ID无效") # 自定义消息
|
|
90
|
+
raise InsufficientStockError(message=f"商品 {{product_id}} 库存不足")
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Error Code 规范**:
|
|
94
|
+
- **推荐使用枚举**定义错误码,枚举值必须为数字字符串(如 `"5001"`)
|
|
95
|
+
- 框架预留编码范围:1xxx 通用、2xxx 数据库、3xxx 业务、4xxx 外部服务、**5xxx+ 用户自定义**
|
|
96
|
+
|
|
97
|
+
## 6.4 异常与 Schema 的关系
|
|
98
|
+
|
|
99
|
+
异常处理中间件会自动将异常转换为 Schema 响应:
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
# Service 层抛出异常
|
|
103
|
+
raise NotFoundError("用户不存在")
|
|
104
|
+
|
|
105
|
+
# 中间件捕获并转换为响应
|
|
106
|
+
# HTTP 状态码:404
|
|
107
|
+
# 响应体:
|
|
108
|
+
# {{
|
|
109
|
+
# "code": "NOT_FOUND",
|
|
110
|
+
# "message": "用户不存在",
|
|
111
|
+
# "data": null
|
|
112
|
+
# }}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**最佳实践**:
|
|
116
|
+
- 在 Service 层抛出业务异常,不要在 API 层手动处理
|
|
117
|
+
- 使用框架内置异常或自定义异常,不要直接抛出 `Exception`
|
|
118
|
+
- 自定义异常继承 `BaseError`,框架会自动处理
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# 缓存
|
|
2
|
+
|
|
3
|
+
框架的 `CacheManager` 支持**命名多实例**,可以为不同用途配置不同的缓存后端。
|
|
4
|
+
|
|
5
|
+
## 7.1 基本用法
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
from aury.boot.infrastructure.cache import CacheManager
|
|
9
|
+
|
|
10
|
+
# 默认实例
|
|
11
|
+
cache = CacheManager.get_instance()
|
|
12
|
+
await cache.set("key", value, expire=300) # 5 分钟
|
|
13
|
+
value = await cache.get("key")
|
|
14
|
+
await cache.delete("key")
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## 7.2 多实例使用
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
# 获取不同用途的缓存实例
|
|
21
|
+
session_cache = CacheManager.get_instance("session")
|
|
22
|
+
rate_limit_cache = CacheManager.get_instance("rate_limit")
|
|
23
|
+
|
|
24
|
+
# 分别初始化(可以使用不同的后端或配置)
|
|
25
|
+
await session_cache.initialize(
|
|
26
|
+
backend="redis",
|
|
27
|
+
url="redis://localhost:6379/1", # 使用不同的 Redis DB
|
|
28
|
+
)
|
|
29
|
+
await rate_limit_cache.initialize(
|
|
30
|
+
backend="memory",
|
|
31
|
+
max_size=10000,
|
|
32
|
+
)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## 7.3 缓存装饰器
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
cache = CacheManager.get_instance()
|
|
39
|
+
|
|
40
|
+
@cache.cached(expire=300, key_prefix="user")
|
|
41
|
+
async def get_user(user_id: int):
|
|
42
|
+
# 缓存键自动生成:user:<func_name>:<args_hash>
|
|
43
|
+
return await repo.get(user_id)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## 7.4 支持的后端
|
|
47
|
+
|
|
48
|
+
- `redis` - Redis(推荐生产用)
|
|
49
|
+
- `memory` - 内存缓存(开发/测试用)
|
|
50
|
+
- `memcached` - Memcached
|
|
51
|
+
|
|
52
|
+
## 7.5 API 响应缓存装饰器
|
|
53
|
+
|
|
54
|
+
使用 `@cache.cache_response()` 装饰器自动缓存 API 响应:
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
cache = CacheManager.get_instance()
|
|
58
|
+
|
|
59
|
+
# 基本用法:自动生成缓存键
|
|
60
|
+
@router.get("/{{id}}")
|
|
61
|
+
@cache.cache_response(expire=300)
|
|
62
|
+
async def get_todo(id: UUID, service: TodoService = Depends(get_service)):
|
|
63
|
+
entity = await service.get(id)
|
|
64
|
+
return BaseResponse(code=200, message="获取成功", data=TodoResponse.model_validate(entity))
|
|
65
|
+
|
|
66
|
+
# 自定义缓存键
|
|
67
|
+
@router.get("/{{id}}")
|
|
68
|
+
@cache.cache_response(
|
|
69
|
+
expire=300,
|
|
70
|
+
key_builder=lambda id, **kwargs: f"todo:{{id}}"
|
|
71
|
+
)
|
|
72
|
+
async def get_todo(id: UUID, service: TodoService = Depends(get_service)):
|
|
73
|
+
entity = await service.get(id)
|
|
74
|
+
return BaseResponse(code=200, message="获取成功", data=TodoResponse.model_validate(entity))
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## 7.6 模式删除(delete_pattern)
|
|
78
|
+
|
|
79
|
+
使用通配符批量删除缓存:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
# 删除所有 todo 相关缓存
|
|
83
|
+
await cache.delete_pattern("api:todo:*")
|
|
84
|
+
|
|
85
|
+
# 删除所有列表缓存
|
|
86
|
+
await cache.delete_pattern("api:todo:list:*")
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## 7.7 缓存清理策略
|
|
90
|
+
|
|
91
|
+
数据变更时应及时清理相关缓存:
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
# 更新时清理缓存
|
|
95
|
+
@router.put("/{{id}}")
|
|
96
|
+
async def update_todo(id: UUID, data: TodoUpdate, service: TodoService = Depends(get_service)):
|
|
97
|
+
entity = await service.update(id, data)
|
|
98
|
+
|
|
99
|
+
# 清理单个 + 模式删除
|
|
100
|
+
await cache.delete(f"api:todo:{{id}}")
|
|
101
|
+
await cache.delete_pattern("api:todo:list:*") # 清理所有列表缓存
|
|
102
|
+
|
|
103
|
+
return BaseResponse(code=200, message="更新成功", data=TodoResponse.model_validate(entity))
|
|
104
|
+
|
|
105
|
+
# 删除时清理缓存
|
|
106
|
+
@router.delete("/{{id}}")
|
|
107
|
+
async def delete_todo(id: UUID, service: TodoService = Depends(get_service)):
|
|
108
|
+
await service.delete(id)
|
|
109
|
+
await cache.delete(f"api:todo:{{id}}")
|
|
110
|
+
await cache.delete_pattern("api:todo:*") # 清理所有相关缓存
|
|
111
|
+
return BaseResponse(code=200, message="删除成功", data=None)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 7.7.1 缓存键命名规范
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
# 推荐格式:{{layer}}:{{resource}}:{{identifier}}
|
|
118
|
+
f"api:todo:{{id}}" # 单个资源
|
|
119
|
+
f"api:todo:list:{{page}}" # 列表
|
|
120
|
+
f"api:todo:statistics" # 统计
|
|
121
|
+
f"service:user:{{user_id}}" # Service 层缓存
|
|
122
|
+
```
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# 定时任务(Scheduler)
|
|
2
|
+
|
|
3
|
+
**文件**: `{package_name}/schedules/__init__.py`
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
"""定时任务模块。"""
|
|
7
|
+
|
|
8
|
+
from aury.boot.common.logging import logger
|
|
9
|
+
from aury.boot.infrastructure.scheduler import SchedulerManager
|
|
10
|
+
|
|
11
|
+
scheduler = SchedulerManager.get_instance()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@scheduler.scheduled_job("interval", seconds=60)
|
|
15
|
+
async def every_minute():
|
|
16
|
+
"""每 60 秒执行。"""
|
|
17
|
+
logger.info("定时任务执行中...")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@scheduler.scheduled_job("cron", hour=0, minute=0)
|
|
21
|
+
async def daily_task():
|
|
22
|
+
"""每天凌晨执行。"""
|
|
23
|
+
logger.info("每日任务执行中...")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@scheduler.scheduled_job("cron", day_of_week="mon", hour=9)
|
|
27
|
+
async def weekly_report():
|
|
28
|
+
"""每周一 9 点执行。"""
|
|
29
|
+
logger.info("周报任务执行中...")
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
启用方式:配置 `SCHEDULER_ENABLED=true`,框架自动加载 `{package_name}/schedules/` 模块。
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# 异步任务(Dramatiq)
|
|
2
|
+
|
|
3
|
+
**文件**: `{package_name}/tasks/__init__.py`
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
"""异步任务模块。"""
|
|
7
|
+
|
|
8
|
+
from aury.boot.common.logging import logger
|
|
9
|
+
from aury.boot.infrastructure.tasks import conditional_task
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@conditional_task()
|
|
13
|
+
def send_email(to: str, subject: str, body: str):
|
|
14
|
+
"""异步发送邮件。"""
|
|
15
|
+
logger.info(f"发送邮件到 {{to}}: {{subject}}")
|
|
16
|
+
# 实际发送逻辑...
|
|
17
|
+
return {{"status": "sent"}}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@conditional_task()
|
|
21
|
+
def process_order(order_id: str):
|
|
22
|
+
"""异步处理订单。"""
|
|
23
|
+
logger.info(f"处理订单: {{order_id}}")
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
调用方式:
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
# 异步执行(发送到队列)
|
|
30
|
+
send_email.send("user@example.com", "Hello", "World")
|
|
31
|
+
|
|
32
|
+
# 延迟执行
|
|
33
|
+
send_email.send_with_options(args=("user@example.com", "Hello", "World"), delay=60000) # 60秒后
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
启用方式:
|
|
37
|
+
1. 配置 `TASK_BROKER_URL`(如 `redis://localhost:6379/0`)
|
|
38
|
+
2. 运行 Worker:`aury worker`
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# 对象存储(基于 aury-sdk-storage)
|
|
2
|
+
|
|
3
|
+
## 10.1 安装
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
# 完整安装(S3/COS/OSS + STS 支持)
|
|
7
|
+
uv add "aury-sdk-storage[aws]"
|
|
8
|
+
# 或
|
|
9
|
+
pip install "aury-sdk-storage[aws]"
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## 10.2 基本用法(StorageManager)
|
|
13
|
+
|
|
14
|
+
`StorageManager` 支持**命名多实例**,可以同时管理多个存储后端。
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
from aury.boot.infrastructure.storage import (
|
|
18
|
+
StorageManager, StorageConfig, StorageBackend, StorageFile,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# 默认实例
|
|
22
|
+
storage = StorageManager.get_instance()
|
|
23
|
+
await storage.initialize(StorageConfig(
|
|
24
|
+
backend=StorageBackend.COS,
|
|
25
|
+
bucket_name="my-bucket-1250000000",
|
|
26
|
+
region="ap-guangzhou",
|
|
27
|
+
endpoint="https://cos.ap-guangzhou.myqcloud.com",
|
|
28
|
+
access_key_id="AKIDxxxxx",
|
|
29
|
+
access_key_secret="xxxxx",
|
|
30
|
+
))
|
|
31
|
+
|
|
32
|
+
# 多实例示例:源存储和目标存储
|
|
33
|
+
source = StorageManager.get_instance("source")
|
|
34
|
+
target = StorageManager.get_instance("target")
|
|
35
|
+
await source.initialize(StorageConfig(backend=StorageBackend.COS, ...))
|
|
36
|
+
await target.initialize(StorageConfig(backend=StorageBackend.S3, ...))
|
|
37
|
+
|
|
38
|
+
# 上传文件(返回 URL)
|
|
39
|
+
# data 支持: bytes / BytesIO / BinaryIO
|
|
40
|
+
url = await storage.upload_file(
|
|
41
|
+
StorageFile(
|
|
42
|
+
object_name="user/123/avatar.png",
|
|
43
|
+
data=image_bytes,
|
|
44
|
+
content_type="image/png",
|
|
45
|
+
)
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# 下载文件
|
|
49
|
+
content = await storage.download_file("user/123/avatar.png")
|
|
50
|
+
|
|
51
|
+
# 获取预签名 URL
|
|
52
|
+
url = await storage.get_file_url("user/123/avatar.png", expires_in=3600)
|
|
53
|
+
|
|
54
|
+
# 检查文件是否存在
|
|
55
|
+
exists = await storage.file_exists("user/123/avatar.png")
|
|
56
|
+
|
|
57
|
+
# 删除文件
|
|
58
|
+
await storage.delete_file("user/123/avatar.png")
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## 10.3 STS 临时凭证(前端直传)
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from aury.sdk.storage.sts import (
|
|
65
|
+
STSProviderFactory, ProviderType, STSRequest, ActionType,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# 创建腾讯云 STS Provider
|
|
69
|
+
provider = STSProviderFactory.create(
|
|
70
|
+
ProviderType.TENCENT,
|
|
71
|
+
secret_id="AKIDxxxxx",
|
|
72
|
+
secret_key="xxxxx",
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# 签发临时上传凭证
|
|
76
|
+
credentials = await provider.get_credentials(
|
|
77
|
+
STSRequest(
|
|
78
|
+
bucket="my-bucket-1250000000",
|
|
79
|
+
region="ap-guangzhou",
|
|
80
|
+
allow_path="user/123/",
|
|
81
|
+
action_type=ActionType.WRITE,
|
|
82
|
+
duration_seconds=900,
|
|
83
|
+
)
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# 返回给前端
|
|
87
|
+
return {{
|
|
88
|
+
"accessKeyId": credentials.access_key_id,
|
|
89
|
+
"secretAccessKey": credentials.secret_access_key,
|
|
90
|
+
"sessionToken": credentials.session_token,
|
|
91
|
+
"expiration": credentials.expiration.isoformat(),
|
|
92
|
+
"bucket": credentials.bucket,
|
|
93
|
+
"region": credentials.region,
|
|
94
|
+
"endpoint": credentials.endpoint,
|
|
95
|
+
}}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## 10.4 本地存储(开发测试)
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from aury.boot.infrastructure.storage import (
|
|
102
|
+
StorageManager, StorageConfig, StorageBackend, StorageFile,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
storage = StorageManager.get_instance()
|
|
106
|
+
await storage.initialize(StorageConfig(
|
|
107
|
+
backend=StorageBackend.LOCAL,
|
|
108
|
+
base_path="./dev_storage",
|
|
109
|
+
))
|
|
110
|
+
|
|
111
|
+
url = await storage.upload_file(
|
|
112
|
+
StorageFile(object_name="test.txt", data=b"hello")
|
|
113
|
+
)
|
|
114
|
+
# url: file:///path/to/dev_storage/default/test.txt
|
|
115
|
+
```
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# 日志
|
|
2
|
+
|
|
3
|
+
基于 loguru 的日志系统,trace_id 自动注入每条日志,无需手动记录。
|
|
4
|
+
|
|
5
|
+
## 11.1 基本用法
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
from aury.boot.common.logging import logger
|
|
9
|
+
|
|
10
|
+
# trace_id 自动包含在日志格式中,无需手动记录
|
|
11
|
+
logger.info("操作成功")
|
|
12
|
+
logger.warning("警告信息")
|
|
13
|
+
logger.error("错误信息")
|
|
14
|
+
logger.exception("异常信息") # 自动记录堆栈
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
输出示例:
|
|
18
|
+
```
|
|
19
|
+
2024-01-15 12:00:00 | INFO | app.service:create:42 | abc123 - 操作成功
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## 11.2 注入用户信息
|
|
23
|
+
|
|
24
|
+
框架不内置用户系统,但支持注入自定义请求上下文:
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
# app/auth/context.py
|
|
28
|
+
from contextvars import ContextVar
|
|
29
|
+
from aury.boot.common.logging import register_request_context
|
|
30
|
+
|
|
31
|
+
_user_id: ContextVar[str] = ContextVar("user_id", default="")
|
|
32
|
+
|
|
33
|
+
def set_user_id(uid: str) -> None:
|
|
34
|
+
_user_id.set(uid)
|
|
35
|
+
|
|
36
|
+
# 启动时注册(只需一次)
|
|
37
|
+
register_request_context("user_id", _user_id.get)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
在认证中间件中设置(order < 100 以在日志中间件前执行):
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
class AuthMiddleware(Middleware):
|
|
44
|
+
order = 50 # 在日志中间件(order=100)之前执行
|
|
45
|
+
|
|
46
|
+
async def dispatch(self, request, call_next):
|
|
47
|
+
user = await verify_token(request)
|
|
48
|
+
if user:
|
|
49
|
+
set_user_id(str(user.id))
|
|
50
|
+
return await call_next(request)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
结果:
|
|
54
|
+
```
|
|
55
|
+
← GET /api/users | 状态: 200 | 耗时: 0.05s | Trace-ID: abc123
|
|
56
|
+
[REQUEST_CONTEXT] Trace-ID: abc123 | user_id: 123
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## 11.3 性能监控装饰器
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from aury.boot.common.logging import log_performance, log_exceptions
|
|
63
|
+
|
|
64
|
+
@log_performance(threshold=0.5) # 超过 0.5 秒记录警告
|
|
65
|
+
async def slow_operation():
|
|
66
|
+
...
|
|
67
|
+
|
|
68
|
+
@log_exceptions # 自动记录异常
|
|
69
|
+
async def risky_operation():
|
|
70
|
+
...
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## 11.4 HTTP 请求日志
|
|
74
|
+
|
|
75
|
+
框架内置 `RequestLoggingMiddleware` 自动记录:
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
# 请求日志(包含查询参数和请求体)
|
|
79
|
+
→ POST /api/users | 参数: {{'page': '1'}} | Body: {{"name": "test"}} | Trace-ID: abc123
|
|
80
|
+
|
|
81
|
+
# 响应日志(包含状态码和耗时)
|
|
82
|
+
← POST /api/users | 状态: 201 | 耗时: 0.123s | Trace-ID: abc123
|
|
83
|
+
|
|
84
|
+
# 慢请求警告(超过 1 秒)
|
|
85
|
+
慢请求: GET /api/reports | 耗时: 2.345s (超过1秒) | Trace-ID: abc123
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## 11.5 自定义日志文件
|
|
89
|
+
|
|
90
|
+
为特定业务创建独立的日志文件:
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from aury.boot.common.logging import register_log_sink, logger
|
|
94
|
+
|
|
95
|
+
# 启动时注册(生成 payment_2024-01-15.log)
|
|
96
|
+
register_log_sink("payment", filter_key="payment")
|
|
97
|
+
|
|
98
|
+
# 业务代码中使用
|
|
99
|
+
logger.bind(payment=True).info(f"支付成功 | 订单: {{order_id}}")
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## 11.6 异步任务链路追踪
|
|
103
|
+
|
|
104
|
+
跨进程任务需要手动传递 trace_id:
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from aury.boot.common.logging import get_trace_id, set_trace_id
|
|
108
|
+
|
|
109
|
+
# 发送任务时传递
|
|
110
|
+
process_order.send(order_id="123", trace_id=get_trace_id())
|
|
111
|
+
|
|
112
|
+
# 任务执行时恢复
|
|
113
|
+
@tm.conditional_task()
|
|
114
|
+
async def process_order(order_id: str, trace_id: str | None = None):
|
|
115
|
+
if trace_id:
|
|
116
|
+
set_trace_id(trace_id)
|
|
117
|
+
logger.info(f"处理订单: {{order_id}}") # 自动包含 trace_id
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## 11.7 服务上下文隔离
|
|
121
|
+
|
|
122
|
+
日志自动按服务类型分离:
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
logs/
|
|
126
|
+
├── api_info_2024-01-15.log # API 服务日志
|
|
127
|
+
├── api_error_2024-01-15.log
|
|
128
|
+
├── scheduler_info_2024-01-15.log # 调度器日志
|
|
129
|
+
├── worker_info_2024-01-15.log # Worker 日志
|
|
130
|
+
└── access_2024-01-15.log # HTTP 访问日志
|
|
131
|
+
```
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# 管理后台(Admin Console,基于 SQLAdmin)
|
|
2
|
+
|
|
3
|
+
默认提供可选的 SQLAdmin 后台(组件自动装配)。启用后路径默认为 `/api/admin-console`。
|
|
4
|
+
|
|
5
|
+
- 组件开关与配置由环境变量控制;启用后框架会在启动时自动挂载后台路由。
|
|
6
|
+
- SQLAdmin 通常需要同步 SQLAlchemy Engine;如果你使用的是异步 `DATABASE__URL`,建议单独设置同步的 `ADMIN__DATABASE_URL`(框架也会尝试自动推导常见驱动映射)。
|
|
7
|
+
|
|
8
|
+
## 快速启用(.env)
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
# 启用与基本路径
|
|
12
|
+
ADMIN__ENABLED=true
|
|
13
|
+
ADMIN__PATH=/api/admin-console
|
|
14
|
+
|
|
15
|
+
# 认证(二选一,推荐 basic 或 bearer)
|
|
16
|
+
ADMIN__AUTH_MODE=basic
|
|
17
|
+
ADMIN__AUTH_SECRET_KEY=CHANGE_ME_TO_A_RANDOM_SECRET
|
|
18
|
+
ADMIN__AUTH_BASIC_USERNAME=admin
|
|
19
|
+
ADMIN__AUTH_BASIC_PASSWORD=change_me
|
|
20
|
+
|
|
21
|
+
# 如果使用 bearer
|
|
22
|
+
# ADMIN__AUTH_MODE=bearer
|
|
23
|
+
# ADMIN__AUTH_SECRET_KEY=CHANGE_ME
|
|
24
|
+
# ADMIN__AUTH_BEARER_TOKENS=["token1","token2"]
|
|
25
|
+
|
|
26
|
+
# 如需显式提供同步数据库 URL(可选)
|
|
27
|
+
# ADMIN__DATABASE_URL=postgresql+psycopg://user:pass@localhost:5432/{project_name}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## 注册后台视图
|
|
31
|
+
|
|
32
|
+
**文件**: `admin_console.py`
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from sqladmin import ModelView
|
|
36
|
+
from {package_name}.models.user import User
|
|
37
|
+
|
|
38
|
+
class UserAdmin(ModelView, model=User):
|
|
39
|
+
column_list = [User.id, User.username, User.email]
|
|
40
|
+
|
|
41
|
+
# 方式一:声明式(简单)
|
|
42
|
+
ADMIN_VIEWS = [UserAdmin]
|
|
43
|
+
|
|
44
|
+
# 方式二:函数注册(更灵活)
|
|
45
|
+
# def register_admin(admin):
|
|
46
|
+
# admin.add_view(UserAdmin)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## 自定义认证(可选,高阶)
|
|
50
|
+
|
|
51
|
+
- 通过 `ADMIN__AUTH_BACKEND=module:attr` 指定自定义 backend;或在 `admin_console.py` 实现 `register_admin_auth(config)` 返回 SQLAdmin 的 `AuthenticationBackend`。
|
|
52
|
+
- 生产环境下必须设置 `ADMIN__AUTH_SECRET_KEY`,不允许 `none` 模式。
|
|
53
|
+
|
|
54
|
+
## 访问
|
|
55
|
+
|
|
56
|
+
启动服务后访问:`http://localhost:8000/api/admin-console`(或你配置的 `ADMIN__PATH`)。
|