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 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
- // 门店库里最新的那块 TSE(换过 TSE 会有多行);只读数据库,TSE 坏了也读得到
780
- function readCertFromDb() {
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', 'select serialNumber, certificate from RealTseInfo order by id desc limit 1',
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
- const [serial, certificate] = out.split('\t')
792
- return {serial, certificate: certificate && certificate !== 'NULL' ? certificate : null}
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
- const ping = await httpGet(`${tseHost}/ping`)
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.117"};
19249
+ module.exports = {"i8":"1.6.118"};
19187
19250
 
19188
19251
  /***/ })
19189
19252
 
@@ -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
- // 门店库里最新的那块 TSE(换过 TSE 会有多行);只读数据库,TSE 坏了也读得到
107
- function readCertFromDb() {
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', 'select serialNumber, certificate from RealTseInfo order by id desc limit 1',
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
- const [serial, certificate] = out.split('\t')
119
- return {serial, certificate: certificate && certificate !== 'NULL' ? certificate : null}
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
- const ping = await httpGet(`${tseHost}/ping`)
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
  // 整体出错就什么都不报,云端保留上次的值
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aaden-cli",
3
- "version": "1.6.117",
3
+ "version": "1.6.118",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {