feat(iit): Complete REDCap production deployment on Alibaba Cloud ECS

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>
This commit is contained in:
2026-02-02 22:27:05 +08:00
parent 4c2c9b437b
commit 4b9b90ffb8
5 changed files with 1162 additions and 10 deletions

View File

@@ -0,0 +1,200 @@
/**
* 对比本地数据库和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);