mirror of
https://devops.lemonos.cn/lawson/FendxPHP.git
synced 2026-06-15 23:12:49 +08:00
feat(database): 添加用户角色权限系统及相关监控功能
- 创建用户表(users)包含基本信息和认证字段 - 创建角色表(roles)用于权限控制 - 创建权限表(permissions)定义系统权限 - 创建用户角色关联表(user_roles)建立用户与角色关系 - 创建角色权限关联表(role_permissions)建立角色与权限关系 - 创建迁移记录表(migrations)追踪数据库变更 - 添加AdminController提供管理员面板功能 - 实现系统监控、配置管理、缓存清理等功能 - 添加AOP切面编程支持的各种通知类型 - 实现告警管理AlertManager支持多渠道告警 - 添加文档注解接口规范
This commit is contained in:
497
app/Controller/MonitorController.php
Normal file
497
app/Controller/MonitorController.php
Normal file
@@ -0,0 +1,497 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use Fendx\Core\Annotation\Controller;
|
||||
use Fendx\Web\Annotation\GetRoute;
|
||||
use Fendx\Web\Request\Request;
|
||||
use Fendx\Web\Response\Response;
|
||||
use Fendx\Monitor\Service\MonitorService;
|
||||
|
||||
#[Controller('/monitor')]
|
||||
class MonitorController
|
||||
{
|
||||
#[GetRoute('/health')]
|
||||
public function health(): array
|
||||
{
|
||||
$health = MonitorService::getHealthStatus();
|
||||
|
||||
$httpCode = match ($health['status']) {
|
||||
'healthy' => 200,
|
||||
'warning' => 200,
|
||||
'critical' => 503,
|
||||
default => 200
|
||||
};
|
||||
|
||||
return Response::success($health, 'Health check completed', $httpCode);
|
||||
}
|
||||
|
||||
#[GetRoute('/health/{component}')]
|
||||
public function healthComponent(string $component): array
|
||||
{
|
||||
try {
|
||||
$health = MonitorService::checkIndividualHealth($component);
|
||||
|
||||
$httpCode = match ($health['status']) {
|
||||
'healthy' => 200,
|
||||
'warning' => 200,
|
||||
'critical' => 503,
|
||||
default => 200
|
||||
};
|
||||
|
||||
return Response::success($health, "Health check for {$component} completed", $httpCode);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
return Response::error(404, "Health check '{$component}' not found");
|
||||
}
|
||||
}
|
||||
|
||||
#[GetRoute('/health/checks')]
|
||||
public function healthChecks(): array
|
||||
{
|
||||
$checks = MonitorService::getAvailableHealthChecks();
|
||||
return Response::success($checks);
|
||||
}
|
||||
|
||||
#[GetRoute('/metrics')]
|
||||
public function metrics(Request $request): array
|
||||
{
|
||||
$format = $request->get('format', 'json');
|
||||
|
||||
if ($format === 'prometheus') {
|
||||
header('Content-Type: text/plain; version=0.0.4');
|
||||
echo MonitorService::exportMetrics('prometheus');
|
||||
exit;
|
||||
}
|
||||
|
||||
$metrics = MonitorService::getMetricsSummary();
|
||||
return Response::success($metrics);
|
||||
}
|
||||
|
||||
#[GetRoute('/alerts')]
|
||||
public function alerts(): array
|
||||
{
|
||||
$alerts = MonitorService::getAlerts();
|
||||
return Response::success($alerts);
|
||||
}
|
||||
|
||||
#[GetRoute('/alerts/active')]
|
||||
public function activeAlerts(): array
|
||||
{
|
||||
$alerts = MonitorService::getActiveAlerts();
|
||||
return Response::success($alerts);
|
||||
}
|
||||
|
||||
#[PostRoute('/alerts/{alertId}/acknowledge')]
|
||||
public function acknowledgeAlert(string $alertId, Request $request): array
|
||||
{
|
||||
$data = $request->all();
|
||||
$acknowledgedBy = $data['acknowledged_by'] ?? 'system';
|
||||
|
||||
$success = MonitorService::acknowledgeAlert($alertId, $acknowledgedBy);
|
||||
|
||||
if ($success) {
|
||||
return Response::success(null, 'Alert acknowledged');
|
||||
} else {
|
||||
return Response::error(404, 'Alert not found or already acknowledged');
|
||||
}
|
||||
}
|
||||
|
||||
#[PostRoute('/alerts/{alertId}/resolve')]
|
||||
public function resolveAlert(string $alertId): array
|
||||
{
|
||||
$success = MonitorService::resolveAlert($alertId);
|
||||
|
||||
if ($success) {
|
||||
return Response::success(null, 'Alert resolved');
|
||||
} else {
|
||||
return Response::error(404, 'Alert not found');
|
||||
}
|
||||
}
|
||||
|
||||
#[GetRoute('/errors')]
|
||||
public function errors(Request $request): array
|
||||
{
|
||||
$filters = [];
|
||||
|
||||
if ($type = $request->get('type')) {
|
||||
$filters['type'] = $type;
|
||||
}
|
||||
|
||||
if ($severity = $request->get('severity')) {
|
||||
$filters['severity'] = $severity;
|
||||
}
|
||||
|
||||
$errors = MonitorService::getErrors($filters);
|
||||
return Response::success($errors);
|
||||
}
|
||||
|
||||
#[GetRoute('/errors/statistics')]
|
||||
public function errorStatistics(): array
|
||||
{
|
||||
$stats = MonitorService::getErrorStatistics();
|
||||
return Response::success($stats);
|
||||
}
|
||||
|
||||
#[GetRoute('/errors/trends')]
|
||||
public function errorTrends(Request $request): array
|
||||
{
|
||||
$hours = (int)($request->get('hours', 24));
|
||||
$trends = MonitorService::getErrorTrends($hours);
|
||||
return Response::success($trends);
|
||||
}
|
||||
|
||||
#[GetRoute('/logs/search')]
|
||||
public function searchLogs(Request $request): array
|
||||
{
|
||||
$criteria = [
|
||||
'level' => $request->get('level'),
|
||||
'message' => $request->get('message'),
|
||||
'trace_id' => $request->get('trace_id'),
|
||||
'start_time' => $request->get('start_time'),
|
||||
'end_time' => $request->get('end_time'),
|
||||
'pattern' => $request->get('pattern'),
|
||||
'limit' => (int)($request->get('limit', 100)),
|
||||
'offset' => (int)($request->get('offset', 0)),
|
||||
'sort' => $request->get('sort', 'desc'),
|
||||
'sort_by' => $request->get('sort_by', 'timestamp')
|
||||
];
|
||||
|
||||
$results = MonitorService::searchLogs($criteria);
|
||||
return Response::success($results);
|
||||
}
|
||||
|
||||
#[GetRoute('/logs/aggregate')]
|
||||
public function aggregateLogs(Request $request): array
|
||||
{
|
||||
$criteria = [
|
||||
'time_range' => $request->get('time_range', '1h'),
|
||||
'group_by' => $request->get('group_by', 'level')
|
||||
];
|
||||
|
||||
$results = MonitorService::aggregateLogs($criteria);
|
||||
return Response::success($results);
|
||||
}
|
||||
|
||||
#[GetRoute('/logs/files')]
|
||||
public function getLogFiles(): array
|
||||
{
|
||||
$files = MonitorService::getLogFiles();
|
||||
return Response::success($files);
|
||||
}
|
||||
|
||||
#[GetRoute('/logs/content')]
|
||||
public function getLogContent(Request $request): array
|
||||
{
|
||||
$file = $request->get('file');
|
||||
$lines = (int)($request->get('lines', 100));
|
||||
$offset = (int)($request->get('offset', 0));
|
||||
|
||||
if (!$file) {
|
||||
return Response::error(400, 'File parameter is required');
|
||||
}
|
||||
|
||||
$content = MonitorService::getLogContent($file, $lines, $offset);
|
||||
return Response::success($content);
|
||||
}
|
||||
|
||||
#[GetRoute('/logs/realtime')]
|
||||
public function getRealTimeLogs(Request $request): array
|
||||
{
|
||||
$tail = (int)($request->get('tail', 100));
|
||||
$logs = MonitorService::getRealTimeLogs($tail);
|
||||
return Response::success($logs);
|
||||
}
|
||||
|
||||
#[GetRoute('/logs/export')]
|
||||
public function exportLogs(Request $request): void
|
||||
{
|
||||
$criteria = [
|
||||
'level' => $request->get('level'),
|
||||
'message' => $request->get('message'),
|
||||
'start_time' => $request->get('start_time'),
|
||||
'end_time' => $request->get('end_time'),
|
||||
'limit' => (int)($request->get('limit', 1000))
|
||||
];
|
||||
|
||||
$format = $request->get('format', 'json');
|
||||
$content = MonitorService::exportLogs($criteria, $format);
|
||||
|
||||
$filename = 'logs_' . date('Y-m-d_H-i-s') . '.' . $format;
|
||||
|
||||
header('Content-Type: ' . match ($format) {
|
||||
'json' => 'application/json',
|
||||
'csv' => 'text/csv',
|
||||
'txt' => 'text/plain',
|
||||
default => 'application/octet-stream'
|
||||
});
|
||||
header('Content-Disposition: attachment; filename="' . $filename . '"');
|
||||
echo $content;
|
||||
exit;
|
||||
}
|
||||
|
||||
#[GetRoute('/logs/charts/{chartType}')]
|
||||
public function generateLogChart(string $chartType, Request $request): void
|
||||
{
|
||||
$data = [];
|
||||
$title = $request->get('title', '');
|
||||
|
||||
switch ($chartType) {
|
||||
case 'timeline':
|
||||
case 'error_trend':
|
||||
$timeRange = $request->get('time_range', '1h');
|
||||
$aggregation = MonitorService::aggregateLogs(['time_range' => $timeRange]);
|
||||
$data = $aggregation['timeline'] ?? [];
|
||||
break;
|
||||
|
||||
case 'level_distribution':
|
||||
$aggregation = MonitorService::aggregateLogs();
|
||||
$data = $aggregation['groups'] ?? [];
|
||||
break;
|
||||
|
||||
case 'top_errors':
|
||||
$aggregation = MonitorService::aggregateLogs();
|
||||
$data = $aggregation['top_errors'] ?? [];
|
||||
break;
|
||||
|
||||
case 'bar':
|
||||
$aggregation = MonitorService::aggregateLogs();
|
||||
$data = $aggregation['groups'] ?? [];
|
||||
$title = $title ?: 'Log Distribution';
|
||||
break;
|
||||
|
||||
default:
|
||||
$data = [];
|
||||
}
|
||||
|
||||
$chart = MonitorService::generateLogChart($chartType, $data, $title);
|
||||
|
||||
header('Content-Type: image/svg+xml');
|
||||
header('Cache-Control: no-cache');
|
||||
echo $chart;
|
||||
exit;
|
||||
}
|
||||
|
||||
#[GetRoute('/stats')]
|
||||
public function stats(): array
|
||||
{
|
||||
$metrics = MonitorService::getMetricsSummary();
|
||||
|
||||
$stats = [
|
||||
'system' => $metrics['system'] ?? [],
|
||||
'http_requests' => $this->extractHttpStats($metrics),
|
||||
'database' => $this->extractDatabaseStats($metrics),
|
||||
'cache' => $this->extractCacheStats($metrics),
|
||||
'errors' => $this->extractErrorStats($metrics),
|
||||
'performance' => $this->extractPerformanceStats($metrics)
|
||||
];
|
||||
|
||||
return Response::success($stats);
|
||||
}
|
||||
|
||||
#[GetRoute('/dashboard')]
|
||||
public function dashboard(): array
|
||||
{
|
||||
$health = MonitorService::getHealthStatus();
|
||||
$metrics = MonitorService::getMetricsSummary();
|
||||
$alerts = MonitorService::getAlerts();
|
||||
|
||||
$dashboard = [
|
||||
'overview' => [
|
||||
'status' => $health['status'],
|
||||
'uptime' => $metrics['system']['uptime'] ?? 0,
|
||||
'memory_usage' => $metrics['system']['memory_usage'] ?? 0,
|
||||
'cpu_usage' => $metrics['system']['cpu_usage'] ?? 0,
|
||||
'active_alerts' => count($alerts)
|
||||
],
|
||||
'health_checks' => $health['checks'],
|
||||
'metrics' => [
|
||||
'requests_total' => $this->getTotalRequests($metrics),
|
||||
'errors_total' => $this->getTotalErrors($metrics),
|
||||
'avg_response_time' => $this->getAverageResponseTime($metrics),
|
||||
'success_rate' => $this->getSuccessRate($metrics)
|
||||
],
|
||||
'recent_alerts' => array_slice($alerts, 0, 10)
|
||||
];
|
||||
|
||||
return Response::success($dashboard);
|
||||
}
|
||||
|
||||
#[GetRoute('/clear')]
|
||||
public function clear(): array
|
||||
{
|
||||
MonitorService::clearMetrics();
|
||||
return Response::success(null, 'Metrics cleared');
|
||||
}
|
||||
|
||||
private function extractHttpStats(array $metrics): array
|
||||
{
|
||||
$httpStats = [];
|
||||
|
||||
if (isset($metrics['histograms']['http_request_duration'])) {
|
||||
$httpStats['duration'] = $metrics['histograms']['http_request_duration'];
|
||||
}
|
||||
|
||||
if (isset($metrics['counters'])) {
|
||||
$httpStats['requests_by_status'] = [];
|
||||
foreach ($metrics['counters'] as $key => $value) {
|
||||
if (str_contains($key, 'http_requests_total')) {
|
||||
$httpStats['requests_by_status'][$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $httpStats;
|
||||
}
|
||||
|
||||
private function extractDatabaseStats(array $metrics): array
|
||||
{
|
||||
$dbStats = [];
|
||||
|
||||
if (isset($metrics['histograms']['db_query_duration'])) {
|
||||
$dbStats['query_duration'] = $metrics['histograms']['db_query_duration'];
|
||||
}
|
||||
|
||||
if (isset($metrics['counters'])) {
|
||||
$dbStats['queries_by_type'] = [];
|
||||
foreach ($metrics['counters'] as $key => $value) {
|
||||
if (str_contains($key, 'db_queries_total')) {
|
||||
$dbStats['queries_by_type'][$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $dbStats;
|
||||
}
|
||||
|
||||
private function extractCacheStats(array $metrics): array
|
||||
{
|
||||
$cacheStats = [];
|
||||
|
||||
if (isset($metrics['counters'])) {
|
||||
$cacheStats['operations'] = [];
|
||||
$totalHits = 0;
|
||||
$totalMisses = 0;
|
||||
|
||||
foreach ($metrics['counters'] as $key => $value) {
|
||||
if (str_contains($key, 'cache_operations_total')) {
|
||||
$cacheStats['operations'][$key] = $value;
|
||||
|
||||
if (str_contains($key, 'hit:true')) {
|
||||
$totalHits += $value;
|
||||
} elseif (str_contains($key, 'hit:false')) {
|
||||
$totalMisses += $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$total = $totalHits + $totalMisses;
|
||||
$cacheStats['hit_rate'] = $total > 0 ? round(($totalHits / $total) * 100, 2) . '%' : '0%';
|
||||
}
|
||||
|
||||
return $cacheStats;
|
||||
}
|
||||
|
||||
private function extractErrorStats(array $metrics): array
|
||||
{
|
||||
$errorStats = [];
|
||||
|
||||
if (isset($metrics['counters'])) {
|
||||
$errorStats['errors_by_type'] = [];
|
||||
foreach ($metrics['counters'] as $key => $value) {
|
||||
if (str_contains($key, 'errors_total')) {
|
||||
$errorStats['errors_by_type'][$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($metrics['metrics']['error'])) {
|
||||
$errorStats['recent_errors'] = array_slice($metrics['metrics']['error'], -10);
|
||||
}
|
||||
|
||||
return $errorStats;
|
||||
}
|
||||
|
||||
private function extractPerformanceStats(array $metrics): array
|
||||
{
|
||||
$perfStats = [
|
||||
'memory' => $metrics['system'] ?? [],
|
||||
'response_times' => [],
|
||||
'throughput' => []
|
||||
];
|
||||
|
||||
if (isset($metrics['histograms']['http_request_duration'])) {
|
||||
$perfStats['response_times'] = $metrics['histograms']['http_request_duration'];
|
||||
}
|
||||
|
||||
// 计算吞吐量(每秒请求数)
|
||||
if (isset($metrics['metrics']['http_requests_total'])) {
|
||||
$requests = $metrics['metrics']['http_requests_total'];
|
||||
$now = microtime(true);
|
||||
$requestsInLastMinute = 0;
|
||||
|
||||
foreach ($requests as $request) {
|
||||
if ($now - $request['timestamp'] < 60) {
|
||||
$requestsInLastMinute++;
|
||||
}
|
||||
}
|
||||
|
||||
$perfStats['throughput']['requests_per_minute'] = $requestsInLastMinute;
|
||||
$perfStats['throughput']['requests_per_second'] = round($requestsInLastMinute / 60, 2);
|
||||
}
|
||||
|
||||
return $perfStats;
|
||||
}
|
||||
|
||||
private function getTotalRequests(array $metrics): int
|
||||
{
|
||||
$total = 0;
|
||||
|
||||
if (isset($metrics['counters'])) {
|
||||
foreach ($metrics['counters'] as $key => $value) {
|
||||
if (str_contains($key, 'http_requests_total')) {
|
||||
$total += (int)$value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $total;
|
||||
}
|
||||
|
||||
private function getTotalErrors(array $metrics): int
|
||||
{
|
||||
$total = 0;
|
||||
|
||||
if (isset($metrics['counters'])) {
|
||||
foreach ($metrics['counters'] as $key => $value) {
|
||||
if (str_contains($key, 'errors_total')) {
|
||||
$total += (int)$value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $total;
|
||||
}
|
||||
|
||||
private function getAverageResponseTime(array $metrics): float
|
||||
{
|
||||
if (isset($metrics['histograms']['http_request_duration']['mean'])) {
|
||||
return round($metrics['histograms']['http_request_duration']['mean'], 3);
|
||||
}
|
||||
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
private function getSuccessRate(array $metrics): string
|
||||
{
|
||||
$totalRequests = $this->getTotalRequests($metrics);
|
||||
$totalErrors = $this->getTotalErrors($metrics);
|
||||
|
||||
if ($totalRequests === 0) {
|
||||
return '100%';
|
||||
}
|
||||
|
||||
$successRate = (($totalRequests - $totalErrors) / $totalRequests) * 100;
|
||||
return round($successRate, 2) . '%';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user