feat: machine mode, ECH subscriptions, batch ops & security hardening

This commit is contained in:
xboard
2026-04-17 02:27:47 +08:00
parent edbd8de356
commit e297b5fe9f
25 changed files with 1564 additions and 343 deletions
@@ -0,0 +1,118 @@
<?php
namespace App\Http\Controllers\V2\Server;
use App\Http\Controllers\Controller;
use App\Models\ServerMachine;
use App\Models\ServerMachineLoadHistory;
use App\Services\ServerService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/**
* machine controller
*/
class MachineController extends Controller
{
/**
* get nodes list for machine
*/
public function nodes(Request $request): JsonResponse
{
$machine = $this->authenticateMachine($request);
$nodes = ServerService::getMachineNodes($machine)
->map(fn($node) => [
'id' => $node->id,
'type' => $node->type,
'name' => $node->name,
])->values();
return response()->json([
'nodes' => $nodes,
'base_config' => [
'push_interval' => (int) admin_setting('server_push_interval', 60),
'pull_interval' => (int) admin_setting('server_pull_interval', 60),
],
]);
}
/**
* report machine status
*/
public function status(Request $request): JsonResponse
{
$request->validate([
'cpu' => 'required|numeric|min:0|max:100',
'mem.total' => 'required|integer|min:0',
'mem.used' => 'required|integer|min:0',
'swap.total' => 'nullable|integer|min:0',
'swap.used' => 'nullable|integer|min:0',
'disk.total' => 'nullable|integer|min:0',
'disk.used' => 'nullable|integer|min:0',
]);
$machine = $this->authenticateMachine($request);
$recordedAt = now()->timestamp;
$machine->forceFill([
'load_status' => [
'cpu' => (float) $request->input('cpu'),
'mem' => [
'total' => (int) $request->input('mem.total'),
'used' => (int) $request->input('mem.used'),
],
'swap' => [
'total' => (int) $request->input('swap.total', 0),
'used' => (int) $request->input('swap.used', 0),
],
'disk' => [
'total' => (int) $request->input('disk.total', 0),
'used' => (int) $request->input('disk.used', 0),
],
'updated_at' => $recordedAt,
],
'last_seen_at' => $recordedAt,
])->save();
ServerMachineLoadHistory::create([
'machine_id' => $machine->id,
'cpu' => (float) $request->input('cpu'),
'mem_total' => (int) $request->input('mem.total'),
'mem_used' => (int) $request->input('mem.used'),
'disk_total' => (int) $request->input('disk.total', 0),
'disk_used' => (int) $request->input('disk.used', 0),
'recorded_at' => $recordedAt,
]);
// Time-based cleanup: keep 24h of data, runs on ~5% of requests
if (random_int(1, 20) === 1) {
ServerMachineLoadHistory::query()
->where('machine_id', $machine->id)
->where('recorded_at', '<', now()->subDay()->timestamp)
->delete();
}
return response()->json(['data' => true]);
}
private function authenticateMachine(Request $request): ServerMachine
{
$request->validate([
'machine_id' => 'required|integer',
'token' => 'required|string',
]);
$machine = ServerMachine::where('id', $request->input('machine_id'))
->where('token', $request->input('token'))
->first();
if (!$machine || !$machine->is_active) {
abort(403, 'Machine not found or disabled');
}
$machine->forceFill(['last_seen_at' => now()->timestamp])->saveQuietly();
return $machine;
}
}
@@ -3,14 +3,9 @@
namespace App\Http\Controllers\V2\Server;
use App\Http\Controllers\Controller;
use App\Services\DeviceStateService;
use App\Services\ServerService;
use App\Services\UserService;
use App\Utils\CacheKey;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Http\JsonResponse;
use Log;
class ServerController extends Controller
{
@@ -43,91 +38,34 @@ class ServerController extends Controller
}
/**
* node report api - merge traffic + alive + status
* POST /api/v2/server/node/report
* node report api - merge traffic + alive + status + metrics
*/
public function report(Request $request): JsonResponse
{
$node = $request->attributes->get('node_info');
$nodeType = $node->type;
$nodeId = $node->id;
Cache::put(CacheKey::get('SERVER_' . strtoupper($nodeType) . '_LAST_CHECK_AT', $nodeId), time(), 3600);
ServerService::touchNode($node);
// hanle traffic data
$traffic = $request->input('traffic');
if (is_array($traffic) && !empty($traffic)) {
$data = array_filter($traffic, function ($item) {
return is_array($item)
&& count($item) === 2
&& is_numeric($item[0])
&& is_numeric($item[1]);
});
if (!empty($data)) {
Cache::put(
CacheKey::get('SERVER_' . strtoupper($nodeType) . '_ONLINE_USER', $nodeId),
count($data),
3600
);
Cache::put(
CacheKey::get('SERVER_' . strtoupper($nodeType) . '_LAST_PUSH_AT', $nodeId),
time(),
3600
);
$userService = new UserService();
$userService->trafficFetch($node, $nodeType, $data);
}
ServerService::processTraffic($node, $traffic);
}
// handle alive data
$alive = $request->input('alive');
if (is_array($alive) && !empty($alive)) {
$deviceStateService = app(DeviceStateService::class);
foreach ($alive as $uid => $ips) {
$deviceStateService->setDevices((int) $uid, $nodeId, (array) $ips);
}
ServerService::processAlive($node->id, $alive);
}
// handle active connections
$online = $request->input('online');
if (is_array($online) && !empty($online)) {
$cacheTime = max(300, (int) admin_setting('server_push_interval', 60) * 3);
foreach ($online as $uid => $conn) {
$cacheKey = CacheKey::get("USER_ONLINE_CONN_{$nodeType}_{$nodeId}", $uid);
Cache::put($cacheKey, (int) $conn, $cacheTime);
}
ServerService::processOnline($node, $online);
}
// handle node status
$status = $request->input('status');
if (is_array($status) && !empty($status)) {
$statusData = [
'cpu' => (float) ($status['cpu'] ?? 0),
'mem' => [
'total' => (int) ($status['mem']['total'] ?? 0),
'used' => (int) ($status['mem']['used'] ?? 0),
],
'swap' => [
'total' => (int) ($status['swap']['total'] ?? 0),
'used' => (int) ($status['swap']['used'] ?? 0),
],
'disk' => [
'total' => (int) ($status['disk']['total'] ?? 0),
'used' => (int) ($status['disk']['used'] ?? 0),
],
'updated_at' => now()->timestamp,
'kernel_status' => $status['kernel_status'] ?? null,
];
$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);
ServerService::processStatus($node, $status);
}
// handle node metrics (Metrics)
$metrics = $request->input('metrics');
if (is_array($metrics) && !empty($metrics)) {
ServerService::updateMetrics($node, $metrics);