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:
@@ -0,0 +1,563 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Fendx\Observability;
|
||||
|
||||
/**
|
||||
* 可观测性平台
|
||||
* 统一的 Metrics + Tracing + Logging 平台
|
||||
*/
|
||||
class ObservabilityPlatform
|
||||
{
|
||||
private MetricsCollector $metrics;
|
||||
private Tracer $tracer;
|
||||
private Logger $logger;
|
||||
private array $config;
|
||||
|
||||
public function __construct(array $config = [])
|
||||
{
|
||||
$this->config = array_merge([
|
||||
'service_name' => 'fendx-php',
|
||||
'service_version' => '1.0.0',
|
||||
'environment' => 'production',
|
||||
'tracing' => [
|
||||
'enabled' => true,
|
||||
'sample_rate' => 0.1,
|
||||
'exporter' => 'jaeger',
|
||||
'jaeger_endpoint' => 'http://jaeger:14268/api/traces',
|
||||
],
|
||||
'metrics' => [
|
||||
'enabled' => true,
|
||||
'exporter' => 'prometheus',
|
||||
'port' => 9100,
|
||||
'path' => '/metrics',
|
||||
],
|
||||
'logging' => [
|
||||
'enabled' => true,
|
||||
'level' => 'info',
|
||||
'format' => 'json',
|
||||
'exporters' => ['stdout', 'elasticsearch'],
|
||||
],
|
||||
], $config);
|
||||
|
||||
$this->initializeComponents();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化组件
|
||||
*/
|
||||
private function initializeComponents(): void
|
||||
{
|
||||
if ($this->config['metrics']['enabled']) {
|
||||
$this->metrics = new MetricsCollector($this->config['metrics']);
|
||||
}
|
||||
|
||||
if ($this->config['tracing']['enabled']) {
|
||||
$this->tracer = new Tracer($this->config['tracing']);
|
||||
}
|
||||
|
||||
if ($this->config['logging']['enabled']) {
|
||||
$this->logger = new Logger($this->config['logging']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录请求
|
||||
*/
|
||||
public function recordRequest(RequestData $request, ResponseData $response): void
|
||||
{
|
||||
$traceId = $this->getOrCreateTraceId();
|
||||
$duration = $response->getDuration();
|
||||
$statusCode = $response->getStatusCode();
|
||||
|
||||
// 指标收集
|
||||
if ($this->metrics) {
|
||||
$this->metrics->increment('requests_total', [
|
||||
'method' => $request->getMethod(),
|
||||
'status' => $statusCode,
|
||||
'service' => $this->config['service_name'],
|
||||
'version' => $this->config['service_version'],
|
||||
]);
|
||||
|
||||
$this->metrics->histogram('request_duration_ms', $duration * 1000, [
|
||||
'method' => $request->getMethod(),
|
||||
'endpoint' => $request->getPath(),
|
||||
'service' => $this->config['service_name'],
|
||||
]);
|
||||
|
||||
$this->metrics->gauge('active_requests', $this->getActiveRequests(), [
|
||||
'service' => $this->config['service_name'],
|
||||
]);
|
||||
|
||||
// 错误率指标
|
||||
if ($statusCode >= 400) {
|
||||
$this->metrics->increment('errors_total', [
|
||||
'method' => $request->getMethod(),
|
||||
'status' => $statusCode,
|
||||
'service' => $this->config['service_name'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// 链路追踪
|
||||
if ($this->tracer) {
|
||||
$span = $this->tracer->startSpan('http_request');
|
||||
$span->setTag('http.method', $request->getMethod());
|
||||
$span->setTag('http.url', $request->getFullUrl());
|
||||
$span->setTag('http.status_code', $statusCode);
|
||||
$span->setTag('service.name', $this->config['service_name']);
|
||||
$span->setTag('service.version', $this->config['service_version']);
|
||||
$span->setDuration($duration);
|
||||
|
||||
// 添加用户信息
|
||||
if ($request->getUserId()) {
|
||||
$span->setTag('user.id', (string) $request->getUserId());
|
||||
}
|
||||
|
||||
// 添加业务标签
|
||||
$this->addBusinessTags($span, $request, $response);
|
||||
|
||||
$span->finish();
|
||||
}
|
||||
|
||||
// 结构化日志
|
||||
if ($this->logger) {
|
||||
$logLevel = $this->getLogLevel($statusCode);
|
||||
$this->logger->log($logLevel, 'Request processed', [
|
||||
'trace_id' => $traceId,
|
||||
'span_id' => $span?->getSpanId(),
|
||||
'duration_ms' => round($duration * 1000, 2),
|
||||
'method' => $request->getMethod(),
|
||||
'path' => $request->getPath(),
|
||||
'status' => $statusCode,
|
||||
'user_id' => $request->getUserId(),
|
||||
'user_agent' => $request->getUserAgent(),
|
||||
'ip' => $request->getClientIp(),
|
||||
'service' => $this->config['service_name'],
|
||||
'version' => $this->config['service_version'],
|
||||
'environment' => $this->config['environment'],
|
||||
]);
|
||||
}
|
||||
|
||||
// 性能警告
|
||||
if ($duration > 1.0) {
|
||||
$this->recordSlowRequest($request, $response, $duration);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录异常
|
||||
*/
|
||||
public function recordException(\Throwable $exception, ?RequestData $request = null): void
|
||||
{
|
||||
$traceId = $this->getOrCreateTraceId();
|
||||
|
||||
// 指标收集
|
||||
if ($this->metrics) {
|
||||
$this->metrics->increment('exceptions_total', [
|
||||
'type' => get_class($exception),
|
||||
'service' => $this->config['service_name'],
|
||||
'method' => $request?->getMethod(),
|
||||
]);
|
||||
}
|
||||
|
||||
// 链路追踪
|
||||
if ($this->tracer) {
|
||||
$span = $this->tracer->startSpan('exception');
|
||||
$span->setTag('exception.type', get_class($exception));
|
||||
$span->setTag('exception.message', $exception->getMessage());
|
||||
$span->setTag('exception.stack_trace', $exception->getTraceAsString());
|
||||
$span->setTag('service.name', $this->config['service_name']);
|
||||
$span->finish();
|
||||
}
|
||||
|
||||
// 错误日志
|
||||
if ($this->logger) {
|
||||
$this->logger->error('Exception occurred', [
|
||||
'trace_id' => $traceId,
|
||||
'span_id' => $span?->getSpanId(),
|
||||
'exception' => [
|
||||
'type' => get_class($exception),
|
||||
'message' => $exception->getMessage(),
|
||||
'file' => $exception->getFile(),
|
||||
'line' => $exception->getLine(),
|
||||
'stack_trace' => $exception->getTraceAsString(),
|
||||
],
|
||||
'request' => $request ? [
|
||||
'method' => $request->getMethod(),
|
||||
'path' => $request->getPath(),
|
||||
'user_id' => $request->getUserId(),
|
||||
] : null,
|
||||
'service' => $this->config['service_name'],
|
||||
'environment' => $this->config['environment'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录业务事件
|
||||
*/
|
||||
public function recordBusinessEvent(string $eventName, array $data, ?string $userId = null): void
|
||||
{
|
||||
$traceId = $this->getOrCreateTraceId();
|
||||
|
||||
// 指标收集
|
||||
if ($this->metrics) {
|
||||
$this->metrics->increment('business_events_total', [
|
||||
'event' => $eventName,
|
||||
'service' => $this->config['service_name'],
|
||||
]);
|
||||
}
|
||||
|
||||
// 链路追踪
|
||||
if ($this->tracer) {
|
||||
$span = $this->tracer->startSpan("business_event.{$eventName}");
|
||||
$span->setTag('event.name', $eventName);
|
||||
$span->setTag('service.name', $this->config['service_name']);
|
||||
|
||||
foreach ($data as $key => $value) {
|
||||
$span->setTag("event.{$key}", (string) $value);
|
||||
}
|
||||
|
||||
$span->finish();
|
||||
}
|
||||
|
||||
// 业务日志
|
||||
if ($this->logger) {
|
||||
$this->logger->info("Business event: {$eventName}", [
|
||||
'trace_id' => $traceId,
|
||||
'span_id' => $span?->getSpanId(),
|
||||
'event' => $eventName,
|
||||
'data' => $data,
|
||||
'user_id' => $userId,
|
||||
'service' => $this->config['service_name'],
|
||||
'timestamp' => time(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录数据库查询
|
||||
*/
|
||||
public function recordDatabaseQuery(string $query, float $duration, bool $success, ?string $error = null): void
|
||||
{
|
||||
$traceId = $this->getOrCreateTraceId();
|
||||
|
||||
// 指标收集
|
||||
if ($this->metrics) {
|
||||
$this->metrics->histogram('db_query_duration_ms', $duration * 1000, [
|
||||
'operation' => $this->extractQueryType($query),
|
||||
'service' => $this->config['service_name'],
|
||||
]);
|
||||
|
||||
if (!$success) {
|
||||
$this->metrics->increment('db_errors_total', [
|
||||
'operation' => $this->extractQueryType($query),
|
||||
'service' => $this->config['service_name'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// 链路追踪
|
||||
if ($this->tracer) {
|
||||
$span = $this->tracer->startSpan('db_query');
|
||||
$span->setTag('db.type', 'sql');
|
||||
$span->setTag('db.statement', $query);
|
||||
$span->setTag('db.duration_ms', round($duration * 1000, 2));
|
||||
$span->setTag('service.name', $this->config['service_name']);
|
||||
|
||||
if (!$success) {
|
||||
$span->setTag('error', true);
|
||||
$span->setTag('error.message', $error);
|
||||
}
|
||||
|
||||
$span->finish();
|
||||
}
|
||||
|
||||
// 慢查询日志
|
||||
if ($duration > 0.5) {
|
||||
if ($this->logger) {
|
||||
$this->logger->warning('Slow database query detected', [
|
||||
'trace_id' => $traceId,
|
||||
'span_id' => $span?->getSpanId(),
|
||||
'query' => $query,
|
||||
'duration_ms' => round($duration * 1000, 2),
|
||||
'success' => $success,
|
||||
'error' => $error,
|
||||
'service' => $this->config['service_name'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录缓存操作
|
||||
*/
|
||||
public function recordCacheOperation(string $operation, string $key, bool $hit, float $duration = 0): void
|
||||
{
|
||||
$traceId = $this->getOrCreateTraceId();
|
||||
|
||||
// 指标收集
|
||||
if ($this->metrics) {
|
||||
$this->metrics->increment('cache_operations_total', [
|
||||
'operation' => $operation,
|
||||
'hit' => $hit ? 'true' : 'false',
|
||||
'service' => $this->config['service_name'],
|
||||
]);
|
||||
|
||||
if ($duration > 0) {
|
||||
$this->metrics->histogram('cache_operation_duration_ms', $duration * 1000, [
|
||||
'operation' => $operation,
|
||||
'service' => $this->config['service_name'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// 链路追踪
|
||||
if ($this->tracer) {
|
||||
$span = $this->tracer->startSpan("cache.{$operation}");
|
||||
$span->setTag('cache.key', $key);
|
||||
$span->setTag('cache.hit', $hit);
|
||||
$span->setTag('service.name', $this->config['service_name']);
|
||||
$span->finish();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录慢请求
|
||||
*/
|
||||
private function recordSlowRequest(RequestData $request, ResponseData $response, float $duration): void
|
||||
{
|
||||
if ($this->logger) {
|
||||
$this->logger->warning('Slow request detected', [
|
||||
'trace_id' => $this->getOrCreateTraceId(),
|
||||
'duration_ms' => round($duration * 1000, 2),
|
||||
'method' => $request->getMethod(),
|
||||
'path' => $request->getPath(),
|
||||
'status' => $response->getStatusCode(),
|
||||
'user_id' => $request->getUserId(),
|
||||
'service' => $this->config['service_name'],
|
||||
]);
|
||||
}
|
||||
|
||||
if ($this->metrics) {
|
||||
$this->metrics->increment('slow_requests_total', [
|
||||
'method' => $request->getMethod(),
|
||||
'endpoint' => $request->getPath(),
|
||||
'service' => $this->config['service_name'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加业务标签
|
||||
*/
|
||||
private function addBusinessTags($span, RequestData $request, ResponseData $response): void
|
||||
{
|
||||
// 添加业务相关的标签
|
||||
if ($request->getUserId()) {
|
||||
$span->setTag('user.authenticated', 'true');
|
||||
} else {
|
||||
$span->setTag('user.authenticated', 'false');
|
||||
}
|
||||
|
||||
// 添加API版本标签
|
||||
if ($request->getApiVersion()) {
|
||||
$span->setTag('api.version', $request->getApiVersion());
|
||||
}
|
||||
|
||||
// 添加响应大小标签
|
||||
$span->setTag('response.size_bytes', $response->getSize());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取或创建 Trace ID
|
||||
*/
|
||||
private function getOrCreateTraceId(): string
|
||||
{
|
||||
// 从请求头或上下文获取 Trace ID
|
||||
$traceId = $_SERVER['HTTP_X_TRACE_ID'] ?? null;
|
||||
|
||||
if (!$traceId) {
|
||||
$traceId = $this->generateTraceId();
|
||||
}
|
||||
|
||||
return $traceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 Trace ID
|
||||
*/
|
||||
private function generateTraceId(): string
|
||||
{
|
||||
return uniqid('trace_', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取活跃请求数
|
||||
*/
|
||||
private function getActiveRequests(): int
|
||||
{
|
||||
// 这里应该从实际的请求计数器获取
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据状态码获取日志级别
|
||||
*/
|
||||
private function getLogLevel(int $statusCode): string
|
||||
{
|
||||
return match (true) {
|
||||
$statusCode >= 500 => 'error',
|
||||
$statusCode >= 400 => 'warning',
|
||||
$statusCode >= 300 => 'info',
|
||||
default => 'info',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取查询类型
|
||||
*/
|
||||
private function extractQueryType(string $query): string
|
||||
{
|
||||
$query = trim(strtoupper($query));
|
||||
|
||||
if (str_starts_with($query, 'SELECT')) return 'SELECT';
|
||||
if (str_starts_with($query, 'INSERT')) return 'INSERT';
|
||||
if (str_starts_with($query, 'UPDATE')) return 'UPDATE';
|
||||
if (str_starts_with($query, 'DELETE')) return 'DELETE';
|
||||
|
||||
return 'OTHER';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取平台状态
|
||||
*/
|
||||
public function getStatus(): array
|
||||
{
|
||||
return [
|
||||
'service_name' => $this->config['service_name'],
|
||||
'service_version' => $this->config['service_version'],
|
||||
'environment' => $this->config['environment'],
|
||||
'components' => [
|
||||
'metrics' => [
|
||||
'enabled' => $this->config['metrics']['enabled'],
|
||||
'exporter' => $this->config['metrics']['exporter'],
|
||||
'port' => $this->config['metrics']['port'],
|
||||
],
|
||||
'tracing' => [
|
||||
'enabled' => $this->config['tracing']['enabled'],
|
||||
'exporter' => $this->config['tracing']['exporter'],
|
||||
'sample_rate' => $this->config['tracing']['sample_rate'],
|
||||
],
|
||||
'logging' => [
|
||||
'enabled' => $this->config['logging']['enabled'],
|
||||
'level' => $this->config['logging']['level'],
|
||||
'format' => $this->config['logging']['format'],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指标
|
||||
*/
|
||||
public function getMetrics(): array
|
||||
{
|
||||
return $this->metrics?->getMetrics() ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取追踪信息
|
||||
*/
|
||||
public function getTraces(array $filters = []): array
|
||||
{
|
||||
return $this->tracer?->getTraces($filters) ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取日志
|
||||
*/
|
||||
public function getLogs(array $filters = []): array
|
||||
{
|
||||
return $this->logger?->getLogs($filters) ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求数据
|
||||
*/
|
||||
class RequestData
|
||||
{
|
||||
public function __construct(
|
||||
private string $method,
|
||||
private string $path,
|
||||
private string $fullUrl,
|
||||
private ?string $userId = null,
|
||||
private ?string $userAgent = null,
|
||||
private ?string $clientIp = null,
|
||||
private ?string $apiVersion = null,
|
||||
) {}
|
||||
|
||||
public function getMethod(): string
|
||||
{
|
||||
return $this->method;
|
||||
}
|
||||
|
||||
public function getPath(): string
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
public function getFullUrl(): string
|
||||
{
|
||||
return $this->fullUrl;
|
||||
}
|
||||
|
||||
public function getUserId(): ?string
|
||||
{
|
||||
return $this->userId;
|
||||
}
|
||||
|
||||
public function getUserAgent(): ?string
|
||||
{
|
||||
return $this->userAgent;
|
||||
}
|
||||
|
||||
public function getClientIp(): ?string
|
||||
{
|
||||
return $this->clientIp;
|
||||
}
|
||||
|
||||
public function getApiVersion(): ?string
|
||||
{
|
||||
return $this->apiVersion;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 响应数据
|
||||
*/
|
||||
class ResponseData
|
||||
{
|
||||
public function __construct(
|
||||
private int $statusCode,
|
||||
private float $duration,
|
||||
private int $size = 0,
|
||||
) {}
|
||||
|
||||
public function getStatusCode(): int
|
||||
{
|
||||
return $this->statusCode;
|
||||
}
|
||||
|
||||
public function getDuration(): float
|
||||
{
|
||||
return $this->duration;
|
||||
}
|
||||
|
||||
public function getSize(): int
|
||||
{
|
||||
return $this->size;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user