mirror of
https://gitee.com/ulthon/ulthon_admin.git
synced 2026-07-01 15:32:48 +08:00
122 lines
4.0 KiB
PHP
122 lines
4.0 KiB
PHP
|
||
<?php
|
||
|
||
namespace base\common\service;
|
||
|
||
use app\common\model\SystemHost;
|
||
use think\facade\App;
|
||
use think\facade\Log;
|
||
|
||
/**
|
||
* 主机(节点)服务 - 基础类
|
||
* 用于注册、上报主机心跳及性能指标
|
||
*/
|
||
class HostServiceBase
|
||
{
|
||
/**
|
||
* 获取当前主机的唯一ID。
|
||
* 该方法确保即使在多个具有相同hostname的隔离网络中,ID也是唯一的。
|
||
* 它会在首次运行时生成一个ID并持久化到本地文件中,以保证重启后ID不变。
|
||
*
|
||
* @return string
|
||
*/
|
||
public static function getHostId(): string
|
||
{
|
||
// 定义一个持久化存储ID的文件路径 (runtime目录在部署时通常是可写的)
|
||
$idFilePath = App::getRuntimePath() . 'host_id.lock';
|
||
|
||
// 1. 如果ID文件已存在,直接读取并返回
|
||
if (file_exists($idFilePath)) {
|
||
$hostId = file_get_contents($idFilePath);
|
||
if (!empty($hostId)) {
|
||
return trim($hostId);
|
||
}
|
||
}
|
||
|
||
// 2. 如果文件不存在或为空,生成新的唯一ID
|
||
// 格式: hostname-8位随机字符串
|
||
$hostname = gethostname() ?: 'unknown_host';
|
||
$uniqueSuffix = substr(md5(uniqid((string)mt_rand(), true)), 0, 8);
|
||
$newHostId = "{$hostname}-{$uniqueSuffix}";
|
||
|
||
// 3. 将新ID写入文件,以便下次启动时使用
|
||
try {
|
||
file_put_contents($idFilePath, $newHostId);
|
||
} catch (\Exception $e) {
|
||
Log::error('无法写入主机ID文件: ' . $idFilePath . ' - ' . $e->getMessage());
|
||
}
|
||
|
||
return $newHostId;
|
||
}
|
||
|
||
/**
|
||
* 主机注册与心跳更新
|
||
* 这是一个原子操作,如果主机不存在则创建,如果存在则更新。
|
||
*/
|
||
public static function heartbeat()
|
||
{
|
||
$hostId = self::getHostId();
|
||
if (empty($hostId)) {
|
||
Log::error('无法获取当前主机ID,心跳更新失败');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// findOrEmpty可以避免查询不到时抛出异常
|
||
$host = SystemHost::where('host_id', $hostId)->findOrEmpty();
|
||
|
||
// 收集动态性能指标
|
||
$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);
|
||
}
|
||
} catch (\Exception $e) {
|
||
Log::error("主机 [{$hostId}] 心跳更新失败: " . $e->getMessage());
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 收集主机的动态性能指标 (不依赖任何外部系统命令)
|
||
* @return array
|
||
*/
|
||
public static function collectHostMetrics(): array
|
||
{
|
||
// 获取CPU平均负载 (如果函数存在且未被禁用)
|
||
$cpuLoad = null;
|
||
if (function_exists('sys_getloadavg')) {
|
||
$load = sys_getloadavg();
|
||
$cpuLoad = is_array($load) ? implode(',', array_map(fn($l) => round($l, 2), $load)) : null;
|
||
}
|
||
|
||
return [
|
||
'status' => 1,
|
||
'last_heartbeat_at' => date('Y-m-d H:i:s'),
|
||
'ip_address' => gethostbyname(gethostname()),
|
||
'cpu_load' => $cpuLoad,
|
||
'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,
|
||
];
|
||
}
|
||
}
|