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,163 @@
<?php
declare(strict_types=1);
namespace App\Interceptor;
use Fendx\Core\Aop\JoinPoint;
use Fendx\Core\Aop\Advice\AroundAdvice;
use Fendx\Web\Request\Request;
use Fendx\Web\Response\HttpResponse;
use Fendx\Security\Auth\JwtManager;
/**
* 认证拦截器
*/
class AuthInterceptor implements AroundAdvice
{
private JwtManager $jwtManager;
public function __construct()
{
$this->jwtManager = new JwtManager([
'secret_key' => config('jwt.secret_key', 'your-secret-key'),
'algorithm' => config('jwt.algorithm', 'HS256'),
'expires_in' => config('jwt.expires_in', 3600),
]);
}
public function invoke(JoinPoint $joinPoint): mixed
{
$request = $this->getRequest();
// 检查是否需要认证
if ($this->isPublicRoute($request)) {
return $joinPoint->proceed();
}
// 获取令牌
$token = $this->extractToken($request);
if (!$token) {
return $this->unauthorizedResponse('Missing authentication token');
}
// 验证令牌
try {
$payload = $this->jwtManager->validateToken($token);
} catch (\Exception $e) {
return $this->unauthorizedResponse('Invalid or expired token');
}
// 设置用户信息到上下文
$this->setUserContext($payload);
return $joinPoint->proceed();
}
/**
* 获取当前请求对象
*/
private function getRequest(): Request
{
return Request::createFromGlobals();
}
/**
* 检查是否为公开路由
*/
private function isPublicRoute(Request $request): bool
{
$path = $request->path();
$method = $request->method();
$publicRoutes = [
// 登录相关
'POST:/api/auth/login',
'POST:/api/auth/register',
'POST:/api/auth/refresh',
// 公开API
'GET:/api/health',
'GET:/api/version',
// 静态资源
'GET:/',
'GET:/favicon.ico',
];
$currentRoute = "{$method}:{$path}";
// 精确匹配
if (in_array($currentRoute, $publicRoutes)) {
return true;
}
// 模糊匹配
foreach ($publicRoutes as $route) {
if (str_ends_with($route, '*')) {
$prefix = substr($route, 0, -1);
if (str_starts_with($currentRoute, $prefix)) {
return true;
}
}
}
return false;
}
/**
* 从请求中提取令牌
*/
private function extractToken(Request $request): ?string
{
// 从Authorization头获取
$authHeader = $request->header('Authorization');
if ($authHeader) {
$token = $this->jwtManager->extractTokenFromHeader($authHeader);
if ($token) {
return $token;
}
}
// 从Cookie获取
$token = $request->cookie('token');
if ($token) {
return $token;
}
// 从查询参数获取(不推荐,仅用于调试)
$token = $request->get('token');
if ($token) {
return $token;
}
return null;
}
/**
* 设置用户上下文
*/
private function setUserContext(array $payload): void
{
// 这里应该设置到全局上下文中
// Context::set('user_id', $payload['user_id']);
// Context::set('username', $payload['username']);
// Context::set('roles', $payload['roles'] ?? []);
// Context::set('permissions', $payload['permissions'] ?? []);
}
/**
* 返回未授权响应
*/
private function unauthorizedResponse(string $message): HttpResponse
{
return (new HttpResponse())
->setStatusCode(401)
->json([
'code' => 401,
'message' => $message,
'data' => null,
'trace_id' => \Fendx\Core\Context\Context::getTraceId(),
]);
}
}