aaden-cli 1.6.122 → 1.6.124
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.
|
@@ -11,6 +11,14 @@
|
|
|
11
11
|
local(localhost/127.x,收银机本机)、lan(局域网 IP,平板等)、none(没有 Referer,一般是直接打开页面)。
|
|
12
12
|
只统计真实存在的目录,扫描器乱打的路径不算。
|
|
13
13
|
|
|
14
|
+
另外按「客户端类型」统计打到 /PHP/ 的请求,应用名以 "@client/" 开头(原生 App 不带 Referer,上面那套看不到):
|
|
15
|
+
- User-Agent 是 Aaden-<应用>/<版本> 的(约定的统一格式)→ "@client/Aaden-<应用>/<版本>"
|
|
16
|
+
- 否则按 HTTP 库分:okhttp(快消版安卓收银 2.0/3.0、K2)、ktor(点单 / 老板端)、
|
|
17
|
+
electron-<系统>(餐厅版桌面,windows / linux)、browser-<系统>(android 多半是商米机)、
|
|
18
|
+
system(门店主机自己的 cron / PHP / worker、云端经 ngrok 的请求)、other
|
|
19
|
+
- okhttp 另外数几个只有某一代收银才调的接口(MARKERS),用来区分 2.0 和 3.x,比如 "@client/okhttp:DashboardData.php"
|
|
20
|
+
客户端类的 sources 按客户端 IP 分(没有 Referer):local=127.x/::1(含经 ngrok 进来的),lan=其它。
|
|
21
|
+
|
|
14
22
|
用法: appCenterUsage.py [日志目录,默认 /var/log/apache2];需要能读 access.log*(root 或 adm 组)。
|
|
15
23
|
"""
|
|
16
24
|
import datetime
|
|
@@ -49,6 +57,52 @@ def source_of(ref_host):
|
|
|
49
57
|
return 'lan'
|
|
50
58
|
|
|
51
59
|
|
|
60
|
+
# 统一 User-Agent 约定:Aaden-<应用>/<版本> (<平台>; build <提交>; <渠道>)
|
|
61
|
+
AADEN_UA = re.compile(r'^Aaden[- ]([A-Za-z0-9_.-]{1,24})/([A-Za-z0-9_.+-]{1,20})') # 也认老的 "Aaden Buffet/2.16"
|
|
62
|
+
# 门店主机自己(cron 的 wget/curl、PHP 的 Guzzle、node worker 的 axios)和云端经 ngrok 来的请求,不是收银前端
|
|
63
|
+
SYSTEM_UA = ('wget/', 'curl/', 'guzzlehttp', 'axios/', 'reactornetty', 'java/', 'apache-httpclient', 'undici',
|
|
64
|
+
'python-requests', 'go-http-client', 'node-fetch', 'php/')
|
|
65
|
+
# 只有某一代安卓收银会调的 PHP 接口:DashboardData.php 只在 2.0,QuickOrder.php 只在较新的 3.x
|
|
66
|
+
MARKERS = ('DashboardData.php', 'QuickOrder.php')
|
|
67
|
+
CLIENT = '@client/'
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def client_kind(ua):
|
|
71
|
+
"""User-Agent → 客户端类型(不含 "@client/" 前缀)"""
|
|
72
|
+
m = AADEN_UA.match(ua)
|
|
73
|
+
if m:
|
|
74
|
+
return f'Aaden-{m.group(1)}/{m.group(2)}'
|
|
75
|
+
low = ua.lower()
|
|
76
|
+
if low in ('', '-') or low.startswith(SYSTEM_UA):
|
|
77
|
+
return 'system'
|
|
78
|
+
if low.startswith('okhttp'):
|
|
79
|
+
return 'okhttp'
|
|
80
|
+
if 'ktor' in low:
|
|
81
|
+
return 'ktor'
|
|
82
|
+
if 'electron' in low:
|
|
83
|
+
return 'electron-' + ua_os(low)
|
|
84
|
+
if low.startswith('mozilla'):
|
|
85
|
+
return 'browser-' + ua_os(low)
|
|
86
|
+
return 'other'
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def ua_os(low):
|
|
90
|
+
"""餐厅版要分 Windows / Ubuntu 桌面和安卓平板(商米等)"""
|
|
91
|
+
if 'android' in low:
|
|
92
|
+
return 'android'
|
|
93
|
+
if 'windows' in low:
|
|
94
|
+
return 'windows'
|
|
95
|
+
if 'linux' in low or 'x11' in low:
|
|
96
|
+
return 'linux'
|
|
97
|
+
if 'iphone' in low or 'ipad' in low or 'mac os' in low:
|
|
98
|
+
return 'apple'
|
|
99
|
+
return 'other'
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def ip_source(ip):
|
|
103
|
+
return 'local' if ip.startswith('127.') or ip == '::1' else 'lan'
|
|
104
|
+
|
|
105
|
+
|
|
52
106
|
TS = re.compile(r'^(\d{2})/([A-Z][a-z]{2})/(\d{4}):(\d{2}:\d{2}:\d{2})')
|
|
53
107
|
|
|
54
108
|
|
|
@@ -122,6 +176,25 @@ def _app_version(rel):
|
|
|
122
176
|
return info
|
|
123
177
|
|
|
124
178
|
|
|
179
|
+
def count_client(stats, m):
|
|
180
|
+
"""一条 /PHP/ 请求按客户端类型记一次;okhttp 碰到标记接口再多记一行"""
|
|
181
|
+
day, at = parse_ts(m['ts'])
|
|
182
|
+
if not day:
|
|
183
|
+
return
|
|
184
|
+
kind = client_kind(m['ua'])
|
|
185
|
+
names = [CLIENT + kind]
|
|
186
|
+
if kind == 'okhttp':
|
|
187
|
+
bare = m['path'].split('?')[0]
|
|
188
|
+
names += [f'{CLIENT}okhttp:{mk}' for mk in MARKERS if bare.endswith('/' + mk)]
|
|
189
|
+
for name in names:
|
|
190
|
+
s = stats[(name, day)]
|
|
191
|
+
s['requests'] += 1
|
|
192
|
+
s['clients'].add((m['ip'], '-', m['ua'][:120]))
|
|
193
|
+
s['sources'][ip_source(m['ip'])] += 1
|
|
194
|
+
if at > s['lastSeen']:
|
|
195
|
+
s['lastSeen'] = at
|
|
196
|
+
|
|
197
|
+
|
|
125
198
|
def main():
|
|
126
199
|
installed = []
|
|
127
200
|
if os.path.isdir(APPCENTER):
|
|
@@ -159,11 +232,16 @@ def main():
|
|
|
159
232
|
log_from = at
|
|
160
233
|
if log_to is None or at > log_to:
|
|
161
234
|
log_to = at
|
|
162
|
-
|
|
235
|
+
php = ' /PHP/' in line
|
|
236
|
+
if not php and (prefilter is None or not prefilter.search(line)):
|
|
163
237
|
continue
|
|
164
238
|
m = LINE.match(line)
|
|
165
239
|
if not m:
|
|
166
240
|
continue
|
|
241
|
+
if php and m['path'].startswith('/PHP/'):
|
|
242
|
+
count_client(stats, m)
|
|
243
|
+
if prefilter is None or not prefilter.search(line):
|
|
244
|
+
continue
|
|
167
245
|
path, ref = m['path'], m['ref']
|
|
168
246
|
pm = APP_IN_PATH.match(path)
|
|
169
247
|
rm = APP_IN_REF.match(ref) if ref and ref != '-' else None
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Windows 门店(后端跑在 WSL 里):经 WSL interop 调 Windows 的 PowerShell,查看 / 升级桌面收银 Aaden Desktop。
|
|
3
|
+
# 云端设备命令 WINDOWS_PROBE(probe)和 DESKTOP_UPDATE(update)用,输出整段回传云端。
|
|
4
|
+
#
|
|
5
|
+
# bash windowsDesktop.sh probe 只读:装在哪、什么版本、谁在用、多久没人操作、有没有已下载待装的更新
|
|
6
|
+
# bash windowsDesktop.sh update 下载最新安装包 → 收银电脑 10 分钟没人操作才动手 → 关掉收银程序、静默安装、重新打开
|
|
7
|
+
#
|
|
8
|
+
# 起因(POS-129 / POS-148):1.2.160 以前的桌面端只在正常退出时才装更新,收银机全屏、没人正常退出,
|
|
9
|
+
# 75 台 Windows 停在旧版本。装上 1.2.160 以后它会自己更新,这里只需要推一次。
|
|
10
|
+
#
|
|
11
|
+
# WSL 是门店 Windows 登录用户的启动项(run_hidden.vbs → start-aaden-server.bat)拉起来的,
|
|
12
|
+
# 所以经 interop 起的 Windows 进程以这个登录用户、在他的桌面会话里运行,桌面端也装在他的 %LOCALAPPDATA%。
|
|
13
|
+
#
|
|
14
|
+
# 退出码:0 成功(探测成功 / 装好了 / 已是最新);3 不是 WSL;4 调不起 Windows 程序;
|
|
15
|
+
# 22 收银电脑有人在用,没动;其余是升级失败(见输出)
|
|
16
|
+
MODE=${1:-probe}
|
|
17
|
+
MIN_IDLE_SECONDS=600
|
|
18
|
+
PS=/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe
|
|
19
|
+
|
|
20
|
+
if [ "$MODE" != probe ] && [ "$MODE" != update ]; then
|
|
21
|
+
echo "用法: windowsDesktop.sh probe|update"
|
|
22
|
+
exit 2
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
if ! grep -qi microsoft /proc/version 2>/dev/null; then
|
|
26
|
+
echo "不是 WSL 设备($(uname -r)),这条命令只对 Windows 门店"
|
|
27
|
+
exit 3
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
echo "WSL: $(uname -r) 用户: $(id -un)"
|
|
31
|
+
echo "binfmt WSLInterop: $(head -1 /proc/sys/fs/binfmt_misc/WSLInterop 2>/dev/null || echo 没有)"
|
|
32
|
+
echo "WSL_INTEROP: ${WSL_INTEROP:-(未设置,cron 下正常)}"
|
|
33
|
+
ls -l /run/WSL/ 2>/dev/null | sed 's/^/ /'
|
|
34
|
+
|
|
35
|
+
if [ ! -e "$PS" ]; then
|
|
36
|
+
echo "找不到 $PS(C 盘没挂到 /mnt/c?)"
|
|
37
|
+
ls -ld /mnt/c 2>&1
|
|
38
|
+
exit 4
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
PS_PREFIX=()
|
|
42
|
+
try_ps() { timeout 60 "${PS_PREFIX[@]}" "$PS" -NoProfile -NonInteractive -Command 'exit 0' </dev/null >/dev/null 2>&1; }
|
|
43
|
+
|
|
44
|
+
# cron 和命令执行进程里没有 WSL_INTEROP,直接调 .exe 可能起不来:依次试 /run/WSL 下的 interop socket,再试 sudo
|
|
45
|
+
setup_interop() {
|
|
46
|
+
try_ps && { echo "interop: 直接可用"; return 0; }
|
|
47
|
+
local s
|
|
48
|
+
for s in $(ls -t /run/WSL/*_interop 2>/dev/null); do
|
|
49
|
+
PS_PREFIX=(env WSL_INTEROP="$s")
|
|
50
|
+
try_ps && { echo "interop: 用 $s"; return 0; }
|
|
51
|
+
done
|
|
52
|
+
if [ -n "$AADEN_PW" ] && echo "$AADEN_PW" | sudo -S -p '' -v 2>/dev/null; then
|
|
53
|
+
for s in $(ls -t /run/WSL/*_interop 2>/dev/null); do
|
|
54
|
+
PS_PREFIX=(sudo -n env WSL_INTEROP="$s")
|
|
55
|
+
try_ps && { echo "interop: sudo + $s"; return 0; }
|
|
56
|
+
done
|
|
57
|
+
fi
|
|
58
|
+
PS_PREFIX=()
|
|
59
|
+
return 1
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if ! setup_interop; then
|
|
63
|
+
echo "调不起 Windows 的 PowerShell(interop 没开,或者 socket 不可用),没法从这里管桌面端"
|
|
64
|
+
exit 4
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
# 跑一段 PowerShell:用 -EncodedCommand 传,免得引号转义;Windows 输出的 \r 去掉
|
|
68
|
+
run_ps() {
|
|
69
|
+
local enc
|
|
70
|
+
enc=$(printf '%s' "$1" | iconv -f UTF-8 -t UTF-16LE | base64 -w0)
|
|
71
|
+
timeout "${2:-120}" "${PS_PREFIX[@]}" "$PS" -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand "$enc" </dev/null 2>&1 | tr -d '\r'
|
|
72
|
+
return "${PIPESTATUS[0]}"
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# 两种模式共用:路径、版本、桌面会话、空闲时间
|
|
76
|
+
read -r -d '' PS_COMMON <<'EOF'
|
|
77
|
+
$ErrorActionPreference = 'Stop'
|
|
78
|
+
$ProgressPreference = 'SilentlyContinue'
|
|
79
|
+
$exe = Join-Path $env:LOCALAPPDATA 'Programs\aaden\Aaden Desktop.exe'
|
|
80
|
+
$mySession = [Diagnostics.Process]::GetCurrentProcess().SessionId
|
|
81
|
+
function Get-InstalledVersion {
|
|
82
|
+
if (-not (Test-Path $exe)) { return $null }
|
|
83
|
+
$v = (Get-Item $exe).VersionInfo.ProductVersion
|
|
84
|
+
if (-not $v) { return $null }
|
|
85
|
+
return (($v -split '[^0-9.]')[0].Split('.') | Select-Object -First 3) -join '.'
|
|
86
|
+
}
|
|
87
|
+
function Get-DesktopProcesses { @(Get-CimInstance Win32_Process -Filter "Name='Aaden Desktop.exe'") }
|
|
88
|
+
function Get-IdleSeconds {
|
|
89
|
+
if (-not ('AadenIdle' -as [type])) {
|
|
90
|
+
Add-Type -TypeDefinition @'
|
|
91
|
+
using System;
|
|
92
|
+
using System.Runtime.InteropServices;
|
|
93
|
+
public static class AadenIdle {
|
|
94
|
+
[StructLayout(LayoutKind.Sequential)] struct LastInput { public uint cbSize; public uint dwTime; }
|
|
95
|
+
[DllImport("user32.dll")] static extern bool GetLastInputInfo(ref LastInput info);
|
|
96
|
+
public static long Seconds() {
|
|
97
|
+
var info = new LastInput(); info.cbSize = (uint)Marshal.SizeOf(info);
|
|
98
|
+
if (!GetLastInputInfo(ref info)) return -1;
|
|
99
|
+
return (long)(((uint)Environment.TickCount - info.dwTime) / 1000);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
'@
|
|
103
|
+
}
|
|
104
|
+
[AadenIdle]::Seconds()
|
|
105
|
+
}
|
|
106
|
+
function Get-LatestRelease {
|
|
107
|
+
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
|
108
|
+
Invoke-RestMethod 'https://api.github.com/repos/innerken-gmbh/aaden-vue-app/releases/latest' -UseBasicParsing -TimeoutSec 30
|
|
109
|
+
}
|
|
110
|
+
EOF
|
|
111
|
+
|
|
112
|
+
read -r -d '' PS_PROBE <<'EOF'
|
|
113
|
+
$ErrorActionPreference = 'Continue'
|
|
114
|
+
"windows_user=$env:USERDOMAIN\$env:USERNAME session=$mySession"
|
|
115
|
+
try { $os = Get-CimInstance Win32_OperatingSystem; "os=$($os.Caption) $($os.Version) boot=$($os.LastBootUpTime)" } catch { "os=? $_" }
|
|
116
|
+
"exe=$exe exists=$(Test-Path $exe) version=$(Get-InstalledVersion)"
|
|
117
|
+
$machineWide = Join-Path $env:ProgramFiles 'Aaden Desktop\Aaden Desktop.exe'
|
|
118
|
+
"program_files_install=$(Test-Path $machineWide)"
|
|
119
|
+
$procs = Get-DesktopProcesses
|
|
120
|
+
"desktop_processes=$($procs.Count)"
|
|
121
|
+
$procs | Group-Object SessionId, ExecutablePath | ForEach-Object { " session/path=$($_.Name) count=$($_.Count)" }
|
|
122
|
+
$main = $procs | Where-Object { $procs.ProcessId -notcontains $_.ParentProcessId } | Select-Object -First 1
|
|
123
|
+
if ($main) {
|
|
124
|
+
try { $o = Invoke-CimMethod -InputObject $main -MethodName GetOwner; " main pid=$($main.ProcessId) owner=$($o.Domain)\$($o.User) started=$($main.CreationDate)" } catch { " owner=? $_" }
|
|
125
|
+
}
|
|
126
|
+
try { "idle_seconds=$(Get-IdleSeconds)" } catch { "idle_seconds=? $_" }
|
|
127
|
+
$pending = Join-Path $env:LOCALAPPDATA 'aaden-updater\pending'
|
|
128
|
+
if (Test-Path $pending) { "updater_pending=$((Get-ChildItem $pending | ForEach-Object { $_.Name }) -join ',')" } else { 'updater_pending=none' }
|
|
129
|
+
$startup = [Environment]::GetFolderPath('Startup')
|
|
130
|
+
"startup_run_hidden=$(Test-Path (Join-Path $startup 'run_hidden.vbs'))"
|
|
131
|
+
try { $r = Get-LatestRelease; "latest_release=$($r.tag_name)" } catch { "latest_release=? $_" }
|
|
132
|
+
EOF
|
|
133
|
+
|
|
134
|
+
read -r -d '' PS_UPDATE <<'EOF'
|
|
135
|
+
$current = Get-InstalledVersion
|
|
136
|
+
"installed=$current"
|
|
137
|
+
if (-not $current) { "ABORT: Aaden Desktop is not installed at $exe"; exit 20 }
|
|
138
|
+
$other = @(Get-DesktopProcesses | Where-Object { $_.SessionId -ne $mySession })
|
|
139
|
+
if ($other.Count) { "ABORT: Aaden Desktop is running in another session ($(($other | ForEach-Object { $_.SessionId } | Sort-Object -Unique) -join ',')), ours is $mySession"; exit 21 }
|
|
140
|
+
$idle = Get-IdleSeconds
|
|
141
|
+
"idle_seconds=$idle"
|
|
142
|
+
if ($idle -lt __MIN_IDLE__) { "BUSY: last keyboard/mouse input $idle s ago (< __MIN_IDLE__ s), not touching it"; exit 22 }
|
|
143
|
+
$r = Get-LatestRelease
|
|
144
|
+
$latest = $r.tag_name.TrimStart('v')
|
|
145
|
+
"latest=$latest"
|
|
146
|
+
if ($current -eq $latest) { 'UP_TO_DATE'; exit 0 }
|
|
147
|
+
$asset = $r.assets | Where-Object { $_.name -like '*.exe' } | Select-Object -First 1
|
|
148
|
+
if (-not $asset) { "ABORT: release $($r.tag_name) has no .exe"; exit 23 }
|
|
149
|
+
$file = Join-Path $env:TEMP $asset.name
|
|
150
|
+
Invoke-WebRequest $asset.browser_download_url -OutFile $file -UseBasicParsing -TimeoutSec 900
|
|
151
|
+
$size = (Get-Item $file).Length
|
|
152
|
+
"downloaded $($asset.name) $size bytes"
|
|
153
|
+
if ($size -ne $asset.size) { "ABORT: size mismatch, expected $($asset.size)"; exit 23 }
|
|
154
|
+
# 下载可能要几分钟,动手前再确认一次没人在用
|
|
155
|
+
$idle = Get-IdleSeconds
|
|
156
|
+
if ($idle -lt __MIN_IDLE__) { "BUSY after download: last input $idle s ago, not touching it"; exit 22 }
|
|
157
|
+
Stop-Process -Name 'Aaden Desktop' -Force -ErrorAction SilentlyContinue
|
|
158
|
+
Start-Sleep -Seconds 2
|
|
159
|
+
$installer = Start-Process $file -ArgumentList '/S' -PassThru
|
|
160
|
+
$null = $installer.Handle
|
|
161
|
+
$finished = $installer.WaitForExit(300000)
|
|
162
|
+
$after = Get-InstalledVersion
|
|
163
|
+
# 不管装没装好都把收银程序开回来;经 explorer 打开,不挂在这个进程下面
|
|
164
|
+
Start-Process explorer.exe -ArgumentList ('"' + $exe + '"')
|
|
165
|
+
if (-not $finished) { "FAILED: installer still running after 5 minutes"; exit 24 }
|
|
166
|
+
"installer_exit=$($installer.ExitCode) installed_after=$after"
|
|
167
|
+
if ($after -ne $latest) { "FAILED: version is $after, expected $latest"; exit 25 }
|
|
168
|
+
"INSTALLED $current -> $after"
|
|
169
|
+
exit 0
|
|
170
|
+
EOF
|
|
171
|
+
|
|
172
|
+
# 升级后过一会儿再看:收银程序没起来就再开一次
|
|
173
|
+
read -r -d '' PS_CHECK_RUNNING <<'EOF'
|
|
174
|
+
$n = (Get-DesktopProcesses).Count
|
|
175
|
+
"desktop_processes=$n installed=$(Get-InstalledVersion)"
|
|
176
|
+
if (-not $n) { Start-Process explorer.exe -ArgumentList ('"' + $exe + '"'); 'relaunched'; exit 1 }
|
|
177
|
+
EOF
|
|
178
|
+
|
|
179
|
+
case "$MODE" in
|
|
180
|
+
probe)
|
|
181
|
+
echo "--- Windows 侧"
|
|
182
|
+
run_ps "$PS_COMMON"$'\n'"$PS_PROBE" 180
|
|
183
|
+
exit $?
|
|
184
|
+
;;
|
|
185
|
+
update)
|
|
186
|
+
echo "--- 升级(收银电脑 ${MIN_IDLE_SECONDS} 秒没人操作才动手)"
|
|
187
|
+
run_ps "$PS_COMMON"$'\n'"${PS_UPDATE//__MIN_IDLE__/$MIN_IDLE_SECONDS}" 1500
|
|
188
|
+
code=$?
|
|
189
|
+
[ "$code" -ne 0 ] && exit "$code"
|
|
190
|
+
sleep 20
|
|
191
|
+
echo "--- 20 秒后确认收银程序在运行"
|
|
192
|
+
run_ps "$PS_COMMON"$'\n'"$PS_CHECK_RUNNING" 120
|
|
193
|
+
;;
|
|
194
|
+
esac
|
package/dist/index.js
CHANGED
|
@@ -116,6 +116,9 @@ const COMMANDS = {
|
|
|
116
116
|
// 诊断脚本在门店后端里(≥ 1.9.80):do-log 坏了的设备由云端经 ngrok 触发同一个脚本
|
|
117
117
|
DIAGNOSE: () => 'S=/var/www/html/PHP/scripts/diagnose.sh; if [ -f "$S" ]; then bash "$S"; ' +
|
|
118
118
|
'else echo "后端低于 1.9.80,没有诊断脚本:先下发「只更新后端」"; exit 2; fi',
|
|
119
|
+
// Windows 门店(后端在 WSL 里):经 interop 查看 / 升级 Windows 上的桌面收银(POS-148)
|
|
120
|
+
WINDOWS_PROBE: root => 'bash ' + JSON.stringify(path.join(root, 'AadenDeploy/windowsDesktop.sh')) + ' probe',
|
|
121
|
+
DESKTOP_UPDATE: root => 'bash ' + JSON.stringify(path.join(root, 'AadenDeploy/windowsDesktop.sh')) + ' update',
|
|
119
122
|
}
|
|
120
123
|
|
|
121
124
|
/** 每条命令最多跑多久;latest.zip 全量在慢网络上要 20 分钟 */
|
|
@@ -19352,7 +19355,7 @@ module.exports = JSON.parse('{"name":"axios","version":"0.19.2","description":"P
|
|
|
19352
19355
|
/***/ ((module) => {
|
|
19353
19356
|
|
|
19354
19357
|
"use strict";
|
|
19355
|
-
module.exports = {"i8":"1.6.
|
|
19358
|
+
module.exports = {"i8":"1.6.124"};
|
|
19356
19359
|
|
|
19357
19360
|
/***/ })
|
|
19358
19361
|
|
|
@@ -29,6 +29,9 @@ const COMMANDS = {
|
|
|
29
29
|
// 诊断脚本在门店后端里(≥ 1.9.80):do-log 坏了的设备由云端经 ngrok 触发同一个脚本
|
|
30
30
|
DIAGNOSE: () => 'S=/var/www/html/PHP/scripts/diagnose.sh; if [ -f "$S" ]; then bash "$S"; ' +
|
|
31
31
|
'else echo "后端低于 1.9.80,没有诊断脚本:先下发「只更新后端」"; exit 2; fi',
|
|
32
|
+
// Windows 门店(后端在 WSL 里):经 interop 查看 / 升级 Windows 上的桌面收银(POS-148)
|
|
33
|
+
WINDOWS_PROBE: root => 'bash ' + JSON.stringify(path.join(root, 'AadenDeploy/windowsDesktop.sh')) + ' probe',
|
|
34
|
+
DESKTOP_UPDATE: root => 'bash ' + JSON.stringify(path.join(root, 'AadenDeploy/windowsDesktop.sh')) + ' update',
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
/** 每条命令最多跑多久;latest.zip 全量在慢网络上要 20 分钟 */
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aaden-cli",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.124",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"test": "node test/snap-version.test.js && python3 test/app_version_test.py",
|
|
7
|
+
"test": "node test/snap-version.test.js && python3 test/app_version_test.py && python3 test/client_kind_test.py",
|
|
8
8
|
"update": "git add . && release-it --ci"
|
|
9
9
|
},
|
|
10
10
|
"bin": {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# python3 test/client_kind_test.py:appCenterUsage 按客户端类型统计 /PHP/ 请求
|
|
2
|
+
import importlib.util, io, json, os, sys, tempfile, contextlib
|
|
3
|
+
sys.dont_write_bytecode = True
|
|
4
|
+
|
|
5
|
+
logdir = tempfile.mkdtemp()
|
|
6
|
+
root = tempfile.mkdtemp()
|
|
7
|
+
os.makedirs(os.path.join(root, 'App')); open(os.path.join(root, 'App', 'index.html'), 'w').write('<p>')
|
|
8
|
+
os.makedirs(os.path.join(root, 'PHP'))
|
|
9
|
+
|
|
10
|
+
L = '{ip} - - [14/Oct/2026:12:{mm}:00 +0200] "GET {path} HTTP/1.1" 200 12 "{ref}" "{ua}"\n'
|
|
11
|
+
lines = [
|
|
12
|
+
# 2.0 安卓收银:okhttp + 2.0 独有的 DashboardData.php
|
|
13
|
+
L.format(ip='192.168.1.20', mm='01', path='/PHP/Orders.php?op=list', ref='-', ua='okhttp/5.0.0-alpha.14'),
|
|
14
|
+
L.format(ip='192.168.1.20', mm='02', path='/PHP/DashboardData.php?x=1', ref='-', ua='okhttp/5.0.0-alpha.14'),
|
|
15
|
+
# 3.x 安卓收银:QuickOrder.php
|
|
16
|
+
L.format(ip='192.168.1.21', mm='03', path='/PHP/QuickOrder.php?op=config', ref='-', ua='okhttp/5.0.0-alpha.14'),
|
|
17
|
+
# 统一格式的 UA
|
|
18
|
+
L.format(ip='192.168.1.22', mm='04', path='/PHP/Orders.php', ref='-', ua='Aaden-Antelope/3.1.32 (android; build 38a5a79; pgyer)'),
|
|
19
|
+
# 商米机浏览器里的 /App:既算 App(Referer)也算 browser-android
|
|
20
|
+
L.format(ip='127.0.0.1', mm='05', path='/PHP/Orders.php', ref='http://localhost/App/', ua='Mozilla/5.0 (Linux; Android 7.1.2; T2s Build/NHG47K) Chrome/120'),
|
|
21
|
+
L.format(ip='127.0.0.1', mm='06', path='/PHP/Orders.php', ref='-', ua='Mozilla/5.0 (Windows NT 10.0; Win64; x64) aaden-desktop Electron/28.0.0'),
|
|
22
|
+
L.format(ip='192.168.1.30', mm='07', path='/PHP/Dishes.php', ref='-', ua='Ktor client'),
|
|
23
|
+
# 不是 /PHP/ 的不算客户端;乱码时间整行跳过
|
|
24
|
+
L.format(ip='192.168.1.20', mm='08', path='/Resource/dishImg/1.png', ref='-', ua='okhttp/5.0.0-alpha.14'),
|
|
25
|
+
'\x16\x03 - - [\x00garbage] "GET /PHP/x.php HTTP/1.1" 200 1 "-" "okhttp/5"\n',
|
|
26
|
+
]
|
|
27
|
+
open(os.path.join(logdir, 'access.log'), 'w').writelines(lines)
|
|
28
|
+
|
|
29
|
+
spec = importlib.util.spec_from_file_location('usage', os.path.join(os.path.dirname(__file__), '..', 'AadenDeploy', 'appCenterUsage.py'))
|
|
30
|
+
usage = importlib.util.module_from_spec(spec)
|
|
31
|
+
sys.argv = [sys.argv[0], logdir]
|
|
32
|
+
spec.loader.exec_module(usage)
|
|
33
|
+
usage.DOCROOT = root
|
|
34
|
+
usage.APPCENTER = os.path.join(root, 'AppCenter')
|
|
35
|
+
|
|
36
|
+
buf = io.StringIO()
|
|
37
|
+
with contextlib.redirect_stdout(buf):
|
|
38
|
+
usage.main()
|
|
39
|
+
out = json.loads(buf.getvalue())
|
|
40
|
+
rows = {r['app']: r for r in out['usage']}
|
|
41
|
+
|
|
42
|
+
assert rows['@client/okhttp']['requests'] == 3 and rows['@client/okhttp']['clients'] == 2, rows['@client/okhttp']
|
|
43
|
+
assert rows['@client/okhttp:DashboardData.php']['requests'] == 1
|
|
44
|
+
assert rows['@client/okhttp:QuickOrder.php']['requests'] == 1
|
|
45
|
+
assert rows['@client/Aaden-Antelope/3.1.32']['requests'] == 1
|
|
46
|
+
assert rows['@client/browser-android']['requests'] == 1 and rows['@client/browser-android']['sources']['local'] == 1
|
|
47
|
+
assert rows['@client/electron-windows']['requests'] == 1
|
|
48
|
+
assert rows['@client/ktor']['requests'] == 1 and rows['@client/ktor']['sources']['lan'] == 1
|
|
49
|
+
assert rows['App']['requests'] == 1, rows['App']
|
|
50
|
+
assert not any(a.startswith('@client/') for a in out['installedApps'])
|
|
51
|
+
assert set(rows) == {'App', '@client/okhttp', '@client/okhttp:DashboardData.php', '@client/okhttp:QuickOrder.php',
|
|
52
|
+
'@client/Aaden-Antelope/3.1.32', '@client/browser-android', '@client/electron-windows', '@client/ktor'}, set(rows)
|
|
53
|
+
assert all(len(a) <= 64 for a in rows)
|
|
54
|
+
assert usage.client_kind('Mozilla/5.0 (X11; Linux x86_64) aaden/1.0 Electron/22') == 'electron-linux'
|
|
55
|
+
assert usage.client_kind('Mozilla/5.0 (Windows NT 10.0) Chrome/120') == 'browser-windows'
|
|
56
|
+
assert usage.client_kind('curl/8') == 'system' and usage.client_kind('-') == 'system'
|
|
57
|
+
assert usage.client_kind('GuzzleHttp/7') == 'system' and usage.client_kind('Wget/1.20.3 (linux-gnu)') == 'system'
|
|
58
|
+
assert usage.client_kind('Aaden Buffet/2.16 (iPad; iOS 27.0.1; Scale/2.00)') == 'Aaden-Buffet/2.16'
|
|
59
|
+
assert usage.client_kind('ktor-client') == 'ktor'
|
|
60
|
+
assert usage.client_kind('SomethingNew/1') == 'other'
|
|
61
|
+
print('client_kind_test OK')
|