Bug #1403
進行中Feature #1397: [功能開發] LIFF DriverState、離峰充電與 Remote Operation 狀態契約重構
Feature #1398: [Backend] 實作 DriverState、離峰排程與 Remote Operation lifecycle
[Backend Bug] Semantic command dispatch 成功後因 transaction snapshot 誤回 409
是由 陳國瑋 於 約 22 小時 前加入. 於 約 17 小時 前更新.
概述
問題¶
Ken 環境對 CP001-001 執行 START_CHARGING 時,Branch 已建立 operation、送出 RemoteStartTransaction,但 HQ 實際回給 LIFF 409,導致 FE 正確顯示「操作未成功」。FE 對 HTTP 200/202 與 WebSocket operationResult 的處理已符合 contract,不需修改。
已確認證據¶
- 2026-08-02 15:54:48,HQ access log 顯示
POST /api/connector/commands回409。 - Branch DB operation
539已建立,CP 後續回Accepted。 - Ken DB transaction isolation 為
REPEATABLE-READ。 -
ConnectorCommandService先以hasActive()建立 transaction snapshot;RemoteOperationPersistenceService再以REQUIRES_NEWcommit operation;outer transaction 隨後重新查getActiveOperation()時看不到新 row,誤丟START_OPERATION_MISSING。 - STOP command 存在相同重新查詢模式,也需一併修正。
修正方向¶
保留 dispatch 前 REQUIRES_NEW 持久化。由 charging orchestration 直接回傳本次建立的 operation reference,ConnectorCommandService 使用該 reference 建立 202 Accepted response,不再重新查詢 connector 最新 active operation。
驗收¶
- START/STOP dispatch 成功後皆回
202,包含正確operationId、operationStatus與activeOperation。 - 不受 MySQL
REPEATABLE-READsnapshot 影響。 - 真正 dispatch/設備拒絕仍依既有錯誤 contract 回應。
- FE 無須修改。
是由 鍾正剛 於 約 22 小時 前更新 · 已被編輯
FE 初步調查結果:#1403 描述的 bug 位置與 code 現況不一致¶
調查對象:feature/redmine-1399-liff-driver-state-actions(目前 LIFF 部署的 code,最後 commit 524aaaf / 2026-07-31 23:11)。尚未修改任何程式碼。
1. #1403 的四項 FE 要求,現況都已經做到¶
| 要求 | 現況 | 位置 |
|---|---|---|
| 200/202 都視為成功 | OK。axios 只在非 2xx 才 throw,202 走成功路徑 | use-connector-commands.ts:75-81 |
使用 ConnectorCommandResponse 欄位 |
OK。action/operationId/operationStatus/enableOffPeak/driverState/activeOperation 均有讀取 |
use-dashboard-actions.ts:186-205 |
| 202 不等 StartTransaction,立即依 response/WS 更新畫面 | OK。applyCommandResult() 立即套用 driverState,202 時不顯示任何 modal |
use-dashboard-actions.ts:189-211 |
| 只有 403/409/request exception 才顯示失敗 modal | OK。ConnectorCommandError 僅在 axios error 時建立,並用 body 的 message/driverState/enableOffPeak 校正畫面 |
use-connector-commands.ts:82-94 |
(以上路徑皆位於 react/web/app/charger/hooks/)
另外,舊版 dashboard-api.tsx / dashboard-private.tsx(使用 isSuccess 判斷、失敗文案為「錯誤」)在此 branch 已刪除,LIFF 只剩新的 semantic command 路徑,不存在「仍走舊 contract」的程式碼。
Backend 端亦已確認正常:Branch dispatch 成功回 202(ConnectorCommandService.java:172),HQ 原樣轉送 Branch 的 status 與 body(ConnectorBranchApiHandler.java:97-98、ConnectorController#executeCommand)。
2.「操作未成功」只可能來自兩條路徑¶
路徑 A — HTTP 403/409/5xx/網路錯誤(use-dashboard-actions.ts:238)
modal 內文為 backend 的 message。可疑點:Branch start() 在 dispatch 成功後會再查一次 getActiveOperation(),查不到即丟 409 START_OPERATION_MISSING(ConnectorCommandService.java:169-171)。此情境下 log 一樣會建立 operation、狀態一樣會轉 ACCEPTED,與本 issue 描述的現象完全相容。
路徑 B — WebSocket 推送的 terminal operationResult 被判為失敗(use-dashboard-actions.ts:260-274)
FE 只把 STARTED / STARTED_LATE / STOPPED 視為成功,其餘(TIMED_OUT / REJECTED / FAILED)顯示「操作未成功」。此路徑最為可疑:START 的 business deadline 為 ConnectorType.maxWaitTime,FORTUNE(華城)僅 30 秒(ConnectorType.java:13),而本案 CP 正是「還在等待車端電流」而尚未送出 StartTransaction,容易超過 deadline 被 Branch 判為 TIMED_OUT 並推送 terminal result。若屬此路徑,root cause 在 Branch 的 deadline/終結語意,而非 FE 的 202 判斷。
3. 需要的決定性證據(任一項即可定案)¶
- 按下「開始充電」到彈出 modal 的間隔:立即(< 2 秒)=路徑 A;約 30 秒後=路徑 B。
- modal 標題下方的完整內文文字。
- LIFF 端 log 任一行:
連接器操作: 指令被拒絕或失敗(含httpStatus、errorCode)或WebSocket 收到 operation result(含operationStatus);或 HQ 回給 LIFF 的實際 HTTP status code。
在取得上述證據前不進行修改:若直接依 #1403 目前描述的方向修改,會改到已經正確的程式碼,真正的 root cause 仍會重現。
(本備註為 2026-08-02 08:55 該則的 Markdown 重排版,內容未變更)
是由 陳國瑋 於 約 20 小時 前更新
Backend implementation completed locally:
- RemoteStart/RemoteStop dispatch success now carries the exact persisted operation snapshot from charging orchestration to ConnectorCommandService.
- Semantic command response no longer re-queries the newly committed REQUIRES_NEW row inside the outer REPEATABLE-READ transaction.
- 202 response uses the same snapshot for operationId, operationStatus, activeOperation and PREPARING/FINISHING driverState.
- Internal handoff field is excluded from /api/tx JSON and OpenAPI contract.
Verification:
- Targeted unit tests: 27 passed, 0 failed.
- mvn -DskipTests package: BUILD SUCCESS.
- E2E inventory: CHG-034 updated to P0 regression; catalog validator passed.
- Environment E2E remains pending deployment.
Note: A full mvn test attempt exposed that the integration-test profile points directly to Ken DB and its SQL fixtures are destructive. The affected CP001 data was restored from FULL row binlog and verified. The unrelated existing ConnectorE2EIntegrationTest tariff assertion failure is not counted as #1403 verification.
是由 陳國瑋 於 約 20 小時 前更新
Implementation committed and pushed for manual deployment testing.
- Code commit: f317d10 fix(#1403): 修正 command operation snapshot 誤回 409
- Feature branch: feature/redmine-1403-command-operation-snapshot
- Merged and pushed to private: 98cd943
- Test-plan SOP update: b5f9bc2
Per the updated SOP, no additional test plan was executed during commit/merge/push. Ken will manually deploy and explicitly start the environment test afterward.
是由 陳國瑋 於 約 20 小時 前更新
Ken 環境部署後續驗證發現 STOP 被舊 ACTIVE transaction 阻斷:
- 18:34:08 START operation 543 成功 dispatch,18:34:42 CP 送 StartTransaction。
- Branch 因 connector.currentTransactionId 為 null,從所有 ACTIVE 中選到 2026-01-22 遺留的 transaction 77,修復 pointer 並回傳 77;driverState 隨後進入 CHARGING。
- LIFF 於 18:34:59、18:35:06 送出 STOP_CHARGING,但 ConnectorCommandService 依最新一筆交易判斷;最新交易 117 已 COMPLETED,因此回 409 NO_ACTIVE_TRANSACTION,未建立 STOP operation、未送 RemoteStopTransaction。
- 18:35:16 最後由 CP 實體 stop detection 送 StopTransaction(77, EVDisconnected) 才結束。
已確認修正規則:
- connector.currentTransactionId 有值且指向 ACTIVE 時,沿用為重複 StartTransaction。
- pointer 缺失/無效時,先查 connector 最新一筆 transaction(不限狀態),再判斷是否 ACTIVE。
- 最新一筆為 ACTIVE 才修復 pointer;最新一筆已完成時,不得回頭撿更舊的孤兒 ACTIVE,應為本次 StartTransaction 建立新交易。
- STOP 不改成查任意 ACTIVE,避免舊交易造成錯誤 owner 授權或停止錯誤交易。
將補 stale ACTIVE + newer COMPLETED regression;FE/API contract 不變。
是由 陳國瑋 於 約 20 小時 前更新
Follow-up implementation completed locally:
- TransactionService pointer repair now queries the connector's latest transaction without pre-filtering ACTIVE.
- currentTransactionId that already points to ACTIVE remains authoritative for duplicate StartTransaction.
- Missing/invalid pointer is repaired only when the latest transaction is ACTIVE; if the latest transaction is COMPLETED, the incoming StartTransaction creates a new transaction and does not reuse an older orphan ACTIVE.
- STOP transaction/owner lookup and API contract are unchanged.
- Added P0 regression CHG-039 and updated the Core Smoke transaction reconciliation set.
Minimal verification allowed by SOP:
- Single isolated Mockito unit test: 1 passed, 0 failed; no Spring context or shared DB.
- mvn -DskipTests package: BUILD SUCCESS.
- E2E catalog validator: PASS (144 cases; P0=40, P1=57, P2=42, P3=5).
No integration/E2E test plan was started. Changes are not yet committed or pushed.
是由 陳國瑋 於 約 19 小時 前更新
Ken 環境 follow-up:RemoteStopTransaction/StopTransaction、DB AVAILABLE 與 driverState READY 均已成功,但 LIFF 持續停在 FINISHING。確認 Ken Branch runtime 連到 ems-mq.sylksoft.com,staging HQ 監聽 ems-mq-sta.sylksoft.com,Connector Event 因 broker 不同而未進入 HQ WebSocket。已將 java/ems_branch/conf/ken/application.yml 改為預設 ${RABBITMQ_HOST:ems-mq-sta.sylksoft.com} 並保留環境變數覆寫;REL-007 補上 FINISHING→READY 回歸驗證。Ken profile mvn -DskipTests package 成功,JAR 內設定確認正確。
是由 陳國瑋 於 約 17 小時 前更新
- 狀態 從 In Progress 變更為 Resolved
#1403 已處理完成。
最終修正與驗證摘要:
- 已修正 semantic command dispatch 成功後,在 MySQL REPEATABLE-READ outer transaction 內重新查詢
REQUIRES_NEWoperation row 而誤回409 START_OPERATION_MISSING的問題。 - 已修正 StartTransaction pointer repair:
currentTransactionId指向 ACTIVE 時才沿用;pointer 缺失/無效時只以 connector 最新一筆 transaction 判斷,不再撿更舊的孤兒 ACTIVE transaction,避免後續 STOP 被錯誤 transaction 阻斷。 - Ken 環境 follow-up 已確認 RemoteStopTransaction/StopTransaction、DB connector AVAILABLE、driverState READY 均成功。
- LIFF 曾卡在 FINISHING 的後續原因是 Ken Branch 與 staging HQ 使用不同 RabbitMQ broker;已將
java/ems_branch/conf/ken/application.yml的預設 RabbitMQ host 調整為 staging broker,並保留環境變數覆寫。
相關 commit/branch:
f317d10 fix(#1403): 修正 command operation snapshot 誤回 409- merged to private:
98cd943 - branch:
feature/redmine-1403-command-operation-snapshot
驗證紀錄:
- Targeted unit tests pass。
-
mvn -DskipTests packagepass。 - E2E catalog validator pass。
- Ken 環境人工驗證已確認本 issue 的 START/STOP semantic command 流程可正常收斂。