@wneng/create-keel 0.3.4 → 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 +211 -18
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/templates/ci-gitee/files/PULL_REQUEST_TEMPLATE.md +13 -1
- package/src/templates/ci-gitee/files/pipeline.yml +71 -0
- package/src/templates/ci-github/files/PULL_REQUEST_TEMPLATE.md +13 -1
- package/src/templates/ci-github/files/ci.yml +169 -0
- package/src/templates/contracts-events/fragment.yaml +1 -1
- package/src/templates/contracts-rest-events/fragment.yaml +1 -1
- 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-change-tiers.md +135 -0
- package/src/templates/docs-skeleton/files/governance-database.md +150 -0
- package/src/templates/docs-skeleton/fragment.yaml +6 -0
- package/src/templates/root-files/files/AGENTS.md +42 -7
- package/src/templates/server-node/files/_eslintrc.cjs +5 -1
- 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
- package/src/templates/web-react/files/_eslintrc.cjs +5 -1
- package/src/templates/web-vue/files/_eslintrc.cjs +5 -1
|
@@ -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)
|
|
@@ -114,6 +114,12 @@ files:
|
|
|
114
114
|
- from: files/governance-ai-access-modes.md
|
|
115
115
|
to: docs/governance/ai-access-modes.md
|
|
116
116
|
render: true
|
|
117
|
+
- from: files/governance-change-tiers.md
|
|
118
|
+
to: docs/governance/change-tiers.md
|
|
119
|
+
render: false
|
|
120
|
+
- from: files/governance-database.md
|
|
121
|
+
to: docs/governance/database.md
|
|
122
|
+
render: false
|
|
117
123
|
- from: files/governance-checklists.md
|
|
118
124
|
to: docs/governance/checklists.md
|
|
119
125
|
render: true
|
|
@@ -45,7 +45,9 @@
|
|
|
45
45
|
| `.claude/` | AI | Claude Code slash-commands |
|
|
46
46
|
<% } %>
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
<% if (it.options.database !== 'none') { %>> **数据库相关产物**(已启用 `database=<%= it.options.database %>`):`server/db/`(迁移、种子<% if (it.options.database === 'elasticsearch') { %>、ES index template<% } %>、运维脚本入口)的归属规则见 [`docs/governance/database.md`](docs/governance/database.md)。<% if (it.options.backend === 'java') { %> Java + <%= it.options.database === 'elasticsearch' ? 'spring-data-elasticsearch' : 'Flyway' %> 因 Spring Boot classpath 约定,<%= it.options.database === 'elasticsearch' ? 'index template' : '迁移' %>文件实际在 `server/src/main/resources/<%= it.options.database === 'elasticsearch' ? 'index-templates/' : 'db/migration/' %>`。<% } %>
|
|
49
|
+
|
|
50
|
+
<% } %>### 1.1 单应用 vs 多应用模式
|
|
49
51
|
|
|
50
52
|
每个执行环境目录(`server/` `web/` `mobile/` `miniapp/` `agent/`)默认处于**单应用模式**。需要容纳第二个应用时切换到**多应用模式**:
|
|
51
53
|
|
|
@@ -57,7 +59,7 @@
|
|
|
57
59
|
|
|
58
60
|
## 2. 不可协商原则
|
|
59
61
|
|
|
60
|
-
- **Contract First
|
|
62
|
+
- **Contract First**:契约定义优先于代码实现;变更触及契约时,**契约先冻结再写 code**(详见 §6 变更分级)。
|
|
61
63
|
- 接口、事件、错误码、字典、状态机统一进入 `contracts/`,**禁止**在模块内部重复定义。
|
|
62
64
|
- 由契约生成的 `generated/` 代码**禁止手改**;CI 校验"重新生成后 diff 为空"。
|
|
63
65
|
- **禁止**提交 secret、私钥、真实 PII、真实用户数据。
|
|
@@ -93,6 +95,19 @@
|
|
|
93
95
|
| 后端模块详细设计 | `docs/04-后端详细设计/` |
|
|
94
96
|
| 前端 / 客户端模块详细设计 | `docs/05-前端客户端详细设计/` |
|
|
95
97
|
<% if (it.options.integrations) { %>| 跨执行环境对接(已启用 `integrations`) | `docs/06-集成对接/<consumer>-to-<producer>/` |
|
|
98
|
+
<% } else { %>| 跨执行环境对接(**未启用 `integrations`**,启用后位于此目录) | `docs/06-集成对接/` |
|
|
99
|
+
<% } %><% if (it.options.roles && it.options.roles.includes('qa')) { %>| 测试计划 / 用例 / 缺陷库(已启用 qa 角色)| `docs/07-质量与测试/` |
|
|
100
|
+
<% } else { %>| 测试计划 / 用例 / 缺陷库(**未启用 qa 角色**,启用后位于此目录) | `docs/07-质量与测试/` |
|
|
101
|
+
<% } %><% if (it.options.roles && it.options.roles.includes('field')) { %>| 客户部署手册 / 上线作业书(已启用 field 角色,on-demand) | `docs/08-部署与现场实施/` |
|
|
102
|
+
<% } else { %>| 客户部署手册 / 上线作业书(**未启用 field 角色**) | `docs/08-部署与现场实施/`(启用后 on-demand)|
|
|
103
|
+
<% } %><% if (it.options.roles && it.options.roles.includes('data')) { %>| 埋点契约 / 看板 / 数据资产(已启用 data 角色) | `docs/09-数据与埋点/` |
|
|
104
|
+
<% } else { %>| 埋点契约 / 看板 / 数据资产(**未启用 data 角色**) | `docs/09-数据与埋点/` |
|
|
105
|
+
<% } %><% if (it.options.roles && it.options.roles.includes('legal-security')) { %>| 合规清单 / PIA / 安全证据(已启用 legal-security 角色,on-demand) | `docs/10-合规与安全/` |
|
|
106
|
+
<% } else { %>| 合规清单 / PIA / 安全证据(**未启用 legal-security 角色**) | `docs/10-合规与安全/`(启用后 on-demand) |
|
|
107
|
+
<% } %><% if (it.options.roles && it.options.roles.includes('marketing')) { %>| 产品介绍 / 白皮书 / 案例(已启用 marketing 角色,on-demand) | `docs/11-市场与对外材料/` |
|
|
108
|
+
<% } else { %>| 产品介绍 / 白皮书 / 案例(**未启用 marketing 角色**) | `docs/11-市场与对外材料/`(启用后 on-demand) |
|
|
109
|
+
<% } %><% if (it.options.roles && it.options.roles.includes('design')) { %>| 设计稿 / UI 规范(已启用 design 角色,on-demand) | `docs/design/` |
|
|
110
|
+
<% } else { %>| 设计稿 / UI 规范(**未启用 design 角色**) | `docs/design/`(启用后 on-demand) |
|
|
96
111
|
<% } %>| 治理细则(CI / 安全 / Git / 资产 / 等) | `docs/governance/`(详见 §8) |
|
|
97
112
|
| 架构图源、UI 稿、截图、设计稿 | `docs/assets/{diagrams,images,design}/` |
|
|
98
113
|
| 行业标准、厂商手册、法规、合规参考 | `docs/references/{standards,vendors,legal}/` |
|
|
@@ -125,11 +140,28 @@
|
|
|
125
140
|
- 完整规则 → [`docs/governance/integrations.md`](docs/governance/integrations.md)
|
|
126
141
|
<% } %>
|
|
127
142
|
|
|
128
|
-
## 6.
|
|
143
|
+
## 6. 核心协作规则(变更分级)
|
|
144
|
+
|
|
145
|
+
变更分**六档**,文档与契约的"厚度"匹配影响面。完整判定决策树、反例、与 SemVer 关系 → [`docs/governance/change-tiers.md`](docs/governance/change-tiers.md)。
|
|
146
|
+
|
|
147
|
+
| Tier | 典型 | 文档要求 | 契约动作 |
|
|
148
|
+
|---|---|---|---|
|
|
149
|
+
| 0 trivial | typo / 格式化 / patch 升级 / 补测试 | PR title + 1-2 行描述 | 不动 |
|
|
150
|
+
| 1 bugfix | 修 NPE / SQL / UI 错位(无新行为) | PR 描述:重现 + 根因 + 修复 + 回归测试 | 不动 |
|
|
151
|
+
| 2 small | 加按钮 / 排序 / 文案 / 配置项 | PR 描述(动机 + 方案 + 影响面) | 通常不动 |
|
|
152
|
+
| 3 contract | 加 API / 字段 / 错误码 / 事件 / 状态 | 简短设计章节进 `docs/04` 或 `docs/05` | **必须**先冻结契约 + CHANGELOG |
|
|
153
|
+
| 4 arch | 跨服务 / 鉴权变化 / 删字段 / breaking | PRD(如来自需求)→ ADR → 详细设计 → 契约 → code | **必须** ADR + 契约 + 迁移说明 |
|
|
154
|
+
| 5 spike | 探索 / 方案未明 | `spike/*` 分支自由探索 | 合入 `main` 前补到对应 tier |
|
|
155
|
+
|
|
156
|
+
**判定**:改 `contracts/` → 至少 Tier 3;改鉴权 / 跨服务 / 删字段 → Tier 4;纯局部 → 0/1/2;探索 → 5。
|
|
157
|
+
|
|
158
|
+
**AI 引用义务**(按 tier 升级):
|
|
159
|
+
- Tier 0/1/2:PR 描述自身即方案记录,**不强制**引用契约或 ADR
|
|
160
|
+
- Tier 3+:必须显式引用契约锚点(如 `contracts/openapi/api.yaml#/paths/~1users/post`)
|
|
161
|
+
- Tier 4:必须引用 ADR / 详细设计文档
|
|
129
162
|
|
|
130
|
-
|
|
131
|
-
-
|
|
132
|
-
- `spike/*` 分支允许暂时跳过契约;合入 `main` 前必须补齐。
|
|
163
|
+
其他:
|
|
164
|
+
- `spike/*` 分支允许暂时跳过契约(即 Tier 5);合入 `main` 前必须补齐到对应 tier
|
|
133
165
|
- 完整 PR 模板与 CODEOWNERS → [`docs/governance/git-workflow.md`](docs/governance/git-workflow.md)
|
|
134
166
|
- 契约 SemVer / CHANGELOG / 废弃流程 → [`contracts/README.md`](contracts/README.md)
|
|
135
167
|
|
|
@@ -146,7 +178,8 @@
|
|
|
146
178
|
| `docs/assets/**` 或根 `.gitattributes` | `docs/governance/assets.md` |
|
|
147
179
|
| `ops/**` 或 `deploy/**` | `docs/governance/deploy-ops.md`、`docs/governance/security.md` |
|
|
148
180
|
| `tools/**` 或 `scripts/**` | `docs/governance/tools-scripts.md` |
|
|
149
|
-
<% if (it.options.
|
|
181
|
+
<% if (it.options.database !== 'none') { %>| `server/db/**`(迁移 / 种子 / ES template / applier) | `docs/governance/database.md`、`contracts/dictionaries/`(如改字典 seed) |
|
|
182
|
+
<% } %><% if (it.options.ci === 'gitee') { %>| `.gitee/**` | `docs/governance/ci.md`、`docs/governance/security.md` |
|
|
150
183
|
<% } else { %>| `.github/**` | `docs/governance/ci.md`、`docs/governance/security.md` |
|
|
151
184
|
<% } %>
|
|
152
185
|
|
|
@@ -174,6 +207,8 @@
|
|
|
174
207
|
| 集成对接 | [`docs/governance/integrations.md`](docs/governance/integrations.md) |
|
|
175
208
|
| ops / deploy 完整规则 | [`docs/governance/deploy-ops.md`](docs/governance/deploy-ops.md) |
|
|
176
209
|
| tools / scripts 完整规则 | [`docs/governance/tools-scripts.md`](docs/governance/tools-scripts.md) |
|
|
210
|
+
| 变更分级(六档 Tier) | [`docs/governance/change-tiers.md`](docs/governance/change-tiers.md) |
|
|
211
|
+
| 数据库目录 / 迁移工具 / SQL 归属 | [`docs/governance/database.md`](docs/governance/database.md) |
|
|
177
212
|
| 完整 checklist | [`docs/governance/checklists.md`](docs/governance/checklists.md) |
|
|
178
213
|
| 治理细则索引 | [`docs/governance/README.md`](docs/governance/README.md) |
|
|
179
214
|
|
|
@@ -13,7 +13,11 @@ module.exports = {
|
|
|
13
13
|
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
|
|
14
14
|
rules: {
|
|
15
15
|
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
|
|
16
|
-
|
|
16
|
+
// docs/03-工程规范与研发基础设施/coding-style-typescript.md forbids `any`.
|
|
17
|
+
// Use `unknown` + narrowing, or define the missing type. Escape hatch:
|
|
18
|
+
// a per-line `// eslint-disable-next-line @typescript-eslint/no-explicit-any`
|
|
19
|
+
// with a comment explaining why.
|
|
20
|
+
'@typescript-eslint/no-explicit-any': 'error',
|
|
17
21
|
'@typescript-eslint/consistent-type-imports': 'warn',
|
|
18
22
|
},
|
|
19
23
|
ignorePatterns: ['dist', 'node_modules', 'generated'],
|
|
@@ -1,4 +1,77 @@
|
|
|
1
1
|
# server/
|
|
2
2
|
|
|
3
|
-
Python backend for **<%= it.options.projectName
|
|
4
|
-
|
|
3
|
+
Python backend for **<%= it.options.projectName %>**, built on **FastAPI**.
|
|
4
|
+
|
|
5
|
+
> Contract First:路由 / 请求体 / 响应体应当与 [`../contracts/openapi/api.yaml`](../contracts/openapi/api.yaml) 对齐。新增端点的工作流见根 `AGENTS.md` §6(变更分级)+ `docs/governance/change-tiers.md`:端点新增 / 字段新增是 Tier 3,必须先冻结 contracts 再写实现。
|
|
6
|
+
|
|
7
|
+
## 目录布局
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
server/
|
|
11
|
+
├── pyproject.toml # 依赖钉版本、ruff / mypy / pytest 配置
|
|
12
|
+
├── app/
|
|
13
|
+
│ ├── __init__.py # re-export `app` 给 `python -m app`
|
|
14
|
+
│ ├── main.py # FastAPI 实例 + /healthz + CORS
|
|
15
|
+
│ └── config.py # pydantic-settings;从环境 / .env 读配置
|
|
16
|
+
└── tests/
|
|
17
|
+
└── test_healthz.py # ASGI smoke test
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## 工具与版本
|
|
21
|
+
|
|
22
|
+
| 项 | 值 |
|
|
23
|
+
|---|---|
|
|
24
|
+
| Python | 3.11+ |
|
|
25
|
+
| Framework | FastAPI `0.115.5` |
|
|
26
|
+
| ASGI server | uvicorn `0.32.0` |
|
|
27
|
+
| Validation | pydantic `2.9.2` + pydantic-settings `2.6.1` |
|
|
28
|
+
| Lint / format | ruff `0.6.8+` |
|
|
29
|
+
| Type check | mypy `1.11.0+`(strict-ish) |
|
|
30
|
+
| Test | pytest `8.3.0+` + pytest-asyncio + httpx |
|
|
31
|
+
|
|
32
|
+
版本钉死见 `pyproject.toml`,并镜像到 `docs/03-工程规范与研发基础设施/tech-stack-server.md`。governance-lint 检测两边漂移并阻断 PR。
|
|
33
|
+
|
|
34
|
+
## 常用命令
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# 创建虚拟环境 + 装依赖
|
|
38
|
+
python -m venv .venv && source .venv/bin/activate
|
|
39
|
+
pip install -e ".[dev]"
|
|
40
|
+
|
|
41
|
+
# 本地开发(热加载)
|
|
42
|
+
uvicorn app.main:app --reload --port 8000
|
|
43
|
+
# 浏览自动生成的 OpenAPI UI:http://127.0.0.1:8000/docs
|
|
44
|
+
|
|
45
|
+
# 测试 / lint / format / typecheck
|
|
46
|
+
pytest -q
|
|
47
|
+
ruff check .
|
|
48
|
+
ruff format --check .
|
|
49
|
+
mypy .
|
|
50
|
+
|
|
51
|
+
# 安全扫描
|
|
52
|
+
pip-audit
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## 配置
|
|
56
|
+
|
|
57
|
+
环境变量优先级:CLI / 容器注入 > `.env` 文件 > `app/config.py` 默认值。
|
|
58
|
+
|
|
59
|
+
| 变量 | 默认 | 说明 |
|
|
60
|
+
|---|---|---|
|
|
61
|
+
| `APP_NAME` | `<projectName>-server` | 显示在 OpenAPI title |
|
|
62
|
+
| `APP_VERSION` | `0.1.0` | 显示在 OpenAPI version |
|
|
63
|
+
| `CORS_ALLOW_ORIGINS` | `["http://localhost:5173","http://localhost:3000"]` | 逗号分隔;生产环境收紧 |
|
|
64
|
+
|
|
65
|
+
## 部署
|
|
66
|
+
|
|
67
|
+
打包 / 镜像 / 流水线归 `deploy/` 与 `ops/`,详见 [`../docs/governance/deploy-ops.md`](../docs/governance/deploy-ops.md)。最小生产命令示例:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 4
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## 与契约的同步
|
|
74
|
+
|
|
75
|
+
- API 端点:`contracts/openapi/api.yaml` → `app/main.py` 路由;改契约同 PR 改实现
|
|
76
|
+
- 字典 / 枚举:`contracts/dictionaries/enums.yaml`(如启用)→ Pydantic Enum
|
|
77
|
+
- 错误码:`contracts/errors/error-codes.yaml` → Exception → HTTP status 映射
|
|
@@ -1 +1,11 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""<%= it.options.projectName %>-server.
|
|
2
|
+
|
|
3
|
+
Application package. Entry point: app.main:app
|
|
4
|
+
Run locally: `uvicorn app.main:app --reload`
|
|
5
|
+
|
|
6
|
+
Contract First:路由实现见 contracts/openapi/api.yaml;新端点先改契约再写代码。
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from .main import app
|
|
10
|
+
|
|
11
|
+
__all__ = ["app"]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Application configuration via pydantic-settings.
|
|
2
|
+
|
|
3
|
+
环境变量映射规则:
|
|
4
|
+
- 全部大写、`_` 分隔
|
|
5
|
+
- 嵌套字段用 `__` 分隔(pydantic-settings 默认)
|
|
6
|
+
- 例:APP_NAME / APP_VERSION / CORS_ALLOW_ORIGINS=http://localhost:5173,http://localhost:3000
|
|
7
|
+
|
|
8
|
+
`.env` 文件由 pydantic-settings 自动加载(项目根目录)。
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from pydantic import Field
|
|
12
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Settings(BaseSettings):
|
|
16
|
+
"""Runtime settings.
|
|
17
|
+
|
|
18
|
+
新增字段必须:
|
|
19
|
+
- 给默认值(不要让生产环境因缺一个变量启动崩溃)
|
|
20
|
+
- 同步更新根 `.env.example`(如启用 contracts-base fragment)
|
|
21
|
+
- 敏感字段(密钥、token)只能从环境变量读,绝不写默认值
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
model_config = SettingsConfigDict(
|
|
25
|
+
env_file=".env",
|
|
26
|
+
env_file_encoding="utf-8",
|
|
27
|
+
case_sensitive=False,
|
|
28
|
+
extra="ignore",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
app_name: str = Field(default="<%= it.options.projectName %>-server")
|
|
32
|
+
app_version: str = Field(default="0.1.0")
|
|
33
|
+
|
|
34
|
+
# CORS:逗号分隔字符串解析为列表
|
|
35
|
+
cors_allow_origins: list[str] = Field(
|
|
36
|
+
default_factory=lambda: ["http://localhost:5173", "http://localhost:3000"]
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
settings = Settings()
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""<%= it.options.projectName %>-server entry point.
|
|
2
|
+
|
|
3
|
+
Contract First:路由 / 请求体 / 响应体应当与 contracts/openapi/api.yaml 对齐。
|
|
4
|
+
新增端点的工作流见根 AGENTS.md §6(变更分级)+ docs/governance/change-tiers.md:
|
|
5
|
+
端点新增 / 字段新增是 Tier 3,必须先冻结 contracts/openapi/api.yaml 再写实现。
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from collections.abc import AsyncIterator
|
|
9
|
+
from contextlib import asynccontextmanager
|
|
10
|
+
|
|
11
|
+
from fastapi import FastAPI
|
|
12
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
13
|
+
|
|
14
|
+
from .config import settings
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@asynccontextmanager
|
|
18
|
+
async def lifespan(_app: FastAPI) -> AsyncIterator[None]:
|
|
19
|
+
"""启动 / 关闭钩子。
|
|
20
|
+
|
|
21
|
+
在这里做:
|
|
22
|
+
- 数据库连接池初始化(如启用 db)
|
|
23
|
+
- Elasticsearch 客户端初始化(如启用 db=elasticsearch)
|
|
24
|
+
- 后台任务启动
|
|
25
|
+
关闭时反向清理。
|
|
26
|
+
"""
|
|
27
|
+
yield
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
app = FastAPI(
|
|
31
|
+
title=settings.app_name,
|
|
32
|
+
version=settings.app_version,
|
|
33
|
+
description="Generated by @wneng/create-keel; implement per contracts/openapi/api.yaml.",
|
|
34
|
+
lifespan=lifespan,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
# CORS — 默认放开本地前端开发常用端口;生产请通过环境变量收紧 allow_origins
|
|
38
|
+
app.add_middleware(
|
|
39
|
+
CORSMiddleware,
|
|
40
|
+
allow_origins=settings.cors_allow_origins,
|
|
41
|
+
allow_credentials=True,
|
|
42
|
+
allow_methods=["*"],
|
|
43
|
+
allow_headers=["*"],
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@app.get("/healthz", tags=["meta"])
|
|
48
|
+
async def healthz() -> dict[str, str]:
|
|
49
|
+
"""Liveness 探针。
|
|
50
|
+
|
|
51
|
+
部署时由 Kubernetes / Docker Compose 周期性调用;返回非 200 即视为不健康。
|
|
52
|
+
"""
|
|
53
|
+
return {"status": "ok"}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@app.get("/", tags=["meta"])
|
|
57
|
+
async def root() -> dict[str, str]:
|
|
58
|
+
return {
|
|
59
|
+
"name": settings.app_name,
|
|
60
|
+
"version": settings.app_version,
|
|
61
|
+
"docs": "/docs",
|
|
62
|
+
}
|
|
@@ -2,13 +2,20 @@
|
|
|
2
2
|
name = "<%= it.options.projectName %>-server"
|
|
3
3
|
version = "0.1.0"
|
|
4
4
|
requires-python = ">=3.11"
|
|
5
|
-
dependencies = [
|
|
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
|
+
]
|
|
6
11
|
|
|
7
12
|
[project.optional-dependencies]
|
|
8
13
|
dev = [
|
|
9
14
|
"ruff>=0.6.8",
|
|
10
15
|
"mypy>=1.11.0",
|
|
11
16
|
"pytest>=8.3.0",
|
|
17
|
+
"pytest-asyncio>=0.24.0",
|
|
18
|
+
"httpx>=0.27.0",
|
|
12
19
|
"pip-audit>=2.7.0",
|
|
13
20
|
]
|
|
14
21
|
|
|
@@ -47,3 +54,9 @@ exclude = ["generated", ".venv"]
|
|
|
47
54
|
|
|
48
55
|
[tool.pytest.ini_options]
|
|
49
56
|
testpaths = ["tests"]
|
|
57
|
+
asyncio_mode = "auto"
|
|
58
|
+
|
|
59
|
+
# 显式声明应用包,避免 setuptools 把 generated/ 误认为顶层包导致 build error
|
|
60
|
+
[tool.setuptools.packages.find]
|
|
61
|
+
include = ["app*"]
|
|
62
|
+
exclude = ["tests*", "generated*"]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Smoke test for the healthz endpoint.
|
|
2
|
+
|
|
3
|
+
CI 跑 `pytest -q` 时会运行;端到端验证:
|
|
4
|
+
- FastAPI app 能 import(语法、依赖、配置都对)
|
|
5
|
+
- lifespan 钩子能起、能落
|
|
6
|
+
- /healthz 返回 200 + {"status": "ok"}
|
|
7
|
+
|
|
8
|
+
新加端点应在 tests/ 下补对应测试;契约级断言(响应 schema 一致性)由
|
|
9
|
+
contracts/tests/contracts/ 下的契约测试覆盖,不在这里重复。
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import pytest
|
|
13
|
+
from httpx import ASGITransport, AsyncClient
|
|
14
|
+
|
|
15
|
+
from app.main import app
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@pytest.mark.asyncio
|
|
19
|
+
async def test_healthz_returns_ok() -> None:
|
|
20
|
+
transport = ASGITransport(app=app)
|
|
21
|
+
async with AsyncClient(transport=transport, base_url="http://test") as client:
|
|
22
|
+
response = await client.get("/healthz")
|
|
23
|
+
assert response.status_code == 200
|
|
24
|
+
assert response.json() == {"status": "ok"}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@pytest.mark.asyncio
|
|
28
|
+
async def test_root_returns_metadata() -> None:
|
|
29
|
+
transport = ASGITransport(app=app)
|
|
30
|
+
async with AsyncClient(transport=transport, base_url="http://test") as client:
|
|
31
|
+
response = await client.get("/")
|
|
32
|
+
assert response.status_code == 200
|
|
33
|
+
body = response.json()
|
|
34
|
+
assert body["name"].endswith("-server")
|
|
35
|
+
assert body["version"] == "0.1.0"
|
|
36
|
+
assert body["docs"] == "/docs"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
name: server-python
|
|
2
|
-
version: 1.
|
|
2
|
+
version: 1.1.0
|
|
3
3
|
appliesWhen:
|
|
4
4
|
backend: python
|
|
5
5
|
priority: 30
|
|
@@ -12,6 +12,15 @@ files:
|
|
|
12
12
|
render: true
|
|
13
13
|
- from: files/app-init.py
|
|
14
14
|
to: server/app/__init__.py
|
|
15
|
+
render: true
|
|
16
|
+
- from: files/main.py
|
|
17
|
+
to: server/app/main.py
|
|
18
|
+
render: true
|
|
19
|
+
- from: files/config.py
|
|
20
|
+
to: server/app/config.py
|
|
21
|
+
render: true
|
|
22
|
+
- from: files/test_healthz.py
|
|
23
|
+
to: server/tests/test_healthz.py
|
|
15
24
|
render: false
|
|
16
25
|
- from: files/generated-gitkeep
|
|
17
26
|
to: server/generated/.gitkeep
|
|
@@ -27,7 +27,11 @@ module.exports = {
|
|
|
27
27
|
'react/react-in-jsx-scope': 'off',
|
|
28
28
|
'react/prop-types': 'off',
|
|
29
29
|
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
|
|
30
|
-
|
|
30
|
+
// docs/03-工程规范与研发基础设施/coding-style-typescript.md forbids `any`.
|
|
31
|
+
// Use `unknown` + narrowing, or define the missing type. Escape hatch:
|
|
32
|
+
// a per-line `// eslint-disable-next-line @typescript-eslint/no-explicit-any`
|
|
33
|
+
// with a comment explaining why.
|
|
34
|
+
'@typescript-eslint/no-explicit-any': 'error',
|
|
31
35
|
'@typescript-eslint/consistent-type-imports': 'warn',
|
|
32
36
|
},
|
|
33
37
|
ignorePatterns: ['dist', 'node_modules', 'generated'],
|
|
@@ -21,7 +21,11 @@ module.exports = {
|
|
|
21
21
|
],
|
|
22
22
|
rules: {
|
|
23
23
|
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
|
|
24
|
-
|
|
24
|
+
// docs/03-工程规范与研发基础设施/coding-style-typescript.md forbids `any`.
|
|
25
|
+
// Use `unknown` + narrowing, or define the missing type. Escape hatch:
|
|
26
|
+
// a per-line `// eslint-disable-next-line @typescript-eslint/no-explicit-any`
|
|
27
|
+
// with a comment explaining why.
|
|
28
|
+
'@typescript-eslint/no-explicit-any': 'error',
|
|
25
29
|
'vue/multi-word-component-names': 'off',
|
|
26
30
|
},
|
|
27
31
|
ignorePatterns: ['dist', 'node_modules', 'generated'],
|