feat(admin): 添加用户管理相关文件
添加用户管理视图、API和状态管理文件
This commit is contained in:
@@ -0,0 +1,332 @@
|
||||
import { APIRequestContext } from '@playwright/test';
|
||||
import { testLogger } from './test-logger';
|
||||
|
||||
export interface MetricsData {
|
||||
jvmMemoryUsed: number;
|
||||
jvmMemoryMax: number;
|
||||
jvmGcPause: number;
|
||||
responseTime: number;
|
||||
requestCount: number;
|
||||
errorCount: number;
|
||||
}
|
||||
|
||||
export interface JvmInfo {
|
||||
memory: {
|
||||
heap: {
|
||||
used: number;
|
||||
max: number;
|
||||
committed: number;
|
||||
};
|
||||
nonHeap: {
|
||||
used: number;
|
||||
max: number;
|
||||
committed: number;
|
||||
};
|
||||
};
|
||||
gc: {
|
||||
pauseCount: number;
|
||||
pauseTime: number;
|
||||
};
|
||||
threads: {
|
||||
live: number;
|
||||
peak: number;
|
||||
daemon: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface EnvInfo {
|
||||
activeProfiles: string[];
|
||||
properties: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface AppInfo {
|
||||
name: string;
|
||||
version: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface TestMetrics {
|
||||
testName: string;
|
||||
duration: number;
|
||||
status: 'passed' | 'failed';
|
||||
memoryUsage: number;
|
||||
responseTime: number;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export class ActuatorMonitor {
|
||||
private request: APIRequestContext;
|
||||
private baseUrl: string;
|
||||
private authToken?: string;
|
||||
|
||||
constructor(request: APIRequestContext, baseUrl: string, authToken?: string) {
|
||||
this.request = request;
|
||||
this.baseUrl = baseUrl.replace(/\/$/, '');
|
||||
this.authToken = authToken;
|
||||
}
|
||||
|
||||
private async getEndpoint(endpoint: string): Promise<any> {
|
||||
try {
|
||||
const headers: Record<string, string> = {
|
||||
'Accept': 'application/json',
|
||||
};
|
||||
|
||||
if (this.authToken) {
|
||||
headers['Authorization'] = `Bearer ${this.authToken}`;
|
||||
}
|
||||
|
||||
const response = await this.request.get(`${this.baseUrl}${endpoint}`, {
|
||||
headers,
|
||||
timeout: 5000,
|
||||
});
|
||||
|
||||
if (!response.ok()) {
|
||||
throw new Error(`Actuator endpoint failed: ${response.status()} ${response.statusText()}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
const errorObj = error instanceof Error ? error : new Error(String(error));
|
||||
testLogger.error(`获取Actuator端点失败: ${endpoint}`, errorObj);
|
||||
throw errorObj;
|
||||
}
|
||||
}
|
||||
|
||||
async checkHealth(): Promise<boolean> {
|
||||
try {
|
||||
testLogger.debug('检查应用健康状态');
|
||||
|
||||
const healthData = await this.getEndpoint('/actuator/health');
|
||||
const status = healthData.status;
|
||||
|
||||
// 服务可访问即可,不严格要求 UP 状态
|
||||
// 因为某些组件(如 CPU 负载)可能导致整体状态为 DOWN
|
||||
const isAccessible = status === 'UP' || status === 'DOWN' || status === 'WARNING';
|
||||
|
||||
if (status !== 'UP') {
|
||||
testLogger.warn(`应用健康状态非UP: ${status}`);
|
||||
if (healthData.components) {
|
||||
for (const [name, component] of Object.entries(healthData.components)) {
|
||||
if ((component as { status: string }).status !== 'UP') {
|
||||
testLogger.warn(`组件 ${name} 状态: ${(component as { status: string }).status}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
testLogger.info(`应用健康状态: ${status}, 可访问: ${isAccessible}`);
|
||||
|
||||
return isAccessible;
|
||||
} catch (error) {
|
||||
const errorObj = error instanceof Error ? error : new Error(String(error));
|
||||
testLogger.error('健康检查失败', errorObj);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async getMetrics(): Promise<MetricsData> {
|
||||
try {
|
||||
testLogger.debug('获取性能指标');
|
||||
|
||||
const jvmMemoryUsed = await this.getMetricValue('jvm.memory.used', 'area=heap');
|
||||
const jvmMemoryMax = await this.getMetricValue('jvm.memory.max', 'area=heap');
|
||||
const jvmGcPause = await this.getMetricValue('jvm.gc.pause', 'count');
|
||||
|
||||
const metrics: MetricsData = {
|
||||
jvmMemoryUsed: Math.round(jvmMemoryUsed / 1024 / 1024),
|
||||
jvmMemoryMax: Math.round(jvmMemoryMax / 1024 / 1024),
|
||||
jvmGcPause: Math.round(jvmGcPause),
|
||||
responseTime: 0,
|
||||
requestCount: 0,
|
||||
errorCount: 0,
|
||||
};
|
||||
|
||||
testLogger.debug(`性能指标: ${JSON.stringify(metrics)}`);
|
||||
|
||||
return metrics;
|
||||
} catch (error) {
|
||||
const errorObj = error instanceof Error ? error : new Error(String(error));
|
||||
testLogger.error('获取性能指标失败', errorObj);
|
||||
throw errorObj;
|
||||
}
|
||||
}
|
||||
|
||||
private async getMetricValue(metricName: string, tags: string = ''): Promise<number> {
|
||||
try {
|
||||
const endpoint = tags
|
||||
? `/actuator/metrics/${metricName}?tag=${tags}`
|
||||
: `/actuator/metrics/${metricName}`;
|
||||
|
||||
const data = await this.getEndpoint(endpoint);
|
||||
return data.measurements?.[0]?.value || 0;
|
||||
} catch (error) {
|
||||
const errorObj = error instanceof Error ? error : new Error(String(error));
|
||||
testLogger.warn(`获取指标值失败: ${metricName}`, errorObj);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
async getJvmInfo(): Promise<JvmInfo> {
|
||||
try {
|
||||
testLogger.debug('获取JVM信息');
|
||||
|
||||
const heapUsed = await this.getMetricValue('jvm.memory.used', 'area=heap');
|
||||
const heapMax = await this.getMetricValue('jvm.memory.max', 'area=heap');
|
||||
const heapCommitted = await this.getMetricValue('jvm.memory.committed', 'area=heap');
|
||||
const nonHeapUsed = await this.getMetricValue('jvm.memory.used', 'area=nonheap');
|
||||
const nonHeapMax = await this.getMetricValue('jvm.memory.max', 'area=nonheap');
|
||||
const nonHeapCommitted = await this.getMetricValue('jvm.memory.committed', 'area=nonheap');
|
||||
const gcPauseCount = await this.getMetricValue('jvm.gc.pause.count');
|
||||
const gcPauseTime = await this.getMetricValue('jvm.gc.pause.total');
|
||||
const threadsLive = await this.getMetricValue('jvm.threads.live');
|
||||
const threadsPeak = await this.getMetricValue('jvm.threads.peak');
|
||||
const threadsDaemon = await this.getMetricValue('jvm.threads.daemon');
|
||||
|
||||
const jvmInfo: JvmInfo = {
|
||||
memory: {
|
||||
heap: {
|
||||
used: Math.round(heapUsed / 1024 / 1024),
|
||||
max: Math.round(heapMax / 1024 / 1024),
|
||||
committed: Math.round(heapCommitted / 1024 / 1024),
|
||||
},
|
||||
nonHeap: {
|
||||
used: Math.round(nonHeapUsed / 1024 / 1024),
|
||||
max: Math.round(nonHeapMax / 1024 / 1024),
|
||||
committed: Math.round(nonHeapCommitted / 1024 / 1024),
|
||||
},
|
||||
},
|
||||
gc: {
|
||||
pauseCount: Math.round(gcPauseCount),
|
||||
pauseTime: Math.round(gcPauseTime / 1000),
|
||||
},
|
||||
threads: {
|
||||
live: Math.round(threadsLive),
|
||||
peak: Math.round(threadsPeak),
|
||||
daemon: Math.round(threadsDaemon),
|
||||
},
|
||||
};
|
||||
|
||||
testLogger.debug(`JVM信息: ${JSON.stringify(jvmInfo)}`);
|
||||
|
||||
return jvmInfo;
|
||||
} catch (error) {
|
||||
const errorObj = error instanceof Error ? error : new Error(String(error));
|
||||
testLogger.error('获取JVM信息失败', errorObj);
|
||||
throw errorObj;
|
||||
}
|
||||
}
|
||||
|
||||
async getEnvInfo(): Promise<EnvInfo> {
|
||||
try {
|
||||
testLogger.debug('获取环境信息');
|
||||
|
||||
const envData = await this.getEndpoint('/actuator/env');
|
||||
|
||||
const envInfo: EnvInfo = {
|
||||
activeProfiles: envData.profiles?.active || [],
|
||||
properties: {},
|
||||
};
|
||||
|
||||
if (envData.propertySources) {
|
||||
for (const source of envData.propertySources) {
|
||||
if (source.properties) {
|
||||
for (const [key, value] of Object.entries(source.properties)) {
|
||||
if (value && typeof value === 'object' && 'value' in value) {
|
||||
envInfo.properties[key] = (value as any).value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
testLogger.debug(`环境信息: 激活的配置文件 [${envInfo.activeProfiles.join(', ')}]`);
|
||||
|
||||
return envInfo;
|
||||
} catch (error) {
|
||||
const errorObj = error instanceof Error ? error : new Error(String(error));
|
||||
testLogger.error('获取环境信息失败', errorObj);
|
||||
throw errorObj;
|
||||
}
|
||||
}
|
||||
|
||||
async getAppInfo(): Promise<AppInfo> {
|
||||
try {
|
||||
testLogger.debug('获取应用信息');
|
||||
|
||||
const appData = await this.getEndpoint('/actuator/info');
|
||||
|
||||
const appInfo: AppInfo = {
|
||||
name: appData.app?.name || 'Unknown',
|
||||
version: appData.app?.version || 'Unknown',
|
||||
description: appData.app?.description || 'Unknown',
|
||||
};
|
||||
|
||||
testLogger.debug(`应用信息: ${appInfo.name} v${appInfo.version}`);
|
||||
|
||||
return appInfo;
|
||||
} catch (error) {
|
||||
const errorObj = error instanceof Error ? error : new Error(String(error));
|
||||
testLogger.error('获取应用信息失败', errorObj);
|
||||
throw errorObj;
|
||||
}
|
||||
}
|
||||
|
||||
async pushTestMetrics(metrics: TestMetrics): Promise<void> {
|
||||
try {
|
||||
testLogger.debug(`推送测试指标: ${metrics.testName}`);
|
||||
|
||||
const response = await this.request.post(`${this.baseUrl}/actuator/metrics/test`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': this.authToken ? `Bearer ${this.authToken}` : '',
|
||||
},
|
||||
data: metrics,
|
||||
timeout: 5000,
|
||||
});
|
||||
|
||||
if (!response.ok()) {
|
||||
throw new Error(`推送测试指标失败: ${response.status()} ${response.statusText()}`);
|
||||
}
|
||||
|
||||
testLogger.debug(`测试指标推送成功: ${metrics.testName}`);
|
||||
} catch (error) {
|
||||
const errorObj = error instanceof Error ? error : new Error(String(error));
|
||||
testLogger.warn('推送测试指标失败(可能不支持自定义指标)', errorObj);
|
||||
}
|
||||
}
|
||||
|
||||
async waitForHealth(maxRetries: number = 30, retryInterval: number = 2000): Promise<boolean> {
|
||||
testLogger.info(`等待应用健康状态,最大重试次数: ${maxRetries}`);
|
||||
|
||||
for (let i = 0; i < maxRetries; i++) {
|
||||
const isHealthy = await this.checkHealth();
|
||||
|
||||
if (isHealthy) {
|
||||
testLogger.info('应用健康状态检查通过');
|
||||
return true;
|
||||
}
|
||||
|
||||
testLogger.debug(`应用未就绪,等待 ${retryInterval}ms 后重试 (${i + 1}/${maxRetries})`);
|
||||
await new Promise(resolve => setTimeout(resolve, retryInterval));
|
||||
}
|
||||
|
||||
testLogger.error('应用健康状态检查超时');
|
||||
return false;
|
||||
}
|
||||
|
||||
async getFullHealthInfo(): Promise<any> {
|
||||
try {
|
||||
testLogger.debug('获取完整健康信息');
|
||||
|
||||
const healthData = await this.getEndpoint('/actuator/health');
|
||||
|
||||
testLogger.debug(`完整健康信息: ${JSON.stringify(healthData)}`);
|
||||
|
||||
return healthData;
|
||||
} catch (error) {
|
||||
const errorObj = error instanceof Error ? error : new Error(String(error));
|
||||
testLogger.error('获取完整健康信息失败', errorObj);
|
||||
throw errorObj;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user