create-dwy 0.5.1 → 0.6.0

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 (156) hide show
  1. package/bin/index.js +1 -1
  2. package/package.json +2 -2
  3. package/src/create.js +61 -37
  4. package/src/sync.js +212 -61
  5. package/templates/claude-global/CLAUDE.md +92 -0
  6. package/templates/claude-global/rules/dwy-android-core.md +610 -0
  7. package/templates/claude-global/rules/dwy-api-design.md +110 -0
  8. package/templates/claude-global/rules/dwy-db-migration.md +46 -0
  9. package/templates/claude-global/rules/dwy-flutter-core.md +564 -0
  10. package/templates/claude-global/rules/dwy-flutter-network.md +222 -0
  11. package/templates/claude-global/rules/dwy-flutter-router.md +97 -0
  12. package/templates/claude-global/rules/dwy-flutter-testing.md +94 -0
  13. package/templates/claude-global/rules/dwy-git-commit-convention.md +82 -0
  14. package/templates/claude-global/rules/dwy-git-security.md +53 -0
  15. package/templates/claude-global/rules/dwy-payload-limits.md +178 -0
  16. package/templates/claude-global/rules/{prefer-open-source.md → dwy-prefer-open-source.md} +4 -0
  17. package/templates/claude-global/rules/dwy-project-structure.md +181 -0
  18. package/templates/claude-global/rules/{python-code-style.md → dwy-python-core.md} +292 -358
  19. package/templates/claude-global/rules/dwy-python-fastapi.md +272 -0
  20. package/templates/claude-global/rules/dwy-python-orm.md +61 -0
  21. package/templates/claude-global/rules/{backend-security.md → dwy-python-security.md} +99 -72
  22. package/templates/claude-global/rules/dwy-python-testing.md +53 -0
  23. package/templates/claude-global/rules/{server-security.md → dwy-server-security.md} +1 -2
  24. package/templates/claude-global/rules/dwy-tdd-development.md +70 -0
  25. package/templates/claude-global/rules/dwy-tech-stack.md +60 -0
  26. package/templates/claude-global/rules/dwy-vue-api.md +146 -0
  27. package/templates/claude-global/rules/{vue-code-style.md → dwy-vue-core.md} +338 -152
  28. package/templates/claude-global/rules/dwy-vue-pinia.md +75 -0
  29. package/templates/claude-global/rules/dwy-vue-testing.md +102 -0
  30. package/templates/claude-global/skills/dwy-deploy-audit/SKILL.md +481 -0
  31. package/templates/claude-global/skills/dwy-deploy-audit/references/checks-data.md +117 -0
  32. package/templates/claude-global/skills/dwy-deploy-audit/references/checks-network.md +74 -0
  33. package/templates/claude-global/skills/dwy-deploy-audit/references/checks-runtime.md +184 -0
  34. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_base.sh +39 -0
  35. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_capacity.sh +278 -0
  36. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_db.sh +122 -0
  37. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_docker.sh +249 -0
  38. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_env.sh +64 -0
  39. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_https.sh +60 -0
  40. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_logs.sh +209 -0
  41. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_nginx.sh +92 -0
  42. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_resilience.sh +157 -0
  43. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_secrets.sh +327 -0
  44. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_services.sh +77 -0
  45. package/templates/claude-global/skills/dwy-deploy-audit/scripts/check_ssh.sh +56 -0
  46. package/templates/claude-global/skills/dwy-deploy-audit/scripts/run_all.sh +105 -0
  47. package/templates/claude-global/skills/dwy-docker/SKILL.md +187 -0
  48. package/templates/claude-global/skills/dwy-docker/references/ask-templates.md +142 -0
  49. package/templates/claude-global/skills/dwy-docker/references/templates.md +479 -0
  50. package/templates/claude-global/skills/dwy-docker/references/version-rules.md +76 -0
  51. package/templates/claude-global/skills/dwy-docker/scripts/query_dockerhub.py +204 -0
  52. package/templates/claude-global/skills/dwy-docker/scripts/scan_images.sh +185 -0
  53. package/templates/claude-global/skills/dwy-eapi/SKILL.md +433 -0
  54. package/templates/claude-global/skills/dwy-eapi/references/tasks-integration-guide.md +387 -0
  55. package/templates/claude-global/skills/dwy-ekit/SKILL.md +85 -0
  56. package/templates/claude-global/skills/dwy-ekit/references/data.md +135 -0
  57. package/templates/claude-global/skills/dwy-ekit/references/form.md +68 -0
  58. package/templates/claude-global/skills/dwy-ekit/references/request.md +183 -0
  59. package/templates/claude-global/skills/dwy-ekit/references/ui.md +109 -0
  60. package/templates/claude-global/skills/{dwy-frontend-eui → dwy-eui}/SKILL.md +129 -15
  61. package/templates/claude-global/skills/dwy-eui/references/eui-design-guide.md +257 -0
  62. package/templates/claude-global/skills/dwy-eui/references/eui-integration-guide.md +302 -0
  63. package/templates/claude-global/skills/dwy-eui/references/eui-landing-design-guide.md +437 -0
  64. package/templates/claude-global/skills/dwy-fullstack-scaffold/SKILL.md +214 -0
  65. package/templates/claude-global/skills/dwy-mirror-source/SKILL.md +275 -0
  66. package/templates/claude-global/skills/dwy-mirror-source/references/dockerfile-snippets.md +228 -0
  67. package/templates/claude-global/skills/dwy-mirror-source/references/mirror-providers.md +150 -0
  68. package/templates/claude-global/skills/dwy-mirror-source/scripts/__pycache__/check_mirrors.cpython-313.pyc +0 -0
  69. package/templates/claude-global/skills/dwy-mirror-source/scripts/apply_mirrors.py +666 -0
  70. package/templates/claude-global/skills/dwy-mirror-source/scripts/check_mirrors.py +868 -0
  71. package/templates/claude-global/skills/dwy-pentest/SKILL.md +344 -0
  72. package/templates/claude-global/skills/dwy-pentest/scripts/lib/__init__.py +1 -0
  73. package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/__init__.cpython-311.pyc +0 -0
  74. package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/config.cpython-311.pyc +0 -0
  75. package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/executor.cpython-311.pyc +0 -0
  76. package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/severity.cpython-311.pyc +0 -0
  77. package/templates/claude-global/skills/dwy-pentest/scripts/lib/__pycache__/validator.cpython-311.pyc +0 -0
  78. package/templates/claude-global/skills/dwy-pentest/scripts/lib/config.py +109 -0
  79. package/templates/claude-global/skills/dwy-pentest/scripts/lib/executor.py +157 -0
  80. package/templates/claude-global/skills/dwy-pentest/scripts/lib/severity.py +96 -0
  81. package/templates/claude-global/skills/dwy-pentest/scripts/lib/validator.py +113 -0
  82. package/templates/claude-global/skills/dwy-pentest/scripts/pentest_auth.py +289 -0
  83. package/templates/claude-global/skills/dwy-pentest/scripts/pentest_recon.py +217 -0
  84. package/templates/claude-global/skills/dwy-pentest/scripts/pentest_report.py +343 -0
  85. package/templates/claude-global/skills/dwy-pentest/scripts/pentest_scan.py +205 -0
  86. package/templates/claude-global/skills/dwy-pentest/scripts/pentest_ssl.py +208 -0
  87. package/templates/claude-global/skills/dwy-pentest/scripts/pentest_vuln.py +205 -0
  88. package/templates/claude-global/skills/dwy-pentest/scripts/pentest_web.py +330 -0
  89. package/templates/claude-global/skills/dwy-pentest/scripts/pyproject.toml +14 -0
  90. package/templates/claude-global/skills/dwy-pentest/scripts/setup_tools.sh +97 -0
  91. package/templates/claude-global/skills/dwy-pentest/scripts/uv.lock +415 -0
  92. package/templates/claude-global/skills/{dwy-release → dwy-publish}/SKILL.md +42 -18
  93. package/templates/claude-global/skills/dwy-vue-core/SKILL.md +890 -0
  94. package/templates/project/backend/.dwy.hbs +5 -0
  95. package/templates/project/backend/.python-version +1 -0
  96. package/templates/project/backend/CLAUDE.md.hbs +40 -0
  97. package/templates/project/{web/backend → backend}/alembic/script.py.mako +2 -0
  98. package/templates/project/{web/backend → backend}/alembic.ini.hbs +0 -1
  99. package/templates/project/backend/deploy.sh.hbs +18 -0
  100. package/templates/project/{web → backend}/docker-compose.prod.yml.hbs +1 -1
  101. package/templates/project/{web/backend → backend}/pyproject.toml.hbs +4 -5
  102. package/templates/project/{web/backend → backend}/src/app/config.py.hbs +1 -1
  103. package/templates/project/backend/src/app/database.py +10 -0
  104. package/templates/project/backend/src/app/dependencies.py +5 -0
  105. package/templates/project/{web/backend → backend}/src/app/main.py.hbs +3 -3
  106. package/templates/project/backend/src/app/models/base.py +5 -0
  107. package/templates/project/web/CLAUDE.md.hbs +29 -36
  108. package/templates/project/web/{frontend → apps/web}/index.html.hbs +1 -0
  109. package/templates/project/web/{frontend → apps/web}/package.json.hbs +4 -3
  110. package/templates/project/web/apps/web/src/App.vue +9 -0
  111. package/templates/project/web/apps/web/src/api/client.ts +5 -0
  112. package/templates/project/web/apps/web/src/composables/.gitkeep +0 -0
  113. package/templates/project/web/apps/web/src/layouts/DefaultLayout.vue +8 -0
  114. package/templates/project/web/{frontend → apps/web}/src/router/index.ts +1 -1
  115. package/templates/project/web/{frontend → apps/web}/src/stores/auth.ts +4 -2
  116. package/templates/project/web/apps/web/src/style.css +2 -0
  117. package/templates/project/web/apps/web/src/types/router.d.ts +8 -0
  118. package/templates/project/web/apps/web/src/views/HomeView.vue +8 -0
  119. package/templates/project/web/apps/web/tsconfig.app.json +18 -0
  120. package/templates/project/web/apps/web/tsconfig.json +6 -0
  121. package/templates/project/web/{frontend → apps/web}/vite.config.ts.hbs +7 -1
  122. package/templates/project/web/package.json.hbs +16 -0
  123. package/templates/project/web/packages/.gitkeep +0 -0
  124. package/templates/project/web/pnpm-workspace.yaml +3 -0
  125. package/templates/claude-global/hooks/post-sync-claude-global.sh +0 -72
  126. package/templates/claude-global/rules/agent-team-execution.md +0 -138
  127. package/templates/claude-global/rules/agent-team-patterns.md +0 -157
  128. package/templates/claude-global/rules/git-commit-convention.md +0 -106
  129. package/templates/claude-global/rules/git-security.md +0 -144
  130. package/templates/claude-global/skills/dwy-backend-base/SKILL.md +0 -279
  131. package/templates/claude-global/skills/dwy-frontend-core/SKILL.md +0 -181
  132. package/templates/project/web/.claude/commands/deploy.md +0 -4
  133. package/templates/project/web/backend/src/app/models/base.py +0 -5
  134. package/templates/project/web/deploy.sh.hbs +0 -113
  135. package/templates/project/web/frontend/src/App.vue +0 -3
  136. package/templates/project/web/frontend/src/api/client.ts +0 -13
  137. package/templates/project/web/frontend/src/style.css +0 -1
  138. package/templates/project/web/frontend/src/views/HomeView.vue +0 -5
  139. package/templates/project/web/frontend/tsconfig.app.json +0 -14
  140. package/templates/project/web/frontend/tsconfig.json +0 -4
  141. /package/templates/{project/web/backend → claude-global/skills/dwy-pentest/scripts}/.python-version +0 -0
  142. /package/templates/project/{web/backend → backend}/Dockerfile +0 -0
  143. /package/templates/project/{web/backend → backend}/alembic/env.py.hbs +0 -0
  144. /package/templates/{claude-global/skills → project/backend/alembic/versions}/.gitkeep +0 -0
  145. /package/templates/project/{web → backend}/docker-compose.dev.yml.hbs +0 -0
  146. /package/templates/project/{web/backend → backend}/src/app/__init__.py +0 -0
  147. /package/templates/project/{web/backend → backend}/src/app/models/__init__.py +0 -0
  148. /package/templates/project/{web/backend → backend}/src/app/routers/__init__.py +0 -0
  149. /package/templates/project/{web/backend → backend}/src/app/routers/health.py +0 -0
  150. /package/templates/project/{web/backend → backend}/src/app/schemas/__init__.py +0 -0
  151. /package/templates/project/{web/backend → backend}/src/app/services/__init__.py +0 -0
  152. /package/templates/project/{web/backend → backend}/tests/__init__.py +0 -0
  153. /package/templates/project/{web/backend → backend}/tests/conftest.py.hbs +0 -0
  154. /package/templates/project/{web/backend/alembic/versions → mobile}/.gitkeep +0 -0
  155. /package/templates/project/web/{frontend → apps/web}/src/components/.gitkeep +0 -0
  156. /package/templates/project/web/{frontend → apps/web}/src/main.ts +0 -0
