Files
AIclinicalresearch/docs/00-系统总体设计/05-Schema隔离方案与成本分析.md
HaHafeng 2e8699c217 feat(asl): Week 2 Day 2 - Excel import with template download and intelligent dedup
Features:
- feat: Excel template generation and download (with examples)
- feat: Excel file parsing in memory (cloud-native, no disk write)
- feat: Field validation (title + abstract required)
- feat: Smart deduplication (DOI priority + Title fallback)
- feat: Literature preview table with statistics
- feat: Complete submission flow (create project + import literatures)

Components:
- feat: Create excelUtils.ts with full Excel processing toolkit
- feat: Enhance TitleScreeningSettings page with upload/preview/submit
- feat: Update API interface signatures and export unified aslApi object

Dependencies:
- chore: Add xlsx library for Excel file processing

Ref: Week 2 Frontend Development - Day 2
Scope: ASL Module MVP - Title Abstract Screening
Cloud-Native: Memory parsing, no file persistence
2025-11-19 10:24:47 +08:00

23 KiB
Raw Blame History

Schema隔离方案与成本分析

文档版本: v1.0
创建日期: 2025-11-06
最后更新: 2025-11-06
文档状态: 架构分析
作者: 技术架构师


📋 核心问题

  1. 什么是真正的Schema隔离
  2. 从逻辑隔离到物理隔离的改造成本有多高?
  3. 现在做Schema隔离的成本高吗
  4. 当前业务体量下,需要现在做吗?
  5. 最佳实施时机是什么时候?

🎯 两种隔离方式对比

逻辑隔离(当前方案)

定义:

  • 所有表都在同一个Schemapublic)中
  • 通过表名前缀来区分不同模块
  • 代码层面按模块组织

示例:

-- 所有表都在public schema
public.users                    -- 用户表
public.aia_projects             -- AI问答模块的项目表
public.aia_conversations        -- AI问答模块的对话表
public.asl_projects             -- AI文献模块的项目表
public.asl_literature_items     -- AI文献模块的文献表
public.pkb_knowledge_bases      -- 知识库模块的知识库表
public.dc_projects              -- 数据清洗模块的项目表
public.review_tasks             -- 稿件审查模块的任务表

Prisma Schema示例

// 逻辑隔离:使用@@map指定表名
model AiaProject {
  id        String @id @default(uuid())
  userId    String @map("user_id")
  name      String
  // ...
  
  @@map("aia_projects")     // 表名前缀标识模块
}

model AslProject {
  id        String @id @default(uuid())
  userId    String @map("user_id")
  name      String
  // ...
  
  @@map("asl_projects")     // 表名前缀标识模块
}

查询方式:

// 业务代码无感知
const project = await prisma.aiaProject.findUnique({
  where: { id: projectId }
});

物理隔离真正的Schema隔离

定义:

  • 为每个模块创建独立的PostgreSQL Schema
  • 表分散在不同的Schema中
  • 数据库层面真正隔离

示例:

-- 创建独立Schema
CREATE SCHEMA platform_schema;      -- 平台层
CREATE SCHEMA aia_schema;           -- AI问答模块
CREATE SCHEMA asl_schema;           -- AI文献模块
CREATE SCHEMA pkb_schema;           -- 知识库模块
CREATE SCHEMA dc_schema;            -- 数据清洗模块
CREATE SCHEMA review_schema;        -- 稿件审查模块
CREATE SCHEMA admin_schema;         -- 运营管理端

-- 表分布在不同Schema
platform_schema.users               -- 用户表
aia_schema.projects                 -- AI问答的项目表
aia_schema.conversations            -- AI问答的对话表
asl_schema.projects                 -- AI文献的项目表
asl_schema.literature_items         -- AI文献的文献表
pkb_schema.knowledge_bases          -- 知识库表
dc_schema.projects                  -- 数据清洗的项目表
review_schema.tasks                 -- 稿件审查的任务表

Prisma Schema示例

