daShangDao_planA/tool/process/process.go

875 lines
26 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package process
import (
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"planA/controlState/lock"
"planA/initialization/config"
"planA/modules/logs"
"planA/service"
planAMysql "planA/service/mysql"
_type "planA/type"
"runtime"
"strconv"
"strings"
"syscall"
"time"
_redis "github.com/go-redis/redis/v8"
)
var (
modkernel32 = syscall.NewLazyDLL("kernel32.dll")
modntdll = syscall.NewLazyDLL("ntdll.dll")
procOpenProcess = modkernel32.NewProc("OpenProcess")
procCloseHandle = modkernel32.NewProc("CloseHandle")
procNtSuspendProcess = modntdll.NewProc("NtSuspendProcess")
procNtResumeProcess = modntdll.NewProc("NtResumeProcess")
)
const (
PROCESS_SUSPEND_RESUME = 0x0800
)
// RunTaskWorker 启动 B程序
// @param taskId 任务ID
// @return string 进程ID
// @return error 错误
func RunTaskWorker(taskId string) (string, error) {
// 1. 尝试加锁
if !lock.TryLock(taskId) {
return "", fmt.Errorf("taskId %s 已被上锁跳过B程序执行", taskId)
}
// 2 加锁成功执行B程序确保defer释放锁即使执行出错也能解锁
defer lock.DestroyLock(taskId)
// 3 判断pid是否存在
processId, getProcessIdErr := service.GetProcessId(taskId)
// 检查是否有错误排除redis key不存在的情况
if getProcessIdErr != nil && !errors.Is(getProcessIdErr, _redis.Nil) {
return "", getProcessIdErr
}
// 4 判断pid是否存在
if processId != "" {
//验证进程是否真实存在
if !IsProcessExistWindows(processId) {
// 删除 header中的pid
deleteProcessIdErr := service.DeleteProcessId(taskId)
if deleteProcessIdErr != nil {
return "", deleteProcessIdErr
}
} else {
// 返回 pid
return processId, nil
}
}
// 5 判断任务状态
taskStatus, getTaskStatusErr := service.GetTaskHeader(taskId)
if getTaskStatusErr != nil {
return "", getTaskStatusErr
}
if taskStatus.Status == _type.TaskStatusPaused {
return "", fmt.Errorf("任务暂停中,请先尝试恢复")
}
if taskStatus.Status == _type.TaskStatusStopped || taskStatus.Status == _type.TaskStatusOver {
return "", fmt.Errorf("任务已结束,不支持重启")
}
// 6 启动B程序
_, CallSendPublishingERR := CallSendPublishing(taskId)
if CallSendPublishingERR != nil {
return "", CallSendPublishingERR
}
return processId, nil
}
// RunCProgram 启动 C程序
func RunCProgram() error {
// 1. 构建并验证程序路径
fileUrlConfig, getFileUrlConfigErr := config.GetFileUrlConfig()
if getFileUrlConfigErr != nil {
errMsg := fmt.Sprintf("获取文件路径配置失败: %v", getFileUrlConfigErr)
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
return fmt.Errorf(errMsg)
}
programPath := fileUrlConfig.CFileName
// 2. 验证程序路径是否存在
absProgramPath, err := filepath.Abs(programPath)
if err != nil {
errMsg := fmt.Sprintf("转换程序路径为绝对路径失败: %s, 原始路径: %s", err, programPath)
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
return fmt.Errorf(errMsg)
}
// 3. 验证程序文件
_, statErr := os.Stat(absProgramPath)
if statErr != nil {
errMsg := fmt.Sprintf("程序文件不存在或无访问权限: %s, 错误: %v", absProgramPath, statErr)
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
return fmt.Errorf(errMsg)
}
// 4. 修改PowerShell脚本 - 不等待进程退出
psScript := fmt.Sprintf(`
$ErrorActionPreference = "Stop"
$programPath = "%s"
try {
if (-not (Test-Path $programPath -PathType Leaf)) {
throw "程序文件不存在: $programPath"
}
$psi = New-Object System.Diagnostics.ProcessStartInfo
$psi.FileName = $programPath
$psi.UseShellExecute = $true
$psi.WindowStyle = 'Normal'
$psi.WorkingDirectory = (Split-Path $programPath -Parent)
$psi.CreateNoWindow = $false
Write-Host "正在启动程序: $programPath"
$process = [System.Diagnostics.Process]::Start($psi)
Write-Host "程序启动成功PID: $($process.Id)"
# 不要调用 WaitForExit()让PowerShell立即退出
# 但确保进程独立运行
$process.Dispose()
Write-Host "程序已启动PowerShell即将退出"
} catch {
Write-Error "启动程序失败: $_"
exit 1
}
`, programPath)
// 5. 执行PowerShell命令 - 不等待完成
cmd := exec.Command("powershell", "-NoProfile", "-Command", psScript)
if runtime.GOOS == "windows" {
cmd.SysProcAttr = &syscall.SysProcAttr{
CreationFlags: 0x00000010, // CREATE_NEW_CONSOLE
HideWindow: false,
}
}
// 6. 启动PowerShell但不等待或者等待短时间后分离
err = cmd.Start()
if err != nil {
return fmt.Errorf("启动程序失败: %v", err)
}
// 可选等待1秒让程序启动然后让PowerShell独立运行
go func() {
time.Sleep(1 * time.Second)
cmd.Process.Release() // 释放进程句柄让PowerShell独立运行
}()
logs.LoggingMiddleware(logs.LOG_LEVEL_INFO, "C程序已启动")
return nil
}
// RunFProgram 启动 F程序
func RunFProgram() error {
// 1. 构建并验证程序路径
fileUrlConfig, getFileUrlConfigErr := config.GetFileUrlConfig()
if getFileUrlConfigErr != nil {
errMsg := fmt.Sprintf("获取文件路径配置失败: %v", getFileUrlConfigErr)
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
return fmt.Errorf(errMsg)
}
programPath := fileUrlConfig.FFileName
// 2. 验证程序路径是否存在
absProgramPath, err := filepath.Abs(programPath)
if err != nil {
errMsg := fmt.Sprintf("转换程序路径为绝对路径失败: %s, 原始路径: %s", err, programPath)
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
return fmt.Errorf(errMsg)
}
// 3. 验证程序文件
_, statErr := os.Stat(absProgramPath)
if statErr != nil {
errMsg := fmt.Sprintf("程序文件不存在或无访问权限: %s, 错误: %v", absProgramPath, statErr)
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
return fmt.Errorf(errMsg)
}
// 4. 修改PowerShell脚本 - 不等待进程退出
psScript := fmt.Sprintf(`
$ErrorActionPreference = "Stop"
$programPath = "%s"
try {
if (-not (Test-Path $programPath -PathType Leaf)) {
throw "程序文件不存在: $programPath"
}
$psi = New-Object System.Diagnostics.ProcessStartInfo
$psi.FileName = $programPath
$psi.UseShellExecute = $true
$psi.WindowStyle = 'Normal'
$psi.WorkingDirectory = (Split-Path $programPath -Parent)
$psi.CreateNoWindow = $false
Write-Host "正在启动程序: $programPath"
$process = [System.Diagnostics.Process]::Start($psi)
Write-Host "程序启动成功PID: $($process.Id)"
# 不要调用 WaitForExit()让PowerShell立即退出
# 但确保进程独立运行
$process.Dispose()
Write-Host "程序已启动PowerShell即将退出"
} catch {
Write-Error "启动程序失败: $_"
exit 1
}
`, programPath)
// 5. 执行PowerShell命令 - 不等待完成
cmd := exec.Command("powershell", "-NoProfile", "-Command", psScript)
if runtime.GOOS == "windows" {
cmd.SysProcAttr = &syscall.SysProcAttr{
CreationFlags: 0x00000010, // CREATE_NEW_CONSOLE
HideWindow: false,
}
}
// 6. 启动PowerShell但不等待或者等待短时间后分离
err = cmd.Start()
if err != nil {
return fmt.Errorf("启动程序失败: %v", err)
}
// 可选等待1秒让程序启动然后让PowerShell独立运行
go func() {
time.Sleep(1 * time.Second)
cmd.Process.Release() // 释放进程句柄让PowerShell独立运行
}()
logs.LoggingMiddleware(logs.LOG_LEVEL_INFO, "C程序已启动")
return nil
}
// RunDprogram 启动 D程序
// @param taskId 任务ID
// @return string 进程ID
// @return error 错误
func RunDprogram(taskId string) (string, error) {
taskIdKey := taskId + "_d"
// 1. 尝试加锁
if !lock.TryLock(taskIdKey) {
return "", fmt.Errorf("taskId %s 已被上锁跳过D程序执行", taskId)
}
// 2 加锁成功执行B程序确保defer释放锁即使执行出错也能解锁
defer lock.DestroyLock(taskIdKey)
// 3 判断pid是否存在
delTask, getDelTaskByTaskIdErr := planAMysql.GetDelTaskByTaskId(taskId)
if getDelTaskByTaskIdErr != nil {
return "", getDelTaskByTaskIdErr
}
var processId string
if delTask.Pid == nil {
processId = ""
} else {
processId = *delTask.Pid
}
// 4 判断pid是否存在
if processId != "" {
//验证进程是否真实存在
if !IsProcessExistWindows(processId) {
// 删除 del_task中的pid
updateDelTaskPidByTaskIdErr := planAMysql.UpdateDelTaskPidByTaskId(taskId)
if updateDelTaskPidByTaskIdErr != nil {
return "", updateDelTaskPidByTaskIdErr
}
} else {
fmt.Println("程序存在 不启动D程序")
// 返回 pid
return processId, nil
}
}
// 6 启动D程序
_, CallSendPublishingERR := CallSendPublishingMysql(taskId)
if CallSendPublishingERR != nil {
return "", CallSendPublishingERR
}
return processId, nil
}
// SuspendProcess 暂停指定PID的进程
// @param taskId 任务ID
// @return error 错误
func SuspendProcess(taskId string) error {
// 1. 判断 pid是否存在
processId, getProcessIdErr := service.GetProcessId(taskId)
// 检查是否有错误排除redis key不存在的情况
if getProcessIdErr != nil && !errors.Is(getProcessIdErr, _redis.Nil) {
return getProcessIdErr
}
// 2. 判断pid是否存在
if processId != "" {
//验证进程是否真实存在
if IsProcessExistWindows(processId) {
// 将字符串转换为整数
pid, err := strconv.Atoi(processId)
if err != nil {
return fmt.Errorf("PID格式错误: %s, 错误: %v", processId, err)
}
// 检查 PID是否有效
if pid <= 0 {
return fmt.Errorf("PID必须为正整数")
}
// 打开进程
hProcess, _, err := procOpenProcess.Call(
PROCESS_SUSPEND_RESUME,
uintptr(0),
uintptr(pid),
)
if hProcess == 0 {
return fmt.Errorf("打开进程失败: %v", err)
}
defer procCloseHandle.Call(hProcess)
// 暂停进程
callSstatus, _, _ := procNtSuspendProcess.Call(hProcess)
if callSstatus != 0 {
return fmt.Errorf("NtSuspendProcess 失败: 0x%X", callSstatus)
}
}
}
// 3. 修改Header中的状态
status := int64(_type.TaskStatusPaused)
updateHeaderStatusErr := service.UpdateHeaderStatus(taskId, status)
if updateHeaderStatusErr != nil {
return updateHeaderStatusErr
}
return nil
}
// ResumeProcess 恢复指定PID的进程
// @param taskId 任务ID
// @return error 错误
func ResumeProcess(taskId string) error {
// 1. 查询PID
processId, getProcessIdErr := service.GetProcessId(taskId)
if getProcessIdErr != nil {
return getProcessIdErr
}
// 2. 将字符串转换为整数
pid, err := strconv.Atoi(processId)
if err != nil {
return fmt.Errorf("PID格式错误: %s, 错误: %v", processId, err)
}
// 3. 检查PID是否有效
if pid <= 0 {
return fmt.Errorf("PID必须为正整数")
}
//验证进程是否真实存在
if IsProcessExistWindows(processId) {
// 4. 打开进程
hProcess, _, err := procOpenProcess.Call(
PROCESS_SUSPEND_RESUME,
uintptr(0),
uintptr(pid),
)
if hProcess == 0 {
return fmt.Errorf("打开进程失败: %v", err)
}
defer procCloseHandle.Call(hProcess)
// 5. 恢复进程
callStatus, _, _ := procNtResumeProcess.Call(hProcess)
if callStatus != 0 {
return fmt.Errorf("NtResumeProcess 失败: 0x%X", callStatus)
}
}
// 6. 修改Header中的状态
status := int64(_type.TaskStatusRunning)
updateHeaderStatusErr := service.UpdateHeaderStatus(taskId, status)
if updateHeaderStatusErr != nil {
return updateHeaderStatusErr
}
return nil
}
// StopTask 停止任务
// @param taskId 队列名称
// @return error 错误
func StopTask(taskId string) error {
// 1. 判断 pid是否存在
processId, getProcessIdErr := service.GetProcessId(taskId)
// 检查是否有错误排除redis key不存在的情况
if getProcessIdErr != nil && !errors.Is(getProcessIdErr, _redis.Nil) {
return getProcessIdErr
}
// 2. 判断pid是否存在
if processId != "" {
//验证进程是否真实存在
if IsProcessExistWindows(processId) {
// 恢复 B程序避免程序处于暂停状态
resumeProcessErr := ResumeProcess(taskId)
if resumeProcessErr != nil {
return resumeProcessErr
}
}
}
// 3. 修改 Header中的状态 并且 删除 bodyWait 中的数据
stopTaskErr := service.StopTask(taskId)
if stopTaskErr != nil {
return stopTaskErr
}
return nil
}
func CallSendPublishing(taskId string) (*os.Process, error) {
// 1. 基础入参校验
if taskId == "" {
return nil, errors.New("队列名称qName不能为空")
}
// 先在Redis中创建一个占位符表示进程即将启动
placeholderPID := "starting"
setProcessIdErr := service.SetProcessId(taskId, placeholderPID)
if setProcessIdErr != nil {
errMsg := fmt.Sprintf("保存进程占位符到Redis失败: %v, taskId: %s", setProcessIdErr, taskId)
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
return nil, fmt.Errorf(errMsg)
}
// 2. 构建并验证程序路径
fileUrlConfig, getFileUrlConfigErr := config.GetFileUrlConfig()
if getFileUrlConfigErr != nil {
errMsg := fmt.Sprintf("获取文件路径配置失败: %v", getFileUrlConfigErr)
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
return nil, fmt.Errorf(errMsg)
}
programPath := fileUrlConfig.BFileName
// 3. 验证程序路径是否存在
absProgramPath, err := filepath.Abs(programPath)
if err != nil {
errMsg := fmt.Sprintf("转换程序路径为绝对路径失败: %s, 原始路径: %s", err, programPath)
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
return nil, fmt.Errorf(errMsg)
}
// 4. 验证程序路径
_, statErr := os.Stat(absProgramPath)
if statErr != nil {
errMsg := fmt.Sprintf("程序文件不存在或无访问权限: %s, 错误: %v", absProgramPath, statErr)
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
return nil, fmt.Errorf(errMsg)
}
// 5. 修复后的PowerShell脚本
psScript := fmt.Sprintf(`
# 设置错误捕获模式
$ErrorActionPreference = "Stop"
$programPath = "%s"
$arguments = "%s"
try {
# 再次验证程序存在性
if (-not (Test-Path $programPath -PathType Leaf)) {
throw "程序文件不存在: $programPath"
}
# 构建进程启动信息
$psi = New-Object System.Diagnostics.ProcessStartInfo
$psi.FileName = $programPath
$psi.Arguments = $arguments
$psi.UseShellExecute = $true
$psi.WindowStyle = 'Normal'
$psi.WorkingDirectory = (Split-Path $programPath -Parent) # 设置工作目录为程序所在目录
# 启动进程
Write-Host "开始启动程序: $programPath 参数: $arguments"
$process = [System.Diagnostics.Process]::Start($psi)
Write-Host "程序启动成功PID: $($process.Id)"
# 等待窗口出现,设置超时时间
$timeout = 3000
$startTime = Get-Date
$hwnd = $null
# 等待窗口句柄不为空
while ($true) {
try {
$process.Refresh()
$hwnd = $process.MainWindowHandle
if ($hwnd -and $hwnd -ne [IntPtr]::Zero) {
break
}
} catch {
# 忽略刷新错误
}
if (((Get-Date) - $startTime).TotalMilliseconds -ge $timeout) {
Write-Warning "等待窗口句柄超时 (PID: $($process.Id))"
break
}
Start-Sleep -Milliseconds 50
}
# 尝试将窗口前置(仅在成功获取窗口句柄时)
if ($hwnd -and $hwnd -ne [IntPtr]::Zero) {
try {
Add-Type @"
using System;
using System.Runtime.InteropServices;
public class WindowHelper {
[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern bool AllowSetForegroundWindow(uint dwProcessId);
}
"@
[WindowHelper]::AllowSetForegroundWindow($process.Id)
[WindowHelper]::ShowWindow($hwnd, 9) # 9 = SW_RESTORE
[WindowHelper]::SetForegroundWindow($hwnd)
Write-Host "成功将窗口前置,句柄: $hwnd"
} catch {
Write-Warning "窗口前置操作失败: $_"
# 不抛出异常,继续执行
}
} else {
Write-Warning "未能获取窗口句柄,程序可能没有窗口界面 (PID: $($process.Id))"
}
# 输出PID供Go解析
Write-Output $process.Id
} catch {
# 捕获所有异常并输出
Write-Error "启动程序失败: $_"
exit 1 # 返回非0退出码
}
`, absProgramPath, taskId)
// 6. 执行PowerShell命令同时捕获stdout和stderr
cmd := exec.Command("powershell", "-NoProfile", "-NonInteractive", "-Command", psScript)
if runtime.GOOS == "windows" {
cmd.SysProcAttr = &syscall.SysProcAttr{
CreationFlags: 0x00000010, // CREATE_NEW_CONSOLE - 创建新控制台
}
}
// 7. 同时捕获标准输出和标准错误
var stdout, stderr strings.Builder
cmd.Stdout = &stdout
cmd.Stderr = &stderr
// 8. 执行命令
runErr := cmd.Run()
// 9. 输出所有PowerShell的输出
stdoutStr := stdout.String()
stderrStr := stderr.String()
// 记录标准输出(调试用)
if stdoutStr != "" {
logs.LoggingMiddleware(logs.LOG_LEVEL_INFO, fmt.Sprintf("PowerShell标准输出: %s", stdoutStr))
}
// 记录标准错误(警告信息不算错误)
if stderrStr != "" {
// 检查是否为警告信息
if strings.Contains(stderrStr, "WARNING:") {
logs.LoggingMiddleware(logs.LOG_LEVEL_WARNING, fmt.Sprintf("PowerShell警告: %s", stderrStr))
} else {
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, fmt.Sprintf("PowerShell错误: %s", stderrStr))
}
}
// 10. 检查命令执行是否失败
if runErr != nil {
errMsg := fmt.Sprintf("PowerShell执行失败: %v, 错误输出: %s", runErr, stderrStr)
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
return nil, fmt.Errorf(errMsg)
}
// 11. 解析PID增加校验
var pid uint32
lines := strings.Split(stdoutStr, "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
if line == "" {
continue
}
// 跳过非纯数字行(如 Write-Host 的输出)
pidInt, err := strconv.Atoi(line)
if err == nil && pidInt > 0 {
pid = uint32(pidInt)
logs.LoggingMiddleware(logs.LOG_LEVEL_INFO, fmt.Sprintf("成功解析PID: %d", pid))
break // 找到有效PID立即退出
}
}
if pid == 0 {
errMsg := fmt.Sprintf("未解析到有效PIDPowerShell输出: %s, 错误输出: %s", stdoutStr, stderrStr)
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
return nil, fmt.Errorf(errMsg)
}
// 12. 更新Redis中的PID
processID := fmt.Sprintf("%d", pid)
setProcessIdErr = service.SetProcessId(taskId, processID)
if setProcessIdErr != nil {
errMsg := fmt.Sprintf("更新进程PID到Redis失败: %v, taskId: %s, PID: %d", setProcessIdErr, taskId, pid)
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
return nil, fmt.Errorf(errMsg)
}
// 13. 返回进程句柄
process := &os.Process{Pid: int(pid)}
return process, nil
}
func CallSendPublishingMysql(taskId string) (*os.Process, error) {
// 1. 基础入参校验
if taskId == "" {
return nil, errors.New("taskId不能为空")
}
// 先在Redis中创建一个占位符表示进程即将启动
placeholderPID := "starting"
setProcessIdErr := planAMysql.UpdateDelTaskPidByTaskIdAndPid(taskId, placeholderPID)
if setProcessIdErr != nil {
errMsg := fmt.Sprintf("保存进程占位符到Redis失败: %v, taskId: %s", setProcessIdErr, taskId)
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
return nil, fmt.Errorf(errMsg)
}
// 2. 构建并验证程序路径
fileUrlConfig, getFileUrlConfigErr := config.GetFileUrlConfig()
if getFileUrlConfigErr != nil {
errMsg := fmt.Sprintf("获取文件路径配置失败: %v", getFileUrlConfigErr)
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
return nil, fmt.Errorf(errMsg)
}
programPath := fileUrlConfig.DFileName
// 3. 验证程序路径是否存在
absProgramPath, err := filepath.Abs(programPath)
if err != nil {
errMsg := fmt.Sprintf("转换程序路径为绝对路径失败: %s, 原始路径: %s", err, programPath)
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
return nil, fmt.Errorf(errMsg)
}
// 4. 验证程序路径
_, statErr := os.Stat(absProgramPath)
if statErr != nil {
errMsg := fmt.Sprintf("程序文件不存在或无访问权限: %s, 错误: %v", absProgramPath, statErr)
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
return nil, fmt.Errorf(errMsg)
}
// 5. 修复后的PowerShell脚本
psScript := fmt.Sprintf(`
# 设置错误捕获模式
$ErrorActionPreference = "Stop"
$programPath = "%s"
$arguments = "%s"
try {
# 再次验证程序存在性
if (-not (Test-Path $programPath -PathType Leaf)) {
throw "程序文件不存在: $programPath"
}
# 构建进程启动信息
$psi = New-Object System.Diagnostics.ProcessStartInfo
$psi.FileName = $programPath
$psi.Arguments = $arguments
$psi.UseShellExecute = $true
$psi.WindowStyle = 'Normal'
$psi.WorkingDirectory = (Split-Path $programPath -Parent) # 设置工作目录为程序所在目录
# 启动进程
Write-Host "开始启动程序: $programPath 参数: $arguments"
$process = [System.Diagnostics.Process]::Start($psi)
Write-Host "程序启动成功PID: $($process.Id)"
# 等待窗口出现,设置超时时间
$timeout = 3000
$startTime = Get-Date
$hwnd = $null
# 等待窗口句柄不为空
while ($true) {
try {
$process.Refresh()
$hwnd = $process.MainWindowHandle
if ($hwnd -and $hwnd -ne [IntPtr]::Zero) {
break
}
} catch {
# 忽略刷新错误
}
if (((Get-Date) - $startTime).TotalMilliseconds -ge $timeout) {
Write-Warning "等待窗口句柄超时 (PID: $($process.Id))"
break
}
Start-Sleep -Milliseconds 50
}
# 尝试将窗口前置(仅在成功获取窗口句柄时)
if ($hwnd -and $hwnd -ne [IntPtr]::Zero) {
try {
Add-Type @"
using System;
using System.Runtime.InteropServices;
public class WindowHelper {
[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern bool AllowSetForegroundWindow(uint dwProcessId);
}
"@
[WindowHelper]::AllowSetForegroundWindow($process.Id)
[WindowHelper]::ShowWindow($hwnd, 9) # 9 = SW_RESTORE
[WindowHelper]::SetForegroundWindow($hwnd)
Write-Host "成功将窗口前置,句柄: $hwnd"
} catch {
Write-Warning "窗口前置操作失败: $_"
# 不抛出异常,继续执行
}
} else {
Write-Warning "未能获取窗口句柄,程序可能没有窗口界面 (PID: $($process.Id))"
}
# 输出PID供Go解析
Write-Output $process.Id
} catch {
# 捕获所有异常并输出
Write-Error "启动程序失败: $_"
exit 1 # 返回非0退出码
}
`, absProgramPath, taskId)
// 6. 执行PowerShell命令同时捕获stdout和stderr
cmd := exec.Command("powershell", "-NoProfile", "-NonInteractive", "-Command", psScript)
if runtime.GOOS == "windows" {
cmd.SysProcAttr = &syscall.SysProcAttr{
CreationFlags: 0x00000010, // CREATE_NEW_CONSOLE - 创建新控制台
}
}
// 7. 同时捕获标准输出和标准错误
var stdout, stderr strings.Builder
cmd.Stdout = &stdout
cmd.Stderr = &stderr
// 8. 执行命令
runErr := cmd.Run()
// 9. 输出所有PowerShell的输出
stdoutStr := stdout.String()
stderrStr := stderr.String()
// 记录标准输出(调试用)
if stdoutStr != "" {
logs.LoggingMiddleware(logs.LOG_LEVEL_INFO, fmt.Sprintf("PowerShell标准输出: %s", stdoutStr))
}
// 记录标准错误(警告信息不算错误)
if stderrStr != "" {
// 检查是否为警告信息
if strings.Contains(stderrStr, "WARNING:") {
logs.LoggingMiddleware(logs.LOG_LEVEL_WARNING, fmt.Sprintf("PowerShell警告: %s", stderrStr))
} else {
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, fmt.Sprintf("PowerShell错误: %s", stderrStr))
}
}
// 10. 检查命令执行是否失败
if runErr != nil {
errMsg := fmt.Sprintf("PowerShell执行失败: %v, 错误输出: %s", runErr, stderrStr)
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
return nil, fmt.Errorf(errMsg)
}
// 11. 解析PID增加校验
var pid uint32
lines := strings.Split(stdoutStr, "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
if line == "" {
continue
}
// 跳过非纯数字行(如 Write-Host 的输出)
pidInt, err := strconv.Atoi(line)
if err == nil && pidInt > 0 {
pid = uint32(pidInt)
logs.LoggingMiddleware(logs.LOG_LEVEL_INFO, fmt.Sprintf("成功解析PID: %d", pid))
break // 找到有效PID立即退出
}
}
if pid == 0 {
errMsg := fmt.Sprintf("未解析到有效PIDPowerShell输出: %s, 错误输出: %s", stdoutStr, stderrStr)
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
return nil, fmt.Errorf(errMsg)
}
// 12. 更新Redis中的PID
processID := fmt.Sprintf("%d", pid)
setProcessIdErr = planAMysql.UpdateDelTaskPidByTaskIdAndPid(taskId, processID)
if setProcessIdErr != nil {
errMsg := fmt.Sprintf("更新进程PID到Redis失败: %v, taskId: %s, PID: %d", setProcessIdErr, taskId, pid)
logs.LoggingMiddleware(logs.LOG_LEVEL_ERROR, errMsg)
return nil, fmt.Errorf(errMsg)
}
// 13. 返回进程句柄
process := &os.Process{Pid: int(pid)}
return process, nil
}
// IsProcessExistWindows 检查Windows进程是否存在
func IsProcessExistWindows(pid string) bool {
if pid == "" {
return false
}
pid64, err := strconv.ParseInt(pid, 10, 0)
if err != nil {
logs.LoggingMiddleware(logs.LOG_LEVEL_WARNING, fmt.Sprintf("转换进程PID:%v失败: %v", pid, err))
return false
}
pidInt := int(pid64)
// 使用 tasklist命令检查进程是否存在
cmd := exec.Command("tasklist", "/FI", fmt.Sprintf("PID eq %d", pidInt))
output, err := cmd.Output()
if err != nil {
logs.LoggingMiddleware(logs.LOG_LEVEL_WARNING, fmt.Sprintf("检查进程PID:%v失败: %v", pid, err))
return false
}
return strings.Contains(strings.ToLower(string(output)), fmt.Sprintf("%d", pidInt))
}