[REFACTOR] 統一重啟邏輯安全防護與 MQTT 交易結案規格
1. 統一 MQTT 交易結案規格:移除 TransactionFinalizePayload 與 Builder 中的 remote_command_id 欄位,強制使用 code_id。 2. 強化定時重啟安全性:將 HomeActivity 與 FontendActivity 的重啟邏輯統一為「閒置 90 秒 + 無開啟對話框」才執行硬重啟。 3. 統一 12:00 重啟行為:將中午 12 點的軟重啟改為受保護的硬重啟,確保資源清理。 4. 加固遠端指令安全性:MqttService 收到 reboot 或 unlock 指令時,若偵測到有人操作(閒置 < 60 秒或對話框開啟)則回傳失敗 ACK,不執行重啟。
This commit is contained in:
parent
98cb59c431
commit
ccab0905c6
@ -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 {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,7 +40,6 @@ public class TransactionFinalizeBuilder {
|
||||
private final List<TransactionFinalizePayload.DispenseRecord> 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());
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user