1. 新增 `ApkVersion` Model 與對應的資料表遷移檔 (`create_apk_versions_table`),用以記錄 APK 版本、代碼、風味、更新說明、下載連結與更新模式等欄位。 2. 實作 `ApkVersionController` 控制器,提供 APK 版本之 CRUD(列表、建立、刪除)以及 OTA 推送功能。 3. 移除了 `SpecialPermissionController` 中舊有的 `apkVersions` 占位路由方法,並於 `routes/web.php` 的基本設定 (`basic-settings`) 下新增完整的 APK 版本管理與推送路由。 4. 於 `RoleSeeder.php` 角色種子資料中為 Super Admin 與系統管理員新增 `menu.basic.apk-versions` 權限。 5. 於 `sidebar-menu.blade.php` 基本設定選單中,加入「APK版本管理」之子選單選項,並以對應權限進行防護。 6. 實作 APK 版本列表與上傳頁面 Blade 視圖,採用 Preline UI 與 Alpine.js 提供極簡奢華風的 UI 互動,包含檔案上傳拖曳與 OTA 設備選擇部署彈窗。 7. 更新繁體中文語系檔 `zh_TW.json`,對齊所有新增之 OTA 韌體與版本管理翻譯字詞。
119 lines
4.2 KiB
PHP
119 lines
4.2 KiB
PHP
<?php
|
||
|
||
namespace App\Http\Controllers\Admin\BasicSettings;
|
||
|
||
use App\Http\Controllers\Admin\AdminController;
|
||
use App\Models\Machine\ApkVersion;
|
||
use App\Models\Machine\Machine;
|
||
use App\Services\Machine\MqttService;
|
||
use Illuminate\Http\Request;
|
||
use Illuminate\Support\Facades\Storage;
|
||
|
||
use Illuminate\View\View;
|
||
|
||
class ApkVersionController extends AdminController
|
||
{
|
||
public function index(): View
|
||
{
|
||
$versions = ApkVersion::orderBy('version_code', 'desc')->paginate(10);
|
||
// 用於 OTA 下發時讓使用者選擇的機台列表
|
||
$machines = Machine::all();
|
||
return view('admin.basic-settings.apk-versions.index', compact('versions', 'machines'));
|
||
}
|
||
|
||
public function create(): View
|
||
{
|
||
return view('admin.basic-settings.apk-versions.create');
|
||
}
|
||
|
||
public function store(Request $request)
|
||
{
|
||
$request->validate([
|
||
'version_name' => 'required|string|max:100',
|
||
'version_code' => 'required|integer|unique:apk_versions,version_code',
|
||
'flavor' => 'required|string|max:100',
|
||
'apk_file' => 'required_without:download_url|nullable|file|max:102400', // 上傳限制 100MB
|
||
'download_url' => 'required_without:apk_file|nullable|url|max:500',
|
||
'release_notes' => 'nullable|string',
|
||
'is_forced' => 'nullable|boolean',
|
||
]);
|
||
|
||
$filePath = null;
|
||
if ($request->hasFile('apk_file')) {
|
||
$filePath = $request->file('apk_file')->store('apks', 'public');
|
||
}
|
||
|
||
ApkVersion::create([
|
||
'version_name' => $request->version_name,
|
||
'version_code' => $request->version_code,
|
||
'flavor' => $request->flavor,
|
||
'file_path' => $filePath,
|
||
'download_url' => $request->download_url,
|
||
'release_notes' => $request->release_notes,
|
||
'is_forced' => $request->has('is_forced') || (bool) $request->input('is_forced', false),
|
||
]);
|
||
|
||
return redirect()->route('admin.basic-settings.apk-versions.index')
|
||
->with('success', __('APK version uploaded successfully.'));
|
||
}
|
||
|
||
public function destroy(ApkVersion $apkVersion)
|
||
{
|
||
if ($apkVersion->file_path) {
|
||
Storage::disk('public')->delete($apkVersion->file_path);
|
||
}
|
||
$apkVersion->delete();
|
||
|
||
return redirect()->route('admin.basic-settings.apk-versions.index')
|
||
->with('success', __('APK version deleted successfully.'));
|
||
}
|
||
|
||
/**
|
||
* 推送 OTA 更新指令給指定機台
|
||
*/
|
||
public function push(Request $request, ApkVersion $apkVersion, MqttService $mqttService)
|
||
{
|
||
$request->validate([
|
||
'machine_ids' => 'required|array',
|
||
'machine_ids.*' => 'exists:machines,id',
|
||
]);
|
||
|
||
$machines = Machine::whereIn('id', $request->machine_ids)->get();
|
||
$successCount = 0;
|
||
|
||
foreach ($machines as $machine) {
|
||
// 1. 先建立 RemoteCommand 紀錄,以取得自增 ID 作為 command_id(與系統慣例一致)
|
||
$command = \App\Models\Machine\RemoteCommand::create([
|
||
'machine_id' => $machine->id,
|
||
'user_id' => auth()->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,command_id 使用資料庫自增流水號
|
||
$pushed = $mqttService->pushCommand(
|
||
$machine->serial_no,
|
||
'update_app',
|
||
$command->payload,
|
||
(string) $command->id
|
||
);
|
||
|
||
if ($pushed) {
|
||
$successCount++;
|
||
} else {
|
||
// 推播失敗,將紀錄標記為失敗
|
||
$command->update(['status' => 'failed']);
|
||
}
|
||
}
|
||
|
||
return redirect()->route('admin.basic-settings.apk-versions.index')
|
||
->with('success', sprintf(__('OTA command sent to %d devices successfully.'), $successCount));
|
||
}
|
||
}
|