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` 語系檔,並完成字母排序與反逸出斜線處理。
120 lines
4.3 KiB
PHP
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;
|
|
}
|
|
}
|