diff --git a/app/common/service/TimerService.php b/app/common/service/TimerService.php new file mode 100644 index 0000000..42a7643 --- /dev/null +++ b/app/common/service/TimerService.php @@ -0,0 +1,9 @@ + true, + 'auto_timestamp' => 'int', // 时间字段取出后的默认时间格式 'datetime_format' => 'Y-m-d H:i:s', diff --git a/extend/base/common/command/TimerBase.php b/extend/base/common/command/TimerBase.php index f575e18..89e051a 100644 --- a/extend/base/common/command/TimerBase.php +++ b/extend/base/common/command/TimerBase.php @@ -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; - } } diff --git a/extend/base/common/service/HostServiceBase.php b/extend/base/common/service/HostServiceBase.php index 9215705..18c26a7 100644 --- a/extend/base/common/service/HostServiceBase.php +++ b/extend/base/common/service/HostServiceBase.php @@ -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, ]; diff --git a/extend/base/common/service/TimerServiceBase.php b/extend/base/common/service/TimerServiceBase.php new file mode 100644 index 0000000..5263f66 --- /dev/null +++ b/extend/base/common/service/TimerServiceBase.php @@ -0,0 +1,74 @@ + $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; + } +} diff --git a/extend/base/tools/controller/timer/SystemHostBase.php b/extend/base/tools/controller/timer/SystemHostBase.php new file mode 100644 index 0000000..3245a37 --- /dev/null +++ b/extend/base/tools/controller/timer/SystemHostBase.php @@ -0,0 +1,22 @@ +