update
All checks were successful
Deploy to Production / deploy (push) Successful in 23s

This commit is contained in:
silverpro89
2026-01-27 18:33:35 +07:00
parent 2c7b4675a7
commit 816794a861
14 changed files with 1827 additions and 3 deletions

161
scripts/pm2.js Normal file
View File

@@ -0,0 +1,161 @@
#!/usr/bin/env node
/**
* PM2 Management Script
* Helper script để quản lý các PM2 processes
*/
const { execSync } = require('child_process');
const path = require('path');
const ECOSYSTEM_FILE = path.join(__dirname, '..', 'ecosystem.config.js');
// Colors for console output
const colors = {
reset: '\x1b[0m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
red: '\x1b[31m',
};
const log = (message, color = 'reset') => {
console.log(`${colors[color]}${message}${colors.reset}`);
};
const exec = (command, options = {}) => {
try {
const result = execSync(command, {
stdio: 'inherit',
...options,
});
return result;
} catch (error) {
log(`Error executing command: ${command}`, 'red');
process.exit(1);
}
};
// Commands
const commands = {
start: () => {
log('\n🚀 Starting all processes...', 'blue');
exec(`pm2 start ${ECOSYSTEM_FILE}`);
log('✅ All processes started!', 'green');
},
stop: () => {
log('\n⏸ Stopping all processes...', 'yellow');
exec('pm2 stop ecosystem.config.js');
log('✅ All processes stopped!', 'green');
},
restart: () => {
log('\n🔄 Restarting all processes...', 'blue');
exec(`pm2 restart ${ECOSYSTEM_FILE}`);
log('✅ All processes restarted!', 'green');
},
delete: () => {
log('\n🗑 Deleting all processes...', 'yellow');
exec('pm2 delete ecosystem.config.js');
log('✅ All processes deleted!', 'green');
},
status: () => {
log('\n📊 Process status:', 'blue');
exec('pm2 list');
},
logs: (processName) => {
if (processName) {
log(`\n📋 Showing logs for ${processName}...`, 'blue');
exec(`pm2 logs ${processName}`);
} else {
log('\n📋 Showing all logs...', 'blue');
exec('pm2 logs');
}
},
monit: () => {
log('\n📈 Opening process monitor...', 'blue');
exec('pm2 monit');
},
flush: () => {
log('\n🧹 Flushing all logs...', 'yellow');
exec('pm2 flush');
log('✅ Logs flushed!', 'green');
},
save: () => {
log('\n💾 Saving PM2 process list...', 'blue');
exec('pm2 save');
log('✅ Process list saved!', 'green');
},
startup: () => {
log('\n⚙ Configuring PM2 to start on system boot...', 'blue');
exec('pm2 startup');
log('✅ Run the command above, then run: npm run pm2:save', 'yellow');
},
'start:api': () => {
log('\n🚀 Starting API server only...', 'blue');
exec(`pm2 start ${ECOSYSTEM_FILE} --only sena-api`);
log('✅ API server started!', 'green');
},
'start:workers': () => {
log('\n🚀 Starting all workers...', 'blue');
exec(`pm2 start ${ECOSYSTEM_FILE} --only worker-db-write,worker-lesson-fill,worker-process-data`);
log('✅ All workers started!', 'green');
},
help: () => {
console.log(`
╔════════════════════════════════════════════════════════════════╗
║ Sena PM2 Management Commands ║
╠════════════════════════════════════════════════════════════════╣
║ ║
║ npm run pm2:start - Start all processes ║
║ npm run pm2:stop - Stop all processes ║
║ npm run pm2:restart - Restart all processes ║
║ npm run pm2:delete - Delete all processes ║
║ npm run pm2:status - Show process status ║
║ npm run pm2:logs [name] - Show logs (optional: process) ║
║ npm run pm2:monit - Open process monitor ║
║ npm run pm2:flush - Flush all logs ║
║ npm run pm2:save - Save process list ║
║ npm run pm2:startup - Configure startup script ║
║ ║
║ npm run pm2:start:api - Start API server only ║
║ npm run pm2:start:workers - Start all workers only ║
║ ║
╠════════════════════════════════════════════════════════════════╣
║ Process Names: ║
║ - sena-api (Main API Server) ║
║ - worker-db-write (Database Write Worker) ║
║ - worker-lesson-fill (Lesson Data Fill Worker) ║
║ - worker-process-data (Process Data Worker) ║
╚════════════════════════════════════════════════════════════════╝
`);
},
};
// Parse command
const command = process.argv[2];
const arg = process.argv[3];
if (!command || command === 'help' || command === '--help' || command === '-h') {
commands.help();
process.exit(0);
}
if (commands[command]) {
commands[command](arg);
} else {
log(`Unknown command: ${command}`, 'red');
commands.help();
process.exit(1);
}

