Features - User Management (Phase 4.1): - Database: Add user_modules table for fine-grained module permissions - Database: Add 4 user permissions (view/create/edit/delete) to role_permissions - Backend: UserService (780 lines) - CRUD with tenant isolation - Backend: UserController + UserRoutes (648 lines) - 13 API endpoints - Backend: Batch import users from Excel - Frontend: UserListPage (412 lines) - list/filter/search/pagination - Frontend: UserFormPage (341 lines) - create/edit with module config - Frontend: UserDetailPage (393 lines) - details/tenant/module management - Frontend: 3 modal components (592 lines) - import/assign/configure - API: GET/POST/PUT/DELETE /api/admin/users/* endpoints Architecture Upgrade - Module Permission System: - Backend: Add getUserModules() method in auth.service - Backend: Login API returns modules array in user object - Frontend: AuthContext adds hasModule() method - Frontend: Navigation filters modules based on user.modules - Frontend: RouteGuard checks requiredModule instead of requiredVersion - Frontend: Remove deprecated version-based permission system - UX: Only show accessible modules in navigation (clean UI) - UX: Smart redirect after login (avoid 403 for regular users) Fixes: - Fix UTF-8 encoding corruption in ~100 docs files - Fix pageSize type conversion in userService (String to Number) - Fix authUser undefined error in TopNavigation - Fix login redirect logic with role-based access check - Update Git commit guidelines v1.2 with UTF-8 safety rules Database Changes: - CREATE TABLE user_modules (user_id, tenant_id, module_code, is_enabled) - ADD UNIQUE CONSTRAINT (user_id, tenant_id, module_code) - INSERT 4 permissions + role assignments - UPDATE PUBLIC tenant with 8 module subscriptions Technical: - Backend: 5 new files (~2400 lines) - Frontend: 10 new files (~2500 lines) - Docs: 1 development record + 2 status updates + 1 guideline update - Total: ~4900 lines of code Status: User management 100% complete, module permission system operational
41 KiB
PostgreSQL 15 数据库部署策略 - 摸底报告
文档版本: v1.1
创建日期: 2025-12-14
最后更新: 2025-12-14
数据库版本: PostgreSQL 15.14 (Docker: postgres:15-alpine)
目标环境: 阿里云 RDS PostgreSQL 15
报告类型: 技术摸底 + 部署策略
📝 版本修订记录
v1.1 (2025-12-14) - 专业建议修正版
修正要点:
-
✅ pg-boss表"自愈"机制澄清
- 修正:pg-boss会在应用启动时自动创建表,只要权限够
- 降低严重性:从"严重差异❌"改为"自愈机制✅(无需担心)"
- 增加说明:pg-boss的智能自愈能力和权限要求
-
✅ 白名单配置强化说明
- 新增:必须使用VPC网段,不能用单机IP
- 示例:172.16.0.0/12(SAE的VPC网段)
- 原因:SAE实例IP会变化,单机IP会导致连接失败
- 位置:RDS准备、网络安全、下一步行动多处强调
-
✅ 备份策略简化(更务实)
- 修正:初期只需RDS自动备份(含日志备份)
- 降级:pg_dump ECS脚本从"第三道防线"降为"可选(业务做大后再考虑)"
- 理由:RDS自动备份+PITR已足够,省心省力
- 保留:脚本示例作为参考,标注"⚠️ 仅供参考,初期不需要"
贡献者: 项目技术团队反馈
📋 目录
本地数据库真实情况
1. 基础信息
Docker镜像:postgres:15-alpine
数据库名称:ai_clinical_research
连接信息:postgresql://postgres:postgres@localhost:5432/ai_clinical_research
数据库大小:26 MB(测试/开发环境)
用户数据:3个用户账号
2. Schema隔离架构(10个Schema)✅
你的数据库已经成功实施了10个Schema隔离架构:
| # | Schema名称 | 表数量 | 状态 | 说明 |
|---|---|---|---|---|
| 1 | platform_schema |
8个表 | ✅ 已实施 | 平台基础设施(用户、pg-boss队列) |
| 2 | aia_schema |
5个表 | ✅ 已实施 | AI智能问答(项目、对话、消息) |
| 3 | pkb_schema |
5个表 | ✅ 已实施 | 个人知识库(知识库、文档、批处理) |
| 4 | asl_schema |
6个表 | ✅ 已实施 | AI智能文献(文献筛选) |
| 5 | dc_schema |
6个表 | ✅ 已实施 | 数据清洗(模板、提取任务、Tool C) |
| 6 | admin_schema |
0个表 | 📋 空Schema | 运营管理(预留) |
| 7 | rvw_schema |
0个表 | 📋 空Schema | 审稿系统(预留) |
| 8 | ssa_schema |
0个表 | 📋 空Schema | 智能统计分析(预留) |
| 9 | st_schema |
0个表 | 📋 空Schema | 统计分析工具(预留) |
| 10 | common_schema |
0个表 | 📋 空Schema | 通用能力层(预留) |
| 11 | public |
4个表 | ⚠️ 旧表遗留 | _prisma_migrations, admin_logs, review_tasks, users |
总计:34个表(30个在隔离Schema中,4个在public中)
3. 详细表清单
3.1 platform_schema(8个表)
✅ users - 用户表(3条记录)
✅ app_cache - Postgres-Only缓存(替代Redis)
✅ job - pg-boss任务表
✅ job_common - pg-boss任务通用表
✅ queue - pg-boss队列表
✅ schedule - pg-boss定时任务表
✅ subscription - pg-boss订阅表
✅ version - pg-boss版本表
⚠️ 重要发现:pg-boss的6个表(job/queue等)是自动创建的,不在Prisma Schema中!
3.2 aia_schema(5个表)
✅ projects - 项目管理(2条记录)
✅ conversations - 项目对话
✅ messages - 对话消息
✅ general_conversations - 通用对话
✅ general_messages - 通用消息
3.3 pkb_schema(5个表)
✅ knowledge_bases - 知识库(2条记录)
✅ documents - 文档
✅ batch_tasks - 批处理任务
✅ batch_results - 批处理结果(976 KB)
✅ task_templates - 任务模板
3.4 asl_schema(6个表)
✅ screening_projects - 文献筛选项目(18条记录)
✅ literatures - 文献数据(2.9 MB)
✅ screening_results - 筛选结果(1.2 MB)
✅ screening_tasks - 筛选任务
✅ fulltext_screening_tasks - 全文筛选任务
✅ fulltext_screening_results - 全文筛选结果
3.5 dc_schema(6个表)
✅ dc_health_checks - 健康检查
✅ dc_templates - 数据清洗模板(4条记录,424 KB)
✅ dc_extraction_tasks - 提取任务(728 KB)
✅ dc_extraction_items - 提取项(6.5 MB,最大表)
✅ dc_tool_c_sessions - Tool C会话(960 KB)
✅ dc_tool_c_ai_history - Tool C AI历史(1 MB)
3.6 public schema(4个表)⚠️
⚠️ _prisma_migrations - Prisma迁移记录(6条记录)
⚠️ admin_logs - 管理日志(遗留)
⚠️ review_tasks - 审查任务(632 KB,遗留)
⚠️ users - 用户表(遗留,与platform_schema.users重复)
说明:
public.users是Schema迁移前的旧表,与platform_schema.users结构完全相同admin_logs和review_tasks应该迁移到admin_schema和rvw_schema_prisma_migrations应该保留在public,这是Prisma的标准位置
4. 数据量统计(Top 15大表)
| Schema | 表名 | 大小 | 说明 |
|---|---|---|---|
| dc_schema | dc_extraction_items | 6.5 MB | 数据清洗提取项(最大表) |
| asl_schema | literatures | 2.9 MB | 文献数据 |
| asl_schema | screening_results | 1.2 MB | 筛选结果 |
| dc_schema | dc_tool_c_ai_history | 1 MB | Tool C AI历史 |
| pkb_schema | batch_results | 976 KB | 批处理结果 |
| dc_schema | dc_tool_c_sessions | 960 KB | Tool C会话 |
| dc_schema | dc_extraction_tasks | 728 KB | 提取任务 |
| public | review_tasks | 632 KB | 审查任务(遗留) |
| dc_schema | dc_templates | 424 KB | 数据清洗模板 |
| pkb_schema | documents | 296 KB | 文档 |
| aia_schema | general_messages | 208 KB | 通用消息 |
| asl_schema | screening_projects | 144 KB | 筛选项目 |
| aia_schema | conversations | 112 KB | 对话 |
| pkb_schema | batch_tasks | 112 KB | 批处理任务 |
| aia_schema | messages | 104 KB | 消息 |
总结:数据主要集中在 DC(数据清洗)和 ASL(文献筛选)模块。
5. Prisma迁移历史
-- 6次Prisma迁移记录(按时间顺序)
20251010075003_init -- 2025-10-12(初始化)
20251010122727_add_conversation_metadata -- 2025-10-12(对话元数据)
20251012124747_add_batch_processing_module -- 2025-10-12(批处理模块)
20251014120128_add_review_tasks -- 2025-10-14(审查任务)
20251127_add_dc_tool_b_tables -- 2025-11-27(DC Tool B)
20251208_add_column_mapping -- 2025-12-08(列映射)
说明:
- 最早的迁移(init)创建的是
publicschema 中的表 - 后续通过手工SQL迁移到了10个隔离Schema
- Prisma并没有记录Schema迁移过程(这些是手工SQL完成的)
Prisma与数据库的差异分析
1. pg-boss表的"自愈"机制✅(无需担心)
现象:
platform_schema 有 8 个表,但 Prisma Schema 只定义了 2 个:
- ✅ Prisma定义:
AppCache,User - 🔧 pg-boss自动管理:
job,job_common,queue,schedule,subscription,version(共6个表)
原因:
这6个表是 pg-boss 库在应用启动时自动创建的(用于Postgres-Only任务队列),不需要在Prisma Schema中定义。
pg-boss的"自愈"能力:
// backend启动时,pg-boss会自动检查表是否存在
const boss = new PgBoss(process.env.DATABASE_URL)
await boss.start()
// ✅ 如果表不存在,pg-boss会自动创建
// ✅ 只要数据库用户(如aiclinical_rw)有 CREATE TABLE 权限
// ✅ 完全不需要手工干预
影响评估:
- ✅ 运行时零影响:pg-boss自己管理这些表,不通过Prisma访问
- ✅ 首次部署自动创建:RDS部署时,backend启动会自动创建这6个表
- ⚠️ Prisma db pull会检测到:执行
npx prisma db pull时会发现"未定义的表"(可忽略) - ⚠️ Prisma migrate不操作:迁移不会同步这些表(这是正确的)
最佳实践:
// backend/prisma/schema.prisma
// 在文件开头添加注释(文档用途)
// ==================== pg-boss 自动管理的表(不需要定义) ====================
// 以下6个表由pg-boss库自动创建和维护,请勿手工修改:
// - platform_schema.job - 任务表
// - platform_schema.job_common - 任务通用配置
// - platform_schema.queue - 队列表
// - platform_schema.schedule - 定时任务表
// - platform_schema.subscription - 订阅表
// - platform_schema.version - 版本表
// ==================== 以上表自动管理,无需Prisma定义 ====================
结论:pg-boss表"缺失"是正常的,无需担心!
2. public schema遗留表⚠️(需要清理)
问题:
public schema 有 4 个表:
_prisma_migrations:Prisma标准表,正确✅users:与platform_schema.users重复❌admin_logs:应迁移到admin_schema❌review_tasks:应迁移到rvw_schema❌
影响:
- ⚠️ 数据不一致风险(如果代码误读
public.users) - ⚠️ 混淆(两个
users表) - ⚠️ 空间浪费(重复数据)
解决方案:
-- 步骤1:迁移 admin_logs 到 admin_schema
ALTER TABLE public.admin_logs SET SCHEMA admin_schema;
-- 步骤2:迁移 review_tasks 到 rvw_schema
ALTER TABLE public.review_tasks SET SCHEMA rvw_schema;
-- 步骤3:验证 platform_schema.users 和 public.users 数据一致后删除
-- (先在生产环境验证一周,确保代码不再引用 public.users)
DROP TABLE public.users;
3. Prisma Schema的定义状态
| Schema | Prisma定义的模型 | 实际数据库表 | 状态 |
|---|---|---|---|
| platform_schema | 2个(AppCache, User) | 8个(+6个pg-boss自动管理) | ✅ 正常(pg-boss自愈) |
| aia_schema | 5个 | 5个 | ✅ 一致 |
| pkb_schema | 5个 | 5个 | ✅ 一致 |
| asl_schema | 6个 | 6个 | ✅ 一致 |
| dc_schema | 6个 | 6个 | ✅ 一致 |
| public | 2个(AdminLog, ReviewTask) | 4个(+_prisma_migrations, users) | ⚠️ 需清理 |
结论:
- ✅ Prisma Schema 完全准确:pg-boss的6个表会自动创建,不需要定义
- ⚠️ public schema需要清理:遗留了旧的
users表(首次部署后清理)
代码如何连接数据库
1. 环境变量配置
1.1 默认连接字符串(env.ts)
// AIclinicalresearch/backend/src/config/env.ts (line 50)
databaseUrl: process.env.DATABASE_URL || 'postgresql://postgres:postgres@localhost:5432/ai_clinical'
⚠️ 注意:
- 默认值是
ai_clinical(不存在) - 实际数据库名是
ai_clinical_research - 说明你的
.env文件中已经正确配置了DATABASE_URL
1.2 连接池配置(database.ts)
// AIclinicalresearch/backend/src/config/database.ts
export const prisma = new PrismaClient({
datasources: {
db: {
url: process.env.DATABASE_URL, // 从环境变量读取
},
},
})
// 连接池计算公式
connectionLimit = Math.floor(RDS_MAX_CONNECTIONS / MAX_INSTANCES) - 10
// 示例:400 / 20 - 10 = 10个连接/实例
云原生连接池策略:
DATABASE_URL=postgresql://user:pass@host:5432/db?connection_limit=10&pool_timeout=10
2. 连接方式
| 连接参数 | 本地开发 | 阿里云SAE |
|---|---|---|
| 主机 | localhost | RDS内网地址(如 rm-xxxxx.pg.rds.aliyuncs.com) |
| 端口 | 5432 | 5432 |
| 数据库 | ai_clinical_research | ai_clinical_research(保持一致) |
| 用户名 | postgres | 自定义(如 aiclinical_rw) |
| 密码 | postgres | 强密码(RDS创建时设置) |
| 连接限制 | 无限制 | connection_limit=10(SAE每实例) |
| 连接超时 | 默认 | pool_timeout=10 |
3. 多Schema访问(Prisma)
// AIclinicalresearch/backend/prisma/schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
schemas = ["platform_schema", "aia_schema", "pkb_schema", "asl_schema",
"common_schema", "dc_schema", "rvw_schema", "admin_schema",
"ssa_schema", "st_schema", "public"]
}
generator client {
provider = "prisma-client-js"
previewFeatures = ["multiSchema"] // 启用多Schema支持
}
代码中的使用:
// 自动路由到正确的Schema
await prisma.user.findMany() // 访问 platform_schema.users
await prisma.project.findMany() // 访问 aia_schema.projects
4. 原生SQL访问(跨Schema)
// 支持跨Schema查询
const result = await prisma.$queryRaw`
SELECT u.name, p.name AS project_name
FROM platform_schema.users u
JOIN aia_schema.projects p ON p.user_id = u.id
`
5. 优雅关闭机制
// AIclinicalresearch/backend/src/config/database.ts
process.on('SIGTERM', () => gracefulShutdown('SIGTERM')) // SAE实例停止
process.on('SIGINT', () => gracefulShutdown('SIGINT')) // Ctrl+C
async function gracefulShutdown(signal: string): Promise<void> {
await prisma.$disconnect() // 关闭所有连接
process.exit(0)
}
这是云原生最佳实践,防止SAE扩容/缩容时连接泄漏。
首次部署方案
方案对比
| 方案 | 方法 | 优点 | 缺点 | 推荐度 |
|---|---|---|---|---|
| 方案A | pg_dump 全量导入 |
✅ 100%完整(包括pg-boss表) ✅ 结构+数据+索引+外键 ✅ 一次性完成 |
❌ 需要手工清理测试数据 ❌ 包含遗留的public表 |
⭐⭐⭐⭐⭐ 强烈推荐 |
| 方案B | Prisma Migrate Deploy | ✅ 版本化管理 ✅ 可重复执行 |
❌ 缺少pg-boss表(运行时会报错) ❌ 需要手工补充 |
⚠️ 不推荐(不完整) |
| 方案C | 手工SQL脚本 | ✅ 完全可控 | ❌ 工作量大 ❌ 容易出错 |
⚠️ 不推荐(费时费力) |
⭐ 推荐方案A:pg_dump全量导入(详细步骤)
步骤1:本地数据库清理(可选)
# 如果本地有测试数据,可以选择清理
docker exec -it ai-clinical-postgres psql -U postgres -d ai_clinical_research
# 删除测试用户(保留真实用户)
DELETE FROM platform_schema.users WHERE email LIKE '%test%';
# 清理遗留的 public.users(Schema迁移后的旧表)
DROP TABLE IF EXISTS public.users;
步骤2:导出数据库(包含结构+数据)
# 完整导出(包括所有Schema、表、数据、索引、外键)
docker exec ai-clinical-postgres pg_dump -U postgres -d ai_clinical_research \
--format=plain \
--no-owner \
--no-acl \
--encoding=UTF8 \
> D:\MyCursor\ai_clinical_research_backup_$(Get-Date -Format 'yyyyMMdd_HHmmss').sql
# 文件大小应该在 100-200 KB(当前数据量26MB)
参数说明:
--format=plain:纯文本SQL(方便查看和编辑)--no-owner:不导出所有者信息(避免RDS用户名不匹配)--no-acl:不导出权限信息(使用RDS默认权限)--encoding=UTF8:UTF-8编码(中文支持)
步骤3:RDS PostgreSQL 15 准备
# 1. 在阿里云控制台创建RDS PostgreSQL 15实例
规格:rds.pg.s2.large(2核4GB,入门级)
存储:100GB SSD(支持自动扩容)
版本:PostgreSQL 15
网络:VPC(与SAE在同一VPC)
白名单:⚠️ 必须配置VPC网段,不能用单机IP!
- 示例:172.16.0.0/12(SAE的VPC网段)
- 查看方式:SAE控制台 > 应用详情 > 网络配置 > VPC网段
- ❌ 错误示例:172.16.1.23(单机IP,SAE实例IP会变化)
# 2. 创建数据库
CREATE DATABASE ai_clinical_research
WITH ENCODING='UTF8'
LC_COLLATE='en_US.UTF-8'
LC_CTYPE='en_US.UTF-8'
TEMPLATE=template0;
# 3. 创建应用用户(不要用超级用户)
CREATE USER aiclinical_rw WITH PASSWORD '你的强密码';
GRANT ALL PRIVILEGES ON DATABASE ai_clinical_research TO aiclinical_rw;
GRANT ALL ON SCHEMA public TO aiclinical_rw;
步骤4:导入到RDS
# 方法1:从本地直接导入(需要RDS公网地址)
psql -h rm-xxxxx.pg.rds.aliyuncs.com \
-p 5432 \
-U aiclinical_rw \
-d ai_clinical_research \
-f ai_clinical_research_backup_20251214_150000.sql
# 方法2:通过ECS跳板机导入(推荐,更安全)
scp backup.sql root@your-ecs-ip:/tmp/
ssh root@your-ecs-ip
psql -h rm-xxxxx.pg.rds.aliyuncs.com -U aiclinical_rw -d ai_clinical_research -f /tmp/backup.sql
步骤5:验证导入结果
-- 1. 验证Schema数量
SELECT nspname FROM pg_namespace WHERE nspname LIKE '%_schema' ORDER BY nspname;
-- 应该有10个Schema
-- 2. 验证表数量
SELECT schemaname, COUNT(*) as table_count
FROM pg_tables
WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
GROUP BY schemaname
ORDER BY schemaname;
-- platform_schema: 8, aia_schema: 5, pkb_schema: 5, asl_schema: 6, dc_schema: 6
-- 3. 验证数据量
SELECT COUNT(*) FROM platform_schema.users; -- 应该有3条(或你保留的数量)
SELECT COUNT(*) FROM aia_schema.projects; -- 应该有2条
SELECT COUNT(*) FROM asl_schema.literatures; -- 应该有对应数量
-- 4. 验证pg-boss表(关键)
SELECT tablename FROM pg_tables WHERE schemaname = 'platform_schema' ORDER BY tablename;
-- 应该包括:job, job_common, queue, schedule, subscription, version
-- 5. 验证外键约束
SELECT conname, conrelid::regclass, confrelid::regclass
FROM pg_constraint
WHERE contype = 'f'
LIMIT 10;
步骤6:配置SAE环境变量
# 在SAE应用配置中添加
DATABASE_URL=postgresql://aiclinical_rw:强密码@rm-xxxxx.pg.rds.aliyuncs.com:5432/ai_clinical_research?connection_limit=10&pool_timeout=10&connect_timeout=10
# 其他相关配置
DB_MAX_CONNECTIONS=400 # RDS最大连接数
MAX_INSTANCES=20 # SAE最大实例数
NODE_ENV=production
步骤7:应用启动验证
// backend启动时会自动执行
npm run build
npm run start
// 日志输出应该显示:
// ✅ 数据库连接成功!
// 📊 数据库版本: PostgreSQL 15.x
// 📊 当前数据库连接数: 1
步骤8:Prisma Client生成(重要)
# 在backend目录下
cd backend
npx prisma generate
# 验证生成的Client
ls node_modules/.prisma/client/
# 应该包含所有Schema的类型定义
⚠️ 方案B为什么不推荐:Prisma Migrate的局限
# 如果执行 prisma migrate deploy
cd backend
npx prisma migrate deploy
# 问题1:缺少 pg-boss 表
# prisma migrate deploy 只会创建 Prisma Schema 中定义的表
# 不会创建 pg-boss 的 6 个表(job, queue等)
# 问题2:应用启动时报错
# Error: relation "platform_schema.job" does not exist
# 因为代码中使用了 pg-boss,但表不存在
# 问题3:需要手工补救
# 必须手工执行 pg-boss 的初始化SQL
# 非常繁琐且容易出错
结论:pg_dump 全量导入是首次部署的最佳方案!
未来更新策略
场景1:新增一个表或修改一个字段(小更新)
推荐方案:Prisma Migrate(版本化管理)
# 步骤1:在本地开发环境修改 schema.prisma
# 示例:为 User 表添加一个字段
model User {
id String @id @default(uuid())
email String @unique
// ... 其他字段 ...
phoneNumber String? @map("phone_number") // 新增字段
@@map("users")
@@schema("platform_schema")
}
# 步骤2:创建迁移(本地)
cd backend
npx prisma migrate dev --name add_user_phone_number
# 生成的迁移文件示例:
# backend/prisma/migrations/20251214120000_add_user_phone_number/migration.sql
ALTER TABLE "platform_schema"."users" ADD COLUMN "phone_number" TEXT;
# 步骤3:测试本地迁移
npm run dev
# 验证新字段可用
# 步骤4:提交到Git
git add backend/prisma/migrations/20251214120000_add_user_phone_number
git commit -m "feat: add phone_number to User model"
git push
# 步骤5:部署到SAE(自动执行迁移)
# 在 SAE 的应用启动命令中添加:
# CMD ["sh", "-c", "npx prisma migrate deploy && node dist/index.js"]
# ⚠️ 注意:Dockerfile 的 CMD 应该是:
CMD ["sh", "-c", "node dist/index.js"]
# 不要在生产启动时执行 migrate deploy(风险太大)
# 步骤6:手工执行迁移(推荐)
# 方法1:通过ECS跳板机执行
ssh root@your-ecs-ip
psql -h rm-xxxxx.pg.rds.aliyuncs.com -U aiclinical_rw -d ai_clinical_research \
-f migrations/20251214120000_add_user_phone_number/migration.sql
# 方法2:使用 RDS 控制台的 SQL 窗口
# 直接粘贴迁移SQL执行
# 步骤7:验证
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_schema = 'platform_schema'
AND table_name = 'users'
AND column_name = 'phone_number';
为什么不推荐在启动命令中执行 prisma migrate deploy?
- 并发风险:SAE有多个实例,可能同时执行迁移
- 回滚困难:如果迁移失败,应用已经启动
- 数据安全:生产数据库应该手工迁移,有备份和回滚计划
场景2:新增一个大模块(如SSA智能统计分析)
推荐方案:结构化迁移流程
# 步骤1:在本地开发环境设计Schema
# 示例:SSA(智能统计分析)模块
# backend/prisma/schema.prisma
// SSA Schema(新增)
model SsaAnalysisTask {
id String @id @default(uuid())
userId String @map("user_id")
projectId String @map("project_id")
taskName String @map("task_name")
status String @default("pending")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User @relation(fields: [userId], references: [id])
results SsaAnalysisResult[]
@@map("analysis_tasks")
@@schema("ssa_schema")
}
model SsaAnalysisResult {
id String @id @default(uuid())
taskId String @map("task_id")
resultType String @map("result_type")
resultData Json @map("result_data")
createdAt DateTime @default(now()) @map("created_at")
task SsaAnalysisTask @relation(fields: [taskId], references: [id])
@@map("analysis_results")
@@schema("ssa_schema")
}
# 步骤2:创建迁移
npx prisma migrate dev --name add_ssa_module
# 步骤3:验证迁移SQL
# backend/prisma/migrations/20251214_add_ssa_module/migration.sql
-- 检查:
-- 1. 是否在 ssa_schema 中创建表
-- 2. 外键是否正确引用 platform_schema.users
-- 3. 索引是否完整
# 步骤4:本地测试
# 开发SSA模块的业务逻辑
# 确保所有CRUD操作正常
# 步骤5:准备生产部署
# 5.1 数据库备份(强制)
# 在RDS控制台手动创建快照:ai_clinical_research_before_ssa_module_20251214
# 5.2 准备回滚脚本
# backend/prisma/migrations/20251214_add_ssa_module/rollback.sql
DROP TABLE IF EXISTS ssa_schema.analysis_results CASCADE;
DROP TABLE IF EXISTS ssa_schema.analysis_tasks CASCADE;
# 步骤6:生产环境迁移(分步执行)
# 6.1 停机窗口(可选,如果无法零停机)
# 晚上11点-凌晨2点,用户最少
# 6.2 执行迁移SQL
psql -h rm-xxxxx.pg.rds.aliyuncs.com -U aiclinical_rw -d ai_clinical_research \
-f migrations/20251214_add_ssa_module/migration.sql
# 6.3 验证表结构
\dt ssa_schema.*
\d ssa_schema.analysis_tasks
\d ssa_schema.analysis_results
# 6.4 验证外键
SELECT conname, conrelid::regclass, confrelid::regclass
FROM pg_constraint
WHERE connamespace = 'ssa_schema'::regnamespace;
# 步骤7:部署应用代码
# 7.1 更新backend
git pull
npm run build
# SAE自动部署或手动触发部署
# 7.2 健康检查
curl https://your-backend.sae.aliyuncs.com/health
# 应该返回 200 OK
# 步骤8:烟雾测试(Smoke Testing)
# 8.1 测试SSA模块基本功能
POST /api/v1/ssa/analysis-tasks
GET /api/v1/ssa/analysis-tasks/:id
# 8.2 测试旧模块(回归测试)
GET /api/v1/aia/projects
GET /api/v1/pkb/knowledge-bases
# 确保旧功能不受影响
# 步骤9:监控24小时
# 9.1 RDS监控
# - 连接数是否正常
# - CPU/内存使用率
# - 慢查询日志
# 9.2 SAE监控
# - 应用日志(错误数量)
# - API响应时间
# - 实例健康状态
# 步骤10:如果失败,立即回滚
# 10.1 回滚数据库
psql -h rm-xxxxx.pg.rds.aliyuncs.com -U aiclinical_rw -d ai_clinical_research \
-f migrations/20251214_add_ssa_module/rollback.sql
# 10.2 回滚应用代码
# 在SAE控制台选择上一个版本,点击"回滚"
# 10.3 恢复RDS快照(如果必要)
# 在RDS控制台恢复快照:ai_clinical_research_before_ssa_module_20251214
场景3:紧急修复(Hotfix)
# 示例:production环境发现某个字段长度不够
# 步骤1:本地创建紧急迁移
# backend/prisma/schema.prisma
model User {
// 将 email 字段长度从 varchar(255) 改为 text
email String @unique // Prisma默认是text,无需修改
// ...
}
# 步骤2:生成迁移
npx prisma migrate dev --name hotfix_user_email_length
# 步骤3:测试迁移SQL(本地)
# 确保不会丢失数据
# 步骤4:生产环境执行(快速通道)
# 4.1 备份(快照)
# RDS控制台 > 备份 > 立即备份 > 备注:hotfix_before_email_length
# 4.2 执行SQL(通过SQL窗口,秒级完成)
ALTER TABLE platform_schema.users ALTER COLUMN email TYPE TEXT;
# 4.3 验证
SELECT column_name, data_type, character_maximum_length
FROM information_schema.columns
WHERE table_schema = 'platform_schema'
AND table_name = 'users'
AND column_name = 'email';
# 4.4 提交Git
git add backend/prisma/migrations/20251214_hotfix_user_email_length
git commit -m "hotfix: increase user email column length"
git push
RDS备份策略
1. 阿里云RDS自动备份(强烈推荐)✅
1.1 配置自动备份
# 在RDS控制台配置
备份设置 > 备份策略
# 推荐配置:
数据备份保留时间:7天(免费额度)
日志备份保留时间:7天
备份周期:每天一次
备份时间:凌晨2:00-4:00(业务低峰期)
备份方式:物理备份(快,占用空间小)
优势:
- ✅ 全自动,无需人工干预
- ✅ 时间点恢复(Point-in-Time Recovery,PITR),可恢复到任意秒
- ✅ 存储在OSS,独立于RDS实例
- ✅ 免费(7天内)
1.2 手动快照(重要操作前)
# 场景:
# - 大版本升级前
# - 数据库Schema重大变更前
# - 删除大量数据前
# 操作:
RDS控制台 > 备份恢复 > 备份实例 > 创建备份
备份方式:物理备份
备注:migration_before_ssa_module_20251214
快照保留建议:
- 迁移前快照:保留30天
- 版本升级前快照:保留60天
- 季度归档快照:保留1年
2. 逻辑备份(pg_dump)- 可选策略(业务做大后再考虑)
⚠️ 初期建议:不需要自己写脚本,RDS自动备份就够了!
| 阶段 | 备份策略 | 理由 |
|---|---|---|
| 初期(当前) | ✅ 只用RDS自动备份 | - 自动备份+日志备份=PITR(任意时间点恢复) - 阿里云托管,稳定可靠 - 无需维护脚本和ECS |
| 成长期 | 可选:增加异地灾备 | - 业务关键后,考虑多地域备份 - 使用RDS的"跨地域备份"功能(一键配置) |
| 成熟期 | 可选:自定义备份 | - 需要特殊备份策略(如每小时备份) - 需要长期归档(如5年) |
如果未来需要自定义备份,参考脚本:
# ⚠️ 仅供参考,初期不需要执行
#!/bin/bash
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/data/pg_backups"
RDS_HOST="rm-xxxxx.pg.rds.aliyuncs.com"
RDS_USER="aiclinical_rw"
RDS_DB="ai_clinical_research"
# 导出SQL
pg_dump -h $RDS_HOST -U $RDS_USER -d $RDS_DB \
--format=plain \
--no-owner \
--no-acl \
--encoding=UTF8 \
--file=$BACKUP_DIR/ai_clinical_research_$DATE.sql
# 压缩并上传到OSS
gzip $BACKUP_DIR/ai_clinical_research_$DATE.sql
ossutil cp $BACKUP_DIR/ai_clinical_research_$DATE.sql.gz \
oss://your-bucket/database-backups/
# 清理本地备份
ls -t $BACKUP_DIR/*.sql.gz | tail -n +4 | xargs rm -f
总结:初期专注RDS自动备份,省心省力!
3. 恢复演练(每季度一次)
# 目的:验证备份可用,熟悉恢复流程
# 步骤1:创建测试RDS实例
# RDS控制台 > 创建实例 > 按备份创建
# 选择最新的自动备份或快照
# 步骤2:验证数据完整性
psql -h test-rm-xxxxx.pg.rds.aliyuncs.com -U aiclinical_rw -d ai_clinical_research
# 检查:
SELECT schemaname, COUNT(*) FROM pg_tables
WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
GROUP BY schemaname;
SELECT COUNT(*) FROM platform_schema.users;
# 步骤3:记录恢复时间
# 26MB数据库,完整恢复约5-10分钟
# 步骤4:删除测试实例
4. 备份监控与告警
# 在阿里云监控配置告警规则
# 规则1:备份失败告警
资源:RDS实例
指标:备份任务状态
条件:备份失败
通知:钉钉群 + 邮件
# 规则2:备份空间不足告警
资源:RDS实例
指标:备份使用空间
条件:>80%
通知:邮件
# 规则3:超过24小时未备份告警
资源:RDS实例
指标:最后备份时间
条件:>24小时
通知:紧急(短信+电话)
最佳实践与禁止操作
✅ 最佳实践(11条黄金法则)
1. 连接池管理
// ✅ 正确:在 DATABASE_URL 中配置连接限制
DATABASE_URL=postgresql://user:pass@host:5432/db?connection_limit=10&pool_timeout=10
// ✅ 正确:根据SAE实例数动态计算
connectionLimit = Math.floor(400 / 20) - 10 = 10
// ❌ 错误:不配置连接限制(会耗尽RDS连接)
DATABASE_URL=postgresql://user:pass@host:5432/db
2. 优雅关闭
// ✅ 正确:监听 SIGTERM 信号
process.on('SIGTERM', async () => {
await prisma.$disconnect()
process.exit(0)
})
// ❌ 错误:不关闭连接(SAE缩容时连接泄漏)
3. 事务管理
// ✅ 正确:使用Prisma事务
await prisma.$transaction(async (tx) => {
await tx.user.create({ data: { email: 'test@example.com' } })
await tx.project.create({ data: { userId: 'xxx', name: 'Test' } })
})
// ❌ 错误:手动管理事务(容易忘记COMMIT)
await prisma.$executeRaw`BEGIN`
// ... 多个操作 ...
await prisma.$executeRaw`COMMIT` // 如果中间出错,不会回滚
4. Schema隔离访问
// ✅ 正确:Prisma自动路由到正确的Schema
await prisma.user.findMany() // 自动访问 platform_schema.users
// ✅ 正确:跨Schema查询(外键关系)
await prisma.user.findUnique({
where: { id: 'xxx' },
include: { projects: true } // 自动JOIN aia_schema.projects
})
// ❌ 错误:手写Schema名称(Prisma不推荐)
await prisma.$queryRaw`SELECT * FROM platform_schema.users` // 可以工作,但类型不安全
5. 索引优化
-- ✅ 正确:为频繁查询的字段创建索引
CREATE INDEX idx_users_email ON platform_schema.users(email);
CREATE INDEX idx_projects_user_id ON aia_schema.projects(user_id);
-- ✅ 正确:为外键创建索引(Prisma不自动创建)
CREATE INDEX idx_conversations_project_id ON aia_schema.conversations(project_id);
-- ❌ 错误:创建过多索引(影响写入性能)
-- 只为频繁查询的字段创建索引
6. 迁移测试
# ✅ 正确:先在测试环境验证
# 测试环境 > 预发环境 > 生产环境
# ✅ 正确:准备回滚脚本
# 每个迁移都有对应的rollback.sql
# ❌ 错误:直接在生产环境执行未测试的迁移
7. 备份验证
# ✅ 正确:每季度恢复演练
# 确保备份可用
# ✅ 正确:重要操作前手动快照
# 大版本升级、Schema变更前
# ❌ 错误:从不验证备份(备份失效时才发现)
8. 监控告警
# ✅ 正确:监控关键指标
- RDS连接数(告警阈值:80%)
- 慢查询(告警阈值:>1秒)
- 死锁(告警阈值:>0)
- CPU使用率(告警阈值:>70%)
# ❌ 错误:不监控(问题发生后才发现)
9. 密码管理
# ✅ 正确:使用强密码(16位+大小写+数字+符号)
DATABASE_URL=postgresql://user:Abc123!@#XYZ456@host:5432/db
# ✅ 正确:定期轮换密码(每6个月)
# ❌ 错误:使用弱密码(postgres/123456)
10. 网络安全
# ✅ 正确:RDS只允许VPC内网访问
# 白名单配置:必须使用VPC网段,不能用单机IP
白名单示例:172.16.0.0/12(SAE的VPC网段)
获取方式:SAE控制台 > 应用详情 > 网络配置 > VPC网段
# ✅ 正确:不开放公网访问(除非临时调试)
# ❌ 错误1:白名单配置 0.0.0.0/0(全世界可访问)
# ❌ 错误2:白名单配置单机IP 172.16.1.23(SAE实例IP会变化,导致连接失败)
# ❌ 错误3:配置多个单机IP(维护困难,且无法应对SAE弹性扩容)
11. pg-boss表管理
// ✅ 正确:不要在Prisma Schema中定义pg-boss表
// pg-boss自动管理,不需要手工定义
// ✅ 正确:不要手工修改pg-boss表
// 可能导致任务队列异常
// ❌ 错误:删除pg-boss表
// DROP TABLE platform_schema.job; // 会导致应用崩溃
12. 时区统一配置 ⭐⭐⭐⭐⭐
-- ✅ 正确:RDS时区配置为 Asia/Shanghai
-- RDS控制台 > 参数设置 > timezone
timezone = Asia/Shanghai
-- ✅ 验证时区
SHOW timezone;
-- 应该显示:Asia/Shanghai
-- ❌ 错误:使用UTC时区(与应用时区不一致)
-- 后果:
-- 1. 日志时间对不上(前端14:00,数据库06:00)
-- 2. pg-boss定时任务在错误时间触发
-- 3. 用户看到的时间戳错误
配置步骤:
# 步骤1:登录RDS控制台
# 阿里云 > 云数据库RDS > PostgreSQL实例
# 步骤2:修改参数
# 实例详情 > 参数设置 > 搜索 "timezone"
# 修改为:Asia/Shanghai
# 步骤3:重启实例(参数修改需要重启)
# 实例详情 > 重启实例
# 步骤4:验证
psql -h rm-xxxxx.pg.rds.aliyuncs.com -U aiclinical_rw -d ai_clinical_research
SHOW timezone;
# 应该显示:Asia/Shanghai
与应用时区保持一致:
# Node.js后端:ENV TZ=Asia/Shanghai
# Python微服务:ENV TZ=Asia/Shanghai
# 前端Nginx:ENV TZ=Asia/Shanghai
# RDS PostgreSQL:timezone = Asia/Shanghai
# ✅ 所有服务时区统一,避免时间混乱
❌ 绝对禁止的操作(10条红线)
🚫 1. 禁止在生产环境直接执行 DROP TABLE
-- ❌ 绝对禁止
DROP TABLE platform_schema.users; -- 丢失所有用户数据,无法恢复
-- ✅ 正确做法:先备份,再重命名,观察7天后删除
ALTER TABLE platform_schema.users RENAME TO users_deprecated_20251214;
-- 7天后确认无问题,再删除
DROP TABLE platform_schema.users_deprecated_20251214;
🚫 2. 禁止在生产环境执行未测试的迁移
# ❌ 绝对禁止
psql -h production-rds -f untested_migration.sql # 可能破坏数据
# ✅ 正确做法:测试环境 > 预发环境 > 生产环境
🚫 3. 禁止不备份就执行重大变更
-- ❌ 绝对禁止
ALTER TABLE platform_schema.users DROP COLUMN email; -- 没有备份
-- ✅ 正确做法:先创建快照
-- RDS控制台 > 备份实例 > 创建备份
-- 然后再执行变更
🚫 4. 禁止在应用代码中硬编码数据库密码
// ❌ 绝对禁止
const prisma = new PrismaClient({
datasources: {
db: {
url: 'postgresql://user:password@host:5432/db' // 硬编码
}
}
})
// ✅ 正确做法:使用环境变量
const prisma = new PrismaClient({
datasources: {
db: {
url: process.env.DATABASE_URL // 环境变量
}
}
})
🚫 5. 禁止不配置连接池限制
# ❌ 绝对禁止
DATABASE_URL=postgresql://user:pass@host:5432/db # 无限制
# ✅ 正确做法
DATABASE_URL=postgresql://user:pass@host:5432/db?connection_limit=10
🚫 6. 禁止在生产环境使用 prisma migrate dev
# ❌ 绝对禁止
npx prisma migrate dev # 会删除数据重建
# ✅ 正确做法
npx prisma migrate deploy # 只应用新迁移,不删除数据
🚫 7. 禁止在生产环境启用Prisma的 query 日志
// ❌ 绝对禁止(生产环境)
export const prisma = new PrismaClient({
log: ['query', 'info', 'warn', 'error'] // 性能严重下降
})
// ✅ 正确做法(生产环境)
export const prisma = new PrismaClient({
log: process.env.NODE_ENV === 'production' ? ['error'] : ['query', 'info', 'warn', 'error']
})
🚫 8. 禁止手工修改 _prisma_migrations 表
-- ❌ 绝对禁止
DELETE FROM _prisma_migrations WHERE migration_name = 'xxx'; -- 破坏迁移历史
-- ✅ 正确做法:如果迁移出错,回滚并修复
-- 不要手工修改 _prisma_migrations
🚫 9. 禁止在生产环境使用超级用户(postgres)
# ❌ 绝对禁止
DATABASE_URL=postgresql://postgres:password@host:5432/db # 权限过大
# ✅ 正确做法:创建应用专用用户
CREATE USER aiclinical_rw WITH PASSWORD 'xxx';
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA platform_schema TO aiclinical_rw;
🚫 10. 禁止删除pg-boss的表
-- ❌ 绝对禁止
DROP TABLE platform_schema.job; -- 应用立即崩溃
DROP TABLE platform_schema.queue; -- 任务队列失效
-- ✅ 正确做法:永远不要手工操作pg-boss表
-- 如果需要清理,使用pg-boss的API
📝 总结与建议
1. 你的数据库现状(非常好)✅
| 维度 | 状态 | 评分 |
|---|---|---|
| Schema隔离架构 | ✅ 10个Schema全部就位 | ⭐⭐⭐⭐⭐ |
| Postgres-Only架构 | ✅ pg-boss正常工作,支持自愈 | ⭐⭐⭐⭐⭐ |
| 数据完整性 | ✅ 外键约束完整 | ⭐⭐⭐⭐⭐ |
| Prisma Schema | ✅ 完全准确(pg-boss表会自动创建) | ⭐⭐⭐⭐⭐ |
| 数据量 | ✅ 26MB,适合迁移 | ⭐⭐⭐⭐⭐ |
| 遗留问题 | ⚠️ public schema有旧表(首次部署后清理) | ⭐⭐⭐⭐ |
总评:你的数据库架构非常优秀,可以直接部署到RDS!
2. 首次部署推荐方案
🏆 强烈推荐:pg_dump 全量导入
理由:
✅ 100%完整(包括pg-boss表)
✅ 结构+数据+索引+外键一次性完成
✅ 风险低,可重复执行
✅ 恢复快(26MB约30秒)
时间估算:
- 导出:1分钟
- 传输到ECS:1分钟
- 导入RDS:3分钟
- 验证:2分钟
总计:<10分钟
3. 未来更新推荐方案
| 更新类型 | 推荐方案 | 工具 |
|---|---|---|
| 小更新(新增字段) | Prisma Migrate | npx prisma migrate dev |
| 中更新(新增表) | Prisma Migrate | npx prisma migrate dev |
| 大更新(新模块) | 结构化流程 | Prisma + 手工SQL + 备份 |
| 紧急修复(Hotfix) | 快速通道 | 直接SQL(先备份) |
4. 备份策略(初期简化版)
第一道防线:RDS自动备份(必做)⭐⭐⭐⭐⭐
├─ 数据备份:每天凌晨2点
├─ 日志备份:实时(开启PITR)
└─ 保留7天
第二道防线:手动快照(必做)⭐⭐⭐⭐
└─ 重大操作前创建
第三道防线:pg_dump脚本(初期不需要)
└─ 业务做大后再考虑
恢复演练:每季度一次(必做)
初期只需配置RDS自动备份,省心省力!
5. 清理遗留问题(可选)
-- 建议在首次部署到RDS后清理
-- 1. 迁移 admin_logs 到 admin_schema
ALTER TABLE public.admin_logs SET SCHEMA admin_schema;
-- 2. 迁移 review_tasks 到 rvw_schema
ALTER TABLE public.review_tasks SET SCHEMA rvw_schema;
-- 3. 删除重复的 public.users(验证后)
-- 先对比数据一致性
SELECT COUNT(*) FROM public.users;
SELECT COUNT(*) FROM platform_schema.users;
-- 一致后删除
DROP TABLE public.users;
-- 4. 更新Prisma Schema
model AdminLog {
// ...
@@map("admin_logs")
@@schema("admin_schema") // 从 public 改为 admin_schema
}
model ReviewTask {
// ...
@@map("review_tasks")
@@schema("rvw_schema") // 从 public 改为 rvw_schema
}
6. 下一步行动
☐ 1. 创建阿里云RDS PostgreSQL 15实例
☐ 2. 配置VPC、白名单(⚠️用VPC网段)、备份策略(自动备份+日志备份)
☐ 3. 本地清理测试数据(可选)
☐ 4. pg_dump导出数据库
☐ 5. 通过ECS跳板机导入RDS
☐ 6. 验证导入结果(Schema、表、数据、pg-boss)
☐ 7. 配置SAE环境变量(DATABASE_URL)
☐ 8. 部署backend应用
☐ 9. 端到端测试
☐ 10. 监控24小时
预计时间:3-4小时(从0到上线)
文档创建人: AI助手
最后更新: 2025-12-14
版本: v1.0
核心理念:数据安全第一,充分备份,渐进式迁移,完整验证 ⭐⭐⭐