[FEAT] 優化機台硬體通訊協議與管理介面互動性
All checks were successful
star-cloud-deploy-demo / deploy-demo (push) Successful in 1m6s
All checks were successful
star-cloud-deploy-demo / deploy-demo (push) Successful in 1m6s
1. 修復帳號管理與角色權限頁面搜尋功能,支援 Enter 鍵快捷提交。 2. 完成 B013 (機台故障上報) API 實作,改用非同步隊列 (ProcessMachineError) 處理日誌上報。 3. 精簡 B013 API 參數,移除冗餘的 message 欄位,統一由雲端對照表翻譯。 4. 更新技術規格文件 (SKILL.md) 與系統 API 文件配置 (api-docs.php)。 5. 修正平台管理員帳號在搜尋過濾時的資料隔離邏輯。
This commit is contained in:
@@ -71,17 +71,21 @@ class MachineController extends AdminController
|
||||
*/
|
||||
public function logsAjax(Request $request, Machine $machine)
|
||||
{
|
||||
$per_page = $request->input('per_page', 10);
|
||||
$per_page = $request->input('per_page', 20);
|
||||
|
||||
$startDate = $request->get('start_date', now()->format('Y-m-d'));
|
||||
$endDate = $request->get('end_date', now()->format('Y-m-d'));
|
||||
$startDate = $request->get('start_date');
|
||||
$endDate = $request->get('end_date');
|
||||
|
||||
$logs = $machine->logs()
|
||||
->when($request->level, function ($query, $level) {
|
||||
return $query->where('level', $level);
|
||||
})
|
||||
->whereDate('created_at', '>=', $startDate)
|
||||
->whereDate('created_at', '<=', $endDate)
|
||||
->when($startDate, function ($query, $start) {
|
||||
return $query->where('created_at', '>=', str_replace('T', ' ', $start));
|
||||
})
|
||||
->when($endDate, function ($query, $end) {
|
||||
return $query->where('created_at', '<=', str_replace('T', ' ', $end));
|
||||
})
|
||||
->when($request->type, function ($query, $type) {
|
||||
return $query->where('type', $type);
|
||||
})
|
||||
|
||||
@@ -318,7 +318,11 @@ class PermissionController extends Controller
|
||||
}
|
||||
|
||||
if ($user->isSystemAdmin() && $request->filled('company_id')) {
|
||||
$query->where('company_id', $request->company_id);
|
||||
if ($request->company_id === 'system') {
|
||||
$query->whereNull('company_id');
|
||||
} else {
|
||||
$query->where('company_id', $request->company_id);
|
||||
}
|
||||
}
|
||||
|
||||
$per_page = $request->input('per_page', 10);
|
||||
|
||||
@@ -9,6 +9,7 @@ use App\Models\System\User;
|
||||
use App\Models\Machine\Machine;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use App\Jobs\Machine\ProcessStateLog;
|
||||
|
||||
class MachineAuthController extends Controller
|
||||
{
|
||||
@@ -20,30 +21,14 @@ class MachineAuthController extends Controller
|
||||
{
|
||||
// 1. 驗證欄位 (相容舊版 Java App 發送的 JSON 格式)
|
||||
$validated = $request->validate([
|
||||
'machine' => 'required|string',
|
||||
'Su_Account' => 'required|string',
|
||||
'machine' => 'required|string',
|
||||
'Su_Account' => 'required|string',
|
||||
'Su_Password' => 'required|string',
|
||||
'ip' => 'nullable|string',
|
||||
'type' => 'nullable|string',
|
||||
'ip' => 'nullable|string',
|
||||
'type' => 'nullable|string',
|
||||
]);
|
||||
|
||||
// 2. 透過帳號尋找使用者 (允許使用 username 或 email)
|
||||
$user = User::where('username', $validated['Su_Account'])
|
||||
->orWhere('email', $validated['Su_Account'])
|
||||
->first();
|
||||
|
||||
// 若無此帳號或密碼錯誤
|
||||
if (!$user || !Hash::check($validated['Su_Password'], $user->password)) {
|
||||
Log::warning("B000 機台登入失敗: 帳密錯誤", [
|
||||
'account' => $validated['Su_Account'],
|
||||
'machine' => $validated['machine']
|
||||
]);
|
||||
// 按現行設定,Java 端只認 Success 字串,其餘視為帳密錯誤
|
||||
return response()->json(['message' => 'Failed']);
|
||||
}
|
||||
|
||||
// 3. 取得機台物件
|
||||
// 因為此 API 無狀態 (沒有登入 session),為了避免被 global scope 擋住,直接取消所有 scope 來撈取
|
||||
// 2. 取得機台物件 (需優先於帳密驗證,以便記錄日誌到正確機台)
|
||||
$machine = Machine::withoutGlobalScopes()->where('serial_no', $validated['machine'])->first();
|
||||
|
||||
if (!$machine) {
|
||||
@@ -53,38 +38,79 @@ class MachineAuthController extends Controller
|
||||
return response()->json(['message' => 'Failed']);
|
||||
}
|
||||
|
||||
// 4. RBAC 權限驗證 (遵循多租戶與機台授權規範)
|
||||
// 3. 透過帳號尋找使用者 (允許使用 username 或 email)
|
||||
$user = User::where('username', $validated['Su_Account'])
|
||||
->orWhere('email', $validated['Su_Account'])
|
||||
->first();
|
||||
|
||||
// 4. 驗證密碼
|
||||
if (!$user || !Hash::check($validated['Su_Password'], $user->password)) {
|
||||
Log::warning("B000 機台登入失敗: 帳密錯誤", [
|
||||
'account' => $validated['Su_Account'],
|
||||
'machine' => $validated['machine']
|
||||
]);
|
||||
|
||||
// 寫入機台日誌
|
||||
ProcessStateLog::dispatch(
|
||||
$machine->id,
|
||||
$machine->company_id,
|
||||
__("Login failed: :account", ['account' => $validated['Su_Account']]),
|
||||
'warning',
|
||||
[],
|
||||
'login'
|
||||
);
|
||||
|
||||
return response()->json(['message' => 'Failed']);
|
||||
}
|
||||
|
||||
// 5. RBAC 權限驗證 (遵循多租戶與機台授權規範)
|
||||
$isAuthorized = false;
|
||||
if ($user->isSystemAdmin()) {
|
||||
// [系統管理員] : 擁有最高權限,可登入平台下轄所有機台,直接放行
|
||||
|
||||
$isAuthorized = true;
|
||||
} elseif ($user->is_admin) {
|
||||
// [公司管理員] : 不需要檢查 machine_user 表,但【必須驗證】該機台是否隸屬於他的公司
|
||||
if ($machine->company_id !== $user->company_id) {
|
||||
Log::warning("B000 機台登入失敗: 企圖越權登入其他公司的機台", [
|
||||
'user_id' => $user->id,
|
||||
'user_company' => $user->company_id,
|
||||
'machine_company' => $machine->company_id
|
||||
]);
|
||||
return response()->json(['message' => 'Forbidden']);
|
||||
if ($machine->company_id === $user->company_id) {
|
||||
$isAuthorized = true;
|
||||
}
|
||||
|
||||
} else {
|
||||
// [一般租戶帳號] : (包括補貨員等)必須嚴格檢查該帳號有沒有被分配到這台機器的 machine_user 關聯授權
|
||||
if (!$user->machines()->where('machine_id', $machine->id)->exists()) {
|
||||
Log::warning("B000 機台登入失敗: 該帳號沒有此機台的授權", [
|
||||
'user_id' => $user->id,
|
||||
'machine_id' => $machine->id
|
||||
]);
|
||||
return response()->json(['message' => 'Forbidden']);
|
||||
if ($user->machines()->where('machine_id', $machine->id)->exists()) {
|
||||
$isAuthorized = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 驗證完美通過!回傳固定字串 Success 讓 Java 端放行
|
||||
if (!$isAuthorized) {
|
||||
Log::warning("B000 機台登入失敗: 權限不足", [
|
||||
'user_id' => $user->id,
|
||||
'machine_id' => $machine->id
|
||||
]);
|
||||
|
||||
ProcessStateLog::dispatch(
|
||||
$machine->id,
|
||||
$machine->company_id,
|
||||
__("Unauthorized login attempt: :account", ['account' => $user->username]),
|
||||
'warning',
|
||||
[],
|
||||
'login'
|
||||
);
|
||||
|
||||
return response()->json(['message' => 'Forbidden']);
|
||||
}
|
||||
|
||||
// 6. 驗證完美通過!
|
||||
Log::info("B000 機台登入成功", [
|
||||
'account' => $user->username,
|
||||
'account' => $user->username,
|
||||
'machine' => $machine->serial_no
|
||||
]);
|
||||
|
||||
|
||||
// 寫入成功登入日誌
|
||||
ProcessStateLog::dispatch(
|
||||
$machine->id,
|
||||
$machine->company_id,
|
||||
__("User logged in: :name", ['name' => $user->name ?? $user->username]),
|
||||
'info',
|
||||
[],
|
||||
'login'
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Success'
|
||||
]);
|
||||
|
||||
@@ -9,8 +9,11 @@ use App\Models\System\User;
|
||||
use App\Jobs\Machine\ProcessHeartbeat;
|
||||
use App\Jobs\Machine\ProcessTimerStatus;
|
||||
use App\Jobs\Machine\ProcessCoinInventory;
|
||||
use App\Jobs\Machine\ProcessMachineError;
|
||||
use App\Jobs\Machine\ProcessStateLog;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class MachineController extends Controller
|
||||
{
|
||||
@@ -22,6 +25,69 @@ class MachineController extends Controller
|
||||
$machine = $request->get('machine');
|
||||
$data = $request->except(['machine', 'key']); // 排除 Middleware 注入的 Model 物件與認證 key
|
||||
|
||||
// === 狀態異動觸發 (Redis 快取免查 DB) ===
|
||||
$cacheKey = "machine:{$machine->serial_no}:state";
|
||||
$oldState = Cache::get($cacheKey);
|
||||
|
||||
$currentPage = $data['current_page'] ?? null;
|
||||
$doorStatus = $data['door_status'] ?? null;
|
||||
$firmwareVersion = $data['firmware_version'] ?? null;
|
||||
$model = $data['model'] ?? null;
|
||||
|
||||
if ($currentPage !== null || $doorStatus !== null || $firmwareVersion !== null || $model !== null) {
|
||||
// 更新目前狀態到 Redis (保存 1 天)
|
||||
$newState = $oldState ?? [];
|
||||
if ($currentPage !== null) $newState['current_page'] = $currentPage;
|
||||
if ($doorStatus !== null) $newState['door_status'] = $doorStatus;
|
||||
if ($firmwareVersion !== null) $newState['firmware_version'] = $firmwareVersion;
|
||||
if ($model !== null) $newState['model'] = $model;
|
||||
|
||||
Cache::put($cacheKey, $newState, 86400);
|
||||
|
||||
// 若有歷史紀錄才進行比對 (避開 Cache Miss 造成的雪崩)
|
||||
if ($oldState !== null) {
|
||||
// 1. 判斷頁面是否變更
|
||||
if ($currentPage !== null && (string)$currentPage !== (string)($oldState['current_page'] ?? '')) {
|
||||
// 只記錄「絕對狀態」,配合 lang 中 "Page X" 的翻譯
|
||||
ProcessStateLog::dispatch($machine->id, $machine->company_id, "Page {$currentPage}", 'info');
|
||||
}
|
||||
|
||||
// 2. 判斷門禁是否變更 (0: 關閉, 1: 開啟)
|
||||
if ($doorStatus !== null && (string)$doorStatus !== (string)($oldState['door_status'] ?? '')) {
|
||||
$doorMessage = $doorStatus == 1 ? "Door Opened" : "Door Closed";
|
||||
$doorLevel = 'info'; // 不論開關門皆為 info,避免觸發異常狀態
|
||||
ProcessStateLog::dispatch($machine->id, $machine->company_id, $doorMessage, $doorLevel);
|
||||
}
|
||||
|
||||
// 3. 判斷韌體版本是否變更
|
||||
if ($firmwareVersion !== null && (string)$firmwareVersion !== (string)($oldState['firmware_version'] ?? '')) {
|
||||
$oldVersion = $oldState['firmware_version'] ?? 'Unknown';
|
||||
// 直接在 Controller 進行翻譯並填值,確保儲存到 DB 的是最終正確字串
|
||||
$versionMessage = __("Firmware updated to :version", ['version' => $firmwareVersion]);
|
||||
ProcessStateLog::dispatch(
|
||||
$machine->id,
|
||||
$machine->company_id,
|
||||
$versionMessage,
|
||||
'info',
|
||||
['old' => $oldVersion, 'new' => $firmwareVersion]
|
||||
);
|
||||
}
|
||||
|
||||
// 4. 判斷型號是否變更
|
||||
if ($model !== null && (string)$model !== (string)($oldState['model'] ?? '')) {
|
||||
$oldModel = $oldState['model'] ?? 'Unknown';
|
||||
$modelMessage = __("Model changed to :model", ['model' => $model]);
|
||||
ProcessStateLog::dispatch(
|
||||
$machine->id,
|
||||
$machine->company_id,
|
||||
$modelMessage,
|
||||
'info',
|
||||
['old' => $oldModel, 'new' => $model]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 異步處理狀態更新
|
||||
ProcessHeartbeat::dispatch($machine->serial_no, $data);
|
||||
|
||||
@@ -372,4 +438,22 @@ class MachineController extends Controller
|
||||
'data' => $products
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* B013: Report Machine Hardware Error/Status (Asynchronous)
|
||||
*/
|
||||
public function reportError(Request $request)
|
||||
{
|
||||
$machine = $request->get('machine');
|
||||
$data = $request->only(['tid', 'error_code']);
|
||||
|
||||
// 異步分派處理 (Dispatch to queue)
|
||||
ProcessMachineError::dispatch($machine->serial_no, $data);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'code' => 200,
|
||||
'message' => 'Error report accepted',
|
||||
], 202); // 202 Accepted
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user