aaden-cli 1.6.117 → 1.6.118
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +71 -8
- package/modules/tse-info.js +70 -7
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -694,6 +694,15 @@ const WINDOWS_TSE_DIR = 'Desktop/AadenTseOnWindows'
|
|
|
694
694
|
const WINDOWS_DEFAULT_JAR = 'tse-0.0.3.jar'
|
|
695
695
|
// 和 PHP 的 Api.php 一样给 3 秒
|
|
696
696
|
const PROBE_TIMEOUT_MS = 3000
|
|
697
|
+
const PING_RETRY_DELAY_MS = 20000
|
|
698
|
+
|
|
699
|
+
function safeSync(fn, fallback = null) {
|
|
700
|
+
try {
|
|
701
|
+
return fn()
|
|
702
|
+
} catch (e) {
|
|
703
|
+
return fallback
|
|
704
|
+
}
|
|
705
|
+
}
|
|
697
706
|
|
|
698
707
|
async function safe(fn, fallback = null) {
|
|
699
708
|
try {
|
|
@@ -776,20 +785,67 @@ function servingJarPath() {
|
|
|
776
785
|
return match ? match[1] : LINUX_DEFAULT_JAR
|
|
777
786
|
}
|
|
778
787
|
|
|
779
|
-
//
|
|
780
|
-
function
|
|
788
|
+
// 只读门店库,返回第一行按 tab 分开的各列(没有结果为 null)
|
|
789
|
+
function queryDb(sql) {
|
|
781
790
|
const db = readIniSection('DB')
|
|
782
791
|
const out = execFileSync('mysql', [
|
|
783
792
|
'-h', db.host || 'localhost', '-u', db.user || 'aaden', '-N', '-B', '--raw', db.name || 'aaden',
|
|
784
|
-
'-e',
|
|
793
|
+
'-e', sql,
|
|
785
794
|
], {
|
|
786
795
|
encoding: 'utf-8', timeout: 5000, stdio: ['ignore', 'pipe', 'ignore'],
|
|
787
796
|
// 密码走环境变量,不出现在进程列表里
|
|
788
797
|
env: Object.assign({}, process.env, {MYSQL_PWD: db.password || ''}),
|
|
789
798
|
}).trim()
|
|
790
799
|
if (!out) return null
|
|
791
|
-
|
|
792
|
-
|
|
800
|
+
return out.split('\n')[0].split('\t').map(v => (v === 'NULL' ? null : v))
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// 门店库里最新的那块 TSE(换过 TSE 会有多行);只读数据库,TSE 坏了也读得到
|
|
804
|
+
function readCertFromDb() {
|
|
805
|
+
const row = queryDb('select serialNumber, certificate from RealTseInfo order by id desc limit 1')
|
|
806
|
+
if (!row) return null
|
|
807
|
+
const [serial, certificate] = row
|
|
808
|
+
return {serial, certificate: certificate || null}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// 结账后签名任务进 TseExecuteQueue(finish / finish-zero),后台几秒内执行;
|
|
812
|
+
// 结账超过 5 分钟还没有签名的就算漏签(任务报错、卡在队列里、或者 TSE 回了错)。
|
|
813
|
+
// 同一张单可能有多条任务(补签),按单算
|
|
814
|
+
const DUE_ORDERS = `select q.orderId, max(q.createdAt) as createdAt, max(q.id) as taskId,
|
|
815
|
+
exists (select 1 from RealTseLog l
|
|
816
|
+
where l.orderId = q.orderId and l.bestellungId is null and l.signature <> '') as signed
|
|
817
|
+
from TseExecuteQueue q
|
|
818
|
+
where q.executeType in ('finish', 'finish-zero')
|
|
819
|
+
and q.createdAt >= now() - interval 24 hour
|
|
820
|
+
and q.createdAt <= now() - interval 5 minute
|
|
821
|
+
group by q.orderId`
|
|
822
|
+
|
|
823
|
+
// 最近 24 小时结账的单有没有真的签上:这是 TSE 有没有坏的实际结果,比 ping 准——
|
|
824
|
+
// 关店、睡眠、开机自检时没人结账,不会算成故障;真坏了也一定有单签不上
|
|
825
|
+
function readSigningFromDb() {
|
|
826
|
+
const counts = queryDb(`select count(*), coalesce(sum(not signed), 0),
|
|
827
|
+
unix_timestamp(max(if(signed, createdAt, null))),
|
|
828
|
+
unix_timestamp(max(if(signed, null, createdAt)))
|
|
829
|
+
from (${DUE_ORDERS}) t`)
|
|
830
|
+
if (!counts) return null
|
|
831
|
+
const [due, unsigned, lastSigned, lastUnsigned] = counts
|
|
832
|
+
// 最近一张漏签的单为什么没签上:TSE 回的错(RealTseLog.tseError),没有就看队列任务的报错 / 还在排队
|
|
833
|
+
// 单独兜底:原因查不出来(比如两张表排序规则不一样)也不能连单数一起丢
|
|
834
|
+
const reason = Number(unsigned) > 0 ? safeSync(() => queryDb(`select coalesce(
|
|
835
|
+
(select l.tseError from RealTseLog l where l.orderId = t.orderId and l.tseError <> '' order by l.id desc limit 1),
|
|
836
|
+
nullif(q.note, ''),
|
|
837
|
+
if(q.finished = 0, '签名任务还在排队', null))
|
|
838
|
+
from (${DUE_ORDERS}) t join TseExecuteQueue q on q.id = t.taskId
|
|
839
|
+
where not t.signed
|
|
840
|
+
order by t.createdAt desc limit 1`)) : null
|
|
841
|
+
const toIso = sec => (sec ? new Date(Number(sec) * 1000).toISOString() : null)
|
|
842
|
+
return {
|
|
843
|
+
tseOrders24h: Number(due) || 0,
|
|
844
|
+
tseUnsigned24h: Number(unsigned) || 0,
|
|
845
|
+
tseLastSignedAt: toIso(lastSigned),
|
|
846
|
+
tseLastUnsignedAt: toIso(lastUnsigned),
|
|
847
|
+
tseLastUnsignedReason: reason && reason[0] ? reason[0].slice(0, 250) : null,
|
|
848
|
+
}
|
|
793
849
|
}
|
|
794
850
|
|
|
795
851
|
// TSE 自己的序列号、证书有效期和型号(dnQualifier:BSI-DSZ-CC-1121 是第一代,-V2 是第二代)
|
|
@@ -825,7 +881,12 @@ async function probeTse() {
|
|
|
825
881
|
}
|
|
826
882
|
|
|
827
883
|
// ok:TSE 正常;error:服务在但 TSE 在自检 / 没初始化 / 硬件报错;down:服务连不上
|
|
828
|
-
|
|
884
|
+
// 开机 / 唤醒时 TseHost 先自检约 15 秒,这期间 /ping 回 400:失败就等 20 秒再试一次,免得报成故障
|
|
885
|
+
let ping = await httpGet(`${tseHost}/ping`)
|
|
886
|
+
if (ping.status !== 200) {
|
|
887
|
+
await new Promise(resolve => setTimeout(resolve, PING_RETRY_DELAY_MS))
|
|
888
|
+
ping = await httpGet(`${tseHost}/ping`)
|
|
889
|
+
}
|
|
829
890
|
const tseStatus = ping.status === 0 ? 'down' : (ping.status === 200 ? 'ok' : 'error')
|
|
830
891
|
|
|
831
892
|
// 连不上时报 null,云端保留上次的版本;服务在但没有这个接口(2024-09 之前的构建)报空串
|
|
@@ -843,8 +904,10 @@ async function probeTse() {
|
|
|
843
904
|
}
|
|
844
905
|
|
|
845
906
|
const cert = await collectCert(tseHost, ping.status !== 0)
|
|
907
|
+
// 老版本门店库没有 TseExecuteQueue(2025-03 之前)就不报,云端保留上次的值
|
|
908
|
+
const signing = (await safe(readSigningFromDb)) || {}
|
|
846
909
|
|
|
847
|
-
return {tseEnabled, tseHost, tseStatus, tseAdapterVersion, tseJarSize, ...cert}
|
|
910
|
+
return {tseEnabled, tseHost, tseStatus, tseAdapterVersion, tseJarSize, ...cert, ...signing}
|
|
848
911
|
}
|
|
849
912
|
|
|
850
913
|
// 整体出错就什么都不报,云端保留上次的值
|
|
@@ -19183,7 +19246,7 @@ module.exports = JSON.parse('{"name":"axios","version":"0.19.2","description":"P
|
|
|
19183
19246
|
/***/ ((module) => {
|
|
19184
19247
|
|
|
19185
19248
|
"use strict";
|
|
19186
|
-
module.exports = {"i8":"1.6.
|
|
19249
|
+
module.exports = {"i8":"1.6.118"};
|
|
19187
19250
|
|
|
19188
19251
|
/***/ })
|
|
19189
19252
|
|
package/modules/tse-info.js
CHANGED
|
@@ -21,6 +21,15 @@ const WINDOWS_TSE_DIR = 'Desktop/AadenTseOnWindows'
|
|
|
21
21
|
const WINDOWS_DEFAULT_JAR = 'tse-0.0.3.jar'
|
|
22
22
|
// 和 PHP 的 Api.php 一样给 3 秒
|
|
23
23
|
const PROBE_TIMEOUT_MS = 3000
|
|
24
|
+
const PING_RETRY_DELAY_MS = 20000
|
|
25
|
+
|
|
26
|
+
function safeSync(fn, fallback = null) {
|
|
27
|
+
try {
|
|
28
|
+
return fn()
|
|
29
|
+
} catch (e) {
|
|
30
|
+
return fallback
|
|
31
|
+
}
|
|
32
|
+
}
|
|
24
33
|
|
|
25
34
|
async function safe(fn, fallback = null) {
|
|
26
35
|
try {
|
|
@@ -103,20 +112,67 @@ function servingJarPath() {
|
|
|
103
112
|
return match ? match[1] : LINUX_DEFAULT_JAR
|
|
104
113
|
}
|
|
105
114
|
|
|
106
|
-
//
|
|
107
|
-
function
|
|
115
|
+
// 只读门店库,返回第一行按 tab 分开的各列(没有结果为 null)
|
|
116
|
+
function queryDb(sql) {
|
|
108
117
|
const db = readIniSection('DB')
|
|
109
118
|
const out = execFileSync('mysql', [
|
|
110
119
|
'-h', db.host || 'localhost', '-u', db.user || 'aaden', '-N', '-B', '--raw', db.name || 'aaden',
|
|
111
|
-
'-e',
|
|
120
|
+
'-e', sql,
|
|
112
121
|
], {
|
|
113
122
|
encoding: 'utf-8', timeout: 5000, stdio: ['ignore', 'pipe', 'ignore'],
|
|
114
123
|
// 密码走环境变量,不出现在进程列表里
|
|
115
124
|
env: Object.assign({}, process.env, {MYSQL_PWD: db.password || ''}),
|
|
116
125
|
}).trim()
|
|
117
126
|
if (!out) return null
|
|
118
|
-
|
|
119
|
-
|
|
127
|
+
return out.split('\n')[0].split('\t').map(v => (v === 'NULL' ? null : v))
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// 门店库里最新的那块 TSE(换过 TSE 会有多行);只读数据库,TSE 坏了也读得到
|
|
131
|
+
function readCertFromDb() {
|
|
132
|
+
const row = queryDb('select serialNumber, certificate from RealTseInfo order by id desc limit 1')
|
|
133
|
+
if (!row) return null
|
|
134
|
+
const [serial, certificate] = row
|
|
135
|
+
return {serial, certificate: certificate || null}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// 结账后签名任务进 TseExecuteQueue(finish / finish-zero),后台几秒内执行;
|
|
139
|
+
// 结账超过 5 分钟还没有签名的就算漏签(任务报错、卡在队列里、或者 TSE 回了错)。
|
|
140
|
+
// 同一张单可能有多条任务(补签),按单算
|
|
141
|
+
const DUE_ORDERS = `select q.orderId, max(q.createdAt) as createdAt, max(q.id) as taskId,
|
|
142
|
+
exists (select 1 from RealTseLog l
|
|
143
|
+
where l.orderId = q.orderId and l.bestellungId is null and l.signature <> '') as signed
|
|
144
|
+
from TseExecuteQueue q
|
|
145
|
+
where q.executeType in ('finish', 'finish-zero')
|
|
146
|
+
and q.createdAt >= now() - interval 24 hour
|
|
147
|
+
and q.createdAt <= now() - interval 5 minute
|
|
148
|
+
group by q.orderId`
|
|
149
|
+
|
|
150
|
+
// 最近 24 小时结账的单有没有真的签上:这是 TSE 有没有坏的实际结果,比 ping 准——
|
|
151
|
+
// 关店、睡眠、开机自检时没人结账,不会算成故障;真坏了也一定有单签不上
|
|
152
|
+
function readSigningFromDb() {
|
|
153
|
+
const counts = queryDb(`select count(*), coalesce(sum(not signed), 0),
|
|
154
|
+
unix_timestamp(max(if(signed, createdAt, null))),
|
|
155
|
+
unix_timestamp(max(if(signed, null, createdAt)))
|
|
156
|
+
from (${DUE_ORDERS}) t`)
|
|
157
|
+
if (!counts) return null
|
|
158
|
+
const [due, unsigned, lastSigned, lastUnsigned] = counts
|
|
159
|
+
// 最近一张漏签的单为什么没签上:TSE 回的错(RealTseLog.tseError),没有就看队列任务的报错 / 还在排队
|
|
160
|
+
// 单独兜底:原因查不出来(比如两张表排序规则不一样)也不能连单数一起丢
|
|
161
|
+
const reason = Number(unsigned) > 0 ? safeSync(() => queryDb(`select coalesce(
|
|
162
|
+
(select l.tseError from RealTseLog l where l.orderId = t.orderId and l.tseError <> '' order by l.id desc limit 1),
|
|
163
|
+
nullif(q.note, ''),
|
|
164
|
+
if(q.finished = 0, '签名任务还在排队', null))
|
|
165
|
+
from (${DUE_ORDERS}) t join TseExecuteQueue q on q.id = t.taskId
|
|
166
|
+
where not t.signed
|
|
167
|
+
order by t.createdAt desc limit 1`)) : null
|
|
168
|
+
const toIso = sec => (sec ? new Date(Number(sec) * 1000).toISOString() : null)
|
|
169
|
+
return {
|
|
170
|
+
tseOrders24h: Number(due) || 0,
|
|
171
|
+
tseUnsigned24h: Number(unsigned) || 0,
|
|
172
|
+
tseLastSignedAt: toIso(lastSigned),
|
|
173
|
+
tseLastUnsignedAt: toIso(lastUnsigned),
|
|
174
|
+
tseLastUnsignedReason: reason && reason[0] ? reason[0].slice(0, 250) : null,
|
|
175
|
+
}
|
|
120
176
|
}
|
|
121
177
|
|
|
122
178
|
// TSE 自己的序列号、证书有效期和型号(dnQualifier:BSI-DSZ-CC-1121 是第一代,-V2 是第二代)
|
|
@@ -152,7 +208,12 @@ async function probeTse() {
|
|
|
152
208
|
}
|
|
153
209
|
|
|
154
210
|
// ok:TSE 正常;error:服务在但 TSE 在自检 / 没初始化 / 硬件报错;down:服务连不上
|
|
155
|
-
|
|
211
|
+
// 开机 / 唤醒时 TseHost 先自检约 15 秒,这期间 /ping 回 400:失败就等 20 秒再试一次,免得报成故障
|
|
212
|
+
let ping = await httpGet(`${tseHost}/ping`)
|
|
213
|
+
if (ping.status !== 200) {
|
|
214
|
+
await new Promise(resolve => setTimeout(resolve, PING_RETRY_DELAY_MS))
|
|
215
|
+
ping = await httpGet(`${tseHost}/ping`)
|
|
216
|
+
}
|
|
156
217
|
const tseStatus = ping.status === 0 ? 'down' : (ping.status === 200 ? 'ok' : 'error')
|
|
157
218
|
|
|
158
219
|
// 连不上时报 null,云端保留上次的版本;服务在但没有这个接口(2024-09 之前的构建)报空串
|
|
@@ -170,8 +231,10 @@ async function probeTse() {
|
|
|
170
231
|
}
|
|
171
232
|
|
|
172
233
|
const cert = await collectCert(tseHost, ping.status !== 0)
|
|
234
|
+
// 老版本门店库没有 TseExecuteQueue(2025-03 之前)就不报,云端保留上次的值
|
|
235
|
+
const signing = (await safe(readSigningFromDb)) || {}
|
|
173
236
|
|
|
174
|
-
return {tseEnabled, tseHost, tseStatus, tseAdapterVersion, tseJarSize, ...cert}
|
|
237
|
+
return {tseEnabled, tseHost, tseStatus, tseAdapterVersion, tseJarSize, ...cert, ...signing}
|
|
175
238
|
}
|
|
176
239
|
|
|
177
240
|
// 整体出错就什么都不报,云端保留上次的值
|