feat(admin): 添加用户管理相关文件
添加用户管理视图、API和状态管理文件
This commit is contained in:
@@ -0,0 +1,285 @@
|
||||
import { exec, spawn, ChildProcess } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { logger } from '../utils/logger';
|
||||
import { TestEnvironment } from '../models/test-result';
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
interface ServiceConfig {
|
||||
name: string;
|
||||
command: string;
|
||||
cwd?: string;
|
||||
healthCheckUrl?: string;
|
||||
healthCheckCommand?: string;
|
||||
port?: number;
|
||||
startupTimeout: number;
|
||||
env?: Record<string, string>;
|
||||
}
|
||||
|
||||
export class TestEnvironmentManager {
|
||||
private processes: Map<string, ChildProcess> = new Map();
|
||||
private projectRoot: string;
|
||||
private testRoot: string;
|
||||
private environment: TestEnvironment | null = null;
|
||||
|
||||
constructor() {
|
||||
this.testRoot = process.cwd();
|
||||
this.projectRoot = path.dirname(this.testRoot);
|
||||
}
|
||||
|
||||
async prepare(): Promise<TestEnvironment> {
|
||||
logger.section('环境准备');
|
||||
|
||||
logger.info('检查系统环境...');
|
||||
await this.checkSystemRequirements();
|
||||
|
||||
logger.info('检查并安装依赖...');
|
||||
await this.installDependencies();
|
||||
|
||||
logger.info('收集环境信息...');
|
||||
this.environment = await this.collectEnvironmentInfo();
|
||||
|
||||
logger.info('环境准备完成', { environment: this.environment });
|
||||
return this.environment;
|
||||
}
|
||||
|
||||
private async checkSystemRequirements(): Promise<void> {
|
||||
const requirements = [
|
||||
{ name: 'Node.js', command: 'node --version', minVersion: '18.0.0' },
|
||||
{ name: 'npm', command: 'npm --version', minVersion: '9.0.0' }
|
||||
];
|
||||
|
||||
for (const req of requirements) {
|
||||
try {
|
||||
const { stdout } = await execAsync(req.command);
|
||||
const version = stdout.trim();
|
||||
logger.debug(`${req.name} 版本: ${version}`);
|
||||
} catch (error) {
|
||||
throw new Error(`${req.name} 未安装或版本过低,最低要求: ${req.minVersion}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async installDependencies(): Promise<void> {
|
||||
const nodeModulesPath = path.join(this.testRoot, 'node_modules');
|
||||
if (!fs.existsSync(nodeModulesPath)) {
|
||||
logger.info('安装 npm 依赖...');
|
||||
await execAsync('npm install', { cwd: this.testRoot });
|
||||
}
|
||||
|
||||
const playwrightPath = path.join(nodeModulesPath, '@playwright');
|
||||
if (!fs.existsSync(playwrightPath)) {
|
||||
logger.info('安装 Playwright 浏览器...');
|
||||
await execAsync('npx playwright install', { cwd: this.testRoot });
|
||||
}
|
||||
}
|
||||
|
||||
private async collectEnvironmentInfo(): Promise<TestEnvironment> {
|
||||
const { stdout: nodeVersion } = await execAsync('node --version');
|
||||
|
||||
let browserVersions: Record<string, string> = {};
|
||||
try {
|
||||
const { stdout } = await execAsync('npx playwright --version', { cwd: this.testRoot });
|
||||
browserVersions['playwright'] = stdout.trim();
|
||||
} catch {
|
||||
browserVersions['playwright'] = 'unknown';
|
||||
}
|
||||
|
||||
return {
|
||||
nodeVersion: nodeVersion.trim(),
|
||||
os: process.platform,
|
||||
browserVersions,
|
||||
apiBaseUrl: process.env.API_BASE_URL || 'http://localhost:8080',
|
||||
adminBaseUrl: process.env.ADMIN_BASE_URL || 'http://localhost:5174',
|
||||
uniappBaseUrl: process.env.UNIAPP_BASE_URL || 'http://localhost:8081',
|
||||
databaseType: process.env.DB_TYPE || 'h2',
|
||||
timestamp: new Date()
|
||||
};
|
||||
}
|
||||
|
||||
async startAPIService(): Promise<void> {
|
||||
logger.section('启动 API 服务');
|
||||
|
||||
const apiProjectPath = path.join(this.projectRoot, 'everything-is-suitable-api');
|
||||
|
||||
if (!fs.existsSync(apiProjectPath)) {
|
||||
logger.warn('API 项目目录不存在,跳过 API 服务启动');
|
||||
return;
|
||||
}
|
||||
|
||||
const serviceConfig: ServiceConfig = {
|
||||
name: 'api',
|
||||
command: 'mvn',
|
||||
cwd: apiProjectPath,
|
||||
port: 8080,
|
||||
startupTimeout: 120000,
|
||||
env: {
|
||||
SPRING_PROFILES_ACTIVE: 'test',
|
||||
SPRING_DATASOURCE_URL: 'jdbc:h2:mem:testdb',
|
||||
SPRING_DATASOURCE_DRIVER_CLASS_NAME: 'org.h2.Driver',
|
||||
SPRING_JPA_DATABASE_PLATFORM: 'org.hibernate.dialect.H2Dialect'
|
||||
}
|
||||
};
|
||||
|
||||
await this.startService(serviceConfig, ['spring-boot:run', '-Dspring-boot.run.profiles=test']);
|
||||
}
|
||||
|
||||
async startAdminService(): Promise<void> {
|
||||
logger.section('启动 Admin 服务');
|
||||
|
||||
const adminProjectPath = path.join(this.projectRoot, 'everything-is-suitable-admin');
|
||||
|
||||
if (!fs.existsSync(adminProjectPath)) {
|
||||
logger.warn('Admin 项目目录不存在,跳过 Admin 服务启动');
|
||||
return;
|
||||
}
|
||||
|
||||
const serviceConfig: ServiceConfig = {
|
||||
name: 'admin',
|
||||
command: 'npm',
|
||||
cwd: adminProjectPath,
|
||||
port: 5174,
|
||||
startupTimeout: 60000,
|
||||
env: {}
|
||||
};
|
||||
|
||||
if (!fs.existsSync(path.join(adminProjectPath, 'node_modules'))) {
|
||||
logger.info('安装 Admin 项目依赖...');
|
||||
await execAsync('npm install', { cwd: adminProjectPath });
|
||||
}
|
||||
|
||||
await this.startService(serviceConfig, ['run', 'dev']);
|
||||
}
|
||||
|
||||
async startUniappService(): Promise<void> {
|
||||
logger.section('启动 Uniapp 服务');
|
||||
|
||||
const uniappProjectPath = path.join(this.projectRoot, 'everything-is-suitable-uniapp');
|
||||
|
||||
if (!fs.existsSync(uniappProjectPath)) {
|
||||
logger.warn('Uniapp 项目目录不存在,跳过 Uniapp 服务启动');
|
||||
return;
|
||||
}
|
||||
|
||||
const serviceConfig: ServiceConfig = {
|
||||
name: 'uniapp',
|
||||
command: 'npm',
|
||||
cwd: uniappProjectPath,
|
||||
port: 8081,
|
||||
startupTimeout: 60000,
|
||||
env: {}
|
||||
};
|
||||
|
||||
if (!fs.existsSync(path.join(uniappProjectPath, 'node_modules'))) {
|
||||
logger.info('安装 Uniapp 项目依赖...');
|
||||
await execAsync('npm install', { cwd: uniappProjectPath });
|
||||
}
|
||||
|
||||
await this.startService(serviceConfig, ['run', 'dev:h5']);
|
||||
}
|
||||
|
||||
private async startService(config: ServiceConfig, args: string[]): Promise<void> {
|
||||
logger.info(`启动 ${config.name} 服务...`);
|
||||
|
||||
const env = { ...process.env, ...config.env };
|
||||
|
||||
const childProcess = spawn(config.command, args, {
|
||||
cwd: config.cwd,
|
||||
env,
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
detached: false
|
||||
});
|
||||
|
||||
this.processes.set(config.name, childProcess);
|
||||
|
||||
childProcess.stdout?.on('data', (data) => {
|
||||
const output = data.toString();
|
||||
logger.debug(`[${config.name}] ${output.trim()}`);
|
||||
});
|
||||
|
||||
childProcess.stderr?.on('data', (data) => {
|
||||
const output = data.toString();
|
||||
logger.debug(`[${config.name}] ERROR: ${output.trim()}`);
|
||||
});
|
||||
|
||||
childProcess.on('error', (error) => {
|
||||
logger.error(`${config.name} 服务启动失败`, error);
|
||||
});
|
||||
|
||||
await this.waitForServiceReady(config);
|
||||
logger.info(`${config.name} 服务已启动`);
|
||||
}
|
||||
|
||||
private async waitForServiceReady(config: ServiceConfig): Promise<void> {
|
||||
const startTime = Date.now();
|
||||
const timeout = config.startupTimeout;
|
||||
|
||||
while (Date.now() - startTime < timeout) {
|
||||
try {
|
||||
if (config.port) {
|
||||
const isReady = await this.checkPort(config.port);
|
||||
if (isReady) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
await this.sleep(1000);
|
||||
} catch (error) {
|
||||
logger.debug(`等待 ${config.name} 服务就绪...`);
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`${config.name} 服务启动超时 (${timeout}ms)`);
|
||||
}
|
||||
|
||||
private async checkPort(port: number): Promise<boolean> {
|
||||
try {
|
||||
const { stdout } = await execAsync(`lsof -i :${port} -t`);
|
||||
return stdout.trim().length > 0;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async checkServiceHealth(name: string, url: string): Promise<boolean> {
|
||||
try {
|
||||
const { stdout } = await execAsync(`curl -s -o /dev/null -w "%{http_code}" ${url}`);
|
||||
const statusCode = parseInt(stdout.trim(), 10);
|
||||
return statusCode >= 200 && statusCode < 300;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async stopAllServices(): Promise<void> {
|
||||
logger.section('停止所有服务');
|
||||
|
||||
const entries = Array.from(this.processes.entries());
|
||||
for (const [name, process] of entries) {
|
||||
logger.info(`停止 ${name} 服务...`);
|
||||
try {
|
||||
process.kill('SIGTERM');
|
||||
logger.info(`${name} 服务已停止`);
|
||||
} catch (error) {
|
||||
logger.warn(`停止 ${name} 服务时出错`, { error });
|
||||
}
|
||||
}
|
||||
|
||||
this.processes.clear();
|
||||
}
|
||||
|
||||
async cleanup(): Promise<void> {
|
||||
logger.section('清理环境');
|
||||
await this.stopAllServices();
|
||||
logger.info('环境清理完成');
|
||||
}
|
||||
|
||||
private sleep(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
getEnvironment(): TestEnvironment | null {
|
||||
return this.environment;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user