a3bb78e06b
Security Quality Gate / npm audit, Bandit, pip-audit, detect-secrets (pull_request) Failing after 16m35s
795 lines
21 KiB
Markdown
795 lines
21 KiB
Markdown
# AI 合同平台 V2 数据库优化重构方案
|
||
|
||
日期:2026-06-24
|
||
|
||
## 1. 结论
|
||
|
||
需要做数据库优化重构,而且应该在 V2 继续扩展业务功能前完成。
|
||
|
||
当前 V2 代码已经按 `yuqei-contract-platform`、`yuqei-ai-platform`、`yuqei-ai-sdk-python`、`yuqei-infra` 重新划分,但数据库层还处在 MVP 过渡状态:
|
||
|
||
- Contract API 已有 Alembic 和 SQLAlchemy 仓储,但表结构主要服务“主流程跑通”,缺少企业级租户、权限、软删除、组合索引和最终状态建模。
|
||
- AI Platform 已有知识库、chunk、AI Trace、质量评测、pgvector 起点,但还没有形成生产级检索索引、租户隔离、分区、冷热归档和可审计配置发布模型。
|
||
- 当前 compose 为了本地联调,把 Contract API 和 AI Platform 放在同一个 PostgreSQL 数据库里,只用不同 Alembic version table 隔离迁移历史。这适合开发,不适合作为最终生产数据库边界。
|
||
|
||
因为项目尚未正式上线,建议不要继续背 V1 兼容包袱,也不要在现有 MVP 迁移上无限叠加。应该在 V2 进入更多业务功能前,执行一次“干净数据库基线”重构。
|
||
|
||
## 2. 本方案范围
|
||
|
||
本方案覆盖:
|
||
|
||
- V1 数据库问题归纳。
|
||
- 当前 V2 数据库现状判断。
|
||
- V2 最终数据库边界与核心表设计。
|
||
- 性能索引、全文检索、向量检索、分区策略。
|
||
- 安全、租户隔离、审计、备份恢复。
|
||
- 从当前 V2 MVP 到最终数据库基线的实施路径。
|
||
|
||
本方案不直接改代码,不直接迁移生产数据。下一轮开发可按本文拆分 DB 任务执行。
|
||
|
||
## 3. V1 数据库主要问题
|
||
|
||
根据 `D:\yuqei-project\ai-contract-platform-v1` 的模型与迁移观察,V1 有以下典型问题。
|
||
|
||
### 3.1 迁移数量多,功能演进痕迹重
|
||
|
||
V1 后端已有大量迁移,覆盖审查、合同生成、知识库、法条、治理、商业订单、AI 质量评测、模型参数、审批等领域。
|
||
|
||
问题不是“迁移多”本身,而是这些能力都堆在一个业务后端数据库里,导致:
|
||
|
||
- 合同业务、AI 能力、知识库、治理、商业订单混在一起。
|
||
- 不同模块数据增长速度差异巨大,却共享同一套表空间、索引和备份策略。
|
||
- 后续性能问题难以定位:到底是合同列表慢、知识库检索慢,还是 AI Trace 膨胀拖慢。
|
||
|
||
V2 不能继续沿用这种“一个后端库不断追加功能表”的模式。
|
||
|
||
### 3.2 租户字段存在但不统一
|
||
|
||
V1 多数核心表有 `tenant_id`,例如 users、review_tasks、files、knowledge_bases、legal_articles 等。
|
||
|
||
但问题是:
|
||
|
||
- 并非所有业务表都有完整 `tenant_id`。
|
||
- 主要依赖应用层过滤,缺少数据库层最后防线。
|
||
- 多租户唯一约束不够标准,例如应该更多使用 `(tenant_id, code)`、`(tenant_id, username)` 这类唯一约束。
|
||
- 审批、审计、文件、知识库、AI Trace 在租户维度上的隔离策略不一致。
|
||
|
||
V2 必须从数据库层统一租户上下文。
|
||
|
||
### 3.3 JSON 与大文本字段使用过重
|
||
|
||
V1 `review_results` 中大量结构化结果存为 Text JSON:
|
||
|
||
- `basic_info_json`
|
||
- `clauses_json`
|
||
- `risk_items_json`
|
||
- `risk_summary_json`
|
||
- `confidence_summary`
|
||
|
||
这种做法短期开发快,但会带来:
|
||
|
||
- 查询困难,无法稳定索引风险项、法条引用、风险等级等字段。
|
||
- 数据质量不可控,字段结构漂移后很难校验。
|
||
- 后续统计分析、质量评测、用户筛选都需要反复解析 JSON。
|
||
|
||
V2 可以保留 JSONB 作为扩展字段,但核心查询字段必须结构化。
|
||
|
||
### 3.4 文件内容曾进入数据库
|
||
|
||
V1 `generated_contracts` 里存在 `file_content_base64`,这类设计不适合生产:
|
||
|
||
- 数据库膨胀快。
|
||
- 备份和恢复变慢。
|
||
- 文件下载性能受数据库影响。
|
||
- 文件去重、对象存储生命周期、冷热归档难做。
|
||
|
||
V2 必须坚持:数据库只存文件元数据和对象存储指针,文件正文进入 MinIO/S3。
|
||
|
||
### 3.5 AI 能力和合同业务耦合
|
||
|
||
V1 里模型设置、知识库、法条、AI 质量评测、Prompt 资产等都和合同后端存在较强耦合。
|
||
|
||
V2 已经拆出 `yuqei-ai-platform`,数据库也必须跟着拆清楚:
|
||
|
||
- 合同业务库不直接保存模型 provider、Prompt、RAG 内部 chunk。
|
||
- AI 中台库不保存合同审批流、合同履约状态、合同房间业务 UI 状态。
|
||
- 两边通过 `ai_audit_run_id`、`contract_id`、`trace_id` 做弱关联,而不是跨库强耦合事务。
|
||
|
||
## 4. 当前 V2 数据库现状
|
||
|
||
### 4.1 Contract API 当前表
|
||
|
||
当前 `yuqei-contract-platform/services/contract-api` 已有:
|
||
|
||
- `contracts`
|
||
- `approval_timeline_items`
|
||
- `contract_files`
|
||
- `review_tasks`
|
||
- `contract_audit_events`
|
||
|
||
优点:
|
||
|
||
- 已从内存数据推进到 SQLAlchemy/Alembic。
|
||
- 文件、审查任务、合同审计已经独立成表。
|
||
- 附件内容已走文件存储路径,不再把文件内容直接塞入合同表。
|
||
|
||
不足:
|
||
|
||
- 缺少 `tenant_id`、`organization_id`、`department_id`、`created_by`、`updated_by`、`deleted_at`。
|
||
- `status_label`、`status_tone`、`next_action`、`next_actions` 偏 UI 展示,不应作为核心持久化真相。
|
||
- `owner`、`requested_by`、`actor_name` 仍是字符串,不是正式用户外键或快照字段组合。
|
||
- 索引以单列索引为主,缺少工作台、合同列表、审批待办、文件仓库、审计时间线的组合索引。
|
||
- 审批流还只是 timeline 表,没有正式 `approval_instances`、`approval_steps`、`approval_actions`。
|
||
- 版本、履约、归档仍未形成正式数据库模型。
|
||
|
||
### 4.2 AI Platform 当前表
|
||
|
||
当前 `yuqei-ai-platform/services/ai-platform-api` 已有:
|
||
|
||
- `ai_provider_configs`
|
||
- `prompt_templates`
|
||
- `knowledge_documents`
|
||
- `knowledge_import_batches`
|
||
- `knowledge_chunks`
|
||
- `ai_run_audits`
|
||
- 质量规则、质量标签、改进项、配置变更、worker runtime 等后续表
|
||
|
||
优点:
|
||
|
||
- AI 中台已经形成独立数据库迁移历史。
|
||
- 已有 `knowledge_chunks` 和 pgvector column 起点。
|
||
- AI Trace 已记录 provider、model、token、费用、检索命中、质量标签等方向。
|
||
|
||
不足:
|
||
|
||
- 生产级向量索引还不完整,需要明确 HNSW/IVFFLAT 策略、维度、重建流程。
|
||
- 知识库、AI Trace、质量评测缺少统一 `tenant_id` 与访问边界。
|
||
- `knowledge_documents.content` 和 `knowledge_chunks.content` 均可能增长较快,需要全文索引、分区和冷热策略。
|
||
- `ai_run_audits` 会高频增长,应从一开始设计分区或归档。
|
||
- Prompt、模型配置应有“草稿、发布、回滚、审计、影响范围”完整版本模型。
|
||
|
||
### 4.3 当前基础设施问题
|
||
|
||
当前本地 compose 使用一个 PostgreSQL 数据库 `yuqei_v2` 同时承载 Contract API 和 AI Platform,并通过:
|
||
|
||
- `contract_api_alembic_version`
|
||
- `ai_platform_alembic_version`
|
||
|
||
隔离迁移历史。
|
||
|
||
这适合本地开发,但最终生产建议改为:
|
||
|
||
- `yuqei_contract`:合同业务数据库。
|
||
- `yuqei_ai`:AI 中台数据库。
|
||
- `yuqei_ops`:可选,任务、运行状态、监控、队列补偿等运行数据。
|
||
|
||
这样数据库边界和服务边界一致,避免 AI 数据膨胀拖慢合同主流程。
|
||
|
||
## 5. V2 数据库最终目标
|
||
|
||
### 5.1 总原则
|
||
|
||
1. PostgreSQL 16 作为唯一生产关系数据库。
|
||
2. 合同平台和 AI 中台逻辑拆库,避免数据增长互相拖累。
|
||
3. 所有核心业务表都有 `tenant_id`。
|
||
4. 所有核心表都有 `created_at`、`updated_at`、`created_by`、`updated_by`。
|
||
5. 用户可删除/撤回的数据使用 `deleted_at` 软删除。
|
||
6. 审计和 AI Trace 使用 append-only 模型,不允许物理更新业务含义。
|
||
7. 文件内容不进入数据库,只存对象存储元数据、hash、size、mime、object_key。
|
||
8. 高频时间序列表按月分区。
|
||
9. 搜索字段使用 PostgreSQL FTS,向量字段使用 pgvector。
|
||
10. 数据库迁移必须可以从空库一键执行。
|
||
|
||
### 5.2 生产数据库边界
|
||
|
||
推荐生产结构:
|
||
|
||
```text
|
||
PostgreSQL cluster
|
||
yuqei_contract
|
||
identity schema
|
||
contract schema
|
||
document schema
|
||
audit schema
|
||
|
||
yuqei_ai
|
||
ai schema
|
||
knowledge schema
|
||
prompt schema
|
||
audit schema
|
||
|
||
yuqei_ops
|
||
jobs schema
|
||
runtime schema
|
||
notification schema
|
||
```
|
||
|
||
本地开发可以继续用一个数据库,但 schema 和环境变量要与生产边界保持一致。不要让本地便利破坏最终模型。
|
||
|
||
## 6. Contract Platform 目标模型
|
||
|
||
### 6.1 identity schema
|
||
|
||
用于企业、部门、用户、角色。
|
||
|
||
核心表:
|
||
|
||
- `tenants`
|
||
- `organizations`
|
||
- `departments`
|
||
- `users`
|
||
- `tenant_memberships`
|
||
- `roles`
|
||
- `role_bindings`
|
||
- `permission_policies`
|
||
|
||
关键字段:
|
||
|
||
```text
|
||
tenant_id
|
||
organization_id
|
||
department_id
|
||
user_id
|
||
role_code
|
||
status
|
||
created_at
|
||
updated_at
|
||
deleted_at
|
||
```
|
||
|
||
关键约束:
|
||
|
||
- `users`: `(tenant_id, email)` unique
|
||
- `departments`: `(tenant_id, parent_department_id, name)` index
|
||
- `role_bindings`: `(tenant_id, user_id, role_code, scope_type, scope_id)` unique
|
||
|
||
### 6.2 contract schema
|
||
|
||
用于合同主数据和合同房间。
|
||
|
||
核心表:
|
||
|
||
- `contracts`
|
||
- `contract_parties`
|
||
- `contract_versions`
|
||
- `contract_sections`
|
||
- `contract_comments`
|
||
- `contract_status_events`
|
||
- `contract_obligations`
|
||
- `contract_reminders`
|
||
|
||
`contracts` 不应保存 UI 展示字段。目标字段:
|
||
|
||
```text
|
||
id
|
||
tenant_id
|
||
contract_no
|
||
title
|
||
contract_type
|
||
counterparty_name
|
||
amount_cents
|
||
currency
|
||
lifecycle_status
|
||
review_status
|
||
approval_status
|
||
signing_status
|
||
archive_status
|
||
owner_user_id
|
||
department_id
|
||
current_version_id
|
||
effective_at
|
||
expires_at
|
||
created_by
|
||
updated_by
|
||
created_at
|
||
updated_at
|
||
deleted_at
|
||
```
|
||
|
||
说明:
|
||
|
||
- `status_label`、`status_tone` 在前端或 API serializer 层生成。
|
||
- `next_action` 由后端根据状态、角色、任务计算,不作为长期真相字段。
|
||
- 合同正文进入 `contract_versions`,不要直接塞在 `contracts` 主表。
|
||
|
||
`contract_versions` 目标字段:
|
||
|
||
```text
|
||
id
|
||
tenant_id
|
||
contract_id
|
||
version_no
|
||
source_type
|
||
body_text
|
||
body_hash
|
||
created_by
|
||
created_at
|
||
change_summary
|
||
ai_generated_run_id
|
||
```
|
||
|
||
### 6.3 approval schema
|
||
|
||
审批流独立建模,不再只靠 timeline。
|
||
|
||
核心表:
|
||
|
||
- `approval_instances`
|
||
- `approval_steps`
|
||
- `approval_actions`
|
||
- `approval_delegations`
|
||
|
||
关键查询:
|
||
|
||
- 待我审批
|
||
- 退回后重提
|
||
- 审批时间线
|
||
- 审批人处理统计
|
||
|
||
关键索引:
|
||
|
||
```sql
|
||
CREATE INDEX idx_approval_steps_tenant_assignee_status_created
|
||
ON approval_steps (tenant_id, assignee_user_id, status, created_at DESC);
|
||
|
||
CREATE INDEX idx_approval_instances_tenant_contract_status
|
||
ON approval_instances (tenant_id, contract_id, status);
|
||
```
|
||
|
||
### 6.4 document schema
|
||
|
||
文件仓库只存元数据,不存文件正文。
|
||
|
||
核心表:
|
||
|
||
- `files`
|
||
- `file_links`
|
||
- `file_versions`
|
||
- `file_text_extractions`
|
||
- `file_processing_jobs`
|
||
|
||
`files` 目标字段:
|
||
|
||
```text
|
||
id
|
||
tenant_id
|
||
bucket
|
||
object_key
|
||
file_name
|
||
mime_type
|
||
file_size
|
||
sha256
|
||
file_type
|
||
storage_status
|
||
encryption_status
|
||
uploaded_by
|
||
uploaded_at
|
||
deleted_at
|
||
```
|
||
|
||
关键索引:
|
||
|
||
```sql
|
||
CREATE INDEX idx_files_tenant_type_uploaded
|
||
ON files (tenant_id, file_type, uploaded_at DESC)
|
||
WHERE deleted_at IS NULL;
|
||
|
||
CREATE INDEX idx_files_tenant_sha256
|
||
ON files (tenant_id, sha256)
|
||
WHERE deleted_at IS NULL;
|
||
|
||
CREATE INDEX idx_file_links_tenant_resource
|
||
ON file_links (tenant_id, resource_type, resource_id, created_at DESC);
|
||
```
|
||
|
||
### 6.5 audit schema
|
||
|
||
合同侧审计必须 append-only。
|
||
|
||
核心表:
|
||
|
||
- `audit_events`
|
||
- `audit_event_hash_chain`
|
||
|
||
`audit_events` 目标字段:
|
||
|
||
```text
|
||
id
|
||
tenant_id
|
||
actor_user_id
|
||
actor_display_name
|
||
event_type
|
||
resource_type
|
||
resource_id
|
||
summary
|
||
metadata_jsonb
|
||
request_id
|
||
trace_id
|
||
ip_address
|
||
user_agent
|
||
created_at
|
||
```
|
||
|
||
关键策略:
|
||
|
||
- 禁止业务代码 update/delete 审计事件。
|
||
- 高频审计按月分区。
|
||
- 可选 hash chain:每条审计记录保存 previous_hash 与 event_hash。
|
||
|
||
## 7. AI Platform 目标模型
|
||
|
||
### 7.1 ai schema
|
||
|
||
核心表:
|
||
|
||
- `ai_provider_configs`
|
||
- `ai_provider_config_versions`
|
||
- `ai_model_routes`
|
||
- `ai_runs`
|
||
- `ai_run_steps`
|
||
- `ai_run_costs`
|
||
- `ai_quality_labels`
|
||
- `ai_quality_improvement_actions`
|
||
|
||
`ai_runs` 目标字段:
|
||
|
||
```text
|
||
id
|
||
tenant_id
|
||
operation
|
||
caller_service
|
||
caller_user_id
|
||
request_id
|
||
trace_id
|
||
provider
|
||
model
|
||
prompt_template_id
|
||
prompt_template_version
|
||
retrieval_trace_id
|
||
status
|
||
latency_ms
|
||
prompt_tokens
|
||
completion_tokens
|
||
total_tokens
|
||
estimated_cost
|
||
quality_status
|
||
quality_score
|
||
created_at
|
||
```
|
||
|
||
关键索引:
|
||
|
||
```sql
|
||
CREATE INDEX idx_ai_runs_tenant_operation_created
|
||
ON ai_runs (tenant_id, operation, created_at DESC);
|
||
|
||
CREATE INDEX idx_ai_runs_tenant_status_created
|
||
ON ai_runs (tenant_id, status, created_at DESC);
|
||
|
||
CREATE UNIQUE INDEX ux_ai_runs_request_id
|
||
ON ai_runs (request_id)
|
||
WHERE request_id IS NOT NULL;
|
||
```
|
||
|
||
### 7.2 prompt schema
|
||
|
||
核心表:
|
||
|
||
- `prompt_templates`
|
||
- `prompt_template_versions`
|
||
- `prompt_releases`
|
||
- `prompt_change_audits`
|
||
|
||
原则:
|
||
|
||
- Prompt 内容按版本不可变。
|
||
- 发布记录独立于草稿记录。
|
||
- AI Run 必须记录命中的 prompt name + version。
|
||
|
||
### 7.3 knowledge schema
|
||
|
||
核心表:
|
||
|
||
- `knowledge_bases`
|
||
- `knowledge_documents`
|
||
- `knowledge_document_versions`
|
||
- `knowledge_import_batches`
|
||
- `knowledge_chunks`
|
||
- `knowledge_chunk_embeddings`
|
||
- `retrieval_traces`
|
||
- `retrieval_trace_hits`
|
||
|
||
推荐把 embedding 从 `knowledge_chunks` 中拆出:
|
||
|
||
```text
|
||
knowledge_chunks
|
||
id
|
||
tenant_id
|
||
knowledge_base_id
|
||
document_id
|
||
chunk_index
|
||
locator
|
||
content
|
||
content_hash
|
||
token_count
|
||
index_status
|
||
created_at
|
||
|
||
knowledge_chunk_embeddings
|
||
id
|
||
tenant_id
|
||
chunk_id
|
||
provider
|
||
model
|
||
dimension
|
||
embedding vector
|
||
created_at
|
||
```
|
||
|
||
这样同一个 chunk 可以支持多套 embedding 模型并行评测,避免未来换模型时覆盖历史向量。
|
||
|
||
关键索引:
|
||
|
||
```sql
|
||
CREATE INDEX idx_knowledge_chunks_tenant_kb_status
|
||
ON knowledge_chunks (tenant_id, knowledge_base_id, index_status, created_at DESC);
|
||
|
||
CREATE INDEX idx_knowledge_chunks_fts
|
||
ON knowledge_chunks USING GIN (to_tsvector('simple', content));
|
||
|
||
CREATE INDEX idx_chunk_embeddings_vector_hnsw
|
||
ON knowledge_chunk_embeddings
|
||
USING hnsw (embedding vector_cosine_ops);
|
||
```
|
||
|
||
说明:
|
||
|
||
- `simple` 分词只是基础方案;中文法规检索后续可评估 `zhparser`、外部搜索服务或应用层分词字段。
|
||
- pgvector 索引必须按实际维度和数据量压测后决定 HNSW 参数。
|
||
|
||
## 8. 统一索引策略
|
||
|
||
### 8.1 合同工作台
|
||
|
||
```sql
|
||
CREATE INDEX idx_contracts_tenant_owner_updated
|
||
ON contracts (tenant_id, owner_user_id, updated_at DESC)
|
||
WHERE deleted_at IS NULL;
|
||
|
||
CREATE INDEX idx_contracts_tenant_lifecycle_updated
|
||
ON contracts (tenant_id, lifecycle_status, updated_at DESC)
|
||
WHERE deleted_at IS NULL;
|
||
```
|
||
|
||
### 8.2 待办任务
|
||
|
||
建议独立 `work_items` 表,不要每个页面临时拼任务。
|
||
|
||
```sql
|
||
CREATE INDEX idx_work_items_tenant_assignee_status_due
|
||
ON work_items (tenant_id, assignee_user_id, status, due_at ASC, updated_at DESC)
|
||
WHERE deleted_at IS NULL;
|
||
```
|
||
|
||
### 8.3 统一搜索
|
||
|
||
合同、文件、审查任务先用 PostgreSQL FTS:
|
||
|
||
```sql
|
||
CREATE INDEX idx_contract_search_fts
|
||
ON contract_search_documents USING GIN (search_vector);
|
||
|
||
CREATE INDEX idx_contract_search_tenant_type_updated
|
||
ON contract_search_documents (tenant_id, resource_type, updated_at DESC);
|
||
```
|
||
|
||
建议建立 `contract_search_documents` 物化表或同步表,而不是每次跨 contracts/files/review_tasks 临时 OR 查询。
|
||
|
||
### 8.4 审计时间线
|
||
|
||
```sql
|
||
CREATE INDEX idx_audit_tenant_resource_created
|
||
ON audit_events (tenant_id, resource_type, resource_id, created_at DESC);
|
||
|
||
CREATE INDEX idx_audit_tenant_actor_created
|
||
ON audit_events (tenant_id, actor_user_id, created_at DESC);
|
||
```
|
||
|
||
按月分区后,每个分区保留同类本地索引。
|
||
|
||
## 9. 分区与归档策略
|
||
|
||
从 V2 正式基线开始,以下表建议设计分区能力:
|
||
|
||
- `audit_events`:按月分区。
|
||
- `ai_runs`:按月分区。
|
||
- `ai_run_steps`:按月分区。
|
||
- `retrieval_traces`:按月分区。
|
||
- `file_processing_jobs`:按月分区。
|
||
- `worker_job_executions`:按月分区。
|
||
|
||
归档策略:
|
||
|
||
- 热数据:最近 3 到 6 个月,在线查询。
|
||
- 温数据:6 到 24 个月,保留数据库分区但降频查询。
|
||
- 冷数据:超过 24 个月,导出到对象存储归档,保留索引摘要。
|
||
|
||
## 10. 安全策略
|
||
|
||
### 10.1 租户隔离
|
||
|
||
所有核心业务表必须有 `tenant_id NOT NULL`。
|
||
|
||
应用层:
|
||
|
||
- 所有 repository 方法必须显式接收 tenant context。
|
||
- 禁止无 tenant context 的业务查询。
|
||
|
||
数据库层:
|
||
|
||
- PostgreSQL 生产环境建议启用 RLS。
|
||
- 每个请求连接事务内设置 `app.current_tenant_id`。
|
||
|
||
示例:
|
||
|
||
```sql
|
||
ALTER TABLE contracts ENABLE ROW LEVEL SECURITY;
|
||
|
||
CREATE POLICY tenant_isolation_contracts
|
||
ON contracts
|
||
USING (tenant_id = current_setting('app.current_tenant_id')::text);
|
||
```
|
||
|
||
### 10.2 权限与最小账号
|
||
|
||
推荐数据库账号:
|
||
|
||
- `contract_app_rw`:合同服务读写合同库。
|
||
- `contract_app_ro`:合同服务只读报表。
|
||
- `ai_app_rw`:AI 中台读写 AI 库。
|
||
- `migration_owner`:只用于 Alembic migration。
|
||
- `backup_user`:只用于备份。
|
||
|
||
应用运行账号不应拥有 DDL 权限。
|
||
|
||
### 10.3 敏感数据
|
||
|
||
敏感字段:
|
||
|
||
- 用户手机号、邮箱。
|
||
- 合同正文。
|
||
- 文件名和对象 key。
|
||
- AI prompt 输入和回答。
|
||
- 模型 API key。
|
||
|
||
要求:
|
||
|
||
- API key 只保存密文或引用 secret manager key。
|
||
- AI Trace 对管理员展示时支持脱敏。
|
||
- 合同正文导出和下载进入审计。
|
||
- 数据库备份加密。
|
||
|
||
## 11. 备份与恢复
|
||
|
||
生产基线:
|
||
|
||
- 每日全量备份。
|
||
- WAL 连续归档。
|
||
- MinIO/S3 对象存储独立备份。
|
||
- 每周恢复演练。
|
||
- 备份加密和异地保存。
|
||
|
||
验收目标:
|
||
|
||
- RPO:不超过 15 分钟。
|
||
- RTO:核心合同查询 2 小时内恢复。
|
||
- 单租户误删:可按 tenant + 时间点恢复。
|
||
|
||
## 12. 当前 V2 到最终数据库的实施路径
|
||
|
||
因为当前项目未正式上线,推荐采用干净基线,而不是从当前 MVP 迁移一直叠加。
|
||
|
||
### DB-0:数据库现状冻结
|
||
|
||
产出:
|
||
|
||
- 当前 V2 migration 清单。
|
||
- 当前 V1 有价值数据清单。
|
||
- 最终需要迁移的数据类型。
|
||
|
||
建议迁移的数据:
|
||
|
||
- 法规、法条、制度、模板、标准条款。
|
||
- 有价值的 Prompt、审查规则、知识库文档。
|
||
- demo 用户和 demo 合同可重建,不建议从 V1 迁移历史业务数据。
|
||
|
||
### DB-1:建立最终数据库工程
|
||
|
||
动作:
|
||
|
||
- `yuqei_contract` 和 `yuqei_ai` 使用独立 DATABASE_URL。
|
||
- Contract API 和 AI Platform 使用独立 Alembic 目录和迁移。
|
||
- compose 改为两个数据库或两个 Postgres database。
|
||
- 生产禁用 auto create,只能 Alembic migration。
|
||
|
||
### DB-2:Contract Platform 干净基线
|
||
|
||
动作:
|
||
|
||
- 重建合同侧 `0001_v2_contract_baseline`。
|
||
- 引入 `tenants/users/roles` 基础模型。
|
||
- 重构 `contracts`,移除 UI 展示字段。
|
||
- 增加 `contract_versions`、`work_items`、`approval_instances/steps/actions`。
|
||
- 增加 `files/file_links/file_text_extractions`。
|
||
- 增加 `audit_events` append-only。
|
||
|
||
### DB-3:AI Platform 干净基线
|
||
|
||
动作:
|
||
|
||
- 重建 AI 侧 `0001_v2_ai_baseline`。
|
||
- 增加 provider config version/release。
|
||
- 增加 prompt version/release。
|
||
- 拆分 knowledge chunk 和 embedding。
|
||
- 增加 retrieval trace/hits。
|
||
- 增加 ai_runs/ai_run_steps/quality_labels。
|
||
- 明确 pgvector extension 和 HNSW 索引。
|
||
|
||
### DB-4:Repository 全面 tenant 化
|
||
|
||
动作:
|
||
|
||
- 所有 repository 方法增加 tenant context。
|
||
- 所有 API 入口解析 current tenant。
|
||
- 所有测试补 tenant 隔离场景。
|
||
- 禁止直接用字符串 actor_name 作为业务主体,保留 actor_display_name 作为快照字段。
|
||
|
||
### DB-5:搜索与性能基线
|
||
|
||
动作:
|
||
|
||
- 建立 `contract_search_documents`。
|
||
- 建立 FTS 索引。
|
||
- 建立核心组合索引。
|
||
- 为工作台、合同列表、审批待办、文件仓库、统一搜索写 EXPLAIN 验证。
|
||
|
||
### DB-6:安全与备份基线
|
||
|
||
动作:
|
||
|
||
- RLS 策略。
|
||
- 只读/读写/migration 数据库账号。
|
||
- 备份脚本。
|
||
- 恢复演练脚本。
|
||
- 审计表 append-only 限制。
|
||
|
||
## 13. 性能验收指标
|
||
|
||
开发阶段建议使用以下目标压测:
|
||
|
||
| 场景 | 数据量 | 目标 |
|
||
|---|---:|---:|
|
||
| 工作台待办 | 100 万 work_items | P95 < 150ms |
|
||
| 合同列表 | 100 万 contracts | P95 < 200ms |
|
||
| 合同详情 | 10 万 contracts + versions | P95 < 250ms |
|
||
| 审批待办 | 100 万 approval_steps | P95 < 150ms |
|
||
| 文件仓库 | 100 万 files | P95 < 200ms |
|
||
| 审计时间线 | 1000 万 audit_events | P95 < 300ms |
|
||
| 统一搜索 FTS | 100 万 search docs | P95 < 500ms |
|
||
| 知识库向量检索 | 100 万 chunks | P95 < 800ms |
|
||
| AI Trace 列表 | 1000 万 ai_runs | P95 < 300ms |
|
||
|
||
## 14. 当前最优下一步
|
||
|
||
下一轮建议直接进入 DB-0 / DB-1:
|
||
|
||
1. 新增 `yuqei-infra/compose` 中的双数据库配置:
|
||
- `yuqei_contract`
|
||
- `yuqei_ai`
|
||
2. 新增 Contract API 最终 baseline migration 草案。
|
||
3. 新增 AI Platform 最终 baseline migration 草案。
|
||
4. 把当前 MVP migration 标记为重构前临时迁移,不继续作为最终生产基线。
|
||
5. 写 Postgres 集成测试策略,减少对 SQLite 的关键路径依赖。
|
||
|
||
这一步完成后,V2 后面的合同主流程、AI 中台、知识库、审计和权限都会建立在干净数据库地基上,不会把 V1 的历史包袱带进新版本。
|