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:
24
fendx-framework/fendx-job/composer.json
Normal file
24
fendx-framework/fendx-job/composer.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "fendx/job",
|
||||
"description": "FendxPHP Job Module - 定时任务、队列处理",
|
||||
"type": "library",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Lawson",
|
||||
"email": "lawson@fendx.cn"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=8.1",
|
||||
"fendx/common": "^1.0",
|
||||
"fendx/core": "^1.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Fendx\\Job\\": "src/"
|
||||
}
|
||||
},
|
||||
"minimum-stability": "stable",
|
||||
"prefer-stable": true
|
||||
}
|
||||
17
fendx-framework/fendx-job/src/Annotation/Scheduled.php
Normal file
17
fendx-framework/fendx-job/src/Annotation/Scheduled.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Fendx\Job\Annotation;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_METHOD)]
|
||||
final class Scheduled
|
||||
{
|
||||
public string $cron;
|
||||
public string $description;
|
||||
|
||||
public function __construct(string $cron, string $description = '')
|
||||
{
|
||||
$this->cron = $cron;
|
||||
$this->description = $description;
|
||||
}
|
||||
}
|
||||
207
fendx-framework/fendx-job/src/Scheduler/Scheduler.php
Normal file
207
fendx-framework/fendx-job/src/Scheduler/Scheduler.php
Normal file
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Fendx\Job\Scheduler;
|
||||
|
||||
use Fendx\Job\Annotation\Scheduled;
|
||||
use Fendx\Core\Container\Container;
|
||||
use Fendx\Log\Logger;
|
||||
|
||||
final class Scheduler
|
||||
{
|
||||
private array $jobs = [];
|
||||
private Container $container;
|
||||
private Logger $logger;
|
||||
private bool $running = false;
|
||||
|
||||
public function __construct(Container $container, Logger $logger)
|
||||
{
|
||||
$this->container = $container;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
public function addJob(string $className, string $method, Scheduled $scheduled): void
|
||||
{
|
||||
$this->jobs[] = [
|
||||
'class' => $className,
|
||||
'method' => $method,
|
||||
'cron' => $scheduled->cron,
|
||||
'description' => $scheduled->description,
|
||||
'last_run' => null,
|
||||
'next_run' => $this->getNextRunTime($scheduled->cron)
|
||||
];
|
||||
}
|
||||
|
||||
public function start(): void
|
||||
{
|
||||
$this->running = true;
|
||||
$this->logger->info('Scheduler started');
|
||||
|
||||
while ($this->running) {
|
||||
$this->runDueJobs();
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
public function stop(): void
|
||||
{
|
||||
$this->running = false;
|
||||
$this->logger->info('Scheduler stopped');
|
||||
}
|
||||
|
||||
public function runDueJobs(): void
|
||||
{
|
||||
$now = time();
|
||||
|
||||
foreach ($this->jobs as $job) {
|
||||
if ($job['next_run'] <= $now) {
|
||||
$this->executeJob($job);
|
||||
$job['last_run'] = $now;
|
||||
$job['next_run'] = $this->getNextRunTime($job['cron']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function executeJob(array $job): void
|
||||
{
|
||||
try {
|
||||
$this->logger->info("Executing job: {$job['class']}::{$job['method']}");
|
||||
|
||||
$instance = $this->container->make($job['class']);
|
||||
$method = $job['method'];
|
||||
|
||||
$start = microtime(true);
|
||||
$instance->$method();
|
||||
$duration = round((microtime(true) - $start) * 1000, 2);
|
||||
|
||||
$this->logger->info("Job completed: {$job['class']}::{$job['method']} in {$duration}ms");
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->error("Job failed: {$job['class']}::{$job['method']}", [
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private function getNextRunTime(string $cron): int
|
||||
{
|
||||
// 简单的cron解析实现
|
||||
// 支持格式:* * * * * (分 时 日 月 周)
|
||||
$parts = explode(' ', $cron);
|
||||
|
||||
if (count($parts) !== 5) {
|
||||
return time() + 60; // 默认1分钟后
|
||||
}
|
||||
|
||||
$now = getdate();
|
||||
$next = mktime(
|
||||
$this->getNextValue($parts[1], $now['hours']), // 时
|
||||
$this->getNextValue($parts[0], $now['minutes']), // 分
|
||||
$now['mday'], // 日
|
||||
$now['mon'], // 月
|
||||
$now['year'] // 年
|
||||
);
|
||||
|
||||
return $next;
|
||||
}
|
||||
|
||||
private function getNextValue(string $part, int $current): int
|
||||
{
|
||||
if ($part === '*') {
|
||||
return $current;
|
||||
}
|
||||
|
||||
if (is_numeric($part)) {
|
||||
$value = (int)$part;
|
||||
return $value > $current ? $value : $current + 1;
|
||||
}
|
||||
|
||||
// 支持简单表达式如 */5
|
||||
if (str_starts_with($part, '*/')) {
|
||||
$interval = (int)substr($part, 2);
|
||||
return $current + ($interval - ($current % $interval));
|
||||
}
|
||||
|
||||
return $current + 1;
|
||||
}
|
||||
|
||||
public function getJobs(): array
|
||||
{
|
||||
return $this->jobs;
|
||||
}
|
||||
|
||||
public function isRunning(): bool
|
||||
{
|
||||
return $this->running;
|
||||
}
|
||||
|
||||
public function scanJobs(string $scanPath): void
|
||||
{
|
||||
if (!is_dir($scanPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$files = glob($scanPath . '/**/*.php');
|
||||
|
||||
foreach ($files as $file) {
|
||||
$this->scanJobFile($file);
|
||||
}
|
||||
}
|
||||
|
||||
private function scanJobFile(string $file): void
|
||||
{
|
||||
$className = $this->getClassNameFromFile($file);
|
||||
|
||||
if ($className === null || class_exists($className) === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$reflection = new \ReflectionClass($className);
|
||||
$methods = $reflection->getMethods(\ReflectionMethod::IS_PUBLIC);
|
||||
|
||||
foreach ($methods as $method) {
|
||||
if ($method->getName() === '__construct') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$scheduledAttributes = $method->getAttributes(Scheduled::class);
|
||||
|
||||
foreach ($scheduledAttributes as $attribute) {
|
||||
$scheduled = $attribute->newInstance();
|
||||
$this->addJob($className, $method->getName(), $scheduled);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (\ReflectionException $e) {
|
||||
$this->logger->error("Failed to scan job file $file: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function getClassNameFromFile(string $file): ?string
|
||||
{
|
||||
$content = file_get_contents($file);
|
||||
|
||||
if (preg_match('/namespace\s+([^;]+);/', $content, $matches)) {
|
||||
$namespace = trim($matches[1]);
|
||||
$className = basename($file, '.php');
|
||||
return $namespace . '\\' . $className;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function runJob(string $jobName): void
|
||||
{
|
||||
foreach ($this->jobs as $job) {
|
||||
$jobIdentifier = "{$job['class']}::{$job['method']}";
|
||||
if ($jobIdentifier === $jobName) {
|
||||
$this->executeJob($job);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException("Job not found: $jobName");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user