// 物理隔离指定schema
model AiaProject {
  id        String @id @default(uuid())
  userId    String @map("user_id")
  name      String
  // ...
  
  @@map("projects")              // 表名不需要前缀
  @@schema("aia_schema")         // 指定Schema ⭐
}

model AslProject {
  id        String @id @default(uuid())
  userId    String @map("user_id")
  name      String
  // ...
  
  @@map("projects")              // 表名不需要前缀
  @@schema("asl_schema")         // 指定Schema ⭐
}

查询方式:

// 业务代码无感知Prisma会自动处理
const project = await prisma.aiaProject.findUnique({
  where: { id: projectId }
});

// 实际执行的SQL
// SELECT * FROM aia_schema.projects WHERE id = $1

📊 两种方案对比

维度 逻辑隔离(当前) 物理隔离(目标)
复杂度 简单 中等
实施难度 中等
维护成本 中等
隔离性 中等(代码层面) 高(数据库层面)
权限控制 中等 高(数据库级别)
微服务拆分 需要改造 易于拆分
模块独立部署 困难 容易
跨模块查询 容易同一Schema 需要跨Schema查询
备份恢复 整体备份 可按Schema备份
当前适用性 适合当前阶段 ⚠️ 未来阶段

💰 改造成本分析

从逻辑隔离到物理隔离需要改什么?

1. 数据库层面改造

步骤1创建Schema

-- 创建新Schema非常简单
CREATE SCHEMA platform_schema;
CREATE SCHEMA aia_schema;
CREATE SCHEMA asl_schema;
CREATE SCHEMA pkb_schema;
CREATE SCHEMA dc_schema;
CREATE SCHEMA review_schema;
CREATE SCHEMA admin_schema;

工作量: 10分钟


步骤2创建新表结构

-- 在新Schema中创建表
CREATE TABLE aia_schema.projects (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID NOT NULL,
  name VARCHAR(255) NOT NULL,
  -- ... 其他字段
  
  -- 外键可能需要跨Schema
  CONSTRAINT fk_user FOREIGN KEY (user_id) 
    REFERENCES platform_schema.users(id)
);

-- 类似的,为所有表创建新的表结构

工作量:

  • 自动生成Prisma Migrate1-2小时
  • 手动创建1天

步骤3数据迁移

-- 迁移数据从public到新Schema
INSERT INTO aia_schema.projects 
SELECT * FROM public.aia_projects;

INSERT INTO aia_schema.conversations 
SELECT * FROM public.aia_conversations;

-- ... 为所有表迁移数据

工作量:

  • 数据量小(<100万行1-2小时
  • 数据量大(>100万行半天到1天

风险:

  • ⚠️ 需要停机(或做在线迁移)
  • ⚠️ 需要验证数据完整性
  • ⚠️ 外键约束可能有问题

步骤4清理旧表

-- 删除旧表(确认无误后)
DROP TABLE public.aia_projects;
DROP TABLE public.aia_conversations;
-- ...

工作量: 1小时

总计数据库改造工作量:

  • 自动化半天到1天
  • 手动2-3天

2. 代码层面改造

步骤1修改Prisma Schema

逻辑隔离(当前):

model AiaProject {
  id        String @id @default(uuid())
  userId    String @map("user_id")
  name      String
  
  @@map("aia_projects")     // 需要前缀
}

物理隔离(修改后):

model AiaProject {
  id        String @id @default(uuid())
  userId    String @map("user_id")
  name      String
  
  @@map("projects")         // 不需要前缀
  @@schema("aia_schema")    // 新增指定Schema ⭐
}

工作量:

  • 修改所有Model约50-100个Model
  • 时间2-4小时

步骤2处理外键和关联

问题跨Schema的外键

// User在platform_schema
model User {
  id        String @id @default(uuid())
  email     String
  
  @@map("users")
  @@schema("platform_schema")
}

// Project在aia_schema
model AiaProject {
  id        String @id @default(uuid())
  userId    String @map("user_id")
  
  // 跨Schema关联 ⚠️
  user      User   @relation(fields: [userId], references: [id])
  
  @@map("projects")
  @@schema("aia_schema")
}

Prisma处理

  • Prisma会自动处理跨Schema的关联
  • 生成的SQL会包含正确的Schema前缀
  • ⚠️ 但需要测试验证

工作量:

  • 测试所有跨Schema关联半天到1天

步骤3业务代码改造

好消息:业务代码几乎不需要改!

// 业务代码完全不变
const project = await prisma.aiaProject.findUnique({
  where: { id: projectId },
  include: {
    user: true  // 跨Schema关联Prisma自动处理
  }
});

原因:

  • Prisma ORM抽象了底层Schema
  • 业务代码只依赖Prisma Model不直接写SQL

例外情况:

  • ⚠️ 如果有原始SQL查询prisma.$queryRaw),需要修改
  • ⚠️ 如果有数据库视图View需要修改
  • ⚠️ 如果有数据库函数Function需要修改

工作量:

  • 业务代码0改造如果没有原始SQL
  • 原始SQL需要逐个修改估计10-20处
  • 时间半天到1天

步骤4运行Prisma Migrate

# 生成新的迁移文件
npx prisma migrate dev --name schema-isolation

# 或手动迁移(生产环境)
npx prisma migrate deploy

工作量:

  • 开发环境10分钟
  • 生产环境:需要详细计划(半天准备)

3. 测试验证

需要测试的内容:

  1. 所有API接口是否正常
  2. 跨Schema关联是否正常用户-项目、项目-对话等)
  3. 外键约束是否正常
  4. 数据完整性检查
  5. 性能测试跨Schema查询性能
  6. 备份恢复测试

工作量:

  • 单元测试自动化1-2小时
  • 集成测试:半天
  • 端到端测试1天
  • 总计1-2天

总改造成本

开发环境:

任务 工作量
数据库Schema创建 10分钟
Prisma Schema修改 2-4小时
数据迁移脚本 1-2小时
原始SQL修改 半天
测试验证 1-2天
总计 2-3天

生产环境:

任务 工作量
迁移方案设计 半天
数据备份 1小时
数据迁移 半天到1天
验证测试 1天
应急回滚方案 半天
总计 3-4天

风险评估:

  • ⚠️ 风险等级:中等
  • ⚠️ 主要风险:数据迁移失败、外键约束问题、停机时间
  • 缓解措施:详细测试、回滚方案、灰度发布

🤔 现在做 vs 以后做

现在做的优势

  1. 数据量小,迁移快

    当前数据量(估算):
    - 用户:< 100
    - 项目:< 500
    - 对话:< 5,000
    - 文档:< 1,000
    
    迁移时间:< 1小时
    风险:低
    
  2. 业务复杂度低,测试简单

    当前模块:
    - ✅ AIAAI问答- 已完成
    - ✅ PKB知识库- 已完成
    - ⏳ ASLAI文献- 未开发
    - ⏳ DC、SSA、ST、RVW - 未开发
    
    需要测试的模块少
    
  3. 为未来打下坚实基础

    优势:
    - ✅ 新模块ASL、DC等直接用物理隔离
    - ✅ 避免未来大规模改造
    - ✅ 支持模块独立部署
    - ✅ 支持微服务拆分
    
  4. 团队学习成本低

    当前团队小:
    - 开发人员少,沟通成本低
    - 代码改动影响范围小
    - 易于推广新规范
    

现在做的劣势

  1. 需要投入时间

    开发时间2-3天开发环境
    生产时间3-4天生产环境
    总计1周
    
    延迟ASL模块开发延迟1周
    
  2. 有一定风险

    风险:
    - 数据迁移失败
    - 外键约束问题
    - 需要停机
    
    缓解:详细测试、回滚方案
    
  3. 当前没有紧迫需求

    事实:
    - 没有微服务拆分需求
    - 没有模块独立部署需求
    - 没有私有化部署需求
    
    结论:暂时不需要物理隔离
    

