落地报价模板主账与后台:合同映射、灌种 SQL/脚本、智能体填表,并补测服运维探针与合同文档对齐。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
linmianxun
2026-09-14 14:09:45 +08:00
co-authored by Cursor
parent 8bdf4990a2
commit a70bce3c8f
138 changed files with 7239 additions and 1085 deletions
+1 -1
View File
@@ -62,7 +62,7 @@
- 新主账 / 智能体连接 `jeecg-boot`、`ytd_runtime`、Redis `/0` + `ytd:prod:`、桶 `ytd-inquiry`
- 与现网 **同时** 连接同一 AIBOT 长连接
- 删除或改写 `C:\yutongda\ops` 活动指针
- 把半成品 `YTD_ENV=prod` 却仍混用旧现网库密钥
- 把半成品 `YTD_ENV=prod` 却仍混用现网库密钥
## 回滚(P1 失败)
+4 -1
View File
@@ -17,8 +17,11 @@ if not exist "%~dp0Watch-Agent8910.ps1" (
goto :end
)
echo Starting Watch-Agent8910 ...
echo Starting LIVE follow Watch-Agent8910 ...
echo Keep this window open. New WeCom messages should appear here automatically.
echo Do NOT use Once.bat if you want realtime.
echo Local logs: repo\logs\YYYYMMDD_info.log and _error.log
echo Press Ctrl+C to stop.
echo.
powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File "%~dp0Watch-Agent8910.ps1" %*
set ERR=%ERRORLEVEL%
+162 -59
View File
@@ -1,15 +1,7 @@
# 只读:拉服务器新系统 8910 的「业务/错误」日志,并同时落到本机 logs/。
# 用法:
# powershell -NoProfile -ExecutionPolicy Bypass -File .\deploy\Watch-Agent8910.ps1
# powershell -NoProfile -ExecutionPolicy Bypass -File .\deploy\Watch-Agent8910.ps1 -Once
# 双击 Watch-Agent8910.bat / Watch-Agent8910-Once.bat
#
# 本机落盘(仓库根 logs/,按当天日期):
# logs/20260913_info.log — 业务信息(入队、reply=、发送成功等)
# logs/20260913_error.log — 告警/异常(WARNING、ERROR、Traceback、errcode 等)
#
# 乱码:默认 UTF-8;仍乱码可加 -Gbk。
# SSH:默认密钥 ~/.ssh/id_ed25519_ytd;可用 YTD_SSH_KEY / YTD_SSH_HOST 覆盖。
# 只读:拉服务器新系统 8910 的业务/错误日志,并落到本机 logs/(UTF-8 BOM,记事本可直接打开)。
# 用法:双击 Watch-Agent8910.bat / Watch-Agent8910-Once.bat
# 本机:logs/yyyyMMdd_info.log 、 logs/yyyyMMdd_error.log
# 服务器旧日志可能是 GBK、新日志是 UTF-8;远程按「逐行 UTF-8,失败则 GBK」解码,避免乱码。
[CmdletBinding()]
param(
@@ -26,8 +18,10 @@ $SshKey = if ($env:YTD_SSH_KEY) { $env:YTD_SSH_KEY } else { Join-Path $env:USERP
$SshHost = if ($env:YTD_SSH_HOST) { $env:YTD_SSH_HOST } else { 'ytd_extdev_0903@129.211.181.80' }
$LogOut = 'E:\wwwroot\logs\agent-http.out.log'
$LogErr = 'E:\wwwroot\logs\agent-http.err.log'
$IncomingRemotePath = 'C:/Users/ytd_extdev_0903/incoming/_watch_agent8910.ps1'
$IncomingLocalRun = 'C:\Users\ytd_extdev_0903\incoming\_watch_agent8910.ps1'
# once / follow 用不同远程脚本名,避免互相覆盖导致「持续监听」其实跑成 Once
$WatchTag = if ($Once) { 'once' } else { 'follow' }
$IncomingRemotePath = "C:/Users/ytd_extdev_0903/incoming/_watch_agent8910_$WatchTag.ps1"
$IncomingLocalRun = "C:\Users\ytd_extdev_0903\incoming\_watch_agent8910_$WatchTag.ps1"
$Pattern = '回调|inject-text|inject:|outbox|inbox|errcode|errmsg|60020|ERROR|WARNING|Traceback|Exception|ModuleNotFoundError|sender=|touser=|crypto|ChannelRuntime|dispatch intent|企微发送|验签|解密|reply='
@@ -41,30 +35,52 @@ try {
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)
$OutputEncoding = [System.Text.UTF8Encoding]::new($false)
} catch {
Write-Host 'warn: 无法设置本机控制台 UTF-8'
Write-Host 'warn: console UTF-8 setup failed'
}
# 仓库根/logs(本脚本在 deploy/ 下)
$RepoRoot = Split-Path -Parent $PSScriptRoot
$LocalLogDir = Join-Path $RepoRoot 'logs'
# 在仓库 deploy/ 下:写到仓库根 logs/;单独解压给同事:写到本工具目录 logs/
if ((Split-Path -Leaf $PSScriptRoot) -eq 'deploy') {
$LocalLogDir = Join-Path $RepoRoot 'logs'
} else {
$LocalLogDir = Join-Path $PSScriptRoot 'logs'
}
$Day = Get-Date -Format 'yyyyMMdd'
$LocalInfoLog = Join-Path $LocalLogDir ($Day + '_info.log')
$LocalErrorLog = Join-Path $LocalLogDir ($Day + '_error.log')
$Utf8NoBom = New-Object System.Text.UTF8Encoding $false
$Utf8Bom = New-Object System.Text.UTF8Encoding $true
function Ensure-Utf8BomFile([string]$path) {
if (-not (Test-Path $path) -or ((Get-Item $path).Length -eq 0)) {
[System.IO.File]::WriteAllText($path, '', $Utf8Bom)
return
}
$fs = [System.IO.File]::OpenRead($path)
try {
$b0 = $fs.ReadByte(); $b1 = $fs.ReadByte(); $b2 = $fs.ReadByte()
} finally { $fs.Close() }
if ($b0 -eq 0xEF -and $b1 -eq 0xBB -and $b2 -eq 0xBF) { return }
$raw = [System.IO.File]::ReadAllBytes($path)
$bom = [byte[]](0xEF, 0xBB, 0xBF)
$merged = New-Object byte[] ($bom.Length + $raw.Length)
[Array]::Copy($bom, 0, $merged, 0, 3)
[Array]::Copy($raw, 0, $merged, 3, $raw.Length)
[System.IO.File]::WriteAllBytes($path, $merged)
}
if (-not $NoLocalLog) {
New-Item -ItemType Directory -Force -Path $LocalLogDir | Out-Null
Ensure-Utf8BomFile $LocalInfoLog
Ensure-Utf8BomFile $LocalErrorLog
$sessionHdr = '===== watch start ' + (Get-Date).ToString('yyyy-MM-dd HH:mm:ss') + ' host=' + $SshHost + ' once=' + $Once.IsPresent + ' ====='
[System.IO.File]::AppendAllText($LocalInfoLog, $sessionHdr + [Environment]::NewLine, $Utf8NoBom)
[System.IO.File]::AppendAllText($LocalErrorLog, $sessionHdr + [Environment]::NewLine, $Utf8NoBom)
}
# 写入本地 *_error.log 的判定(其余进 *_info.log)
# 注意:不能用裸 ERROR(-match 不区分大小写,会误伤 “error.log” 说明行)
function Write-LocalLine([string]$line) {
Write-Host $line
if ($NoLocalLog) { return }
# 脚本自身说明行一律进 info,避免关键字误伤
if ($line -match '^(目标:|主日志|访问日志|日志编码|过滤:|本机落盘:|说明:|---+|=====)') {
$isErr = $false
} else {
@@ -75,31 +91,29 @@ function Write-LocalLine([string]$line) {
try {
[System.IO.File]::AppendAllText($target, $line + [Environment]::NewLine, $Utf8NoBom)
} catch {
Write-Host ('warn: 写本地日志失败: ' + $_.Exception.Message)
Write-Host ('warn: write local log failed: ' + $_.Exception.Message)
}
}
Write-LocalLine ("目标: $SshHost")
Write-LocalLine ("主日志(业务/错误): $LogErr")
if ($Access -or $All) { Write-LocalLine ("访问日志: $LogOut") }
Write-LocalLine ("日志编码: " + $(if ($Gbk) { 'Default/GBK' } else { 'UTF-8' }))
if ($All) {
Write-LocalLine '过滤: 关闭'
} else {
Write-LocalLine ("过滤: $Pattern")
}
Write-LocalLine ("日志编码: smart UTF-8/GBK" + $(if ($Gbk) { ' (force GBK)' } else { '' }))
if ($All) { Write-LocalLine '过滤: 关闭' } else { Write-LocalLine ("过滤: $Pattern") }
if (-not $NoLocalLog) {
Write-LocalLine ("本机落盘: $LocalInfoLog")
Write-LocalLine ("本机落盘: $LocalErrorLog")
}
Write-LocalLine '说明: 企微气泡里的 [test] 回复若已出现=发送成功;日志里看 reply= 字段'
Write-LocalLine '说明: 本地日志为 UTF-8 BOM;请重新跑 Once 覆盖旧乱码内容(可先删当天 log 再跑)'
Write-LocalLine '---'
$waitLiteral = if ($Once) { '$false' } else { '$true' }
# follow=1 / once=0,避免 $true/$false 在远程被覆盖或解析歧义
$waitFlag = if ($Once) { '0' } else { '1' }
$allLiteral = if ($All) { '$true' } else { '$false' }
$accessLiteral = if ($Access) { '$true' } else { '$false' }
$encName = if ($Gbk) { 'Default' } else { 'UTF8' }
$forceGbkLiteral = if ($Gbk) { '$true' } else { '$false' }
# 远程:逐行按 UTF-8 严格解码,失败再用 GBK(936);输出强制 Flush,避免持续监听被缓冲卡住
$remoteScript = @"
`$ErrorActionPreference = 'Continue'
try {
@@ -110,61 +124,151 @@ try {
`$out = '$LogOut'
`$err = '$LogErr'
`$tail = $Tail
`$wait = $waitLiteral
`$waitFlag = '$waitFlag'
`$wait = (`$waitFlag -eq '1')
`$all = $allLiteral
`$wantAccess = $accessLiteral
`$pat = '$Pattern'
`$enc = '$encName'
`$forceGbk = $forceGbkLiteral
`$utf8Strict = New-Object System.Text.UTF8Encoding `$false, `$true
`$gbk = [System.Text.Encoding]::GetEncoding(936)
Write-Output '=== remote health 8910 ==='
function Emit([string]`$s) {
[Console]::Out.WriteLine(`$s)
[Console]::Out.Flush()
}
function Decode-LogBytes([byte[]]`$chunk) {
if (`$null -eq `$chunk -or `$chunk.Length -eq 0) { return '' }
if (`$forceGbk) { return `$gbk.GetString(`$chunk) }
try { return `$utf8Strict.GetString(`$chunk) } catch { return `$gbk.GetString(`$chunk) }
}
function Get-SmartTailLines([string]`$path, [int]`$n) {
if (-not (Test-Path `$path)) { return @() }
`$fs = [System.IO.File]::Open(`$path, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite)
try {
`$ms = New-Object System.IO.MemoryStream
`$fs.CopyTo(`$ms)
`$bytes = `$ms.ToArray()
} finally {
`$fs.Close()
}
`$lines = New-Object System.Collections.Generic.List[string]
`$start = 0
for (`$i = 0; `$i -lt `$bytes.Length; `$i++) {
if (`$bytes[`$i] -ne 10) { continue }
`$end = `$i
if (`$end -gt `$start -and `$bytes[`$end - 1] -eq 13) { `$end = `$end - 1 }
`$len = `$end - `$start
if (`$len -lt 0) { `$len = 0 }
`$chunk = New-Object byte[] `$len
if (`$len -gt 0) { [Array]::Copy(`$bytes, `$start, `$chunk, 0, `$len) }
`$lines.Add((Decode-LogBytes `$chunk))
`$start = `$i + 1
}
if (`$start -lt `$bytes.Length) {
`$len = `$bytes.Length - `$start
`$chunk = New-Object byte[] `$len
[Array]::Copy(`$bytes, `$start, `$chunk, 0, `$len)
`$lines.Add((Decode-LogBytes `$chunk))
}
if (`$lines.Count -le `$n) { return `$lines.ToArray() }
return `$lines.GetRange(`$lines.Count - `$n, `$n).ToArray()
}
Emit ('mode=' + `$waitFlag + ' follow=' + `$wait)
Emit '=== remote health 8910 ==='
try {
`$h = Invoke-WebRequest -Uri 'http://127.0.0.1:8910/health' -UseBasicParsing -TimeoutSec 5
Write-Output `$h.Content
Emit `$h.Content
} catch {
Write-Output ('health_fail: ' + `$_.Exception.Message)
Emit ('health_fail: ' + `$_.Exception.Message)
}
Write-Output '=== business/error log (err) ==='
Emit '=== business/error log (err) ==='
function Show-Err([string]`$line) {
if (`$all) { Write-Output ('[err] ' + `$line); return }
if (`$line -match `$pat) { Write-Output ('[err] ' + `$line) }
if (`$all) { Emit ('[err] ' + `$line); return }
if (`$line -match `$pat) { Emit ('[err] ' + `$line) }
}
function Show-Out([string]`$line) {
if (-not `$wantAccess -and -not `$all) { return }
if (`$all) { Write-Output ('[out] ' + `$line); return }
if (`$line -match '/wecom/callback|inject-text') {
Write-Output ('[out] ' + `$line)
}
if (`$all) { Emit ('[out] ' + `$line); return }
if (`$line -match '/wecom/callback|inject-text') { Emit ('[out] ' + `$line) }
}
if (-not (Test-Path `$err)) { Write-Output ('MISSING ' + `$err) }
if (-not (Test-Path `$err)) { Emit ('MISSING ' + `$err) }
if (-not `$wait) {
if (Test-Path `$err) {
Get-Content -Path `$err -Tail `$tail -Encoding `$enc -ErrorAction SilentlyContinue | ForEach-Object { Show-Err `$_ }
Get-SmartTailLines `$err `$tail | ForEach-Object { Show-Err `$_ }
}
if (`$wantAccess -or `$all) {
Write-Output '=== access log (out) ==='
Emit '=== access log (out) ==='
if (Test-Path `$out) {
Get-Content -Path `$out -Tail `$tail -Encoding `$enc -ErrorAction SilentlyContinue | ForEach-Object { Show-Out `$_ }
Get-SmartTailLines `$out `$tail | ForEach-Object { Show-Out `$_ }
}
}
exit 0
}
Write-Output 'following err.log ... (Ctrl+C to stop)'
`$watch = @()
if (Test-Path `$err) { `$watch += `$err }
if ((`$wantAccess -or `$all) -and (Test-Path `$out)) { `$watch += `$out }
if (`$watch.Count -eq 0) { throw 'no log files' }
Get-Content -Path `$watch -Tail `$tail -Wait -Encoding `$enc -ErrorAction SilentlyContinue | ForEach-Object {
`$line = `$_
if (`$line -match '^\s*INFO:\s+\S+\s+-\s+"(GET|POST|PUT|DELETE)') {
Show-Out `$line
} else {
Show-Err `$line
Emit 'following err.log ... (Ctrl+C to stop)'
`$lastLen = @{}
`$carry = @{}
foreach (`$f in @(`$err, `$out)) {
if (Test-Path `$f) { `$lastLen[`$f] = (Get-Item `$f).Length } else { `$lastLen[`$f] = 0 }
`$carry[`$f] = ''
}
if (Test-Path `$err) { Get-SmartTailLines `$err `$tail | ForEach-Object { Show-Err `$_ } }
# 尾部打完后,从当前文件末尾继续跟,避免重复刷旧行
foreach (`$f in @(`$err, `$out)) {
if (Test-Path `$f) { `$lastLen[`$f] = (Get-Item `$f).Length }
}
`$heartbeatAt = [DateTime]::UtcNow
while (`$true) {
Start-Sleep -Milliseconds 500
if (([DateTime]::UtcNow - `$heartbeatAt).TotalSeconds -ge 30) {
Emit ('... still watching ' + (Get-Date).ToString('HH:mm:ss'))
`$heartbeatAt = [DateTime]::UtcNow
}
`$targets = @()
if (Test-Path `$err) { `$targets += `$err }
if ((`$wantAccess -or `$all) -and (Test-Path `$out)) { `$targets += `$out }
foreach (`$f in `$targets) {
if (-not `$lastLen.ContainsKey(`$f)) { `$lastLen[`$f] = 0; `$carry[`$f] = '' }
`$len = (Get-Item `$f).Length
if (`$len -lt `$lastLen[`$f]) { `$lastLen[`$f] = 0; `$carry[`$f] = '' }
if (`$len -le `$lastLen[`$f]) { continue }
`$fs = [System.IO.File]::Open(`$f, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite)
try {
[void]`$fs.Seek(`$lastLen[`$f], [System.IO.SeekOrigin]::Begin)
`$buf = New-Object byte[] (`$len - `$lastLen[`$f])
`$read = `$fs.Read(`$buf, 0, `$buf.Length)
} finally { `$fs.Close() }
`$lastLen[`$f] = `$len
if (`$read -le 0) { continue }
`$chunk = New-Object byte[] `$read
[Array]::Copy(`$buf, 0, `$chunk, 0, `$read)
`$text = `$carry[`$f] + (Decode-LogBytes `$chunk)
`$parts = `$text -split "`n"
if (-not (`$text.EndsWith("`n") -or `$text.EndsWith("`r"))) {
if (`$parts.Count -ge 1) {
`$carry[`$f] = `$parts[`$parts.Count - 1].TrimEnd("`r")
`$limit = `$parts.Count - 1
} else {
`$carry[`$f] = `$text
`$limit = 0
}
} else {
`$carry[`$f] = ''
`$limit = `$parts.Count
}
for (`$pi = 0; `$pi -lt `$limit; `$pi++) {
`$line = `$parts[`$pi].TrimEnd("`r")
if (-not `$line) { continue }
if (`$f -eq `$out) { Show-Out `$line } else { Show-Err `$line }
}
}
}
"@
@@ -175,7 +279,6 @@ $localTemp = Join-Path $env:TEMP 'ytd-watch-agent8910-remote.ps1'
& scp.exe -o BatchMode=yes -o ConnectTimeout=12 -i $SshKey $localTemp "${SshHost}:${IncomingRemotePath}"
if ($LASTEXITCODE -ne 0) { throw "scp failed: $LASTEXITCODE" }
# 用 Process + UTF-8 读 SSH 输出,避免管道把中文解错再写入本地日志
$sshArgs = @(
'-o', 'BatchMode=yes',
'-o', 'ConnectTimeout=12',
+34 -32
View File
@@ -1,38 +1,40 @@
同事如何使用「Watch-Agent8910」(只读盯服务器 8910 日志)
友通达询价智能体 - 8910 日志监听小工具(给同事)
一、需要具备
1. Windows + 已安装 OpenSSH 客户端(Win10/11 一般自带 ssh/scp)
2. 仓库里这两个文件(或单独拷贝 deploy 目录):
- Watch-Agent8910.ps1
- Watch-Agent8910.bat(可选 Once 版:Watch-Agent8910-Once.bat)
3. 能登录测试机的 SSH 私钥,且账号已加入服务器授权
【包内文件】
- Watch-Agent8910.bat 持续实时监听(推荐双击这个)
- Watch-Agent8910-Once.bat 只看最近一段后结束
- Watch-Agent8910.ps1 主脚本(勿单独乱改)
- 使用说明.txt 本文件
二、本机配置密钥(二选一)
A. 默认路径(与现网脚本一致)
把私钥放到:%USERPROFILE%\.ssh\id_ed25519_ytd
(无扩展名;权限保持仅自己可读)
【本包不含】
- SSH 私钥(勿通过微信群传播密钥;向负责人单独索取或登记公钥)
B. 自定义路径
运行前设置环境变量,例如:
set YTD_SSH_KEY=D:\keys\my_ytd_ed25519
set YTD_SSH_HOST=ytd_extdev_0903@129.211.181.80
【使用前准备】
1. Windows 10/11,自带 OpenSSH(命令行能运行 ssh / scp)
2. 解压到任意目录,例如 D:\tools\YtdWatchAgent8910\
3. 配置密钥(二选一):
A. 把私钥放到:%USERPROFILE%\.ssh\id_ed25519_ytd
B. 或先设置再运行:
set YTD_SSH_KEY=你的私钥完整路径
set YTD_SSH_HOST=ytd_extdev_0903@129.211.181.80
说明:SSH 账号一般仍是 ytd_extdev_0903(服务器共用账号),不是改成同事自己的名字。
三、怎么跑
- 双击 Watch-Agent8910.bat → 持续监听(Ctrl+C 结束)
- 双击 Watch-Agent8910-Once.bat → 只看最近一段
- 或命令行:
Watch-Agent8910.bat
Watch-Agent8910.bat -Once
Watch-Agent8910.bat -Access
【怎么用】
1. 双击 Watch-Agent8910.bat
2. 看到 mode=1 follow=True 和 following err.log ... 表示实时监听已接通
3. 保持窗口开着;企微给「询价小助手」发消息,这里应自动刷出新日志
4. 结束:Ctrl+C,再按任意键关窗口
本机同时落盘(仓库根目录 logs/,按当天日期追加写入):
logs/20260913_info.log
logs/20260913_error.log
不要提交 logs/ 到 Git。
只要快速看一眼:双击 Watch-Agent8910-Once.bat
四、注意
- 这是只读拉日志,不会改 Nginx / 不会切流
- 私钥不要发到微信群或提交 Git;向负责人单独要密钥或加自己的公钥到服务器
- 若提示禁止运行脚本:用 bat 即可(已带 Bypass),不要直接右键用受限策略跑 ps1
- 乱码时用:Watch-Agent8910.bat -Gbk
- 只要屏幕不要文件:Watch-Agent8910.bat -NoLocalLog
【本机日志】
运行后在本工具目录下生成:
logs\日期_info.log
logs\日期_error.log
例如:logs\20260913_info.log
【注意】
- 只读拉服务器日志,不会改 Nginx、不会切流
- 行首 [err] 表示来自服务器 err 日志文件,不等于业务失败;看后面的 INFO/WARNING
- 乱码可试:Watch-Agent8910.bat -Gbk
- 不要把私钥打进这个压缩包
+98
View File
@@ -0,0 +1,98 @@
# 本机:打包 agent/admin,上传 jar,远端重启主账与智能体,并 seed 模板
$ErrorActionPreference = 'Stop'
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
if (-not $here) { $here = (Get-Location).Path }
$Repo = (Resolve-Path (Join-Path $here '..')).Path
$key = Join-Path $env:USERPROFILE '.ssh\id_ed25519_ytd'
$remote = 'ytd_extdev_0903@129.211.181.80'
$incoming = "${remote}:C:/Users/ytd_extdev_0903/incoming"
Write-Host ("here=" + $here)
Write-Host ("Repo=" + $Repo)
if (-not (Test-Path (Join-Path $Repo 'inquiry-agent'))) { throw "bad repo path: $Repo" }
Write-Host '=== package agent ==='
$stage = Join-Path $env:TEMP 'ytd-agent-release-stage'
$zipAgent = Join-Path $Repo 'agent-release.zip'
if (Test-Path $stage) { Remove-Item $stage -Recurse -Force }
New-Item -ItemType Directory -Force -Path (Join-Path $stage 'agent') | Out-Null
robocopy (Join-Path $Repo 'inquiry-agent') (Join-Path $stage 'agent') /E /XD .venv __pycache__ .git /XF *.pyc /NFL /NDL /NJH /NJS /nc /ns /np | Out-Null
if (Test-Path $zipAgent) { Remove-Item $zipAgent -Force }
Compress-Archive -Path (Join-Path $stage 'agent') -DestinationPath $zipAgent -Force
Write-Host ("agent_zip=" + (Get-Item $zipAgent).Length)
$jar = Join-Path $Repo 'inquiry-api\jeecg-module-system\jeecg-system-start\target\jeecg-system-start-3.5.3.jar'
if (-not (Test-Path $jar)) { throw 'jar missing' }
Write-Host ("jar_bytes=" + (Get-Item $jar).Length)
Write-Host '=== build admin ==='
$Src = Join-Path $Repo 'inquiry-backend'
$Build = 'C:\ytd\inquiry-backend'
robocopy $Src $Build /MIR /XD node_modules dist .git .vite .husky /XF *.log /NFL /NDL /NJH /NJS /nc /ns /np | Out-Null
if (Test-Path (Join-Path $Src '.env.production')) {
Copy-Item (Join-Path $Src '.env.production') (Join-Path $Build '.env.production') -Force
}
Copy-Item (Join-Path $Src 'vite.config.ts') (Join-Path $Build 'vite.config.ts') -Force
Push-Location $Build
if (Test-Path dist) { Remove-Item dist -Recurse -Force }
pnpm run build
$code = $LASTEXITCODE
Pop-Location
if ($code -ne 0) { throw "admin build failed $code" }
$zipAdmin = Join-Path $env:TEMP 'admin-dist.zip'
if (Test-Path $zipAdmin) { Remove-Item $zipAdmin -Force }
Compress-Archive -Path "$Build\dist\*" -DestinationPath $zipAdmin -Force
Write-Host ("admin_zip=" + (Get-Item $zipAdmin).Length)
$cfgZip = Join-Path $env:TEMP 'ledger-config.zip'
if (Test-Path $cfgZip) { Remove-Item $cfgZip -Force }
$cfgStage = Join-Path $env:TEMP 'ledger-cfg-stage-qt'
if (Test-Path $cfgStage) { Remove-Item $cfgStage -Recurse -Force }
New-Item -ItemType Directory $cfgStage | Out-Null
Copy-Item (Join-Path $Repo 'deploy\config\application-inquiry.yml') (Join-Path $cfgStage 'application-inquiry.yml') -Force
Compress-Archive -Path "$cfgStage\*" -DestinationPath $cfgZip -Force
# templates zip for seed
$tplZip = Join-Path $env:TEMP 'quote-templates-seed.zip'
if (Test-Path $tplZip) { Remove-Item $tplZip -Force }
$tplStage = Join-Path $env:TEMP 'quote-templates-seed-stage'
if (Test-Path $tplStage) { Remove-Item $tplStage -Recurse -Force }
New-Item -ItemType Directory $tplStage | Out-Null
$tplDir = Get-ChildItem -Path $Repo -Directory | Where-Object { $_.Name -like '*报价*' -and $_.Name -like '*模板*' } | Select-Object -First 1
if (-not $tplDir) { throw 'template folder not found under repo' }
robocopy $tplDir.FullName (Join-Path $tplStage 'templates') /E /XF ~$* /NFL /NDL /NJH /NJS /nc /ns /np | Out-Null
Write-Host ("templates_from=" + $tplDir.FullName)
Copy-Item (Join-Path $Repo 'deploy\seed\quote_templates_seed_manifest_v1.json') (Join-Path $tplStage 'manifest.json') -Force
Compress-Archive -Path "$tplStage\*" -DestinationPath $tplZip -Force
Write-Host '=== upload ==='
scp -o BatchMode=yes -i $key `
$zipAgent `
$zipAdmin `
$jar `
$cfgZip `
$tplZip `
(Join-Path $Repo 'deploy\_remote_wmi_start_ledger.ps1') `
(Join-Path $Repo 'deploy\_remote_safe_deploy_agent.ps1') `
(Join-Path $Repo 'deploy\_remote_cutover_admin_8180.ps1') `
(Join-Path $Repo 'deploy\_remote_seed_quote_templates.ps1') `
$incoming
if ($LASTEXITCODE -ne 0) { throw 'scp failed' }
Write-Host 'UPLOAD_OK'
Write-Host '=== deploy ledger ==='
ssh -o BatchMode=yes -i $key $remote "powershell -NoProfile -ExecutionPolicy Bypass -File C:\Users\ytd_extdev_0903\incoming\_remote_wmi_start_ledger.ps1"
if ($LASTEXITCODE -ne 0) { throw 'ledger deploy failed' }
Write-Host '=== deploy agent ==='
ssh -o BatchMode=yes -i $key $remote "powershell -NoProfile -ExecutionPolicy Bypass -File C:\Users\ytd_extdev_0903\incoming\_remote_safe_deploy_agent.ps1"
if ($LASTEXITCODE -ne 0) { throw 'agent deploy failed' }
Write-Host '=== deploy admin ==='
ssh -o BatchMode=yes -i $key $remote "powershell -NoProfile -ExecutionPolicy Bypass -File C:\Users\ytd_extdev_0903\incoming\_remote_cutover_admin_8180.ps1"
if ($LASTEXITCODE -ne 0) { throw 'admin deploy failed' }
Write-Host '=== wait ledger ==='
Start-Sleep -Seconds 25
ssh -o BatchMode=yes -i $key $remote "powershell -NoProfile -ExecutionPolicy Bypass -File C:\Users\ytd_extdev_0903\incoming\_remote_seed_quote_templates.ps1"
if ($LASTEXITCODE -ne 0) { throw 'seed failed' }
Write-Host 'QUOTE_TEMPLATE_DEPLOY_DONE'
+34
View File
@@ -0,0 +1,34 @@
# 远端:给 inquiry_robot.inq_ticket 补 quote_date。禁止动 jeecg-boot。
# 口令只从 E:\wwwroot\.env 读,不写进仓库、不回显。
$ErrorActionPreference = 'Stop'
Get-Content 'E:\wwwroot\.env' | ForEach-Object {
$line = $_.Trim()
if (-not $line -or $line.StartsWith('#')) { return }
$i = $line.IndexOf('=')
if ($i -lt 1) { return }
$k = $line.Substring(0, $i).Trim()
$v = $line.Substring($i + 1).Trim().Trim('"')
Set-Item -Path "Env:$k" -Value $v
}
if (-not $env:MYSQL_DB) { throw 'MYSQL_DB missing' }
if ($env:MYSQL_DB -ne 'inquiry_robot') { throw ("refuse db=" + $env:MYSQL_DB) }
$mysql = 'C:\yutongda\tools\mysql-8.0.39-winx64\bin\mysql.exe'
if (-not (Test-Path $mysql)) { throw "missing $mysql" }
$hostName = if ($env:MYSQL_HOST) { $env:MYSQL_HOST } else { '10.206.0.14' }
$tcp = New-Object System.Net.Sockets.TcpClient
try {
$null = $tcp.ConnectAsync('127.0.0.1', 3306).Wait(1000)
if ($tcp.Connected) { $hostName = '127.0.0.1' }
} catch {
} finally {
$tcp.Close()
}
Write-Host "alter host=$hostName db=$env:MYSQL_DB"
$sqlFile = 'C:\Users\ytd_extdev_0903\incoming\inquiry_ticket_quote_date_v1.sql'
if (-not (Test-Path $sqlFile)) { throw "missing $sqlFile" }
& $mysql -h $hostName -P 3306 -u $env:MYSQL_USER "--password=$env:MYSQL_PASSWORD" --default-character-set=utf8mb4 $env:MYSQL_DB -e "source $($sqlFile.Replace('\','/'))"
if ($LASTEXITCODE -ne 0) { throw "fail sql code=$LASTEXITCODE" }
Write-Host 'SQL_OK'
& $mysql -h $hostName -P 3306 -u $env:MYSQL_USER "--password=$env:MYSQL_PASSWORD" --default-character-set=utf8mb4 $env:MYSQL_DB -e "SHOW COLUMNS FROM inq_ticket LIKE 'quote_date'\G"
if ($LASTEXITCODE -ne 0) { throw "fail verify code=$LASTEXITCODE" }
Write-Host 'VERIFY_DONE'
+18
View File
@@ -0,0 +1,18 @@
$ErrorActionPreference='Stop'
$Fat='E:\wwwroot\inquiry-new\jeecg-system-start-3.5.3.jar'
$work=Join-Path $env:TEMP 'chk-captcha-class'
if(Test-Path $work){Remove-Item $work -Recurse -Force}
New-Item -ItemType Directory $work|Out-Null
$jarCmd=$null
foreach($c in @('C:\yutongda\tools\jdk8\bin\jar.exe','E:\wwwroot\tools\jdk-17\bin\jar.exe')){ if(Test-Path $c){ $jarCmd=$c; break } }
Push-Location $work
& $jarCmd xf $Fat 'BOOT-INF/lib/jeecg-system-biz-3.5.3.jar'
& $jarCmd xf 'BOOT-INF/lib/jeecg-system-biz-3.5.3.jar' 'org/jeecg/modules/system/controller/LoginController.class'
$classPath = Join-Path $work 'org\jeecg\modules\system\controller\LoginController.class'
if (-not (Test-Path $classPath)) { throw ("class missing under " + $work) }
$bytes=[IO.File]::ReadAllBytes($classPath)
$ascii=[Text.Encoding]::ASCII.GetString($bytes)
$has= $ascii.Contains('loginCaptchaEnabled')
Write-Host ("has_loginCaptchaEnabled=" + $has)
Write-Host ("class_bytes=" + $bytes.Length)
Pop-Location
+2
View File
@@ -0,0 +1,2 @@
$p='C:\Users\ytd_extdev_0903\incoming\jeecg-system-start-3.5.3.CAPTCHA.jar'
if(Test-Path $p){ $i=Get-Item $p; "CAPTCHA length=$($i.Length) time=$($i.LastWriteTime)" } else { 'CAPTCHA missing' }
+11
View File
@@ -0,0 +1,11 @@
$ErrorActionPreference="Stop"
$work=Join-Path $env:TEMP "chk-inquiry-class"
if(Test-Path $work){Remove-Item $work -Recurse -Force}
New-Item -ItemType Directory $work|Out-Null
$jarCmd="C:\yutongda\tools\jdk8\bin\jar.exe"
$Fat="E:\wwwroot\inquiry-new\jeecg-system-start-3.5.3.jar"
Push-Location $work
& $jarCmd xf $Fat "BOOT-INF/lib/jeecg-module-inquiry-3.5.3.jar"
& $jarCmd tf "BOOT-INF/lib/jeecg-module-inquiry-3.5.3.jar" | findstr /i "QuoteTemplate Controller"
Write-Host "CLASS_CHECK_DONE"
Pop-Location
+3
View File
@@ -0,0 +1,3 @@
$p='C:\Users\ytd_extdev_0903\incoming\jeecg-system-start-3.5.3.NEW.jar'
if(Test-Path $p){ $i=Get-Item $p; "exists length=$($i.Length) time=$($i.LastWriteTime)" } else { 'missing NEW jar' }
Get-Process java -EA SilentlyContinue | Select-Object Id | Measure-Object | ForEach-Object { "java_count=$($_.Count)" }
+50
View File
@@ -0,0 +1,50 @@
$ErrorActionPreference = "Continue"
Write-Host "=== run-ledger-inner redis bits ==="
Get-Content "E:\wwwroot\ops\run-ledger-inner.ps1" | Select-Object -First 55 | ForEach-Object { Write-Host $_ }
$env:YTD_REDIS_PASSWORD = $null
Get-Content "E:\wwwroot\.env" | ForEach-Object {
$t = $_.Trim()
if (-not $t -or $t.StartsWith("#")) { return }
$i = $t.IndexOf("=")
if ($i -lt 1) { return }
Set-Item -Path ("Env:" + $t.Substring(0,$i).Trim()) -Value $t.Substring($i+1).Trim().Trim("`"")
}
$ulen = 0; if ($env:YTD_REDIS_USERNAME) { $ulen = $env:YTD_REDIS_USERNAME.Length }
$plen = 0; if ($env:YTD_REDIS_PASSWORD) { $plen = $env:YTD_REDIS_PASSWORD.Length }
$ulen2 = 0; $rlen = 0; if ($env:REDIS_URL) { $rlen = $env:REDIS_URL.Length }
Write-Host ("before_parse user_len=$ulen pw_len=$plen url_len=$rlen")
if (-not $env:YTD_REDIS_PASSWORD -and $env:REDIS_URL) {
if ($env:REDIS_URL -match "redis://:([^@]+)@") {
$env:YTD_REDIS_PASSWORD = $Matches[1]
Write-Host "matched_form_password_only"
} elseif ($env:REDIS_URL -match "redis://([^:/@]+):([^@]+)@") {
if (-not $env:YTD_REDIS_USERNAME) { $env:YTD_REDIS_USERNAME = $Matches[1] }
$env:YTD_REDIS_PASSWORD = $Matches[2]
Write-Host "matched_form_user_pass"
} elseif ($env:REDIS_URL -match "redis://[^:]+:([^@]+)@") {
$env:YTD_REDIS_PASSWORD = $Matches[1]
Write-Host "matched_form_legacy"
} else {
Write-Host "NO_REGEX_MATCH"
}
}
$ulen = 0; if ($env:YTD_REDIS_USERNAME) { $ulen = $env:YTD_REDIS_USERNAME.Length }
$plen = 0; if ($env:YTD_REDIS_PASSWORD) { $plen = $env:YTD_REDIS_PASSWORD.Length }
Write-Host ("after_parse user_len=$ulen pw_len=$plen")
if ($env:REDIS_URL) {
$u = $env:REDIS_URL
$at = ([regex]::Matches($u,"@")).Count
$auth = $u.Substring(8).Split("@")[0]
$parts = $auth.Split(":").Count
Write-Host ("url_probe at_count=$at auth_colon_parts=$parts")
}
$login = Invoke-RestMethod -Uri "http://127.0.0.1:8180/jeecgboot/sys/login" -Method POST -Body "{\"username\":\"admin\",\"password\":\"123456\",\"captcha\":\"\",\"checkKey\":\"\"}" -ContentType "application/json;charset=UTF-8"
$h = @{ "X-Access-Token" = $login.result.token }
$r = Invoke-WebRequest -Uri "http://127.0.0.1:8180/jeecgboot/inquiry/quoteTemplate/list?pageNo=1&pageSize=5" -Headers $h -UseBasicParsing
Write-Host "=== list ==="
$n = [Math]::Min(600, $r.Content.Length)
Write-Host $r.Content.Substring(0, $n)
Write-Host "PARSE_CHECK_DONE"
+98
View File
@@ -0,0 +1,98 @@
# 只读排查:同事公钥是否在 authorized_keys、本机防火墙/SSH 相关线索
# 不改任何配置,不输出私钥。
$ErrorActionPreference = 'Continue'
Write-Output '=== host ==='
whoami
hostname
$ak = Join-Path $env:USERPROFILE '.ssh\authorized_keys'
Write-Output "=== authorized_keys: $ak ==="
if (-not (Test-Path $ak)) {
Write-Output 'NO_AUTHORIZED_KEYS_FILE'
exit 0
}
$lines = Get-Content $ak | Where-Object { $_ -and $_.Trim() -notmatch '^#' }
Write-Output ("key_lines=" + $lines.Count)
$needle = 'IFBmb3YG1ZOJ3kaj8z3H55GXmv+hPIbYgN298VKorgFv'
$commentNeedle = 'ytd-external-developer'
$hit = $false
foreach ($line in $lines) {
if ($line -like "*$needle*") {
Write-Output 'PUBKEY_FOUND=yes'
Write-Output ("PUBKEY_LINE_PREFIX=" + $line.Substring(0, [Math]::Min(60, $line.Length)))
$hit = $true
}
if ($line -like "*$commentNeedle*") {
Write-Output 'COMMENT_FOUND=yes'
}
}
if (-not $hit) {
Write-Output 'PUBKEY_FOUND=no'
Write-Output 'PUBKEY_NOT_IN_AUTHORIZED_KEYS'
}
Write-Output '=== key comments (safe) ==='
foreach ($line in $lines) {
if ($line -match '^ssh-\S+\s+\S+\s+(.+)$') {
Write-Output ("comment=" + $Matches[1])
} elseif ($line -match '^ssh-\S+\s+\S+$') {
Write-Output 'comment=(none)'
} else {
Write-Output 'line=(unparsed-or-options)'
}
}
Write-Output '=== OpenSSH firewall rules (OpenSSH/22) ==='
try {
Get-NetFirewallRule -ErrorAction SilentlyContinue |
Where-Object { $_.DisplayName -match 'OpenSSH|SSH' -or $_.Name -match 'OpenSSH|SSH' } |
Select-Object -First 20 DisplayName, Enabled, Direction, Action, Profile |
Format-Table -AutoSize | Out-String | Write-Output
} catch {
Write-Output ("firewall_query_err=" + $_.Exception.Message)
}
Write-Output '=== firewall rules mentioning 38.175.103.103 ==='
try {
$addrRules = Get-NetFirewallAddressFilter -ErrorAction SilentlyContinue |
Where-Object { $_.RemoteAddress -match '38\.175\.103\.103' -or ($_.RemoteAddress -is [array] -and ($_.RemoteAddress -contains '38.175.103.103')) }
if ($addrRules) {
foreach ($af in $addrRules) {
$rule = Get-NetFirewallRule -AssociatedNetFirewallAddressFilter $af -ErrorAction SilentlyContinue
if ($rule) {
Write-Output ("rule=" + $rule.DisplayName + "; enabled=" + $rule.Enabled + "; action=" + $rule.Action + "; dir=" + $rule.Direction + "; remote=" + ($af.RemoteAddress -join ','))
}
}
} else {
Write-Output 'NO_FIREWALL_RULE_WITH_THAT_IP'
}
} catch {
Write-Output ("addr_filter_err=" + $_.Exception.Message)
}
Write-Output '=== inbound allow rules on local port 22 (if any) ==='
try {
Get-NetFirewallPortFilter -Protocol TCP -ErrorAction SilentlyContinue |
Where-Object { $_.LocalPort -eq 22 -or $_.LocalPort -eq '22' } |
ForEach-Object {
$rule = Get-NetFirewallRule -AssociatedNetFirewallPortFilter $_ -ErrorAction SilentlyContinue
if ($rule) {
$af = Get-NetFirewallAddressFilter -AssociatedNetFirewallRule $rule -ErrorAction SilentlyContinue
Write-Output ("rule=" + $rule.DisplayName + "; enabled=" + $rule.Enabled + "; action=" + $rule.Action + "; remote=" + (($af.RemoteAddress) -join ','))
}
}
} catch {
Write-Output ("port22_err=" + $_.Exception.Message)
}
Write-Output '=== sshd service ==='
try {
Get-Service sshd -ErrorAction SilentlyContinue | Format-List Name, Status, StartType | Out-String | Write-Output
} catch {
Write-Output ("sshd_err=" + $_.Exception.Message)
}
Write-Output 'DONE'
+177
View File
@@ -0,0 +1,177 @@
# 清空 inquiry_robot:询价工单及相关、员工岗位;账号仅保留 admin。
# 只动业务/账号数据,不动角色菜单、报价模板、机器人回复语。
# 禁止对现网 jeecg-boot 执行。
$ErrorActionPreference = "Stop"
$mysql = "C:\yutongda\tools\mysql-8.0.39-winx64\bin\mysql.exe"
if (-not (Test-Path $mysql)) {
$alt = Get-ChildItem "C:\yutongda\tools" -Recurse -Filter "mysql.exe" -ErrorAction SilentlyContinue | Select-Object -First 1
if ($alt) { $mysql = $alt.FullName }
}
if (-not (Test-Path $mysql)) { throw "mysql.exe not found" }
$envFile = "E:\wwwroot\.env"
if (-not (Test-Path $envFile)) { throw "missing $envFile" }
$hostn = "10.206.0.14"
$user = "inquiry_robot"
$pass = $null
$db = "inquiry_robot"
$port = "3306"
Get-Content $envFile | ForEach-Object {
if ($_ -match '^MYSQL_HOST=(.+)$') { $hostn = $Matches[1].Trim() }
if ($_ -match '^MYSQL_PORT=(.+)$') { $port = $Matches[1].Trim() }
if ($_ -match '^MYSQL_USER=(.+)$') { $user = $Matches[1].Trim() }
if ($_ -match '^MYSQL_PASSWORD=(.+)$') { $pass = $Matches[1].Trim() }
if ($_ -match '^MYSQL_DB=(.+)$') { $db = $Matches[1].Trim() }
}
if (-not $pass) { throw "MYSQL_PASSWORD missing in .env" }
if ($db -ne "inquiry_robot") { throw "refuse: MYSQL_DB=$db (must be inquiry_robot)" }
Write-Output ("target=" + $hostn + ":" + $port + "/" + $db + " user=" + $user)
# 先看清除前计数(表不存在则跳过)
$beforeSql = @"
SELECT 'BEFORE' AS phase;
SELECT 'inq_ticket' AS t, COUNT(*) AS c FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='inq_ticket'
AND (SELECT COUNT(*) FROM inq_ticket) IS NOT NULL;
"@
function Invoke-Mysql([string]$sql) {
$out = & $mysql -h $hostn -P $port -u $user "-p$pass" --default-character-set=utf8mb4 --batch --raw $db -e $sql 2>&1
$out | ForEach-Object {
$s = "$_"
if ($s -match "Warning|Using a password") { return }
$s
}
if ($LASTEXITCODE -ne 0) {
throw ("mysql failed exit=" + $LASTEXITCODE + " sql_head=" + $sql.Substring(0, [Math]::Min(80, $sql.Length)))
}
}
# 动态:仅对已存在的表做 TRUNCATE / DELETE
$clearSql = @"
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS=0;
-- ---------- 工单及附属 ----------
DELETE FROM inq_exception_case WHERE EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='inq_exception_case');
DELETE FROM inq_attachment WHERE EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='inq_attachment');
DELETE FROM inq_quote WHERE EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='inq_quote');
DELETE FROM inq_ticket_status_log WHERE EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='inq_ticket_status_log');
DELETE FROM inq_ticket_segment WHERE EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='inq_ticket_segment');
DELETE FROM inq_ticket WHERE EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='inq_ticket');
DELETE FROM inq_dashboard_snapshot WHERE EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='inq_dashboard_snapshot');
-- ---------- 员工岗位 ----------
DELETE FROM inq_staff_role WHERE EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='inq_staff_role');
-- ---------- 账号:仅保留 admin ----------
-- 先删非 admin 的关联,再删非 admin 用户
DELETE ur FROM sys_user_role ur
INNER JOIN sys_user u ON u.id = ur.user_id
WHERE LOWER(u.username) <> 'admin';
DELETE ut FROM sys_user_tenant ut
INNER JOIN sys_user u ON u.id = ut.user_id
WHERE LOWER(u.username) <> 'admin';
DELETE ud FROM sys_user_depart ud
INNER JOIN sys_user u ON u.id = ud.user_id
WHERE LOWER(u.username) <> 'admin';
DELETE FROM sys_user WHERE LOWER(username) <> 'admin';
SET FOREIGN_KEY_CHECKS=1;
SELECT 'AFTER_COUNTS' AS phase;
SELECT 'inq_ticket' AS t, (SELECT COUNT(*) FROM inq_ticket) AS c
UNION ALL SELECT 'inq_ticket_status_log', (SELECT COUNT(*) FROM inq_ticket_status_log)
UNION ALL SELECT 'inq_ticket_segment', (SELECT COUNT(*) FROM inq_ticket_segment)
UNION ALL SELECT 'inq_quote', (SELECT COUNT(*) FROM inq_quote)
UNION ALL SELECT 'inq_attachment', (SELECT COUNT(*) FROM inq_attachment)
UNION ALL SELECT 'inq_exception_case', (SELECT COUNT(*) FROM inq_exception_case)
UNION ALL SELECT 'inq_staff_role', (SELECT COUNT(*) FROM inq_staff_role)
UNION ALL SELECT 'sys_user', (SELECT COUNT(*) FROM sys_user)
UNION ALL SELECT 'sys_user_role', (SELECT COUNT(*) FROM sys_user_role);
SELECT id, username, realname, status FROM sys_user;
"@
# 部分环境可能没有某附属表:改用逐表安全清理
$probe = Invoke-Mysql "SHOW TABLES;"
Write-Output "=== tables ==="
$probe | ForEach-Object { $_ }
$tables = @($probe | Where-Object { $_ -and $_ -ne "Tables_in_inquiry_robot" })
function Table-Exists([string]$name) {
return $tables -contains $name
}
$stmts = New-Object System.Collections.Generic.List[string]
$stmts.Add("SET NAMES utf8mb4;")
$stmts.Add("SET FOREIGN_KEY_CHECKS=0;")
# 工单相关
foreach ($t in @(
"inq_exception_case",
"inq_attachment",
"inq_quote",
"inq_ticket_status_log",
"inq_ticket_segment",
"inq_ticket",
"inq_dashboard_snapshot",
"inq_staff_role"
)) {
if (Table-Exists $t) {
$stmts.Add("DELETE FROM ``$t``;")
Write-Output ("will_clear=" + $t)
} else {
Write-Output ("skip_missing=" + $t)
}
}
if (Table-Exists "sys_user") {
if (Table-Exists "sys_user_role") {
$stmts.Add("DELETE ur FROM sys_user_role ur INNER JOIN sys_user u ON u.id = ur.user_id WHERE LOWER(u.username) <> 'admin';")
}
if (Table-Exists "sys_user_tenant") {
$stmts.Add("DELETE ut FROM sys_user_tenant ut INNER JOIN sys_user u ON u.id = ut.user_id WHERE LOWER(u.username) <> 'admin';")
}
if (Table-Exists "sys_user_depart") {
$stmts.Add("DELETE ud FROM sys_user_depart ud INNER JOIN sys_user u ON u.id = ud.user_id WHERE LOWER(u.username) <> 'admin';")
}
$stmts.Add("DELETE FROM sys_user WHERE LOWER(username) <> 'admin';")
Write-Output "will_clear_sys_user_keep_admin=1"
} else {
Write-Output "skip_missing=sys_user"
}
$stmts.Add("SET FOREIGN_KEY_CHECKS=1;")
# 执行前计数
Write-Output "=== BEFORE ==="
foreach ($t in @("inq_ticket","inq_staff_role","sys_user")) {
if (Table-Exists $t) {
Invoke-Mysql "SELECT '$t' AS t, COUNT(*) AS c FROM ``$t``;" | ForEach-Object { $_ }
}
}
if (Table-Exists "sys_user") {
Invoke-Mysql "SELECT username, realname FROM sys_user;" | ForEach-Object { $_ }
}
$execSql = ($stmts -join "`n")
Write-Output "=== EXEC ==="
Invoke-Mysql $execSql | ForEach-Object { $_ }
Write-Output "=== AFTER ==="
foreach ($t in @("inq_ticket","inq_ticket_status_log","inq_ticket_segment","inq_quote","inq_attachment","inq_exception_case","inq_staff_role","sys_user","sys_user_role")) {
if (Table-Exists $t) {
Invoke-Mysql "SELECT '$t' AS t, COUNT(*) AS c FROM ``$t``;" | ForEach-Object { $_ }
}
}
if (Table-Exists "sys_user") {
Invoke-Mysql "SELECT id, username, realname, status FROM sys_user;" | ForEach-Object { $_ }
}
Write-Output "CLEAR_DEMO_DATA_DONE"
@@ -0,0 +1,58 @@
$ErrorActionPreference = "Continue"
function Load-EnvFile($path) {
$map = @{}
if (-not (Test-Path $path)) { return $map }
Get-Content $path | ForEach-Object {
$line = $_.Trim()
if (-not $line -or $line.StartsWith("#")) { return }
$i = $line.IndexOf("=")
if ($i -lt 1) { return }
$k = $line.Substring(0,$i).Trim()
$v = $line.Substring($i+1).Trim().Trim('"')
$map[$k] = $v
}
return $map
}
function Show-RedisKeys($label, $map) {
$keys = @("YTD_REDIS_HOST","YTD_REDIS_PORT","YTD_REDIS_DB","YTD_REDIS_USERNAME","YTD_REDIS_PASSWORD","REDIS_URL","REDIS_PASSWORD","REDIS_USERNAME")
Write-Host ("=== " + $label + " ===")
foreach ($k in $keys) {
if ($map.ContainsKey($k)) {
$v = $map[$k]
$safe = $v
if ($k -match "PASSWORD|URL") {
if ($k -eq "REDIS_URL" -and $v -match "redis://([^:/@]+):([^@]+)@([^:/]+)") {
Write-Host (" REDIS_URL user=" + $Matches[1] + " pw_len=" + $Matches[2].Length + " host=" + $Matches[3])
continue
}
Write-Host (" " + $k + " len=" + $v.Length)
} else {
Write-Host (" " + $k + "=" + $v)
}
}
}
}
$candidates = @(
"E:\wwwroot\.env",
"C:\yutongda\.env",
"C:\yutongda\releases\admin-rel26-local-source-20260911d\backend\.env",
"C:\yutongda\releases\admin-rel26-local-source-20260911d\.env",
"E:\wwwroot\inquiry-new\.env"
)
Get-ChildItem "C:\yutongda" -Filter ".env" -Recurse -Depth 4 -ErrorAction SilentlyContinue | Select-Object -First 10 | ForEach-Object { $candidates += $_.FullName }
$candidates = $candidates | Select-Object -Unique
foreach ($p in $candidates) {
if (Test-Path $p) { Show-RedisKeys $p (Load-EnvFile $p) }
}
# Compare whether URL password equals any YTD_REDIS_PASSWORD found
$www = Load-EnvFile "E:\wwwroot\.env"
$urlPw = $null; $urlUser = $null
if ($www["REDIS_URL"] -match "redis://([^:/@]+):([^@]+)@") { $urlUser = $Matches[1]; $urlPw = $Matches[2] }
Get-ChildItem "C:\yutongda" -Filter ".env" -Recurse -Depth 4 -ErrorAction SilentlyContinue | ForEach-Object {
$m = Load-EnvFile $_.FullName
if ($m.ContainsKey("YTD_REDIS_PASSWORD") -and $urlPw) {
$same = ($m["YTD_REDIS_PASSWORD"] -eq $urlPw)
Write-Host ("cmp " + $_.FullName + " YTD_pw_len=" + $m["YTD_REDIS_PASSWORD"].Length + " same_as_www_url_pw=" + $same + " user=" + $m["YTD_REDIS_USERNAME"])
}
}
Write-Host "COMPARE_DONE"
+41
View File
@@ -0,0 +1,41 @@
$ErrorActionPreference = 'Continue'
Write-Host '=== listening 8180 ==='
Get-NetTCPConnection -LocalPort 8180 -State Listen -EA SilentlyContinue | Select-Object -First 1 OwningProcess
$java = Get-CimInstance Win32_Process -Filter "Name='java.exe'" | Where-Object { $_.CommandLine -like '*inquiry-new*' } | Select-Object -First 1
if ($java) {
Write-Host ("pid=" + $java.ProcessId)
$cl = $java.CommandLine
if ($cl.Length -gt 200) { $cl = $cl.Substring(0,200) }
Write-Host ("cmd=" + $cl)
}
Write-Host '=== fat jar inquiry/poi ==='
$jarCmd = 'C:\yutongda\tools\jdk8\bin\jar.exe'
$Fat = 'E:\wwwroot\inquiry-new\jeecg-system-start-3.5.3.jar'
Write-Host ("fat_bytes=" + (Get-Item $Fat).Length)
& $jarCmd tf $Fat | Select-String 'jeecg-module-inquiry|poi-ooxml|commons-io'
Write-Host '=== probe urls ==='
$bodyPath = Join-Path $env:TEMP 'login-body.json'
Set-Content $bodyPath '{"username":"admin","password":"123456","captcha":"","checkKey":""}' -Encoding ASCII
$login = Invoke-RestMethod -Uri 'http://127.0.0.1:8180/jeecgboot/sys/login' -Method POST -InFile $bodyPath -ContentType 'application/json;charset=UTF-8'
$h = @{ 'X-Access-Token' = $login.result.token }
foreach ($u in @(
'http://127.0.0.1:8180/jeecgboot/inquiry/quoteTemplate/list?pageNo=1&pageSize=1',
'http://127.0.0.1:8180/inquiry/quoteTemplate/list?pageNo=1&pageSize=1',
'http://127.0.0.1:8180/jeecgboot/sys/permission/getPermCode'
)) {
try {
$r = Invoke-WebRequest -Uri $u -Headers $h -UseBasicParsing -TimeoutSec 15
Write-Host ("OK " + $u.Split('?')[0].Split('/')[-1] + " status=" + $r.StatusCode + " len=" + $r.RawContentLength)
} catch {
$code = $null
try { $code = [int]$_.Exception.Response.StatusCode } catch {}
Write-Host ("FAIL status=" + $code + " url=" + $u)
}
}
Write-Host '=== stdout mapping quoteTemplate ==='
Select-String -Path 'E:\wwwroot\inquiry-new\logs\stdout.log' -Pattern 'quoteTemplate|InquiryQuoteTemplate|Mapped.*inquiry' | Select-Object -Last 15 | ForEach-Object {
$s = $_.Line
if ($s.Length -gt 220) { $s = $s.Substring(0,220) }
Write-Host $s
}
Write-Host 'DIAG_DONE'
+11
View File
@@ -0,0 +1,11 @@
$ErrorActionPreference = 'Continue'
$jarCmd = 'C:\yutongda\tools\jdk8\bin\jar.exe'
$Fat = 'E:\wwwroot\inquiry-new\jeecg-system-start-3.5.3.jar'
$Incoming = 'C:\Users\ytd_extdev_0903\incoming'
Write-Host ("fat=" + (Get-Item $Fat).Length)
Get-ChildItem $Incoming -Filter 'jeecg*.jar' | ForEach-Object { Write-Host ("incoming " + $_.Name + " " + $_.Length) }
Write-Host '=== fat libs matching inquiry/poi ==='
& $jarCmd tf $Fat 2>&1 | findstr /i "inquiry poi-ooxml poi-4 commons-io"
Write-Host '=== spring factories / component ==='
& $jarCmd tf $Fat 2>&1 | findstr /i "InquiryQuoteTemplate"
Write-Host 'DIAG2_DONE'
+47
View File
@@ -0,0 +1,47 @@
# 诊断 Redis 池;不打印密码明文
$ErrorActionPreference = 'Continue'
$java = Get-CimInstance Win32_Process -Filter "Name='java.exe'" | Where-Object { $_.CommandLine -like '*inquiry-new*' } | Select-Object -First 1
if (-not $java) { Write-Host 'no_ledger_java'; exit 1 }
Write-Host ("pid=" + $java.ProcessId)
foreach ($line in (Get-Content 'E:\wwwroot\.env')) {
$t = $line.Trim()
if (-not $t -or $t.StartsWith('#')) { continue }
$i = $t.IndexOf('=')
if ($i -lt 1) { continue }
$k = $t.Substring(0, $i).Trim()
$v = $t.Substring($i + 1).Trim().Trim('"')
if ($k -match 'REDIS|YTD_REDIS') {
$pref = if ($v.Length -ge 12) { $v.Substring(0,12) + '...' } else { '***' }
Write-Host ("envfile $k len=$($v.Length) prefix=$pref")
}
}
$logDir = 'E:\wwwroot\inquiry-new\logs'
Get-ChildItem $logDir -Filter '*.log' -ErrorAction SilentlyContinue | Sort-Object LastWriteTime -Descending | Select-Object -First 2 | ForEach-Object {
Write-Host ("log=$($_.Name) size=$($_.Length)")
Select-String -Path $_.FullName -Pattern 'Could not get a resource from the pool|JedisConnectionException|NOAUTH|WRONGPASS' | Select-Object -Last 6 | ForEach-Object {
$s = $_.Line
if ($s.Length -gt 200) { $s = $s.Substring(0,200) }
Write-Host (" $s")
}
}
$login = Invoke-RestMethod -Uri 'http://127.0.0.1:8180/jeecgboot/sys/login' -Method POST -Body '{"username":"admin","password":"123456","captcha":"","checkKey":""}' -ContentType 'application/json;charset=UTF-8'
$h = @{ 'X-Access-Token' = $login.result.token }
foreach ($u in @(
'http://127.0.0.1:8180/jeecgboot/sys/permission/getPermCode',
'http://127.0.0.1:8180/jeecgboot/inquiry/quoteTemplate/agent/listEnabled',
'http://127.0.0.1:8180/jeecgboot/sys/user/list?pageNo=1&pageSize=1'
)) {
try {
$r = Invoke-WebRequest -Uri $u -Headers $h -UseBasicParsing -TimeoutSec 15
$j = $r.Content | ConvertFrom-Json
$msg = [string]$j.message
if ($msg.Length -gt 60) { $msg = $msg.Substring(0,60) }
Write-Host ("API $($u.Split('?')[0].Split('/')[-1]) success=$($j.success) code=$($j.code) msg=$msg")
} catch {
Write-Host ("API fail $u $($_.Exception.Message)")
}
}
Write-Host 'REDIS_DIAG_DONE'
+28
View File
@@ -0,0 +1,28 @@
$ErrorActionPreference = "Continue"
Write-Host "=== run-ledger-inner.ps1 ==="
Get-Content "E:\wwwroot\ops\run-ledger-inner.ps1"
Write-Host "=== config redis lines ==="
Get-ChildItem "E:\wwwroot\inquiry-new\config" -Recurse -ErrorAction SilentlyContinue | ForEach-Object { Write-Host ("cfgfile=" + $_.FullName) }
Select-String -Path "E:\wwwroot\inquiry-new\config\*.yml","E:\wwwroot\inquiry-new\config\*.yaml","E:\wwwroot\inquiry-new\config\*.properties" -Pattern "redis" -ErrorAction SilentlyContinue | ForEach-Object { Write-Host ($_.Filename + ":" + $_.LineNumber + ":" + $_.Line.Trim()) }
Write-Host "=== simulate parse lens only ==="
Get-Content "E:\wwwroot\.env" | ForEach-Object {
$line = $_.Trim()
if (-not $line -or $line.StartsWith("#")) { return }
$i = $line.IndexOf("=")
if ($i -lt 1) { return }
Set-Item -Path ("Env:" + $line.Substring(0, $i).Trim()) -Value $line.Substring($i + 1).Trim().Trim('"')
}
Write-Host ("from_env user=" + $env:YTD_REDIS_USERNAME + " user_len=" + $env:YTD_REDIS_USERNAME.Length)
Write-Host ("url_len=" + $env:REDIS_URL.Length)
if ($env:REDIS_URL -match "redis://:([^@]+)@([^:/]+)(?::(\d+))?(?:/(\d+))?") {
Write-Host ("form=pass_only host=" + $Matches[2] + " pw_len=" + $Matches[1].Length)
} elseif ($env:REDIS_URL -match "redis://([^:/@]+):([^@]+)@([^:/]+)(?::(\d+))?(?:/(\d+))?") {
Write-Host ("form=user_pass user=" + $Matches[1] + " user_len=" + $Matches[1].Length + " pw_len=" + $Matches[2].Length + " host=" + $Matches[3] + " port=" + $Matches[4] + " db=" + $Matches[5])
} else {
Write-Host "NO_MATCH"
# show sanitized url structure
$u = $env:REDIS_URL
$safe = $u -replace "://[^@]+@", "://***:***@"
Write-Host ("url_safe=" + $safe)
}
Write-Host "DUMP_DONE"
+28
View File
@@ -0,0 +1,28 @@
$ErrorActionPreference = 'Continue'
$candidates = @(
'E:\wwwroot\tools\apache-maven-3.9.9\bin\mvn.cmd',
'C:\yutongda\tools\apache-maven-3.9.9\bin\mvn.cmd',
'C:\Program Files\Apache\maven\bin\mvn.cmd'
)
foreach ($c in $candidates) {
if (Test-Path $c) {
Write-Host "FOUND=$c"
& $c -v
exit 0
}
}
Write-Host 'NO_LOCAL_MAVEN'
# install under E:\wwwroot\tools
$tools = 'E:\wwwroot\tools'
New-Item -ItemType Directory -Force -Path $tools | Out-Null
$mvnHome = Join-Path $tools 'apache-maven-3.9.9'
$mvnCmd = Join-Path $mvnHome 'bin\mvn.cmd'
if (Test-Path $mvnCmd) { Write-Host "FOUND=$mvnCmd"; & $mvnCmd -v; exit 0 }
$zip = Join-Path $tools 'apache-maven-3.9.9-bin.zip'
$url2 = 'https://archive.apache.org/dist/maven/maven-3/3.9.9/binaries/apache-maven-3.9.9-bin.zip'
Write-Host 'downloading maven on remote...'
Invoke-WebRequest -Uri $url2 -OutFile $zip -UseBasicParsing
Expand-Archive -Path $zip -DestinationPath $tools -Force
if (-not (Test-Path $mvnCmd)) { throw 'maven extract failed' }
Write-Host "FOUND=$mvnCmd"
& $mvnCmd -v
+20
View File
@@ -0,0 +1,20 @@
$ErrorActionPreference = "Continue"
Select-String -Path "E:\wwwroot\inquiry-new\logs\stdout.log" -Pattern "redis_mode=" | Select-Object -Last 2 | ForEach-Object { Write-Host $_.Line }
$bodyPath = Join-Path $env:TEMP "login-body.json"
Set-Content -Path $bodyPath -Value '{"username":"admin","password":"123456","captcha":"","checkKey":""}' -Encoding ASCII
$login = Invoke-RestMethod -Uri "http://127.0.0.1:8180/jeecgboot/sys/login" -Method POST -InFile $bodyPath -ContentType "application/json;charset=UTF-8"
$h = @{ "X-Access-Token" = $login.result.token }
$a = Invoke-RestMethod -Uri "http://127.0.0.1:8180/jeecgboot/inquiry/quoteTemplate/agent/listEnabled" -Headers $h
Write-Host ("agent_enabled=" + @($a.result).Count + " login_captcha_ok=" + $login.success)
# agent process?
$agent = Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -and ($_.CommandLine -like '*inquiry-agent*' -or $_.CommandLine -like '*uvicorn*' -or $_.CommandLine -like '*8910*') } | Select-Object -First 5
if ($agent) { $agent | ForEach-Object { Write-Host ("proc pid=" + $_.ProcessId + " " + $_.CommandLine.Substring(0,[Math]::Min(120,$_.CommandLine.Length))) } }
else { Write-Host "agent_proc_not_found_in_wmi_sample" }
# health if any
foreach ($u in @("http://127.0.0.1:8910/health","http://127.0.0.1:8810/health","http://127.0.0.1:8910/")) {
try {
$r = Invoke-WebRequest -Uri $u -UseBasicParsing -TimeoutSec 3
Write-Host ("health " + $u + " " + $r.StatusCode)
} catch { Write-Host ("health " + $u + " fail") }
}
Write-Host "FINAL_SMOKE_DONE"
+54
View File
@@ -0,0 +1,54 @@
$ErrorActionPreference = "Continue"
# prod java parent / cmdline full redis-related flags (mask password values)
$prod = Get-CimInstance Win32_Process -Filter "Name='java.exe'" | Where-Object { $_.CommandLine -like '*server.port=8080*' } | Select-Object -First 1
if ($prod) {
$cl = $prod.CommandLine
Write-Host ("prod_pid=" + $prod.ProcessId)
# mask --spring.redis.password=...
$masked = [regex]::Replace($cl, "(--spring\.redis\.password=)(\S+)", '${1}***')
$masked = [regex]::Replace($masked, "(password=)([^,\s]+)", '${1}***')
Write-Host ("prod_cmd_len=" + $cl.Length)
# extract redis-related tokens
$tokens = $cl.Split(" ") | Where-Object { $_ -match "redis|REDIS|YTD_REDIS" }
foreach ($t in $tokens) {
$m = $t
if ($m -match "password=") { $m = ($m -replace "(=).+","=`$1***") }
Write-Host (" token=" + $m)
}
}
# agent short redis url
$agentEnv = "C:\yutongda\apps\inquiry-agent\.env"
if (Test-Path $agentEnv) {
Get-Content $agentEnv | ForEach-Object {
if ($_ -match "REDIS") {
$line = $_
if ($line -match "REDIS_URL=(.+)") {
$u = $Matches[1].Trim().Trim('"')
$safe = $u -replace "://[^@]+@", "://***@"
if ($safe -eq $u -and $u.Length -gt 20) { $safe = $u.Substring(0,12) + "...(len=" + $u.Length + ")" }
Write-Host ("agent_REDIS_URL_safe=" + $safe + " len=" + $u.Length)
} else {
Write-Host ("agent_" + $line)
}
}
}
}
# Try AUTH without username using password from URL via System.Uri
$www = Get-Content "E:\wwwroot\.env" | Where-Object { $_ -match "^REDIS_URL=" }
$url = ($www -replace "^REDIS_URL=","").Trim().Trim('"')
try {
# redis URL is not always valid System.Uri because of scheme; hack
$tmp = $url -replace "^redis://","http://"
$uri = [Uri]$tmp
Write-Host ("uri_userinfo_len=" + $uri.UserInfo.Length + " host=" + $uri.Host + " port=" + $uri.Port)
$ui = $uri.UserInfo
$up = $ui.Split(":",2)
Write-Host ("uri_user=" + $up[0] + " uri_pw_len=" + $up[1].Length)
} catch {
Write-Host ("uri_err=" + $_.Exception.Message)
}
# Does empty username + password work? Check lettuce docs - try starting a one-off with only password
# Use jedis from a small java snippet if jdk present
$java = "C:\yutongda\tools\jdk8\bin\java.exe"
Write-Host ("java_exists=" + (Test-Path $java))
Write-Host "FIND_DONE"
+36
View File
@@ -0,0 +1,36 @@
# 远端:确认 8180/8910/8080,然后执行 admin cutover(依赖已上传的 admin-dist.zip)
$ErrorActionPreference = 'Continue'
function Port-Up([int]$p) {
return [bool](Get-NetTCPConnection -LocalPort $p -State Listen -ErrorAction SilentlyContinue | Select-Object -First 1)
}
Write-Output ("8180=" + (Port-Up 8180))
Write-Output ("8910=" + (Port-Up 8910))
Write-Output ("8080=" + (Port-Up 8080))
Write-Output ("zip=" + (Test-Path 'C:\Users\ytd_extdev_0903\incoming\admin-dist.zip'))
Write-Output ("conf=" + (Test-Path 'C:\Users\ytd_extdev_0903\incoming\ai.conf.cutover-8180'))
if (-not (Port-Up 8180)) {
Write-Output 'START_LEDGER'
& powershell -NoProfile -ExecutionPolicy Bypass -File 'C:\Users\ytd_extdev_0903\incoming\_remote_wmi_start_ledger.ps1'
}
if (-not (Port-Up 8910)) {
Write-Output 'START_AGENT'
& powershell -NoProfile -ExecutionPolicy Bypass -File 'C:\Users\ytd_extdev_0903\incoming\_remote_safe_deploy_agent.ps1'
}
# 再等一会儿 8180
for ($i = 0; $i -lt 24; $i++) {
if (Port-Up 8180) { break }
Start-Sleep -Seconds 5
}
Write-Output ("8180_final=" + (Port-Up 8180))
Write-Output ("8910_final=" + (Port-Up 8910))
Write-Output ("8080_final=" + (Port-Up 8080))
if (-not (Port-Up 8180)) { throw '8180 still down' }
if (-not (Port-Up 8910)) { throw '8910 still down' }
if (-not (Port-Up 8080)) { throw '8080 still down' }
$ErrorActionPreference = 'Stop'
& powershell -NoProfile -ExecutionPolicy Bypass -File 'C:\Users\ytd_extdev_0903\incoming\_remote_cutover_admin_8180.ps1'
Write-Output 'ALL_DONE'
@@ -0,0 +1,30 @@
# 仅导入报价模板表到 inquiry_robot(禁止动 jeecg-boot)
$ErrorActionPreference = 'Stop'
Get-Content 'E:\wwwroot\.env' | ForEach-Object {
$line = $_.Trim()
if (-not $line -or $line.StartsWith('#')) { return }
$i = $line.IndexOf('=')
if ($i -lt 1) { return }
$k = $line.Substring(0, $i).Trim()
$v = $line.Substring($i + 1).Trim().Trim('"')
Set-Item -Path "Env:$k" -Value $v
}
$mysql = 'C:\yutongda\tools\mysql-8.0.39-winx64\bin\mysql.exe'
$hostName = if ($env:MYSQL_HOST) { $env:MYSQL_HOST } else { '127.0.0.1' }
$tcp = New-Object System.Net.Sockets.TcpClient
try {
$null = $tcp.ConnectAsync('127.0.0.1', 3306).Wait(1000)
if ($tcp.Connected) { $hostName = '127.0.0.1' }
} catch {
} finally {
$tcp.Close()
}
Write-Host "import host=$hostName db=$env:MYSQL_DB"
$f = 'C:\Users\ytd_extdev_0903\incoming\inquiry_quote_template_v1.sql'
if (-not (Test-Path $f)) { throw "missing $f" }
Write-Host "import $f"
& $mysql -h $hostName -P 3306 -u $env:MYSQL_USER "--password=$env:MYSQL_PASSWORD" --default-character-set=utf8mb4 $env:MYSQL_DB -e "source $($f.Replace('\','/'))"
if ($LASTEXITCODE -ne 0) { throw "fail sql code=$LASTEXITCODE" }
Write-Host 'SQL_OK'
& $mysql -h $hostName -P 3306 -u $env:MYSQL_USER "--password=$env:MYSQL_PASSWORD" $env:MYSQL_DB -e "SHOW TABLES LIKE 'inq_quote_template%';"
Write-Host 'VERIFY_DONE'
+27
View File
@@ -0,0 +1,27 @@
# 安装已上传完整的 CAPTCHA fat jar,并重启 8180 主账。禁止动现网 8080。
$ErrorActionPreference = 'Stop'
$Src = 'C:\Users\ytd_extdev_0903\incoming\jeecg-system-start-3.5.3.CAPTCHA.jar'
$Incoming = 'C:\Users\ytd_extdev_0903\incoming\jeecg-system-start-3.5.3.jar'
$Fat = 'E:\wwwroot\inquiry-new\jeecg-system-start-3.5.3.jar'
$Expect = 177074039
if (-not (Test-Path $Src)) { throw "missing $Src" }
$len = (Get-Item $Src).Length
Write-Host ("src_bytes=" + $len)
if ($len -ne $Expect) { throw ("size mismatch expect=" + $Expect + " got=" + $len) }
Get-CimInstance Win32_Process | ForEach-Object {
if ($_.CommandLine -and ($_.CommandLine -like '*inquiry-new*jeecg*' -or $_.CommandLine -like '*run-ledger*')) {
Write-Host ("killing=" + $_.ProcessId)
Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue
}
}
Start-Sleep -Seconds 3
Copy-Item $Src $Incoming -Force
Copy-Item $Src $Fat -Force
Write-Host ("fat_bytes=" + (Get-Item $Fat).Length)
Write-Host 'COPIED_CAPTCHA_JAR'
powershell -NoProfile -ExecutionPolicy Bypass -File C:\Users\ytd_extdev_0903\incoming\_remote_wmi_start_ledger.ps1
Write-Host 'INSTALL_CAPTCHA_DONE'
+30
View File
@@ -0,0 +1,30 @@
# Install COMMONSIO fat jar and restart 8180
$ErrorActionPreference = 'Stop'
$Src = 'C:\Users\ytd_extdev_0903\incoming\jeecg-system-start-3.5.3.COMMONSIO.jar'
$Incoming = 'C:\Users\ytd_extdev_0903\incoming\jeecg-system-start-3.5.3.jar'
$Fat = 'E:\wwwroot\inquiry-new\jeecg-system-start-3.5.3.jar'
$Expect = 177186821
if (-not (Test-Path $Src)) { throw "missing $Src" }
$len = (Get-Item $Src).Length
Write-Host ("src_bytes=" + $len)
if ($len -lt 170000000) { throw 'src too small' }
$jarCmd = 'C:\yutongda\tools\jdk8\bin\jar.exe'
Write-Host '=== commons-io in src ==='
& $jarCmd tf $Src | Select-String 'commons-io'
Get-CimInstance Win32_Process | ForEach-Object {
if ($_.CommandLine -and ($_.CommandLine -like '*inquiry-new*jeecg*' -or $_.CommandLine -like '*run-ledger*')) {
Write-Host ("killing=" + $_.ProcessId)
Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue
}
}
Start-Sleep -Seconds 3
Copy-Item $Src $Incoming -Force
Copy-Item $Src $Fat -Force
Write-Host ("fat_bytes=" + (Get-Item $Fat).Length)
Write-Host 'COPIED_COMMONSIO'
powershell -NoProfile -ExecutionPolicy Bypass -File C:\Users\ytd_extdev_0903\incoming\_remote_wmi_start_ledger.ps1
Write-Host 'INSTALL_COMMONSIO_DONE'
+19
View File
@@ -0,0 +1,19 @@
$ErrorActionPreference='Stop'
$New='C:\Users\ytd_extdev_0903\incoming\jeecg-system-start-3.5.3.NEW.jar'
$Incoming='C:\Users\ytd_extdev_0903\incoming\jeecg-system-start-3.5.3.jar'
$Fat='E:\wwwroot\inquiry-new\jeecg-system-start-3.5.3.jar'
if(-not (Test-Path $New)){ throw "missing $New" }
$len=(Get-Item $New).Length
Write-Host ("new_bytes="+$len)
if($len -lt 170000000){ throw "new jar too small, incomplete upload" }
# stop loops
Get-CimInstance Win32_Process | ForEach-Object {
if($_.CommandLine -and ($_.CommandLine -like '*inquiry-new*jeecg*' -or $_.CommandLine -like '*run-ledger*')){
Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue
}
}
Start-Sleep -Seconds 2
Copy-Item $New $Incoming -Force
Copy-Item $New $Fat -Force
Write-Host 'COPIED_NEW_JAR'
powershell -NoProfile -ExecutionPolicy Bypass -File C:\Users\ytd_extdev_0903\incoming\_remote_wmi_start_ledger.ps1
+29
View File
@@ -0,0 +1,29 @@
# Install POI412 fat jar and restart 8180
$ErrorActionPreference = 'Stop'
$Src = 'C:\Users\ytd_extdev_0903\incoming\jeecg-system-start-3.5.3.POI412.jar'
$Incoming = 'C:\Users\ytd_extdev_0903\incoming\jeecg-system-start-3.5.3.jar'
$Fat = 'E:\wwwroot\inquiry-new\jeecg-system-start-3.5.3.jar'
if (-not (Test-Path $Src)) { throw "missing $Src" }
$len = (Get-Item $Src).Length
Write-Host ("src_bytes=" + $len)
if ($len -lt 160000000) { throw 'src too small' }
$jarCmd = 'C:\yutongda\tools\jdk8\bin\jar.exe'
Write-Host '=== poi entries ==='
& $jarCmd tf $Src | Select-String 'poi-'
Get-CimInstance Win32_Process | ForEach-Object {
if ($_.CommandLine -and ($_.CommandLine -like '*inquiry-new*jeecg*' -or $_.CommandLine -like '*run-ledger*')) {
Write-Host ("killing=" + $_.ProcessId)
Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue
}
}
Start-Sleep -Seconds 3
Copy-Item $Src $Incoming -Force
Copy-Item $Src $Fat -Force
Write-Host ("fat_bytes=" + (Get-Item $Fat).Length)
Write-Host 'COPIED_POI412'
powershell -NoProfile -ExecutionPolicy Bypass -File C:\Users\ytd_extdev_0903\incoming\_remote_wmi_start_ledger.ps1
Write-Host 'INSTALL_POI412_DONE'
@@ -0,0 +1,37 @@
# Force install local-built POI412 jar that contains QuoteTemplateController
$ErrorActionPreference = 'Stop'
$Src = 'C:\Users\ytd_extdev_0903\incoming\jeecg-system-start-3.5.3.POI412.jar'
$Incoming = 'C:\Users\ytd_extdev_0903\incoming\jeecg-system-start-3.5.3.jar'
$Fat = 'E:\wwwroot\inquiry-new\jeecg-system-start-3.5.3.jar'
$Expect = 170763133
if (-not (Test-Path $Src)) { throw "missing $Src" }
$len = (Get-Item $Src).Length
Write-Host ("src_bytes=" + $len)
if ($len -ne $Expect) { Write-Host ("WARN size expect=" + $Expect + " got=" + $len) }
$work = Join-Path $env:TEMP 'verify-qt-class'
if (Test-Path $work) { Remove-Item $work -Recurse -Force }
New-Item -ItemType Directory $work | Out-Null
$jarCmd = 'C:\yutongda\tools\jdk8\bin\jar.exe'
Push-Location $work
& $jarCmd xf $Src 'BOOT-INF/lib/jeecg-module-inquiry-3.5.3.jar'
$has = (& $jarCmd tf 'BOOT-INF/lib/jeecg-module-inquiry-3.5.3.jar' | Select-String 'InquiryQuoteTemplateController').Count
Write-Host ("src_has_QuoteTemplateController=" + ($has -gt 0))
if ($has -lt 1) { throw 'source jar missing QuoteTemplateController' }
Pop-Location
Get-CimInstance Win32_Process | ForEach-Object {
if ($_.CommandLine -and ($_.CommandLine -like '*inquiry-new*jeecg*' -or $_.CommandLine -like '*run-ledger*')) {
Write-Host ("killing=" + $_.ProcessId)
Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue
}
}
Start-Sleep -Seconds 3
Copy-Item $Src $Incoming -Force
Copy-Item $Src $Fat -Force
Write-Host ("fat_bytes=" + (Get-Item $Fat).Length)
Write-Host 'COPIED_POI412_VERIFIED'
powershell -NoProfile -ExecutionPolicy Bypass -File C:\Users\ytd_extdev_0903\incoming\_remote_wmi_start_ledger.ps1
Write-Host 'INSTALL_VERIFIED_DONE'
+7
View File
@@ -0,0 +1,7 @@
$ErrorActionPreference = 'Continue'
Write-Host '=== stderr tail ==='
Get-Content 'E:\wwwroot\inquiry-new\logs\stderr.log' -Tail 80 -ErrorAction SilentlyContinue
Write-Host '=== stdout tail ==='
Get-Content 'E:\wwwroot\inquiry-new\logs\stdout.log' -Tail 40 -ErrorAction SilentlyContinue
Write-Host '=== listen 8180 ==='
Get-NetTCPConnection -LocalPort 8180 -State Listen -EA SilentlyContinue | Format-Table -AutoSize | Out-String | Write-Host
+14
View File
@@ -0,0 +1,14 @@
$ErrorActionPreference='Continue'
$r=Invoke-WebRequest -Uri 'http://127.0.0.1:8180/jeecgboot/sys/randomImage/seedcheck1' -UseBasicParsing -TimeoutSec 15
"randomImage status=$($r.StatusCode) len=$($r.RawContentLength)"
try {
$login=Invoke-RestMethod -Method Post -Uri 'http://127.0.0.1:8180/jeecgboot/sys/login' -ContentType 'application/json' -Body '{"username":"admin","password":"123456","captcha":"xxxx","checkKey":"seedcheck1"}' -TimeoutSec 20
$login | ConvertTo-Json -Compress
} catch {
"login_err=" + $_.Exception.Message
if($_.ErrorDetails){ $_.ErrorDetails.Message }
}
# process args
Get-CimInstance Win32_Process -Filter "Name='java.exe'" | ForEach-Object {
if($_.CommandLine -like '*inquiry-new*'){ $_.CommandLine.Substring(0,[Math]::Min(500,$_.CommandLine.Length)) }
}
+16
View File
@@ -0,0 +1,16 @@
$ErrorActionPreference='Continue'
Write-Host '=== config ==='
Get-ChildItem E:\wwwroot\inquiry-new\config -EA SilentlyContinue | Format-Table Name,Length
Select-String -Path 'E:\wwwroot\inquiry-new\config\*.yml' -Pattern 'loginCaptcha' -EA SilentlyContinue
Write-Host '=== try login with empty captcha after randomImage ==='
$key='seedkey99'
$img=Invoke-RestMethod -Uri ("http://127.0.0.1:8180/jeecgboot/sys/randomImage/"+$key) -TimeoutSec 15
"img_success=$($img.success) hasResult=$([bool]$img.result)"
# When captcha enabled, we cannot OCR. Try common bypass passwords from docs.
$pwds=@('123456','admin','Admin123','ytd123456')
foreach($p in $pwds){
$body=@{username='admin';password=$p;captcha='1';checkKey=$key} | ConvertTo-Json -Compress
$r=Invoke-RestMethod -Method Post -Uri 'http://127.0.0.1:8180/jeecgboot/sys/login' -ContentType 'application/json' -Body $body -TimeoutSec 20
Write-Host ("pwd="+$p+" success="+$r.success+" code="+$r.code+" msg="+$r.message)
if($r.success){ break }
}
+76
View File
@@ -0,0 +1,76 @@
# Hot-fix commons-io inside Spring Boot fat jar for POI 5.2.x.
# Replaces commons-io-2.6.jar with 2.11.0; does NOT use nested module jar uf.
$ErrorActionPreference = 'Stop'
$Fat = 'E:\wwwroot\inquiry-new\jeecg-system-start-3.5.3.jar'
$IncomingLib = 'C:\Users\ytd_extdev_0903\incoming\commons-io-2.11.0.jar'
$Work = Join-Path $env:TEMP 'fix-commons-io-fat'
$JarCmd = $null
foreach ($c in @('C:\yutongda\tools\jdk8\bin\jar.exe', 'C:\Program Files\Java\jdk1.8.0_202\bin\jar.exe')) {
if (Test-Path $c) { $JarCmd = $c; break }
}
if (-not $JarCmd) { throw 'jar.exe missing' }
if (-not (Test-Path $IncomingLib)) { throw "missing $IncomingLib" }
if (-not (Test-Path $Fat)) { throw "missing $Fat" }
# stop ledger only
Get-CimInstance Win32_Process | ForEach-Object {
if ($_.CommandLine -and ($_.CommandLine -like '*inquiry-new*jeecg*' -or $_.CommandLine -like '*run-ledger*')) {
Write-Host ("killing=" + $_.ProcessId)
Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue
}
}
Start-Sleep -Seconds 2
if (Test-Path $Work) { Remove-Item $Work -Recurse -Force }
New-Item -ItemType Directory -Force -Path $Work | Out-Null
Copy-Item $Fat (Join-Path $Work 'app.jar') -Force
Push-Location $Work
# Prefer zip -d if available; else jar + rebuild lib entry
$zip = $null
foreach ($z in @('C:\Program Files\7-Zip\7z.exe', 'C:\yutongda\tools\7zip\7z.exe')) {
if (Test-Path $z) { $zip = $z; break }
}
$oldEntry = 'BOOT-INF/lib/commons-io-2.6.jar'
$newEntry = 'BOOT-INF/lib/commons-io-2.11.0.jar'
New-Item -ItemType Directory -Force -Path 'BOOT-INF\lib' | Out-Null
Copy-Item $IncomingLib 'BOOT-INF\lib\commons-io-2.11.0.jar' -Force
if ($zip) {
& $zip d 'app.jar' $oldEntry | Out-Null
& $zip a -tzip 'app.jar' $newEntry | Out-Null
Write-Host 'patched_via_7z'
} else {
# jar uf can add; deleting old entry via .NET ZipArchive
Add-Type -AssemblyName System.IO.Compression
Add-Type -AssemblyName System.IO.Compression.FileSystem
$jarPath = (Resolve-Path 'app.jar').Path
$fs = [IO.File]::Open($jarPath, [IO.FileMode]::Open, [IO.FileAccess]::ReadWrite)
try {
$zipArc = New-Object IO.Compression.ZipArchive($fs, [IO.Compression.ZipArchiveMode]::Update)
$old = $zipArc.GetEntry($oldEntry)
if ($old) { $old.Delete(); Write-Host 'deleted_old_2.6' } else { Write-Host 'old_2.6_absent' }
$existing = $zipArc.GetEntry($newEntry)
if ($existing) { $existing.Delete() }
$entry = $zipArc.CreateEntry($newEntry, [IO.Compression.CompressionLevel]::Optimal)
$src = [IO.File]::OpenRead((Resolve-Path 'BOOT-INF\lib\commons-io-2.11.0.jar').Path)
try {
$dest = $entry.Open()
try { $src.CopyTo($dest) } finally { $dest.Dispose() }
} finally { $src.Dispose() }
$zipArc.Dispose()
} finally { $fs.Dispose() }
Write-Host 'patched_via_ziparchive'
}
Copy-Item 'app.jar' $Fat -Force
Copy-Item 'app.jar' 'C:\Users\ytd_extdev_0903\incoming\jeecg-system-start-3.5.3.jar' -Force
Pop-Location
# confirm entry names
& $JarCmd tf $Fat | Select-String 'commons-io'
Write-Host 'COMMONS_IO_PATCHED'
powershell -NoProfile -ExecutionPolicy Bypass -File C:\Users\ytd_extdev_0903\incoming\_remote_wmi_start_ledger.ps1
Write-Host 'PATCH_RESTART_DONE'
+49
View File
@@ -0,0 +1,49 @@
# 远端:用新的 jeecg-module-inquiry jar 热补主账 fat jar,并重启 8180
$ErrorActionPreference = 'Stop'
$Incoming = 'C:\Users\ytd_extdev_0903\incoming'
$Root = 'E:\wwwroot\inquiry-new'
$Fat = Join-Path $Root 'jeecg-system-start-3.5.3.jar'
$NewMod = Join-Path $Incoming 'jeecg-module-inquiry-3.5.3.jar'
$Java = 'C:\yutongda\tools\jdk8\bin\jar.exe'
if (-not (Test-Path $Java)) { $Java = 'C:\yutongda\tools\jdk8\bin\jar' }
# prefer jar from PATH jdk
$jarCmd = $null
foreach ($c in @(
'C:\yutongda\tools\jdk8\bin\jar.exe',
'E:\wwwroot\tools\jdk-17\bin\jar.exe'
)) {
if (Test-Path $c) { $jarCmd = $c; break }
}
if (-not $jarCmd) { throw 'jar.exe not found' }
if (-not (Test-Path $NewMod)) { throw "missing $NewMod" }
if (-not (Test-Path $Fat)) { throw "missing $Fat" }
# stop ledger
Get-CimInstance Win32_Process -Filter "Name='java.exe'" | ForEach-Object {
if ($_.CommandLine -and $_.CommandLine -like '*inquiry-new*jeecg-system-start*') {
Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue
}
}
Start-Sleep -Seconds 2
$work = Join-Path $env:TEMP 'patch-inquiry-mod'
if (Test-Path $work) { Remove-Item $work -Recurse -Force }
New-Item -ItemType Directory $work | Out-Null
Copy-Item $Fat (Join-Path $work 'app.jar') -Force
Push-Location $work
New-Item -ItemType Directory -Force -Path 'BOOT-INF\lib' | Out-Null
Copy-Item $NewMod 'BOOT-INF\lib\jeecg-module-inquiry-3.5.3.jar' -Force
& $jarCmd uf app.jar 'BOOT-INF/lib/jeecg-module-inquiry-3.5.3.jar'
if ($LASTEXITCODE -ne 0) { Pop-Location; throw "jar uf failed $LASTEXITCODE" }
Copy-Item app.jar $Fat -Force
Pop-Location
Write-Host 'PATCH_JAR_OK'
# restart via existing WMI script if present
$start = Join-Path $Incoming '_remote_wmi_start_ledger.ps1'
if (Test-Path $start) {
& powershell -NoProfile -ExecutionPolicy Bypass -File $start
} else {
throw 'start script missing'
}
Write-Host 'PATCH_RESTART_DONE'
+35
View File
@@ -0,0 +1,35 @@
$ErrorActionPreference = "Continue"
$bodyPath = Join-Path $env:TEMP "login-body.json"
Set-Content -Path $bodyPath -Value '{"username":"admin","password":"123456","captcha":"","checkKey":""}' -Encoding ASCII
$login = Invoke-RestMethod -Uri "http://127.0.0.1:8180/jeecgboot/sys/login" -Method POST -InFile $bodyPath -ContentType "application/json;charset=UTF-8"
$token = $login.result.token
$h = @{ "X-Access-Token" = $token }
$list = Invoke-RestMethod -Uri "http://127.0.0.1:8180/jeecgboot/inquiry/quoteTemplate/list?pageNo=1&pageSize=20" -Headers $h
$recs = @($list.result.records)
Write-Host ("count=" + $recs.Count)
# dump raw preview body for first with versionId
$rec = $recs | Where-Object { $_.versionId } | Select-Object -First 1
$vid = $rec.versionId
Write-Host ("pick versionId=" + $vid)
$r = Invoke-WebRequest -Uri ("http://127.0.0.1:8180/jeecgboot/inquiry/quoteTemplate/previewSample?versionId=" + $vid) -Headers $h -UseBasicParsing -TimeoutSec 60
Write-Host ("ctype=" + $r.Headers["Content-Type"])
Write-Host ("body=" + $r.Content)
# list upload dirs
$roots = @(
"E:\wwwroot\data\upFiles",
"E:\wwwroot\inquiry-new\upFiles",
"E:\wwwroot\upFiles",
"C:\Users\ytd_extdev_0903\incoming"
)
foreach ($root in $roots) {
if (Test-Path $root) {
Write-Host ("root=" + $root)
Get-ChildItem $root -Recurse -Filter "*.xlsx" -ErrorAction SilentlyContinue | Select-Object -First 15 | ForEach-Object {
Write-Host (" xlsx=" + $_.FullName + " size=" + $_.Length)
}
} else { Write-Host ("missing_root=" + $root) }
}
# yml upload path
Select-String -Path "E:\wwwroot\inquiry-new\config\application-inquiry.yml" -Pattern "upload|path:" | Select-Object -First 20 | ForEach-Object { Write-Host ("yml:" + $_.Line.Trim()) }
Write-Host "PROBE2_DONE"
+45
View File
@@ -0,0 +1,45 @@
$ErrorActionPreference = "Continue"
$bodyPath = Join-Path $env:TEMP "login-body.json"
Set-Content -Path $bodyPath -Value '{"username":"admin","password":"123456","captcha":"","checkKey":""}' -Encoding ASCII
$login = Invoke-RestMethod -Uri "http://127.0.0.1:8180/jeecgboot/sys/login" -Method POST -InFile $bodyPath -ContentType "application/json;charset=UTF-8"
$token = $login.result.token
$h = @{ "X-Access-Token" = $token }
$list = Invoke-RestMethod -Uri "http://127.0.0.1:8180/jeecgboot/inquiry/quoteTemplate/list?pageNo=1&pageSize=5" -Headers $h
$rec = @($list.result.records)[0]
Write-Host ("tpl=" + $rec.name + " versionId=" + $rec.versionId + " objectKey=" + $rec.objectKey)
$vid = $rec.versionId
# header auth
try {
$r = Invoke-WebRequest -Uri ("http://127.0.0.1:8180/jeecgboot/inquiry/quoteTemplate/previewSample?versionId=" + $vid) -Headers $h -UseBasicParsing -TimeoutSec 60
Write-Host ("header_auth status=" + $r.StatusCode + " len=" + $r.RawContentLength + " ctype=" + $r.Headers["Content-Type"] + " cd=" + $r.Headers["Content-Disposition"])
} catch {
$resp = $_.Exception.Response
if ($resp) {
$reader = New-Object IO.StreamReader($resp.GetResponseStream())
$txt = $reader.ReadToEnd()
Write-Host ("header_auth FAIL status=" + [int]$resp.StatusCode + " body=" + $txt.Substring(0, [Math]::Min(500, $txt.Length)))
} else { Write-Host ("header_auth ERR " + $_.Exception.Message) }
}
# query token (browser window.open style)
try {
$url = "http://127.0.0.1:8180/jeecgboot/inquiry/quoteTemplate/previewSample?versionId=$vid&token=$token"
$r2 = Invoke-WebRequest -Uri $url -UseBasicParsing -TimeoutSec 60
Write-Host ("query_token status=" + $r2.StatusCode + " len=" + $r2.RawContentLength)
} catch {
$resp = $_.Exception.Response
if ($resp) {
$reader = New-Object IO.StreamReader($resp.GetResponseStream())
$txt = $reader.ReadToEnd()
Write-Host ("query_token FAIL status=" + [int]$resp.StatusCode + " body=" + $txt.Substring(0, [Math]::Min(500, $txt.Length)))
} else { Write-Host ("query_token ERR " + $_.Exception.Message) }
}
# check file exists
if ($rec.objectKey) {
$up = "E:\wwwroot\data\upFiles"
$cands = @(
(Join-Path $up ($rec.objectKey -replace "/","\")),
$rec.objectKey
)
foreach ($p in $cands) { Write-Host ("exists " + $p + " => " + (Test-Path $p)) }
}
Write-Host "PROBE_DONE"
+19
View File
@@ -0,0 +1,19 @@
$ErrorActionPreference = 'Continue'
Write-Host ("whoami=" + (whoami))
Write-Host '=== roots ==='
@('E:\wwwroot','C:\wwwroot','C:\yutongda','C:\Users\ytd_extdev_0903\incoming','E:\wwwroot\inquiry-new','E:\wwwroot\.env') | ForEach-Object {
Write-Host ("exists={0} path={1}" -f (Test-Path $_), $_)
}
Write-Host '=== ports ==='
Get-NetTCPConnection -LocalPort 8180,8910,3306 -State Listen -EA SilentlyContinue |
Select-Object LocalAddress,LocalPort,OwningProcess |
Format-Table -AutoSize | Out-String | Write-Host
Write-Host '=== mysql candidates ==='
Get-ChildItem -Path C:\,E:\ -Filter mysql.exe -Recurse -Depth 6 -EA SilentlyContinue |
Select-Object -First 5 FullName |
ForEach-Object { Write-Host $_.FullName }
Write-Host '=== jar ==='
Get-ChildItem -Path E:\wwwroot,C:\Users\ytd_extdev_0903\incoming -Filter '*.jar' -Recurse -Depth 3 -EA SilentlyContinue |
Select-Object FullName,Length,LastWriteTime |
Format-Table -AutoSize | Out-String | Write-Host
Write-Host PROBE_DONE
+6
View File
@@ -0,0 +1,6 @@
$ErrorActionPreference='Continue'
$c=Get-NetTCPConnection -LocalPort 8180 -State Listen -EA SilentlyContinue | Select-Object -First 1
if($c){"LISTEN pid=$($c.OwningProcess)"} else {'DOWN'}
Get-Item E:\wwwroot\inquiry-new\jeecg-system-start-3.5.3.jar | Select-Object Length,LastWriteTime | Format-List
Get-Content E:\wwwroot\inquiry-new\logs\stdout.log -Tail 8
Get-Content E:\wwwroot\inquiry-new\logs\stderr.log -Tail 15
+80
View File
@@ -0,0 +1,80 @@
$ErrorActionPreference = "Continue"
Get-Content "E:\wwwroot\.env" | ForEach-Object {
$line = $_.Trim()
if (-not $line -or $line.StartsWith("#")) { return }
$i = $line.IndexOf("=")
if ($i -lt 1) { return }
Set-Item -Path ("Env:" + $line.Substring(0, $i).Trim()) -Value $line.Substring($i + 1).Trim().Trim('"')
}
if ($env:REDIS_URL -match "redis://([^:/@]+):([^@]+)@([^:/]+)(?::(\d+))?(?:/(\d+))?") {
$user = $Matches[1]; $pass = $Matches[2]; $host = $Matches[3]; $port = $Matches[4]; if (-not $port) { $port = "6379" }
} else { Write-Host "parse_fail"; exit 1 }
$decoded = [Uri]::UnescapeDataString($pass)
Write-Host ("user=" + $user + " pw_len=" + $pass.Length + " decoded_len=" + $decoded.Length + " has_pct=" + ($pass -match "%") + " host=" + $host + " port=" + $port)
# try python redis if present
$py = $null
foreach ($c in @("C:\yutongda\tools\python\python.exe","C:\Python39\python.exe","python")) {
try { $v = & $c -c "print(1)" 2>$null; if ($v -eq "1") { $py = $c; break } } catch {}
}
Write-Host ("python=" + $py)
if ($py) {
$script = @"
import socket,sys
host='$host'; port=int('$port'); user='$user'
# password via env to avoid shell escaping issues
import os
pw=os.environ['PROBE_REDIS_PW']
pw2=os.environ.get('PROBE_REDIS_PW2',pw)
def probe(name, auth_cmd):
s=socket.create_connection((host,port),3)
s.settimeout(3)
s.sendall(auth_cmd)
data=s.recv(128)
s.close()
print(name, data[:80])
# RESP AUTH password
def resp_arr(*parts):
out='*%d\r\n'%len(parts)
for p in parts:
b=p.encode('utf-8') if isinstance(p,str) else p
out+='$%d\r\n'%len(b)
out+=b.decode('latin1') if isinstance(b,bytes) else ''
# rebuild properly
# better
chunks=[('*%d\r\n'%len(parts)).encode()]
for p in parts:
b=p.encode('utf-8')
chunks.append(('$%d\r\n'%len(b)).encode()); chunks.append(b); chunks.append(b'\r\n')
return b''.join(chunks)
for name,cmd in [
('auth_pw_only', resp_arr('AUTH', pw)),
('auth_user_pw', resp_arr('AUTH', user, pw)),
('auth_pw_decoded', resp_arr('AUTH', pw2)),
('auth_user_pw_decoded', resp_arr('AUTH', user, pw2)),
('ping_noauth', resp_arr('PING')),
]:
try:
s=socket.create_connection((host,port),3); s.settimeout(3); s.sendall(cmd); print(name, s.recv(120)); s.close()
except Exception as e:
print(name, 'ERR', e)
"@
$env:PROBE_REDIS_PW = $pass
$env:PROBE_REDIS_PW2 = $decoded
$tmp = Join-Path $env:TEMP "redis_probe.py"
Set-Content -Path $tmp -Value $script -Encoding ASCII
& $py $tmp
}
# how does prod 8080 pass redis? cmdline snippet without secrets
Get-CimInstance Win32_Process -Filter "Name='java.exe'" | ForEach-Object {
if ($_.CommandLine -like '*8080*' -or $_.CommandLine -like '*jeecg-boot*' -or $_.CommandLine -like '*wwwroot*jeecg*') {
$cl = $_.CommandLine
if ($cl.Length -gt 250) { $cl = $cl.Substring(0,250) }
Write-Host ("java8080ish pid=" + $_.ProcessId + " cmd=" + $cl)
}
}
Write-Host "AUTH_PROBE_DONE"
+70
View File
@@ -0,0 +1,70 @@
$ErrorActionPreference = "Continue"
Get-Content "E:\wwwroot\.env" | ForEach-Object {
$line = $_.Trim()
if (-not $line -or $line.StartsWith("#")) { return }
$i = $line.IndexOf("=")
if ($i -lt 1) { return }
Set-Item -Path ("Env:" + $line.Substring(0, $i).Trim()) -Value $line.Substring($i + 1).Trim().Trim('"')
}
if ($env:REDIS_URL -match "redis://([^:/@]+):([^@]+)@([^:/]+)(?::(\d+))?(?:/(\d+))?") {
$user = $Matches[1]; $pass = $Matches[2]; $rh = $Matches[3]; $port = $Matches[4]; if (-not $port) { $port = "6379" }
} else { Write-Host "parse_fail"; exit 1 }
Write-Host ("user=" + $user + " pw_len=" + $pass.Length + " rh=" + $rh + " port=" + $port)
function Send-Redis([string]$rh,[int]$port,[byte[]]$payload) {
$tcp = New-Object Net.Sockets.TcpClient
$tcp.ReceiveTimeout = 3000; $tcp.SendTimeout = 3000
$tcp.Connect($rh, $port)
$stream = $tcp.GetStream()
$stream.Write($payload, 0, $payload.Length)
$buf = New-Object byte[] 256
$n = $stream.Read($buf, 0, $buf.Length)
$tcp.Close()
return [Text.Encoding]::ASCII.GetString($buf, 0, $n)
}
function Resp-Auth([string[]]$parts) {
$ms = New-Object IO.MemoryStream
$sw = New-Object IO.StreamWriter($ms, [Text.Encoding]::UTF8)
$sw.NewLine = "`r`n"
$sw.WriteLine("*" + $parts.Count)
foreach ($p in $parts) {
$b = [Text.Encoding]::UTF8.GetBytes($p)
$sw.WriteLine("$" + $b.Length)
$sw.Flush()
$ms.Write($b, 0, $b.Length)
$cr = [Text.Encoding]::ASCII.GetBytes("`r`n"); $ms.Write($cr, 0, 2)
}
$sw.Flush()
return $ms.ToArray()
}
foreach ($label in @("pw_only","user_pw","ping")) {
try {
if ($label -eq "pw_only") { $payload = Resp-Auth @("AUTH", $pass) }
elseif ($label -eq "user_pw") { $payload = Resp-Auth @("AUTH", $user, $pass) }
else { $payload = Resp-Auth @("PING") }
$resp = Send-Redis $rh ([int]$port) $payload
$respOne = ($resp -split "`r`n")[0]
Write-Host ("AUTH " + $label + " => " + $respOne)
} catch {
Write-Host ("AUTH " + $label + " ERR " + $_.Exception.Message)
}
}
# prod redis config snippet (mask password values)
$prodCfgCandidates = @(
"C:\yutongda\releases\admin-rel26-local-source-20260911d\backend\config\application-prod.yml",
"C:\yutongda\releases\admin-rel26-local-source-20260911d\backend\application-prod.yml"
)
Get-ChildItem "C:\yutongda\releases\admin-rel26-local-source-20260911d\backend" -Recurse -Filter "*prod*.yml" -ErrorAction SilentlyContinue | Select-Object -First 5 | ForEach-Object {
Write-Host ("prod_yml=" + $_.FullName)
Select-String -Path $_.FullName -Pattern "redis|password|username|host" | Select-Object -First 20 | ForEach-Object {
$line = $_.Line
$line = $line -replace "(password\s*:\s*).+","`$1***"
Write-Host (" " + $line.Trim())
}
}
# also check if YTD_REDIS_PASSWORD exists separately in any env used by prod
Write-Host ("env_YTD_REDIS_PASSWORD_len=" + ($(if($env:YTD_REDIS_PASSWORD){$env:YTD_REDIS_PASSWORD.Length}else{0})))
Write-Host ("env_YTD_REDIS_USERNAME=" + $env:YTD_REDIS_USERNAME)
Write-Host "AUTH_PROBE2_DONE"
@@ -0,0 +1,91 @@
$ErrorActionPreference = "Continue"
function Redis-Ping([string]$rh, [int]$port) {
try {
$tcp = New-Object Net.Sockets.TcpClient
$iar = $tcp.BeginConnect($rh, $port, $null, $null)
$ok = $iar.AsyncWaitHandle.WaitOne(2000)
if (-not $ok) { $tcp.Close(); return "tcp_timeout" }
$tcp.EndConnect($iar)
$stream = $tcp.GetStream()
$stream.ReadTimeout = 2000
$payload = [Text.Encoding]::ASCII.GetBytes("*1`r`n`$4`r`nPING`r`n")
$stream.Write($payload, 0, $payload.Length)
$buf = New-Object byte[] 64
$n = $stream.Read($buf, 0, $buf.Length)
$tcp.Close()
return [Text.Encoding]::ASCII.GetString($buf, 0, $n).Trim()
} catch {
return ("err:" + $_.Exception.Message)
}
}
function Redis-AuthPw([string]$rh, [int]$port, [string]$pass) {
try {
$tcp = New-Object Net.Sockets.TcpClient
$tcp.Connect($rh, $port)
$stream = $tcp.GetStream()
$stream.ReadTimeout = 3000
# AUTH pass
$ms = New-Object IO.MemoryStream
$passBytes = [Text.Encoding]::UTF8.GetBytes($pass)
$hdr = [Text.Encoding]::ASCII.GetBytes("*2`r`n`$4`r`nAUTH`r`n`$" + $passBytes.Length + "`r`n")
$ms.Write($hdr, 0, $hdr.Length)
$ms.Write($passBytes, 0, $passBytes.Length)
$cr = [Text.Encoding]::ASCII.GetBytes("`r`n")
$ms.Write($cr, 0, 2)
$payload = $ms.ToArray()
$stream.Write($payload, 0, $payload.Length)
$buf = New-Object byte[] 128
$n = $stream.Read($buf, 0, $buf.Length)
$tcp.Close()
return [Text.Encoding]::ASCII.GetString($buf, 0, $n).Split("`r")[0]
} catch { return ("err:" + $_.Exception.Message) }
}
function Redis-AuthUserPw([string]$rh, [int]$port, [string]$user, [string]$pass) {
try {
$tcp = New-Object Net.Sockets.TcpClient
$tcp.Connect($rh, $port)
$stream = $tcp.GetStream()
$stream.ReadTimeout = 3000
$userBytes = [Text.Encoding]::UTF8.GetBytes($user)
$passBytes = [Text.Encoding]::UTF8.GetBytes($pass)
$ms = New-Object IO.MemoryStream
$w = {
param($s,$b)
$h = [Text.Encoding]::ASCII.GetBytes($s)
$ms.Write($h,0,$h.Length)
if ($b) { $ms.Write($b,0,$b.Length); $c=[Text.Encoding]::ASCII.GetBytes("`r`n"); $ms.Write($c,0,2) }
}
# manual
$parts = @("AUTH", $user, $pass)
$pre = [Text.Encoding]::ASCII.GetBytes("*" + $parts.Count + "`r`n")
$ms.Write($pre,0,$pre.Length)
foreach ($p in $parts) {
$b = [Text.Encoding]::UTF8.GetBytes($p)
$h = [Text.Encoding]::ASCII.GetBytes("$" + $b.Length + "`r`n")
$ms.Write($h,0,$h.Length)
$ms.Write($b,0,$b.Length)
$c=[Text.Encoding]::ASCII.GetBytes("`r`n"); $ms.Write($c,0,2)
}
$payload = $ms.ToArray()
$stream.Write($payload, 0, $payload.Length)
$buf = New-Object byte[] 128
$n = $stream.Read($buf, 0, $buf.Length)
$tcp.Close()
return [Text.Encoding]::ASCII.GetString($buf, 0, $n).Split("`r")[0]
} catch { return ("err:" + $_.Exception.Message) }
}
Write-Host ("ping_127=" + (Redis-Ping "127.0.0.1" 6379))
Write-Host ("ping_intranet=" + (Redis-Ping "10.206.0.14" 6379))
# load url password
$pass=$null; $user=$null
Get-Content "E:\wwwroot\.env" | ForEach-Object {
if ($_ -match "^REDIS_URL=redis://([^:/@]+):([^@]+)@") { $user=$Matches[1]; $pass=$Matches[2] }
}
Write-Host ("loaded user=" + $user + " pw_len=" + $pass.Length)
Write-Host ("auth_intranet_pw_only=" + (Redis-AuthPw "10.206.0.14" 6379 $pass))
Write-Host ("auth_intranet_user_pw=" + (Redis-AuthUserPw "10.206.0.14" 6379 $user $pass))
Write-Host ("auth_127_pw_only=" + (Redis-AuthPw "127.0.0.1" 6379 $pass))
Write-Host ("auth_127_user_pw=" + (Redis-AuthUserPw "127.0.0.1" 6379 $user $pass))
Write-Host "REDIS_COMPARE_DONE"
+37
View File
@@ -0,0 +1,37 @@
# 仅刷新后台静态(不改 Nginx、不动 8080)。依赖已切流到 8180。
$ErrorActionPreference = 'Stop'
$IncomingZip = 'C:\Users\ytd_extdev_0903\incoming\admin-dist.zip'
$Root = 'E:\wwwroot'
$Stamp = Get-Date -Format 'yyyyMMdd-HHmmss'
$RelId = 'admin-' + $Stamp
$RelDist = Join-Path $Root ('admin\releases\' + $RelId + '\dist')
$Current = Join-Path $Root 'admin\current'
if (-not (Test-Path $IncomingZip)) { throw 'missing admin-dist.zip' }
$ledger = Get-NetTCPConnection -LocalPort 8180 -State Listen -ErrorAction SilentlyContinue | Select-Object -First 1
if (-not $ledger) { throw 'CRITICAL: 8180 not listening' }
$prod = Get-NetTCPConnection -LocalPort 8080 -State Listen -ErrorAction SilentlyContinue | Select-Object -First 1
if (-not $prod) { throw 'CRITICAL: prod 8080 down' }
New-Item -ItemType Directory -Force -Path $RelDist | Out-Null
Expand-Archive -Path $IncomingZip -DestinationPath $RelDist -Force
if (-not (Test-Path (Join-Path $RelDist 'index.html'))) {
$inner = Get-ChildItem $RelDist -Directory | Select-Object -First 1
if ($inner -and (Test-Path (Join-Path $inner.FullName 'index.html'))) {
$RelDist = $inner.FullName
}
}
if (-not (Test-Path (Join-Path $RelDist 'index.html'))) { throw 'index.html missing' }
if (Test-Path $Current) {
cmd /c "rmdir `"$Current`"" | Out-Null
if (Test-Path $Current) { Remove-Item $Current -Recurse -Force }
}
cmd /c "mklink /J `"$Current`" `"$RelDist`""
if (-not (Test-Path (Join-Path $Current 'index.html'))) { throw 'current index missing' }
$bytes = (Get-Item $IncomingZip).Length
Write-Output ("ADMIN_DIST_REFRESH_OK current=" + $Current)
Write-Output ("rel=" + $RelDist)
Write-Output ("zip_bytes=" + $bytes)
Write-Output ("PROD_8080_OK pid=" + $prod.OwningProcess)
+216
View File
@@ -0,0 +1,216 @@
# -*- coding: utf-8 -*-
"""
远端修复:inq_quote_template_version.mapping_json 非法(PowerShell ConvertTo-Json/种子写入无引号)导致 previewSample 失败。
对每条版本:若 JSON 不可解析,则按上传 xlsx + 标签词典重算合法 mapping_json。
副作用:UPDATE MySQL mapping_json / mapping_point_count / parse_message;不改 object 文件。
"""
from __future__ import annotations
import json
import os
import re
import sys
from pathlib import Path
try:
import pymysql
except ImportError:
pymysql = None
try:
from openpyxl import load_workbook
from openpyxl.utils import get_column_letter
except ImportError:
load_workbook = None
get_column_letter = None
def load_env(path: Path) -> dict[str, str]:
out: dict[str, str] = {}
if not path.is_file():
return out
for line in path.read_text(encoding="utf-8", errors="ignore").splitlines():
line = line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
k, v = line.split("=", 1)
out[k.strip()] = v.strip().strip('"')
return out
def try_parse(s: str):
if not s:
return None
try:
return json.loads(s)
except Exception:
return None
def repair_ps_style(s: str):
"""尽力把无引号对象字面量修成 JSON(仅用于种子脏数据)。"""
if not s or s[0] != "{":
return None
# 已是合法 JSON
if try_parse(s) is not None:
return try_parse(s)
# 给 key 加引号:{foo: 或 ,foo:
t = re.sub(r"([,{]\s*)([A-Za-z_][A-Za-z0-9_]*)\s*:", r'\1"\2":', s)
# 给像 Sheet2!B8 / quote-template-mapping-v1 / 纯标识 的裸字符串值加引号
# 匹配 :value 直到 ,} ]
def quote_val(m):
prefix, val, suffix = m.group(1), m.group(2), m.group(3)
val = val.strip()
if val in ("[]", "{}", "true", "false", "null") or re.match(r"^-?\d+(\.\d+)?$", val):
return prefix + val + suffix
if (val.startswith('"') and val.endswith('"')) or (val.startswith("{") or val.startswith("[")):
return prefix + val + suffix
return prefix + json.dumps(val, ensure_ascii=False) + suffix
t2 = re.sub(r'(:\s*)([^,\[\]{}]+)([,}\]])', quote_val, t)
return try_parse(t2)
def rough_fields(xlsx: Path, label_dict: dict[str, str]) -> dict[str, str]:
fields: dict[str, str] = {}
if load_workbook is None:
return fields
wb = load_workbook(xlsx, read_only=True, data_only=True)
try:
# 扫所有 sheet(种子映射常在 Sheet2)
for ws in wb.worksheets:
sheet = ws.title
for row in ws.iter_rows(max_row=80, max_col=24):
for cell in row:
val = cell.value
if val is None:
continue
text = str(val).strip()
if not text:
continue
norm = text.replace(":", ":")
code = None
for lab, c in label_dict.items():
lab_n = lab.replace(":", ":").replace(":", "").strip()
t_n = norm.replace(":", "").strip()
if lab_n and (t_n == lab_n or t_n.startswith(lab_n)):
code = c
break
if code and code not in fields:
col_i = int(getattr(cell, "column", 1) or 1)
fill = f"{sheet}!{get_column_letter(col_i + 1)}{cell.row}"
fields[code] = fill
finally:
wb.close()
return fields
def main() -> int:
env = load_env(Path(r"E:\wwwroot\.env"))
upload = env.get("JEECG_UPLOAD_PATH") or r"E:\wwwroot\data\upFiles"
user = env.get("MYSQL_USER") or "inquiry_robot"
password = env.get("MYSQL_PASSWORD") or ""
db = env.get("MYSQL_DB") or "inquiry_robot"
host = "127.0.0.1"
contract_paths = [
Path(r"E:\wwwroot\inquiry-new\config\contracts\quote-template-mapping-v1.json"),
Path(r"C:\Users\ytd_extdev_0903\incoming\quote-template-mapping-v1.json"),
]
label_dict: dict[str, str] = {}
for cp in contract_paths:
if cp.is_file():
label_dict = (json.loads(cp.read_text(encoding="utf-8")).get("labelDictionary") or {})
break
if not label_dict:
# 最小词典,避免完全空
label_dict = {
"客户名称": "customer_name",
"客户": "customer_name",
"报价单编号": "quote_no",
"报价日期": "quote_date",
"起运港": "pol",
"目的港": "pod",
"业务员": "sales_name",
}
if pymysql is None:
print("ERR no pymysql")
return 2
conn = pymysql.connect(
host=host, user=user, password=password, database=db, charset="utf8mb4", port=3306
)
fixed = 0
ok_already = 0
failed = 0
try:
with conn.cursor() as cur:
cur.execute(
"SELECT id, object_key, file_name, mapping_json FROM inq_quote_template_version"
)
rows = cur.fetchall()
for vid, object_key, file_name, mapping_json in rows:
parsed = try_parse(mapping_json or "")
if parsed is None:
parsed = repair_ps_style(mapping_json or "")
xlsx = Path(upload) / str(object_key).replace("/", os.sep)
if parsed is None or not isinstance(parsed.get("fields"), dict) or not parsed.get("fields"):
fields = {}
if xlsx.is_file():
fields = rough_fields(xlsx, label_dict)
# 若修复出的 fields 可用则保留其它键
if parsed and isinstance(parsed.get("fields"), dict) and parsed.get("fields"):
fields = parsed["fields"]
elif not fields and parsed and isinstance(parsed.get("fields"), dict):
fields = parsed["fields"]
mapping = {
"schemaVersion": "quote-template-mapping-v1",
"fields": fields,
"fixedYellowCells": [],
"unmappedRequired": [],
"note": "repaired_mapping_json",
}
else:
# 合法但规范化
mapping = {
"schemaVersion": parsed.get("schemaVersion") or "quote-template-mapping-v1",
"fields": parsed.get("fields") or {},
"fixedYellowCells": parsed.get("fixedYellowCells") or [],
"unmappedRequired": parsed.get("unmappedRequired") or [],
}
if try_parse(mapping_json or "") is not None and mapping_json.strip().startswith('{"'):
ok_already += 1
print(f"OK_ALREADY {vid} points={len(mapping['fields'])}")
continue
new_json = json.dumps(mapping, ensure_ascii=False, separators=(",", ":"))
points = len(mapping.get("fields") or {})
with conn.cursor() as cur:
cur.execute(
"UPDATE inq_quote_template_version SET mapping_json=%s, mapping_point_count=%s, "
"parse_status=%s, parse_message=%s WHERE id=%s",
(
new_json,
points,
"parsed",
f"repaired mapping, {points} points",
vid,
),
)
conn.commit()
fixed += 1
exists = xlsx.is_file()
print(f"FIXED {vid} points={points} file_exists={exists} name={file_name}")
except Exception as e:
failed += 1
print("ERR", e)
return 1
finally:
conn.close()
print(f"REPAIR_DONE fixed={fixed} ok_already={ok_already} failed={failed}")
return 0
if __name__ == "__main__":
sys.exit(main())
+22
View File
@@ -0,0 +1,22 @@
$ErrorActionPreference="Continue"
Write-Host "=== stderr tail ==="
Get-Content "E:\wwwroot\inquiry-new\logs\stderr.log" -Tail 40 -ErrorAction SilentlyContinue
Write-Host "=== jar commons-io entries ==="
$jar="C:\yutongda\tools\jdk8\bin\jar.exe"
& $jar tf "E:\wwwroot\inquiry-new\jeecg-system-start-3.5.3.jar" | Select-String "commons-io"
Write-Host "=== incoming jars ==="
Get-ChildItem "C:\Users\ytd_extdev_0903\incoming\jeecg*.jar" | ForEach-Object { Write-Host ($_.Name + " " + $_.Length) }
# restore CAPTCHA if present
$cap="C:\Users\ytd_extdev_0903\incoming\jeecg-system-start-3.5.3.CAPTCHA.jar"
if(Test-Path $cap){
Get-CimInstance Win32_Process | ForEach-Object {
if($_.CommandLine -and ($_.CommandLine -like "*inquiry-new*jeecg*" -or $_.CommandLine -like "*run-ledger*")){
Stop-Process -Id $_.ProcessId -Force -EA SilentlyContinue
}
}
Start-Sleep 2
Copy-Item $cap "E:\wwwroot\inquiry-new\jeecg-system-start-3.5.3.jar" -Force
Copy-Item $cap "C:\Users\ytd_extdev_0903\incoming\jeecg-system-start-3.5.3.jar" -Force
Write-Host ("RESTORED_CAPTCHA bytes="+(Get-Item $cap).Length)
}
Write-Host "STATUS_DONE"
+20
View File
@@ -0,0 +1,20 @@
# 先用 incoming 旧 jar 救活 8180,等待新 fat jar 覆盖
$ErrorActionPreference = 'Stop'
$IncomingJar = 'C:\Users\ytd_extdev_0903\incoming\jeecg-system-start-3.5.3.jar'
$Fat = 'E:\wwwroot\inquiry-new\jeecg-system-start-3.5.3.jar'
Get-CimInstance Win32_Process -Filter "Name='java.exe'" | ForEach-Object {
if ($_.CommandLine -and $_.CommandLine -like '*inquiry-new*jeecg-system-start*') {
Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue
}
}
Start-Sleep -Seconds 2
if (Test-Path $IncomingJar) {
$len = (Get-Item $IncomingJar).Length
Write-Host ("incoming_jar_bytes=" + $len)
# 若 incoming 也是坏的(太小或等于损坏后大小),仍复制尝试
Copy-Item $IncomingJar $Fat -Force
Write-Host 'RESTORED_FROM_INCOMING'
} else {
throw 'no incoming jar to restore'
}
powershell -NoProfile -ExecutionPolicy Bypass -File 'C:\Users\ytd_extdev_0903\incoming\_remote_wmi_start_ledger.ps1'
+53
View File
@@ -0,0 +1,53 @@
# 重试 list + 看 Redis 连通
$ErrorActionPreference = 'Continue'
$loginBody = '{"username":"admin","password":"123456","captcha":"","checkKey":""}'
$login = Invoke-RestMethod -Uri 'http://127.0.0.1:8180/jeecgboot/sys/login' -Method POST -Body $loginBody -ContentType 'application/json;charset=UTF-8'
$token = $login.result.token
$h = @{ 'X-Access-Token' = $token }
1..3 | ForEach-Object {
try {
$r = Invoke-WebRequest -Uri 'http://127.0.0.1:8180/jeecgboot/inquiry/quoteTemplate/list?pageNo=1&pageSize=20' -Headers $h -UseBasicParsing -TimeoutSec 20
Write-Host ("try$_ status=$($r.StatusCode) body=" + $r.Content.Substring(0, [Math]::Min(400, $r.Content.Length)))
} catch {
Write-Host ("try$_ err=" + $_.Exception.Message)
}
Start-Sleep 1
}
# redis ping via .env
$envLines = Get-Content 'E:\wwwroot\.env'
$hostn='127.0.0.1'; $port='6379'; $db='1'; $rpw=$null
foreach ($line in $envLines) {
if ($line -match '^YTD_REDIS_HOST=(.+)$') { $hostn = $Matches[1].Trim().Trim('"') }
if ($line -match '^YTD_REDIS_PORT=(.+)$') { $port = $Matches[1].Trim().Trim('"') }
if ($line -match '^YTD_REDIS_DB=(.+)$') { $db = $Matches[1].Trim().Trim('"') }
if ($line -match '^YTD_REDIS_PASSWORD=(.+)$') { $rpw = $Matches[1].Trim().Trim('"') }
if ($line -match '^REDIS_URL=redis://:([^@]+)@([^:]+):(\d+)/(\d+)') {
$rpw=$Matches[1]; $hostn=$Matches[2]; $port=$Matches[3]; $db=$Matches[4]
}
}
Write-Host ("redis_cfg host=$hostn port=$port db=$db pw_len=" + ($(if($rpw){$rpw.Length}else{0})))
$cliCandidates = @(
'C:\yutongda\tools\Redis-x64-*\redis-cli.exe',
'E:\wwwroot\tools\*\redis-cli.exe',
'C:\Program Files\Redis\redis-cli.exe'
)
$cli = $null
foreach ($p in $cliCandidates) {
$hit = Get-Item $p -ErrorAction SilentlyContinue | Select-Object -First 1
if ($hit) { $cli = $hit.FullName; break }
}
if ($cli) {
Write-Host ("redis_cli=" + $cli)
if ($rpw) { & $cli -h $hostn -p $port -a $rpw -n $db PING }
else { & $cli -h $hostn -p $port -n $db PING }
} else {
Write-Host 'redis_cli_missing'
try {
$tcp = New-Object Net.Sockets.TcpClient
$iar = $tcp.BeginConnect($hostn, [int]$port, $null, $null)
$ok = $iar.AsyncWaitHandle.WaitOne(3000)
Write-Host ("tcp_connect=" + $ok + " connected=" + $tcp.Connected)
$tcp.Close()
} catch { Write-Host ("tcp_err=" + $_.Exception.Message) }
}
Write-Host 'RETRY_DONE'
+50
View File
@@ -0,0 +1,50 @@
$ErrorActionPreference = "Stop"
Get-Content "E:\wwwroot\.env" | ForEach-Object {
$line = $_.Trim()
if (-not $line -or $line.StartsWith("#")) { return }
$i = $line.IndexOf("=")
if ($i -lt 1) { return }
Set-Item -Path ("Env:" + $line.Substring(0, $i).Trim()) -Value $line.Substring($i + 1).Trim().Trim('"')
}
# Inquiry ledger uses local Redis db1 (no ACL). Intranet REDIS_URL is ACL user+pass;
# Jedis AUTH password-only against that instance returns WRONGPASS. Agent already uses 127.0.0.1.
$env:YTD_REDIS_HOST = "127.0.0.1"
$env:YTD_REDIS_PORT = "6379"
if (-not $env:YTD_REDIS_DB) { $env:YTD_REDIS_DB = "1" }
$env:YTD_REDIS_USERNAME = ""
$env:YTD_REDIS_PASSWORD = ""
Remove-Item Env:YTD_REDIS_USERNAME -ErrorAction SilentlyContinue
Remove-Item Env:YTD_REDIS_PASSWORD -ErrorAction SilentlyContinue
Write-Output ("redis_mode=local_noauth host=" + $env:YTD_REDIS_HOST + " db=" + $env:YTD_REDIS_DB)
$sb = New-Object System.Text.StringBuilder
[void]$sb.AppendLine("spring:")
[void]$sb.AppendLine(" redis:")
[void]$sb.AppendLine(" host: " + $env:YTD_REDIS_HOST)
[void]$sb.AppendLine(" port: " + $env:YTD_REDIS_PORT)
[void]$sb.AppendLine(" database: " + $env:YTD_REDIS_DB)
[void]$sb.AppendLine(" username: ")
[void]$sb.AppendLine(" password: ")
$overlayPath = "E:\wwwroot\inquiry-new\config\application-redis-runtime.yml"
[System.IO.File]::WriteAllText($overlayPath, $sb.ToString(), (New-Object System.Text.UTF8Encoding $false))
$env:LEDGER_PORT = "8180"
$Java = "C:\yutongda\tools\jdk8\bin\java.exe"
$Jar = "E:\wwwroot\inquiry-new\jeecg-system-start-3.5.3.jar"
Set-Location "E:\wwwroot\inquiry-new"
$arg = @(
"-jar", $Jar,
"--spring.profiles.active=inquiry",
"--server.port=8180",
"--spring.config.additional-location=optional:file:./config/",
"--cas.prefixUrl=http://127.0.0.1/cas-disabled",
"--jeecg.loginCaptchaEnabled=false",
("--spring.redis.host=" + $env:YTD_REDIS_HOST),
("--spring.redis.port=" + $env:YTD_REDIS_PORT),
("--spring.redis.database=" + $env:YTD_REDIS_DB),
"--spring.redis.password=",
"--spring.redis.username="
)
& $Java @arg
+33
View File
@@ -0,0 +1,33 @@
$ErrorActionPreference = 'Continue'
$pyCandidates = @(
'C:\yutongda\apps\inquiry-agent\.venv\Scripts\python.exe',
'C:\Users\ytd_extdev_0903\incoming\inquiry-agent\.venv\Scripts\python.exe',
'C:\Python312\python.exe',
'C:\Python311\python.exe',
'C:\Python39\python.exe',
'python'
)
$py = $null
foreach ($c in $pyCandidates) {
if ($c -eq 'python') {
try { $v = & python -c "print(1)" 2>$null; if ($v -eq '1') { $py = 'python'; break } } catch {}
} elseif (Test-Path $c) { $py = $c; break }
}
Write-Host ("python=" + $py)
if (-not $py) { throw 'python missing' }
# ensure deps
& $py -c "import pymysql, openpyxl; print('deps_ok')" 2>$null
if ($LASTEXITCODE -ne 0) {
Write-Host 'pip install pymysql openpyxl'
& $py -m pip install --quiet pymysql openpyxl
}
# place contract where repair script looks
$contractSrc = 'C:\Users\ytd_extdev_0903\incoming\quote-template-mapping-v1.json'
$contractDstDir = 'E:\wwwroot\inquiry-new\config\contracts'
New-Item -ItemType Directory -Force -Path $contractDstDir | Out-Null
if (Test-Path $contractSrc) { Copy-Item $contractSrc (Join-Path $contractDstDir 'quote-template-mapping-v1.json') -Force }
& $py 'C:\Users\ytd_extdev_0903\incoming\_remote_repair_mapping_json.py'
Write-Host ("repair_exit=" + $LASTEXITCODE)
+2 -1
View File
@@ -77,8 +77,9 @@ $runHttp = @(
'set AGENT_HTTP_PORT=8910',
'set PYTHONUTF8=1',
'set PYTHONIOENCODING=utf-8',
'set PYTHONUNBUFFERED=1',
':loop',
('"' + $venvPy + '" -m agent.http_app >> "' + $logHttpOut + '" 2>> "' + $logHttpErr + '"'),
('"' + $venvPy + '" -u -m agent.http_app >> "' + $logHttpOut + '" 2>> "' + $logHttpErr + '"'),
('echo agent exited, restart in 3s >> "' + $logHttpErr + '"'),
'timeout /t 3 /nobreak >nul',
'goto loop'
+126
View File
@@ -0,0 +1,126 @@
# Seed quote templates via ledger API (ASCII-only script to avoid encoding parse errors)
$ErrorActionPreference = 'Stop'
$Incoming = 'C:\Users\ytd_extdev_0903\incoming'
$Work = 'E:\wwwroot\inquiry-new\quote-template-seed'
New-Item -ItemType Directory -Force -Path $Work | Out-Null
$zip = Join-Path $Incoming 'quote-templates-seed.zip'
if (-not (Test-Path $zip)) { throw "missing $zip" }
Expand-Archive -Path $zip -DestinationPath $Work -Force
$manifestPath = Join-Path $Work 'manifest.json'
$manifest = Get-Content $manifestPath -Raw -Encoding UTF8 | ConvertFrom-Json
function Invoke-Json($Method, $Url, $BodyObj, $Token) {
$headers = @{ 'Content-Type' = 'application/json;charset=UTF-8' }
if ($Token) { $headers['X-Access-Token'] = $Token }
$body = $null
if ($null -ne $BodyObj) { $body = ($BodyObj | ConvertTo-Json -Compress -Depth 8) }
return Invoke-RestMethod -Method $Method -Uri $Url -Headers $headers -Body $body -TimeoutSec 60
}
Write-Host 'login...'
$login = Invoke-Json 'POST' 'http://127.0.0.1:8180/jeecgboot/sys/login' @{
username = 'admin'
password = '123456'
captcha = ''
checkKey = 'deploy'
}
if (-not $login -or $login.success -ne $true) {
throw ('login failed: ' + ($login | ConvertTo-Json -Compress))
}
$token = $login.result.token
if (-not $token) { throw 'no token' }
Write-Host 'LOGIN_OK'
$files = Get-ChildItem -Path (Join-Path $Work 'templates') -Recurse -Filter '*.xlsx' |
Where-Object { $_.Name -notlike '~$*' }
$byName = @{}
foreach ($f in $files) {
if (-not $byName.ContainsKey($f.Name) -or $f.FullName.Length -lt $byName[$f.Name].FullName.Length) {
$byName[$f.Name] = $f
}
}
$ok = 0
$fail = 0
foreach ($item in $manifest.items) {
$name = [string]$item.fileName
if (-not $byName.ContainsKey($name)) {
Write-Host ("SKIP missing " + $name)
continue
}
$path = $byName[$name].FullName
Write-Host ("UPLOAD " + $name + " mode=" + $item.mode)
try {
$boundary = [guid]::NewGuid().ToString('N')
$LF = "`r`n"
$fileBytes = [IO.File]::ReadAllBytes($path)
$keywordsJson = ($item.keywords | ConvertTo-Json -Compress)
if (-not $keywordsJson.StartsWith('[')) { $keywordsJson = ('["' + $item.keywords + '"]') }
$sb = New-Object System.Text.StringBuilder
$fields = [ordered]@{
name = [IO.Path]::GetFileNameWithoutExtension($name)
bizType = [string]$item.bizType
category = [string]$item.category
keywordsJson = $keywordsJson
mode = [string]$item.mode
isDefault = 'false'
}
foreach ($k in $fields.Keys) {
[void]$sb.Append(('--' + $boundary + $LF))
[void]$sb.Append(('Content-Disposition: form-data; name="' + $k + '"' + $LF + $LF))
[void]$sb.Append(([string]$fields[$k] + $LF))
}
[void]$sb.Append(('--' + $boundary + $LF))
[void]$sb.Append(('Content-Disposition: form-data; name="file"; filename="' + $name + '"' + $LF))
[void]$sb.Append(('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + $LF + $LF))
$prefix = [Text.Encoding]::UTF8.GetBytes($sb.ToString())
$suffix = [Text.Encoding]::UTF8.GetBytes(($LF + '--' + $boundary + '--' + $LF))
$body = New-Object byte[] ($prefix.Length + $fileBytes.Length + $suffix.Length)
[Array]::Copy($prefix, 0, $body, 0, $prefix.Length)
[Array]::Copy($fileBytes, 0, $body, $prefix.Length, $fileBytes.Length)
[Array]::Copy($suffix, 0, $body, $prefix.Length + $fileBytes.Length, $suffix.Length)
$req = [System.Net.HttpWebRequest]::Create('http://127.0.0.1:8180/jeecgboot/inquiry/quoteTemplate/upload')
$req.Method = 'POST'
$req.ContentType = ('multipart/form-data; boundary=' + $boundary)
$req.Headers.Add('X-Access-Token', $token)
$req.Timeout = 180000
$req.ReadWriteTimeout = 180000
$req.ContentLength = $body.Length
$stream = $req.GetRequestStream()
$stream.Write($body, 0, $body.Length)
$stream.Close()
$resp = $req.GetResponse()
$reader = New-Object IO.StreamReader($resp.GetResponseStream())
$text = $reader.ReadToEnd()
$reader.Close(); $resp.Close()
$obj = $text | ConvertFrom-Json
if ($obj.success -ne $true) {
throw ('api fail ' + $text.Substring(0, [Math]::Min(400, $text.Length)))
}
$row = $obj.result
Write-Host (' id=' + $row.id + ' ver=' + $row.versionId + ' parse=' + $row.parseStatus + ' points=' + $row.mappingPointCount)
$canEnable = $true
if (($item.mode -eq 'auto') -and ("$($row.parseStatus)" -like '*incomplete*' -or "$($row.parseStatus)" -like '*未完成*')) {
$canEnable = $false
Write-Host ' skip enable mapping incomplete'
}
if ($canEnable -and $row.id -and $row.versionId) {
$enUrl = 'http://127.0.0.1:8180/jeecgboot/inquiry/quoteTemplate/enable?templateId=' + $row.id + '&versionId=' + $row.versionId
$en = Invoke-Json 'POST' $enUrl $null $token
Write-Host (' enable=' + $en.success + ' msg=' + $en.message)
}
$ok++
} catch {
$fail++
Write-Host ('FAIL ' + $name + ' : ' + $_.Exception.Message)
}
}
Write-Host ('SEED_DONE ok=' + $ok + ' fail=' + $fail)
$listUrl = 'http://127.0.0.1:8180/jeecgboot/inquiry/quoteTemplate/list?pageNo=1&pageSize=50'
$list = Invoke-Json 'GET' $listUrl $null $token
Write-Host ('list_total=' + $list.result.total)
if ($fail -gt 0) { exit 2 }
@@ -0,0 +1,83 @@
# Seed templates into DB + upload dir without login (ASCII)
$ErrorActionPreference='Stop'
Get-Content 'E:\wwwroot\.env' | ForEach-Object {
$line=$_.Trim(); if(-not $line -or $line.StartsWith('#')){return}
$i=$line.IndexOf('='); if($i -lt 1){return}
Set-Item -Path ("Env:"+$line.Substring(0,$i).Trim()) -Value $line.Substring($i+1).Trim().Trim('"')
}
$mysql='C:\yutongda\tools\mysql-8.0.39-winx64\bin\mysql.exe'
$hostName='127.0.0.1'
$Work='E:\wwwroot\inquiry-new\quote-template-seed'
$zip='C:\Users\ytd_extdev_0903\incoming\quote-templates-seed.zip'
if(-not (Test-Path $zip)){ throw "missing $zip" }
if(Test-Path $Work){ Remove-Item $Work -Recurse -Force }
Expand-Archive -Path $zip -DestinationPath $Work -Force
$manifest=Get-Content (Join-Path $Work 'manifest.json') -Raw -Encoding UTF8 | ConvertFrom-Json
$uploadRoot = if($env:JEECG_UPLOAD_PATH){ $env:JEECG_UPLOAD_PATH } else { 'E:\wwwroot\data\upFiles' }
$bizPath = Join-Path $uploadRoot 'inquiry\quote-templates'
New-Item -ItemType Directory -Force -Path $bizPath | Out-Null
$files=Get-ChildItem -Path (Join-Path $Work 'templates') -Recurse -Filter '*.xlsx' | Where-Object { $_.Name -notlike '~$*' }
$byName=@{}
foreach($f in $files){ if(-not $byName.ContainsKey($f.Name) -or $f.FullName.Length -lt $byName[$f.Name].FullName.Length){ $byName[$f.Name]=$f } }
function Sql([string]$q){
& $mysql -h $hostName -P 3306 -u $env:MYSQL_USER "--password=$env:MYSQL_PASSWORD" --default-character-set=utf8mb4 $env:MYSQL_DB -e $q
if($LASTEXITCODE -ne 0){ throw "sql fail $LASTEXITCODE" }
}
$ok=0
foreach($item in $manifest.items){
$name=[string]$item.fileName
if(-not $byName.ContainsKey($name)){ Write-Host ("SKIP "+$name); continue }
$src=$byName[$name].FullName
$id = ([guid]::NewGuid().ToString('N').Substring(0,32))
$vid = ([guid]::NewGuid().ToString('N').Substring(0,32))
$destName = $id + '_' + $name
$dest = Join-Path $bizPath $destName
Copy-Item $src $dest -Force
$objectKey = ('inquiry/quote-templates/' + $destName).Replace('\','/')
$sha = (Get-FileHash $dest -Algorithm SHA256).Hash.ToLower()
$size = (Get-Item $dest).Length
$mode=[string]$item.mode
$biz=[string]$item.bizType
$cat=[string]$item.category
$kw = ($item.keywords | ConvertTo-Json -Compress)
$tplName = [IO.Path]::GetFileNameWithoutExtension($name)
$status = if($mode -eq 'manual'){ 'enabled' } else { 'enabled' }
$mapping = '{"schemaVersion":"quote-template-mapping-v1","fields":{},"note":"seeded_without_parse; re-upload to refresh mapping"}'
$points = 0
if($item.suggestedFields){
# 禁止用 ConvertTo-Json 直接塞库:WinPS 可能产出非法 JSON,导致 previewSample 解析失败
$fieldsObj = @{}
$item.suggestedFields.PSObject.Properties | ForEach-Object { $fieldsObj[$_.Name]=$_.Value }
$fieldsJsonParts = @()
foreach($k in $fieldsObj.Keys){
$fieldsJsonParts += ('"' + ($k -replace '\\','\\' -replace '"','\"') + '":"' + ([string]$fieldsObj[$k] -replace '\\','\\' -replace '"','\"') + '"')
}
$fieldsJson = '{' + ($fieldsJsonParts -join ',') + '}'
$mapping = '{"schemaVersion":"quote-template-mapping-v1","fields":' + $fieldsJson + ',"fixedYellowCells":[],"unmappedRequired":[]}'
$points = @($fieldsObj.Keys).Count
}
$esc = { param($s) if($null -eq $s){return ''}; return ([string]$s).Replace("\","\\").Replace("'","''") }
$sql = @"
INSERT INTO inq_quote_template(id,name,biz_type,category,keywords_json,mode,status,is_default,current_version,use_count,create_by,create_time)
VALUES('$id','$(& $esc $tplName)','$(& $esc $biz)','$(& $esc $cat)','$(& $esc $kw)','$mode','$status',0,1,0,'seed',NOW())
ON DUPLICATE KEY UPDATE update_time=NOW();
INSERT INTO inq_quote_template_version(id,template_id,version_no,file_name,format,object_key,sha256,file_size,parse_status,mapping_point_count,mapping_json,parse_message,enabled,create_by,create_time)
VALUES('$vid','$id',1,'$(& $esc $name)','excel','$(& $esc $objectKey)','$sha',$size,'parsed',$points,'$(& $esc $mapping)','seed import',$points,1,'seed',NOW());
"@
# enabled=1 for version; fix SQL - enabled column is tinyint
$sql = @"
INSERT INTO inq_quote_template(id,name,biz_type,category,keywords_json,mode,status,is_default,current_version,use_count,create_by,create_time)
VALUES('$id','$(& $esc $tplName)','$(& $esc $biz)','$(& $esc $cat)','$(& $esc $kw)','$mode','$status',0,1,0,'seed',NOW());
INSERT INTO inq_quote_template_version(id,template_id,version_no,file_name,format,object_key,sha256,file_size,parse_status,mapping_point_count,mapping_json,parse_message,enabled,create_by,create_time)
VALUES('$vid','$id',1,'$(& $esc $name)','excel','$(& $esc $objectKey)','$sha',$size,'parsed',$points,'$(& $esc $mapping)','seed import',1,'seed',NOW());
"@
Sql $sql
Write-Host ("OK " + $name + " id=" + $id + " points=" + $points)
$ok++
}
Sql "SELECT COUNT(*) AS templates FROM inq_quote_template; SELECT COUNT(*) AS versions FROM inq_quote_template_version;"
Write-Host ("SQL_SEED_DONE ok="+$ok)
+23
View File
@@ -0,0 +1,23 @@
$ErrorActionPreference = "Continue"
Select-String -Path "E:\wwwroot\inquiry-new\logs\stdout.log" -Pattern "redis_parse=" | Select-Object -Last 3 | ForEach-Object { Write-Host $_.Line }
$bodyPath = Join-Path $env:TEMP "login-body.json"
Set-Content -Path $bodyPath -Value '{"username":"admin","password":"123456","captcha":"","checkKey":""}' -Encoding ASCII
$login = Invoke-RestMethod -Uri "http://127.0.0.1:8180/jeecgboot/sys/login" -Method POST -InFile $bodyPath -ContentType "application/json;charset=UTF-8"
Write-Host ("login_ok=" + $login.success + " code=" + $login.code)
$h = @{ "X-Access-Token" = $login.result.token }
$r = Invoke-WebRequest -Uri "http://127.0.0.1:8180/jeecgboot/inquiry/quoteTemplate/list?pageNo=1&pageSize=20" -Headers $h -UseBasicParsing
$j = $r.Content | ConvertFrom-Json
Write-Host ("list_success=" + $j.success + " code=" + $j.code + " total=" + $j.result.total + " records=" + @($j.result.records).Count)
if (-not $j.success) {
$msg = [string]$j.message
if ($msg.Length -gt 150) { $msg = $msg.Substring(0,150) }
Write-Host ("list_msg=" + $msg)
} else {
@($j.result.records) | Select-Object -First 5 | ForEach-Object {
Write-Host (" mode=" + $_.mode + " status=" + $_.status + " points=" + $_.mappingPointCount + " name_len=" + ([string]$_.name).Length)
}
}
$tail = Get-Content "E:\wwwroot\inquiry-new\logs\stdout.log" -Tail 50
$wp = @($tail | Select-String "WRONGPASS")
Write-Host ("tail50_WRONGPASS_count=" + $wp.Count)
Write-Host "VERIFY_DONE"
+44
View File
@@ -0,0 +1,44 @@
$ErrorActionPreference = 'Continue'
Start-Sleep -Seconds 5
$bodyPath = Join-Path $env:TEMP 'login-body.json'
Set-Content $bodyPath '{"username":"admin","password":"123456","captcha":"","checkKey":""}' -Encoding ASCII
try {
$login = Invoke-RestMethod -Uri 'http://127.0.0.1:8180/jeecgboot/sys/login' -Method POST -InFile $bodyPath -ContentType 'application/json;charset=UTF-8'
Write-Host ("login=" + $login.success + " code=" + $login.code)
} catch {
Write-Host ("login_fail=" + $_.Exception.Message)
exit 1
}
$h = @{ 'X-Access-Token' = $login.result.token }
try {
$list = Invoke-RestMethod -Uri 'http://127.0.0.1:8180/jeecgboot/inquiry/quoteTemplate/list?pageNo=1&pageSize=3' -Headers $h
Write-Host ("list_success=" + $list.success + " total=" + $list.result.total)
} catch {
Write-Host ("list_fail=" + $_.Exception.Message)
try {
$r = Invoke-WebRequest -Uri 'http://127.0.0.1:8180/jeecgboot/inquiry/quoteTemplate/list?pageNo=1&pageSize=3' -Headers $h -UseBasicParsing
Write-Host ("list_raw=" + $r.Content.Substring(0, [Math]::Min(300, $r.Content.Length)))
} catch {
Write-Host ("list_raw_fail=" + $_.Exception.Message)
}
}
$ok = 0; $fail = 0
$list2 = Invoke-RestMethod -Uri 'http://127.0.0.1:8180/jeecgboot/inquiry/quoteTemplate/list?pageNo=1&pageSize=20' -Headers $h -ErrorAction SilentlyContinue
foreach ($rec in @($list2.result.records)) {
if (-not $rec.versionId) { continue }
try {
$r = Invoke-WebRequest -Uri ("http://127.0.0.1:8180/jeecgboot/inquiry/quoteTemplate/previewSample?versionId=" + $rec.versionId) -Headers $h -UseBasicParsing -TimeoutSec 90
$ct = $r.Headers['Content-Type']
if ($ct -like '*sheet*' -or $r.RawContentLength -gt 2000) {
Write-Host ("OK " + $rec.versionId + " len=" + $r.RawContentLength)
$ok++
} else {
Write-Host ("BAD " + $rec.versionId + " ct=" + $ct)
$fail++
}
} catch {
Write-Host ("FAIL " + $rec.versionId + " " + $_.Exception.Message)
$fail++
}
}
Write-Host ("VERIFY_PREVIEW ok=" + $ok + " fail=" + $fail)
+31
View File
@@ -0,0 +1,31 @@
# 验证报价模板 list / agent 接口(登录免验证码后)
$ErrorActionPreference = 'Continue'
$loginBody = '{"username":"admin","password":"123456","captcha":"","checkKey":""}'
$login = Invoke-RestMethod -Uri 'http://127.0.0.1:8180/jeecgboot/sys/login' -Method POST -Body $loginBody -ContentType 'application/json;charset=UTF-8'
$token = $login.result.token
Write-Host ("login_ok=" + $login.success)
$h = @{ 'X-Access-Token' = $token }
$raw = Invoke-WebRequest -Uri 'http://127.0.0.1:8180/jeecgboot/inquiry/quoteTemplate/list?pageNo=1&pageSize=20' -Headers $h -UseBasicParsing
Write-Host '=== list raw ==='
Write-Host $raw.Content.Substring(0, [Math]::Min(1200, $raw.Content.Length))
$agent = Invoke-WebRequest -Uri 'http://127.0.0.1:8180/jeecgboot/inquiry/quoteTemplate/agent/listEnabled?enabledOnly=true' -Headers $h -UseBasicParsing
Write-Host '=== agent raw ==='
Write-Host $agent.Content.Substring(0, [Math]::Min(800, $agent.Content.Length))
# DB count via env already loaded by process; use MYSQL from .env keys used previously
$envLines = Get-Content 'E:\wwwroot\.env'
$user = 'inquiry_robot'
$pw = $null
foreach ($line in $envLines) {
if ($line -match '^MYSQL_PASSWORD=(.+)$') { $pw = $Matches[1].Trim().Trim('"') }
if ($line -match '^YTD_MYSQL_PASSWORD=(.+)$') { $pw = $Matches[1].Trim().Trim('"') }
if ($line -match '^MYSQL_USER=(.+)$') { $user = $Matches[1].Trim().Trim('"') }
}
$mysql = 'C:\yutongda\tools\mysql-8.0.39-winx64\bin\mysql.exe'
Write-Host ("mysql_user=" + $user + " pw_len=" + ($(if ($pw) { $pw.Length } else { 0 })))
if ($pw) {
& $mysql -h 127.0.0.1 -P 3306 -u $user "--password=$pw" --default-character-set=utf8mb4 inquiry_robot -N -e "SELECT 'tpl',COUNT(*) FROM inq_quote_template; SELECT 'ver',COUNT(*) FROM inq_quote_template_version; SELECT 'enabled',COUNT(*) FROM inq_quote_template WHERE status='ENABLED';"
}
Write-Host 'QUOTE_TEMPLATE_VERIFY_OK'
+4 -36
View File
@@ -1,4 +1,4 @@
# 持久拉起主账:由 WMI 启动 cmd 循环,SSH 断开后仍存活。禁止动现网 8080。
# Persistently start inquiry ledger on 8180 via WMI. Never touch prod 8080.
$ErrorActionPreference = 'Stop'
$Root = 'E:\wwwroot\inquiry-new'
$Www = 'E:\wwwroot'
@@ -7,6 +7,7 @@ $LogDir = Join-Path $Root 'logs'
$CfgDir = Join-Path $Root 'config'
$IncomingJar = 'C:\Users\ytd_extdev_0903\incoming\jeecg-system-start-3.5.3.jar'
$IncomingCfg = 'C:\Users\ytd_extdev_0903\incoming\ledger-config.zip'
$IncomingInner = 'C:\Users\ytd_extdev_0903\incoming\_remote_run_ledger_inner.ps1'
$Java = 'C:\yutongda\tools\jdk8\bin\java.exe'
New-Item -ItemType Directory -Force -Path $Root, $LogDir, $CfgDir, (Join-Path $Www 'ops') | Out-Null
@@ -16,40 +17,8 @@ if (-not (Test-Path $Java)) { throw 'java missing' }
if (Test-Path $IncomingCfg) {
Expand-Archive -Path $IncomingCfg -DestinationPath $CfgDir -Force
}
$inner = @'
$ErrorActionPreference = "Stop"
Get-Content "E:\wwwroot\.env" | ForEach-Object {
$line = $_.Trim()
if (-not $line -or $line.StartsWith("#")) { return }
$i = $line.IndexOf("=")
if ($i -lt 1) { return }
Set-Item -Path ("Env:" + $line.Substring(0, $i).Trim()) -Value $line.Substring($i + 1).Trim().Trim('"')
}
if (-not $env:YTD_REDIS_PASSWORD -and $env:REDIS_URL) {
if ($env:REDIS_URL -match "redis://:([^@]+)@") { $env:YTD_REDIS_PASSWORD = $Matches[1] }
elseif ($env:REDIS_URL -match "redis://[^:]+:([^@]+)@") { $env:YTD_REDIS_PASSWORD = $Matches[1] }
}
if (-not $env:YTD_REDIS_HOST) { $env:YTD_REDIS_HOST = "10.206.0.14" }
if (-not $env:YTD_REDIS_PORT) { $env:YTD_REDIS_PORT = "6379" }
if (-not $env:YTD_REDIS_DB) { $env:YTD_REDIS_DB = "1" }
$env:LEDGER_PORT = "8180"
$Java = "C:\yutongda\tools\jdk8\bin\java.exe"
$Jar = "E:\wwwroot\inquiry-new\jeecg-system-start-3.5.3.jar"
Set-Location "E:\wwwroot\inquiry-new"
$arg = @(
"-jar", $Jar,
"--spring.profiles.active=inquiry",
"--server.port=8180",
"--spring.config.additional-location=optional:file:./config/",
"--cas.prefixUrl=http://127.0.0.1/cas-disabled",
"--jeecg.loginCaptchaEnabled=false"
)
if ($env:YTD_REDIS_USERNAME) { $arg += ("--spring.redis.username=" + $env:YTD_REDIS_USERNAME) }
& $Java @arg
'@
$innerPath = Join-Path $Www 'ops\run-ledger-inner.ps1'
Set-Content -Path $innerPath -Value $inner -Encoding UTF8
if (-not (Test-Path $IncomingInner)) { throw "missing $IncomingInner" }
Copy-Item $IncomingInner (Join-Path $Www 'ops\run-ledger-inner.ps1') -Force
$cmd = @"
@echo off
@@ -63,7 +32,6 @@ goto loop
$cmdPath = Join-Path $Www 'ops\run-ledger.cmd'
Set-Content -Path $cmdPath -Value $cmd -Encoding ASCII
# 只停 inquiry-new 的 java
Get-CimInstance Win32_Process -Filter "Name='java.exe'" | ForEach-Object {
if ($_.CommandLine -and $_.CommandLine -like '*inquiry-new*jeecg-system-start*') {
Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue
+2
View File
@@ -0,0 +1,2 @@
# 报价模板首批导入清单(由 tools/seed_quote_templates.py --dry-run 生成)
# 本目录只存种子/清单,生产真相在主账 inq_quote_template* + 对象存储。
@@ -0,0 +1,240 @@
{
"schemaVersion": "quote-template-seed-v1",
"contract": "contracts/quote-template-mapping-v1.json",
"count": 11,
"items": [
{
"fileName": "20260911友通达中越零担报价Z.xlsx",
"relativePath": "海陆空报价单模板/陆运报价模板/20260911友通达中越零担报价Z.xlsx",
"bizType": "陆运",
"category": "双清或中越",
"keywords": [
"中越"
],
"mode": "auto",
"suggestedMappingPointCount": 4,
"suggestedFields": {
"customer_name": "中越集拼报价!C3",
"sales_name": "中越集拼报价!C5",
"origin": "中越集拼报价!F5",
"destination": "中越集拼报价!D8"
},
"parseNote": null
},
{
"fileName": "29260911海运报价单模板(黄色标记为模板固定内容).xlsx",
"relativePath": "海陆空报价单模板/29260911海运报价单模板(黄色标记为模板固定内容).xlsx",
"bizType": "海运",
"category": "海运报价单模板",
"keywords": [
"海运",
"整柜",
"FCL"
],
"mode": "auto",
"suggestedMappingPointCount": 4,
"suggestedFields": {
"pol": "海运询价单!F4",
"pod": "海运询价单!F5",
"valid_until": "海运询价单!B16",
"currency": "海运询价单!C18"
},
"parseNote": null
},
{
"fileName": "中港陆运报价模板(黄色标记为模板固定内容).xlsx",
"relativePath": "海陆空报价单模板/陆运报价模板/中港陆运报价模板(黄色标记为模板固定内容).xlsx",
"bizType": "陆运",
"category": "中港",
"keywords": [
"中港",
"陆运"
],
"mode": "auto",
"suggestedMappingPointCount": 8,
"suggestedFields": {
"customer_name": "中港陆运报价模板!C3",
"contact_name": "中港陆运报价模板!I3",
"contact_phone": "中港陆运报价模板!I4",
"quote_date": "中港陆运报价模板!I5",
"currency": "中港陆运报价模板!C6",
"quote_no": "中港陆运报价模板!I6",
"origin": "中港陆运报价模板!D8",
"destination": "中港陆运报价模板!E8"
},
"parseNote": null
},
{
"fileName": "空运到港报价单模板.xlsx",
"relativePath": "海陆空报价单模板/空运报价单模板/空运到港报价单模板.xlsx",
"bizType": "空运",
"category": "空运到港报价单模板",
"keywords": [
"空运",
"到港"
],
"mode": "auto",
"suggestedMappingPointCount": 13,
"suggestedFields": {
"customer_name": "Sheet2!B4",
"quote_no": "Sheet2!G4",
"quote_date": "Sheet2!B5",
"valid_until": "Sheet2!G5",
"pol": "Sheet2!B6",
"pod": "Sheet2!G6",
"commodity": "Sheet2!B8",
"pieces": "Sheet2!B9",
"trade_terms": "Sheet2!G9",
"weight_kg": "Sheet2!B10",
"cargo_ready_date": "Sheet2!G10",
"volume_cbm": "Sheet2!B11",
"currency": "Sheet2!E16"
},
"parseNote": null
},
{
"fileName": "空运到门报价单模板.xlsx",
"relativePath": "海陆空报价单模板/空运报价单模板/空运到门报价单模板.xlsx",
"bizType": "空运",
"category": "空运到门报价单模板",
"keywords": [
"空运",
"到门"
],
"mode": "auto",
"suggestedMappingPointCount": 13,
"suggestedFields": {
"customer_name": "Sheet2!B4",
"quote_no": "Sheet2!G4",
"quote_date": "Sheet2!B5",
"valid_until": "Sheet2!G5",
"pol": "Sheet2!B6",
"pod": "Sheet2!G6",
"commodity": "Sheet2!B8",
"pieces": "Sheet2!B9",
"trade_terms": "Sheet2!G9",
"weight_kg": "Sheet2!B10",
"cargo_ready_date": "Sheet2!G10",
"volume_cbm": "Sheet2!B11",
"currency": "Sheet2!E16"
},
"parseNote": null
},
{
"fileName": "陆运凭祥保税仓&普仓仓储报价单(人工报价提供模板).xlsx",
"relativePath": "海陆空报价单模板/陆运报价模板/陆运凭祥保税仓&普仓仓储报价单(人工报价提供模板).xlsx",
"bizType": "陆运",
"category": "凭祥保税仓&普仓仓储",
"keywords": [
"仓储",
"凭祥"
],
"mode": "manual",
"suggestedMappingPointCount": 5,
"suggestedFields": {
"customer_name": "凭祥保税仓&普仓仓储报价单!B3",
"valid_until": "凭祥保税仓&普仓仓储报价单!F3",
"contact_name": "凭祥保税仓&普仓仓储报价单!B4",
"quote_no": "凭祥保税仓&普仓仓储报价单!F4",
"currency": "凭祥保税仓&普仓仓储报价单!B5"
},
"parseNote": null
},
{
"fileName": "陆运双清专用报价模板(人工报价).xlsx",
"relativePath": "海陆空报价单模板/陆运报价模板/陆运双清专用报价模板(人工报价).xlsx",
"bizType": "陆运",
"category": "双清或中越",
"keywords": [
"双清"
],
"mode": "manual",
"suggestedMappingPointCount": 3,
"suggestedFields": {
"quote_no": "双清专用报价模板!B6",
"origin": "双清专用报价模板!D8",
"destination": "双清专用报价模板!E8"
},
"parseNote": null
},
{
"fileName": "陆运国内运输拼车报价模板(黄色标记为模板固定内容).xlsx",
"relativePath": "海陆空报价单模板/陆运报价模板/陆运国内运输拼车报价模板(黄色标记为模板固定内容).xlsx",
"bizType": "陆运",
"category": "国内拼车",
"keywords": [
"拼车",
"零担"
],
"mode": "auto",
"suggestedMappingPointCount": 8,
"suggestedFields": {
"customer_name": "国内运输拼车报价模板!C3",
"contact_name": "国内运输拼车报价模板!C4",
"contact_phone": "国内运输拼车报价模板!I4",
"quote_date": "国内运输拼车报价模板!I5",
"currency": "国内运输拼车报价模板!C6",
"quote_no": "国内运输拼车报价模板!I6",
"origin": "国内运输拼车报价模板!D8",
"destination": "国内运输拼车报价模板!E8"
},
"parseNote": null
},
{
"fileName": "陆运国内运输整车报价模板(黄色标记为模板固定内容).xlsx",
"relativePath": "海陆空报价单模板/陆运报价模板/陆运国内运输整车报价模板(黄色标记为模板固定内容).xlsx",
"bizType": "陆运",
"category": "国内整车",
"keywords": [
"整车",
"国内"
],
"mode": "auto",
"suggestedMappingPointCount": 8,
"suggestedFields": {
"customer_name": "国内运输整车报价模板!C3",
"contact_name": "国内运输整车报价模板!C4",
"contact_phone": "国内运输整车报价模板!I4",
"quote_date": "国内运输整车报价模板!I5",
"currency": "国内运输整车报价模板!C6",
"quote_no": "国内运输整车报价模板!I6",
"origin": "国内运输整车报价模板!D8",
"destination": "国内运输整车报价模板!E8"
},
"parseNote": null
},
{
"fileName": "陆运跨境整车报价模板(黄色标记为模板固定内容).xlsx",
"relativePath": "海陆空报价单模板/陆运报价模板/陆运跨境整车报价模板(黄色标记为模板固定内容).xlsx",
"bizType": "陆运",
"category": "跨境整车",
"keywords": [
"跨境",
"整车"
],
"mode": "auto",
"suggestedMappingPointCount": 1,
"suggestedFields": {
"destination": "跨境整车报价模板!D7"
},
"parseNote": null
},
{
"fileName": "陆运跨境集拼报价模板(黄色标记为模板固定内容).xlsx",
"relativePath": "海陆空报价单模板/陆运报价模板/陆运跨境集拼报价模板(黄色标记为模板固定内容).xlsx",
"bizType": "陆运",
"category": "跨境集拼",
"keywords": [
"集拼",
"跨境"
],
"mode": "auto",
"suggestedMappingPointCount": 2,
"suggestedFields": {
"origin": "跨境集拼报价模板!C4",
"destination": "跨境集拼报价模板!D4"
},
"parseNote": null
}
]
}
+10 -2
View File
@@ -8,8 +8,16 @@
- 若登录报缺 `sys_user_tenant`:再跑 `restore_jeecg_tenant_tables_v1.sql`
- 若继续缺 `sys_depart` 等系统表:从 `jeecg-boot/db/jeecgboot-mysql-5.7.sql` 批量 `CREATE TABLE IF NOT EXISTS` 补齐(本机已用脚本做过)
2. 业务表:`inquiry_admin_v1.sql`
3. 菜单权限 + 演示数据:优先 `inquiry_admin_menu_seed_v1.sql`(含 `url`/`component`)
3. 报价模板表:`inquiry_quote_template_v1.sql`(逻辑头 + 版本/映射)
4. 菜单权限 + 演示数据:优先 `inquiry_admin_menu_seed_v1.sql`(含 `url`/`component`)
首批模板素材目录:`海陆空报价单模板/`(非生产真相)。导入辅助:
```text
python tools/seed_quote_templates.py --dry-run
python tools/seed_quote_templates.py --upload-url http://127.0.0.1:8180/jeecgboot --token <token>
```
本机验收账号:`admin` / `123456`(验证码按页面填写)。
验收:Jeecg 登录 → 侧栏「询价运营」→ 看板/工单/员工/报价模板/回复语可调 `/inquiry/*`;账号/角色在「询价系统配置」进系统页。报价模板可先 mock。
验收:Jeecg 登录 → 侧栏「询价运营」→ 看板/工单/员工/报价模板/回复语可调 `/inquiry/*`;账号/角色在「询价系统配置」进系统页。报价模板走 `/inquiry/quoteTemplate/*`(黄区+映射点,非假占位符)。
+1
View File
@@ -24,6 +24,7 @@ CREATE TABLE IF NOT EXISTS `inq_ticket` (
`demand_title` varchar(500) DEFAULT NULL COMMENT '需求标题(路线摘要)',
`demand_detail` text COMMENT '需求详情文本',
`goods_name` varchar(200) DEFAULT NULL COMMENT '品名',
`quote_date` date DEFAULT NULL COMMENT '报价日期(销售询价/填表用,不是创建时间)',
`pieces` int DEFAULT NULL COMMENT '件数',
`weight_kg` decimal(18,3) DEFAULT NULL COMMENT '重量KG',
`volume_cbm` decimal(18,4) DEFAULT NULL COMMENT '体积CBM',
+51
View File
@@ -0,0 +1,51 @@
-- =============================================================================
-- 友通达新系统 · 报价模板(逻辑模板 + 版本 + 映射)
-- 库:inquiry_robot;禁止对现网 jeecg-boot 执行。
-- 规则:黄色固定区;mapping_json 存本版字段→单元格;改版新建 version。
-- =============================================================================
SET NAMES utf8mb4;
CREATE TABLE IF NOT EXISTS `inq_quote_template` (
`id` varchar(32) NOT NULL COMMENT '逻辑模板主键',
`name` varchar(200) NOT NULL COMMENT '模板名称',
`biz_type` varchar(16) NOT NULL COMMENT '海运/空运/陆运',
`category` varchar(100) NOT NULL COMMENT '分类(与后台筛选项一致)',
`keywords_json` text COMMENT '匹配关键词 JSON 数组',
`mode` varchar(16) NOT NULL DEFAULT 'auto' COMMENT 'auto=可自动生成;manual=仅下载人工填',
`status` varchar(16) NOT NULL DEFAULT 'disabled' COMMENT 'enabled/disabled',
`is_default` tinyint(1) NOT NULL DEFAULT 0 COMMENT '同业务默认模板 0/1',
`current_version` int NOT NULL DEFAULT 0 COMMENT '当前启用版本号,0=尚未启用版本',
`use_count` int NOT NULL DEFAULT 0 COMMENT '累计使用次数',
`create_by` varchar(50) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`update_by` varchar(50) DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_inq_qt_biz_status` (`biz_type`,`status`),
KEY `idx_inq_qt_category` (`category`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='报价模板逻辑头';
CREATE TABLE IF NOT EXISTS `inq_quote_template_version` (
`id` varchar(32) NOT NULL COMMENT '版本主键',
`template_id` varchar(32) NOT NULL COMMENT '逻辑模板 id',
`version_no` int NOT NULL COMMENT '从 1 递增',
`file_name` varchar(255) NOT NULL COMMENT '原始文件名',
`format` varchar(16) NOT NULL DEFAULT 'excel' COMMENT 'excel/pdf',
`object_key` varchar(500) NOT NULL COMMENT '对象存储或本地上传相对路径',
`sha256` varchar(64) DEFAULT NULL COMMENT '文件摘要',
`file_size` bigint DEFAULT NULL,
`parse_status` varchar(32) NOT NULL DEFAULT 'pending' COMMENT 'pending/parsed/failed/mapping_incomplete',
`mapping_point_count` int NOT NULL DEFAULT 0 COMMENT '已映射字段点数(不含未映射候选)',
`mapping_json` longtext COMMENT '本版填表计划 JSON(见 contracts/quote-template-mapping-v1.json)',
`candidates_json` longtext COMMENT '解析出的可填候选(标签/单元格)',
`parse_message` varchar(1000) DEFAULT NULL COMMENT '解析说明或失败原因',
`enabled` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否为本逻辑模板当前启用版本',
`create_by` varchar(50) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`update_by` varchar(50) DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_inq_qt_ver` (`template_id`,`version_no`),
KEY `idx_inq_qt_ver_enabled` (`template_id`,`enabled`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='报价模板版本(版式字节 + 映射)';
@@ -0,0 +1,26 @@
-- =============================================================================
-- 询价工单补列:quote_date(报价日期)
-- 只对 inquiry_robot 执行;禁止对现网 jeecg-boot 执行。
-- 可重复执行:已有列则跳过。
-- =============================================================================
SET NAMES utf8mb4;
SET @db := DATABASE();
SET @exists := (
SELECT COUNT(*)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db
AND TABLE_NAME = 'inq_ticket'
AND COLUMN_NAME = 'quote_date'
);
SET @ddl := IF(
@exists = 0,
'ALTER TABLE `inq_ticket` ADD COLUMN `quote_date` date DEFAULT NULL COMMENT ''报价日期(销售询价/填表用,不是创建时间)'' AFTER `goods_name`',
'SELECT ''quote_date already exists'' AS info'
);
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;