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
@@ -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": "完整功能 int版",
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": "完整功能 UUID版",
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", "uuid")
254
- # 默认通过 soft_delete/timestamps 推断时使用 UUID
255
- return "uuid"
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 == "UUIDAuditableStateModel":
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
- # 默认选择 UUIDAuditableStateModel(第 4 个)
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 = "UUIDAuditableStateModel"
560
- base_doc = """继承 UUIDAuditableStateModel 自动获得:
561
- - id: UUID 主键
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 = "UUIDModel"
567
- base_doc = """继承 UUIDModel 自动获得:
568
- - id: UUID 主键
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/UUIDModel/UUIDAuditableStateModel/VersionedModel 等)"
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
- Model, AuditableStateModel, UUIDModel, UUIDAuditableStateModel,
807
- VersionedModel, VersionedTimestampedModel, VersionedUUIDModel,
808
- FullFeaturedModel, FullFeaturedUUIDModel
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/UUIDModel/UUIDAuditableStateModel/VersionedModel 等)"
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 Model # 使用 int 主键
1126
- aury generate crud user --base UUIDAuditableStateModel # 使用 UUID 主键(推荐)
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
  """
@@ -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": "env.example.tpl",
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 中开启 ADMIN_* 配置
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("# ADMIN_ENABLED=false", "ADMIN_ENABLED=true")
255
- .replace("# ADMIN_PATH=/api/admin-console", "ADMIN_PATH=/api/admin-console")
256
- .replace("# ADMIN_AUTH_MODE=basic", "ADMIN_AUTH_MODE=basic")
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
- "# ADMIN_AUTH_SECRET_KEY=CHANGE_ME_TO_A_RANDOM_SECRET",
259
- "ADMIN_AUTH_SECRET_KEY=CHANGE_ME_TO_A_RANDOM_SECRET",
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("# ADMIN_AUTH_BASIC_USERNAME=admin", "ADMIN_AUTH_BASIC_USERNAME=admin")
262
- .replace("# ADMIN_AUTH_BASIC_PASSWORD=change_me", "ADMIN_AUTH_BASIC_PASSWORD=change_me")
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
- - 基类选择(UUIDAuditableStateModel 等)
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
- - **推荐**使用 `UUIDAuditableStateModel`(UUID 主键 + 时间戳 + 软删除)
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 UUIDAuditableStateModel
134
+ from aury.boot.domain.models import AuditableStateModel
131
135
 
132
- class User(UUIDAuditableStateModel):
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
- | `Model` | int | ✓ | ✗ | ✗ | 简单实体 |
10
- | `AuditableStateModel` | int | ✓ | ✓ | ✗ | 需软删除 |
11
- | `UUIDModel` | UUID | ✓ | | | 分布式 |
12
- | `UUIDAuditableStateModel` | UUID | ✓ | | ✗ | **推荐** |
13
- | `VersionedModel` | int | | | | 乐观锁 |
14
- | `VersionedUUIDModel` | UUID | | ✗ | ✓ | UUID+乐观锁 |
15
- | `FullFeaturedUUIDModel` | UUID | ✓ | | ✓ | 全功能 |
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 UUIDAuditableStateModel
74
+ from aury.boot.domain.models import AuditableStateModel
74
75
 
75
76
 
76
- class User(UUIDAuditableStateModel):
77
+ class User(AuditableStateModel):
77
78
  """User 模型。
78
79
 
79
- 继承 UUIDAuditableStateModel 自动获得:
80
- - id: UUID 主键(使用 SQLAlchemyUuid 自动适配数据库)
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(UUIDAuditableStateModel):
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[uuid.UUID | None] = mapped_column(index=True)
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(UUIDModel): # UUIDModel 不包含软删除
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[uuid.UUID | None] = mapped_column(index=True)
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("错误信息", exc_info=True)
13
+ logger.error("错误信息")
14
+ logger.exception("异常信息") # 自动记录堆栈
15
+ ```
13
16
 
14
- # 绑定上下文
15
- logger.bind(user_id=123).info("用户操作")
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
- from aury.boot.common.logging import get_trace_id, set_trace_id
27
+ # app/auth/context.py
28
+ from contextvars import ContextVar
29
+ from aury.boot.common.logging import register_request_context
22
30
 
23
- # 自动生成或获取当前 trace_id
24
- trace_id = get_trace_id()
31
+ _user_id: ContextVar[str] = ContextVar("user_id", default="")
25
32
 
26
- # 手动设置(如从请求头获取)
27
- set_trace_id("abc-123")
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
- ```python
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
- INFO → POST /api/users | 客户端: 127.0.0.1 | Trace-ID: abc-123
61
- INFO POST /api/users | 状态: 201 | 耗时: 0.052s | Trace-ID: abc-123
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 WebSocket 日志中间件
88
+ ## 11.5 自定义日志文件
89
+
90
+ 为特定业务创建独立的日志文件:
65
91
 
66
92
  ```python
67
- from aury.boot.application.middleware.logging import WebSocketLoggingMiddleware
93
+ from aury.boot.common.logging import register_log_sink, logger
68
94
 
69
- app.add_middleware(
70
- WebSocketLoggingMiddleware,
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 register_log_sink
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
- # 注册 access 日志
88
- register_log_sink("access", filter_key="access")
122
+ 日志自动按服务类型分离:
89
123
 
90
- # 写入 access 日志
91
- logger.bind(access=True).info("GET /api/users 200 0.05s")
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;如果你使用的是异步 `DATABASE_URL`,建议单独设置同步的 `ADMIN_DATABASE_URL`(框架也会尝试自动推导常见驱动映射)。
6
+ - SQLAdmin 通常需要同步 SQLAlchemy Engine;如果你使用的是异步 `DATABASE__URL`,建议单独设置同步的 `ADMIN__DATABASE_URL`(框架也会尝试自动推导常见驱动映射)。
7
7
 
8
8
  ## 快速启用(.env)
9
9
 
10
10
  ```bash
11
11
  # 启用与基本路径
12
- ADMIN_ENABLED=true
13
- ADMIN_PATH=/api/admin-console
12
+ ADMIN__ENABLED=true
13
+ ADMIN__PATH=/api/admin-console
14
14
 
15
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
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
- # ADMIN_AUTH_MODE=bearer
23
- # ADMIN_AUTH_SECRET_KEY=CHANGE_ME
24
- # ADMIN_AUTH_BEARER_TOKENS=["token1","token2"]
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
- # ADMIN_DATABASE_URL=postgresql+psycopg://user:pass@localhost:5432/{project_name}
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
- - 通过 `ADMIN_AUTH_BACKEND=module:attr` 指定自定义 backend;或在 `admin_console.py` 实现 `register_admin_auth(config)` 返回 SQLAdmin 的 `AuthenticationBackend`。
52
- - 生产环境下必须设置 `ADMIN_AUTH_SECRET_KEY`,不允许 `none` 模式。
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`(或你配置的 `ADMIN_PATH`)。
56
+ 启动服务后访问:`http://localhost:8000/api/admin-console`(或你配置的 `ADMIN__PATH`)。