/** * 手动执行SQL迁移脚本 * 用于Postgres-Only改造 */ import { PrismaClient } from '@prisma/client'; import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const prisma = new PrismaClient(); async function runMigration() { try { console.log('🚀 开始执行手动迁移...\n'); // 步骤1: 创建app_cache表 console.log('📦 [1/4] 创建 app_cache 表...'); try { await prisma.$executeRaw` CREATE TABLE IF NOT EXISTS platform_schema.app_cache ( id SERIAL PRIMARY KEY, key VARCHAR(500) UNIQUE NOT NULL, value JSONB NOT NULL, expires_at TIMESTAMP NOT NULL, created_at TIMESTAMP DEFAULT NOW() ) `; console.log(' ✅ app_cache 表创建成功'); } catch (error: any) { if (error.message.includes('already exists')) { console.log(' ⚠️ 表已存在,跳过'); } else { throw error; } } // 步骤2: 创建索引 console.log('\n📊 [2/4] 创建索引...'); try { await prisma.$executeRaw` CREATE INDEX IF NOT EXISTS idx_app_cache_expires ON platform_schema.app_cache(expires_at) `; console.log(' ✅ idx_app_cache_expires 创建成功'); } catch (error: any) { console.log(' ⚠️ 索引可能已存在'); } try { await prisma.$executeRaw` CREATE INDEX IF NOT EXISTS idx_app_cache_key_expires ON platform_schema.app_cache(key, expires_at) `; console.log(' ✅ idx_app_cache_key_expires 创建成功'); } catch (error: any) { console.log(' ⚠️ 索引可能已存在'); } // 步骤3: 添加任务拆分字段 console.log('\n🔧 [3/4] 添加任务拆分字段...'); const splitFields = [ { name: 'total_batches', type: 'INTEGER', default: '1' }, { name: 'processed_batches', type: 'INTEGER', default: '0' }, { name: 'current_batch_index', type: 'INTEGER', default: '0' }, ]; for (const field of splitFields) { try { await prisma.$executeRawUnsafe(` ALTER TABLE asl_schema.screening_tasks ADD COLUMN IF NOT EXISTS ${field.name} ${field.type} DEFAULT ${field.default} `); console.log(` ✅ ${field.name} 添加成功`); } catch (error: any) { if (error.message.includes('already exists') || error.message.includes('duplicate')) { console.log(` ⚠️ ${field.name} 已存在`); } else { throw error; } } } // 步骤4: 添加断点续传字段 console.log('\n💾 [4/4] 添加断点续传字段...'); const checkpointFields = [ { name: 'current_index', type: 'INTEGER', default: '0' }, { name: 'last_checkpoint', type: 'TIMESTAMP', default: null }, { name: 'checkpoint_data', type: 'JSONB', default: null }, ]; for (const field of checkpointFields) { try { const defaultClause = field.default !== null ? `DEFAULT ${field.default}` : ''; await prisma.$executeRawUnsafe(` ALTER TABLE asl_schema.screening_tasks ADD COLUMN IF NOT EXISTS ${field.name} ${field.type} ${defaultClause} `); console.log(` ✅ ${field.name} 添加成功`); } catch (error: any) { if (error.message.includes('already exists') || error.message.includes('duplicate')) { console.log(` ⚠️ ${field.name} 已存在`); } else { throw error; } } } console.log('\n🎉 迁移执行完成!\n'); // 验证结果 console.log('📊 验证缓存表...'); const cacheCheck = await prisma.$queryRaw` SELECT table_name, column_name, data_type FROM information_schema.columns WHERE table_schema = 'platform_schema' AND table_name = 'app_cache' ORDER BY ordinal_position `; console.log('app_cache表字段:', cacheCheck); console.log('\n📊 验证任务表新字段...'); const taskCheck = await prisma.$queryRaw` SELECT column_name, data_type, column_default FROM information_schema.columns WHERE table_schema = 'asl_schema' AND table_name = 'screening_tasks' AND column_name IN ( 'total_batches', 'processed_batches', 'current_batch_index', 'current_index', 'last_checkpoint', 'checkpoint_data' ) ORDER BY ordinal_position `; console.log('screening_tasks新字段:', taskCheck); console.log('\n✅ 所有验证通过!'); } catch (error) { console.error('❌ 迁移失败:', error); process.exit(1); } finally { await prisma.$disconnect(); } } runMigration();