feat(database): 添加用户角色权限系统及相关监控功能

- 创建用户表(users)包含基本信息和认证字段
- 创建角色表(roles)用于权限控制
- 创建权限表(permissions)定义系统权限
- 创建用户角色关联表(user_roles)建立用户与角色关系
- 创建角色权限关联表(role_permissions)建立角色与权限关系
- 创建迁移记录表(migrations)追踪数据库变更
- 添加AdminController提供管理员面板功能
- 实现系统监控、配置管理、缓存清理等功能
- 添加AOP切面编程支持的各种通知类型
- 实现告警管理AlertManager支持多渠道告警
- 添加文档注解接口规范
This commit is contained in:
Lawson
2026-04-08 17:00:28 +08:00
commit 2782d765fb
270 changed files with 107192 additions and 0 deletions

View File

@@ -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;
}
}