- 创建统一的下载错误处理模块 (lib/download-errors.ts) - 实现13种错误类型分类和中文错误消息 - 后端统一使用 createDownloadError 创建标准化错误响应 - 前端统一使用 parseDownloadError 解析错误并显示 - 修复正式系统数据库Schema,添加产品全案相关字段和表 - 执行数据库迁移:添加 task_no, task_status, task_fetched_at 字段 - 创建 material_assets, api_keys, api_call_logs 表
5.7 KiB
5.7 KiB
用户无感升级方案 - 快速参考
核心原则: 新旧字段并存,双写双读,逐步迁移,零数据丢失
🎯 核心策略
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) - 代码已更新(双写双读)
- 功能测试通过
- 性能测试通过
🚨 注意事项
绝对不能做的:
- ❌ 删除旧字段 - 至少保留3个月
- ❌ 修改现有字段类型 - 只添加新字段
- ❌ 强制迁移 - 使用双写双读,逐步迁移
必须做的:
- ✅ 备份数据库 - 每个阶段前必须备份
- ✅ 验证数据 - 每个阶段后必须验证
- ✅ 双写双读 - 所有代码更新都要双写双读
- ✅ 保留旧字段 - 至少保留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