create-dwy 0.10.0 → 0.12.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/package.json +3 -2
- package/src/prompts/grouped-checkbox.js +223 -0
- package/src/sync.js +61 -39
- package/templates/claude-global/hooks/pre-git-commit-sensitive-check.sh +0 -0
- package/templates/claude-global/rules/{dwy-python-backend.md → Python/dwy-python-backend.md} +43 -3
- package/templates/claude-global/rules/{dwy-python-core.md → Python/dwy-python-core.md} +35 -2
- package/templates/claude-global/skills//345/237/272/347/241/200/345/272/223/dwy-eapi/SKILL.md +226 -0
- package/templates/claude-global/skills//346/225/260/346/215/256/345/272/223/dwy-dolphindb/SKILL.md +144 -0
- package/templates/claude-global/skills//346/225/260/346/215/256/345/272/223/dwy-dolphindb/references/create-database-table.md +219 -0
- package/templates/claude-global/skills//346/225/260/346/215/256/345/272/223/dwy-dolphindb/references/crud-operations.md +281 -0
- package/templates/claude-global/skills//346/225/260/346/215/256/345/272/223/dwy-dolphindb/references/data-import.md +271 -0
- package/templates/claude-global/skills//346/225/260/346/215/256/345/272/223/dwy-dolphindb/references/docker-deployment.md +63 -0
- package/templates/claude-global/skills//346/225/260/346/215/256/345/272/223/dwy-dolphindb/references/performance-tuning.md +223 -0
- package/templates/claude-global/skills//346/225/260/346/215/256/345/272/223/dwy-dolphindb/references/pitfalls-and-best-practices.md +352 -0
- package/templates/claude-global/skills//346/225/260/346/215/256/345/272/223/dwy-dolphindb/references/python-api.md +306 -0
- package/templates/claude-global/skills//346/225/260/346/215/256/345/272/223/dwy-dolphindb/references/setup-and-connection.md +143 -0
- package/templates/claude-global/skills//346/225/260/346/215/256/345/272/223/dwy-dolphindb/references/sources.md +46 -0
- package/templates/claude-global/skills//346/225/260/346/215/256/345/272/223/dwy-dolphindb/references/sql-batch-compute.md +206 -0
- package/templates/claude-global/skills//350/277/220/347/273/264/345/217/221/345/270/203/dwy-cibuildwheel/SKILL.md +354 -0
- package/templates/claude-global/skills//350/277/220/347/273/264/345/217/221/345/270/203/dwy-cibuildwheel/references/release-workflow-template.yml +99 -0
- package/templates/claude-global/skills/{dwy-deploy-audit → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-deploy-audit}/scripts/check_base.sh +0 -0
- package/templates/claude-global/skills/{dwy-deploy-audit → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-deploy-audit}/scripts/check_capacity.sh +0 -0
- package/templates/claude-global/skills/{dwy-deploy-audit → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-deploy-audit}/scripts/check_db.sh +0 -0
- package/templates/claude-global/skills/{dwy-deploy-audit → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-deploy-audit}/scripts/check_docker.sh +0 -0
- package/templates/claude-global/skills/{dwy-deploy-audit → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-deploy-audit}/scripts/check_env.sh +0 -0
- package/templates/claude-global/skills/{dwy-deploy-audit → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-deploy-audit}/scripts/check_https.sh +0 -0
- package/templates/claude-global/skills/{dwy-deploy-audit → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-deploy-audit}/scripts/check_logs.sh +0 -0
- package/templates/claude-global/skills/{dwy-deploy-audit → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-deploy-audit}/scripts/check_nginx.sh +0 -0
- package/templates/claude-global/skills/{dwy-deploy-audit → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-deploy-audit}/scripts/check_resilience.sh +0 -0
- package/templates/claude-global/skills/{dwy-deploy-audit → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-deploy-audit}/scripts/check_secrets.sh +0 -0
- package/templates/claude-global/skills/{dwy-deploy-audit → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-deploy-audit}/scripts/check_services.sh +0 -0
- package/templates/claude-global/skills/{dwy-deploy-audit → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-deploy-audit}/scripts/check_ssh.sh +0 -0
- package/templates/claude-global/skills/{dwy-deploy-audit → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-deploy-audit}/scripts/run_all.sh +0 -0
- package/templates/claude-global/skills/{dwy-docker → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-docker}/scripts/query_dockerhub.py +0 -0
- package/templates/claude-global/skills/{dwy-docker → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-docker}/scripts/scan_images.sh +0 -0
- package/templates/claude-global/skills/{dwy-mirror-source → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-mirror-source}/scripts/apply_mirrors.py +0 -0
- package/templates/claude-global/skills/{dwy-mirror-source → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-mirror-source}/scripts/check_mirrors.py +0 -0
- package/templates/claude-global/skills/dwy-dolphindb/SKILL.md +0 -98
- package/templates/claude-global/skills/dwy-dolphindb/evals/evals.json +0 -67
- package/templates/claude-global/skills/dwy-dolphindb/references/dolphindb-rules.md +0 -441
- package/templates/claude-global/skills/dwy-eapi/SKILL.md +0 -495
- package/templates/claude-global/skills/dwy-eapi/references/health.md +0 -47
- package/templates/claude-global/skills/dwy-eapi/references/logger.md +0 -60
- /package/templates/claude-global/rules/{dwy-android-core.md → Android/dwy-android-core.md} +0 -0
- /package/templates/claude-global/rules/{dwy-flutter-core.md → Flutter/dwy-flutter-core.md} +0 -0
- /package/templates/claude-global/rules/{dwy-flutter-network.md → Flutter/dwy-flutter-network.md} +0 -0
- /package/templates/claude-global/rules/{dwy-flutter-router.md → Flutter/dwy-flutter-router.md} +0 -0
- /package/templates/claude-global/rules/{dwy-flutter-testing.md → Flutter/dwy-flutter-testing.md} +0 -0
- /package/templates/claude-global/rules/{dwy-python-testing.md → Python/dwy-python-testing.md} +0 -0
- /package/templates/claude-global/rules/{dwy-vue-core.md → Vue/dwy-vue-core.md} +0 -0
- /package/templates/claude-global/rules/{dwy-vue-pinia.md → Vue/dwy-vue-pinia.md} +0 -0
- /package/templates/claude-global/rules/{dwy-vue-testing.md → Vue/dwy-vue-testing.md} +0 -0
- /package/templates/claude-global/rules/{dwy-tdd-development.md → /345/274/200/345/217/221/346/265/201/347/250/213/dwy-tdd-development.md"} +0 -0
- /package/templates/claude-global/rules/{dwy-db-migration.md → /346/225/260/346/215/256/345/272/223/dwy-db-migration.md"} +0 -0
- /package/templates/claude-global/rules/{dwy-postgres.md → /346/225/260/346/215/256/345/272/223/dwy-postgres.md"} +0 -0
- /package/templates/claude-global/rules/{dwy-redis.md → /346/225/260/346/215/256/345/272/223/dwy-redis.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-eapi/references/tasks-integration-guide.md → /345/237/272/347/241/200/345/272/223/dwy-eapi/references/tasks-integration-guide.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-ekit/SKILL.md → /345/237/272/347/241/200/345/272/223/dwy-ekit/SKILL.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-eui/SKILL.md → /345/237/272/347/241/200/345/272/223/dwy-eui/SKILL.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-eui/references/eui-design-guide.md → /345/237/272/347/241/200/345/272/223/dwy-eui/references/eui-design-guide.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-eui/references/eui-integration-guide.md → /345/237/272/347/241/200/345/272/223/dwy-eui/references/eui-integration-guide.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-eui/references/eui-landing-design-guide.md → /345/237/272/347/241/200/345/272/223/dwy-eui/references/eui-landing-design-guide.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/SKILL.md → /345/256/211/345/205/250/dwy-pentest/SKILL.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/.python-version → /345/256/211/345/205/250/dwy-pentest/scripts/.python-version"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/lib/__init__.py → /345/256/211/345/205/250/dwy-pentest/scripts/lib/__init__.py"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/lib/__pycache__/__init__.cpython-311.pyc → /345/256/211/345/205/250/dwy-pentest/scripts/lib/__pycache__/__init__.cpython-311.pyc"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/lib/__pycache__/config.cpython-311.pyc → /345/256/211/345/205/250/dwy-pentest/scripts/lib/__pycache__/config.cpython-311.pyc"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/lib/__pycache__/executor.cpython-311.pyc → /345/256/211/345/205/250/dwy-pentest/scripts/lib/__pycache__/executor.cpython-311.pyc"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/lib/__pycache__/severity.cpython-311.pyc → /345/256/211/345/205/250/dwy-pentest/scripts/lib/__pycache__/severity.cpython-311.pyc"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/lib/__pycache__/validator.cpython-311.pyc → /345/256/211/345/205/250/dwy-pentest/scripts/lib/__pycache__/validator.cpython-311.pyc"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/lib/config.py → /345/256/211/345/205/250/dwy-pentest/scripts/lib/config.py"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/lib/executor.py → /345/256/211/345/205/250/dwy-pentest/scripts/lib/executor.py"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/lib/severity.py → /345/256/211/345/205/250/dwy-pentest/scripts/lib/severity.py"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/lib/validator.py → /345/256/211/345/205/250/dwy-pentest/scripts/lib/validator.py"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/pentest_auth.py → /345/256/211/345/205/250/dwy-pentest/scripts/pentest_auth.py"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/pentest_recon.py → /345/256/211/345/205/250/dwy-pentest/scripts/pentest_recon.py"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/pentest_report.py → /345/256/211/345/205/250/dwy-pentest/scripts/pentest_report.py"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/pentest_scan.py → /345/256/211/345/205/250/dwy-pentest/scripts/pentest_scan.py"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/pentest_ssl.py → /345/256/211/345/205/250/dwy-pentest/scripts/pentest_ssl.py"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/pentest_vuln.py → /345/256/211/345/205/250/dwy-pentest/scripts/pentest_vuln.py"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/pentest_web.py → /345/256/211/345/205/250/dwy-pentest/scripts/pentest_web.py"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/pyproject.toml → /345/256/211/345/205/250/dwy-pentest/scripts/pyproject.toml"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/setup_tools.sh → /345/256/211/345/205/250/dwy-pentest/scripts/setup_tools.sh"} +0 -0
- /package/templates/claude-global/skills/{dwy-pentest/scripts/uv.lock → /345/256/211/345/205/250/dwy-pentest/scripts/uv.lock"} +0 -0
- /package/templates/claude-global/skills/{dwy-dev-qa/SKILL.md → /345/274/200/345/217/221/346/265/201/347/250/213/dwy-dev-qa/SKILL.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-git-commit/SKILL.md → /345/274/200/345/217/221/346/265/201/347/250/213/dwy-git-commit/SKILL.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-fullstack-scaffold/SKILL.md → /350/204/232/346/211/213/346/236/266/dwy-fullstack-scaffold/SKILL.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-tech-stack/SKILL.md → /350/204/232/346/211/213/346/236/266/dwy-tech-stack/SKILL.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-deploy-audit/SKILL.md → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-deploy-audit/SKILL.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-deploy-audit/references/checks-data.md → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-deploy-audit/references/checks-data.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-deploy-audit/references/checks-network.md → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-deploy-audit/references/checks-network.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-deploy-audit/references/checks-runtime.md → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-deploy-audit/references/checks-runtime.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-docker/SKILL.md → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-docker/SKILL.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-docker/references/ask-templates.md → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-docker/references/ask-templates.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-docker/references/templates.md → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-docker/references/templates.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-docker/references/version-rules.md → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-docker/references/version-rules.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-mirror-source/SKILL.md → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-mirror-source/SKILL.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-mirror-source/references/dockerfile-snippets.md → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-mirror-source/references/dockerfile-snippets.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-mirror-source/references/mirror-providers.md → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-mirror-source/references/mirror-providers.md"} +0 -0
- /package/templates/claude-global/skills/{dwy-mirror-source/scripts/__pycache__/check_mirrors.cpython-313.pyc → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-mirror-source/scripts/__pycache__/check_mirrors.cpython-313.pyc"} +0 -0
- /package/templates/claude-global/skills/{dwy-publish/SKILL.md → /350/277/220/347/273/264/345/217/221/345/270/203/dwy-publish/SKILL.md"} +0 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dwy-eapi
|
|
3
|
+
description: "dwyeapi FastAPI 后端基础设施使用指南。涉及以下任何主题,**必须**使用此 skill(即使用户没有明确说 'dwyeapi' / 'eapi'):FastAPI 项目搭建 / Pydantic Settings 配置 / 异步 SQLAlchemy / Redis 缓存 / JWT 认证 / bcrypt 密码哈希 / 异常体系(AppError / NotFoundError / BusinessError)/ 统一响应信封(ApiResponse / PageData)/ 分页 / 日志(loguru)/ 健康检查 / PII 脱敏 / 时区敏感的时间处理 / 异步任务(ARQ)/ 邮件验证码发送。本 skill 是 @dwydev/eapi 的唯一权威导航来源。"
|
|
4
|
+
eapi_baseline_version: "0.9.0"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# dwyeapi 后端基础设施使用指南
|
|
8
|
+
|
|
9
|
+
Python 3.11+ FastAPI 基础设施包,全异步。单文件模块设计(每个能力一个 .py),便于直接读源码。
|
|
10
|
+
|
|
11
|
+
本 skill 索引基于 dwyeapi **0.9.0**。消费方版本可能不同 — 见 [版本兼容规则](#版本兼容规则)。
|
|
12
|
+
|
|
13
|
+
## 模块清单(13 个)
|
|
14
|
+
|
|
15
|
+
| 模块 | 用途 |
|
|
16
|
+
|------|------|
|
|
17
|
+
| `config` | Pydantic BaseSettings + 运行环境识别 (is_dev / is_prod / get_environment) |
|
|
18
|
+
| `exceptions` | AppError 异常体系 + FastAPI 全局 handler 注册 |
|
|
19
|
+
| `database` | 异步 SQLAlchemy engine/session 工厂 + Base + TimestampMixin |
|
|
20
|
+
| `dependencies` | FastAPI 依赖注入工厂(如 get_db) |
|
|
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
|
+
---
|
|
78
|
+
|
|
79
|
+
## 版本兼容规则
|
|
80
|
+
|
|
81
|
+
- 本 skill `eapi_baseline_version: 0.9.0`,模块清单、踩坑提示都基于这个版本
|
|
82
|
+
- 消费方装的是不同版本(更新或更旧)时,**必须**按"查 API 标准动作"流程读 .py,不要默认本文档准确
|
|
83
|
+
- dwyeapi < 0.9.0 可能没有 dt / tasks / providers 模块:先 `ls <site-packages>/dwyeapi/` 确认存在再用
|
|
84
|
+
- 主版本号变更(0.x → 1.x)时,本文档的索引可能完全失效:先 `ls <site-packages>/dwyeapi/` 重建认知
|
|
85
|
+
|
|
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`
|
|
123
|
+
|
|
124
|
+
### 邮件 Provider
|
|
125
|
+
|
|
126
|
+
- 内置仅 `resend`。业务用其他通道(腾讯 SES / SMTP / 自建):继承 `EmailProviderBase` 实现 `_send` → `register_email_provider("name", factory)` → `.env` 设 `EMAIL__PROVIDER=name`
|
|
127
|
+
- 验证码生成 + Redis 存储 + 品牌化模板 由基类全部复用,子类只管"发出去"
|
|
128
|
+
|
|
129
|
+
### 日志
|
|
130
|
+
|
|
131
|
+
- `logger.configure()` 启动时调用一次;`logger.close()` 在 lifespan 结束时调用
|
|
132
|
+
- `intercept_stdlib=True`(默认)会把 stdlib logging(uvicorn / SQLAlchemy 等)也接管到统一 sink
|
|
133
|
+
- 多进程部署时必须 `enqueue=True`(默认)——避免日志文件竞争写
|
|
134
|
+
|
|
135
|
+
### 健康检查
|
|
136
|
+
|
|
137
|
+
- `create_health_router()` **只**探活、**不**触达 PostgreSQL / Redis 等依赖
|
|
138
|
+
- 理由:健康端点常对公网开放,内置 dependency check 会被攻击者用来 DoS 放大数据库/缓存
|
|
139
|
+
- 需要 readiness 的业务自行实现并限制内网访问
|
|
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"))
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## 响应契约速查(前后端对齐用)
|
|
192
|
+
|
|
193
|
+
所有接口返回统一信封:
|
|
194
|
+
|
|
195
|
+
```json
|
|
196
|
+
{
|
|
197
|
+
"code": "SUCCESS",
|
|
198
|
+
"message": "success",
|
|
199
|
+
"data": { ... },
|
|
200
|
+
"timestamp": 1775625000
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
- 成功:`code = "SUCCESS"`,`data` 是业务载荷(单体或 `PageData[T]`)
|
|
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`。
|
|
209
|
+
|
|
210
|
+
错误响应**不要业务代码构造** —— 直接 `raise NotFoundError("用户")`,handler 自动转。
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## 防腐层硬性约束(写代码前必读)
|
|
215
|
+
|
|
216
|
+
dwyeapi 把基础设施细节封装在自有 API 后面。业务代码**禁止**直接调下面这些底层 API,违反会导致升级困难、行为不一致:
|
|
217
|
+
|
|
218
|
+
| 禁止 | 改用 |
|
|
219
|
+
|------|------|
|
|
220
|
+
| `datetime.now()` / `datetime.utcnow()` | `dt.now()` / `dt.utc_now()` |
|
|
221
|
+
| `import logging; logging.getLogger()` | `from dwyeapi import logger; logger.get_logger()` |
|
|
222
|
+
| 手写 `from redis.asyncio import Redis` 全局变量 | `cache.configure()` + `cache.get_redis()` |
|
|
223
|
+
| 手写 JWT encode/decode(含 `import jwt`) | `security.create_token()` / `security.decode_token()` |
|
|
224
|
+
| 直接 `raise HTTPException(404, ...)` | `raise NotFoundError("资源名")` |
|
|
225
|
+
| 自己写 `{"code": ..., "data": ...}` dict 当响应 | `ApiResponse.ok(data)` / `ApiResponse.page(...)` |
|
|
226
|
+
| 自己写 `?page=&page_size=` Query 参数 | `params: PaginationParams = Depends()` |
|
package/templates/claude-global/skills//346/225/260/346/215/256/345/272/223/dwy-dolphindb/SKILL.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dwy-dolphindb
|
|
3
|
+
description: >
|
|
4
|
+
DolphinDB 3.0 社区版(2核8G / 1控2节点)开发与审查 skill:写 DolphinDB Script (.dos)、
|
|
5
|
+
用 Python SDK (dolphindb 3.0.4)、设计 DFS 分区表(TSDB/OLAP 引擎选型)、导入 tick/snapshot/
|
|
6
|
+
kline 金融数据、优化慢查询、排查 OOM / 卡死作业、Docker 部署。也用于代码审查:检查分区
|
|
7
|
+
裁剪(链式比较 `a <= col <= b`、函数包裹分区列)、批量写入 ≤500K 行、SYMBOL ≤255 字节、
|
|
8
|
+
STRING/BLOB 静默截断、输入校验防脚本注入、连接池配置、`pool.run` async/sync 用法。
|
|
9
|
+
关键字触发:dolphindb, DDB, loadTable, loadTextEx, createPartitionedTable, append!,
|
|
10
|
+
dropPartition, DBConnectionPool, tableAppender, PartitionedTableAppender,
|
|
11
|
+
MultithreadedTableWriter, addRangePartitions, newValuePartitionPolicy, TSDB, OLAP,
|
|
12
|
+
getClusterPerf, getConsoleJobs, level2 行情, tick 数据, 时序数据库, 8848 端口, .dos 脚本.
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# DolphinDB 3.0 社区版使用指南
|
|
16
|
+
|
|
17
|
+
## 当前环境
|
|
18
|
+
|
|
19
|
+
| 项 | 值 |
|
|
20
|
+
|---|---|
|
|
21
|
+
| 版本 | DolphinDB 3.0 社区版 |
|
|
22
|
+
| 拓扑 | 1 控制节点 + 2 计算/数据节点 |
|
|
23
|
+
| 资源 | 2 核 / 8GB 内存(已分配 3GB),磁盘 33 TB |
|
|
24
|
+
| 端口 | Web/控制 `http://localhost:8848`,集群另含 `192.168.0.107:8848` / `172.17.0.1:8848` 等 |
|
|
25
|
+
| Worker | 4 线程,连接上限 512(当前 15) |
|
|
26
|
+
| Python SDK | `dolphindb==3.0.4` |
|
|
27
|
+
| 场景 | 个人/小团队 + 教学,**单查询内存上限按 2GB 控制**(详见 [[performance-tuning]])|
|
|
28
|
+
|
|
29
|
+
## 何时进入哪份 reference
|
|
30
|
+
|
|
31
|
+
| 你正在做 | 看这里 |
|
|
32
|
+
|---|---|
|
|
33
|
+
| 第一次连 DDB / `import dolphindb` 报错 / 8848 进不去 | [[setup-and-connection]] |
|
|
34
|
+
| 设计 DFS 分区表 / 选 TSDB 还是 OLAP / 给 level2 建表 | [[create-database-table]] |
|
|
35
|
+
| `loadText` 慢 / 想多线程导入 / Python 写 DataFrame 进 DDB | [[data-import]] |
|
|
36
|
+
| 写 SELECT/INSERT/UPDATE/DELETE / `delete` 删一整个分区 | [[crud-operations]] |
|
|
37
|
+
| 合成 K 线 / 算 SOIR 订单失衡 / 截面排名 / 用 `mr` 分布式跑批 | [[sql-batch-compute]] |
|
|
38
|
+
| Python SDK 用法 / `session.run` 高级参数 / 流订阅 / 异步写 / 类型映射 | [[python-api]] |
|
|
39
|
+
| 查询慢 / OOM / 节点卡住 / 内存涨不下来 / 不知道哪个作业在跑 | [[performance-tuning]] |
|
|
40
|
+
| 时间类型踩坑 / DECIMAL 精度 / SYMBOL 长度 / backtick 单行 / 反模式 | [[pitfalls-and-best-practices]] |
|
|
41
|
+
| Docker 部署 / Alpine 镜像坑 / healthcheck / 资源限制 | [[docker-deployment]] |
|
|
42
|
+
| 官方文档链接 / 版本基线 / GitHub 仓库 | [[sources]] |
|
|
43
|
+
|
|
44
|
+
## 30 秒速查
|
|
45
|
+
|
|
46
|
+
```dolphindb
|
|
47
|
+
// 连接(Python)
|
|
48
|
+
import dolphindb as ddb
|
|
49
|
+
s = ddb.session()
|
|
50
|
+
s.connect("localhost", 8848, "admin", "123456")
|
|
51
|
+
|
|
52
|
+
// 看集群健康
|
|
53
|
+
s.run("getClusterPerf()") // CPU/内存/磁盘
|
|
54
|
+
s.run("getRecentJobs(20)") // 最近 20 个批作业
|
|
55
|
+
s.run("pnodeRun(getConsoleJobs)") // 所有节点同步作业
|
|
56
|
+
s.run("getSessionMemoryStat()") // 当前 session 内存
|
|
57
|
+
|
|
58
|
+
// 看库表
|
|
59
|
+
s.run("getClusterDFSDatabases()") // 所有 DFS 库
|
|
60
|
+
s.run("getClusterDFSTables('dfs://xxx')") // 库下所有表
|
|
61
|
+
s.run("schema(loadTable('dfs://xxx','t'))") // 表结构
|
|
62
|
+
|
|
63
|
+
// 杀作业(按 rootJobId)
|
|
64
|
+
s.run("cancelJob(`xxxx)") // 异步作业
|
|
65
|
+
s.run("cancelConsoleJob(`xxxx)") // 同步作业
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## 核心原则(社区版 2核8G 必须遵守)
|
|
69
|
+
|
|
70
|
+
1. **单查询不超 2GB**:8GB 内存 → 系统/缓存 2GB + worker 缓冲 2GB + 单查询预算 ≤ 2GB。先 `count()` 估行数 × 列宽,超了就走分区分批或 `mr`。
|
|
71
|
+
2. **任何 WHERE 必带分区列**:否则全表扫,2 节点直接 OOM。用 `select [HINT_EXPLAIN] ...` 验证是否触发分区剪枝。
|
|
72
|
+
3. **TSDB 排序列别选高基数**:`sortColumns` 不要用主键/唯一列(索引膨胀);金融场景用 `[SecurityID, TradeTime]`。
|
|
73
|
+
4. **写入用 PartitionedTableAppender + DBConnectionPool**:单连接 + `tableInsert` 慢,4 worker → pool size 3。
|
|
74
|
+
5. **删数据按分区删**:`dropPartition()` 秒级,`delete from` 全表扫慢且占内存。
|
|
75
|
+
|
|
76
|
+
## 反模式(看到立即停手)
|
|
77
|
+
|
|
78
|
+
| 反模式 | 后果 | 改成 |
|
|
79
|
+
|---|---|---|
|
|
80
|
+
| `select * from t where 2024.01.01 <= TradeDate <= 2024.01.05` | 链式条件不触发剪枝 → 全表扫 | `where TradeDate between 2024.01.01 and 2024.01.05` |
|
|
81
|
+
| `delete from t where TradeDate=2024.01.01` | 重写整个分区,慢+占内存 | `dropPartition(database(dbName), 2024.01.01, tableName='t')` |
|
|
82
|
+
| 在客户端 `s.run("select * from huge_table")` | 一次拉全部,OOM | `s.run("...", fetchSize=8192)` 分段读 |
|
|
83
|
+
| DECIMAL 列用 FLOAT/DOUBLE | 精度丢失(金融场景禁用) | `DECIMAL32(3)` / `DECIMAL64(6)` |
|
|
84
|
+
| `DATETIME` 存 2038 后的时间 | 32 位 int 溢出 → 显示 1970 | 全用 `TIMESTAMP`(毫秒精度 64 位) |
|
|
85
|
+
| 建表时 `sortColumns` 用 UUID/序列号 | 索引膨胀,TSDB 查询变慢 | 用 `[业务键, 时间列]` 组合 |
|
|
86
|
+
|
|
87
|
+
**完整反模式清单**(类型陷阱 / SQL 陷阱 / 库表设计 / 写入 / 运维)见 [[pitfalls-and-best-practices]]。
|
|
88
|
+
|
|
89
|
+
## 审查模式(代码 review / CR 用)
|
|
90
|
+
|
|
91
|
+
任务是**审查**他人代码时,按以下格式输出:
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
⚠️ [reference名] 具体问题
|
|
95
|
+
违规代码:<摘录>
|
|
96
|
+
修复建议:<可直接替换的代码>
|
|
97
|
+
原因:<对应 reference 里的依据,一句话>
|
|
98
|
+
|
|
99
|
+
✅ [reference名] 符合要求 — <简要说明>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 10 条致命违规(零容忍)
|
|
103
|
+
|
|
104
|
+
| # | 违规 | 后果 |
|
|
105
|
+
|---|---|---|
|
|
106
|
+
| 1 | 分区列被函数包裹(`date(col)` / `year(col)` / `temporalAdd(col,...)`) | 全表扫 → OOM |
|
|
107
|
+
| 2 | 链式比较 `a <= col <= b` | 不触发剪枝 → 全表扫 |
|
|
108
|
+
| 3 | 单次 `append` > 500K 行 | TSDB 报 `exceeds max limit` |
|
|
109
|
+
| 4 | 未校验外部输入拼到脚本 | 脚本注入 |
|
|
110
|
+
| 5 | 连接池里 upload + run(变量跨连接不可见) | 变量找不到 |
|
|
111
|
+
| 6 | `select *`(30+ 列全传) | 浪费带宽和内存 |
|
|
112
|
+
| 7 | 分页用 `LIMIT x OFFSET y` | DDB 不支持 OFFSET 关键字 |
|
|
113
|
+
| 8 | `asyncio.to_thread(pool.run, script)` | SDK v3 `pool.run` 已是原生 async |
|
|
114
|
+
| 9 | SYMBOL 列写入未校验 ≤255 字节 | 整批写入失败 |
|
|
115
|
+
| 10 | 单分区 < 100MB 且总分区数 > 65536 | 触达 `maxPartitionNumPerQuery` 上限 |
|
|
116
|
+
|
|
117
|
+
完整审查清单 ↓
|
|
118
|
+
|
|
119
|
+
## 变更检查清单(写 / 改 DDB 代码前过一遍)
|
|
120
|
+
|
|
121
|
+
- [ ] WHERE 是否命中分区列?分区列是否被函数包裹(`year()` / `date()` / `temporalAdd()`)?
|
|
122
|
+
- [ ] 范围条件用了 `between ... and ...` 或两个独立条件?没有链式比较 `a <= col <= b`?
|
|
123
|
+
- [ ] 单次 `append` ≤ 500K 行?循环里有 `del + gc.collect()`?
|
|
124
|
+
- [ ] 大文件用 pyarrow `read_table + slice`,不是 `pd.read_feather` 全量加载?
|
|
125
|
+
- [ ] 拼接 DDB 脚本时,外部输入有正则白名单校验?(防注入)
|
|
126
|
+
- [ ] `pool.run()` 用 `await`(原生 async),`Session.run()` 用 `asyncio.to_thread()`?
|
|
127
|
+
- [ ] 连接池大小 ≤ CPU 核数 × 2(社区版 4 worker → pool 3)?
|
|
128
|
+
- [ ] `select` 只取需要的列?没有 `select *`?
|
|
129
|
+
- [ ] 多次聚合查询能否合并为一次?
|
|
130
|
+
- [ ] 多因子横向查询用了 `pivot by`,不是 Python 端 merge?
|
|
131
|
+
- [ ] backtick 列名定义在单行内?
|
|
132
|
+
- [ ] 分页用 `limit offset, count`(**不是** `LIMIT x OFFSET y`)?
|
|
133
|
+
- [ ] 新建库表:单分区估算大小落在引擎区间?总分区数远低于 65536?二级高基数列走 `HASH` 不是 `VALUE`?
|
|
134
|
+
- [ ] 写入 SYMBOL 列前校验 ≤ 255 字节?STRING/BLOB 有长度断言防静默截断?
|
|
135
|
+
- [ ] 删数据用 `dropPartition`,不是 `delete from`?
|
|
136
|
+
- [ ] pandas → DDB:`uint` 是否 `.astype('int64')`?NaN→int 是否 `.fillna(0)`?
|
|
137
|
+
- [ ] Session / Pool 有 `try/finally close()`?
|
|
138
|
+
|
|
139
|
+
## 必读官方资料
|
|
140
|
+
|
|
141
|
+
完整源 URL 见 [[sources]]。常用 3 个:
|
|
142
|
+
- 金融入门:https://docs.dolphindb.cn/zh/tutorials/new_users_finance.html
|
|
143
|
+
- 使用须知:https://docs.dolphindb.cn/zh/tutorials/usage_guidelines.html
|
|
144
|
+
- Python SDK:https://docs.dolphindb.cn/zh/pydoc/ (3.0.4)
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# 建库建表
|
|
2
|
+
|
|
3
|
+
## 30 秒选型
|
|
4
|
+
|
|
5
|
+
| 你的数据 | 引擎 | 分区策略 | 排序列 |
|
|
6
|
+
|---|---|---|---|
|
|
7
|
+
| 行情快照 / tick / 逐笔 / level2 | **TSDB** | `VALUE(日期), HASH(SymbolField, N)` | `[SecurityID, TradeTime]` |
|
|
8
|
+
| 分钟/日 K 线 / 因子结果 | **OLAP** | `VALUE(日期)` 或 `RANGE(月份)` | — |
|
|
9
|
+
| 维度信息(股票基础信息表等) | — | 维度表(不分区) | — |
|
|
10
|
+
| 流式行情写入 + 实时查询 | TSDB | 同 tick | 同 tick |
|
|
11
|
+
|
|
12
|
+
**OLAP vs TSDB**:
|
|
13
|
+
- OLAP = 列存,写入快,**不支持点查/更新**。适合 K 线、因子、批分析。
|
|
14
|
+
- TSDB = 列存 + 索引,**支持 sortKey 排序+索引**,秒级点查。适合 tick/snapshot/逐笔,分区 400MB-1GB。
|
|
15
|
+
- 选错了改不了,整个库 drop 重建。
|
|
16
|
+
|
|
17
|
+
## 分区策略
|
|
18
|
+
|
|
19
|
+
| 类型 | 关键字 | 自动扩展 | 适用 |
|
|
20
|
+
|---|---|---|---|
|
|
21
|
+
| VALUE | `VALUE(2024.01.01..2024.12.31)` | ✅ | 日期、状态 |
|
|
22
|
+
| HASH | `HASH([SYMBOL, 50])` | ✅ | 高基数列(股票代码) |
|
|
23
|
+
| RANGE | `RANGE(0 100 200 300)` | ❌ 需 `addRangePartitions` | 数值范围 |
|
|
24
|
+
| LIST | `LIST([['A','B'], ['C']])` | ❌ | 明确分组 |
|
|
25
|
+
| COMPO | `partitioned by VALUE(...), HASH(...)` | 看子分区 | 复合(最多 3 级)|
|
|
26
|
+
|
|
27
|
+
**分区层级硬限制**:1-3 级。常用 2 级 = `日期 + 股票代码 HASH`。
|
|
28
|
+
|
|
29
|
+
**分区粒度目标**:单分区压缩后 **TSDB 400MB-1GB / OLAP 100MB-300MB**。粒度太细 → 元数据爆炸;太粗 → 查询不能并行。
|
|
30
|
+
|
|
31
|
+
**`maxPartitionNumPerQuery` 硬限**:默认 65536。单次查询触达的分区数超过这个值直接报 `The number of partitions relevant to the query is too large`。
|
|
32
|
+
|
|
33
|
+
### 分区设计反例(**新手最常踩**)
|
|
34
|
+
|
|
35
|
+
> 股票行情数据(每天 5000 万条),先按 `TradeDate` 值分区,再按 `SecurityID` 二级 VALUE 分区。
|
|
36
|
+
> A 股 5000+ 标的 → **一天 5000 个分区,每个仅 0.6MB**。
|
|
37
|
+
|
|
38
|
+
后果:
|
|
39
|
+
- 查 14 天 → 70000 分区 → 超 65536 上限报错
|
|
40
|
+
- 单分区 0.6MB 远低于 TSDB 400MB 下限,元数据开销 > 数据本身
|
|
41
|
+
- 列存收益丧失,查询比单级分区还慢
|
|
42
|
+
|
|
43
|
+
**修复**:二级改 `HASH([SYMBOL, 50])`(固定 50 桶),每天 50 个分区,单分区 60MB(仍偏小但元数据 OK)。
|
|
44
|
+
|
|
45
|
+
### VALUE 分区自动扩展
|
|
46
|
+
|
|
47
|
+
```dolphindb
|
|
48
|
+
// 库参数:写入新值时自动建分区
|
|
49
|
+
create database "dfs://xxx"
|
|
50
|
+
partitioned by VALUE(2024.01.01..2024.01.07)
|
|
51
|
+
engine='TSDB',
|
|
52
|
+
newValuePartitionPolicy=add // 关键:新日期自动建分区,不需预创建
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
不设 `newValuePartitionPolicy=add` → 写入未预建的日期会报 `partition does not exist`。
|
|
56
|
+
|
|
57
|
+
### RANGE 分区静默丢弃陷阱
|
|
58
|
+
|
|
59
|
+
```dolphindb
|
|
60
|
+
// 库参数 allowMissingPartitions 默认 true
|
|
61
|
+
// → 范围外数据【静默丢弃,不报错】!
|
|
62
|
+
create database "dfs://xxx"
|
|
63
|
+
partitioned by RANGE(2024.01M..2024.12M)
|
|
64
|
+
// 写 2025 年数据 → 默默丢
|
|
65
|
+
|
|
66
|
+
// 需要严格性:
|
|
67
|
+
create database "dfs://xxx"
|
|
68
|
+
partitioned by RANGE(2024.01M..2024.12M),
|
|
69
|
+
allowMissingPartitions=false // 越界写入直接报错
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
RANGE 分区**不会**自动扩展,必须手动 `addRangePartitions(database, futureRanges)`。
|
|
73
|
+
|
|
74
|
+
### 估算 HASH 桶数
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
桶数 ≈ (每日数据量 / 期望单分区大小)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
A 股 5000+ 股票 + level2 一天 ~80GB → 按日期分区后 80GB / 1GB ≈ 80 桶 → 取 `HASH([SYMBOL, 50])`(保守留 buffer)。
|
|
81
|
+
|
|
82
|
+
## 建库 DDL
|
|
83
|
+
|
|
84
|
+
### 行情快照(TSDB + COMPO 分区)
|
|
85
|
+
|
|
86
|
+
```dolphindb
|
|
87
|
+
// 删库重来(开发期)
|
|
88
|
+
if(existsDatabase("dfs://stock_lv2_snapshot")) {
|
|
89
|
+
dropDatabase("dfs://stock_lv2_snapshot")
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
create database "dfs://stock_lv2_snapshot"
|
|
93
|
+
partitioned by VALUE(2024.01.03..2024.01.04), HASH([SYMBOL, 50]),
|
|
94
|
+
engine='TSDB'
|
|
95
|
+
// VALUE 写头 2 天即可,写入新日期会自动扩展;写多了只是元数据占位
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**注意**:
|
|
99
|
+
- `VALUE` 用一小段日期初始化即可,会自动扩展,**不要塞几年的空分区**(元数据膨胀)。
|
|
100
|
+
- 删库用 `dropDatabase`,要在 controller 上跑。
|
|
101
|
+
- `engine='TSDB'` 不写默认是 OLAP。
|
|
102
|
+
|
|
103
|
+
### 分钟 K 线(OLAP + 单级 VALUE)
|
|
104
|
+
|
|
105
|
+
```dolphindb
|
|
106
|
+
if(existsDatabase("dfs://k_minute_level")) {
|
|
107
|
+
dropDatabase("dfs://k_minute_level")
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
create database "dfs://k_minute_level"
|
|
111
|
+
partitioned by VALUE(2024.01.01..2024.01.31)
|
|
112
|
+
engine='OLAP'
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 维度表(不分区,跟在某个 DFS 库下)
|
|
116
|
+
|
|
117
|
+
```dolphindb
|
|
118
|
+
db = database("dfs://stock_lv2_snapshot")
|
|
119
|
+
createDimensionTable(db, "security_info",
|
|
120
|
+
table(1:0, ["SecurityID","Name","Industry"], [SYMBOL,STRING,SYMBOL])
|
|
121
|
+
)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## 建表 DDL
|
|
125
|
+
|
|
126
|
+
### TSDB 表(必须 sortColumns)
|
|
127
|
+
|
|
128
|
+
```dolphindb
|
|
129
|
+
create table "dfs://stock_lv2_snapshot"."snapshot"(
|
|
130
|
+
TradeDate DATE[comment="交易日期", compress="delta"]
|
|
131
|
+
TradeTime TIME[comment="交易时间", compress="delta"]
|
|
132
|
+
SecurityID SYMBOL
|
|
133
|
+
LastPrice DOUBLE
|
|
134
|
+
OpenPrice DOUBLE
|
|
135
|
+
HighPrice DOUBLE
|
|
136
|
+
LowPrice DOUBLE
|
|
137
|
+
TotalVolumeTrade LONG
|
|
138
|
+
OfferPrice DOUBLE[] // 数组向量:10 档卖价
|
|
139
|
+
BidPrice DOUBLE[]
|
|
140
|
+
OfferOrderQty LONG[]
|
|
141
|
+
BidOrderQty LONG[]
|
|
142
|
+
)
|
|
143
|
+
partitioned by TradeDate, SecurityID,
|
|
144
|
+
sortColumns=["SecurityID","TradeTime"],
|
|
145
|
+
keepDuplicates=ALL
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**关键参数**:
|
|
149
|
+
- `partitioned by`:必须和库的 partition 顺序一致。
|
|
150
|
+
- `sortColumns`:**最后一列必须是时间列**,前面是高频查询的过滤列(如 `SecurityID`)。
|
|
151
|
+
- `keepDuplicates`:`ALL`(保留)/`FIRST`/`LAST`,金融 tick 一般 `ALL`。
|
|
152
|
+
- `compress="delta"`:单调递增列(日期、时间、序号)必加,能压缩 80%+。
|
|
153
|
+
- `DOUBLE[]`:数组向量类型,10 档行情用一列搞定(vs 拆 20 列)。
|
|
154
|
+
|
|
155
|
+
### OLAP 表(轻量,无 sortColumns)
|
|
156
|
+
|
|
157
|
+
```dolphindb
|
|
158
|
+
create table "dfs://k_minute_level"."k_minute"(
|
|
159
|
+
securityid SYMBOL
|
|
160
|
+
tradetime TIMESTAMP
|
|
161
|
+
open DOUBLE
|
|
162
|
+
close DOUBLE
|
|
163
|
+
high DOUBLE
|
|
164
|
+
low DOUBLE
|
|
165
|
+
vol INT
|
|
166
|
+
val DOUBLE
|
|
167
|
+
vwap DOUBLE
|
|
168
|
+
)
|
|
169
|
+
partitioned by tradetime
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## sortColumns 选择规则(TSDB)
|
|
173
|
+
|
|
174
|
+
✅ 推荐:`[业务键, 时间列]` —— `[SecurityID, TradeTime]`、`[InstrumentID, Timestamp]`
|
|
175
|
+
|
|
176
|
+
❌ 禁止:
|
|
177
|
+
- 把**唯一键/主键**当 sortColumn(如订单号、UUID)→ 索引膨胀,TSDB 索引文件比数据还大
|
|
178
|
+
- **时间列放第一位** → 同一时刻的不同标的散开,按 SecurityID 查变慢
|
|
179
|
+
|
|
180
|
+
## 数据类型选择
|
|
181
|
+
|
|
182
|
+
| 场景 | 类型 | 不要用 |
|
|
183
|
+
|---|---|---|
|
|
184
|
+
| 价格、金额(金融) | `DECIMAL32(3)` / `DECIMAL64(6)` | `FLOAT/DOUBLE`(精度丢失) |
|
|
185
|
+
| 成交量、笔数 | `LONG`(大数)/ `INT`(小数) | — |
|
|
186
|
+
| 股票代码、合约代码 | `SYMBOL` | `STRING`(symbol 列式压缩 + 字典编码更省) |
|
|
187
|
+
| 名称、备注 | `STRING` | — |
|
|
188
|
+
| 日期 | `DATE` | — |
|
|
189
|
+
| 时间戳 | `TIMESTAMP`(毫秒)/`NANOTIMESTAMP`(纳秒) | `DATETIME`(32 位 int,2038 溢出!)|
|
|
190
|
+
| 10 档行情 | `DOUBLE[]` 数组向量 | 拆 20 列(写慢、查慢) |
|
|
191
|
+
|
|
192
|
+
## ALTER 限制(建表前想清楚)
|
|
193
|
+
|
|
194
|
+
| 操作 | TSDB | OLAP |
|
|
195
|
+
|---|---|---|
|
|
196
|
+
| 新增列(只能末尾) | ✅ | ✅ |
|
|
197
|
+
| 删列 | ❌ | ✅(非分区列) |
|
|
198
|
+
| 改列名 | ❌ | ✅(非分区列) |
|
|
199
|
+
| 改列类型 | ❌ | ✅(非分区列) |
|
|
200
|
+
| 改列顺序 | ❌ | ❌ |
|
|
201
|
+
| 改分区列 | ❌ | ❌ |
|
|
202
|
+
| 主键/唯一索引 | ❌ | ❌ |
|
|
203
|
+
| 自增列 | ❌ | ❌ |
|
|
204
|
+
|
|
205
|
+
**结论**:TSDB 表 schema 一旦定下基本不能动。建表前用一周数据 dry-run。
|
|
206
|
+
|
|
207
|
+
## 元数据查询
|
|
208
|
+
|
|
209
|
+
```dolphindb
|
|
210
|
+
getClusterDFSDatabases() // 所有 DFS 库
|
|
211
|
+
getClusterDFSTables("dfs://stock_lv2_snapshot")// 库下所有表
|
|
212
|
+
getTables(database("dfs://xxx")) // 同上
|
|
213
|
+
schema(loadTable("dfs://xxx","t")) // 完整 schema
|
|
214
|
+
getDatabaseDDL("dfs://xxx") // 反查建库 SQL
|
|
215
|
+
getDBTableDDL("dfs://xxx","t") // 反查建表 SQL
|
|
216
|
+
getTableDiskUsage("dfs://xxx","t") // 表占用磁盘
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
跨引用:导入数据 → [[data-import]],查询 → [[crud-operations]],反模式 → [[pitfalls-and-best-practices]]。
|