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.
- aury/boot/_version.py +2 -2
- aury/boot/application/__init__.py +15 -0
- 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/middlewares.py +7 -4
- aury/boot/application/config/multi_instance.py +42 -26
- aury/boot/application/config/settings.py +111 -191
- aury/boot/application/middleware/logging.py +14 -1
- aury/boot/commands/generate.py +22 -22
- aury/boot/commands/init.py +41 -9
- aury/boot/commands/templates/project/AGENTS.md.tpl +8 -4
- aury/boot/commands/templates/project/aury_docs/01-model.md.tpl +17 -16
- aury/boot/commands/templates/project/aury_docs/11-logging.md.tpl +82 -43
- aury/boot/commands/templates/project/aury_docs/12-admin.md.tpl +14 -14
- aury/boot/commands/templates/project/aury_docs/13-channel.md.tpl +40 -28
- aury/boot/commands/templates/project/aury_docs/14-mq.md.tpl +9 -9
- aury/boot/commands/templates/project/aury_docs/15-events.md.tpl +8 -8
- aury/boot/commands/templates/project/aury_docs/16-adapter.md.tpl +403 -0
- aury/boot/commands/templates/project/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/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/infrastructure/database/config.py +6 -14
- aury/boot/infrastructure/tasks/config.py +5 -13
- aury/boot/infrastructure/tasks/manager.py +8 -4
- aury/boot/testing/base.py +2 -2
- {aury_boot-0.0.5.dist-info → aury_boot-0.0.7.dist-info}/METADATA +2 -1
- {aury_boot-0.0.5.dist-info → aury_boot-0.0.7.dist-info}/RECORD +48 -27
- aury/boot/commands/templates/project/env.example.tpl +0 -281
- {aury_boot-0.0.5.dist-info → aury_boot-0.0.7.dist-info}/WHEEL +0 -0
- {aury_boot-0.0.5.dist-info → aury_boot-0.0.7.dist-info}/entry_points.txt +0 -0
aury/boot/commands/generate.py
CHANGED
|
@@ -183,7 +183,7 @@ MODEL_BASE_CLASSES = {
|
|
|
183
183
|
"has_timestamps": True,
|
|
184
184
|
},
|
|
185
185
|
"AuditableStateModel": {
|
|
186
|
-
"desc": "
|
|
186
|
+
"desc": "int 主键 + 软删除(推荐)",
|
|
187
187
|
"features": ["id: int", "created_at", "updated_at", "deleted_at"],
|
|
188
188
|
"id_type": "int",
|
|
189
189
|
"has_timestamps": True,
|
|
@@ -195,7 +195,7 @@ MODEL_BASE_CLASSES = {
|
|
|
195
195
|
"has_timestamps": True,
|
|
196
196
|
},
|
|
197
197
|
"UUIDAuditableStateModel": {
|
|
198
|
-
"desc": "UUID 主键 +
|
|
198
|
+
"desc": "UUID 主键 + 软删除",
|
|
199
199
|
"features": ["id: UUID", "created_at", "updated_at", "deleted_at"],
|
|
200
200
|
"id_type": "uuid",
|
|
201
201
|
"has_timestamps": True,
|
|
@@ -219,13 +219,13 @@ MODEL_BASE_CLASSES = {
|
|
|
219
219
|
"has_timestamps": True,
|
|
220
220
|
},
|
|
221
221
|
"FullFeaturedModel": {
|
|
222
|
-
"desc": "
|
|
222
|
+
"desc": "int 主键 + 全功能",
|
|
223
223
|
"features": ["id: int", "created_at", "updated_at", "deleted_at", "version"],
|
|
224
224
|
"id_type": "int",
|
|
225
225
|
"has_timestamps": True,
|
|
226
226
|
},
|
|
227
227
|
"FullFeaturedUUIDModel": {
|
|
228
|
-
"desc": "
|
|
228
|
+
"desc": "UUID 主键 + 全功能",
|
|
229
229
|
"features": ["id: UUID", "created_at", "updated_at", "deleted_at", "version"],
|
|
230
230
|
"id_type": "uuid",
|
|
231
231
|
"has_timestamps": True,
|
|
@@ -250,9 +250,9 @@ class ModelDefinition:
|
|
|
250
250
|
def id_type(self) -> str:
|
|
251
251
|
"""获取 id 类型:'int' 或 'uuid'。"""
|
|
252
252
|
if self.base_class:
|
|
253
|
-
return MODEL_BASE_CLASSES.get(self.base_class, {}).get("id_type", "
|
|
254
|
-
#
|
|
255
|
-
return "
|
|
253
|
+
return MODEL_BASE_CLASSES.get(self.base_class, {}).get("id_type", "int")
|
|
254
|
+
# 默认使用 int 主键
|
|
255
|
+
return "int"
|
|
256
256
|
|
|
257
257
|
@property
|
|
258
258
|
def id_py_type(self) -> str:
|
|
@@ -373,14 +373,14 @@ def _collect_base_class_interactive() -> str:
|
|
|
373
373
|
info = MODEL_BASE_CLASSES[name]
|
|
374
374
|
# 推荐的加标记
|
|
375
375
|
desc = info["desc"]
|
|
376
|
-
if name == "
|
|
376
|
+
if name == "AuditableStateModel":
|
|
377
377
|
desc = f"[bold green]★ {desc}[/bold green]"
|
|
378
378
|
table.add_row(str(i), name, desc, ", ".join(info["features"]))
|
|
379
379
|
|
|
380
380
|
console.print(table)
|
|
381
381
|
console.print()
|
|
382
382
|
|
|
383
|
-
# 默认选择
|
|
383
|
+
# 默认选择 AuditableStateModel(第 4 个)
|
|
384
384
|
choice = Prompt.ask(
|
|
385
385
|
"请选择基类序号",
|
|
386
386
|
default="4",
|
|
@@ -556,16 +556,16 @@ def _generate_model_content(model: ModelDefinition) -> str:
|
|
|
556
556
|
features = base_info.get("features", [])
|
|
557
557
|
base_doc = f"继承 {base_class} 自动获得:\n - " + "\n - ".join(features) if features else f"继承 {base_class} 基类。"
|
|
558
558
|
elif model.soft_delete and model.timestamps:
|
|
559
|
-
base_class = "
|
|
560
|
-
base_doc = """继承
|
|
561
|
-
- id:
|
|
559
|
+
base_class = "AuditableStateModel"
|
|
560
|
+
base_doc = """继承 AuditableStateModel 自动获得:
|
|
561
|
+
- id: int 自增主键
|
|
562
562
|
- created_at: 创建时间
|
|
563
563
|
- updated_at: 更新时间
|
|
564
564
|
- deleted_at: 软删除时间戳"""
|
|
565
565
|
elif model.timestamps:
|
|
566
|
-
base_class = "
|
|
567
|
-
base_doc = """继承
|
|
568
|
-
- id:
|
|
566
|
+
base_class = "Model"
|
|
567
|
+
base_doc = """继承 Model 自动获得:
|
|
568
|
+
- id: int 自增主键
|
|
569
569
|
- created_at: 创建时间
|
|
570
570
|
- updated_at: 更新时间"""
|
|
571
571
|
else:
|
|
@@ -773,7 +773,7 @@ def generate_model(
|
|
|
773
773
|
),
|
|
774
774
|
base: str | None = typer.Option(
|
|
775
775
|
None, "--base", "-b",
|
|
776
|
-
help="模型基类(Model/
|
|
776
|
+
help="模型基类(AuditableStateModel/Model/FullFeaturedModel 等)"
|
|
777
777
|
),
|
|
778
778
|
force: bool = typer.Option(False, "--force", "-f", help="强制覆盖"),
|
|
779
779
|
no_soft_delete: bool = typer.Option(False, "--no-soft-delete", help="禁用软删除"),
|
|
@@ -803,9 +803,9 @@ def generate_model(
|
|
|
803
803
|
(length) - 字符串长度,如 str(100)
|
|
804
804
|
|
|
805
805
|
可用基类:
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
806
|
+
AuditableStateModel, Model, FullFeaturedModel,
|
|
807
|
+
UUIDModel, UUIDAuditableStateModel, FullFeaturedUUIDModel,
|
|
808
|
+
VersionedModel, VersionedTimestampedModel, VersionedUUIDModel
|
|
809
809
|
|
|
810
810
|
示例:
|
|
811
811
|
aury generate model user
|
|
@@ -1104,7 +1104,7 @@ def generate_crud(
|
|
|
1104
1104
|
),
|
|
1105
1105
|
base: str | None = typer.Option(
|
|
1106
1106
|
None, "--base", "-b",
|
|
1107
|
-
help="模型基类(Model/
|
|
1107
|
+
help="模型基类(AuditableStateModel/Model/FullFeaturedModel 等)"
|
|
1108
1108
|
),
|
|
1109
1109
|
force: bool = typer.Option(False, "--force", "-f", help="强制覆盖"),
|
|
1110
1110
|
no_soft_delete: bool = typer.Option(False, "--no-soft-delete", help="禁用软删除"),
|
|
@@ -1122,8 +1122,8 @@ def generate_crud(
|
|
|
1122
1122
|
|
|
1123
1123
|
示例:
|
|
1124
1124
|
aury generate crud user
|
|
1125
|
-
aury generate crud user --base
|
|
1126
|
-
aury generate crud user --base
|
|
1125
|
+
aury generate crud user --base AuditableStateModel # int 主键 + 软删除(推荐)
|
|
1126
|
+
aury generate crud user --base Model # int 主键 + 时间戳
|
|
1127
1127
|
aury generate crud user email:str:unique age:int? --force
|
|
1128
1128
|
aury generate crud article title:str(200) content:text status:str=draft
|
|
1129
1129
|
"""
|
aury/boot/commands/init.py
CHANGED
|
@@ -153,7 +153,7 @@ dev = [
|
|
|
153
153
|
TEMPLATE_FILE_MAP = {
|
|
154
154
|
"main.py": "main.py.tpl",
|
|
155
155
|
"config.py": "config.py.tpl",
|
|
156
|
-
".env.example": "
|
|
156
|
+
".env.example": "env_templates", # 特殊处理:拼接 env_templates/ 目录下的所有 .tpl 文件
|
|
157
157
|
".gitignore": "gitignore.tpl",
|
|
158
158
|
"README.md": "README.md.tpl",
|
|
159
159
|
"AGENTS.md": "AGENTS.md.tpl",
|
|
@@ -161,6 +161,21 @@ TEMPLATE_FILE_MAP = {
|
|
|
161
161
|
"admin_console/__init__.py": "admin_console_init.py.tpl",
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
+
# env 模板拼接顺序
|
|
165
|
+
ENV_TEMPLATE_ORDER = [
|
|
166
|
+
"_header.tpl",
|
|
167
|
+
"service.tpl",
|
|
168
|
+
"database.tpl",
|
|
169
|
+
"cache.tpl",
|
|
170
|
+
"log.tpl",
|
|
171
|
+
"admin.tpl",
|
|
172
|
+
"scheduler.tpl",
|
|
173
|
+
"messaging.tpl",
|
|
174
|
+
"storage.tpl",
|
|
175
|
+
"third_party.tpl",
|
|
176
|
+
"rpc.tpl",
|
|
177
|
+
]
|
|
178
|
+
|
|
164
179
|
# 模块 __init__.py 模板映射
|
|
165
180
|
MODULE_TEMPLATE_MAP = {
|
|
166
181
|
"api": "api.py.tpl",
|
|
@@ -170,8 +185,25 @@ MODULE_TEMPLATE_MAP = {
|
|
|
170
185
|
}
|
|
171
186
|
|
|
172
187
|
|
|
188
|
+
def _read_env_template() -> str:
|
|
189
|
+
"""读取并拼接 env_templates/ 目录下的所有模板文件。"""
|
|
190
|
+
env_dir = TEMPLATES_DIR / "env_templates"
|
|
191
|
+
parts = []
|
|
192
|
+
|
|
193
|
+
for tpl_name in ENV_TEMPLATE_ORDER:
|
|
194
|
+
tpl_path = env_dir / tpl_name
|
|
195
|
+
if tpl_path.exists():
|
|
196
|
+
parts.append(tpl_path.read_text(encoding="utf-8"))
|
|
197
|
+
|
|
198
|
+
return "\n".join(parts)
|
|
199
|
+
|
|
200
|
+
|
|
173
201
|
def _read_template(name: str) -> str:
|
|
174
202
|
"""读取模板文件。"""
|
|
203
|
+
# 特殊处理 .env.example:拼接 env_templates/ 目录
|
|
204
|
+
if name == ".env.example":
|
|
205
|
+
return _read_env_template()
|
|
206
|
+
|
|
175
207
|
# 先尝试从映射中查找 .tpl 文件
|
|
176
208
|
tpl_name = TEMPLATE_FILE_MAP.get(name)
|
|
177
209
|
if tpl_name:
|
|
@@ -244,22 +276,22 @@ def init_admin_console_module(
|
|
|
244
276
|
dest.write_text(content, encoding="utf-8")
|
|
245
277
|
result["file_created"] = True
|
|
246
278
|
|
|
247
|
-
# 2) 尝试在 .env.example 中开启
|
|
279
|
+
# 2) 尝试在 .env.example 中开启 ADMIN__* 配置
|
|
248
280
|
if enable_env:
|
|
249
281
|
env_example = base_path / ".env.example"
|
|
250
282
|
if env_example.exists():
|
|
251
283
|
try:
|
|
252
284
|
s = env_example.read_text(encoding="utf-8")
|
|
253
285
|
s2 = (
|
|
254
|
-
s.replace("#
|
|
255
|
-
.replace("#
|
|
256
|
-
.replace("#
|
|
286
|
+
s.replace("# ADMIN__ENABLED=false", "ADMIN__ENABLED=true")
|
|
287
|
+
.replace("# ADMIN__PATH=/api/admin-console", "ADMIN__PATH=/api/admin-console")
|
|
288
|
+
.replace("# ADMIN__AUTH__MODE=basic", "ADMIN__AUTH__MODE=basic")
|
|
257
289
|
.replace(
|
|
258
|
-
"#
|
|
259
|
-
"
|
|
290
|
+
"# ADMIN__AUTH__SECRET_KEY=CHANGE_ME_TO_A_RANDOM_SECRET",
|
|
291
|
+
"ADMIN__AUTH__SECRET_KEY=CHANGE_ME_TO_A_RANDOM_SECRET",
|
|
260
292
|
)
|
|
261
|
-
.replace("#
|
|
262
|
-
.replace("#
|
|
293
|
+
.replace("# ADMIN__AUTH__BASIC_USERNAME=admin", "ADMIN__AUTH__BASIC_USERNAME=admin")
|
|
294
|
+
.replace("# ADMIN__AUTH__BASIC_PASSWORD=change_me", "ADMIN__AUTH__BASIC_PASSWORD=change_me")
|
|
263
295
|
)
|
|
264
296
|
if s2 != s:
|
|
265
297
|
env_example.write_text(s2, encoding="utf-8")
|
|
@@ -68,7 +68,7 @@ mypy {package_name}/
|
|
|
68
68
|
开发 CRUD 功能时,按顺序阅读以下文档:
|
|
69
69
|
|
|
70
70
|
1. **[aury_docs/01-model.md](./aury_docs/01-model.md)** - Model 定义规范
|
|
71
|
-
- 基类选择(
|
|
71
|
+
- 基类选择(AuditableStateModel 等)
|
|
72
72
|
- 字段类型映射
|
|
73
73
|
- 约束定义(软删除模型的复合唯一约束)
|
|
74
74
|
|
|
@@ -110,6 +110,10 @@ mypy {package_name}/
|
|
|
110
110
|
- **[aury_docs/14-mq.md](./aury_docs/14-mq.md)** - 消息队列
|
|
111
111
|
- **[aury_docs/15-events.md](./aury_docs/15-events.md)** - 事件总线
|
|
112
112
|
|
|
113
|
+
### 第三方集成
|
|
114
|
+
|
|
115
|
+
- **[aury_docs/16-adapter.md](./aury_docs/16-adapter.md)** - 第三方接口适配器(Mock/真实切换)
|
|
116
|
+
|
|
113
117
|
### 配置 / CLI / 环境变量
|
|
114
118
|
|
|
115
119
|
- **[aury_docs/00-overview.md](./aury_docs/00-overview.md)** - 项目概览与最佳实践
|
|
@@ -121,15 +125,15 @@ mypy {package_name}/
|
|
|
121
125
|
### Model 规范
|
|
122
126
|
|
|
123
127
|
- **必须**继承框架预定义基类,**不要**直接继承 `Base`
|
|
124
|
-
- **推荐**使用 `
|
|
128
|
+
- **推荐**使用 `AuditableStateModel`(int 主键 + 时间戳 + 软删除)
|
|
125
129
|
- 软删除模型**必须**使用复合唯一约束(包含 `deleted_at`),不能单独使用 `unique=True`
|
|
126
130
|
- **不建议**使用数据库外键(`ForeignKey`),通过程序控制关系,便于分库分表和微服务拆分
|
|
127
131
|
|
|
128
132
|
```python
|
|
129
133
|
# ✅ 正确
|
|
130
|
-
from aury.boot.domain.models import
|
|
134
|
+
from aury.boot.domain.models import AuditableStateModel
|
|
131
135
|
|
|
132
|
-
class User(
|
|
136
|
+
class User(AuditableStateModel):
|
|
133
137
|
__tablename__ = "users"
|
|
134
138
|
email: Mapped[str] = mapped_column(String(255), index=True)
|
|
135
139
|
__table_args__ = (
|
|
@@ -5,14 +5,15 @@
|
|
|
5
5
|
框架提供多种预组合基类,按需选择:
|
|
6
6
|
|
|
7
7
|
| 基类 | 主键 | 时间戳 | 软删除 | 乐观锁 | 场景 |
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
8
|
+
||------|------|--------|--------|--------|------|
|
|
9
|
+
|| `Model` | int | ✓ | ✗ | ✗ | 简单实体 |
|
|
10
|
+
|| `AuditableStateModel` | int | ✓ | ✓ | ✗ | **推荐** |
|
|
11
|
+
|| `FullFeaturedModel` | int | ✓ | ✓ | ✓ | 全功能 |
|
|
12
|
+
|| `UUIDModel` | UUID | ✓ | ✗ | ✗ | UUID主键 |
|
|
13
|
+
|| `UUIDAuditableStateModel` | UUID | ✓ | ✓ | ✗ | UUID+软删除 |
|
|
14
|
+
|| `VersionedModel` | int | ✗ | ✗ | ✓ | 乐观锁 |
|
|
15
|
+
|| `VersionedUUIDModel` | UUID | ✓ | ✗ | ✓ | UUID+乐观锁 |
|
|
16
|
+
|| `FullFeaturedUUIDModel` | UUID | ✓ | ✓ | ✓ | UUID全功能 |
|
|
16
17
|
|
|
17
18
|
## 1.2 基类自动提供的字段
|
|
18
19
|
|
|
@@ -70,14 +71,14 @@ version: Mapped[int] # 版本号,自动管理
|
|
|
70
71
|
from sqlalchemy import String, Boolean, UniqueConstraint
|
|
71
72
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
72
73
|
|
|
73
|
-
from aury.boot.domain.models import
|
|
74
|
+
from aury.boot.domain.models import AuditableStateModel
|
|
74
75
|
|
|
75
76
|
|
|
76
|
-
class User(
|
|
77
|
+
class User(AuditableStateModel):
|
|
77
78
|
"""User 模型。
|
|
78
79
|
|
|
79
|
-
继承
|
|
80
|
-
- id:
|
|
80
|
+
继承 AuditableStateModel 自动获得:
|
|
81
|
+
- id: int 自增主键
|
|
81
82
|
- created_at, updated_at: 时间戳
|
|
82
83
|
- deleted_at: 软删除支持
|
|
83
84
|
"""
|
|
@@ -116,7 +117,7 @@ from sqlalchemy import String, Integer, Index, UniqueConstraint
|
|
|
116
117
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
117
118
|
import uuid
|
|
118
119
|
|
|
119
|
-
class Example(
|
|
120
|
+
class Example(AuditableStateModel):
|
|
120
121
|
__tablename__ = "examples"
|
|
121
122
|
|
|
122
123
|
# 可选字段
|
|
@@ -132,7 +133,7 @@ class Example(UUIDAuditableStateModel):
|
|
|
132
133
|
# 注意:软删除模型不能单独使用 unique=True,必须使用复合唯一约束
|
|
133
134
|
|
|
134
135
|
# 关联字段(不使用数据库外键,通过程序控制关系)
|
|
135
|
-
category_id: Mapped[
|
|
136
|
+
category_id: Mapped[int | None] = mapped_column(index=True)
|
|
136
137
|
|
|
137
138
|
# 复合索引和复合唯一约束:必须使用 __table_args__(SQLAlchemy 要求)
|
|
138
139
|
# 软删除模型必须使用复合唯一约束(包含 deleted_at),避免删除后无法插入相同值
|
|
@@ -143,7 +144,7 @@ class Example(UUIDAuditableStateModel):
|
|
|
143
144
|
|
|
144
145
|
|
|
145
146
|
# 非软删除模型可以直接使用 unique=True 和 index=True
|
|
146
|
-
class Config(
|
|
147
|
+
class Config(Model): # Model 不包含软删除
|
|
147
148
|
__tablename__ = "configs"
|
|
148
149
|
|
|
149
150
|
# 单列唯一约束:直接在 mapped_column 中使用(推荐)
|
|
@@ -179,5 +180,5 @@ class Config(UUIDModel): # UUIDModel 不包含软删除
|
|
|
179
180
|
- 简化数据迁移
|
|
180
181
|
```python
|
|
181
182
|
# 只存储关联 ID,不使用 ForeignKey
|
|
182
|
-
category_id: Mapped[
|
|
183
|
+
category_id: Mapped[int | None] = mapped_column(index=True)
|
|
183
184
|
```
|
|
@@ -1,30 +1,59 @@
|
|
|
1
1
|
# 日志
|
|
2
2
|
|
|
3
|
-
基于 loguru
|
|
3
|
+
基于 loguru 的日志系统,trace_id 自动注入每条日志,无需手动记录。
|
|
4
4
|
|
|
5
5
|
## 11.1 基本用法
|
|
6
6
|
|
|
7
7
|
```python
|
|
8
8
|
from aury.boot.common.logging import logger
|
|
9
9
|
|
|
10
|
+
# trace_id 自动包含在日志格式中,无需手动记录
|
|
10
11
|
logger.info("操作成功")
|
|
11
12
|
logger.warning("警告信息")
|
|
12
|
-
logger.error("错误信息"
|
|
13
|
+
logger.error("错误信息")
|
|
14
|
+
logger.exception("异常信息") # 自动记录堆栈
|
|
15
|
+
```
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
输出示例:
|
|
18
|
+
```
|
|
19
|
+
2024-01-15 12:00:00 | INFO | app.service:create:42 | abc123 - 操作成功
|
|
16
20
|
```
|
|
17
21
|
|
|
18
|
-
## 11.2
|
|
22
|
+
## 11.2 注入用户信息
|
|
23
|
+
|
|
24
|
+
框架不内置用户系统,但支持注入自定义请求上下文:
|
|
19
25
|
|
|
20
26
|
```python
|
|
21
|
-
|
|
27
|
+
# app/auth/context.py
|
|
28
|
+
from contextvars import ContextVar
|
|
29
|
+
from aury.boot.common.logging import register_request_context
|
|
22
30
|
|
|
23
|
-
|
|
24
|
-
trace_id = get_trace_id()
|
|
31
|
+
_user_id: ContextVar[str] = ContextVar("user_id", default="")
|
|
25
32
|
|
|
26
|
-
|
|
27
|
-
|
|
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
|
|
28
57
|
```
|
|
29
58
|
|
|
30
59
|
## 11.3 性能监控装饰器
|
|
@@ -41,52 +70,62 @@ async def risky_operation():
|
|
|
41
70
|
...
|
|
42
71
|
```
|
|
43
72
|
|
|
44
|
-
## 11.4 HTTP
|
|
73
|
+
## 11.4 HTTP 请求日志
|
|
45
74
|
|
|
46
|
-
|
|
47
|
-
from aury.boot.application.middleware.logging import RequestLoggingMiddleware
|
|
48
|
-
|
|
49
|
-
# 在 FoundationApp 中自动启用,也可手动添加
|
|
50
|
-
app.add_middleware(
|
|
51
|
-
RequestLoggingMiddleware,
|
|
52
|
-
log_request_body=True, # 记录请求体(默认 True)
|
|
53
|
-
max_body_length=2000, # 请求体最大记录长度
|
|
54
|
-
sensitive_fields={{"password", "token"}}, # 敏感字段脱敏
|
|
55
|
-
)
|
|
56
|
-
```
|
|
75
|
+
框架内置 `RequestLoggingMiddleware` 自动记录:
|
|
57
76
|
|
|
58
|
-
日志输出示例:
|
|
59
77
|
```
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
62
86
|
```
|
|
63
87
|
|
|
64
|
-
## 11.5
|
|
88
|
+
## 11.5 自定义日志文件
|
|
89
|
+
|
|
90
|
+
为特定业务创建独立的日志文件:
|
|
65
91
|
|
|
66
92
|
```python
|
|
67
|
-
from aury.boot.
|
|
93
|
+
from aury.boot.common.logging import register_log_sink, logger
|
|
68
94
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
log_messages=False, # 是否记录消息内容(默认 False,注意性能和敏感数据)
|
|
72
|
-
max_message_length=500, # 消息内容最大记录长度
|
|
73
|
-
)
|
|
74
|
-
```
|
|
95
|
+
# 启动时注册(生成 payment_2024-01-15.log)
|
|
96
|
+
register_log_sink("payment", filter_key="payment")
|
|
75
97
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
INFO WS → 连接建立: /ws/chat | 客户端: 127.0.0.1:54321 | Trace-ID: abc-123
|
|
79
|
-
INFO WS ← 连接关闭: /ws/chat | 时长: 120.5s | 收/发: 45/32 | Trace-ID: abc-123
|
|
98
|
+
# 业务代码中使用
|
|
99
|
+
logger.bind(payment=True).info(f"支付成功 | 订单: {{order_id}}")
|
|
80
100
|
```
|
|
81
101
|
|
|
82
|
-
## 11.6
|
|
102
|
+
## 11.6 异步任务链路追踪
|
|
103
|
+
|
|
104
|
+
跨进程任务需要手动传递 trace_id:
|
|
83
105
|
|
|
84
106
|
```python
|
|
85
|
-
from aury.boot.common.logging import
|
|
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 服务上下文隔离
|
|
86
121
|
|
|
87
|
-
|
|
88
|
-
register_log_sink("access", filter_key="access")
|
|
122
|
+
日志自动按服务类型分离:
|
|
89
123
|
|
|
90
|
-
|
|
91
|
-
|
|
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 访问日志
|
|
92
131
|
```
|
|
@@ -3,28 +3,28 @@
|
|
|
3
3
|
默认提供可选的 SQLAdmin 后台(组件自动装配)。启用后路径默认为 `/api/admin-console`。
|
|
4
4
|
|
|
5
5
|
- 组件开关与配置由环境变量控制;启用后框架会在启动时自动挂载后台路由。
|
|
6
|
-
- SQLAdmin 通常需要同步 SQLAlchemy Engine;如果你使用的是异步 `
|
|
6
|
+
- SQLAdmin 通常需要同步 SQLAlchemy Engine;如果你使用的是异步 `DATABASE__URL`,建议单独设置同步的 `ADMIN__DATABASE_URL`(框架也会尝试自动推导常见驱动映射)。
|
|
7
7
|
|
|
8
8
|
## 快速启用(.env)
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
11
|
# 启用与基本路径
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
ADMIN__ENABLED=true
|
|
13
|
+
ADMIN__PATH=/api/admin-console
|
|
14
14
|
|
|
15
15
|
# 认证(二选一,推荐 basic 或 bearer)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
20
|
|
|
21
21
|
# 如果使用 bearer
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
22
|
+
# ADMIN__AUTH_MODE=bearer
|
|
23
|
+
# ADMIN__AUTH_SECRET_KEY=CHANGE_ME
|
|
24
|
+
# ADMIN__AUTH_BEARER_TOKENS=["token1","token2"]
|
|
25
25
|
|
|
26
26
|
# 如需显式提供同步数据库 URL(可选)
|
|
27
|
-
#
|
|
27
|
+
# ADMIN__DATABASE_URL=postgresql+psycopg://user:pass@localhost:5432/{project_name}
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
## 注册后台视图
|
|
@@ -48,9 +48,9 @@ ADMIN_VIEWS = [UserAdmin]
|
|
|
48
48
|
|
|
49
49
|
## 自定义认证(可选,高阶)
|
|
50
50
|
|
|
51
|
-
- 通过 `
|
|
52
|
-
- 生产环境下必须设置 `
|
|
51
|
+
- 通过 `ADMIN__AUTH_BACKEND=module:attr` 指定自定义 backend;或在 `admin_console.py` 实现 `register_admin_auth(config)` 返回 SQLAdmin 的 `AuthenticationBackend`。
|
|
52
|
+
- 生产环境下必须设置 `ADMIN__AUTH_SECRET_KEY`,不允许 `none` 模式。
|
|
53
53
|
|
|
54
54
|
## 访问
|
|
55
55
|
|
|
56
|
-
启动服务后访问:`http://localhost:8000/api/admin-console`(或你配置的 `
|
|
56
|
+
启动服务后访问:`http://localhost:8000/api/admin-console`(或你配置的 `ADMIN__PATH`)。
|