gongxiangp2/docs/migration-summary.md
chenpei ea9076df45 feat: 实现统一的下载错误处理系统并修复产品全案数据库Schema
- 创建统一的下载错误处理模块 (lib/download-errors.ts)
- 实现13种错误类型分类和中文错误消息
- 后端统一使用 createDownloadError 创建标准化错误响应
- 前端统一使用 parseDownloadError 解析错误并显示
- 修复正式系统数据库Schema,添加产品全案相关字段和表
- 执行数据库迁移:添加 task_no, task_status, task_fetched_at 字段
- 创建 material_assets, api_keys, api_call_logs 表
2026-01-05 17:52:42 +08:00

5.7 KiB
Raw Blame History

用户无感升级方案 - 快速参考

核心原则: 新旧字段并存,双写双读,逐步迁移,零数据丢失


🎯 核心策略

1. 双写双读原则

所有字段变更都遵循这个原则:

// ✅ 双写:同时更新新旧字段
await prisma.user.update({
  where: { id: userId },
  data: {
    department: departmentName,    // 旧字段(保持兼容)
    departmentId: departmentId,    // 新字段(逐步迁移)
  }
});

// ✅ 双读:优先使用新字段,回退到旧字段
const department = user.departmentRef?.name || user.department;

2. 数据迁移流程

1. Schema变更(添加新字段,保留旧字段)
   ↓
2. 运行迁移脚本(同步数据到新字段)
   ↓
3. 更新代码(双写双读)
   ↓
4. 验证数据一致性
   ↓
5. 逐步迁移前端(可选)
   ↓
6. 保留旧字段至少3个月

📋 各阶段实施要点

阶段一:用户状态管理

Schema变更:

model User {
  // ✅ 新增字段
  status            String      @default("pending")
  lastLoginAt       DateTime?
  lastLoginIp       String?
  loginCount        Int         @default(0)
  archivedAt        DateTime?
  
  // ✅ 保留旧字段
  isActive          Boolean     @default(false)
}

迁移脚本: scripts/migrate-user-status-safe.ts

代码更新示例:

// 登录API:记录审计字段
await prisma.user.update({
  where: { id: userId },
  data: {
    lastLoginAt: new Date(),
    lastLoginIp: ipAddress,
    loginCount: { increment: 1 },
    status: 'active',      // 新字段
    isActive: true,        // 旧字段(保持兼容)
  }
});

// 用户激活:双写
await prisma.user.update({
  where: { id: userId },
  data: {
    status: 'active',      // 新字段
    isActive: true,        // 旧字段(保持兼容)
  }
});

阶段二:部门外键关联

Schema变更:

model User {
  // ✅ 保留旧字段
  department        String      @default("")
  
  // ✅ 新增字段
  departmentId      String?
  departmentRef     Department? @relation("UserDepartment", ...)
}

model Department {
  id        String   @id @default(uuid())
  name      String   @unique
  
  // ✅ 新增关联
  users     User[]   @relation("UserDepartment")
}

迁移脚本: scripts/migrate-department-to-id-safe.ts

代码更新示例:

// 更新用户部门:双写
async function updateUserDepartment(userId: string, departmentName: string) {
  const dept = await prisma.department.findUnique({
    where: { name: departmentName }
  });
  
  await prisma.user.update({
    where: { id: userId },
    data: {
      department: departmentName,    // 旧字段(保持兼容)
      departmentId: dept.id,          // 新字段(逐步迁移)
    }
  });
}

// 获取用户部门:双读
async function getUserDepartment(userId: string): Promise<string | null> {
  const user = await prisma.user.findUnique({
    where: { id: userId },
    include: { departmentRef: true }
  });
  
  // 优先使用新字段,回退到旧字段
  return user.departmentRef?.name || user.department || null;
}

🔒 安全保障

1. 数据库备份

每个阶段前必须备份:

# PostgreSQL 备份
pg_dump -h 192.168.58.20 -U username -d lobechat > backup_$(date +%Y%m%d_%H%M%S).sql

2. 迁移验证

每个阶段后运行验证:

# 验证所有阶段
npx tsx scripts/verify-migration.ts

# 验证特定阶段
npx tsx scripts/verify-migration.ts 1  # 阶段一
npx tsx scripts/verify-migration.ts 2  # 阶段二

3. 回滚方案

如果出现问题,可以回滚:

  • 阶段一:将 status 同步回 isActive
  • 阶段二:删除 departmentId 字段(保留 department)
  • 阶段三:删除层级字段(保留 name)
  • 阶段四:将 SYSTEM_ADMIN 改回 ADMIN

📅 实施时间表

阶段 内容 预计时间 风险
阶段一 用户状态管理 1周 🟢 低
阶段二 部门外键关联 1-2周 🟡 中
阶段三 部门层级结构 1-2周 🟡 中
阶段四 角色枚举扩展 1周 🟡 中

总计: 4-6周


✅ 检查清单

每个阶段都需要:

  • 数据库备份完成
  • Schema变更已应用(npx prisma db push)
  • 迁移脚本已执行
  • 数据验证通过(npx tsx scripts/verify-migration.ts)
  • 代码已更新(双写双读)
  • 功能测试通过
  • 性能测试通过

🚨 注意事项

绝对不能做的:

  1. ❌ 删除旧字段 - 至少保留3个月
  2. ❌ 修改现有字段类型 - 只添加新字段
  3. ❌ 强制迁移 - 使用双写双读,逐步迁移

必须做的:

  1. ✅ 备份数据库 - 每个阶段前必须备份
  2. ✅ 验证数据 - 每个阶段后必须验证
  3. ✅ 双写双读 - 所有代码更新都要双写双读
  4. ✅ 保留旧字段 - 至少保留3个月

📝 快速命令参考

# 1. 备份数据库
pg_dump -h 192.168.58.20 -U username -d lobechat > backup.sql

# 2. 更新 Schema
npx prisma db push

# 3. 运行迁移脚本
npx tsx scripts/migrate-user-status-safe.ts          # 阶段一
npx tsx scripts/migrate-department-to-id-safe.ts    # 阶段二

# 4. 验证迁移结果
npx tsx scripts/verify-migration.ts

# 5. 生成 Prisma Client
npx prisma generate

📚 相关文档


文档版本: 1.0
创建日期: 2025-12-27
最后更新: 2025-12-27