以后做的优势

  1. 延迟投入

    当前专注业务开发ASL、DC等
    未来:有需求时再改造
    
    优势:快速推进业务
    
  2. 需求明确时再做

    触发条件:
    - 需要微服务拆分
    - 需要模块独立部署
    - 需要私有化部署
    - 数据量大,需要性能优化
    
    此时改造目标明确
    

以后做的劣势

  1. 数据量大,迁移慢

    未来数据量(估算):
    - 用户10,000+
    - 项目100,000+
    - 对话1,000,000+
    - 文档500,000+
    
    迁移时间:数小时到数天
    风险:高
    
  2. 业务复杂,测试困难

    未来模块:
    - ✅ 7个模块全部上线
    - ✅ 复杂的跨模块关联
    - ✅ 大量用户在使用
    
    测试难度:高
    回滚成本:高
    
  3. 需要停机或在线迁移

    停机迁移:
    - 影响用户体验
    - 可能丢失订单
    
    在线迁移:
    - 技术复杂度高
    - 需要双写、数据同步
    
  4. 改造成本成倍增长

    未来改造成本(估算):
    - 开发时间1-2周
    - 测试时间1-2周
    - 生产迁移1周
    - 总计3-5周
    
    现在的3-5倍
    

🎯 建议与决策

方案A现在做物理隔离 强烈推荐

适用场景:

  • 有1周时间投入
  • 重视长期架构健康
  • 未来有模块独立部署需求
  • 未来有私有化部署需求

