gongxiangp2/scripts/migrate-user-status-safe.ts
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

145 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 安全迁移脚本:用户状态管理
*
* 功能:
* 1. 将 isActive 同步到 status 字段
* 2. 确保数据一致性
* 3. 可以重复执行(幂等性)
*
* 使用方法:
* npx tsx scripts/migrate-user-status-safe.ts
*
* 注意:
* - 执行前请备份数据库
* - 可以重复执行,不会重复迁移
*/
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
console.log('🚀 开始用户状态迁移...\n');
try {
// 1. 统计当前状态
const totalUsers = await prisma.user.count();
const activeUsers = await prisma.user.count({
where: { isActive: true }
});
const pendingUsers = await prisma.user.count({
where: { isActive: false }
});
console.log('📊 当前用户统计:');
console.log(` 总用户数: ${totalUsers}`);
console.log(` 已激活 (isActive=true): ${activeUsers}`);
console.log(` 未激活 (isActive=false): ${pendingUsers}\n`);
// 2. 检查 status 字段是否存在(如果不存在,说明 Schema 还未更新)
try {
await prisma.user.findFirst({
select: { status: true }
});
} catch (error: any) {
if (error.message?.includes('Unknown arg `status`')) {
console.error('❌ 错误: status 字段不存在,请先更新 Schema');
console.error(' 请运行: npx prisma db push');
process.exit(1);
}
throw error;
}
// 3. 迁移 isActive=true 的用户到 status='active'
const activeResult = await prisma.user.updateMany({
where: {
isActive: true,
status: { not: 'active' } // 只更新非 active 状态的用户
},
data: {
status: 'active'
}
});
console.log(`✅ 已激活用户迁移: ${activeResult.count} 个用户设置为 status='active'`);
// 4. 确保 isActive=false 的用户 status='pending'(默认值,但显式设置)
const pendingResult = await prisma.user.updateMany({
where: {
isActive: false,
status: { not: 'pending' }
},
data: {
status: 'pending'
}
});
console.log(`✅ 未激活用户迁移: ${pendingResult.count} 个用户设置为 status='pending'`);
// 5. 验证数据一致性
console.log('\n🔍 验证数据一致性...');
const inconsistentActive = await prisma.user.count({
where: {
isActive: true,
status: { not: 'active' }
}
});
const inconsistentPending = await prisma.user.count({
where: {
isActive: false,
status: { not: 'pending' }
}
});
if (inconsistentActive > 0 || inconsistentPending > 0) {
console.warn(`⚠️ 发现不一致数据:`);
console.warn(` isActive=true 但 status!='active': ${inconsistentActive}`);
console.warn(` isActive=false 但 status!='pending': ${inconsistentPending}`);
} else {
console.log('✅ 数据一致性验证通过');
}
// 6. 最终统计
console.log('\n📊 迁移后统计:');
const finalActive = await prisma.user.count({
where: { status: 'active' }
});
const finalPending = await prisma.user.count({
where: { status: 'pending' }
});
const finalSuspended = await prisma.user.count({
where: { status: 'suspended' }
});
const finalArchived = await prisma.user.count({
where: { status: 'archived' }
});
console.log(` status='active': ${finalActive}`);
console.log(` status='pending': ${finalPending}`);
console.log(` status='suspended': ${finalSuspended}`);
console.log(` status='archived': ${finalArchived}`);
console.log('\n✅ 用户状态迁移完成!');
console.log('\n📝 下一步:');
console.log(' 1. 更新登录API,记录审计字段(lastLoginAt, lastLoginIp, loginCount)');
console.log(' 2. 更新用户管理API,实现双写(同时更新 isActive 和 status)');
console.log(' 3. 逐步迁移前端代码到使用 status 字段');
} catch (error) {
console.error('❌ 迁移失败:', error);
throw error;
}
}
main()
.catch((error) => {
console.error('❌ 脚本执行失败:', error);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});