feat: Add node load submission and display functionality

- Implemented node load status submission in UniProxyController with dynamic cache expiration based on server push interval.
- Added log filtering capability in the admin panel for better log management and analysis.
This commit is contained in:
xboard
2025-05-24 12:31:18 +08:00
parent 61300fbcc3
commit a3700ad685
13 changed files with 270 additions and 75 deletions
@@ -211,4 +211,48 @@ class UniProxyController extends Controller
$this->userOnlineService->updateAliveData($data, $node->type, $node->id);
return response()->json(['data' => true]);
}
// 提交节点负载状态
public function status(Request $request): JsonResponse
{
$node = $request->input('node_info');
$data = $request->validate([
'cpu' => 'required|numeric|min:0|max:100',
'mem.total' => 'required|integer|min:0',
'mem.used' => 'required|integer|min:0',
'swap.total' => 'required|integer|min:0',
'swap.used' => 'required|integer|min:0',
'disk.total' => 'required|integer|min:0',
'disk.used' => 'required|integer|min:0',
]);
$nodeType = $node->type;
$nodeId = $node->id;
$statusData = [
'cpu' => (float) $data['cpu'],
'mem' => [
'total' => (int) $data['mem']['total'],
'used' => (int) $data['mem']['used'],
],
'swap' => [
'total' => (int) $data['swap']['total'],
'used' => (int) $data['swap']['used'],
],
'disk' => [
'total' => (int) $data['disk']['total'],
'used' => (int) $data['disk']['used'],
],
'updated_at' => now()->timestamp,
];
$cacheTime = max(300, (int) admin_setting('server_push_interval', 60) * 3);
cache([
CacheKey::get('SERVER_' . strtoupper($nodeType) . '_LOAD_STATUS', $nodeId) => $statusData,
CacheKey::get('SERVER_' . strtoupper($nodeType) . '_LAST_LOAD_AT', $nodeId) => now()->timestamp,
], $cacheTime);
return response()->json(['data' => true, "code" => 0, "message" => "success"]);
}
}
@@ -128,11 +128,26 @@ class SystemController extends Controller
{
$current = $request->input('current') ? $request->input('current') : 1;
$pageSize = $request->input('page_size') >= 10 ? $request->input('page_size') : 10;
$level = $request->input('level');
$keyword = $request->input('keyword');
$builder = LogModel::orderBy('created_at', 'DESC')
->setFilterAllowKeys('level');
->when($level, function ($query) use ($level) {
return $query->where('level', strtoupper($level));
})
->when($keyword, function ($query) use ($keyword) {
return $query->where(function ($q) use ($keyword) {
$q->where('data', 'like', '%' . $keyword . '%')
->orWhere('context', 'like', '%' . $keyword . '%')
->orWhere('title', 'like', '%' . $keyword . '%')
->orWhere('uri', 'like', '%' . $keyword . '%');
});
});
$total = $builder->count();
$res = $builder->forPage($current, $pageSize)
->get();
return response([
'data' => $res,
'total' => $total
+1
View File
@@ -23,6 +23,7 @@ class ServerRoute
$route->post('push', [UniProxyController::class, 'push']);
$route->post('alive', [UniProxyController::class, 'alive']);
$route->get('alivelist', [UniProxyController::class, 'alivelist']);
$route->post('status', [UniProxyController::class, 'status']);
});
$router->group([
'prefix' => 'ShadowsocksTidalab',
+15
View File
@@ -47,6 +47,7 @@ use Illuminate\Database\Eloquent\Casts\Attribute;
* @property int|null $u 上行流量
* @property int|null $d 下行流量
* @property int|null $total 总流量
* @property-read array|null $load_status 负载状态(包含CPU、内存、交换区、磁盘信息)
*/
class Server extends Model
{
@@ -432,4 +433,18 @@ class Server extends Model
}
);
}
/**
* 负载状态访问器
*/
protected function loadStatus(): Attribute
{
return Attribute::make(
get: function () {
$type = strtoupper($this->type);
$serverId = $this->parent_id ?: $this->id;
return Cache::get(CacheKey::get("SERVER_{$type}_LOAD_STATUS", $serverId));
}
);
}
}
+2 -1
View File
@@ -25,7 +25,8 @@ class ServerService
'online',
'is_online',
'available_status',
'cache_key'
'cache_key',
'load_status'
]);
}
+1 -1
View File
@@ -40,7 +40,7 @@ class UpdateService
list($date, $hash) = explode(':', trim($result->output()));
Cache::forever(self::CACHE_VERSION_DATE, $date);
Cache::forever(self::CACHE_VERSION, substr($hash, 0, 7));
Log::info('Version cache updated: ' . $date . '-' . substr($hash, 0, 7));
// Log::info('Version cache updated: ' . $date . '-' . substr($hash, 0, 7));
return;
}
} catch (\Exception $e) {
+45 -49
View File
@@ -4,53 +4,10 @@ namespace App\Utils;
class CacheKey
{
const KEYS = [
// 核心缓存键定义
const CORE_KEYS = [
'EMAIL_VERIFY_CODE' => '邮箱验证码',
'LAST_SEND_EMAIL_VERIFY_TIMESTAMP' => '最后一次发送邮箱验证码时间',
'SERVER_VMESS_ONLINE_USER' => '节点在线用户',
'MULTI_SERVER_VMESS_ONLINE_USER' => '节点多服务器在线用户',
'SERVER_VMESS_LAST_CHECK_AT' => '节点最后检查时间',
'SERVER_VMESS_LAST_PUSH_AT' => '节点最后推送时间',
'SERVER_TROJAN_ONLINE_USER' => 'trojan节点在线用户',
'MULTI_SERVER_TROJAN_ONLINE_USER' => 'trojan节点多服务器在线用户',
'SERVER_TROJAN_LAST_CHECK_AT' => 'trojan节点最后检查时间',
'SERVER_TROJAN_LAST_PUSH_AT' => 'trojan节点最后推送时间',
'SERVER_SHADOWSOCKS_ONLINE_USER' => 'ss节点在线用户',
'MULTI_SERVER_SHADOWSOCKS_ONLINE_USER' => 'ss节点多服务器在线用户',
'SERVER_SHADOWSOCKS_LAST_CHECK_AT' => 'ss节点最后检查时间',
'SERVER_SHADOWSOCKS_LAST_PUSH_AT' => 'ss节点最后推送时间',
'SERVER_HYSTERIA_ONLINE_USER' => 'hysteria节点在线用户',
'MULTI_SERVER_HYSTERIA_ONLINE_USER' => 'hysteria节点多服务器在线用户',
'SERVER_HYSTERIA_LAST_CHECK_AT' => 'hysteria节点最后检查时间',
'SERVER_HYSTERIA_LAST_PUSH_AT' => 'hysteria节点最后推送时间',
'SERVER_VLESS_ONLINE_USER' => 'vless节点在线用户',
'MULTI_SERVER_VLESS_ONLINE_USER' => 'vless节点多服务器在线用户',
'SERVER_VLESS_LAST_CHECK_AT' => 'vless节点最后检查时间',
'SERVER_VLESS_LAST_PUSH_AT' => 'vless节点最后推送时间',
'SERVER_TUIC_ONLINE_USER' => 'TUIC节点在线用户',
'MULTI_SERVER_TUIC_ONLINE_USER' => 'TUIC节点多服务器在线用户',
'SERVER_TUIC_LAST_CHECK_AT' => 'TUIC节点最后检查时间',
'SERVER_TUIC_LAST_PUSH_AT' => 'TUIC节点最后推送时间',
'SERVER_ANYTLS_ONLINE_USER' => 'ANYTLS节点在线用户',
'MULTI_SERVER_ANYTLS_ONLINE_USER' => 'ANYTLS节点多服务器在线用户',
'SERVER_ANYTLS_LAST_CHECK_AT' => 'ANYTLS节点最后检查时间',
'SERVER_ANYTLS_LAST_PUSH_AT' => 'ANYTLS节点最后推送时间',
'SERVER_SOCKS_ONLINE_USER' => 'socks节点在线用户',
'MULTI_SERVER_SOCKS_ONLINE_USER' => 'socks节点多服务器在线用户',
'SERVER_SOCKS_LAST_CHECK_AT' => 'socks节点最后检查时间',
'SERVER_SOCKS_LAST_PUSH_AT' => 'socks节点最后推送时间',
'SERVER_NAIVE_ONLINE_USER' => 'naive节点在线用户',
'MULTI_SERVER_NAIVE_ONLINE_USER' => 'naive节点多服务器在线用户',
'SERVER_NAIVE_LAST_CHECK_AT' => 'naive节点最后检查时间',
'SERVER_NAIVE_LAST_PUSH_AT' => 'naive节点最后推送时间',
'SERVER_HTTP_ONLINE_USER' => 'http节点在线用户',
'MULTI_SERVER_HTTP_ONLINE_USER' => 'http节点多服务器在线用户',
'SERVER_HTTP_LAST_CHECK_AT' => 'http节点最后检查时间',
'SERVER_HTTP_LAST_PUSH_AT' => 'http节点最后推送时间',
'SERVER_MIERU_ONLINE_USER' => 'mieru节点在线用户',
'MULTI_SERVER_MIERU_ONLINE_USER' => 'mieru节点多服务器在线用户',
'SERVER_MIERU_LAST_CHECK_AT' => 'mieru节点最后检查时间',
'SERVER_MIERU_LAST_PUSH_AT' => 'mieru节点最后推送时间',
'TEMP_TOKEN' => '临时令牌',
'LAST_SEND_EMAIL_REMIND_TRAFFIC' => '最后发送流量邮件提醒',
'SCHEDULE_LAST_CHECK_AT' => '计划任务最后检查时间',
@@ -61,11 +18,50 @@ class CacheKey
'FORGET_REQUEST_LIMIT' => '找回密码次数限制'
];
public static function get(string $key, $uniqueValue)
// 允许的缓存键模式(支持通配符)
const ALLOWED_PATTERNS = [
'SERVER_*_ONLINE_USER', // 节点在线用户
'MULTI_SERVER_*_ONLINE_USER', // 多服务器在线用户
'SERVER_*_LAST_CHECK_AT', // 节点最后检查时间
'SERVER_*_LAST_PUSH_AT', // 节点最后推送时间
'SERVER_*_LOAD_STATUS', // 节点负载状态
'SERVER_*_LAST_LOAD_AT', // 节点最后负载提交时间
];
/**
* 生成缓存键
*/
public static function get(string $key, $uniqueValue = null): string
{
if (!in_array($key, array_keys(self::KEYS))) {
abort(500, 'key is not in cache key list');
// 检查是否为核心键
if (array_key_exists($key, self::CORE_KEYS)) {
return $uniqueValue ? $key . '_' . $uniqueValue : $key;
}
return $key . '_' . $uniqueValue;
// 检查是否匹配允许的模式
if (self::matchesPattern($key)) {
return $uniqueValue ? $key . '_' . $uniqueValue : $key;
}
// 开发环境下记录警告,生产环境允许通过
if (app()->environment('local', 'development')) {
logger()->warning("Unknown cache key used: {$key}");
}
return $uniqueValue ? $key . '_' . $uniqueValue : $key;
}
/**
* 检查键名是否匹配允许的模式
*/
private static function matchesPattern(string $key): bool
{
foreach (self::ALLOWED_PATTERNS as $pattern) {
$regex = '/^' . str_replace('*', '[A-Z_]+', $pattern) . '$/';
if (preg_match($regex, $key)) {
return true;
}
}
return false;
}
}