理由:

  1. 成本最低:数据量小,迁移快(< 1小时
  2. 风险最低:业务简单,测试容易
  3. 收益最大为未来7个模块打下坚实基础
  4. 避免技术债:避免未来大规模改造

实施计划:

Week 1Schema隔离改造2-3天
  Day 1-2开发环境改造
    - 创建Schema
    - 修改Prisma Schema
    - 数据迁移脚本
    - 测试验证
    
  Day 3生产环境迁移
    - 备份数据
    - 执行迁移
    - 验证测试
    - 监控
    
Week 2继续ASL模块开发

投入产出比: 极高


方案B暂不做物理隔离继续逻辑隔离 可接受

适用场景:

  • 时间紧迫必须尽快上线ASL模块
  • 近期6个月内没有微服务拆分需求
  • 近期没有私有化部署需求

理由:

  1. 快速推进业务专注ASL模块开发
  2. 逻辑隔离足够用:当前阶段可以满足需求
  3. 延迟改造:未来有需求时再做

但需要遵守纪律: ⚠️ 非常重要

严格使用表名前缀:
- aia_*
- asl_*
- pkb_*
- dc_*
- review_*
- admin_*

为未来改造打基础

触发改造的条件:

  1. 需要微服务拆分
  2. 需要模块独立部署
  3. 需要私有化部署
  4. 数据量超过100万行

投入产出比: 中等


方案C混合方案折中 推荐

方案描述:

  1. 新模块使用物理隔离

    • ASL、DC、SSA、ST、RVW等新模块直接用物理隔离
    • 从一开始就创建独立Schema
  2. 老模块暂时保持逻辑隔离

    • AIA、PKB等已完成模块暂时不动
    • 等未来有需求时再迁移

实施计划:

立即:
1. 创建新Schema
   CREATE SCHEMA asl_schema;
   CREATE SCHEMA dc_schema;
   CREATE SCHEMA admin_schema;
   ...

2. ASL模块使用物理隔离
   model AslProject {
     @@schema("asl_schema")
   }

未来6-12个月后
3. 迁移老模块
   - 迁移AIA到aia_schema
   - 迁移PKB到pkb_schema

优势:

  • 立即投入小只需创建Schema10分钟
  • 新模块享受物理隔离的好处
  • 老模块延迟改造,风险低

投入产出比:


📊 决策矩阵

方案 立即投入 未来成本 风险 灵活性 推荐度
方案A现在做物理隔离 1周
方案B继续逻辑隔离
方案C混合方案 10分钟

🎯 最终建议

我的推荐方案A现在做物理隔离

理由:

1. 成本最低的时间窗口

当前:
- 数据量小(< 1万行
- 迁移时间:< 1小时
- 测试时间1-2天
- 总成本1周

6个月后
- 数据量大100万行+
- 迁移时间:数小时到数天
- 测试时间1-2周
- 总成本3-5周

成本差异3-5倍

2. 最佳学习机会

当前:
- 团队小,沟通成本低
- 模块少,改造范围小
- 易于建立规范

未来:
- 团队大,沟通成本高
- 模块多,改造范围大
- 难以统一规范

3. 避免技术债累积

技术债的特点:
- 时间越久,利息越高
- 改造成本成倍增长
- 影响业务创新

现在改造:
- 一次性还清
- 轻装上阵

4. 为未来打下坚实基础

未来需求6-12个月
- 审稿系统独立部署
- AI文献模块独立销售
- 私有化部署
- 微服务拆分

物理隔离是基础设施

如果必须选择方案B或C

推荐方案C混合方案

最低要求:

  1. 立即创建所有Schema10分钟
  2. 新模块ASL、DC等使用物理隔离
  3. 老模块AIA、PKB延迟迁移

触发老模块迁移的条件:

  • 数据量超过50万行
  • 需要模块独立部署
  • 需要私有化部署

📋 实施清单方案A

准备阶段1天

  • 详细阅读Prisma Schema文档
  • 设计Schema结构platform_schema, aia_schema, asl_schema等
  • 编写数据迁移脚本
  • 准备测试用例
  • 准备回滚方案

开发环境改造1-2天

  • 创建所有Schema
  • 修改Prisma Schema所有Model
  • 运行Prisma Migrate
  • 迁移开发环境数据
  • 运行单元测试
  • 运行集成测试
  • 手动测试所有功能

生产环境迁移1天

  • 备份数据库
  • 创建Schema
  • 执行数据迁移
  • 验证数据完整性
  • 启动应用
  • 监控错误日志
  • 性能测试

验收标准

  • 所有API接口正常
  • 跨Schema关联正常
  • 外键约束正常
  • 数据完整性正常
  • 性能无明显下降
  • 无错误日志

💡 技术细节:如何实现物理隔离

Step 1: 创建Schema

-- 创建所有Schema
CREATE SCHEMA platform_schema;
CREATE SCHEMA aia_schema;
CREATE SCHEMA asl_schema;
CREATE SCHEMA pkb_schema;
CREATE SCHEMA dc_schema;
CREATE SCHEMA ssa_schema;
CREATE SCHEMA st_schema;
CREATE SCHEMA review_schema;
CREATE SCHEMA admin_schema;

Step 2: 修改Prisma Schema

修改database配置

generator client {
  provider        = "prisma-client-js"
  previewFeatures = ["multiSchema"]  // 启用多Schema支持 ⭐
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
  schemas  = [
    "platform_schema",
    "aia_schema",
    "asl_schema",
    "pkb_schema",
    "dc_schema",
    "ssa_schema",
    "st_schema",
    "review_schema",
    "admin_schema"
  ]  // 声明所有Schema ⭐
}

修改Model

// 平台层
model User {
  id        String @id @default(uuid())
  email     String @unique
  password  String
  // ...
  
  @@map("users")
  @@schema("platform_schema")  // 指定Schema ⭐
}

// AI问答模块
model AiaProject {
  id        String @id @default(uuid())
  userId    String @map("user_id")
  name      String
  
  user      User   @relation(fields: [userId], references: [id])
  
  @@map("projects")            // 不需要前缀
  @@schema("aia_schema")       // 指定Schema ⭐
}

// AI文献模块
model AslProject {
  id        String @id @default(uuid())
  userId    String @map("user_id")
  name      String
  
  user      User   @relation(fields: [userId], references: [id])
  
  @@map("projects")
  @@schema("asl_schema")       // 指定Schema ⭐
}

Step 3: 生成迁移

# 生成迁移文件
npx prisma migrate dev --name schema-isolation --create-only

# 查看生成的SQL
# migrations/20251106_schema-isolation/migration.sql

生成的SQL示例

-- CreateSchema
CREATE SCHEMA IF NOT EXISTS "platform_schema";
CREATE SCHEMA IF NOT EXISTS "aia_schema";
-- ...

-- CreateTable
CREATE TABLE "platform_schema"."users" (
  "id" UUID NOT NULL DEFAULT gen_random_uuid(),
  "email" VARCHAR(255) NOT NULL,
  -- ...
  PRIMARY KEY ("id")
);

CREATE TABLE "aia_schema"."projects" (
  "id" UUID NOT NULL DEFAULT gen_random_uuid(),
  "user_id" UUID NOT NULL,
  "name" VARCHAR(255) NOT NULL,
  -- ...
  
  CONSTRAINT "fk_user" FOREIGN KEY ("user_id")
    REFERENCES "platform_schema"."users"("id") ON DELETE CASCADE,
  
  PRIMARY KEY ("id")
);

Step 4: 数据迁移

编写迁移脚本:

-- migrate-data.sql

-- 迁移用户表
INSERT INTO platform_schema.users 
SELECT * FROM public.users;

-- 迁移AIA模块
INSERT INTO aia_schema.projects 
SELECT * FROM public.aia_projects;

INSERT INTO aia_schema.conversations 
SELECT * FROM public.aia_conversations;

INSERT INTO aia_schema.messages 
SELECT * FROM public.aia_messages;

-- 迁移PKB模块
INSERT INTO pkb_schema.knowledge_bases 
SELECT * FROM public.knowledge_bases;

INSERT INTO pkb_schema.documents 
SELECT * FROM public.documents;

-- ... 其他表

执行迁移:

# 方式1使用psql
psql -U postgres -d ai_clinical_research -f migrate-data.sql

# 方式2使用Prisma
npx prisma db execute --file migrate-data.sql

Step 5: 验证

// test-schema-isolation.ts

async function testSchemaIsolation() {
  // 测试跨Schema关联
  const project = await prisma.aiaProject.findUnique({
    where: { id: 'test-id' },
    include: {
      user: true  // 跨Schema关联platform_schema.users
    }
  });
  
  console.log('跨Schema关联正常:', project);
  
  // 测试所有模块
  const stats = {
    users: await prisma.user.count(),
    aiaProjects: await prisma.aiaProject.count(),
    aslProjects: await prisma.aslProject.count(),
    knowledgeBases: await prisma.knowledgeBase.count(),
  };
  
  console.log('数据统计:', stats);
}

📊 成本收益总结

投入成本

项目 时间 风险
学习和准备 1天
开发环境改造 1-2天
测试验证 1-2天
生产环境迁移 1天
总计 5-6天 中等

预期收益

收益 价值
模块独立部署
微服务拆分
数据库级别权限控制
按Schema备份恢复
避免未来大规模改造
总价值 极高

🎯 最终建议总结

我的建议立即做物理隔离方案A

核心理由:

  1. 现在是成本最低的时间窗口(数据量小,改造快)
  2. 避免技术债累积(未来改造成本成倍增长)
  3. 为7个模块打下坚实基础(模块独立部署、微服务拆分)
  4. 支持未来商业模式(模块化销售、私有化部署)

投入: 1周 收益: 避免未来3-5倍的改造成本 投入产出比: 极高


如果您决定采纳方案A我可以立即帮您

  1. 设计详细的Schema结构
  2. 编写Prisma Schema修改方案
  3. 编写数据迁移脚本
  4. 制定详细的实施计划
  5. 准备测试用例和验收标准

您觉得呢? 😊