You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
209 lines
6.6 KiB
209 lines
6.6 KiB
#!/usr/bin/env node
|
|
/**
|
|
* Docker 部署验证脚本
|
|
* 检查所有服务是否正常运行
|
|
*/
|
|
|
|
const http = require('http');
|
|
const { exec } = require('child_process');
|
|
const util = require('util');
|
|
const execPromise = util.promisify(exec);
|
|
|
|
const DELAY = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
|
|
async function checkDocker() {
|
|
console.log('[1] 检查 Docker 服务...');
|
|
try {
|
|
await execPromise('docker ps');
|
|
console.log(' ✓ Docker 运行正常');
|
|
return true;
|
|
} catch (error) {
|
|
console.log(' ✗ Docker 未运行');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async function checkContainers() {
|
|
console.log('[2] 检查容器状态...');
|
|
try {
|
|
const { stdout } = await execPromise('docker-compose ps');
|
|
console.log(stdout);
|
|
|
|
if (stdout.includes('aguzhitou-mysql') && stdout.includes('aguzhitou-redis') && stdout.includes('aguzhitou-app')) {
|
|
console.log(' ✓ 所有容器已启动');
|
|
return true;
|
|
} else {
|
|
console.log(' ✗ 部分容器未启动');
|
|
return false;
|
|
}
|
|
} catch (error) {
|
|
console.log(' ✗ 无法获取容器状态');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async function checkAPI() {
|
|
console.log('[3] 检查 API 服务...');
|
|
return new Promise((resolve) => {
|
|
const req = http.get('http://localhost:3000/api/v1/health', (res) => {
|
|
let data = '';
|
|
res.on('data', chunk => data += chunk);
|
|
res.on('end', () => {
|
|
try {
|
|
const result = JSON.parse(data);
|
|
if (result.code === 200) {
|
|
console.log(' ✓ API 服务正常');
|
|
console.log(` ✓ 服务时间: ${result.data.timestamp}`);
|
|
resolve(true);
|
|
} else {
|
|
console.log(' ✗ API 返回异常');
|
|
resolve(false);
|
|
}
|
|
} catch (e) {
|
|
console.log(' ✗ API 响应解析失败');
|
|
resolve(false);
|
|
}
|
|
});
|
|
});
|
|
|
|
req.on('error', (err) => {
|
|
console.log(' ✗ 无法连接 API');
|
|
resolve(false);
|
|
});
|
|
|
|
req.setTimeout(5000, () => {
|
|
console.log(' ✗ API 连接超时');
|
|
req.destroy();
|
|
resolve(false);
|
|
});
|
|
});
|
|
}
|
|
|
|
async function checkMySQL() {
|
|
console.log('[4] 检查 MySQL 数据库...');
|
|
try {
|
|
const { stdout } = await execPromise(
|
|
'docker-compose exec -T mysql mysql -u root -p1qazse42W3 -e "SELECT COUNT(*) as tables FROM information_schema.tables WHERE table_schema=\'aguzhitou\';"'
|
|
);
|
|
|
|
const match = stdout.match(/(\d+)/);
|
|
if (match && parseInt(match[1]) >= 12) {
|
|
console.log(` ✓ MySQL 数据库正常 (${match[1]} 张表)`);
|
|
return true;
|
|
} else {
|
|
console.log(' ✗ 数据库表数量异常');
|
|
return false;
|
|
}
|
|
} catch (error) {
|
|
console.log(' ✗ 无法连接 MySQL');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async function checkPartitions() {
|
|
console.log('[5] 检查数据库分区...');
|
|
try {
|
|
const { stdout } = await execPromise(
|
|
'docker-compose exec -T mysql mysql -u root -p1qazse42W3 -e "SELECT COUNT(DISTINCT table_name) as tables, COUNT(*) as partitions FROM information_schema.partitions WHERE table_schema=\'aguzhitou\' AND partition_name IS NOT NULL;"'
|
|
);
|
|
|
|
const match = stdout.match(/(\d+)\s*\|\s*(\d+)/);
|
|
if (match) {
|
|
console.log(` ✓ 分区表: ${match[1]} 张`);
|
|
console.log(` ✓ 总分区: ${match[2]} 个`);
|
|
return true;
|
|
}
|
|
return false;
|
|
} catch (error) {
|
|
console.log(' ✗ 无法获取分区信息');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async function checkRedis() {
|
|
console.log('[6] 检查 Redis 缓存...');
|
|
try {
|
|
const { stdout } = await execPromise('docker-compose exec -T redis redis-cli ping');
|
|
if (stdout.includes('PONG')) {
|
|
console.log(' ✓ Redis 运行正常');
|
|
return true;
|
|
}
|
|
console.log(' ✗ Redis 响应异常');
|
|
return false;
|
|
} catch (error) {
|
|
console.log(' ✗ 无法连接 Redis');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async function testAPIEndpoints() {
|
|
console.log('[7] 测试 API 端点...');
|
|
const endpoints = [
|
|
{ path: '/api/v1/market/indices', name: '市场指数' },
|
|
{ path: '/api/v1/sectors', name: '版块列表' },
|
|
{ path: '/api/v1/stocks/search?keyword=茅台', name: '股票搜索' }
|
|
];
|
|
|
|
let success = 0;
|
|
for (const endpoint of endpoints) {
|
|
try {
|
|
const result = await new Promise((resolve) => {
|
|
http.get(`http://localhost:3000${endpoint.path}`, (res) => {
|
|
resolve(res.statusCode === 200);
|
|
}).on('error', () => resolve(false));
|
|
});
|
|
|
|
if (result) {
|
|
console.log(` ✓ ${endpoint.name}`);
|
|
success++;
|
|
} else {
|
|
console.log(` ✗ ${endpoint.name}`);
|
|
}
|
|
} catch (e) {
|
|
console.log(` ✗ ${endpoint.name}`);
|
|
}
|
|
}
|
|
|
|
return success === endpoints.length;
|
|
}
|
|
|
|
async function main() {
|
|
console.log('='.repeat(60));
|
|
console.log('A股智投分析平台 - Docker 部署验证');
|
|
console.log('='.repeat(60));
|
|
console.log();
|
|
|
|
const results = {
|
|
docker: await checkDocker(),
|
|
containers: await checkContainers(),
|
|
api: await checkAPI(),
|
|
mysql: await checkMySQL(),
|
|
partitions: await checkPartitions(),
|
|
redis: await checkRedis(),
|
|
endpoints: await testAPIEndpoints()
|
|
};
|
|
|
|
console.log();
|
|
console.log('='.repeat(60));
|
|
|
|
const allPassed = Object.values(results).every(r => r === true);
|
|
|
|
if (allPassed) {
|
|
console.log('✅ 所有检查通过!部署成功!');
|
|
console.log('='.repeat(60));
|
|
console.log();
|
|
console.log('访问地址:');
|
|
console.log(' • API 文档: http://localhost:3000/api/v1/health');
|
|
console.log(' • MySQL: localhost:3306 (root/1qazse42W3)');
|
|
console.log(' • Redis: localhost:6379');
|
|
console.log();
|
|
} else {
|
|
console.log('❌ 部分检查未通过,请查看日志:');
|
|
console.log(' docker-compose logs');
|
|
console.log('='.repeat(60));
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
main().catch(console.error);
|