<# .SYNOPSIS 一键本地开发启动脚本。 .DESCRIPTION 自动完成以下工作,然后运行 `wails dev`: 1. 定位 Go 与 Wails CLI(即使未写入 PATH 也能找到常见安装位置)。 2. 把 Go / Wails 注入当前会话 PATH,并设置 GOCACHE 到仓库内的 .gocache。 3. 首次运行时自动安装前端依赖(frontend/node_modules)与 Go 模块。 4. 校验企业微信调试所需的 helper.exe 与 DLL 是否在运行目录。 5. 启动 wails dev(前端热更新 + Go 热重载)。 .PARAMETER SkipInstall 跳过依赖安装检查,直接启动(依赖已装好时更快)。 .PARAMETER GoPath 手动指定 go.exe 路径(自动探测失败时使用)。 .PARAMETER WailsPath 手动指定 wails.exe 路径(自动探测失败时使用)。 .EXAMPLE powershell -ExecutionPolicy Bypass -File scripts\dev.ps1 #> [CmdletBinding()] param( [switch]$SkipInstall, [string]$GoPath, [string]$WailsPath ) $ErrorActionPreference = "Stop" $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path $repoRoot = (Resolve-Path (Join-Path $scriptDir "..")).Path Set-Location $repoRoot function Write-Step([string]$msg) { Write-Host "==> $msg" -ForegroundColor Cyan } function Write-Ok([string]$msg) { Write-Host " $msg" -ForegroundColor Green } function Write-Warn2([string]$msg){ Write-Host " $msg" -ForegroundColor Yellow } # ---- 定位 Go ---- function Resolve-Go { param([string]$Hint) $candidates = @() if ($Hint) { $candidates += $Hint } $cmd = Get-Command go.exe -ErrorAction SilentlyContinue if ($cmd) { $candidates += $cmd.Source } $candidates += "$env:LOCALAPPDATA\Programs\Go\bin\go.exe" $candidates += "C:\Program Files\Go\bin\go.exe" $candidates += "C:\Go\bin\go.exe" foreach ($c in $candidates) { if ($c -and (Test-Path $c)) { return (Resolve-Path $c).Path } } throw "未找到 Go。请安装 Go 1.24+ 后重试,或用 -GoPath 指定 go.exe。" } # ---- 定位 Wails CLI ---- function Resolve-Wails { param([string]$Hint) $candidates = @() if ($Hint) { $candidates += $Hint } $cmd = Get-Command wails.exe -ErrorAction SilentlyContinue if ($cmd) { $candidates += $cmd.Source } $candidates += "$env:USERPROFILE\go\bin\wails.exe" foreach ($c in $candidates) { if ($c -and (Test-Path $c)) { return (Resolve-Path $c).Path } } return $null } # ---- 环境准备 ---- Write-Step "定位 Go / Wails 工具链" $go = Resolve-Go -Hint $GoPath $goBin = Split-Path -Parent $go $goPathBin = Join-Path $env:USERPROFILE "go\bin" $env:PATH = "$goBin;$goPathBin;$env:PATH" Write-Ok "Go: $go" $wails = Resolve-Wails -Hint $WailsPath if (-not $wails) { Write-Warn2 "未找到 Wails CLI,正在安装(go install)..." & $go install github.com/wailsapp/wails/v2/cmd/wails@latest $wails = Resolve-Wails if (-not $wails) { throw "Wails CLI 安装失败,请检查网络或 GOPROXY。" } } Write-Ok "Wails: $wails" # GOCACHE 放仓库内,避免污染系统缓存 $goCache = Join-Path $repoRoot ".gocache" New-Item -ItemType Directory -Force -Path $goCache | Out-Null $env:GOCACHE = $goCache # ---- 依赖安装 ---- if (-not $SkipInstall) { $nodeModules = Join-Path $repoRoot "frontend\node_modules" if (-not (Test-Path $nodeModules)) { Write-Step "安装前端依赖 (npm install)" Push-Location (Join-Path $repoRoot "frontend") try { & npm install } finally { Pop-Location } Write-Ok "前端依赖安装完成" } else { Write-Ok "前端依赖已存在,跳过 npm install" } Write-Step "同步 Go 模块 (go mod download)" & $go mod download Write-Ok "Go 模块就绪" } # ---- 编译 32 位 helper.exe(统一用正确链接参数,杜绝手动编错导致主程序拉起即崩)---- # helper 必须 GOARCH=386 且带 -H windowsgui:主程序是 GUI 子系统,用 CreateProcess 拉起 helper 时 # 不分配控制台;若 helper 误编成默认 console 子系统(漏掉 -H windowsgui),被主程序拉起后会因标准 # 句柄处理立即崩溃——表现为主程序日志「辅助程序已成功启动 PID=xxxx」但该 PID 秒退、不写日志、 # 10001 端口无人监听、前端“启动企微”无反应、满屏 dial tcp [::1]:10001 拒绝连接。诡异之处在于 # console 版 helper 在终端手动运行一切正常,只有被主程序拉起才崩,极易误判为二进制本身没问题。 # 这里每次 dev 启动都用与 scripts\build.ps1 完全一致的参数重编 helper,既保证 helper 源码改动生效, # 又把“唯一正确的编译方式”固化进脚本,从源头消除编错风险。 Write-Step "编译 32 位 helper.exe" $binDir = Join-Path $repoRoot "build\bin" New-Item -ItemType Directory -Force -Path $binDir | Out-Null $helperOut = Join-Path $binDir "helper.exe" # 先停掉可能占用 helper.exe 的残留进程,否则无法覆盖输出文件。 Get-Process -Name "helper", "helper_auto_reply" -ErrorAction SilentlyContinue | ForEach-Object { Write-Warn2 "结束残留 helper 进程 (PID $($_.Id))" Stop-Process -Id $_.Id -Force -ErrorAction SilentlyContinue } $oldArch = $env:GOARCH $oldCgo = $env:CGO_ENABLED try { $env:GOARCH = "386" $env:CGO_ENABLED = "0" Push-Location (Join-Path $repoRoot "helper") & $go build -trimpath -ldflags "-H windowsgui -s -w" -o $helperOut . $helperExit = $LASTEXITCODE } finally { Pop-Location if ($null -eq $oldArch) { Remove-Item Env:GOARCH -ErrorAction SilentlyContinue } else { $env:GOARCH = $oldArch } if ($null -eq $oldCgo) { Remove-Item Env:CGO_ENABLED -ErrorAction SilentlyContinue } else { $env:CGO_ENABLED = $oldCgo } } if ($helperExit -ne 0) { throw "helper.exe 编译失败 (exit $helperExit)。企业微信链路将不可用,请检查 helper 目录源码。" } # 子系统自检:windowsgui 版的 PE 头 Subsystem 字段为 2(GUI),console 版为 3。若意外编出 console 版直接拦下。 try { $bytes = [System.IO.File]::ReadAllBytes($helperOut) $peOff = [System.BitConverter]::ToInt32($bytes, 0x3C) # e_lfanew → PE 头偏移 $subsystem = [System.BitConverter]::ToUInt16($bytes, $peOff + 0x5C) # OptionalHeader.Subsystem if ($subsystem -eq 2) { Write-Ok "helper.exe 已编译 (GOARCH=386, GUI 子系统,可被主程序安全拉起)" } else { throw "helper.exe 子系统为 $subsystem(期望 2=GUI)。这会导致主程序拉起即崩,请确认编译参数含 -H windowsgui。" } } catch [System.Management.Automation.RuntimeException] { throw } catch { Write-Warn2 "helper.exe 子系统自检跳过:$($_.Exception.Message)" } $wxDlls = @("Helper_4.1.33.6009.dll", "Loader_4.1.33.6009.dll") foreach ($dll in $wxDlls) { if (Test-Path (Join-Path $repoRoot $dll)) { Write-Ok "$dll 已就绪" } else { Write-Warn2 "缺少 $dll,企业微信链路可能无法工作" } } # wails dev 把主程序与 helper 跑在 build\bin 下,helper 需要在自身目录找到这两个 DLL。 # 仓库根目录有 DLL 但 build\bin 没有 → "启动企微"会失败并提示 No Helper_*.dll found。 # 这里把根目录 DLL 同步到 build\bin(build\bin 由 wails 在编译时创建,缺失则跳过,启动后会自动建好; # 若本次启动后仍报缺 DLL,再次运行本脚本即可补齐)。 $binDir = Join-Path $repoRoot "build\bin" if (Test-Path $binDir) { foreach ($dll in $wxDlls) { $src = Join-Path $repoRoot $dll if (-not (Test-Path $src)) { continue } $dst = Join-Path $binDir $dll # 目标已是同样大小则无需复制(避免覆盖正被 helper 占用的 DLL 而报错) if ((Test-Path $dst) -and ((Get-Item $dst).Length -eq (Get-Item $src).Length)) { Write-Ok "$dll 已在 build\bin(无需同步)" continue } try { Copy-Item -LiteralPath $src -Destination $dst -Force Write-Ok "已同步 $dll 到 build\bin" } catch { if (Test-Path $dst) { Write-Warn2 "$dll 正被占用无法更新,但 build\bin 已有可用副本,继续。" } else { Write-Warn2 "同步 $dll 到 build\bin 失败且目标缺失:$($_.Exception.Message)。请先结束残留 helper.exe 再重试。" } } } } else { Write-Warn2 "build\bin 尚不存在(首次编译后才会创建)。若启动后“启动企微”提示缺少 DLL,请重新运行本脚本以同步。" } # helper 运行时要在自身目录(build\bin)下按消息类型号读取 eventdata\.json 模板来翻译企微事件。 # 若 build\bin 缺少 eventdata,TransformData 会走兜底分支并丢掉顶层 type 字段,导致文本消息被误判成图片、 # 触发图片识别失败并反复回复“无法识别这条图片/视频内容”。requestdata 同理为运行时资源。 # 这里每次 dev 启动都把仓库根目录的 eventdata/requestdata 同步到 build\bin,从源头杜绝该问题复发。 if (Test-Path $binDir) { foreach ($resource in @("eventdata", "requestdata")) { $src = Join-Path $repoRoot $resource if (-not (Test-Path $src)) { Write-Warn2 "仓库缺少 $resource 目录,跳过同步"; continue } $dst = Join-Path $binDir $resource try { Copy-Item -LiteralPath $src -Destination $binDir -Recurse -Force Write-Ok "已同步 $resource 到 build\bin" } catch { Write-Warn2 "同步 $resource 到 build\bin 失败:$($_.Exception.Message)" } } } # ---- 启动 ---- Write-Step "启动 wails dev(Ctrl+C 退出)" Write-Host "" & $wails dev