refactor(frontend): 重命名前端项目为 gym-manage-web
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const { execSync } = require('child_process');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const E2E_DIR = path.join(__dirname, 'e2e');
|
||||
const RESULTS_FILE = path.join(__dirname, 'e2e-performance-results.json');
|
||||
|
||||
function measureE2ETestPerformance() {
|
||||
console.log('🚀 开始E2E性能测试...\n');
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
const output = execSync('npm run test:e2e', {
|
||||
cwd: __dirname,
|
||||
encoding: 'utf8',
|
||||
stdio: 'pipe'
|
||||
});
|
||||
|
||||
const endTime = Date.now();
|
||||
const duration = (endTime - startTime) / 1000;
|
||||
|
||||
const results = {
|
||||
timestamp: new Date().toISOString(),
|
||||
duration: duration,
|
||||
durationFormatted: formatDuration(duration),
|
||||
success: true,
|
||||
message: 'E2E测试执行成功'
|
||||
};
|
||||
|
||||
saveResults(results);
|
||||
|
||||
console.log('\n✅ E2E测试执行成功!');
|
||||
console.log(`⏱️ 总耗时: ${results.durationFormatted}`);
|
||||
console.log(`📊 性能评估: ${evaluatePerformance(duration)}`);
|
||||
|
||||
return results;
|
||||
} catch (error) {
|
||||
const endTime = Date.now();
|
||||
const duration = (endTime - startTime) / 1000;
|
||||
|
||||
const results = {
|
||||
timestamp: new Date().toISOString(),
|
||||
duration: duration,
|
||||
durationFormatted: formatDuration(duration),
|
||||
success: false,
|
||||
message: error.message || 'E2E测试执行失败'
|
||||
};
|
||||
|
||||
saveResults(results);
|
||||
|
||||
console.log('\n❌ E2E测试执行失败!');
|
||||
console.log(`⏱️ 总耗时: ${results.durationFormatted}`);
|
||||
console.log(`📊 性能评估: ${evaluatePerformance(duration)}`);
|
||||
console.log(`💥 错误信息: ${error.message}`);
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
function formatDuration(seconds) {
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const remainingSeconds = Math.floor(seconds % 60);
|
||||
return `${minutes}分${remainingSeconds}秒`;
|
||||
}
|
||||
|
||||
function evaluatePerformance(duration) {
|
||||
if (duration < 60) {
|
||||
return '🟢 优秀 - 执行时间在1分钟以内';
|
||||
} else if (duration < 90) {
|
||||
return '🟡 良好 - 执行时间在1.5分钟以内';
|
||||
} else if (duration < 120) {
|
||||
return '🟠 一般 - 执行时间在2分钟以内';
|
||||
} else {
|
||||
return '🔴 需要优化 - 执行时间超过2分钟';
|
||||
}
|
||||
}
|
||||
|
||||
function saveResults(results) {
|
||||
const history = [];
|
||||
|
||||
if (fs.existsSync(RESULTS_FILE)) {
|
||||
const data = fs.readFileSync(RESULTS_FILE, 'utf8');
|
||||
try {
|
||||
history.push(...JSON.parse(data));
|
||||
} catch (e) {
|
||||
console.warn('⚠️ 无法解析历史结果文件');
|
||||
}
|
||||
}
|
||||
|
||||
history.push(results);
|
||||
|
||||
if (history.length > 10) {
|
||||
history.shift();
|
||||
}
|
||||
|
||||
fs.writeFileSync(RESULTS_FILE, JSON.stringify(history, null, 2));
|
||||
|
||||
console.log('\n📈 性能趋势分析:');
|
||||
analyzePerformanceTrend(history);
|
||||
}
|
||||
|
||||
function analyzePerformanceTrend(history) {
|
||||
if (history.length < 2) {
|
||||
console.log(' 需要更多测试数据来分析趋势');
|
||||
return;
|
||||
}
|
||||
|
||||
const successfulTests = history.filter(r => r.success);
|
||||
if (successfulTests.length < 2) {
|
||||
console.log(' 需要更多成功的测试数据来分析趋势');
|
||||
return;
|
||||
}
|
||||
|
||||
const durations = successfulTests.map(r => r.duration);
|
||||
const avgDuration = durations.reduce((a, b) => a + b, 0) / durations.length;
|
||||
const minDuration = Math.min(...durations);
|
||||
const maxDuration = Math.max(...durations);
|
||||
|
||||
console.log(` 平均执行时间: ${formatDuration(avgDuration)}`);
|
||||
console.log(` 最快执行时间: ${formatDuration(minDuration)}`);
|
||||
console.log(` 最慢执行时间: ${formatDuration(maxDuration)}`);
|
||||
|
||||
const recentTests = successfulTests.slice(-3);
|
||||
if (recentTests.length >= 2) {
|
||||
const recentAvg = recentTests.reduce((a, b) => a + b.duration, 0) / recentTests.length;
|
||||
const olderTests = successfulTests.slice(0, -3);
|
||||
if (olderTests.length > 0) {
|
||||
const olderAvg = olderTests.reduce((a, b) => a + b.duration, 0) / olderTests.length;
|
||||
const improvement = ((olderAvg - recentAvg) / olderAvg * 100).toFixed(1);
|
||||
if (improvement > 0) {
|
||||
console.log(` 📉 性能提升: ${improvement}%`);
|
||||
} else {
|
||||
console.log(` 📈 性能下降: ${Math.abs(improvement)}%`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
measureE2ETestPerformance();
|
||||
}
|
||||
|
||||
module.exports = { measureE2ETestPerformance };
|
||||
@@ -0,0 +1,337 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const API_BASE_URL = process.env.API_BASE_URL || 'http://localhost:8080';
|
||||
const RESULTS_FILE = path.join(__dirname, '../performance-test-results.json');
|
||||
|
||||
class PerformanceTester {
|
||||
constructor(baseUrl) {
|
||||
this.baseUrl = baseUrl;
|
||||
this.results = [];
|
||||
}
|
||||
|
||||
async testEndpoint(endpoint, method = 'GET', body = null) {
|
||||
const url = `${this.baseUrl}${endpoint}`;
|
||||
const startTime = Date.now();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const options = {
|
||||
method: method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
};
|
||||
|
||||
if (body) {
|
||||
options.headers['Content-Length'] = Buffer.byteLength(JSON.stringify(body));
|
||||
}
|
||||
|
||||
const protocol = url.startsWith('https') ? https : http;
|
||||
|
||||
const req = protocol.request(url, options, (res) => {
|
||||
let data = '';
|
||||
|
||||
res.on('data', (chunk) => {
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
const endTime = Date.now();
|
||||
const duration = endTime - startTime;
|
||||
|
||||
resolve({
|
||||
endpoint,
|
||||
method,
|
||||
statusCode: res.statusCode,
|
||||
duration,
|
||||
success: res.statusCode >= 200 && res.statusCode < 300,
|
||||
dataSize: data.length
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (error) => {
|
||||
const endTime = Date.now();
|
||||
const duration = endTime - startTime;
|
||||
|
||||
resolve({
|
||||
endpoint,
|
||||
method,
|
||||
statusCode: 0,
|
||||
duration,
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
});
|
||||
|
||||
if (body) {
|
||||
req.write(JSON.stringify(body));
|
||||
}
|
||||
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
async runLoadTest(endpoint, concurrentRequests = 10, totalRequests = 100) {
|
||||
console.log(`\n📊 开始负载测试: ${endpoint}`);
|
||||
console.log(` 并发数: ${concurrentRequests}`);
|
||||
console.log(` 总请求数: ${totalRequests}\n`);
|
||||
|
||||
const results = [];
|
||||
const startTime = Date.now();
|
||||
|
||||
for (let i = 0; i < totalRequests; i += concurrentRequests) {
|
||||
const batch = Math.min(concurrentRequests, totalRequests - i);
|
||||
const promises = [];
|
||||
|
||||
for (let j = 0; j < batch; j++) {
|
||||
promises.push(this.testEndpoint(endpoint));
|
||||
}
|
||||
|
||||
const batchResults = await Promise.all(promises);
|
||||
results.push(...batchResults);
|
||||
|
||||
console.log(` 进度: ${Math.min(i + batch, totalRequests)}/${totalRequests} 请求已完成`);
|
||||
}
|
||||
|
||||
const endTime = Date.now();
|
||||
const totalDuration = endTime - startTime;
|
||||
|
||||
const successfulRequests = results.filter(r => r.success);
|
||||
const failedRequests = results.filter(r => !r.success);
|
||||
|
||||
const durations = successfulRequests.map(r => r.duration);
|
||||
const avgDuration = durations.length > 0 ? durations.reduce((a, b) => a + b, 0) / durations.length : 0;
|
||||
const minDuration = durations.length > 0 ? Math.min(...durations) : 0;
|
||||
const maxDuration = durations.length > 0 ? Math.max(...durations) : 0;
|
||||
const p95Duration = this.calculatePercentile(durations, 95);
|
||||
const p99Duration = this.calculatePercentile(durations, 99);
|
||||
|
||||
const throughput = (successfulRequests.length / totalDuration) * 1000;
|
||||
|
||||
return {
|
||||
endpoint,
|
||||
concurrentRequests,
|
||||
totalRequests,
|
||||
successfulRequests: successfulRequests.length,
|
||||
failedRequests: failedRequests.length,
|
||||
successRate: (successfulRequests.length / totalRequests * 100).toFixed(2),
|
||||
totalDuration,
|
||||
avgDuration,
|
||||
minDuration,
|
||||
maxDuration,
|
||||
p95Duration,
|
||||
p99Duration,
|
||||
throughput: throughput.toFixed(2),
|
||||
results
|
||||
};
|
||||
}
|
||||
|
||||
calculatePercentile(values, percentile) {
|
||||
if (values.length === 0) return 0;
|
||||
|
||||
const sorted = [...values].sort((a, b) => a - b);
|
||||
const index = Math.ceil((percentile / 100) * sorted.length) - 1;
|
||||
return sorted[Math.max(0, index)];
|
||||
}
|
||||
|
||||
async runPerformanceTests() {
|
||||
console.log('🚀 开始性能测试...\n');
|
||||
|
||||
const endpoints = [
|
||||
{ path: '/api/auth/login', method: 'POST', body: { username: 'admin', password: 'admin123' } },
|
||||
{ path: '/api/users', method: 'GET' },
|
||||
{ path: '/api/roles', method: 'GET' },
|
||||
{ path: '/api/menus', method: 'GET' },
|
||||
{ path: '/api/dicts', method: 'GET' },
|
||||
];
|
||||
|
||||
for (const endpoint of endpoints) {
|
||||
console.log(`\n📡 测试端点: ${endpoint.method} ${endpoint.path}`);
|
||||
|
||||
const results = [];
|
||||
const iterations = 10;
|
||||
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
const result = await this.testEndpoint(endpoint.path, endpoint.method, endpoint.body);
|
||||
results.push(result);
|
||||
console.log(` ${i + 1}/${iterations}: ${result.duration}ms - ${result.success ? '✅' : '❌'}`);
|
||||
}
|
||||
|
||||
const durations = results.filter(r => r.success).map(r => r.duration);
|
||||
const avgDuration = durations.length > 0 ? durations.reduce((a, b) => a + b, 0) / durations.length : 0;
|
||||
const minDuration = durations.length > 0 ? Math.min(...durations) : 0;
|
||||
const maxDuration = durations.length > 0 ? Math.max(...durations) : 0;
|
||||
const successRate = (results.filter(r => r.success).length / results.length * 100).toFixed(2);
|
||||
|
||||
this.results.push({
|
||||
endpoint: endpoint.path,
|
||||
method: endpoint.method,
|
||||
avgDuration,
|
||||
minDuration,
|
||||
maxDuration,
|
||||
successRate,
|
||||
status: this.evaluatePerformance(avgDuration)
|
||||
});
|
||||
}
|
||||
|
||||
this.saveResults();
|
||||
this.printSummary();
|
||||
}
|
||||
|
||||
evaluatePerformance(avgDuration) {
|
||||
if (avgDuration < 100) {
|
||||
return '🟢 优秀';
|
||||
} else if (avgDuration < 300) {
|
||||
return '🟡 良好';
|
||||
} else if (avgDuration < 500) {
|
||||
return '🟠 一般';
|
||||
} else {
|
||||
return '🔴 需要优化';
|
||||
}
|
||||
}
|
||||
|
||||
saveResults() {
|
||||
const timestamp = new Date().toISOString();
|
||||
const data = {
|
||||
timestamp,
|
||||
performanceTests: this.results,
|
||||
loadTests: this.loadTestResults
|
||||
};
|
||||
|
||||
const history = [];
|
||||
if (fs.existsSync(RESULTS_FILE)) {
|
||||
try {
|
||||
history.push(...JSON.parse(fs.readFileSync(RESULTS_FILE, 'utf8')));
|
||||
} catch (e) {
|
||||
console.warn('⚠️ 无法解析历史结果文件');
|
||||
}
|
||||
}
|
||||
|
||||
history.push(data);
|
||||
|
||||
if (history.length > 20) {
|
||||
history.shift();
|
||||
}
|
||||
|
||||
fs.writeFileSync(RESULTS_FILE, JSON.stringify(history, null, 2));
|
||||
}
|
||||
|
||||
printSummary() {
|
||||
console.log('\n📊 性能测试摘要:');
|
||||
console.log('═══════════════════════════════════════');
|
||||
|
||||
const table = this.results.map(r => ({
|
||||
端点: r.endpoint,
|
||||
方法: r.method,
|
||||
平均: `${r.avgDuration.toFixed(0)}ms`,
|
||||
最小: `${r.minDuration}ms`,
|
||||
最大: `${r.maxDuration}ms`,
|
||||
成功率: `${r.successRate}%`,
|
||||
状态: r.status
|
||||
}));
|
||||
|
||||
console.table(table);
|
||||
|
||||
if (this.loadTestResults) {
|
||||
console.log('\n📈 负载测试摘要:');
|
||||
console.log('═══════════════════════════════════════');
|
||||
|
||||
const loadTable = this.loadTestResults.map(r => ({
|
||||
端点: r.endpoint,
|
||||
总请求: r.totalRequests,
|
||||
成功: r.successfulRequests,
|
||||
失败: r.failedRequests,
|
||||
成功率: `${r.successRate}%`,
|
||||
平均响应: `${r.avgDuration.toFixed(0)}ms`,
|
||||
P95: `${r.p95Duration.toFixed(0)}ms`,
|
||||
P99: `${r.p99Duration.toFixed(0)}ms`,
|
||||
吞吐量: `${r.throughput} req/s`
|
||||
}));
|
||||
|
||||
console.table(loadTable);
|
||||
}
|
||||
|
||||
console.log('\n💡 性能优化建议:');
|
||||
this.printRecommendations();
|
||||
}
|
||||
|
||||
printRecommendations() {
|
||||
const slowEndpoints = this.results.filter(r => r.avgDuration > 300);
|
||||
if (slowEndpoints.length > 0) {
|
||||
console.log(' ⚠️ 以下端点响应时间较长,建议优化:');
|
||||
slowEndpoints.forEach(r => {
|
||||
console.log(` - ${r.endpoint}: ${r.avgDuration.toFixed(0)}ms`);
|
||||
});
|
||||
}
|
||||
|
||||
const lowSuccessRate = this.results.filter(r => parseFloat(r.successRate) < 95);
|
||||
if (lowSuccessRate.length > 0) {
|
||||
console.log(' ⚠️ 以下端点成功率较低,建议检查:');
|
||||
lowSuccessRate.forEach(r => {
|
||||
console.log(` - ${r.endpoint}: ${r.successRate}%`);
|
||||
});
|
||||
}
|
||||
|
||||
if (slowEndpoints.length === 0 && lowSuccessRate.length === 0) {
|
||||
console.log(' ✅ 所有端点性能良好,无需优化');
|
||||
}
|
||||
}
|
||||
|
||||
async runLoadTests() {
|
||||
console.log('\n📊 开始负载测试...\n');
|
||||
|
||||
const endpoints = ['/api/users', '/api/roles', '/api/menus'];
|
||||
this.loadTestResults = [];
|
||||
|
||||
for (const endpoint of endpoints) {
|
||||
const result = await this.runLoadTest(endpoint, 10, 100);
|
||||
this.loadTestResults.push(result);
|
||||
|
||||
console.log(`\n📈 ${endpoint} 负载测试结果:`);
|
||||
console.log(` 成功率: ${result.successRate}%`);
|
||||
console.log(` 平均响应时间: ${result.avgDuration.toFixed(0)}ms`);
|
||||
console.log(` P95响应时间: ${result.p95Duration.toFixed(0)}ms`);
|
||||
console.log(` P99响应时间: ${result.p99Duration.toFixed(0)}ms`);
|
||||
console.log(` 吞吐量: ${result.throughput} req/s`);
|
||||
}
|
||||
|
||||
this.saveResults();
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const tester = new PerformanceTester(API_BASE_URL);
|
||||
|
||||
const command = process.argv[2];
|
||||
|
||||
switch (command) {
|
||||
case 'performance':
|
||||
await tester.runPerformanceTests();
|
||||
break;
|
||||
case 'load':
|
||||
await tester.runLoadTests();
|
||||
break;
|
||||
case 'all':
|
||||
await tester.runPerformanceTests();
|
||||
await tester.runLoadTests();
|
||||
break;
|
||||
default:
|
||||
console.log('使用方法:');
|
||||
console.log(' node scripts/performance-test.js performance - 运行性能测试');
|
||||
console.log(' node scripts/performance-test.js load - 运行负载测试');
|
||||
console.log(' node scripts/performance-test.js all - 运行所有测试');
|
||||
console.log('\n环境变量:');
|
||||
console.log(' API_BASE_URL - API基础URL (默认: http://localhost:8080)');
|
||||
}
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
|
||||
module.exports = PerformanceTester;
|
||||
Executable
+46
@@ -0,0 +1,46 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Playwright E2E Headless 模式测试脚本
|
||||
# 用于完整的端到端测试和UAT测试
|
||||
|
||||
set -e
|
||||
|
||||
echo "========================================"
|
||||
echo "Playwright E2E Headless 测试脚本"
|
||||
echo "========================================"
|
||||
|
||||
# 设置工作目录
|
||||
cd /Users/zhangxiang/Codes/Novalon/novalon-manage-system/novalon-manage-web
|
||||
|
||||
# 检查前端开发服务器
|
||||
echo "🔍 检查前端开发服务器..."
|
||||
if ! lsof -ti:3001 > /dev/null; then
|
||||
echo "❌ 前端开发服务器未运行,启动中..."
|
||||
npm run dev > /tmp/frontend.log 2>&1 &
|
||||
echo "✅ 前端开发服务器已启动(PID: $!)"
|
||||
sleep 10
|
||||
fi
|
||||
|
||||
# 检查后端服务
|
||||
echo "🔍 检查后端服务..."
|
||||
if ! lsof -ti:8080 > /dev/null; then
|
||||
echo "❌ 后端服务未运行,启动中..."
|
||||
cd /Users/zhangxiang/Codes/Novalon/novalon-manage-system/novalon-manage-api
|
||||
mvn spring-boot:run -pl manage-gateway > /tmp/gateway.log 2>&1 &
|
||||
echo "✅ 后端服务已启动(PID: $!)"
|
||||
sleep 30
|
||||
fi
|
||||
|
||||
# 回到前端目录
|
||||
cd /Users/zhangxiang/Codes/Novalon/novalon-manage-system/novalon-manage-web
|
||||
|
||||
# 运行 E2E 测试(Headless 模式)
|
||||
echo "🚀 运行 E2E 测试(Headless 模式)..."
|
||||
PLAYWRIGHT_HEADLESS=true npx playwright test --project=chromium --reporter=list
|
||||
|
||||
# 生成测试报告
|
||||
echo "📊 生成测试报告..."
|
||||
npx playwright show-report playwright-report
|
||||
|
||||
echo "✅ E2E Headless 测试完成!"
|
||||
echo "_report: playwright-report/index.html"
|
||||
Reference in New Issue
Block a user