View File

@@ -0,0 +1,117 @@
/**
* Script để trigger Lesson Data Fill Worker
* Chạy script này để bắt đầu xử lý tất cả lessons và tạo process_data tasks
*/
const { Queue } = require('bullmq');
const { connectionOptions } = require('../config/bullmq');
/**
* Trigger worker để xử lý lessons
*/
async function triggerLessonDataFill(options = {}) {
const {
batchSize = 50,
totalLessons = null, // null = process all
filters = {}, // { lesson_content_type: 'vocabulary', chapter_id: 'xxx' }
} = options;
const queue = new Queue('lesson-data-fill', {
connection: connectionOptions,
prefix: process.env.BULLMQ_PREFIX || 'vcb',
});
try {
let offset = 0;
let jobsCreated = 0;
let hasMore = true;
console.log('🚀 Starting lesson data fill process...');
console.log('📋 Config:', { batchSize, totalLessons, filters });
while (hasMore) {
// Kiểm tra nếu đã đạt giới hạn
if (totalLessons && offset >= totalLessons) {
console.log(`✅ Reached limit of ${totalLessons} lessons`);
break;
}
// Tạo job cho batch này
const job = await queue.add(
`process-lessons-batch-${offset}`,
{
batchSize,
offset,
filters,
},
{
priority: 5,
jobId: `lesson-fill-${offset}-${Date.now()}`,
}
);
console.log(`✅ Created job ${job.id} for offset ${offset}`);
jobsCreated++;
// Chờ một chút để tránh quá tải
await new Promise(resolve => setTimeout(resolve, 100));
offset += batchSize;
// Nếu không có totalLessons, sẽ dừng sau 1 job để kiểm tra hasMore
if (!totalLessons) {
// Worker sẽ báo hasMore trong result
hasMore = false; // Tạm dừng, có thể chạy lại script để tiếp tục
}
}
console.log(`\n✅ Process completed!`);
console.log(`📊 Total jobs created: ${jobsCreated}`);
console.log(`📊 Lessons range: 0 to ${offset}`);
await queue.close();
process.exit(0);
} catch (error) {
console.error('❌ Error:', error.message);
await queue.close();
process.exit(1);
}
}
// Parse command line arguments
const args = process.argv.slice(2);
const options = {};
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (arg === '--batch-size' && args[i + 1]) {
options.batchSize = parseInt(args[i + 1]);
i++;
}
if (arg === '--total' && args[i + 1]) {
options.totalLessons = parseInt(args[i + 1]);
i++;
}
if (arg === '--type' && args[i + 1]) {
options.filters = options.filters || {};
options.filters.lesson_content_type = args[i + 1];
i++;
}
if (arg === '--chapter' && args[i + 1]) {
options.filters = options.filters || {};
options.filters.chapter_id = args[i + 1];
i++;
}
}
// Run
console.log('═══════════════════════════════════════════════════════');
console.log(' Lesson Data Fill Trigger Script');
console.log('═══════════════════════════════════════════════════════\n');
triggerLessonDataFill(options);