diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index f62d4ff..dfe61d2 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -2,10 +2,10 @@ namespace App\Console; -use App\Jobs\Host\DeleteSuspendedHostJob; +use App\Jobs\Host\DeleteHostJob; use App\Jobs\Host\DispatchHostCostQueueJob; use App\Jobs\Host\ScanAllHostsJob; -use App\Jobs\Module\FetchModuleJob; +use App\Jobs\Module\DispatchFetchModuleJob; use App\Jobs\Module\SendModuleEarningsJob; use App\Jobs\User\CheckAndChargeBalanceJob; use App\Jobs\User\ClearTasksJob; @@ -33,8 +33,8 @@ protected function schedule(Schedule $schedule): void // 扣费 $schedule->job(new DispatchHostCostQueueJob(now()->minute))->everyMinute()->withoutOverlapping()->onOneServer(); - // 获取模块暴露的信息(服务器等) - $schedule->job(new FetchModuleJob())->withoutOverlapping()->everyMinute(); + // 获取模块暴露的信息(服务器等,检查模块状态) + $schedule->job(new DispatchFetchModuleJob())->withoutOverlapping()->everyMinute(); // 推送工单 $schedule->job(new PushWorkOrderJob())->everyMinute()->onOneServer(); @@ -45,7 +45,7 @@ protected function schedule(Schedule $schedule): void $schedule->job(new ClearTasksJob())->weekly(); // 删除暂停或部署时间超过 3 天以上的主机 - $schedule->job(new DeleteSuspendedHostJob())->hourly(); + $schedule->job(new DeleteHostJob())->hourly(); // 检查主机是否存在于模块 $schedule->job(new ScanAllHostsJob())->everyThirtyMinutes()->withoutOverlapping()->onOneServer(); diff --git a/app/Http/Controllers/Admin/HostController.php b/app/Http/Controllers/Admin/HostController.php index c8528d3..274ac9b 100644 --- a/app/Http/Controllers/Admin/HostController.php +++ b/app/Http/Controllers/Admin/HostController.php @@ -58,7 +58,7 @@ public function update(Request $request, Host $host): RedirectResponse { $request->validate([ 'name' => 'sometimes|string|max:255', - 'status' => 'sometimes|in:running,stopped,suspended,pending', + 'status' => 'sometimes|in:running,stopped,suspended,pending,locked,unavailable', 'price' => 'sometimes|numeric', 'managed_price' => 'nullable|numeric', ]); diff --git a/app/Http/Controllers/Api/HostController.php b/app/Http/Controllers/Api/HostController.php index 9b081f7..3f38d59 100644 --- a/app/Http/Controllers/Api/HostController.php +++ b/app/Http/Controllers/Api/HostController.php @@ -30,6 +30,10 @@ public function update(HostRequest $request, Host $host): JsonResponse 'status' => 'required|in:running,stopped', ]); + if ($host->status === 'locked' || $host->status === 'unavailable') { + return $this->error('当前主机状态不允许操作'); + } + $user = $request->user(); if ($user->balance < 0.5) { diff --git a/app/Http/Controllers/Api/ModuleController.php b/app/Http/Controllers/Api/ModuleController.php index 7bb037e..3ab62a3 100644 --- a/app/Http/Controllers/Api/ModuleController.php +++ b/app/Http/Controllers/Api/ModuleController.php @@ -7,6 +7,7 @@ use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Http\Response; +use Illuminate\Support\Facades\Cache; class ModuleController extends Controller { @@ -17,6 +18,13 @@ public function index(): JsonResponse return $this->success($modules); } + public function servers(Module $module): JsonResponse + { + $servers = Cache::get('module:' . $module->id . ':servers', []); + + return $this->success($servers); + } + public function call(Request $request, Module $module): JsonResponse|Response { return (new \App\Http\Controllers\Module\ModuleController())->call($request, $module); diff --git a/app/Jobs/Host/DeleteSuspendedHostJob.php b/app/Jobs/Host/DeleteHostJob.php similarity index 61% rename from app/Jobs/Host/DeleteSuspendedHostJob.php rename to app/Jobs/Host/DeleteHostJob.php index 234946f..778a9fb 100644 --- a/app/Jobs/Host/DeleteSuspendedHostJob.php +++ b/app/Jobs/Host/DeleteHostJob.php @@ -10,7 +10,7 @@ // use Illuminate\Contracts\Queue\ShouldBeUnique; -class DeleteSuspendedHostJob implements ShouldQueue +class DeleteHostJob implements ShouldQueue { use InteractsWithQueue, Queueable, SerializesModels; @@ -38,11 +38,25 @@ public function handle(): void } }); - // 查找部署时间超过3天以上的 host + // 查找部署时间超过 3 天以上的 host (new Host)->where('status', 'pending')->where('created_at', '<', now()->subDays(3))->chunk(100, function ($hosts) { foreach ($hosts as $host) { dispatch(new HostJob($host, 'delete')); } }); + + // 查找不可用时间超过 3 天以上的 host + (new Host)->where('status', 'unavailable')->where('unavailable_at', '<', now()->subDays(3))->chunk(100, function ($hosts) { + foreach ($hosts as $host) { + dispatch(new HostJob($host, 'delete')); + } + }); + + // 查找锁定时间超过 3 天以上的 host + (new Host)->where('status', 'locked')->where('locked_at', '<', now()->subDays(3))->chunk(100, function ($hosts) { + foreach ($hosts as $host) { + dispatch(new HostJob($host, 'delete')); + } + }); } } diff --git a/app/Jobs/Host/HostJob.php b/app/Jobs/Host/HostJob.php index 1c9801e..c98bd0b 100644 --- a/app/Jobs/Host/HostJob.php +++ b/app/Jobs/Host/HostJob.php @@ -38,6 +38,13 @@ public function __construct(HostModel $host, $type = 'post') public function handle(): void { $host = $this->host; + + // 忽略 unavailable 状态的 host + if ($host->status === 'unavailable') { + return; + } + + $host->load(['module']); switch ($this->type) { diff --git a/app/Jobs/Host/UpdateOrSuspendedHostJob.php b/app/Jobs/Host/UpdateOrDeleteHostJob.php similarity index 82% rename from app/Jobs/Host/UpdateOrSuspendedHostJob.php rename to app/Jobs/Host/UpdateOrDeleteHostJob.php index 3f8356c..289e155 100644 --- a/app/Jobs/Host/UpdateOrSuspendedHostJob.php +++ b/app/Jobs/Host/UpdateOrDeleteHostJob.php @@ -11,7 +11,7 @@ use Illuminate\Support\Arr; use Illuminate\Support\Facades\Log; -class UpdateOrSuspendedHostJob implements ShouldQueue +class UpdateOrDeleteHostJob implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; @@ -41,8 +41,10 @@ public function handle(): void if ($response['status'] === 200) { $host->update(Arr::except($response['json'], ['id', 'user_id', 'module_id', 'created_at', 'updated_at'])); } else if ($response['status'] === 404) { - Log::warning($host->module->name . ' ' . $host->name . ' ' . $host->id . ' 不存在,标记为暂停。'); - dispatch(new HostJob($host, 'delete')); + Log::warning($host->module->name . ' ' . $host->name . ' ' . $host->id . ' 不存在,标记为不可用。'); + // dispatch(new HostJob($host, 'delete')); + $host->status = 'unavailable'; + $host->save(); } } } diff --git a/app/Jobs/Module/DispatchFetchModuleJob.php b/app/Jobs/Module/DispatchFetchModuleJob.php new file mode 100644 index 0000000..7559e42 --- /dev/null +++ b/app/Jobs/Module/DispatchFetchModuleJob.php @@ -0,0 +1,41 @@ +whereNotNull('url')->chunk(100, function ($modules) { + foreach ($modules as $module) { + dispatch(new FetchModuleJob($module)); + } + }); + } +} diff --git a/app/Jobs/Module/FetchModuleJob.php b/app/Jobs/Module/FetchModuleJob.php index 2207ffa..54a3c55 100644 --- a/app/Jobs/Module/FetchModuleJob.php +++ b/app/Jobs/Module/FetchModuleJob.php @@ -2,10 +2,12 @@ namespace App\Jobs\Module; +use App\Jobs\Host\UpdateOrDeleteHostJob; use App\Models\Module; use Exception; 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\Facades\Cache; @@ -13,16 +15,19 @@ class FetchModuleJob implements ShouldQueue { - use InteractsWithQueue, Queueable, SerializesModels; + use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + + + protected Module $module; /** * Create a new job instance. * * @return void */ - public function __construct() + public function __construct(Module $module) { - // + $this->module = $module; } /** @@ -32,78 +37,62 @@ public function __construct() */ public function handle(): void { - // 获取运行完成的时间 + $module = $this->module; - // $last_run = Cache::get('servers_updated_at', false); - // if ($last_run !== false) { - // // 如果和上次运行时间间隔小于一分钟,则不运行 - // if (now()->diffInMinutes($last_run) < 1) { - // return; - // } - // } + $servers = Cache::get('module:' . $module->id . ':servers', []); - // - (new Module)->whereNotNull('url')->chunk(100, function ($modules) { - $servers = []; + try { + $response = $module->http()->get('remote'); + } catch (Exception $e) { + Log::error($e->getMessage()); + return; + } - foreach ($modules as $module) { - try { - $response = $module->http()->get('remote'); - } catch (Exception $e) { - Log::error($e->getMessage()); - continue; - } - - if ($response->successful()) { - - // 如果模块状态不为 up,则更新为 up - if ($module->status !== 'up') { - $module->status = 'up'; - } - - $json = $response->json(); - - if (isset($json['servers']) && is_array($json['servers'])) { - // 只保留 name, status, meta - $servers = array_merge($servers, array_map(function ($server) use ($module) { - return [ - 'name' => $server['name'], - 'status' => $server['status'], - 'meta' => $server['meta'] ?? [], - 'created_at' => $server['created_at'] ?? now(), - 'updated_at' => $server['updated_at'] ?? now(), - 'module' => [ - 'id' => $module->id, - 'name' => $module->name, - ] - ]; - }, $json['servers'])); - - // broadcast(new Servers($servers)); - } - - } else { - - // if module return maintenance, then set module status to maintenance - if ($response->status() == 503) { - $module->status = 'maintenance'; - } else { - $module->status = 'down'; - } - } - - $module->save(); + if ($response->successful()) { + // 如果模块状态不为 up,则更新为 up + if ($module->status !== 'up') { + $module->status = 'up'; } - // if local - if (config('app.env') === 'local') { - Cache::forever('servers', $servers); + $json = $response->json(); + + if (isset($json['servers']) && is_array($json['servers'])) { + // 只保留 name, status, meta + $servers = array_merge($servers, array_map(function ($server) use ($module) { + return [ + 'name' => $server['name'], + 'status' => $server['status'], + 'meta' => $server['meta'] ?? [], + 'created_at' => $server['created_at'] ?? now(), + 'updated_at' => $server['updated_at'] ?? now(), + 'module' => [ + 'id' => $module->id, + 'name' => $module->name, + ] + ]; + }, $json['servers'])); + + // broadcast(new Servers($servers)); + } + + } else { + // if module return maintenance, then set module status to maintenance + $status = $response->status(); + if ($status == 503 || $status == 429 || $status == 502) { + $module->status = 'maintenance'; } else { - Cache::put('servers', $servers, now()->addMinutes(10)); + $module->status = 'down'; } + } + + $module->save(); + + // if local + if (config('app.env') === 'local') { + Cache::forever('module:' . $module->id . ':servers', $servers); + } else { + Cache::put('module:' . $module->id . ':servers', $servers, now()->addMinutes(10)); + } - // 缓存运行完成的时间 - // Cache::put('servers_updated_at', now(), now()->addMinutes(10)); - }); } } diff --git a/app/Models/Host.php b/app/Models/Host.php index 10c6dd1..9477c08 100644 --- a/app/Models/Host.php +++ b/app/Models/Host.php @@ -4,7 +4,7 @@ use App\Events\Users; use App\Jobs\Host\HostJob; -use App\Jobs\Host\UpdateOrSuspendedHostJob; +use App\Jobs\Host\UpdateOrDeleteHostJob; use App\Notifications\WebNotification; use GeneaLabs\LaravelModelCaching\Traits\Cachable; use Illuminate\Database\Eloquent\Collection; @@ -54,23 +54,35 @@ protected static function boot() }); static::created(function (self $model) { - $model->user->notify(new WebNotification($model, 'hosts.created')); - }); - static::updating(function ($model) { + static::updating(function (self $model) { if ($model->isDirty('status')) { if ($model->status == 'suspended') { $model->suspended_at = now(); } else { $model->suspended_at = null; } + + if ($model->status == 'locked') { + $model->locked_at = now(); + } else { + $model->locked_at = null; + } + + if ($model->status == 'unavailable') { + $model->unavailable_at = now(); + } else { + $model->unavailable_at = null; + } } // 调度任务 - dispatch(new HostJob($model, 'patch')); + if ($model->status !== 'unavailable') { + dispatch(new HostJob($model, 'patch')); + } broadcast(new Users($model->user_id, 'hosts.updating', $model)); }); @@ -307,7 +319,7 @@ public function addLog(string $amount = "0"): bool public function updateOrDelete(): bool { - dispatch(new UpdateOrSuspendedHostJob($this)); + dispatch(new UpdateOrDeleteHostJob($this)); return true; } diff --git a/app/Models/Module.php b/app/Models/Module.php index 2e64917..cc80e6a 100644 --- a/app/Models/Module.php +++ b/app/Models/Module.php @@ -20,10 +20,9 @@ class Module extends Authenticatable public $incrementing = false; - // primary key - public $timestamps = false; protected $table = 'modules'; protected $keyType = 'string'; + protected $fillable = [ 'id', 'name', diff --git a/database/migrations/2023_01_20_153636_add_unavailable_status_to_hosts_table.php b/database/migrations/2023_01_20_153636_add_unavailable_status_to_hosts_table.php new file mode 100644 index 0000000..0962c78 --- /dev/null +++ b/database/migrations/2023_01_20_153636_add_unavailable_status_to_hosts_table.php @@ -0,0 +1,36 @@ +timestamp('unavailable_at')->nullable()->comment('不可用时间')->after('suspended_at'); + $table->timestamp('locked_at')->nullable()->comment('锁定时间')->after('unavailable_at'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down(): void + { + \Illuminate\Support\Facades\DB::statement("ALTER TABLE `hosts` CHANGE `status` `status` ENUM('running','stopped','error','suspended','pending') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'pending';"); + + \Illuminate\Support\Facades\Schema::table('hosts', function (Blueprint $table) { + $table->dropColumn('unavailable_at'); + $table->dropColumn('locked_at'); + }); + } +}; diff --git a/resources/views/components/host-status.blade.php b/resources/views/components/host-status.blade.php index 8a118bb..14e96e6 100644 --- a/resources/views/components/host-status.blade.php +++ b/resources/views/components/host-status.blade.php @@ -20,6 +20,14 @@ 错误 @break + @case('unavailable') + 不可用 + @break + + @case('locked') + 锁定 + @break + @default {{ $status }} @endswitch