From ccab0905c68b200a20009e11876d6549bb4718df Mon Sep 17 00:00:00 2001 From: sky121113 Date: Tue, 12 May 2026 14:57:26 +0800 Subject: [PATCH] =?UTF-8?q?[REFACTOR]=20=E7=B5=B1=E4=B8=80=E9=87=8D?= =?UTF-8?q?=E5=95=9F=E9=82=8F=E8=BC=AF=E5=AE=89=E5=85=A8=E9=98=B2=E8=AD=B7?= =?UTF-8?q?=E8=88=87=20MQTT=20=E4=BA=A4=E6=98=93=E7=B5=90=E6=A1=88?= =?UTF-8?q?=E8=A6=8F=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 統一 MQTT 交易結案規格:移除 TransactionFinalizePayload 與 Builder 中的 remote_command_id 欄位,強制使用 code_id。 2. 強化定時重啟安全性:將 HomeActivity 與 FontendActivity 的重啟邏輯統一為「閒置 90 秒 + 無開啟對話框」才執行硬重啟。 3. 統一 12:00 重啟行為:將中午 12 點的軟重啟改為受保護的硬重啟,確保資源清理。 4. 加固遠端指令安全性:MqttService 收到 reboot 或 unlock 指令時,若偵測到有人操作(閒置 < 60 秒或對話框開啟)則回傳失敗 ACK,不執行重啟。 --- .../com/unibuy/smartdevice/HomeActivity.java | 65 ++++++++----------- .../smartdevice/service/MqttService.java | 23 ++++++- .../mqtt/TransactionFinalizePayload.java | 12 ---- .../smartdevice/tools/FlowIdGenerator.java | 16 +++-- .../tools/TransactionFinalizeBuilder.java | 6 -- .../smartdevice/ui/FontendActivity.java | 5 +- 6 files changed, 61 insertions(+), 66 deletions(-) diff --git a/app/src/main/java/com/unibuy/smartdevice/HomeActivity.java b/app/src/main/java/com/unibuy/smartdevice/HomeActivity.java index d8a0388..67096fe 100644 --- a/app/src/main/java/com/unibuy/smartdevice/HomeActivity.java +++ b/app/src/main/java/com/unibuy/smartdevice/HomeActivity.java @@ -119,14 +119,9 @@ public class HomeActivity extends AppCompatActivityAbstract { if (timeByM.equals("23:45")) { new NfcCheckout(this).start(); } - if (timeByM.equals("12:00") ) { - getLogs().info("當前時間:" + timeByM + ",符合條件,執行 ReStartAPP..."); - new RestartAPP(this).start(); - } - if (timeByM.equals("23:59") || timeByM.equals("04:00") || timeByM.equals("08:00") - || timeByM.equals("16:00") || timeByM.equals("20:00")) { - getLogs().info("當前時間:" + timeByM + ",符合條件,執行 ReStartAPP2..."); + || timeByM.equals("12:00") || timeByM.equals("16:00") || timeByM.equals("20:00")) { + getLogs().info("當前時間:" + timeByM + ",符合條件,啟動 RestartAPP2 (硬重啟等待模式)..."); new RestartAPP2(this).start(); } @@ -330,50 +325,42 @@ public class HomeActivity extends AppCompatActivityAbstract { } } + /** + * 【硬重啟】確認閒置秒數後執行真重啟。 + * - 只要閒置 >= 90 秒,立刻啟動 HomeActivity 並 killProcess。 + * - 若有人正在使用(閒置 < 90 秒),則等待 5 秒後再重新判斷,最多等待 10 分鐘。 + */ private class RestartAPP2 extends HandlerMainScheduler { - private int testCount = 0; public RestartAPP2(HandlerMain handlerMain) { super(handlerMain); - testCount = 0; } - @Override - protected HandlerMain setHandlerMain() { - return getSrcHandlerMain(); - } - + protected HandlerMain setHandlerMain() { return getSrcHandlerMain(); } @Override protected void execute(HandlerMain handlerMain) { - for (int testIndex=0; testIndex < 30; testIndex++) { - if (testCount < 3) { - if (!MyApp.getInstance().isShowDialog()) { - getLogs().debug("RestartHost2 testCount:" + testCount); - testCount++; - } else { - testCount = 0; - } + // 最多等待 10 分鐘(120 次 * 5 秒),確保機台有機會重啟 + for (int i = 0; i < 120; i++) { + long idle = MyApp.getInstance().getIdleSeconds(); + boolean isDialog = MyApp.getInstance().isShowDialog(); - try { - Thread.sleep(1000*20); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } else { - break; + if (idle >= 90 && !isDialog) { + getLogs().info("RestartAPP2:閒置 " + idle + " 秒且無對話框,執行真重啟"); + new android.os.Handler(android.os.Looper.getMainLooper()).post(() -> { + ReSetAPP2(); + }); + return; + } + getLogs().debug("RestartAPP2:閒置 " + idle + " 秒,Dialog:" + isDialog + ",等待 5 秒後重新判斷 (" + (i+1) + "/120)"); + try { + Thread.sleep(5000); + } catch (InterruptedException e) { + return; } } - - if (testCount < 3) { - return; - } - - ReSetAPP2(); + getLogs().info("RestartAPP2:等待超過 10 分鐘仍有人在使用,放棄本次重啟"); } - @Override - protected Class setCls() { - return getClass(); - } + protected Class setCls() { return getClass(); } } private class RestartHost extends HandlerMainScheduler { diff --git a/app/src/main/java/com/unibuy/smartdevice/service/MqttService.java b/app/src/main/java/com/unibuy/smartdevice/service/MqttService.java index 0c36896..167e0d3 100644 --- a/app/src/main/java/com/unibuy/smartdevice/service/MqttService.java +++ b/app/src/main/java/com/unibuy/smartdevice/service/MqttService.java @@ -93,9 +93,19 @@ public class MqttService extends Service { try { if ("reboot".equals(cmd)) { logs.info("Executing remote reboot command..."); + + long idle = MyApp.getInstance().getIdleSeconds(); + boolean isDialog = MyApp.getInstance().isShowDialog(); + + if (idle < 60 || isDialog) { + String reason = isDialog ? "Dialog is open" : "User interacting (idle: " + idle + "s)"; + logs.info("Remote reboot rejected: " + reason); + mqttManager.publishCommandAck(cmdId, "failed", "Machine busy: " + reason); + return; + } + mqttManager.publishCommandAck(cmdId, "success", "Rebooting soon..."); // 🚩 優雅重啟:ACK 發出後先 gracefulDisconnect(不觸發 LWT),再 killProcess - // 重啟到初始畫面 (HomeActivity);按「購物去」再經 RestartActivity 釋放硬體,不会再次 killProcess new android.os.Handler(android.os.Looper.getMainLooper()) .postDelayed(() -> mqttManager.gracefulDisconnect(() -> { new android.os.Handler(android.os.Looper.getMainLooper()).post(() -> { @@ -125,6 +135,17 @@ public class MqttService extends Service { }); } else if ("unlock".equals(cmd)) { logs.info("Executing remote unlock command..."); + + long idle = MyApp.getInstance().getIdleSeconds(); + boolean isDialog = MyApp.getInstance().isShowDialog(); + + if (idle < 60 || isDialog) { + String reason = isDialog ? "Dialog is open" : "User interacting (idle: " + idle + "s)"; + logs.info("Remote unlock/reboot rejected: " + reason); + mqttManager.publishCommandAck(cmdId, "failed", "Machine busy: " + reason); + return; + } + com.unibuy.smartdevice.database.SettingsDao settingsDao = new com.unibuy.smartdevice.database.SettingsDao( getApplicationContext()); settingsDao.setMachineLocked(false); diff --git a/app/src/main/java/com/unibuy/smartdevice/structure/mqtt/TransactionFinalizePayload.java b/app/src/main/java/com/unibuy/smartdevice/structure/mqtt/TransactionFinalizePayload.java index d3fc36f..568a5bb 100644 --- a/app/src/main/java/com/unibuy/smartdevice/structure/mqtt/TransactionFinalizePayload.java +++ b/app/src/main/java/com/unibuy/smartdevice/structure/mqtt/TransactionFinalizePayload.java @@ -24,11 +24,6 @@ public class TransactionFinalizePayload { */ private String code_id; - /** - * 遠端指令 ID (僅用於 payment_type = 100) - */ - private String remote_command_id; - /** * 支付類型 (Root 級別,用於後端快速判斷) */ @@ -62,13 +57,6 @@ public class TransactionFinalizePayload { this.code_id = code_id; } - public String getRemote_command_id() { - return remote_command_id; - } - - public void setRemote_command_id(String remote_command_id) { - this.remote_command_id = remote_command_id; - } public int getPayment_type() { return payment_type; diff --git a/app/src/main/java/com/unibuy/smartdevice/tools/FlowIdGenerator.java b/app/src/main/java/com/unibuy/smartdevice/tools/FlowIdGenerator.java index 0ea8383..1532f3f 100644 --- a/app/src/main/java/com/unibuy/smartdevice/tools/FlowIdGenerator.java +++ b/app/src/main/java/com/unibuy/smartdevice/tools/FlowIdGenerator.java @@ -26,16 +26,20 @@ public class FlowIdGenerator { private static final String KEY_COUNTER = "counter"; private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyyMMdd", Locale.getDefault()); + private static final SimpleDateFormat TIME_FORMAT = + new SimpleDateFormat("HHmmss", Locale.getDefault()); /** * 取得下一個流水號(每次呼叫自動遞增)。 * * @param context Android Context - * @return 格式為 "YYYYMMDDXXXX" 的流水號字串,例如 "202605110001" + * @return 格式為 "YYYYMMDDHHMMSSXXXX" 的流水號字串 */ public static synchronized String next(Context context) { SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); - String today = DATE_FORMAT.format(new Date()); + Date now = new Date(); + String today = DATE_FORMAT.format(now); + String timePart = TIME_FORMAT.format(now); String lastDate = prefs.getString(KEY_LAST_DATE, ""); int counter; @@ -48,13 +52,13 @@ public class FlowIdGenerator { if (counter > 9999) counter = 1; } - // 持久化 + // 持久化:使用 commit() 確保同步寫入磁碟,防止異常重啟導致跳號重置 prefs.edit() .putString(KEY_LAST_DATE, today) .putInt(KEY_COUNTER, counter) - .apply(); + .commit(); - // 格式化:日期 + 4 位補零序號 - return today + String.format(Locale.getDefault(), "%04d", counter); + // 格式化:日期 + 時分秒 + 4 位補零序號 + return today + timePart + String.format(Locale.getDefault(), "%04d", counter); } } diff --git a/app/src/main/java/com/unibuy/smartdevice/tools/TransactionFinalizeBuilder.java b/app/src/main/java/com/unibuy/smartdevice/tools/TransactionFinalizeBuilder.java index 2c76eaa..79d8884 100644 --- a/app/src/main/java/com/unibuy/smartdevice/tools/TransactionFinalizeBuilder.java +++ b/app/src/main/java/com/unibuy/smartdevice/tools/TransactionFinalizeBuilder.java @@ -40,7 +40,6 @@ public class TransactionFinalizeBuilder { private final List dispenseRecords = new ArrayList<>(); private String codeId = "0"; - private String remoteCommandId = "0"; public TransactionFinalizeBuilder() { this.orderMachineTime = SDF.format(new Date()); @@ -53,9 +52,6 @@ public class TransactionFinalizeBuilder { this.codeId = codeId; } - public void setRemoteCommandId(String remoteCommandId) { - this.remoteCommandId = remoteCommandId; - } /** * 加入一筆出貨結果記錄(每個貨道出貨後呼叫一次)。 @@ -110,8 +106,6 @@ public class TransactionFinalizeBuilder { } } - // --- remote_command_id:遠端指令 ID --- - payload.setRemote_command_id(this.remoteCommandId); // --- payment_type:支付類型 (Root 級別) --- payload.setPayment_type(flowInfo.getFlowType()); diff --git a/app/src/main/java/com/unibuy/smartdevice/ui/FontendActivity.java b/app/src/main/java/com/unibuy/smartdevice/ui/FontendActivity.java index da68277..3599e92 100644 --- a/app/src/main/java/com/unibuy/smartdevice/ui/FontendActivity.java +++ b/app/src/main/java/com/unibuy/smartdevice/ui/FontendActivity.java @@ -749,8 +749,9 @@ public class FontendActivity extends AppCompatActivityAbstract { // 最多等待 10 分鐘(120 次 * 5 秒),確保機台有機會重啟 for (int i = 0; i < 120; i++) { long idle = MyApp.getInstance().getIdleSeconds(); - if (idle >= 90) { - getLogs().info("RestartAPP2:閒置 " + idle + " 秒,符合條件,執行真重啟"); + boolean isDialog = MyApp.getInstance().isShowDialog(); + if (idle >= 90 && !isDialog) { + getLogs().info("RestartAPP2:閒置 " + idle + " 秒且無對話框,執行真重啟"); // 切回主執行緒執行重啟 new android.os.Handler(android.os.Looper.getMainLooper()).post(() -> { Intent intent = new Intent(getApplicationContext(), com.unibuy.smartdevice.HomeActivity.class);