star-cloud/app/Console/Commands/Machine/ProcessOtaSchedules.php
sky121113 fb1bcf49e1 [FEAT] 新增 OTA 預約排程與部署狀態展示,並優化版本排序與 UI
1. 建立 `ota_schedules` 資料表與 `OtaSchedule` Model,提供 OTA 預約排程基礎儲存結構。
2. 建立 `ota:process-schedules` 背景 Artisan Command 並註冊至 Console Kernel,每分鐘自動執行到期的預約並透過 MQTT 發送。
3. 擴充 `ApkVersionController` index 方法:加入加載列表時自動觸發排程的防呆機制,並在記憶體中快速統計 pending 與 completed 的機台。
4. 擴充 `ApkVersionController` store 與 push 方法,移除已用不到的 `is_forced` (更新模式) 限制,並支援 `scheduled_at` 預約時間的寫入。
5. 調整 `create.blade.php` 頁面,移除「強制更新」的 Switch 欄位,僅保留注意事項防錯提示。
6. 調整 `index.blade.php` 頁面,將「更新模式」改為「部署狀態」一欄,整合系統內建的 `.tooltip` 與原生 `title` 屬性,在加載與刷新時 100% 穩定展示 Hover 機台名單。
7. 優化 `index.blade.php` 彈窗,將原生時間輸入框改為 Flatpickr 樣式,加入日曆圖標並支援動態 locale 語言切換(中/英/日),將標籤統一命名為「選擇更新時間」。
8. 同步更新並對齊中、英、日三語系 `lang/*.json` 語系檔,並完成字母排序與反逸出斜線處理。
2026-05-29 10:55:23 +08:00

120 lines
4.3 KiB
PHP

<?php
namespace App\Console\Commands\Machine;
use Illuminate\Console\Command;
use App\Models\Machine\OtaSchedule;
use App\Models\Machine\Machine;
use App\Models\Machine\RemoteCommand;
use App\Services\Machine\MqttService;
use Exception;
use Illuminate\Support\Facades\Log;
class ProcessOtaSchedules extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'ota:process-schedules';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Process scheduled OTA firmware updates and send MQTT commands to target machines';
/**
* Execute the console command.
*/
public function handle(MqttService $mqttService)
{
$this->info('[' . now()->toDateTimeString() . '] Starting to process scheduled OTA updates...');
// 撈取狀態為待發送且時間已到的排程
$schedules = OtaSchedule::where('status', 'pending')
->where('scheduled_at', '<=', now())
->with('apkVersion')
->get();
if ($schedules->isEmpty()) {
$this->info('No pending OTA schedules to process.');
return 0;
}
$this->info("Found {$schedules->count()} pending OTA schedule(s) to process.");
foreach ($schedules as $schedule) {
$this->info("Processing OTA Schedule ID: {$schedule->id} (APK: {$schedule->apkVersion->version_name})");
// 標記為處理中,防止重複執行
$schedule->update(['status' => 'processing']);
$apkVersion = $schedule->apkVersion;
try {
// 根據 target_type 撈取目標機台
if ($schedule->target_type === 'all') {
$machines = Machine::all();
} else {
$machineIds = $schedule->target_value ?? [];
$machines = Machine::whereIn('id', $machineIds)->get();
}
if ($machines->isEmpty()) {
$this->warn("No target machines found for OTA Schedule ID: {$schedule->id}. Skipping.");
$schedule->update(['status' => 'completed']);
continue;
}
$successCount = 0;
foreach ($machines as $machine) {
// 1. 先建立 RemoteCommand 紀錄
$command = RemoteCommand::create([
'machine_id' => $machine->id,
'user_id' => $schedule->user_id,
'command_type' => 'update_app',
'status' => 'pending',
'payload' => [
'url' => $apkVersion->url,
'version' => $apkVersion->version_name,
'version_code' => $apkVersion->version_code,
'is_forced' => $apkVersion->is_forced,
],
]);
// 2. 推播 MQTT 更新指令
$pushed = $mqttService->pushCommand(
$machine->serial_no,
'update_app',
$command->payload,
(string) $command->id
);
if ($pushed) {
$successCount++;
} else {
$command->update(['status' => 'failed']);
$this->error("Failed to push MQTT OTA command to machine: {$machine->serial_no}");
}
}
$schedule->update(['status' => 'completed']);
$this->info("Successfully processed OTA Schedule ID: {$schedule->id}. MQTT deployed to {$successCount}/{$machines->count()} machines.");
} catch (Exception $e) {
Log::error("Error processing OTA Schedule ID {$schedule->id}: " . $e->getMessage(), [
'exception' => $e
]);
$schedule->update(['status' => 'failed']);
$this->error("Error processing OTA Schedule ID {$schedule->id}: " . $e->getMessage());
}
}
$this->info('OTA schedules processing completed.');
return 0;
}
}