- 创建统一的下载错误处理模块 (lib/download-errors.ts) - 实现13种错误类型分类和中文错误消息 - 后端统一使用 createDownloadError 创建标准化错误响应 - 前端统一使用 parseDownloadError 解析错误并显示 - 修复正式系统数据库Schema,添加产品全案相关字段和表 - 执行数据库迁移:添加 task_no, task_status, task_fetched_at 字段 - 创建 material_assets, api_keys, api_call_logs 表
145 lines
4.2 KiB
TypeScript
145 lines
4.2 KiB
TypeScript
/**
|
||
* 安全迁移脚本:用户状态管理
|
||
*
|
||
* 功能:
|
||
* 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();
|
||
});
|
||
|