From a58d66d72e8daa22a9d70da0053e53a4d593caf3 Mon Sep 17 00:00:00 2001 From: xboard Date: Mon, 30 Mar 2026 02:50:56 +0800 Subject: [PATCH] feat: node traffic limit & batch operations - Traffic monitoring with transfer_enable limit - Batch delete nodes - Reset traffic (single/batch) --- .../V2/Admin/Server/ManageController.php | 92 ++++++++++++++++++- app/Http/Requests/Admin/ServerSave.php | 3 + app/Http/Routes/V2/AdminRoute.php | 3 + app/Jobs/StatServerJob.php | 13 +++ app/Models/Server.php | 7 ++ app/Services/ServerService.php | 13 ++- ...8_161536_add_traffic_fields_to_servers.php | 45 +++++++++ public/assets/admin | 2 +- 8 files changed, 171 insertions(+), 7 deletions(-) create mode 100644 database/migrations/2026_03_28_161536_add_traffic_fields_to_servers.php diff --git a/app/Http/Controllers/V2/Admin/Server/ManageController.php b/app/Http/Controllers/V2/Admin/Server/ManageController.php index f350e57..41a4bac 100644 --- a/app/Http/Controllers/V2/Admin/Server/ManageController.php +++ b/app/Http/Controllers/V2/Admin/Server/ManageController.php @@ -111,6 +111,94 @@ class ManageController extends Controller return $this->success(true); } + /** + * 批量删除节点 + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse + */ + public function batchDelete(Request $request) + { + $request->validate([ + 'ids' => 'required|array', + 'ids.*' => 'integer', + ]); + + $ids = $request->input('ids'); + if (empty($ids)) { + return $this->fail([400, '请选择要删除的节点']); + } + + try { + $deleted = Server::whereIn('id', $ids)->delete(); + if ($deleted === false) { + return $this->fail([500, '批量删除失败']); + } + return $this->success(true); + } catch (\Exception $e) { + Log::error($e); + return $this->fail([500, '批量删除失败']); + } + } + + /** + * 重置节点流量 + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse + */ + public function resetTraffic(Request $request) + { + $request->validate([ + 'id' => 'required|integer', + ]); + + $server = Server::find($request->id); + if (!$server) { + return $this->fail([400202, '服务器不存在']); + } + + try { + $server->u = 0; + $server->d = 0; + $server->save(); + + Log::info("Server {$server->id} ({$server->name}) traffic reset by admin"); + return $this->success(true); + } catch (\Exception $e) { + Log::error($e); + return $this->fail([500, '重置失败']); + } + } + + /** + * 批量重置节点流量 + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse + */ + public function batchResetTraffic(Request $request) + { + $request->validate([ + 'ids' => 'required|array', + 'ids.*' => 'integer', + ]); + + $ids = $request->input('ids'); + if (empty($ids)) { + return $this->fail([400, '请选择要重置的节点']); + } + + try { + Server::whereIn('id', $ids)->update([ + 'u' => 0, + 'd' => 0, + ]); + + Log::info("Servers " . implode(',', $ids) . " traffic reset by admin"); + return $this->success(true); + } catch (\Exception $e) { + Log::error($e); + return $this->fail([500, '批量重置失败']); + } + } /** * 复制节点 @@ -120,11 +208,11 @@ class ManageController extends Controller public function copy(Request $request) { $server = Server::find($request->input('id')); - $server->show = 0; - $server->code = null; if (!$server) { return $this->fail([400202, '服务器不存在']); } + $server->show = 0; + $server->code = null; Server::create($server->toArray()); return $this->success(true); } diff --git a/app/Http/Requests/Admin/ServerSave.php b/app/Http/Requests/Admin/ServerSave.php index 9932302..1038350 100644 --- a/app/Http/Requests/Admin/ServerSave.php +++ b/app/Http/Requests/Admin/ServerSave.php @@ -128,6 +128,7 @@ class ServerSave extends FormRequest 'rate_time_ranges.*.end' => 'required_with:rate_time_ranges|string|date_format:H:i', 'rate_time_ranges.*.rate' => 'required_with:rate_time_ranges|numeric|min:0', 'protocol_settings' => 'array', + 'transfer_enable' => 'nullable|integer|min:0', ]; } @@ -200,6 +201,8 @@ class ServerSave extends FormRequest 'protocol_settings.*.string' => ':attribute 必须是字符串', 'protocol_settings.*.integer' => ':attribute 必须是整数', 'protocol_settings.*.in' => ':attribute 的值不合法', + 'transfer_enable.integer' => '流量上限必须是整数', + 'transfer_enable.min' => '流量上限不能小于0', ]; } } diff --git a/app/Http/Routes/V2/AdminRoute.php b/app/Http/Routes/V2/AdminRoute.php index 5b11660..3d8c910 100644 --- a/app/Http/Routes/V2/AdminRoute.php +++ b/app/Http/Routes/V2/AdminRoute.php @@ -82,6 +82,9 @@ class AdminRoute $router->post('/drop', [ManageController::class, 'drop']); $router->post('/copy', [ManageController::class, 'copy']); $router->post('/sort', [ManageController::class, 'sort']); + $router->post('/batchDelete', [ManageController::class, 'batchDelete']); + $router->post('/resetTraffic', [ManageController::class, 'resetTraffic']); + $router->post('/batchResetTraffic', [ManageController::class, 'batchResetTraffic']); }); // Order diff --git a/app/Jobs/StatServerJob.php b/app/Jobs/StatServerJob.php index e8460a6..c055ca5 100644 --- a/app/Jobs/StatServerJob.php +++ b/app/Jobs/StatServerJob.php @@ -3,12 +3,14 @@ namespace App\Jobs; +use App\Models\Server; use App\Models\StatServer; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; +use Illuminate\Support\Carbon; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; @@ -59,12 +61,23 @@ class StatServerJob implements ShouldQueue try { $this->processServerStat($u, $d, $recordAt); + $this->updateServerTraffic($u, $d); } catch (\Exception $e) { Log::error('StatServerJob failed for server ' . $this->server['id'] . ': ' . $e->getMessage()); throw $e; } } + protected function updateServerTraffic(int $u, int $d): void + { + DB::table('v2_server') + ->where('id', $this->server['id']) + ->incrementEach( + ['u' => $u, 'd' => $d], + ['updated_at' => Carbon::now()] + ); + } + protected function processServerStat(int $u, int $d, int $recordAt): void { $driver = config('database.default'); diff --git a/app/Models/Server.php b/app/Models/Server.php index ff91889..e04a236 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -52,6 +52,10 @@ use Illuminate\Database\Eloquent\Casts\Attribute; * @property int|null $d 下行流量 * @property int|null $total 总流量 * @property-read array|null $load_status 负载状态(包含CPU、内存、交换区、磁盘信息) + * + * @property int $transfer_enable 流量上限,0或者null表示不限制 + * @property int $u 当前上传流量 + * @property int $d 当前下载流量 */ class Server extends Model { @@ -124,6 +128,9 @@ class Server extends Model 'updated_at' => 'timestamp', 'rate_time_ranges' => 'array', 'rate_time_enable' => 'boolean', + 'transfer_enable' => 'integer', + 'u' => 'integer', + 'd' => 'integer', ]; private const MULTIPLEX_CONFIGURATION = [ diff --git a/app/Services/ServerService.php b/app/Services/ServerService.php index 3faa23c..6a08896 100644 --- a/app/Services/ServerService.php +++ b/app/Services/ServerService.php @@ -42,6 +42,11 @@ class ServerService { $servers = Server::whereJsonContains('group_ids', (string) $user->group_id) ->where('show', true) + ->where(function ($query) { + $query->whereNull('transfer_enable') + ->orWhere('transfer_enable', 0) + ->orWhereRaw('u + d < transfer_enable'); + }) ->orderBy('sort', 'ASC') ->get() ->append(['last_check_at', 'last_push_at', 'online', 'is_online', 'available_status', 'cache_key', 'server_key']); @@ -244,10 +249,10 @@ class ServerService default => [], }; - $response = array_filter( - $response, - static fn ($value) => $value !== null - ); + // $response = array_filter( + // $response, + // static fn ($value) => $value !== null + // ); if (!empty($node['route_ids'])) { $response['routes'] = self::getRoutes($node['route_ids']); diff --git a/database/migrations/2026_03_28_161536_add_traffic_fields_to_servers.php b/database/migrations/2026_03_28_161536_add_traffic_fields_to_servers.php new file mode 100644 index 0000000..a9763e1 --- /dev/null +++ b/database/migrations/2026_03_28_161536_add_traffic_fields_to_servers.php @@ -0,0 +1,45 @@ +bigInteger('transfer_enable') + ->default(0) + ->after('rate') + ->comment('Traffic limit , 0 or null=no limit'); + } + if (!Schema::hasColumn('v2_server', 'u')) { + $table->bigInteger('u') + ->default(0) + ->after('transfer_enable') + ->comment('upload traffic'); + } + if (!Schema::hasColumn('v2_server', 'd')) { + $table->bigInteger('d') + ->default(0) + ->after('u') + ->comment('donwload traffic'); + } + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('v2_server', function (Blueprint $table) { + $table->dropColumn(['transfer_enable', 'u', 'd']); + }); + } +}; diff --git a/public/assets/admin b/public/assets/admin index 89c0577..9b2d136 160000 --- a/public/assets/admin +++ b/public/assets/admin @@ -1 +1 @@ -Subproject commit 89c0577191adea858dbc1d48ae154aa6cc91bc1a +Subproject commit 9b2d136d811d60fa1a363ea2057e5c15f01a7140