@@ -0,0 +1,272 @@
1
+ ---
2
+ description: FastAPI 路由/Schema/依赖注入/异常/OSS 封装规范
3
+ paths:
4
+ - "**/routers/**/*.py"
5
+ - "**/schemas/**/*.py"
6
+ - "**/services/**/*.py"
7
+ - "**/dependencies.py"
8
+ - "**/exceptions.py"
9
+ - "**/main.py"
10
+ ---
11
+
12
+ # FastAPI 路由/Schema/依赖注入/异常/OSS 封装规范
13
+
14
+ ## 五、FastAPI 路由与接口设计
15
+
16
+ ### 路由组织
17
+
18
+ ```python
19
+ # routers/user.py
20
+ from fastapi import APIRouter, Depends, status
21
+
22
+ router = APIRouter(prefix="/users", tags=["users"])
23
+
24
+ @router.post("/", status_code=status.HTTP_201_CREATED, response_model=UserResponse)
25
+ async def create_user(
26
+ body: UserCreate,
27
+ service: UserService = Depends(get_user_service),
28
+ ) -> UserResponse:
29
+ return await service.create(body)
30
+ ```
31
+
32
+ ### 强制规则
33
+ - 每个路由模块使用独立 `APIRouter`,在 `main.py` 中统一注册
34
+ - **禁止**所有路由写在 `main.py` 中(超过 2 个路由必须拆分)
35
+ - 路由函数只做:参数接收 → 调用 service → 返回结果,**不写业务逻辑**
36
+ - 必须显式声明 `status_code` 和 `response_model`
37
+ - 路由路径使用复数名词 + RESTful 风格
38
+
39
+ ### RESTful 路由规范
40
+
41
+ | 操作 | 方法 | 路径 | 状态码 |
42
+ |---|---|---|---|
43
+ | 列表查询 | `GET` | `/users` | 200 |
44
+ | 详情查询 | `GET` | `/users/{user_id}` | 200 |
45
+ | 创建 | `POST` | `/users` | 201 |
46
+ | 全量更新 | `PUT` | `/users/{user_id}` | 200 |
47
+ | 部分更新 | `PATCH` | `/users/{user_id}` | 200 |
48
+ | 删除 | `DELETE` | `/users/{user_id}` | 204 |
49
+
50
+ ### 分页查询
51
+
52
+ ```python
53
+ # schemas/common.py
54
+ class PaginationParams(BaseModel):
55
+ page: int = Field(default=1, ge=1)
56
+ page_size: int = Field(default=20, ge=1, le=100)
57
+
58
+ class PaginatedResponse[T](BaseModel):
59
+ items: list[T]
60
+ total: int
61
+ page: int
62
+ page_size: int
63
+
64
+ # routers/user.py
65
+ @router.get("/", response_model=PaginatedResponse[UserResponse])
66
+ async def list_users(
67
+ pagination: PaginationParams = Depends(),
68
+ service: UserService = Depends(get_user_service),
69
+ ) -> PaginatedResponse[UserResponse]:
70
+ return await service.list(pagination)
71
+ ```
72
+
73
+ ## 六、Pydantic Schema 规范
74
+
75
+ ### 强制规则
76
+ - 请求和响应使用不同的 Schema,**禁止**复用同一个 Model
77
+
78
+ ```python
79
+ # schemas/user.py
80
+
81
+ class UserBase(BaseModel):
82
+ """共享字段基类"""
83
+ name: str = Field(max_length=50)
84
+ email: EmailStr
85
+
86
+ class UserCreate(UserBase):
87
+ """创建请求"""
88
+ password: str = Field(min_length=8)
89
+
90
+ class UserUpdate(BaseModel):
91
+ """更新请求 - 所有字段可选"""
92
+ name: str | None = Field(default=None, max_length=50)
93
+ email: EmailStr | None = None
94
+
95
+ class UserResponse(UserBase):
96
+ """响应"""
97
+ id: int
98
+ is_active: bool
99
+ created_at: datetime
100
+
101
+ model_config = ConfigDict(from_attributes=True)
102
+ ```
103
+
104
+ - **禁止**在响应 Schema 中暴露密码、内部 ID、敏感字段
105
+ - 使用 `Field()` 添加校验约束(`min_length`、`ge`、`le`、`pattern` 等)
106
+ - 更新 Schema 的字段必须全部可选(`None` 默认值)
107
+
108
+ ## 七、依赖注入
109
+
110
+ ### 强制规则
111
+ - 数据库会话、当前用户、配置等通过 `Depends()` 注入
112
+ - **禁止**在路由函数中直接创建数据库会话或实例化 service
113
+
114
+ ```python
115
+ # dependencies.py
116
+ from collections.abc import AsyncGenerator
117
+
118
+ async def get_db() -> AsyncGenerator[AsyncSession]:
119
+ async with async_session_factory() as session:
120
+ yield session
121
+
122
+ async def get_current_user(
123
+ token: str = Depends(oauth2_scheme),
124
+ db: AsyncSession = Depends(get_db),
125
+ ) -> User:
126
+ user = await authenticate(token, db)
127
+ if not user:
128
+ raise HTTPException(status_code=401, detail="认证失败")
129
+ return user
130
+
131
+ def get_user_service(db: AsyncSession = Depends(get_db)) -> UserService:
132
+ return UserService(db)
133
+ ```
134
+
135
+ ```python
136
+ # ❌ 路由中直接创建会话
137
+ @router.get("/users/{user_id}")
138
+ async def get_user(user_id: int):
139
+ async with async_session_factory() as db:
140
+ user = await db.get(User, user_id)
141
+ return user
142
+
143
+ # ✅ 依赖注入
144
+ @router.get("/users/{user_id}", response_model=UserResponse)
145
+ async def get_user(
146
+ user_id: int,
147
+ service: UserService = Depends(get_user_service),
148
+ ) -> UserResponse:
149
+ return await service.get_by_id(user_id)
150
+ ```
151
+
152
+ ## 八、异常处理
153
+
154
+ ### 统一异常体系
155
+
156
+ 直接使用 `dwyeapi` 提供的异常体系,禁止自造异常基类:
157
+
158
+ ```python
159
+ # 从 eapi 导入(禁止自定义 AppError 子类体系)
160
+ from dwyeapi.exceptions import (
161
+ AppError, # 基类
162
+ NotFoundError, # 404 — NotFoundError("用户") → {"code": "NOT_FOUND", "message": "用户不存在"}
163
+ BusinessError, # 422 — BusinessError("余额不足", code="INSUFFICIENT_BALANCE")
164
+ PermissionDeniedError, # 403 — PermissionDeniedError()
165
+ AuthenticationError, # 401 — AuthenticationError()
166
+ register_exception_handlers, # 一次性注册全部 handler
167
+ )
168
+
169
+ # main.py 中注册
170
+ from dwyeapi.exceptions import register_exception_handlers
171
+ register_exception_handlers(app)
172
+ ```
173
+
174
+ 项目如需扩展异常,必须继承 `AppError`:
175
+
176
+ ```python
177
+ # app/exceptions.py — 项目级异常(继承 eapi AppError)
178
+ from dwyeapi.exceptions import AppError
179
+
180
+ class QuotaExceededError(AppError):
181
+ def __init__(self) -> None:
182
+ super().__init__(message="配额已用尽", code="QUOTA_EXCEEDED")
183
+ ```
184
+
185
+ ### 强制规则
186
+ - **禁止**在 service 层抛出 `HTTPException`(service 不感知 HTTP)
187
+ - service 层抛出业务异常,由异常处理器统一转换为 HTTP 响应
188
+ - **禁止**裸 `except:` 或 `except Exception: pass`
189
+ - 错误响应格式统一为 `{"code": "ERROR_CODE", "message": "描述"}`
190
+ - **禁止**在错误消息中回显用户输入
191
+
192
+ ```python
193
+ # ❌ service 中抛 HTTPException
194
+ class UserService:
195
+ async def get_by_id(self, user_id: int) -> User:
196
+ user = await self.db.get(User, user_id)
197
+ if not user:
198
+ raise HTTPException(status_code=404) # 耦合 HTTP
199
+ return user
200
+
201
+ # ✅ service 中抛业务异常
202
+ class UserService:
203
+ async def get_by_id(self, user_id: int) -> User:
204
+ user = await self.db.get(User, user_id)
205
+ if not user:
206
+ raise NotFoundError("用户")
207
+ return user
208
+ ```
209
+
210
+ ---
211
+
212
+ ## OSS 规范(MinIO / S3 兼容)
213
+
214
+ ### 核心原则
215
+
216
+ **代码只用 S3 协议,不用任何云厂商专有 API。** 通过环境变量切换 dev/prod 存储。
217
+
218
+ ### 后端封装
219
+
220
+ ```python
221
+ # services/oss.py
222
+ import boto3
223
+ from app.config import settings
224
+
225
+ def get_s3_client():
226
+ return boto3.client(
227
+ "s3",
228
+ endpoint_url=settings.oss_endpoint, # dev: http://localhost:19000
229
+ aws_access_key_id=settings.oss_access_key,
230
+ aws_secret_access_key=settings.oss_secret_key,
231
+ )
232
+
233
+ async def upload_file(key: str, data: bytes, content_type: str = "") -> str:
234
+ client = get_s3_client()
235
+ client.put_object(
236
+ Bucket=settings.oss_bucket,
237
+ Key=key,
238
+ Body=data,
239
+ ContentType=content_type,
240
+ )
241
+ return key
242
+
243
+ async def get_presigned_url(key: str, expires: int = 3600) -> str:
244
+ client = get_s3_client()
245
+ return client.generate_presigned_url(
246
+ "get_object",
247
+ Params={"Bucket": settings.oss_bucket, "Key": key},
248
+ ExpiresIn=expires,
249
+ )
250
+ ```
251
+
252
+ ### 环境变量
253
+
254
+ ```bash
255
+ # .env.development(MinIO)
256
+ OSS_ENDPOINT=http://localhost:19000
257
+ OSS_ACCESS_KEY=minioadmin
258
+ OSS_SECRET_KEY=minioadmin
259
+ OSS_BUCKET=my-app
260
+
261
+ # .env.production(阿里云 OSS / AWS S3)
262
+ OSS_ENDPOINT=https://oss-cn-hangzhou.aliyuncs.com
263
+ OSS_ACCESS_KEY=your-key
264
+ OSS_SECRET_KEY=your-secret
265
+ OSS_BUCKET=my-app-prod
266
+ ```
267
+
268
+ ### 禁止事项
269
+
270
+ - 禁止使用 `minio` Python 库(用 `boto3`,S3 协议通用)
271
+ - 禁止代码中出现 MinIO / 阿里云 / AWS 等厂商名称判断逻辑
272
+ - 禁止硬编码 bucket 名、endpoint 地址
@@ -0,0 +1,61 @@
1
+ ---
2
+ description: SQLAlchemy 2.0 异步 ORM 规范
3
+ paths:
4
+ - "**/models/**/*.py"
5
+ - "**/database.py"
6
+ - "**/db.py"
7
+ ---
8
+
9
+ # SQLAlchemy 2.0 异步 ORM 规范
10
+
11
+ ## 九、数据库与 ORM
12
+
13
+ ### 强制规则
14
+ - 使用 **SQLAlchemy 2.0** 风格(声明式映射 + async)
15
+ - 数据库迁移使用 **Alembic**,**禁止**手动执行 DDL
16
+ - **禁止**在路由层直接写 SQL 查询
17
+
18
+ ```python
19
+ # models/base.py
20
+ from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
21
+ from sqlalchemy import func
22
+ from datetime import datetime
23
+
24
+ class Base(DeclarativeBase):
25
+ pass
26
+
27
+ class TimestampMixin:
28
+ created_at: Mapped[datetime] = mapped_column(server_default=func.now())
29
+ updated_at: Mapped[datetime] = mapped_column(server_default=func.now(), onupdate=func.now())
30
+
31
+ # models/user.py
32
+ class User(Base, TimestampMixin):
33
+ __tablename__ = "users"
34
+
35
+ id: Mapped[int] = mapped_column(primary_key=True)
36
+ name: Mapped[str] = mapped_column(String(50))
37
+ email: Mapped[str] = mapped_column(String(100), unique=True, index=True)
38
+ hashed_password: Mapped[str] = mapped_column(String(128))
39
+ is_active: Mapped[bool] = mapped_column(default=True)
40
+ ```
41
+
42
+ ### 异步数据库会话
43
+
44
+ ```python
45
+ # config.py / database.py
46
+ from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession
47
+
48
+ engine = create_async_engine(settings.database_url, echo=settings.debug)
49
+ async_session_factory = async_sessionmaker(engine, expire_on_commit=False)
50
+ ```
51
+
52
+ ### 查询规范
53
+
54
+ ```python
55
+ # ❌ 旧式查询
56
+ session.query(User).filter(User.id == user_id).first()
57
+
58
+ # ✅ SQLAlchemy 2.0 风格
59
+ result = await session.execute(select(User).where(User.id == user_id))
60
+ user = result.scalar_one_or_none()
61
+ ```