@wneng/create-keel 0.3.6 → 0.4.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/dist/index.js +206 -13
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/templates/ci-gitee/files/pipeline.yml +62 -0
- package/src/templates/ci-github/files/ci.yml +160 -0
- package/src/templates/db-go-elasticsearch/files/Makefile +18 -0
- package/src/templates/db-go-elasticsearch/files/apply_templates.go +80 -0
- package/src/templates/db-go-elasticsearch/files/db-README.md +57 -0
- package/src/templates/db-go-elasticsearch/files/go.mod +7 -0
- package/src/templates/db-go-elasticsearch/files/index-template-init.json +25 -0
- package/src/templates/db-go-elasticsearch/fragment.yaml +22 -0
- package/src/templates/db-go-migrate-mysql/files/000001_init.down.sql +3 -0
- package/src/templates/db-go-migrate-mysql/files/000001_init.up.sql +35 -0
- package/src/templates/db-go-migrate-mysql/files/Makefile +33 -0
- package/src/templates/db-go-migrate-mysql/files/db-README.md +77 -0
- package/src/templates/db-go-migrate-mysql/files/go.mod +8 -0
- package/src/templates/db-go-migrate-mysql/fragment.yaml +22 -0
- package/src/templates/db-go-migrate-postgres/files/000001_init.down.sql +3 -0
- package/src/templates/db-go-migrate-postgres/files/000001_init.up.sql +32 -0
- package/src/templates/db-go-migrate-postgres/files/Makefile +31 -0
- package/src/templates/db-go-migrate-postgres/files/db-README.md +71 -0
- package/src/templates/db-go-migrate-postgres/files/go.mod +8 -0
- package/src/templates/db-go-migrate-postgres/fragment.yaml +22 -0
- package/src/templates/db-java-elasticsearch/files/EsTemplateApplier.java +86 -0
- package/src/templates/db-java-elasticsearch/files/db-README.md +63 -0
- package/src/templates/db-java-elasticsearch/files/index-template-init.json +25 -0
- package/src/templates/db-java-elasticsearch/files/pom.xml +134 -0
- package/src/templates/db-java-elasticsearch/fragment.yaml +19 -0
- package/src/templates/db-java-flyway-mysql/files/V1__init.sql +44 -0
- package/src/templates/db-java-flyway-mysql/files/application.yaml +39 -0
- package/src/templates/db-java-flyway-mysql/files/db-README.md +102 -0
- package/src/templates/db-java-flyway-mysql/files/pom.xml +172 -0
- package/src/templates/db-java-flyway-mysql/fragment.yaml +19 -0
- package/src/templates/db-java-flyway-postgres/files/V1__init.sql +40 -0
- package/src/templates/db-java-flyway-postgres/files/application.yaml +37 -0
- package/src/templates/db-java-flyway-postgres/files/db-README.md +75 -0
- package/src/templates/db-java-flyway-postgres/files/pom.xml +166 -0
- package/src/templates/db-java-flyway-postgres/fragment.yaml +19 -0
- package/src/templates/db-node-elasticsearch/files/apply-templates.cjs +60 -0
- package/src/templates/db-node-elasticsearch/files/db-README.md +76 -0
- package/src/templates/db-node-elasticsearch/files/index-template-init.json +26 -0
- package/src/templates/db-node-elasticsearch/files/package.json +26 -0
- package/src/templates/db-node-elasticsearch/fragment.yaml +19 -0
- package/src/templates/db-node-knex-mysql/files/db-README.md +90 -0
- package/src/templates/db-node-knex-mysql/files/knexfile.cjs +72 -0
- package/src/templates/db-node-knex-mysql/files/migrations-init.cjs +42 -0
- package/src/templates/db-node-knex-mysql/files/package.json +31 -0
- package/src/templates/db-node-knex-mysql/files/seeds-dev-fixtures.cjs +38 -0
- package/src/templates/db-node-knex-mysql/files/seeds-prod-dictionaries.cjs +25 -0
- package/src/templates/db-node-knex-mysql/fragment.yaml +25 -0
- package/src/templates/db-node-knex-postgres/files/db-README.md +81 -0
- package/src/templates/db-node-knex-postgres/files/knexfile.cjs +67 -0
- package/src/templates/db-node-knex-postgres/files/migrations-init.cjs +42 -0
- package/src/templates/db-node-knex-postgres/files/package.json +31 -0
- package/src/templates/db-node-knex-postgres/files/seeds-dev-fixtures.cjs +36 -0
- package/src/templates/db-node-knex-postgres/files/seeds-prod-dictionaries.cjs +26 -0
- package/src/templates/db-node-knex-postgres/fragment.yaml +25 -0
- package/src/templates/db-python-alembic-mysql/files/0001_init.py +70 -0
- package/src/templates/db-python-alembic-mysql/files/alembic.ini +47 -0
- package/src/templates/db-python-alembic-mysql/files/db-README.md +87 -0
- package/src/templates/db-python-alembic-mysql/files/env.py +71 -0
- package/src/templates/db-python-alembic-mysql/files/pyproject.toml +52 -0
- package/src/templates/db-python-alembic-mysql/files/script.py.mako +26 -0
- package/src/templates/db-python-alembic-mysql/fragment.yaml +25 -0
- package/src/templates/db-python-alembic-postgres/files/0001_init.py +62 -0
- package/src/templates/db-python-alembic-postgres/files/alembic.ini +45 -0
- package/src/templates/db-python-alembic-postgres/files/db-README.md +70 -0
- package/src/templates/db-python-alembic-postgres/files/env.py +62 -0
- package/src/templates/db-python-alembic-postgres/files/pyproject.toml +52 -0
- package/src/templates/db-python-alembic-postgres/files/script.py.mako +25 -0
- package/src/templates/db-python-alembic-postgres/fragment.yaml +25 -0
- package/src/templates/db-python-elasticsearch/files/apply_templates.py +55 -0
- package/src/templates/db-python-elasticsearch/files/db-README.md +57 -0
- package/src/templates/db-python-elasticsearch/files/index-template-init.json +25 -0
- package/src/templates/db-python-elasticsearch/files/pyproject.toml +50 -0
- package/src/templates/db-python-elasticsearch/fragment.yaml +19 -0
- package/src/templates/docs-skeleton/files/governance-database.md +150 -0
- package/src/templates/docs-skeleton/fragment.yaml +3 -0
- package/src/templates/root-files/files/AGENTS.md +6 -2
- package/src/templates/server-python/files/README.md +75 -2
- package/src/templates/server-python/files/app-init.py +11 -1
- package/src/templates/server-python/files/config.py +40 -0
- package/src/templates/server-python/files/main.py +62 -0
- package/src/templates/server-python/files/pyproject.toml +14 -1
- package/src/templates/server-python/files/test_healthz.py +36 -0
- package/src/templates/server-python/fragment.yaml +10 -1
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
; Alembic configuration for <%= it.options.projectName %>-server (PostgreSQL).
|
|
2
|
+
;
|
|
3
|
+
; Connection string is read from DATABASE_URL at runtime (see env.py).
|
|
4
|
+
|
|
5
|
+
[alembic]
|
|
6
|
+
script_location = db/migrations
|
|
7
|
+
prepend_sys_path = .
|
|
8
|
+
version_path_separator = os
|
|
9
|
+
sqlalchemy.url =
|
|
10
|
+
|
|
11
|
+
output_encoding = utf-8
|
|
12
|
+
|
|
13
|
+
[loggers]
|
|
14
|
+
keys = root,sqlalchemy,alembic
|
|
15
|
+
|
|
16
|
+
[handlers]
|
|
17
|
+
keys = console
|
|
18
|
+
|
|
19
|
+
[formatters]
|
|
20
|
+
keys = generic
|
|
21
|
+
|
|
22
|
+
[logger_root]
|
|
23
|
+
level = WARN
|
|
24
|
+
handlers = console
|
|
25
|
+
qualname =
|
|
26
|
+
|
|
27
|
+
[logger_sqlalchemy]
|
|
28
|
+
level = WARN
|
|
29
|
+
handlers =
|
|
30
|
+
qualname = sqlalchemy.engine
|
|
31
|
+
|
|
32
|
+
[logger_alembic]
|
|
33
|
+
level = INFO
|
|
34
|
+
handlers =
|
|
35
|
+
qualname = alembic
|
|
36
|
+
|
|
37
|
+
[handler_console]
|
|
38
|
+
class = StreamHandler
|
|
39
|
+
args = (sys.stderr,)
|
|
40
|
+
level = NOTSET
|
|
41
|
+
formatter = generic
|
|
42
|
+
|
|
43
|
+
[formatter_generic]
|
|
44
|
+
format = %(levelname)-5.5s [%(name)s] %(message)s
|
|
45
|
+
datefmt = %Y-%m-%d %H:%M:%S
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# server/db/
|
|
2
|
+
|
|
3
|
+
数据库迁移、种子数据、本地数据库工具的入口。
|
|
4
|
+
|
|
5
|
+
> 跨项目的数据库归属规则(哪类 SQL 放哪个目录、与 `contracts/` 如何对齐)见 [`docs/governance/database.md`](../../docs/governance/database.md)。
|
|
6
|
+
|
|
7
|
+
## 工具与版本
|
|
8
|
+
|
|
9
|
+
| 项 | 值 |
|
|
10
|
+
|---|---|
|
|
11
|
+
| 数据库 | PostgreSQL 14+ |
|
|
12
|
+
| 迁移工具 | [Alembic](https://alembic.sqlalchemy.org) `1.13.3` |
|
|
13
|
+
| ORM | SQLAlchemy `2.0.36` |
|
|
14
|
+
| 驱动 | `psycopg[binary]` `3.2.3`(psycopg v3) |
|
|
15
|
+
|
|
16
|
+
工具版本在 `pyproject.toml` 钉死,并镜像到 `docs/03-工程规范与研发基础设施/tech-stack-server.md`。
|
|
17
|
+
|
|
18
|
+
## 目录布局
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
server/
|
|
22
|
+
├── pyproject.toml
|
|
23
|
+
├── alembic.ini
|
|
24
|
+
└── db/
|
|
25
|
+
├── README.md
|
|
26
|
+
└── migrations/
|
|
27
|
+
├── env.py
|
|
28
|
+
├── script.py.mako
|
|
29
|
+
└── versions/
|
|
30
|
+
└── <NNNN>_<description>.py
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## 常用命令
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
alembic revision -m "create orders table" # 新建迁移
|
|
37
|
+
alembic revision --autogenerate -m "..." # 配合 SQLAlchemy 模型
|
|
38
|
+
alembic upgrade head # 升到最新
|
|
39
|
+
alembic downgrade -1 # 回滚一版
|
|
40
|
+
alembic current # 查看当前版本
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
环境变量 `DATABASE_URL`(默认 `postgresql+psycopg://postgres:@127.0.0.1:5432/<project>_dev`)。
|
|
44
|
+
|
|
45
|
+
## prod vs dev seeds
|
|
46
|
+
|
|
47
|
+
| 类型 | 位置 |
|
|
48
|
+
|---|---|
|
|
49
|
+
| 生产字典(必装) | 写进 `versions/` 的 revision 中(ON CONFLICT DO NOTHING) |
|
|
50
|
+
| 开发样例 | `server/db/seeds/dev/` 下手动执行 |
|
|
51
|
+
|
|
52
|
+
字典类生产种子真值在 `contracts/dictionaries/enums.yaml`;当前手动同步。
|
|
53
|
+
|
|
54
|
+
## 改动分级(参见 `docs/governance/change-tiers.md`)
|
|
55
|
+
|
|
56
|
+
| 改动 | Tier |
|
|
57
|
+
|---|---|
|
|
58
|
+
| 新建 revision 加表 / 加字段 | 3 |
|
|
59
|
+
| 修改既有 revision | **不允许** |
|
|
60
|
+
| 删字段 / 改类型(破坏性) | 4 |
|
|
61
|
+
| 改 dev seed | 1/2 |
|
|
62
|
+
| 改字典数据 | 3(同步 contracts/dictionaries/) |
|
|
63
|
+
| 升级 Alembic / SQLAlchemy / psycopg | 2,但同步改 tech-stack-server.md |
|
|
64
|
+
|
|
65
|
+
## CI 行为
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
alembic upgrade head
|
|
69
|
+
alembic downgrade base
|
|
70
|
+
```
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Alembic environment for PostgreSQL (psycopg v3).
|
|
2
|
+
|
|
3
|
+
Reads DATABASE_URL from env (or falls back to a localhost Postgres DSN);
|
|
4
|
+
imports the application's SQLAlchemy metadata for autogenerate support.
|
|
5
|
+
|
|
6
|
+
Edit `target_metadata` to point at your app's Base.metadata once you
|
|
7
|
+
introduce ORM models.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
from logging.config import fileConfig
|
|
12
|
+
|
|
13
|
+
from alembic import context
|
|
14
|
+
from sqlalchemy import engine_from_config, pool
|
|
15
|
+
|
|
16
|
+
config = context.config
|
|
17
|
+
|
|
18
|
+
if config.config_file_name is not None:
|
|
19
|
+
fileConfig(config.config_file_name)
|
|
20
|
+
|
|
21
|
+
config.set_main_option(
|
|
22
|
+
"sqlalchemy.url",
|
|
23
|
+
os.environ.get(
|
|
24
|
+
"DATABASE_URL",
|
|
25
|
+
"postgresql+psycopg://postgres:@127.0.0.1:5432/" + "<%= it.options.projectName.replace(/-/g, '_') %>" + "_dev",
|
|
26
|
+
),
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
target_metadata = None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def run_migrations_offline() -> None:
|
|
33
|
+
url = config.get_main_option("sqlalchemy.url")
|
|
34
|
+
context.configure(
|
|
35
|
+
url=url,
|
|
36
|
+
target_metadata=target_metadata,
|
|
37
|
+
literal_binds=True,
|
|
38
|
+
dialect_opts={"paramstyle": "named"},
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
with context.begin_transaction():
|
|
42
|
+
context.run_migrations()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def run_migrations_online() -> None:
|
|
46
|
+
connectable = engine_from_config(
|
|
47
|
+
config.get_section(config.config_ini_section, {}),
|
|
48
|
+
prefix="sqlalchemy.",
|
|
49
|
+
poolclass=pool.NullPool,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
with connectable.connect() as connection:
|
|
53
|
+
context.configure(connection=connection, target_metadata=target_metadata)
|
|
54
|
+
|
|
55
|
+
with context.begin_transaction():
|
|
56
|
+
context.run_migrations()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
if context.is_offline_mode():
|
|
60
|
+
run_migrations_offline()
|
|
61
|
+
else:
|
|
62
|
+
run_migrations_online()
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "<%= it.options.projectName %>-server"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
requires-python = ">=3.11"
|
|
5
|
+
dependencies = [
|
|
6
|
+
"fastapi==0.115.5",
|
|
7
|
+
"uvicorn[standard]==0.32.0",
|
|
8
|
+
"pydantic==2.9.2",
|
|
9
|
+
"pydantic-settings==2.6.1",
|
|
10
|
+
"alembic==1.13.3",
|
|
11
|
+
"sqlalchemy==2.0.36",
|
|
12
|
+
"psycopg[binary]==3.2.3",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[project.optional-dependencies]
|
|
16
|
+
dev = [
|
|
17
|
+
"ruff>=0.6.8",
|
|
18
|
+
"mypy>=1.11.0",
|
|
19
|
+
"pytest>=8.3.0",
|
|
20
|
+
"pytest-asyncio>=0.24.0",
|
|
21
|
+
"httpx>=0.27.0",
|
|
22
|
+
"pip-audit>=2.7.0",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[tool.ruff]
|
|
26
|
+
line-length = 100
|
|
27
|
+
target-version = "py311"
|
|
28
|
+
extend-exclude = ["generated", ".venv", "db/migrations/versions"]
|
|
29
|
+
|
|
30
|
+
[tool.ruff.lint]
|
|
31
|
+
select = ["E", "F", "W", "I", "B", "UP", "N"]
|
|
32
|
+
ignore = []
|
|
33
|
+
|
|
34
|
+
[tool.ruff.format]
|
|
35
|
+
quote-style = "double"
|
|
36
|
+
indent-style = "space"
|
|
37
|
+
line-ending = "lf"
|
|
38
|
+
|
|
39
|
+
[tool.mypy]
|
|
40
|
+
python_version = "3.11"
|
|
41
|
+
strict_optional = true
|
|
42
|
+
disallow_untyped_defs = true
|
|
43
|
+
warn_unused_ignores = true
|
|
44
|
+
exclude = ["generated", ".venv", "db/migrations/versions"]
|
|
45
|
+
|
|
46
|
+
[tool.pytest.ini_options]
|
|
47
|
+
testpaths = ["tests"]
|
|
48
|
+
asyncio_mode = "auto"
|
|
49
|
+
|
|
50
|
+
[tool.setuptools.packages.find]
|
|
51
|
+
include = ["app*"]
|
|
52
|
+
exclude = ["tests*", "generated*", "db*"]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""${message}
|
|
2
|
+
|
|
3
|
+
Revision ID: ${up_revision}
|
|
4
|
+
Revises: ${down_revision | comma,n}
|
|
5
|
+
Create Date: ${create_date}
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
from typing import Sequence, Union
|
|
9
|
+
|
|
10
|
+
from alembic import op
|
|
11
|
+
import sqlalchemy as sa
|
|
12
|
+
${imports if imports else ""}
|
|
13
|
+
|
|
14
|
+
revision: str = ${repr(up_revision)}
|
|
15
|
+
down_revision: Union[str, None] = ${repr(down_revision)}
|
|
16
|
+
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
|
|
17
|
+
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def upgrade() -> None:
|
|
21
|
+
${upgrades if upgrades else "pass"}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def downgrade() -> None:
|
|
25
|
+
${downgrades if downgrades else "pass"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
name: db-python-alembic-postgres
|
|
2
|
+
version: 1.0.0
|
|
3
|
+
appliesWhen:
|
|
4
|
+
backend: python
|
|
5
|
+
database: postgres
|
|
6
|
+
priority: 40
|
|
7
|
+
files:
|
|
8
|
+
- from: files/pyproject.toml
|
|
9
|
+
to: server/pyproject.toml
|
|
10
|
+
render: true
|
|
11
|
+
- from: files/alembic.ini
|
|
12
|
+
to: server/alembic.ini
|
|
13
|
+
render: true
|
|
14
|
+
- from: files/env.py
|
|
15
|
+
to: server/db/migrations/env.py
|
|
16
|
+
render: false
|
|
17
|
+
- from: files/script.py.mako
|
|
18
|
+
to: server/db/migrations/script.py.mako
|
|
19
|
+
render: false
|
|
20
|
+
- from: files/0001_init.py
|
|
21
|
+
to: server/db/migrations/versions/0001_init.py
|
|
22
|
+
render: false
|
|
23
|
+
- from: files/db-README.md
|
|
24
|
+
to: server/db/README.md
|
|
25
|
+
render: true
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Idempotent Elasticsearch index-template applier (Python backend).
|
|
2
|
+
|
|
3
|
+
ES 没有事务性 DDL;"migration" 退化为把 db/index-templates/*.json 全部 PUT 一次。
|
|
4
|
+
ES 自身保证 PUT 同名 template 的幂等性。
|
|
5
|
+
|
|
6
|
+
改动 tier(参见 docs/governance/change-tiers.md):
|
|
7
|
+
- 新增 template 文件:Tier 3
|
|
8
|
+
- 修改既有 template 的 mapping 字段:Tier 4(破坏性 - 可能 reindex)
|
|
9
|
+
- 仅改 priority / 新增 alias:Tier 2
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
import os
|
|
16
|
+
import sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
from elasticsearch import Elasticsearch
|
|
20
|
+
|
|
21
|
+
TEMPLATE_DIR = Path(__file__).parent / "index-templates"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def main() -> None:
|
|
25
|
+
url = os.environ.get("ELASTICSEARCH_URL", "http://127.0.0.1:9200")
|
|
26
|
+
username = os.environ.get("ELASTICSEARCH_USERNAME")
|
|
27
|
+
password = os.environ.get("ELASTICSEARCH_PASSWORD")
|
|
28
|
+
|
|
29
|
+
kwargs: dict[str, object] = {"hosts": [url]}
|
|
30
|
+
if username and password:
|
|
31
|
+
kwargs["basic_auth"] = (username, password)
|
|
32
|
+
|
|
33
|
+
client = Elasticsearch(**kwargs)
|
|
34
|
+
client.cluster.health(wait_for_status="yellow", timeout="30s")
|
|
35
|
+
|
|
36
|
+
files = sorted(p for p in TEMPLATE_DIR.glob("*.json"))
|
|
37
|
+
if not files:
|
|
38
|
+
print(f"no index templates found in {TEMPLATE_DIR}")
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
for path in files:
|
|
42
|
+
body = json.loads(path.read_text(encoding="utf-8"))
|
|
43
|
+
name = path.stem # filename minus .json
|
|
44
|
+
client.indices.put_index_template(name=name, body=body)
|
|
45
|
+
print(f"✓ applied {name}")
|
|
46
|
+
|
|
47
|
+
print(f"done; {len(files)} template(s) applied to {url}")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
if __name__ == "__main__":
|
|
51
|
+
try:
|
|
52
|
+
main()
|
|
53
|
+
except Exception as e: # noqa: BLE001
|
|
54
|
+
print(f"✗ failed: {e}", file=sys.stderr)
|
|
55
|
+
sys.exit(1)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# server/db/
|
|
2
|
+
|
|
3
|
+
Elasticsearch index-template 入口(Python 后端)。
|
|
4
|
+
|
|
5
|
+
> 跨项目的数据库归属规则见 [`docs/governance/database.md`](../../docs/governance/database.md)。
|
|
6
|
+
|
|
7
|
+
## "Migration" 的语义
|
|
8
|
+
|
|
9
|
+
**Elasticsearch 没有事务性 DDL,本目录里的 JSON 不是迁移文件。**
|
|
10
|
+
|
|
11
|
+
每个 `db/index-templates/*.json` 是一个 [Index Template v2](https://www.elastic.co/guide/en/elasticsearch/reference/current/index-templates.html)。`apply_templates.py` 按文件名字典序遍历目录,对每个文件 PUT;ES 自身保证幂等。
|
|
12
|
+
|
|
13
|
+
## 工具与版本
|
|
14
|
+
|
|
15
|
+
| 项 | 值 |
|
|
16
|
+
|---|---|
|
|
17
|
+
| Elasticsearch | 8.15+ |
|
|
18
|
+
| Python 客户端 | `elasticsearch` `8.15.1` |
|
|
19
|
+
|
|
20
|
+
## 目录布局
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
server/
|
|
24
|
+
├── pyproject.toml
|
|
25
|
+
└── db/
|
|
26
|
+
├── README.md
|
|
27
|
+
├── apply_templates.py # 幂等 applier
|
|
28
|
+
└── index-templates/
|
|
29
|
+
└── <utc-ts>_<description>.json
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## 常用命令
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
python -m server.db.apply_templates
|
|
36
|
+
# 或
|
|
37
|
+
python server/db/apply_templates.py
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
环境变量 `ELASTICSEARCH_URL`(默认 `http://127.0.0.1:9200`)、`ELASTICSEARCH_USERNAME` / `ELASTICSEARCH_PASSWORD`(可选)。
|
|
41
|
+
|
|
42
|
+
## 改动分级(参见 `docs/governance/change-tiers.md`)
|
|
43
|
+
|
|
44
|
+
| 改动 | Tier |
|
|
45
|
+
|---|---|
|
|
46
|
+
| 新增 template 文件 | 3 |
|
|
47
|
+
| 修改既有 template 的 mapping 字段 | 4(破坏性 - 可能 reindex) |
|
|
48
|
+
| 仅改 priority / 新增 alias / 改 settings.refresh_interval | 2 |
|
|
49
|
+
| 删除 template | 4 |
|
|
50
|
+
|
|
51
|
+
## 与 `contracts/asyncapi/` 的同步
|
|
52
|
+
|
|
53
|
+
事件 ingest 的 schema 真值在 `contracts/asyncapi/asyncapi.yaml`;index-template 的 mapping 应当与之**对齐**。当前手动同步。
|
|
54
|
+
|
|
55
|
+
## CI 行为
|
|
56
|
+
|
|
57
|
+
CI 起一个 ES 8 service container;脚本失败 = CI 红。
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"index_patterns": ["events-*"],
|
|
3
|
+
"priority": 100,
|
|
4
|
+
"template": {
|
|
5
|
+
"settings": {
|
|
6
|
+
"number_of_shards": 1,
|
|
7
|
+
"number_of_replicas": 1,
|
|
8
|
+
"refresh_interval": "5s"
|
|
9
|
+
},
|
|
10
|
+
"mappings": {
|
|
11
|
+
"properties": {
|
|
12
|
+
"@timestamp": { "type": "date" },
|
|
13
|
+
"tenant_id": { "type": "keyword" },
|
|
14
|
+
"event_type": { "type": "keyword" },
|
|
15
|
+
"user_id": { "type": "keyword" },
|
|
16
|
+
"payload": { "type": "object", "dynamic": true },
|
|
17
|
+
"trace_id": { "type": "keyword" }
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"_meta": {
|
|
22
|
+
"description": "Sample event-stream template. Replace mappings with your domain fields.",
|
|
23
|
+
"managed_by": "@wneng/create-keel"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "<%= it.options.projectName %>-server"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
requires-python = ">=3.11"
|
|
5
|
+
dependencies = [
|
|
6
|
+
"fastapi==0.115.5",
|
|
7
|
+
"uvicorn[standard]==0.32.0",
|
|
8
|
+
"pydantic==2.9.2",
|
|
9
|
+
"pydantic-settings==2.6.1",
|
|
10
|
+
"elasticsearch==8.15.1",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
[project.optional-dependencies]
|
|
14
|
+
dev = [
|
|
15
|
+
"ruff>=0.6.8",
|
|
16
|
+
"mypy>=1.11.0",
|
|
17
|
+
"pytest>=8.3.0",
|
|
18
|
+
"pytest-asyncio>=0.24.0",
|
|
19
|
+
"httpx>=0.27.0",
|
|
20
|
+
"pip-audit>=2.7.0",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
[tool.ruff]
|
|
24
|
+
line-length = 100
|
|
25
|
+
target-version = "py311"
|
|
26
|
+
extend-exclude = ["generated", ".venv"]
|
|
27
|
+
|
|
28
|
+
[tool.ruff.lint]
|
|
29
|
+
select = ["E", "F", "W", "I", "B", "UP", "N"]
|
|
30
|
+
ignore = []
|
|
31
|
+
|
|
32
|
+
[tool.ruff.format]
|
|
33
|
+
quote-style = "double"
|
|
34
|
+
indent-style = "space"
|
|
35
|
+
line-ending = "lf"
|
|
36
|
+
|
|
37
|
+
[tool.mypy]
|
|
38
|
+
python_version = "3.11"
|
|
39
|
+
strict_optional = true
|
|
40
|
+
disallow_untyped_defs = true
|
|
41
|
+
warn_unused_ignores = true
|
|
42
|
+
exclude = ["generated", ".venv"]
|
|
43
|
+
|
|
44
|
+
[tool.pytest.ini_options]
|
|
45
|
+
testpaths = ["tests"]
|
|
46
|
+
asyncio_mode = "auto"
|
|
47
|
+
|
|
48
|
+
[tool.setuptools.packages.find]
|
|
49
|
+
include = ["app*"]
|
|
50
|
+
exclude = ["tests*", "generated*", "db*"]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
name: db-python-elasticsearch
|
|
2
|
+
version: 1.0.0
|
|
3
|
+
appliesWhen:
|
|
4
|
+
backend: python
|
|
5
|
+
database: elasticsearch
|
|
6
|
+
priority: 40
|
|
7
|
+
files:
|
|
8
|
+
- from: files/pyproject.toml
|
|
9
|
+
to: server/pyproject.toml
|
|
10
|
+
render: true
|
|
11
|
+
- from: files/apply_templates.py
|
|
12
|
+
to: server/db/apply_templates.py
|
|
13
|
+
render: false
|
|
14
|
+
- from: files/index-template-init.json
|
|
15
|
+
to: server/db/index-templates/20260101000000_init.json
|
|
16
|
+
render: false
|
|
17
|
+
- from: files/db-README.md
|
|
18
|
+
to: server/db/README.md
|
|
19
|
+
render: true
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
---
|
|
2
|
+
last-reviewed: 2026-05-16
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# 数据库治理
|
|
6
|
+
|
|
7
|
+
> 本文件统一回答:"SQL / 索引 / 迁移 / 种子数据放哪个目录、与 `contracts/` 如何对齐"。
|
|
8
|
+
>
|
|
9
|
+
> 工具与命令的细节(哪个项目用 Knex / Flyway / Alembic / golang-migrate / ES applier)见各项目的 `server/db/README.md`,本文件不重复。
|
|
10
|
+
>
|
|
11
|
+
> 工具决策与 mongodb / sqlite 排除理由见 [`../02-系统方案与架构/adr-0005-database-conventions.md`](../02-系统方案与架构/adr-0005-database-conventions.md)。
|
|
12
|
+
|
|
13
|
+
## 1. 五类 SQL 文件的归属
|
|
14
|
+
|
|
15
|
+
数据库相关的产物分五类,每类只有一个合法位置:
|
|
16
|
+
|
|
17
|
+
| 类型 | 例子 | 归属 | 与契约关系 |
|
|
18
|
+
|---|---|---|---|
|
|
19
|
+
| **schema migration**(DDL,版本化) | `V1__create_users.sql` / `0001_init.py` / `000001_init.up.sql` | `server/db/migrations/`(Java 走 `src/main/resources/db/migration/`) | 与 `contracts/openapi/` 中的字段对齐 |
|
|
20
|
+
| **生产种子数据**(字典 / 码表) | 角色码表、状态枚举、错误码 lookup | `server/db/seeds/prod/`(Knex)/ 写入迁移文件(Flyway / Alembic / golang-migrate) | **真值在 `contracts/dictionaries/`**;当前手动同步 |
|
|
21
|
+
| **开发种子数据**(fixture) | 样例用户、demo 商品、调试数据 | `server/db/seeds/dev/` | 与契约无关;不进生产 |
|
|
22
|
+
| **存储过程 / 函数 / 视图 / 触发器** | `usp_calculate_amount.sql`、物化视图 | `server/db/procedures/` | 写入迁移文件,与代码同等对待 |
|
|
23
|
+
| **运维脚本**(一次性) | 数据回填、重建索引、清理过期数据 | `scripts/db/<date>-<topic>.sql` | 与契约无关;不进生产迁移历史 |
|
|
24
|
+
| **报表 / 分析 query**(BI 看板) | 仪表板背后的 SQL、临时调研 query | `docs/09-数据与埋点/`(启用 data 角色) | 与契约无关;与数据资产对齐 |
|
|
25
|
+
|
|
26
|
+
> Elasticsearch 的"migration"指**幂等的 index-template apply**,不在上述 RDBMS 表里;其归属规则:JSON template 在 `server/db/index-templates/`(Java 走 `src/main/resources/index-templates/`),applier 脚本在 `server/db/`。详见各项目 `server/db/README.md`。
|
|
27
|
+
|
|
28
|
+
## 2. 三类特殊场景
|
|
29
|
+
|
|
30
|
+
### 2.1 跨 service 共享同一个 DB(微服务但 DB 没拆)
|
|
31
|
+
|
|
32
|
+
DB schema 升格为契约:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
contracts/schemas/db/
|
|
36
|
+
├── README.md # 谁是 schema 拥有者("owner service")
|
|
37
|
+
├── ddl/ # 真值;其他 service 视为 read-only
|
|
38
|
+
│ ├── core_tables.sql
|
|
39
|
+
│ └── views.sql
|
|
40
|
+
└── CHANGELOG.md # 改 schema 必须 bump SemVer
|
|
41
|
+
server/apps/owner/db/migrations/ # 唯一执行迁移的 service
|
|
42
|
+
server/apps/consumer/db/ # 不存 DDL,只存 ORM 映射
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
只有 owner service 的 PR 能改 `contracts/schemas/db/ddl/`;consumer service 改 → CI 拒(按 contracts/ 现有规则)。
|
|
46
|
+
|
|
47
|
+
### 2.2 多租户 schema-per-tenant
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
server/db/migrations/
|
|
51
|
+
├── shared/ # 公共 schema(账户、租户元数据)
|
|
52
|
+
└── tenant/ # 应用到每个租户 schema 的迁移
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
文档说清楚"新租户 onboarding 要跑哪些 migrations"。
|
|
56
|
+
|
|
57
|
+
### 2.3 多 database per service
|
|
58
|
+
|
|
59
|
+
例如:postgres 主库 + elasticsearch 用作搜索。
|
|
60
|
+
|
|
61
|
+
- 选第二个 fragment:手动从对应 `db-<backend>-<other>` 复制配置
|
|
62
|
+
- 在 `server/db/README.md` 里说明双库各管什么
|
|
63
|
+
- CI 起两个 service container
|
|
64
|
+
|
|
65
|
+
scaffolder 0.4.0 不自动支持双库;用户合并两个 fragment 的内容。
|
|
66
|
+
|
|
67
|
+
## 3. 与 `contracts/` 的同步规则
|
|
68
|
+
|
|
69
|
+
### 3.1 字典数据 → 生产种子
|
|
70
|
+
|
|
71
|
+
`contracts/dictionaries/enums.yaml` 是字典(角色 / 错误码 / 状态枚举)的真值。改字典的 PR 必须**同步**:
|
|
72
|
+
|
|
73
|
+
- 加字典项 → 同 PR 在迁移文件中加 INSERT IGNORE / ON CONFLICT DO NOTHING
|
|
74
|
+
- 改字典项的 name / description → 写新迁移更新对应行(不要改既有迁移)
|
|
75
|
+
- 删字典项 → Tier 4,需 ADR
|
|
76
|
+
|
|
77
|
+
> 当前是**手动同步**。自动派生(codegen)留给后续 `contract-derived-seeds` 特性。
|
|
78
|
+
|
|
79
|
+
### 3.2 OpenAPI 字段 → DB 列
|
|
80
|
+
|
|
81
|
+
OpenAPI 中的实体字段与 DB 表的列**应当**对齐(同名、同类型映射)。手动维护;governance-lint 不强制(语义 diff 太复杂)。
|
|
82
|
+
|
|
83
|
+
### 3.3 AsyncAPI 事件 → ES index template
|
|
84
|
+
|
|
85
|
+
`contracts/asyncapi/asyncapi.yaml` 中的事件 schema 与 `server/db/index-templates/` 中的 mapping **应当**对齐。当前手动同步。
|
|
86
|
+
|
|
87
|
+
## 4. 改动分级(参见 `change-tiers.md`)
|
|
88
|
+
|
|
89
|
+
| 改动 | Tier | 备注 |
|
|
90
|
+
|---|---|---|
|
|
91
|
+
| 新增 migration 加表 / 加字段 | 3 | 同 PR 改 `contracts/CHANGELOG.md`(如果字段进契约) |
|
|
92
|
+
| 修改既有 migration 文件 | **不允许** | 写新 migration 代替;Flyway / Alembic / golang-migrate 都拒绝 |
|
|
93
|
+
| 删字段 / 改类型(破坏性) | 4 | ADR + 详细设计 + 数据迁移预案 |
|
|
94
|
+
| 改 dev seed | 1/2 | 不需要同步契约 |
|
|
95
|
+
| 改 prod seed(字典数据) | 3 | 同 PR 改 `contracts/dictionaries/` |
|
|
96
|
+
| 改 ES index-template 的 mapping | 4 | 破坏性 - 可能需要 reindex |
|
|
97
|
+
| 改 ES index-template 的 priority / settings | 2 | 局部行为调整 |
|
|
98
|
+
| 升级 Knex / Flyway / Alembic / golang-migrate / ES SDK | 2 | 同 PR 改 `tech-stack-server.md` 钉版本 |
|
|
99
|
+
|
|
100
|
+
## 5. CI 行为
|
|
101
|
+
|
|
102
|
+
每个 (backend, database) 组合的 CI 都跑 **migrate up + migrate down** smoke test:
|
|
103
|
+
|
|
104
|
+
- RDBMS:`db-migrate-smoke` job 起 service container,跑 `npm run db:migrate && npm run db:rollback`(或对应工具命令)
|
|
105
|
+
- ES:`db-es-template-apply` job 起 ES service container,跑 applier 脚本一次
|
|
106
|
+
|
|
107
|
+
CI **不**校验:
|
|
108
|
+
- schema 内容(用 DBA tool)
|
|
109
|
+
- 与 `contracts/` 字段对齐(语义 diff 太复杂)
|
|
110
|
+
- 数据回填脚本的正确性(一次性脚本不进 CI)
|
|
111
|
+
|
|
112
|
+
## 6. 目录布局速查(非 multi-app 模式)
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
server/
|
|
116
|
+
├── db/
|
|
117
|
+
│ ├── README.md # 工具与命令(按 backend 不同)
|
|
118
|
+
│ ├── migrations/ # Knex / Alembic / golang-migrate 路径
|
|
119
|
+
│ │ └── <ts/N>_<description>.<ext>
|
|
120
|
+
│ ├── index-templates/ # ES applier 模式
|
|
121
|
+
│ │ └── <ts>_<description>.json
|
|
122
|
+
│ ├── seeds/
|
|
123
|
+
│ │ ├── prod/ # 字典数据;与 contracts/dictionaries 对齐
|
|
124
|
+
│ │ └── dev/ # 仅本地 / 测试
|
|
125
|
+
│ └── apply-templates.{cjs,py,go} # ES applier(按 backend)
|
|
126
|
+
├── src/main/resources/db/migration/ # ← Java + Flyway 的迁移路径(与 server/db/ 分离)
|
|
127
|
+
└── src/main/resources/index-templates/ # ← Java + ES applier 的 template 路径
|
|
128
|
+
|
|
129
|
+
contracts/
|
|
130
|
+
├── dictionaries/enums.yaml # 字典真值(→ prod seed 派生)
|
|
131
|
+
├── openapi/api.yaml # API 字段(→ DB 列对齐)
|
|
132
|
+
└── asyncapi/asyncapi.yaml # 事件 schema(→ ES mapping 对齐)
|
|
133
|
+
|
|
134
|
+
scripts/db/ # 一次性运维(数据回填等)
|
|
135
|
+
└── 2026-05-backfill-tenants.sql
|
|
136
|
+
|
|
137
|
+
ops/db/ # DB 实例 / 权限 / 备份(IaC)
|
|
138
|
+
└── grants/
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## 7. multi-app 模式
|
|
142
|
+
|
|
143
|
+
每个 `server/apps/<app>/` 自己一份 `db/`。共享 schema 时(多 app 同 DB)schema 真值进 `contracts/schemas/db/`,由 owner app 唯一执行迁移。完整规则与示例见 §2.1。
|
|
144
|
+
|
|
145
|
+
## 8. 不在本特性范围内的(推到下次 spec)
|
|
146
|
+
|
|
147
|
+
- mongodb 支持(详见 ADR-0005)
|
|
148
|
+
- sqlite 支持(详见 ADR-0005)
|
|
149
|
+
- contracts/dictionaries → seed migration 自动派生
|
|
150
|
+
- web/mobile/miniapp 本地数据库(IndexedDB / SQLite / Realm)
|
|
@@ -117,6 +117,9 @@ files:
|
|
|
117
117
|
- from: files/governance-change-tiers.md
|
|
118
118
|
to: docs/governance/change-tiers.md
|
|
119
119
|
render: false
|
|
120
|
+
- from: files/governance-database.md
|
|
121
|
+
to: docs/governance/database.md
|
|
122
|
+
render: false
|
|
120
123
|
- from: files/governance-checklists.md
|
|
121
124
|
to: docs/governance/checklists.md
|
|
122
125
|
render: true
|