mirror of
https://gitee.com/ulthon/ulthon_admin.git
synced 2026-07-06 10:02:49 +08:00
feat: 增加内置节点注册任务;优化定时器代码结构;
This commit is contained in:
9
app/common/service/TimerService.php
Normal file
9
app/common/service/TimerService.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\service;
|
||||
|
||||
use base\common\service\TimerServiceBase;
|
||||
|
||||
class TimerService extends TimerServiceBase
|
||||
{
|
||||
}
|
||||
11
app/tools/controller/timer/SystemHost.php
Normal file
11
app/tools/controller/timer/SystemHost.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\tools\controller\timer;
|
||||
|
||||
use base\tools\controller\timer\SystemHostBase;
|
||||
|
||||
class SystemHost extends SystemHostBase
|
||||
{
|
||||
}
|
||||
@@ -14,7 +14,7 @@ return [
|
||||
// 自动写入时间戳字段
|
||||
// true为自动识别类型 false关闭
|
||||
// 字符串则明确指定时间字段类型 支持 int timestamp datetime date
|
||||
'auto_timestamp' => true,
|
||||
'auto_timestamp' => 'int',
|
||||
|
||||
// 时间字段取出后的默认时间格式
|
||||
'datetime_format' => 'Y-m-d H:i:s',
|
||||
|
||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace base\common\command;
|
||||
|
||||
use app\common\model\VirtualModel;
|
||||
use app\common\service\TimerService;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Promise\Utils;
|
||||
use think\console\Command;
|
||||
@@ -48,7 +48,7 @@ class TimerBase extends Command
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
$host = $site_domain;
|
||||
|
||||
if ($input->hasOption('local')) {
|
||||
@@ -58,39 +58,18 @@ class TimerBase extends Command
|
||||
$output->writeln('站点域名:' . $host);
|
||||
$site_host = parse_url($host, PHP_URL_HOST);
|
||||
|
||||
$config_list = include app_file_path('common/command/timer/config.php');
|
||||
$request_list = TimerService::generateAllRequestList();
|
||||
|
||||
$request_list = [];
|
||||
foreach ($config_list as $config_item) {
|
||||
$config_item = static::initConfigItem($config_item);
|
||||
|
||||
if ($config_item['name'] == 'http_demo' && !env('adminsystem.is_demo', false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$concurrency = $config_item['concurrency'];
|
||||
|
||||
for ($i = 0; $i < $concurrency; $i++) {
|
||||
$target = $config_item['target'];
|
||||
$params = [
|
||||
'concurrency_id' => $i,
|
||||
'concurrency_count' => $concurrency,
|
||||
];
|
||||
$target_info = parse_url($target);
|
||||
$query_params = [];
|
||||
if (isset($target_info['query'])) {
|
||||
parse_str($target_info['query'], $query_params);
|
||||
}
|
||||
$query_params = array_merge($query_params, $params);
|
||||
$target_info['query'] = http_build_query($query_params);
|
||||
$target = unparse_url($target_info);
|
||||
|
||||
$config_item['target'] = $target;
|
||||
$config_item['concurrency_id'] = $i;
|
||||
|
||||
$request_list[] = clone $config_item;
|
||||
}
|
||||
}
|
||||
$system_host_register =
|
||||
[
|
||||
'name' => 'system_host_register', // 定时任务的名称,不能重复
|
||||
'type' => 'site', // 定时任务的类型,默认只支持site,你也可以重写定时器命令行以支持其他命令
|
||||
'target' => '/tools/timer.SystemHost/do', // 要访问的地址,如果不是以https开头,那么以后台的系统配置中读取相关配置,如果没有配置则不执行
|
||||
'frequency' => 30, // 执行频率,单位:秒,填写10,则每10秒过后执行一次
|
||||
];
|
||||
$system_host_register = TimerService::initConfigItem($system_host_register);
|
||||
$system_host_request_list = TimerService::generateRequestListFromConfig($system_host_register);
|
||||
$request_list = array_merge($request_list, $system_host_request_list);
|
||||
|
||||
$this->host = $host;
|
||||
$this->siteDomain = $site_domain;
|
||||
@@ -245,24 +224,4 @@ class TimerBase extends Command
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static function initConfigItem($config)
|
||||
{
|
||||
$default = [
|
||||
'name' => 'http_demo',
|
||||
'type' => 'site',
|
||||
'target' => '',
|
||||
'frequency' => 600,
|
||||
'concurrency' => 1,
|
||||
];
|
||||
|
||||
$data = array_merge($default, $config);
|
||||
|
||||
if ($data['frequency'] < 0) {
|
||||
$data['frequency'] = 0;
|
||||
}
|
||||
$model_timer = new VirtualModel($data);
|
||||
|
||||
return $model_timer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
namespace base\common\service;
|
||||
|
||||
use app\common\model\SystemHost;
|
||||
use app\admin\model\SystemHost;
|
||||
use app\common\tools\PathTools;
|
||||
use think\facade\App;
|
||||
use think\facade\Log;
|
||||
|
||||
@@ -13,22 +14,22 @@ use think\facade\Log;
|
||||
class HostServiceBase
|
||||
{
|
||||
/**
|
||||
* 获取当前主机的唯一ID。
|
||||
* 获取当前节点的唯一ID。
|
||||
* 该方法确保即使在多个具有相同hostname的隔离网络中,ID也是唯一的。
|
||||
* 它会在首次运行时生成一个ID并持久化到本地文件中,以保证重启后ID不变。
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getHostId(): string
|
||||
public static function getNodeId(): string
|
||||
{
|
||||
// 定义一个持久化存储ID的文件路径 (runtime目录在部署时通常是可写的)
|
||||
$idFilePath = App::getRuntimePath() . 'host_id.lock';
|
||||
$idFilePath = PathTools::tempBuildPath('node_id.lock');
|
||||
|
||||
// 1. 如果ID文件已存在,直接读取并返回
|
||||
if (file_exists($idFilePath)) {
|
||||
$hostId = file_get_contents($idFilePath);
|
||||
if (!empty($hostId)) {
|
||||
return trim($hostId);
|
||||
$nodeId = file_get_contents($idFilePath);
|
||||
if (!empty($nodeId)) {
|
||||
return trim($nodeId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,58 +37,55 @@ class HostServiceBase
|
||||
// 格式: hostname-8位随机字符串
|
||||
$hostname = gethostname() ?: 'unknown_host';
|
||||
$uniqueSuffix = substr(md5(uniqid((string) mt_rand(), true)), 0, 8);
|
||||
$newHostId = "{$hostname}-{$uniqueSuffix}";
|
||||
$newNodeId = "{$hostname}-{$uniqueSuffix}";
|
||||
|
||||
// 3. 将新ID写入文件,以便下次启动时使用
|
||||
try {
|
||||
file_put_contents($idFilePath, $newHostId);
|
||||
file_put_contents($idFilePath, $newNodeId);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('无法写入主机ID文件: ' . $idFilePath . ' - ' . $e->getMessage());
|
||||
Log::error('无法写入节点ID文件: ' . $idFilePath . ' - ' . $e->getMessage());
|
||||
}
|
||||
|
||||
return $newHostId;
|
||||
return $newNodeId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 主机注册与心跳更新
|
||||
* 这是一个原子操作,如果主机不存在则创建,如果存在则更新。
|
||||
* 节点注册与心跳更新
|
||||
* 这是一个原子操作,如果节点不存在则创建,如果存在则更新。
|
||||
*/
|
||||
public static function heartbeat()
|
||||
{
|
||||
$hostId = self::getHostId();
|
||||
if (empty($hostId)) {
|
||||
Log::error('无法获取当前主机ID,心跳更新失败');
|
||||
$nodeId = self::getNodeId();
|
||||
if (empty($nodeId)) {
|
||||
Log::error('无法获取当前节点ID,心跳更新失败');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// findOrEmpty可以避免查询不到时抛出异常
|
||||
$host = SystemHost::where('host_id', $hostId)->findOrEmpty();
|
||||
$host = SystemHost::where('node_id', $nodeId)->find();
|
||||
|
||||
// 收集动态性能指标
|
||||
$data = self::collectHostMetrics();
|
||||
|
||||
if ($host->isEmpty()) {
|
||||
// 首次注册:补充一次性的静态信息
|
||||
$data['host_id'] = $hostId;
|
||||
$data = array_merge($data, self::collectStaticInfo());
|
||||
SystemHost::create($data);
|
||||
Log::info("主机 [{$hostId}] 已成功注册并上线。");
|
||||
} else {
|
||||
// 后续心跳:仅更新动态信息
|
||||
$host->save($data);
|
||||
$data = self::collectHostInfo();
|
||||
if (empty($host)) {
|
||||
$host = new SystemHost();
|
||||
// 首次注册
|
||||
$data['node_id'] = $nodeId;
|
||||
Log::info("节点 [{$nodeId}] 已成功注册并上线。");
|
||||
}
|
||||
|
||||
$host->save($data);
|
||||
} catch (\Exception $e) {
|
||||
Log::error("主机 [{$hostId}] 心跳更新失败: " . $e->getMessage());
|
||||
throw $e;
|
||||
Log::error("节点 [{$nodeId}] 心跳更新失败: " . $e->getMessage());
|
||||
Log::error($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集主机的动态性能指标 (不依赖任何外部系统命令).
|
||||
* 收集主机的性能指标和静态信息 (不依赖任何外部系统命令).
|
||||
* @return array
|
||||
*/
|
||||
public static function collectHostMetrics(): array
|
||||
public static function collectHostInfo(): array
|
||||
{
|
||||
// 获取CPU平均负载 (如果函数存在且未被禁用)
|
||||
$cpuLoad = null;
|
||||
@@ -97,6 +95,7 @@ class HostServiceBase
|
||||
}
|
||||
|
||||
return [
|
||||
// 动态指标
|
||||
'status' => 1,
|
||||
'last_heartbeat_at' => date('Y-m-d H:i:s'),
|
||||
'ip_address' => gethostbyname(gethostname()),
|
||||
@@ -104,16 +103,8 @@ class HostServiceBase
|
||||
'memory_usage' => memory_get_usage(), // false: 获取脚本自身内存占用
|
||||
'disk_free' => disk_free_space(App::getRootPath()),
|
||||
'disk_total' => disk_total_space(App::getRootPath()),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集主机的静态信息 (仅在首次注册时调用).
|
||||
* @return array
|
||||
*/
|
||||
public static function collectStaticInfo(): array
|
||||
{
|
||||
return [
|
||||
// 静态信息
|
||||
'os_info' => php_uname(),
|
||||
'php_version' => PHP_VERSION,
|
||||
];
|
||||
|
||||
74
extend/base/common/service/TimerServiceBase.php
Normal file
74
extend/base/common/service/TimerServiceBase.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace base\common\service;
|
||||
|
||||
use app\common\model\VirtualModel;
|
||||
|
||||
class TimerServiceBase
|
||||
{
|
||||
public static function generateAllRequestList()
|
||||
{
|
||||
$config_list = include app_file_path('common/command/timer/config.php');
|
||||
$request_list = [];
|
||||
foreach ($config_list as $config_item) {
|
||||
$config_item = static::initConfigItem($config_item);
|
||||
|
||||
if ($config_item['name'] == 'http_demo' && !env('adminsystem.is_demo', false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$request_list = array_merge($request_list, static::generateRequestListFromConfig($config_item));
|
||||
}
|
||||
return $request_list;
|
||||
}
|
||||
|
||||
public static function generateRequestListFromConfig($config_item)
|
||||
{
|
||||
$request_list = [];
|
||||
$concurrency = $config_item['concurrency'];
|
||||
|
||||
for ($i = 0; $i < $concurrency; $i++) {
|
||||
$target = $config_item['target'];
|
||||
$params = [
|
||||
'concurrency_id' => $i,
|
||||
'concurrency_count' => $concurrency,
|
||||
];
|
||||
$target_info = parse_url($target);
|
||||
$query_params = [];
|
||||
if (isset($target_info['query'])) {
|
||||
parse_str($target_info['query'], $query_params);
|
||||
}
|
||||
$query_params = array_merge($query_params, $params);
|
||||
$target_info['query'] = http_build_query($query_params);
|
||||
$target = unparse_url($target_info);
|
||||
|
||||
$new_config_item = clone $config_item;
|
||||
$new_config_item['target'] = $target;
|
||||
$new_config_item['concurrency_id'] = $i;
|
||||
|
||||
$request_list[] = $new_config_item;
|
||||
}
|
||||
return $request_list;
|
||||
}
|
||||
|
||||
|
||||
public static function initConfigItem($config)
|
||||
{
|
||||
$default = [
|
||||
'name' => 'http_demo',
|
||||
'type' => 'site',
|
||||
'target' => '',
|
||||
'frequency' => 600,
|
||||
'concurrency' => 1,
|
||||
];
|
||||
|
||||
$data = array_merge($default, $config);
|
||||
|
||||
if ($data['frequency'] < 0) {
|
||||
$data['frequency'] = 0;
|
||||
}
|
||||
$model_timer = new VirtualModel($data);
|
||||
|
||||
return $model_timer;
|
||||
}
|
||||
}
|
||||
22
extend/base/tools/controller/timer/SystemHostBase.php
Normal file
22
extend/base/tools/controller/timer/SystemHostBase.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace base\tools\controller\timer;
|
||||
|
||||
use app\common\controller\TimerController;
|
||||
use app\common\service\HostService;
|
||||
use think\facade\Db;
|
||||
use think\facade\Log;
|
||||
|
||||
class SystemHostBase extends TimerController
|
||||
{
|
||||
protected $frequency = 10;
|
||||
|
||||
public function do()
|
||||
{
|
||||
HostService::heartbeat();
|
||||
|
||||
return 'success';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user