Summary: - Deploy REDCap 15.8.0 on ECS with Docker CE 26.1.3 - Configure RDS MySQL 8.0 database (redcap_prod) - Setup Nginx reverse proxy with HTTPS/SSL - Domain configured: https://redcap.xunzhengyixue.com/ Documentation: - Add ECS deployment guide - Add deployment info record - Update system status document (v4.5 -> v4.6) Status: REDCap production environment fully operational Co-authored-by: Cursor <cursoragent@cursor.com>
201 lines
6.7 KiB
JavaScript
201 lines
6.7 KiB
JavaScript
/**
|
||
* 对比本地数据库和RDS数据库的差异
|
||
*/
|
||
|
||
const { Client } = require('pg');
|
||
|
||
// 本地数据库配置
|
||
const localConfig = {
|
||
host: 'localhost',
|
||
port: 5432,
|
||
database: 'ai_clinical_research',
|
||
user: 'postgres',
|
||
password: 'postgres123',
|
||
};
|
||
|
||
// RDS数据库配置(测试环境)
|
||
const rdsConfig = {
|
||
host: 'pgm-2zex1m2y3r23hdn5xo.pg.rds.aliyuncs.com', // 外网地址
|
||
port: 5432,
|
||
database: 'ai_clinical_research_test',
|
||
user: 'airesearch',
|
||
password: 'Xibahe@fengzhibo117',
|
||
connectionTimeoutMillis: 30000,
|
||
};
|
||
|
||
async function getSchemas(client) {
|
||
const result = await client.query(`
|
||
SELECT schema_name
|
||
FROM information_schema.schemata
|
||
WHERE schema_name NOT IN ('pg_catalog', 'information_schema', 'pg_toast', 'pg_temp_1', 'pg_toast_temp_1')
|
||
ORDER BY schema_name
|
||
`);
|
||
return result.rows.map(r => r.schema_name);
|
||
}
|
||
|
||
async function getTables(client) {
|
||
const result = await client.query(`
|
||
SELECT table_schema, table_name
|
||
FROM information_schema.tables
|
||
WHERE table_type = 'BASE TABLE'
|
||
AND table_schema NOT IN ('pg_catalog', 'information_schema')
|
||
ORDER BY table_schema, table_name
|
||
`);
|
||
return result.rows.map(r => `${r.table_schema}.${r.table_name}`);
|
||
}
|
||
|
||
async function getExtensions(client) {
|
||
const result = await client.query(`
|
||
SELECT extname, extversion FROM pg_extension ORDER BY extname
|
||
`);
|
||
return result.rows;
|
||
}
|
||
|
||
async function main() {
|
||
console.log('=' .repeat(60));
|
||
console.log('数据库对比工具');
|
||
console.log('=' .repeat(60));
|
||
|
||
// 连接本地数据库
|
||
console.log('\n[1] 连接本地数据库...');
|
||
const localClient = new Client(localConfig);
|
||
try {
|
||
await localClient.connect();
|
||
console.log('✅ 本地数据库连接成功');
|
||
} catch (err) {
|
||
console.log('❌ 本地数据库连接失败:', err.message);
|
||
return;
|
||
}
|
||
|
||
// 连接RDS数据库
|
||
console.log('\n[2] 连接RDS数据库...');
|
||
const rdsClient = new Client(rdsConfig);
|
||
try {
|
||
await rdsClient.connect();
|
||
console.log('✅ RDS数据库连接成功');
|
||
} catch (err) {
|
||
console.log('❌ RDS数据库连接失败:', err.message);
|
||
await localClient.end();
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// 对比 Extensions
|
||
console.log('\n' + '=' .repeat(60));
|
||
console.log('📦 Extensions 对比');
|
||
console.log('=' .repeat(60));
|
||
|
||
const localExtensions = await getExtensions(localClient);
|
||
const rdsExtensions = await getExtensions(rdsClient);
|
||
|
||
console.log('\n本地 Extensions:');
|
||
localExtensions.forEach(e => console.log(` - ${e.extname} (${e.extversion})`));
|
||
|
||
console.log('\nRDS Extensions:');
|
||
rdsExtensions.forEach(e => console.log(` - ${e.extname} (${e.extversion})`));
|
||
|
||
// 对比 Schemas
|
||
console.log('\n' + '=' .repeat(60));
|
||
console.log('📁 Schema 对比');
|
||
console.log('=' .repeat(60));
|
||
|
||
const localSchemas = await getSchemas(localClient);
|
||
const rdsSchemas = await getSchemas(rdsClient);
|
||
|
||
const schemasOnlyInLocal = localSchemas.filter(s => !rdsSchemas.includes(s));
|
||
const schemasOnlyInRds = rdsSchemas.filter(s => !localSchemas.includes(s));
|
||
const commonSchemas = localSchemas.filter(s => rdsSchemas.includes(s));
|
||
|
||
console.log(`\n共同 Schemas (${commonSchemas.length}个):`);
|
||
commonSchemas.forEach(s => console.log(` ✅ ${s}`));
|
||
|
||
if (schemasOnlyInLocal.length > 0) {
|
||
console.log(`\n⚠️ 仅在本地存在的 Schemas (${schemasOnlyInLocal.length}个):`);
|
||
schemasOnlyInLocal.forEach(s => console.log(` 🔴 ${s}`));
|
||
}
|
||
|
||
if (schemasOnlyInRds.length > 0) {
|
||
console.log(`\n仅在RDS存在的 Schemas (${schemasOnlyInRds.length}个):`);
|
||
schemasOnlyInRds.forEach(s => console.log(` 🟡 ${s}`));
|
||
}
|
||
|
||
// 对比 Tables
|
||
console.log('\n' + '=' .repeat(60));
|
||
console.log('📊 Table 对比');
|
||
console.log('=' .repeat(60));
|
||
|
||
const localTables = await getTables(localClient);
|
||
const rdsTables = await getTables(rdsClient);
|
||
|
||
const tablesOnlyInLocal = localTables.filter(t => !rdsTables.includes(t));
|
||
const tablesOnlyInRds = rdsTables.filter(t => !localTables.includes(t));
|
||
|
||
console.log(`\n本地表总数: ${localTables.length}`);
|
||
console.log(`RDS表总数: ${rdsTables.length}`);
|
||
|
||
if (tablesOnlyInLocal.length > 0) {
|
||
console.log(`\n⚠️ 仅在本地存在的表 (${tablesOnlyInLocal.length}个) - 需要同步到RDS:`);
|
||
tablesOnlyInLocal.forEach(t => console.log(` 🔴 ${t}`));
|
||
} else {
|
||
console.log('\n✅ 没有仅在本地存在的表');
|
||
}
|
||
|
||
if (tablesOnlyInRds.length > 0) {
|
||
console.log(`\n仅在RDS存在的表 (${tablesOnlyInRds.length}个):`);
|
||
tablesOnlyInRds.forEach(t => console.log(` 🟡 ${t}`));
|
||
}
|
||
|
||
// 检查关键表的字段差异
|
||
console.log('\n' + '=' .repeat(60));
|
||
console.log('🔍 关键表字段检查');
|
||
console.log('=' .repeat(60));
|
||
|
||
// 检查 prompt_templates 表的 knowledge_config 字段
|
||
const checkColumn = async (client, schema, table, column) => {
|
||
const result = await client.query(`
|
||
SELECT column_name, data_type
|
||
FROM information_schema.columns
|
||
WHERE table_schema = $1 AND table_name = $2 AND column_name = $3
|
||
`, [schema, table, column]);
|
||
return result.rows.length > 0;
|
||
};
|
||
|
||
const localHasKnowledgeConfig = await checkColumn(localClient, 'capability_schema', 'prompt_templates', 'knowledge_config');
|
||
const rdsHasKnowledgeConfig = await checkColumn(rdsClient, 'capability_schema', 'prompt_templates', 'knowledge_config');
|
||
|
||
console.log('\ncapability_schema.prompt_templates.knowledge_config:');
|
||
console.log(` 本地: ${localHasKnowledgeConfig ? '✅ 存在' : '❌ 不存在'}`);
|
||
console.log(` RDS: ${rdsHasKnowledgeConfig ? '✅ 存在' : '❌ 不存在'}`);
|
||
|
||
// 总结
|
||
console.log('\n' + '=' .repeat(60));
|
||
console.log('📋 同步建议');
|
||
console.log('=' .repeat(60));
|
||
|
||
const needSync = schemasOnlyInLocal.length > 0 || tablesOnlyInLocal.length > 0 || (localHasKnowledgeConfig && !rdsHasKnowledgeConfig);
|
||
|
||
if (needSync) {
|
||
console.log('\n⚠️ 需要同步以下内容到RDS:');
|
||
if (schemasOnlyInLocal.length > 0) {
|
||
console.log(` - ${schemasOnlyInLocal.length} 个 Schema`);
|
||
}
|
||
if (tablesOnlyInLocal.length > 0) {
|
||
console.log(` - ${tablesOnlyInLocal.length} 个表`);
|
||
}
|
||
if (localHasKnowledgeConfig && !rdsHasKnowledgeConfig) {
|
||
console.log(' - prompt_templates.knowledge_config 字段');
|
||
}
|
||
console.log('\n建议执行: npx prisma migrate deploy');
|
||
} else {
|
||
console.log('\n✅ 数据库结构已同步,无需迁移');
|
||
}
|
||
|
||
} finally {
|
||
await localClient.end();
|
||
await rdsClient.end();
|
||
console.log('\n数据库连接已关闭');
|
||
}
|
||
}
|
||
|
||
main().catch(console.error);
|