fix: 修复票据详情设备ID缺失并优化自动更新逻辑

- 为票据详情接口添加设备字段兼容映射,支持从e.device/e.device_id/e.detail?.device获取值
- 更新设备ID缺失问题的调试文档,标记已验证的假设并补充最终结论
- 优化售票机和闸机的自动更新逻辑:缩短版本检查间隔至5秒,添加更新中状态标识,改为事件触发更新
This commit is contained in:
2026-07-10 21:32:17 +08:00
parent 5f184bf184
commit f1cdfe3a84
4 changed files with 64 additions and 28 deletions
+6 -5
View File
@@ -13,16 +13,17 @@
## Hypotheses & Verification ## Hypotheses & Verification
| ID | Hypothesis | Likelihood | Effort | Evidence | | ID | Hypothesis | Likelihood | Effort | Evidence |
|----|------------|------------|--------|----------| |----|------------|------------|--------|----------|
| A | 后端接口返回的事件对象里根本没有 `device` / `detail.device` 字段 | High | Low | Pending | | A | 后端接口返回的事件对象里根本没有 `device` / `detail.device` 字段 | High | Low | Rejected |
| B | 前端格式化函数读到了值,但后续字符串拼接或编码问题把值丢掉了 | High | Low | Pending | | B | 前端格式化函数读到了值,但后续字符串拼接或编码问题把值丢掉了 | High | Low | Rejected |
| C | 售票与检票两类事件的数据结构不同,当前前端只兼容了其中一类 | Med | Low | Pending | | C | 售票与检票两类事件的数据结构不同,当前前端只兼容了其中一类 | Med | Low | Confirmed |
| D | 设备端 Lua 确实上报了 ID,但服务端在写库或对外返回时被覆盖成空串 | Med | Med | Pending | | D | 设备端 Lua 确实上报了 ID,但服务端在写库或对外返回时被覆盖成空串 | Med | Med | Rejected |
| E | 页面实际运行的不是当前工作区这份前端代码,导致本地修改未生效 | Med | Med | Pending | | E | 页面实际运行的不是当前工作区这份前端代码,导致本地修改未生效 | Med | Med | Pending |
## Log Evidence ## Log Evidence
- 2026-07-10: 已启动 Debug Server `http://127.0.0.1:7777/event` - 2026-07-10: 已启动 Debug Server `http://127.0.0.1:7777/event`
- 2026-07-10: 已在 `[public.js]` 的票据时间线组装处添加 A/C 埋点 - 2026-07-10: 已在 `[public.js]` 的票据时间线组装处添加 A/C 埋点
- 2026-07-10: 已在 `[index.js]` 与 `[ticket-board.html]` 的设备 ID 格式化处添加 B 埋点 - 2026-07-10: 已在 `[index.js]` 与 `[ticket-board.html]` 的设备 ID 格式化处添加 B 埋点
- 2026-07-10: 本地运行时复现结果:`saleRawDevice="#0"` 但 `saleMappedDevice=null`;同时 `checkRawDevice="#1"` 且 `checkMappedDevice="#1"`,证明 `sale` 映射阶段丢失了 `device`
## Verification Conclusion ## Verification Conclusion
[Pending] - 已实施最小修复:在公开票据详情的 `sale` 事件映射中保留 `device`
+36 -9
View File
@@ -2,7 +2,7 @@ local DEFAULT_SERVER_BASE = "http://ticket.fse-media.group"
local DEFAULT_SERVER_PATH = "/api/tickets/check" local DEFAULT_SERVER_PATH = "/api/tickets/check"
local GATE_OPEN_SECONDS = 2 local GATE_OPEN_SECONDS = 2
local VERSION = "v1.5.10" local VERSION = "v1.5.10"
local VERSION_CHECK_INTERVAL = 60 local VERSION_CHECK_INTERVAL = 5
local CONFIG_PATH = "gate_config.json" local CONFIG_PATH = "gate_config.json"
@@ -93,6 +93,7 @@ local serverLastChangeTs = 0
local expectedGateVersion = nil local expectedGateVersion = nil
local versionMismatch = nil local versionMismatch = nil
local pendingGateUpdate = false local pendingGateUpdate = false
local isUpdating = false
local gateBusyUntilTs = 0 local gateBusyUntilTs = 0
local function nowMs() local function nowMs()
@@ -163,7 +164,11 @@ local function drawVersionIndicator(w)
if #s == 0 then return end if #s == 0 then return end
if w < #s then return end if w < #s then return end
local markerColor = colors.yellow local markerColor = colors.yellow
if versionMismatch == true then local markerText = "*"
if isUpdating then
markerColor = colors.yellow
markerText = "^ing"
elseif versionMismatch == true then
markerColor = colors.red markerColor = colors.red
elseif versionMismatch == false then elseif versionMismatch == false then
markerColor = colors.lime markerColor = colors.lime
@@ -172,9 +177,9 @@ local function drawVersionIndicator(w)
termDev.setTextColor(colors.gray) termDev.setTextColor(colors.gray)
termDev.setCursorPos(1, 1) termDev.setCursorPos(1, 1)
termDev.write(s) termDev.write(s)
if w >= (#s + 1) then if w >= (#s + #markerText) then
termDev.setTextColor(markerColor) termDev.setTextColor(markerColor)
termDev.write("*") termDev.write(markerText)
end end
termDev.setTextColor(colors.white) termDev.setTextColor(colors.white)
end end
@@ -1374,6 +1379,20 @@ end
local versionTimer = os.startTimer(VERSION_CHECK_INTERVAL) local versionTimer = os.startTimer(VERSION_CHECK_INTERVAL)
-- 启动时不再自动触发更新检查逻辑,等待事件触发
-- if pendingGateUpdate and isGateIdleForUpdate() then
-- isUpdating = true
-- drawReadyScreen()
-- os.sleep(0.5)
-- local ok, updated = pcall(runSilentGateUpdate)
-- if ok and updated then
-- os.reboot()
-- return
-- end
-- isUpdating = false
-- drawReadyScreen()
-- end
while true do while true do
local ev = pack(os.pullEvent()) local ev = pack(os.pullEvent())
if ev[1] == "ticket_scanned" or ev[1] == "ic_card_scanned" then if ev[1] == "ticket_scanned" or ev[1] == "ic_card_scanned" then
@@ -1381,21 +1400,29 @@ while true do
processInspectionEvent(ev[1], ev) processInspectionEvent(ev[1], ev)
os.sleep(0.35) os.sleep(0.35)
drawReadyScreen() drawReadyScreen()
elseif ev[1] == "trigger_update" then
pendingGateUpdate = true
elseif ev[1] == "timer" and ev[2] == versionTimer then elseif ev[1] == "timer" and ev[2] == versionTimer then
pcall(function() pcall(function()
refreshRemoteLuaVersion(guessBaseFromStatusURL(serverURL)) refreshRemoteLuaVersion(guessBaseFromStatusURL(serverURL))
end) end)
if versionMismatch == true then -- 取消自动标记更新,仅由外部事件(例如手动控制台)触发时处理
pendingGateUpdate = true -- if versionMismatch == true then
elseif versionMismatch == false then -- pendingGateUpdate = true
pendingGateUpdate = false -- elseif versionMismatch == false then
end -- pendingGateUpdate = false
-- end
if pendingGateUpdate and isGateIdleForUpdate() then if pendingGateUpdate and isGateIdleForUpdate() then
isUpdating = true
drawReadyScreen()
os.sleep(0.5)
local ok, updated = pcall(runSilentGateUpdate) local ok, updated = pcall(runSilentGateUpdate)
if ok and updated then if ok and updated then
os.reboot() os.reboot()
return return
end end
isUpdating = false
pendingGateUpdate = false
end end
drawReadyScreen() drawReadyScreen()
versionTimer = os.startTimer(VERSION_CHECK_INTERVAL) versionTimer = os.startTimer(VERSION_CHECK_INTERVAL)
+1
View File
@@ -733,6 +733,7 @@ router.get('/tickets/:id', async (req, res, next) => {
station_code: e.station_code || '', station_code: e.station_code || '',
station_en: e.station_code ? enNameFor(e.station_code) : (e.station_en || ''), station_en: e.station_code ? enNameFor(e.station_code) : (e.station_en || ''),
ticket_id: e.ticket_id, ticket_id: e.ticket_id,
device: e.device || e.device_id || e.detail?.device || '',
start_name: nameFor(e.start), start_name: nameFor(e.start),
terminal_name: nameFor(e.terminal), terminal_name: nameFor(e.terminal),
train_type: e.train_type || '', train_type: e.train_type || '',
+21 -14
View File
@@ -1,7 +1,7 @@
local CURRENT_STATION_CODE = 'Ticket-Machine' local CURRENT_STATION_CODE = 'Ticket-Machine'
local API_BASE = 'http://ticket.fse-media.group/api' local API_BASE = 'http://ticket.fse-media.group/api'
local VERSION = 'v1.5.12' local VERSION = 'v1.5.12'
local UPDATE_CHECK_INTERVAL = 60 local UPDATE_CHECK_INTERVAL = 5
-- ########################### -- ###########################
-- Core HTTP & JSON Utilities -- Core HTTP & JSON Utilities
@@ -15,6 +15,7 @@ end
local serverConnected = nil local serverConnected = nil
local versionMismatch = nil local versionMismatch = nil
local pendingMachineUpdate = false local pendingMachineUpdate = false
local isUpdating = false
local state local state
local function normalizeVersionTag(v) local function normalizeVersionTag(v)
@@ -662,21 +663,23 @@ local function runSilentMachineUpdate()
end end
local function backgroundSyncTask() local function backgroundSyncTask()
pcall(refreshConfigOnce)
while true do while true do
sleep(UPDATE_CHECK_INTERVAL)
pcall(refreshConfigOnce)
if versionMismatch == true then
pendingMachineUpdate = true
elseif versionMismatch == false then
pendingMachineUpdate = false
end
if pendingMachineUpdate and state and tostring(state.page or 'home') == 'home' then if pendingMachineUpdate and state and tostring(state.page or 'home') == 'home' then
isUpdating = true
pcall(drawVersionIndicator)
sleep(0.5)
local ok, updated = pcall(runSilentMachineUpdate) local ok, updated = pcall(runSilentMachineUpdate)
if ok and updated then if ok and updated then
os.reboot() os.reboot()
return return
end end
isUpdating = false
pendingMachineUpdate = false
pcall(drawVersionIndicator)
end end
sleep(UPDATE_CHECK_INTERVAL)
pcall(refreshConfigOnce)
end end
end end
@@ -961,15 +964,15 @@ local function waitButtons()
while true do while true do
local ev, p1, p2, p3 = os.pullEvent() local ev, p1, p2, p3 = os.pullEvent()
if ev == 'mouse_click' or ev == 'monitor_touch' then if ev == 'mouse_click' or ev == 'monitor_touch' then
-- For mouse_click: p1=button, p2=x, p3=y
-- For monitor_touch: p1=side, p2=x, p3=y
for _, b in ipairs(Buttons) do for _, b in ipairs(Buttons) do
if inRect(b, p2, p3) then if inRect(b, p2, p3) then
clickSound(); if b.onClick then b.onClick() end; return clickSound(); if b.onClick then b.onClick() end; return
end end
end end
elseif ev == 'trigger_update' then
pendingMachineUpdate = true
elseif ev == 'config_updated' then elseif ev == 'config_updated' then
-- Ignore config_updated while waiting for buttons, to prevent interrupting user's selection if state and state.page == 'home' then return end
end end
end end
end end
@@ -1103,7 +1106,11 @@ end
local function drawVersionIndicator() local function drawVersionIndicator()
if w < 1 then return end if w < 1 then return end
local markerColor = colors.yellow local markerColor = colors.yellow
if versionMismatch == true then local markerText = '*'
if isUpdating then
markerColor = colors.yellow
markerText = '^ing'
elseif versionMismatch == true then
markerColor = colors.red markerColor = colors.red
elseif versionMismatch == false then elseif versionMismatch == false then
markerColor = colors.lime markerColor = colors.lime
@@ -1112,9 +1119,9 @@ local function drawVersionIndicator()
termDev.setTextColor(colors.gray) termDev.setTextColor(colors.gray)
termDev.setCursorPos(1, 1) termDev.setCursorPos(1, 1)
termDev.write(tostring(VERSION)) termDev.write(tostring(VERSION))
if w >= (#tostring(VERSION) + 1) then if w >= (#tostring(VERSION) + #markerText) then
termDev.setTextColor(markerColor) termDev.setTextColor(markerColor)
termDev.write('*') termDev.write(markerText)
end end
termDev.setTextColor(colors.white) termDev.setTextColor(colors.white)
end end