create-dwy 0.19.0 → 0.20.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.
- package/CHANGELOG.md +21 -0
- package/package.json +1 -1
- package/src/cli-help.js +28 -2
- package/src/prompt-style.js +82 -0
- package/src/searchable-select.js +12 -29
- package/src/sync-all.js +95 -583
- package/src/sync-codex.js +14 -7
- package/src/sync-opencode.js +8 -22
- package/src/sync.js +60 -149
- package/src/tab-multiselect.js +21 -64
- package/templates/ai-tools/CHANGELOG.md +195 -0
- package/templates/ai-tools/CLAUDE.md +39 -58
- package/templates/ai-tools/VERSION +1 -1
- package/templates/ai-tools/hooks/Git/pre-git-commit-ai-signature-check.sh +5 -4
- package/templates/ai-tools/rules/Android/dwy-android-core.md +147 -13
- package/templates/ai-tools/rules/Android/dwy-android-layering.md +142 -0
- package/templates/ai-tools/rules/Android/dwy-android-mvp.md +55 -0
- package/templates/ai-tools/rules/Android/dwy-android-support-only.md +2 -0
- package/templates/ai-tools/rules/Docker/dwy-docker.md +23 -1
- package/templates/ai-tools/rules/Flutter/dwy-flutter-core.md +2 -0
- package/templates/ai-tools/rules/Flutter/dwy-flutter-testing.md +1 -1
- package/templates/ai-tools/rules/HarmonyOS/dwy-arkts-ui-build.md +12 -8
- package/templates/ai-tools/rules/Python/dwy-python-backend.md +4 -43
- package/templates/ai-tools/rules/Python/dwy-python-base-lib.md +1 -1
- package/templates/ai-tools/rules/Python/dwy-python-layering.md +169 -0
- package/templates/ai-tools/rules/Vue/dwy-vue-core.md +11 -49
- package/templates/ai-tools/rules/Vue/dwy-vue-layering.md +153 -0
- package/templates/ai-tools/rules/iOS/dwy-apple-layering.md +133 -0
- package/templates/ai-tools/rules/iOS/dwy-swift-core.md +2 -0
- package/templates/ai-tools/rules//345/274/200/345/217/221/346/265/201/347/250/213/dwy-code-craft.md +83 -0
- package/templates/ai-tools/rules//345/274/200/345/217/221/346/265/201/347/250/213/dwy-dependency-freshness.md +27 -66
- package/templates/ai-tools/rules//345/274/200/345/217/221/346/265/201/347/250/213/dwy-git-commit.md +8 -219
- package/templates/ai-tools/rules//345/274/200/345/217/221/346/265/201/347/250/213/dwy-oss-commercial-license.md +27 -123
- package/templates/ai-tools/rules//345/274/200/345/217/221/346/265/201/347/250/213/dwy-prod-branch.md +31 -0
- package/templates/ai-tools/rules//346/225/260/346/215/256/345/272/223/dwy-postgres.md +17 -10
- package/templates/ai-tools/rules//346/225/260/346/215/256/345/272/223/dwy-redis.md +6 -3
- package/templates/ai-tools/skills/Docker/dwy-deploy-first/SKILL.md +14 -14
- package/templates/ai-tools/skills/Docker/dwy-deploy-first/references/chapter-01-mirrors.md +2 -2
- package/templates/ai-tools/skills/Docker/dwy-docker/SKILL.md +21 -13
- package/templates/ai-tools/skills/Docker/dwy-docker/references/ask-templates.md +13 -12
- package/templates/ai-tools/skills/Docker/dwy-docker/references/templates.md +3 -1
- package/templates/ai-tools/skills/Docker/dwy-docker/references/version-rules.md +10 -6
- package/templates/ai-tools/skills/Docker/dwy-docker/scripts/query_dockerhub.py +21 -6
- package/templates/ai-tools/skills/Docker/dwy-docker/scripts/scan_images.sh +2 -2
- package/templates/ai-tools/skills/Docker/dwy-mirror-source/SKILL.md +44 -30
- package/templates/ai-tools/skills/Docker/dwy-mirror-source/references/dockerfile-snippets.md +2 -0
- package/templates/ai-tools/skills/Docker/dwy-mirror-source/references/mirror-providers.md +8 -8
- package/templates/ai-tools/skills/Docker/dwy-mirror-source/scripts/apply_mirrors.py +14 -1
- package/templates/ai-tools/skills/Docker/dwy-mirror-source/scripts/check_mirrors.py +4 -0
- package/templates/ai-tools/skills/DolphinDB/dwy-dolphindb/SKILL.md +8 -6
- package/templates/ai-tools/skills/DolphinDB/dwy-dolphindb/references/digest/python-sdk.md +3 -3
- package/templates/ai-tools/skills/DolphinDB/dwy-dolphindb/references/digest/stream-engine.md +2 -2
- package/templates/ai-tools/skills/DolphinDB/dwy-dolphindb/references/review-rules/anti-patterns.md +3 -1
- package/templates/ai-tools/skills/DolphinDB/dwy-dolphindb/references/review-rules/change-checklist.md +4 -4
- package/templates/ai-tools/skills/DolphinDB/dwy-dolphindb/references/review-rules/lethal-violations.md +5 -5
- package/templates/ai-tools/skills/DolphinDB/dwy-dolphindb/references/review-rules/perf-baselines.md +5 -5
- package/templates/ai-tools/skills/Python/dwy-eapi/SKILL.md +59 -200
- package/templates/ai-tools/skills/Python/dwy-eapi/shared/anticorruption.md +13 -0
- package/templates/ai-tools/skills/Python/dwy-eapi/shared/layering.md +5 -0
- package/templates/ai-tools/skills/Python/dwy-eapi/shared/response-envelope.md +20 -0
- package/templates/ai-tools/skills/Python/dwy-eapi/shared/timezone.md +7 -0
- package/templates/ai-tools/skills/Python/dwy-eapi/v0/breaking.md +15 -0
- package/templates/ai-tools/skills/Python/dwy-eapi/v0/email-canonical.md +43 -0
- package/templates/ai-tools/skills/Python/dwy-eapi/v0/index.md +103 -0
- package/templates/ai-tools/skills/Python/dwy-eapi/v0/providers-email.md +49 -0
- package/templates/ai-tools/skills/Python/dwy-eapi/versions.yml +13 -0
- package/templates/ai-tools/skills/Vue/dwy-eui/SKILL.md +62 -306
- package/templates/ai-tools/skills/Vue/dwy-eui/references/eui-design-guide.md +8 -1
- package/templates/ai-tools/skills/Vue/dwy-eui/references/eui-integration-guide.md +0 -31
- package/templates/ai-tools/skills/Vue/dwy-eui/shared/config-theme.md +8 -0
- package/templates/ai-tools/skills/Vue/dwy-eui/shared/form.md +6 -0
- package/templates/ai-tools/skills/Vue/dwy-eui/shared/overlay.md +6 -0
- package/templates/ai-tools/skills/Vue/dwy-eui/shared/reka.md +5 -0
- package/templates/ai-tools/skills/Vue/dwy-eui/v1/index.md +8 -0
- package/templates/ai-tools/skills/Vue/dwy-eui/v2/breaking.md +14 -0
- package/templates/ai-tools/skills/Vue/dwy-eui/v2/index.md +55 -0
- package/templates/ai-tools/skills/Vue/dwy-eui/v2/lookup.md +29 -0
- package/templates/ai-tools/skills/Vue/dwy-eui/versions.yml +12 -0
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-architecture/SKILL.md +11 -14
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-product-launcher/references/state-and-contract.md +1 -1
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-prototype/SKILL.md +1 -1
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-github-action-publish/SKILL.md +32 -15
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-github-action-publish/references/electron-release-workflow-template.yml +3 -1
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-github-action-publish/references/release-workflow-template.yml +8 -5
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-ios-app-store-release/SKILL.md +1 -1
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-ios-app-store-release/assets/appstore-submission.yaml +1 -1
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-ios-app-store-release/references/aso.md +2 -0
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-ios-app-store-release/references/in-app-purchase-review.md +1 -1
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/SKILL.md +4 -3
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/android.md +1 -1
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/backend-deploy.md +1 -1
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/changelog.md +45 -27
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/frontend-deploy.md +1 -1
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/generic-release.md +1 -1
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/git-tag.md +1 -1
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/harmony.md +1 -1
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/ios.md +1 -1
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-semver/SKILL.md +8 -0
- package/templates/ai-tools/skills//345/256/211/345/205/250/dwy-deploy-audit/SKILL.md +7 -7
- package/templates/ai-tools/skills//345/256/211/345/205/250/dwy-deploy-audit/references/checks-runtime.md +63 -59
- package/templates/ai-tools/skills//345/256/211/345/205/250/dwy-deploy-audit/scripts/check_base.sh +10 -1
- package/templates/ai-tools/skills//345/256/211/345/205/250/dwy-deploy-audit/scripts/check_capacity.sh +21 -23
- package/templates/ai-tools/skills//345/256/211/345/205/250/dwy-deploy-audit/scripts/check_db.sh +9 -1
- package/templates/ai-tools/skills//345/256/211/345/205/250/dwy-deploy-audit/scripts/check_docker.sh +20 -24
- package/templates/ai-tools/skills//345/256/211/345/205/250/dwy-deploy-audit/scripts/check_logs.sh +3 -3
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/dwy-doubao-tts/SKILL.md +4 -2
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/dwy-doubao-tts/references/api.md +1 -1
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/dwy-doubao-tts/scripts/synthesize.py +1 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/media-platform-packaging/SKILL.md +7 -5
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/media-platform-packaging/references/platform-copy-examples.md +5 -3
- package/templates/ai-tools/skills//351/200/232/347/224/250/dwy-dependency-choice/SKILL.md +140 -0
- package/templates/ai-tools/skills//351/200/232/347/224/250/dwy-git-commit/SKILL.md +113 -0
- package/templates/ai-tools/skills//351/200/232/347/224/250/dwy-shared/SKILL.md +16 -13
- package/templates/ai-tools/skills//351/200/232/347/224/250/dwy-whiteboard-prototype/SKILL.md +1 -1
- package/src/sync-packs.js +0 -223
- package/src/sync-scene.js +0 -178
- package/templates/ai-tools/rules//345/274/200/345/217/221/346/265/201/347/250/213/dwy-tdd-development.md +0 -115
package/templates/ai-tools/skills/DolphinDB/dwy-dolphindb/references/review-rules/anti-patterns.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# DolphinDB 反模式与最佳实践
|
|
2
2
|
|
|
3
3
|
> 何时引用:审查 DDB 代码 / SQL / Python SDK 用法,命中具体反模式时引用对应章节。
|
|
4
|
-
>
|
|
4
|
+
> 来源:真实踩坑。语言语义以本文件为准,直到官方文档改写了这条语义。字节上限、分区数、行数以当前 server 文档和报错为准,下面的数字是当时的上限。
|
|
5
5
|
|
|
6
6
|
## 类型陷阱
|
|
7
7
|
|
|
@@ -55,6 +55,8 @@ DDB 和 Python/SQL **相反**!老手都踩过。要小数结果用 `\`。
|
|
|
55
55
|
|
|
56
56
|
### 5. SYMBOL ≤255 字节 / STRING 64KB / BLOB 64MB 静默截断(**1.30.23 / 2.00.11 起**)
|
|
57
57
|
|
|
58
|
+
255 字节、64KB、64MB 是这些版本起的已知上限。连上的 server 文档或实际报错不同,以那次为准。
|
|
59
|
+
|
|
58
60
|
| 类型 | 上限 | 超限行为 |
|
|
59
61
|
|---|---|---|
|
|
60
62
|
| `SYMBOL` | **≤ 255 字节** | **整批 append 直接抛异常**,写入失败 |
|
|
@@ -7,18 +7,18 @@
|
|
|
7
7
|
|
|
8
8
|
- [ ] WHERE 是否命中分区列?分区列是否被函数包裹(`year()` / `date()` / `temporalAdd()`)?
|
|
9
9
|
- [ ] 范围条件用了 `between ... and ...` 或两个独立条件?没有链式比较 `a <= col <= b`?
|
|
10
|
-
- [ ] 单次 `append`
|
|
10
|
+
- [ ] 单次 `append` 低于当前 server 的单消息上限?写作时常见约 500K 行,以本次报错为准。循环里有 `del + gc.collect()`?
|
|
11
11
|
- [ ] 大文件用 pyarrow `read_table + slice`,不是 `pd.read_feather` 全量加载?
|
|
12
12
|
- [ ] 拼接 DDB 脚本时,外部输入有正则白名单校验?(防注入)
|
|
13
13
|
- [ ] `pool.run()` 用 `await`(原生 async),`Session.run()` 用 `asyncio.to_thread()`?
|
|
14
|
-
- [ ]
|
|
14
|
+
- [ ] 连接池大小按本次 worker?2 核 8G、4 worker 的先验是 pool 3,不是所有集群的上限。
|
|
15
15
|
- [ ] `select` 只取需要的列?没有 `select *`?
|
|
16
16
|
- [ ] 多次聚合查询能否合并为一次?
|
|
17
17
|
- [ ] 多因子横向查询用了 `pivot by`,不是 Python 端 merge?
|
|
18
18
|
- [ ] backtick 列名定义在单行内?
|
|
19
19
|
- [ ] 分页用 `limit offset, count`(**不是** `LIMIT x OFFSET y`)?
|
|
20
|
-
- [ ]
|
|
21
|
-
- [ ] 写入 SYMBOL
|
|
20
|
+
- [ ] 新建库表:单分区估算大小落在引擎区间?总分区数低于该节点 `maxPartitionNumPerQuery`(默认常见 65536)?二级高基数列走 `HASH` 不是 `VALUE`?
|
|
21
|
+
- [ ] 写入 SYMBOL 列前校验当前版本字节上限(常见 255)?STRING/BLOB 有长度断言防静默截断?
|
|
22
22
|
- [ ] 删数据用 `dropPartition`,不是 `delete from`?
|
|
23
23
|
- [ ] pandas → DDB:`uint` 是否 `.astype('int64')`?NaN→int 是否 `.fillna(0)`?
|
|
24
24
|
- [ ] Session / Pool 有 `try/finally close()`?
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
# 10 条致命违规(零容忍)
|
|
2
2
|
|
|
3
|
-
> 何时引用:审查 DDB 代码 / SQL / Python SDK
|
|
4
|
-
>
|
|
3
|
+
> 何时引用:审查 DDB 代码 / SQL / Python SDK 用法。
|
|
4
|
+
> 分区剪枝、注入、连接池变量这些是语言约束,命中就要改。行数、字节、分区数是当时 server 的上限。官方文档或这次报错更准时,以那次为准,不要用本表压过当前版本文档。
|
|
5
5
|
|
|
6
6
|
| # | 违规 | 后果 |
|
|
7
7
|
|---|---|---|
|
|
8
8
|
| 1 | 分区列被函数包裹(`date(col)` / `year(col)` / `temporalAdd(col,...)`) | 全表扫 → OOM |
|
|
9
9
|
| 2 | 链式比较 `a <= col <= b` | 不触发剪枝 → 全表扫 |
|
|
10
|
-
| 3 | 单次 `append`
|
|
10
|
+
| 3 | 单次 `append` 超过当前 server 的单消息上限(写作时常见约 500K 行) | TSDB 报 `exceeds max limit`。不是永远 500K |
|
|
11
11
|
| 4 | 未校验外部输入拼到脚本 | 脚本注入 |
|
|
12
12
|
| 5 | 连接池里 upload + run(变量跨连接不可见) | 变量找不到 |
|
|
13
13
|
| 6 | `select *`(30+ 列全传) | 浪费带宽和内存 |
|
|
14
14
|
| 7 | 分页用 `LIMIT x OFFSET y` | DDB 不支持 OFFSET 关键字(要用 `limit offset, count`) |
|
|
15
15
|
| 8 | `asyncio.to_thread(pool.run, script)` | SDK v3 `pool.run` 已是原生 async |
|
|
16
|
-
| 9 | SYMBOL
|
|
17
|
-
| 10 |
|
|
16
|
+
| 9 | SYMBOL 列写入未校验当前版本字节上限(1.30.23 / 2.00.11 起常见 255 字节) | 整批写入失败。以本次 server 文档为准 |
|
|
17
|
+
| 10 | 单分区过小且总分区数超过该节点 `maxPartitionNumPerQuery`(默认常见 65536) | 触达查询分区上限。配置改过就用配置值 |
|
|
18
18
|
|
|
19
19
|
## 审查输出格式
|
|
20
20
|
|
package/templates/ai-tools/skills/DolphinDB/dwy-dolphindb/references/review-rules/perf-baselines.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# 性能基线(DolphinDB 3.0 社区版 2 核 8G)
|
|
2
2
|
|
|
3
3
|
> 何时引用:评估查询/写入性能、判断是否需要优化、配置 dolphindb.cfg。
|
|
4
|
-
>
|
|
4
|
+
> 来源:一次 2 核 8G 社区版实测。有 `getClusterPerf`、license 或节点配置时以实测为准。偏离下表不是违规。超过表只说明要排查。
|
|
5
5
|
|
|
6
|
-
##
|
|
6
|
+
## 资源预算(这次 2 核 8G 的冷启动先验)
|
|
7
7
|
|
|
8
8
|
```
|
|
9
9
|
总内存 8 GB
|
|
@@ -29,7 +29,7 @@ where TradeDate between 2024.01.01 and 2024.01.05
|
|
|
29
29
|
|
|
30
30
|
## 配置调优(社区版 dolphindb.cfg)
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
下面是 2 核 8G 社区版的起点。许可或机器不同,按本次核数和内存重算,不要照抄。
|
|
33
33
|
|
|
34
34
|
| 参数 | 建议值 | 说明 |
|
|
35
35
|
|---|---|---|
|
|
@@ -54,7 +54,7 @@ where TradeDate between 2024.01.01 and 2024.01.05
|
|
|
54
54
|
| Python 拉 100 万行 → DataFrame | 2-5s |
|
|
55
55
|
| `PartitionedTableAppender` 写入 | 200-400 万行/s |
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
**比这张表慢 2-3 倍要排查**,不是判死刑:剪枝是否生效(`select [HINT_EXPLAIN]`)、索引是否覆盖、并发是否争抢同分区。机器不同,时延表本身就作废。
|
|
58
58
|
|
|
59
59
|
## 分区粒度目标
|
|
60
60
|
|
|
@@ -63,7 +63,7 @@ where TradeDate between 2024.01.01 and 2024.01.05
|
|
|
63
63
|
| TSDB | 400 MB - 1 GB |
|
|
64
64
|
| OLAP | 100 MB - 300 MB |
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
总分区数不要超过该节点的 `maxPartitionNumPerQuery`。默认常见 65536,配置改过就用配置值,不要死守 65536。
|
|
67
67
|
|
|
68
68
|
## 连接池大小
|
|
69
69
|
|
|
@@ -1,226 +1,85 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: dwy-eapi
|
|
3
|
-
description: "
|
|
4
|
-
eapi_baseline_version: "0.9.0"
|
|
3
|
+
description: "Use when building or changing FastAPI backends that use dwyeapi: Pydantic Settings, async SQLAlchemy, Redis, JWT/bcrypt, AppError/NotFoundError/BusinessError, ApiResponse/PageData, pagination, loguru, health checks, PII masking, timezone-aware dt, ARQ tasks, email verification codes, Gmail alias canonicalization, common email-domain allowlist. The single navigation skill for dwyeapi."
|
|
5
4
|
---
|
|
6
5
|
|
|
7
|
-
# dwyeapi
|
|
6
|
+
# dwyeapi
|
|
8
7
|
|
|
9
|
-
|
|
8
|
+
FastAPI 基础设施包。本文件只做**路由**:探测安装版本 → 选文档线 → 不够再读源码(先 clone tag,拿不到再读虚拟环境)。
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
对外只有这一个 skill。`v0/` `v1/` 不是独立 skill,禁止当多个入口用。
|
|
12
11
|
|
|
13
|
-
##
|
|
12
|
+
## 每次写 dwyeapi 代码前(强制)
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
| `security` | JWT 令牌 + bcrypt 密码哈希(无状态) |
|
|
22
|
-
| `cache` | 异步 Redis 连接管理(懒单例) |
|
|
23
|
-
| `response` | 统一 API 响应信封 `ApiResponse[T]` + `PageData[T]` |
|
|
24
|
-
| `pagination` | 分页参数 PaginationParams + offset/limit 计算 |
|
|
25
|
-
| `dt` | 中国时区敏感的全局时间工具(替代 datetime.now) |
|
|
26
|
-
| `logger` | loguru 全局日志(按天 + 按大小轮转、stdlib 拦截) |
|
|
27
|
-
| `masking` | PII 数据脱敏(手机/邮箱/身份证/银行卡/姓名/地址/IP/车牌) |
|
|
28
|
-
| `health` | 健康检查路由工厂(只探活、不探依赖) |
|
|
29
|
-
| `tasks` | 异步任务系统(基于 ARQ,需 `pip install dwyeapi[tasks]`) |
|
|
30
|
-
| `providers.email` | 邮件验证码发送(可插拔,默认 Resend,可继承注入腾讯 SES / SMTP 等) |
|
|
31
|
-
|
|
32
|
-
---
|
|
33
|
-
|
|
34
|
-
## 查 API 标准动作(核心:每次写 dwyeapi 代码前都做)
|
|
35
|
-
|
|
36
|
-
dwyeapi 的源码 docstring 写得非常详尽(含设计意图、边界行为、Args/Returns/Raises、典型陷阱)。**本文档不再镜像签名表格** —— 因为表格信息密度低于源码 docstring,且会随版本漂移。需要某个 API 的精确签名/语义时,按下面顺序拿:
|
|
37
|
-
|
|
38
|
-
### 第一步:定位 dwyeapi 真实版本和安装路径
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
# 拿版本
|
|
42
|
-
pip show dwyeapi | grep -i version
|
|
43
|
-
# 或:python -c "import dwyeapi; print(dwyeapi.__version__)"
|
|
44
|
-
|
|
45
|
-
# 拿包安装目录
|
|
46
|
-
python -c "import dwyeapi; print(dwyeapi.__file__)"
|
|
47
|
-
# 输出形如 /path/to/.venv/lib/python3.12/site-packages/dwyeapi/__init__.py
|
|
48
|
-
# dirname 后的目录就是 dwyeapi 包根,模块都在里面
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
### 第二步:读模块源码(按存在性回退)
|
|
52
|
-
|
|
53
|
-
```
|
|
54
|
-
a. 优先:<site-packages>/dwyeapi/{module}.py
|
|
55
|
-
—— 下游消费方默认场景。每个模块单文件,含模块级 docstring + 所有公开函数/类的 Args/Returns/Raises
|
|
56
|
-
|
|
57
|
-
b. 次选:<dwy-shared-root>/backend/src/dwyeapi/{module}.py
|
|
58
|
-
—— 仅当在 dwy-shared monorepo 内或并列 clone 时可用,内容相同
|
|
59
|
-
|
|
60
|
-
c. 都拿不到 → 退到本文档下方的 [模块清单](#模块清单13-个)(粗略,仅做导航)
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
### 第三步:tasks 模块查详细文档
|
|
64
|
-
|
|
65
|
-
tasks 模块功能丰富(注册 / 提交 / 查询 / 取消 / 进度),单独维护一份完整集成指南:
|
|
66
|
-
|
|
67
|
-
```
|
|
68
|
-
references/tasks-integration-guide.md
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
只在用到 tasks 时读,平时不必加载。
|
|
72
|
-
|
|
73
|
-
### 第四步:冲突时永远信源码
|
|
74
|
-
|
|
75
|
-
本 SKILL.md 是导航 + 心智模型,**不是 API 真相**。当文档描述与 site-packages 中的 `.py` 不一致:无条件以源码为准。
|
|
76
|
-
|
|
77
|
-
---
|
|
14
|
+
1. **探测安装版本**(按顺序,命中即停)
|
|
15
|
+
```bash
|
|
16
|
+
python -c "import dwyeapi; print(dwyeapi.__version__)"
|
|
17
|
+
```
|
|
18
|
+
失败则读项目 `pyproject.toml` / `uv.lock` 里 `dwyeapi` 约束。
|
|
19
|
+
仍失败:按 `v0` 给脚手架,并声明「未探测到安装版本」。
|
|
78
20
|
|
|
79
|
-
|
|
21
|
+
2. **选线**:读同目录 `versions.yml`。`0.y.z` → `v0/`,`1.y.z` → `v1/`,`2.y.z` → `v2/`。目录不存在(例如装了 1.x 但还没有 `v1/`)→ 不要用 `v0` 冒充;按下面「读源码」拿该版本代码,并说明文档线未建。
|
|
80
22
|
|
|
81
|
-
|
|
82
|
-
- 消费方装的是不同版本(更新或更旧)时,**必须**按"查 API 标准动作"流程读 .py,不要默认本文档准确
|
|
83
|
-
- dwyeapi < 0.9.0 可能没有 dt / tasks / providers 模块:先 `ls <site-packages>/dwyeapi/` 确认存在再用
|
|
84
|
-
- 主版本号变更(0.x → 1.x)时,本文档的索引可能完全失效:先 `ls <site-packages>/dwyeapi/` 重建认知
|
|
23
|
+
3. **读文档**:先 `shared/`(跨大版本不变),再该线 `vN/`。能力标了版本下限(如 `canonicalize_email` ≥0.9.6)时,安装版本低于下限 → 当不存在。
|
|
85
24
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
源码读得出来但容易忽略的"踩坑经验",写后端代码时遵守:
|
|
91
|
-
|
|
92
|
-
### 业务层架构(service / router 分层)
|
|
93
|
-
|
|
94
|
-
- **service 层**抛业务异常(`NotFoundError("用户")` / `BusinessError("余额不足", code="INSUFFICIENT_BALANCE")` 等),不感知 HTTP 概念
|
|
95
|
-
- **router 层**不写 try/except,由 `register_exception_handlers(app)` 统一转成 `ApiResponse` 信封 JSON
|
|
96
|
-
- **禁止**在 service 层抛 `HTTPException` —— 会绕过统一 handler,破坏响应结构一致性
|
|
97
|
-
|
|
98
|
-
### 时间处理(避免时区 bug)
|
|
99
|
-
|
|
100
|
-
- 业务时间统一中国时区,**禁止 `datetime.now()`**,统一走 `dwyeapi.dt`
|
|
101
|
-
- `dt.now()` 返回 naive datetime(Asia/Shanghai,用于数据库存储)
|
|
102
|
-
- `dt.utc_now()` 返回 aware UTC datetime(用于 JWT exp 等协议字段)
|
|
103
|
-
- 这两个不能混用:数据库列存 naive 中国时间、JWT exp 存 UTC aware
|
|
104
|
-
|
|
105
|
-
### 配置
|
|
106
|
-
|
|
107
|
-
- **没有 `debug` 字段**,所有调试开关统一用 `is_dev()`:避免两个维度(debug + environment)冲突
|
|
108
|
-
- `environment` 默认 `"prod"`(误配置时保守):生产环境必须显式关闭 docs/redoc/openapi 三个端点(`docs_url="/docs" if is_dev() else None`),否则路由元信息会泄露给攻击者
|
|
109
|
-
- `BaseSettings` 必填三个字段:`database_url` / `redis_url` / `secret_key`(继承时不能给默认值)
|
|
110
|
-
|
|
111
|
-
### Redis 连接
|
|
112
|
-
|
|
113
|
-
- `cache.configure(redis_url)` 启动时调用一次(lifespan);`cache.get_redis()` 首次调用懒建立连接
|
|
114
|
-
- `close_redis()` 可重复调用(安全幂等),lifespan 关闭阶段调用
|
|
115
|
-
- 未 configure 直接 `get_redis()` 会抛 `RuntimeError`
|
|
116
|
-
|
|
117
|
-
### tasks 模块
|
|
118
|
-
|
|
119
|
-
- 需要 `pip install dwyeapi[tasks]` 单独装 extra(避免不用任务的项目背 ARQ 依赖)
|
|
120
|
-
- 任务函数签名固定 `async def task(ctx: TaskContext, params: dict)`,第一个参数 ctx 由框架注入
|
|
121
|
-
- 取消机制是协作式:任务函数内必须主动 `await ctx.is_cancelled()` 检查,否则取消信号无效
|
|
122
|
-
- 完整细节读 `references/tasks-integration-guide.md`
|
|
25
|
+
4. **文档不够 / 和记忆冲突 / 下限对不上** → 读代码,顺序如下(命中即停):
|
|
26
|
+
1. clone 安装版本对应 tag(见下)
|
|
27
|
+
2. tag 不存在 / clone 失败 → 读**当前虚拟环境**里已安装的 `dwyeapi` 源码
|
|
28
|
+
3. 虚拟环境也没有 → **停下来问人**,禁止猜 API,禁止拿旁边 dwy-shared master 冒充
|
|
123
29
|
|
|
124
|
-
|
|
30
|
+
## 读源码
|
|
125
31
|
|
|
126
|
-
|
|
127
|
-
- 验证码生成 + Redis 存储 + 品牌化模板 由基类全部复用,子类只管"发出去"
|
|
32
|
+
### 1. clone tag(优先)
|
|
128
33
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
-
|
|
138
|
-
|
|
139
|
-
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
## 典型项目接入流程
|
|
144
|
-
|
|
145
|
-
```python
|
|
146
|
-
# config.py
|
|
147
|
-
from dwyeapi.config import BaseSettings
|
|
148
|
-
|
|
149
|
-
class Settings(BaseSettings):
|
|
150
|
-
app_name: str = "My API"
|
|
151
|
-
service_name: str = "my-api"
|
|
152
|
-
|
|
153
|
-
settings = Settings() # 从 .env / 环境变量读 database_url、redis_url、secret_key
|
|
154
|
-
|
|
155
|
-
# database.py
|
|
156
|
-
from dwyeapi.database import create_async_engine_factory, create_session_factory
|
|
157
|
-
from dwyeapi.dependencies import create_get_db
|
|
158
|
-
from dwyeapi import is_dev
|
|
159
|
-
|
|
160
|
-
engine = create_async_engine_factory(settings.database_url, echo=is_dev())
|
|
161
|
-
session_factory = create_session_factory(engine)
|
|
162
|
-
get_db = create_get_db(session_factory)
|
|
163
|
-
|
|
164
|
-
# main.py
|
|
165
|
-
from contextlib import asynccontextmanager
|
|
166
|
-
from fastapi import FastAPI
|
|
167
|
-
from dwyeapi import cache, logger, is_dev
|
|
168
|
-
from dwyeapi.exceptions import register_exception_handlers
|
|
169
|
-
from dwyeapi.health import create_health_router
|
|
170
|
-
|
|
171
|
-
@asynccontextmanager
|
|
172
|
-
async def lifespan(app: FastAPI):
|
|
173
|
-
logger.configure(level="INFO", log_dir="./logs", filename="app")
|
|
174
|
-
cache.configure(settings.redis_url)
|
|
175
|
-
yield
|
|
176
|
-
await cache.close_redis()
|
|
177
|
-
logger.close()
|
|
178
|
-
|
|
179
|
-
app = FastAPI(
|
|
180
|
-
lifespan=lifespan,
|
|
181
|
-
docs_url="/docs" if is_dev() else None,
|
|
182
|
-
redoc_url="/redoc" if is_dev() else None,
|
|
183
|
-
openapi_url="/openapi.json" if is_dev() else None,
|
|
184
|
-
)
|
|
185
|
-
register_exception_handlers(app)
|
|
186
|
-
app.include_router(create_health_router(service_name=settings.service_name, version="1.0.0"))
|
|
34
|
+
```bash
|
|
35
|
+
VER=0.9.6 # 换成第 1 步探测到的版本
|
|
36
|
+
TAG="dwyeapi@${VER}"
|
|
37
|
+
DEST=".dwy/eapi-src/${VER}" # 项目内缓存,已 gitignore
|
|
38
|
+
|
|
39
|
+
if [ ! -d "$DEST/backend/src/dwyeapi" ]; then
|
|
40
|
+
git clone --depth 1 --branch "$TAG" \
|
|
41
|
+
--filter=blob:none --sparse \
|
|
42
|
+
https://github.com/danweiyuancircle/dwy-ai-experience.git \
|
|
43
|
+
"$DEST"
|
|
44
|
+
git -C "$DEST" sparse-checkout set backend/src/dwyeapi
|
|
45
|
+
fi
|
|
46
|
+
# 只读 $DEST/backend/src/dwyeapi/ 的模块 docstring
|
|
187
47
|
```
|
|
188
48
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
## 响应契约速查(前后端对齐用)
|
|
49
|
+
- `--branch` 必须是 `dwyeapi@x.y.z`,禁止 clone 默认 branch。
|
|
50
|
+
- 必须 sparse,只要 `backend/src/dwyeapi`。
|
|
192
51
|
|
|
193
|
-
|
|
52
|
+
### 2. 虚拟环境(clone 拿不到时)
|
|
194
53
|
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
"data": { ... },
|
|
200
|
-
"timestamp": 1775625000
|
|
201
|
-
}
|
|
54
|
+
```bash
|
|
55
|
+
python -c "import dwyeapi, os; print(os.path.dirname(dwyeapi.__file__))"
|
|
56
|
+
# 输出形如 .venv/lib/python3.11/site-packages/dwyeapi
|
|
57
|
+
# 读该目录下模块 docstring
|
|
202
58
|
```
|
|
203
59
|
|
|
204
|
-
|
|
205
|
-
- 失败:`code` 是业务错误码(如 `NOT_FOUND` / `BUSINESS_ERROR` / `VALIDATION_ERROR` 等),HTTP 状态保持语义(404 就是 404)
|
|
206
|
-
- 校验错误 `VALIDATION_ERROR`:`data.errors: [{field, message}]`,field 含 `body.` / `query.` / `path.` 前缀
|
|
207
|
-
|
|
208
|
-
构造响应:用 `ApiResponse.ok(data)` 或 `ApiResponse.page(items, total, page, page_size)`,不直接实例化 `ApiResponse`。
|
|
60
|
+
editable 安装时这个路径就是消费方正在跑的代码,当作安装版本真相。
|
|
209
61
|
|
|
210
|
-
|
|
62
|
+
文档与源码冲突:信源码。clone 与 venv 都读到且不一致:以 **venv** 为准(editable / 热修),回复里写明。
|
|
211
63
|
|
|
212
|
-
|
|
64
|
+
## 文档地图
|
|
213
65
|
|
|
214
|
-
|
|
66
|
+
| 路径 | 何时读 |
|
|
67
|
+
|------|--------|
|
|
68
|
+
| `shared/layering.md` | 分层、禁 HTTPException |
|
|
69
|
+
| `shared/timezone.md` | 禁 `datetime.now` |
|
|
70
|
+
| `shared/response-envelope.md` | ApiResponse 信封 |
|
|
71
|
+
| `shared/anticorruption.md` | 禁止直调 redis/jwt/logging |
|
|
72
|
+
| `v0/index.md` | 0.x 模块清单、接入、陷阱 |
|
|
73
|
+
| `v0/providers-email.md` | 邮件 Provider 注册表 |
|
|
74
|
+
| `v0/email-canonical.md` | Gmail 规范化(≥0.9.6)+ 常见域名白名单 / 发码校验(≥0.10.0) |
|
|
75
|
+
| `v0/breaking.md` | 0.x 内部 breaking |
|
|
76
|
+
| `references/tasks-integration-guide.md` | 只用 tasks 时读 |
|
|
215
77
|
|
|
216
|
-
|
|
78
|
+
## 禁止
|
|
217
79
|
|
|
218
|
-
|
|
|
80
|
+
| 借口 | 实际 |
|
|
219
81
|
|------|------|
|
|
220
|
-
|
|
|
221
|
-
|
|
|
222
|
-
|
|
|
223
|
-
|
|
|
224
|
-
| 直接 `raise HTTPException(404, ...)` | `raise NotFoundError("资源名")` |
|
|
225
|
-
| 自己写 `{"code": ..., "data": ...}` dict 当响应 | `ApiResponse.ok(data)` / `ApiResponse.page(...)` |
|
|
226
|
-
| 自己写 `?page=&page_size=` Query 参数 | `params: PaginationParams = Depends()` |
|
|
82
|
+
| 读旁边 dwy-shared master | 消费方版本可能不同。先 clone 安装版本的 tag,不行再读本项目 venv |
|
|
83
|
+
| 文档是 0.9,装的 0.9.6,差不多 | 有下限的能力必须对照版本 |
|
|
84
|
+
| tag 没有就猜 / 直接停 | clone 失败后读 venv;venv 也没有才停 |
|
|
85
|
+
| 再开一个 `dwy-eapi-v0` skill | 对外只有 `dwy-eapi` |
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# 防腐层
|
|
2
|
+
|
|
3
|
+
业务代码禁止直调底层 API:
|
|
4
|
+
|
|
5
|
+
| 禁止 | 改用 |
|
|
6
|
+
|------|------|
|
|
7
|
+
| `datetime.now()` / `datetime.utcnow()` | `dt.now()` / `dt.utc_now()` |
|
|
8
|
+
| `import logging; logging.getLogger()` | `from dwyeapi import logger; logger.get_logger()` |
|
|
9
|
+
| 手写 `from redis.asyncio import Redis` 全局变量 | `cache.configure()` + `cache.get_redis()` |
|
|
10
|
+
| 手写 JWT encode/decode(含 `import jwt`) | `security.create_token()` / `security.decode_token()` |
|
|
11
|
+
| 直接 `raise HTTPException(404, ...)` | `raise NotFoundError("资源名")` |
|
|
12
|
+
| 自己写 `{"code": ..., "data": ...}` dict | `ApiResponse.ok(data)` / `ApiResponse.page(...)` |
|
|
13
|
+
| 自己写 `?page=&page_size=` Query | `params: PaginationParams = Depends()` |
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# 响应信封
|
|
2
|
+
|
|
3
|
+
所有接口返回:
|
|
4
|
+
|
|
5
|
+
```json
|
|
6
|
+
{
|
|
7
|
+
"code": "SUCCESS",
|
|
8
|
+
"message": "success",
|
|
9
|
+
"data": {},
|
|
10
|
+
"timestamp": 1775625000
|
|
11
|
+
}
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
- 成功:`code = "SUCCESS"`,`data` 是业务载荷(单体或 `PageData[T]`)
|
|
15
|
+
- 失败:`code` 是业务错误码(`NOT_FOUND` / `BUSINESS_ERROR` / `VALIDATION_ERROR`),HTTP 状态保持语义
|
|
16
|
+
- 校验错误 `VALIDATION_ERROR`:`data.errors: [{field, message}]`,field 含 `body.` / `query.` / `path.` 前缀
|
|
17
|
+
|
|
18
|
+
构造:`ApiResponse.ok(data)` 或 `ApiResponse.page(items, total, page, page_size)`,不直接实例化成功态。
|
|
19
|
+
|
|
20
|
+
错误响应不要业务代码拼信封 —— `raise NotFoundError("用户")`,handler 自动转。
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# 0.x 内部 breaking
|
|
2
|
+
|
|
3
|
+
0.x 的 minor 也可以不兼容。整条线仍用 `v0/`,破坏点记在这里。细节 clone `dwyeapi@x.y.z`,不要另开 `v0.10/` 目录。
|
|
4
|
+
|
|
5
|
+
完整段落以该 tag 的 `backend/CHANGELOG.md` 为准。
|
|
6
|
+
|
|
7
|
+
| 版本 | 破坏点 |
|
|
8
|
+
|------|--------|
|
|
9
|
+
| 0.6.0 | 信封改为 `ApiResponse[T]` / `PageData[T]`;删除 `success` / `fail` / `paginated`;`code` 成功态从 int `200` 改为 `"SUCCESS"` |
|
|
10
|
+
| 0.9.0 | 删除 `MockEmailProvider`、`AliyunEmailProvider`、整个 `providers.sms`;`EMAIL__PROVIDER` 改为自由字符串;mock/阿里云改业务 `register_email_provider` |
|
|
11
|
+
| 0.10.0 | `send_code` 默认校验常见邮箱域名;非白名单抛 `EMAIL_DOMAIN_NOT_ALLOWED`。B2B 设 `EMAIL__REQUIRE_COMMON_DOMAIN=false` |
|
|
12
|
+
|
|
13
|
+
0.9.6 起新增 `dwyeapi.email` 与 Redis key 规范化,对旧调用方兼容(发码 key 折到同一收件箱)。能力下限见 `email-canonical.md`。
|
|
14
|
+
|
|
15
|
+
常见域名白名单:`send_code` 默认启用 `require_common_email_domain`(≥0.10.0,`EMAIL__REQUIRE_COMMON_DOMAIN=true`)。B2B 可关。
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# 邮箱规范化与常见域名白名单(≥0.9.6)
|
|
2
|
+
|
|
3
|
+
安装版本 `< 0.9.6`:没有 `dwyeapi.email`,不要 import。
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
from dwyeapi.email import canonicalize_email, is_folded_alias, GMAIL_DOMAINS
|
|
7
|
+
|
|
8
|
+
canonicalize_email("Bai.Wen+1@Gmail.com") # baiwen@gmail.com
|
|
9
|
+
canonicalize_email("a.b@googlemail.com") # ab@gmail.com
|
|
10
|
+
canonicalize_email("a+x@icloud.com") # a+x@icloud.com
|
|
11
|
+
is_folded_alias("a+x@gmail.com") # True
|
|
12
|
+
is_folded_alias("A@gmail.com") # False
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
- 注册查重、登录查找、用户表写入 **必须** 走 `canonicalize_email`
|
|
16
|
+
- Gmail 把 plus / 点号 / `googlemail.com` 当成同一收件箱,不折会开多个号、绕过发码冷却
|
|
17
|
+
- 其它域名只做 strip + lower,保留 `+` 与点
|
|
18
|
+
- `send_code` / `verify_code` 的 Redis key 已自动规范化;发信信封仍用用户输入地址
|
|
19
|
+
|
|
20
|
+
## 常见域名白名单(≥0.10.0)
|
|
21
|
+
|
|
22
|
+
安装版本 `< 0.10.0`:没有 `is_common_email_domain`,不要 import。一次性邮箱会换马甲域,黑名单跟不上。
|
|
23
|
+
|
|
24
|
+
**`send_code` 默认校验**(`EMAIL__REQUIRE_COMMON_DOMAIN=true`)。不在白名单:抛 `BusinessError(code="EMAIL_DOMAIN_NOT_ALLOWED")`,不发信、不写 Redis。B2B 可关。
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from dwyeapi.email import require_common_email_domain
|
|
28
|
+
from dwyeapi.providers.email import EmailSettings
|
|
29
|
+
|
|
30
|
+
# 发码已默认走白名单;业务也可在注册/换绑再调一次
|
|
31
|
+
email = require_common_email_domain(raw)
|
|
32
|
+
|
|
33
|
+
# .env
|
|
34
|
+
# EMAIL__REQUIRE_COMMON_DOMAIN=true
|
|
35
|
+
# EMAIL__EXTRA_ALLOW_DOMAINS=chances.com.cn
|
|
36
|
+
# EMAIL__ALLOW_EDU=true
|
|
37
|
+
# EMAIL__ALLOW_EDU_CN=true
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
- 精确匹配 `COMMON_EMAIL_DOMAINS`(qq / 163 / gmail / outlook / icloud / proton 等),`mail.qq.com` 不等于 `qq.com`
|
|
41
|
+
- 默认放行美国 `.edu`(`columbia.edu`)与 `edu.cn` / `*.edu.cn`;**不**放行 `edu.kg` / `edu.pl` 等国家教育后缀
|
|
42
|
+
- 企业 / 机构域用 `extra_allow` / `EMAIL__EXTRA_ALLOW_DOMAINS`(如 `chances.com.cn`),不要把公司域写进框架常量表
|
|
43
|
+
- 自定义 provider 的 factory 必须把 `require_common_domain` / `extra_allow_domain_list()` / `allow_edu` / `allow_edu_cn` 传给 `EmailProviderBase`
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# dwyeapi 0.x
|
|
2
|
+
|
|
3
|
+
精确签名以安装版本的源码为准(见上级 SKILL.md:先 clone tag,拿不到再读 venv)。本页是 0.x 导航与陷阱。
|
|
4
|
+
|
|
5
|
+
跨大版本不变的分层 / 时区 / 信封 / 防腐层在 `shared/`。
|
|
6
|
+
|
|
7
|
+
## 模块清单(当前 0.9 线)
|
|
8
|
+
|
|
9
|
+
| 模块 | 用途 | 下限 |
|
|
10
|
+
|------|------|------|
|
|
11
|
+
| `config` | BaseSettings + `is_dev` / `is_prod` / `get_environment` | |
|
|
12
|
+
| `exceptions` | AppError + FastAPI handler | |
|
|
13
|
+
| `database` | 异步 engine/session + Base + TimestampMixin | |
|
|
14
|
+
| `dependencies` | `get_db` 工厂 | |
|
|
15
|
+
| `security` | JWT + bcrypt | |
|
|
16
|
+
| `cache` | 异步 Redis | |
|
|
17
|
+
| `response` | `ApiResponse[T]` + `PageData[T]` | ≥0.6.0 |
|
|
18
|
+
| `pagination` | PaginationParams + paginate | |
|
|
19
|
+
| `dt` | 中国时区时间工具 | ≥0.7 左右;`timedelta`/`after` ≥0.9.4 |
|
|
20
|
+
| `logger` | loguru | |
|
|
21
|
+
| `masking` | PII 脱敏 | |
|
|
22
|
+
| `email` | Gmail 规范化 + 常见域名白名单 | 规范化 ≥0.9.6;白名单 / 发码校验 ≥0.10.0 |
|
|
23
|
+
| `health` | 只探活健康检查 | ≥0.7.0 |
|
|
24
|
+
| `tasks` | ARQ 异步任务,需 `[tasks]` | ≥0.7 后 |
|
|
25
|
+
| `providers.email` | 邮件验证码,内置 resend | 工厂注册表 ≥0.9.0 |
|
|
26
|
+
|
|
27
|
+
`<0.9.0` 可能没有 dt / tasks / providers。先 `ls` 安装目录或 clone 该 tag 再调用。
|
|
28
|
+
|
|
29
|
+
## 接入骨架
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from contextlib import asynccontextmanager
|
|
33
|
+
from fastapi import FastAPI
|
|
34
|
+
from dwyeapi import cache, logger, is_dev
|
|
35
|
+
from dwyeapi.config import BaseSettings
|
|
36
|
+
from dwyeapi.database import create_async_engine_factory, create_session_factory
|
|
37
|
+
from dwyeapi.dependencies import create_get_db
|
|
38
|
+
from dwyeapi.exceptions import register_exception_handlers
|
|
39
|
+
from dwyeapi.health import create_health_router
|
|
40
|
+
|
|
41
|
+
class Settings(BaseSettings):
|
|
42
|
+
app_name: str = "My API"
|
|
43
|
+
service_name: str = "my-api"
|
|
44
|
+
|
|
45
|
+
settings = Settings()
|
|
46
|
+
engine = create_async_engine_factory(settings.database_url, echo=is_dev())
|
|
47
|
+
session_factory = create_session_factory(engine)
|
|
48
|
+
get_db = create_get_db(session_factory)
|
|
49
|
+
|
|
50
|
+
@asynccontextmanager
|
|
51
|
+
async def lifespan(app: FastAPI):
|
|
52
|
+
logger.configure(level="INFO", log_dir="./logs", filename="app")
|
|
53
|
+
cache.configure(settings.redis_url)
|
|
54
|
+
yield
|
|
55
|
+
await cache.close_redis()
|
|
56
|
+
logger.close()
|
|
57
|
+
|
|
58
|
+
app = FastAPI(
|
|
59
|
+
lifespan=lifespan,
|
|
60
|
+
docs_url="/docs" if is_dev() else None,
|
|
61
|
+
redoc_url="/redoc" if is_dev() else None,
|
|
62
|
+
openapi_url="/openapi.json" if is_dev() else None,
|
|
63
|
+
)
|
|
64
|
+
register_exception_handlers(app)
|
|
65
|
+
app.include_router(create_health_router(service_name=settings.service_name, version="1.0.0"))
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## 0.x 陷阱
|
|
69
|
+
|
|
70
|
+
### 配置
|
|
71
|
+
|
|
72
|
+
- **没有 `debug` 字段**,调试开关用 `is_dev()`
|
|
73
|
+
- `environment` 默认 `"prod"`。非 dev 必须关掉 docs/redoc/openapi
|
|
74
|
+
- `BaseSettings` 必填:`database_url` / `redis_url` / `secret_key`(子类不能给默认值)
|
|
75
|
+
|
|
76
|
+
### Redis
|
|
77
|
+
|
|
78
|
+
- `cache.configure(redis_url)` 启动时一次;`get_redis()` 懒连接
|
|
79
|
+
- `close_redis()` 可重复调用
|
|
80
|
+
- 未 configure 直接 `get_redis()` → `RuntimeError`
|
|
81
|
+
|
|
82
|
+
### tasks
|
|
83
|
+
|
|
84
|
+
- `pip install dwyeapi[tasks]`
|
|
85
|
+
- 签名 `async def task(ctx: TaskContext, params: dict)`
|
|
86
|
+
- 取消是协作式:必须 `await ctx.is_cancelled()`
|
|
87
|
+
- 细节:`references/tasks-integration-guide.md`
|
|
88
|
+
|
|
89
|
+
### 日志
|
|
90
|
+
|
|
91
|
+
- `logger.configure()` 启动一次;`logger.close()` 在 lifespan 结束
|
|
92
|
+
- `intercept_stdlib=True`(默认)接管 uvicorn / SQLAlchemy
|
|
93
|
+
- 多进程必须 `enqueue=True`(默认)
|
|
94
|
+
|
|
95
|
+
### 健康检查
|
|
96
|
+
|
|
97
|
+
- `create_health_router()` **只**探活,**不**碰 PostgreSQL / Redis
|
|
98
|
+
- readiness 业务自建并限制内网
|
|
99
|
+
|
|
100
|
+
### 邮件 / Gmail
|
|
101
|
+
|
|
102
|
+
- Provider:`v0/providers-email.md`
|
|
103
|
+
- 规范化 / 常见域名白名单:`v0/email-canonical.md`(规范化 ≥0.9.6;白名单 / 发码校验 ≥0.10.0)
|