项目尚无统一工程规范,AI 助手与研发各自发挥导致返工风险。建立 总纲 + 细则两层结构:AGENTS.md 只写硬约束与索引(14 条红线、技术栈 基线、仓库结构、命令契约),细则拆为 10 份文档覆盖 Git、代码风格、 格式化、API 与路由、数据库、测试与 CI、Review、日志、密钥、资产命名。 影响面:仅文档。约束力从本轮起对新建代码生效;与现状冲突处以文档为准。 回滚方式:git revert 本提交。
122 lines
6.4 KiB
Markdown
122 lines
6.4 KiB
Markdown
# 05 · 数据库规范
|
||
|
||
| 项 | 内容 |
|
||
| --- | --- |
|
||
| 文档版本 | V1.0(2026-11-02) |
|
||
| 依据 | 技术架构 §5(数据模型)、§8.3 / §8.4(ADR) |
|
||
| 适用范围 | `prisma/`、所有 SQL、数据迁移脚本 |
|
||
| 强制级别 | 强制 |
|
||
|
||
---
|
||
|
||
## 1 命名与主键
|
||
|
||
| 对象 | 规则 | 示例 |
|
||
| --- | --- | --- |
|
||
| 表 | 复数 snake_case | `media_tasks`、`brief_card_index` |
|
||
| 列 | snake_case | `customer_id`、`created_at` |
|
||
| 主键 | `id`,**应用侧生成 ULID**,`char(26)` | `01JA8Z...` |
|
||
| 外键 | `<单数实体>_id` | `script_id`、`customer_id` |
|
||
| 布尔 | `is_` / `has_` 前缀 | `is_archived` |
|
||
| 时间 | `_at` 后缀,`timestamptz` | `reviewed_at` |
|
||
| 金额 / 成本 | `_cost`、`_amount`,`numeric(18,6)` | `unit_cost` |
|
||
| JSON | `_json` 或语义名(`params`、`snapshot`) | `output_params` |
|
||
| 索引 | `idx_<表>_<列>`;唯一 `uq_*`;部分 `idx_*_partial` | `idx_media_tasks_customer_created` |
|
||
|
||
- `DB-01` **禁止库内生成主键**(不用 `gen_random_uuid()` / `uuidv7()`);主键统一由应用生成 ULID,便于排序与分片。
|
||
- `DB-02` 每张业务表必须含 `created_at`、`updated_at`;需要软删的加 `deleted_at`。
|
||
- `DB-03` Prisma 模型用 PascalCase + `@@map` / `@map` 映射到 snake_case,**禁止**直接在库里用 camelCase 列名。
|
||
|
||
---
|
||
|
||
## 2 客户隔离(最高优先级)
|
||
|
||
- `DB-04` 所有业务表 `customer_id NOT NULL` + 外键;同时冗余 `industry_id` 便于按行业聚合。
|
||
- `DB-05` Repository 方法必须接收 `ScopeContext` 并在 `where` 中注入 `customer_id`;**禁止**「先查全量再在内存过滤」(PRD R-04)。
|
||
- `DB-06` 唯一约束必须包含 `customer_id`(如客户内脚本编号唯一),跨客户唯一只允许编码类字段。
|
||
- `DB-07` 跨客户写操作(跨客户复用授权)必须走显式授权表并写审计,**禁止**在业务 SQL 里放宽 `customer_id`。
|
||
- `DB-08` P1 起启用 PG 行级安全(RLS)兜底;启用前必须在 staging 验证「范围外查询返回 0 行」。
|
||
|
||
---
|
||
|
||
## 3 字段与类型
|
||
|
||
| 场景 | 规则 |
|
||
| --- | --- |
|
||
| 枚举 | 优先字典表 + 数据行;表内保存 `*_snapshot`(名称快照),避免字典变更影响历史 |
|
||
| Prisma enum | 仅用于**永不扩展**的封闭集合,且需迁移评审说明 |
|
||
| 动态字段 | JSONB + `schema_version`;可检索字段物化到 `brief_card_index`(D-03) |
|
||
| 大文本 | `text`;禁止 `varchar(255)` 装富文本 |
|
||
| 时长 | 毫秒整数(`duration_ms`),禁止小数秒 |
|
||
| 比例 | `numeric(6,4)`;禁止 `float` 存比率 |
|
||
| 标签 | 关联表或 `text[]`(仅查询简单场景);**禁止** JSONB 存关联 ID(PRD R-25) |
|
||
| 文件 | 只存对象存储 key(`storage_key`)+ 元数据,**禁止存二进制或外链签名 URL** |
|
||
| 密码 / 密钥 | 只存哈希 / 密文;AI 供应商密钥用 AES-256-GCM 加密列 |
|
||
|
||
- `DB-09` JSONB 上默认不建 GIN 索引;仅在确有按字段查询且 `EXPLAIN` 证明需要时建。
|
||
- `DB-10` 禁止 `SELECT *` 返回含大 JSONB 的列表;查询必须显式 `select`。
|
||
|
||
---
|
||
|
||
## 4 索引与查询
|
||
|
||
- `DB-11` 所有外键列建索引。
|
||
- `DB-12` 列表页统一复合索引:`(customer_id, created_at DESC, id DESC)`;游标分页基于 `(created_at, id)`(技术架构 §5.7)。
|
||
- `DB-13` 唯一约束配合软删用**部分索引**:`WHERE deleted_at IS NULL`。
|
||
- `DB-14` 禁止无条件的 `COUNT(*)` 大表统计;计数走预聚合表或 `EXPLAIN` 验证代价。
|
||
- `DB-15` 禁止 OFFSET 深翻页;分页一律游标。
|
||
- `DB-16` 禁止 N+1:列表查询用 `include`/`select` 显式取关联,或批量 `IN` 查询。
|
||
- `DB-17` 批量写入单批 ≤ 1000 行,超出分片;`createMany` 需注意不触发单条钩子。
|
||
- `DB-18` 慢查询(> 200ms)必须记录到日志并带 `trace_id`,纳入每周巡检。
|
||
|
||
---
|
||
|
||
## 5 迁移(Prisma Migrate)
|
||
|
||
- `DB-19` 迁移文件命名 `YYYYMMDDHHmmss_<动词>_<对象>`,如 `20261102153000_add_material_specs`。
|
||
- `DB-20` **禁止修改已合并的迁移文件**;修正用新迁移。
|
||
- `DB-21` 采用 **expand → migrate → contract** 三步;**禁止**同一次发布内「删字段 + 改代码」(技术架构 §4.6)。
|
||
- `DB-22` 生产迁移必须幂等、可重入;大表加列用可空列或带默认值的在线加列,禁止锁表长事务。
|
||
- `DB-23` 迁移前必须备份,并在 staging 演练;回滚脚本随迁移一起提交(`down` 说明)。
|
||
- `DB-24` `prisma db push` 仅限本地探索,**禁止**用于任何共享环境。
|
||
- `DB-25` 迁移涉及数据回填时,回填脚本独立于 schema 迁移,可重复执行。
|
||
|
||
---
|
||
|
||
## 6 事务与并发
|
||
|
||
- `DB-26` 事务边界在 Service 层;跨表写必须同事务。
|
||
- `DB-27` 事务内**禁止外部 IO**(AI 调用、HTTP、对象存储、ffmpeg)。
|
||
- `DB-28` 编号 / 序号类唯一值(脚本编号、批次号)必须用**原子取号**(`UPDATE ... RETURNING` 或序列表 + 行锁),禁止「先查后写」。
|
||
- `DB-29` 状态迁移用条件更新(`WHERE status = 'pending'`)避免并发覆盖;受影响行数为 0 视为冲突并返回 409。
|
||
- `DB-30` 避免长事务(> 5s);批量任务状态更新按批提交。
|
||
|
||
---
|
||
|
||
## 7 删除与保留
|
||
|
||
| 数据 | 策略 |
|
||
| --- | --- |
|
||
| 业务实体 | 软删 `deleted_at`;被引用的资产**只能停用不能删除**(PRD R-02) |
|
||
| 审计 / 账本 / 任务事件 | **append-only**,禁止 UPDATE / DELETE |
|
||
| 任务临时产物 | `temp` 桶 30 天自动清理 |
|
||
| 导出包 | `exports` 桶 3 天 |
|
||
| 归档 | `archive` 桶 ≥ 12 个月 |
|
||
| 客户数据删除 | 只按 PRD 约定的保留期与流程执行,**禁止**开发者自行清理 |
|
||
|
||
- `DB-31` 硬删只允许:临时文件、失败任务产物、测试数据;且必须有清理任务与日志。
|
||
- `DB-32` 审计表权限:应用账号仅 `INSERT` + `SELECT`,无 `UPDATE` / `DELETE` 授权。
|
||
|
||
---
|
||
|
||
## 8 检查清单
|
||
|
||
- [ ] 表 / 列 snake_case,主键 ULID,`created_at` / `updated_at` 齐全
|
||
- [ ] 业务表有 `customer_id` + 外键 + 冗余 `industry_id`
|
||
- [ ] Repository 接收 `ScopeContext`,SQL 带客户维度
|
||
- [ ] 列表查询走复合索引 + 游标分页,无 `SELECT *`、无 N+1
|
||
- [ ] 迁移 expand/contract,未改历史迁移,staging 已演练
|
||
- [ ] 事务内无外部 IO;编号原子取号;状态迁移带条件
|
||
- [ ] 审计 append-only;临时产物有清理策略
|
||
- [ ] 新增 JSONB 字段有 `schema_version` 与检索策略
|