374 lines
12 KiB
Go
374 lines
12 KiB
Go
package main
|
||
|
||
import (
|
||
"context"
|
||
"embed"
|
||
"fmt"
|
||
"log"
|
||
"os"
|
||
"os/exec"
|
||
"path/filepath"
|
||
"runtime"
|
||
"strings"
|
||
"syscall"
|
||
"time"
|
||
"unsafe"
|
||
|
||
"qiweimanager/config"
|
||
"qiweimanager/logger"
|
||
|
||
"github.com/wailsapp/wails/v2"
|
||
"github.com/wailsapp/wails/v2/pkg/options"
|
||
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
|
||
"github.com/wailsapp/wails/v2/pkg/options/windows"
|
||
)
|
||
|
||
//go:embed all:frontend/dist
|
||
var assets embed.FS
|
||
|
||
// 全局变量
|
||
var (
|
||
// 用于存储辅助程序的进程信息
|
||
helperProcess *os.Process
|
||
// 全局日志器
|
||
globalLogger *logger.Logger
|
||
// 日志总开关
|
||
logEnabled = true
|
||
// 日志级别
|
||
logLevel = logger.LevelDebug
|
||
)
|
||
|
||
// 检查并关闭helper.exe进程的函数
|
||
func checkAndCloseHelperProcess() {
|
||
globalLogger.Info("检查系统中是否存在helper.exe进程...")
|
||
|
||
// 打开系统快照
|
||
hSnapshot, err := syscall.CreateToolhelp32Snapshot(syscall.TH32CS_SNAPPROCESS, 0)
|
||
if err != nil {
|
||
globalLogger.Error("创建进程快照失败: %v", err)
|
||
return
|
||
}
|
||
defer syscall.CloseHandle(hSnapshot)
|
||
|
||
// 初始化进程信息结构
|
||
var pe32 syscall.ProcessEntry32
|
||
pe32.Size = uint32(unsafe.Sizeof(pe32))
|
||
|
||
// 获取第一个进程
|
||
if err := syscall.Process32First(hSnapshot, &pe32); err != nil {
|
||
globalLogger.Error("获取第一个进程失败: %v", err)
|
||
return
|
||
}
|
||
|
||
// 遍历所有进程查找helper.exe
|
||
for {
|
||
// 将进程名从UTF-16转换为Go字符串
|
||
processName := syscall.UTF16ToString(pe32.ExeFile[:])
|
||
|
||
// 匹配进程名
|
||
if strings.EqualFold(processName, "helper.exe") || strings.EqualFold(processName, "helper_auto_reply.exe") {
|
||
pid := pe32.ProcessID
|
||
// 不要关闭当前进程
|
||
if int(pid) == os.Getpid() {
|
||
continue
|
||
}
|
||
|
||
globalLogger.Info("找到helper.exe进程,进程ID: %d,尝试关闭...", pid)
|
||
|
||
// 尝试打开进程
|
||
processHandle, err := syscall.OpenProcess(syscall.PROCESS_TERMINATE, false, pid)
|
||
if err != nil {
|
||
globalLogger.Error("无法打开进程ID %d: %v", pid, err)
|
||
} else {
|
||
defer syscall.CloseHandle(processHandle)
|
||
// 尝试终止进程
|
||
if err := syscall.TerminateProcess(processHandle, 0); err != nil {
|
||
globalLogger.Error("终止进程ID %d失败: %v", pid, err)
|
||
} else {
|
||
globalLogger.Info("成功终止进程ID %d", pid)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 获取下一个进程
|
||
if err := syscall.Process32Next(hSnapshot, &pe32); err != nil {
|
||
// 如果没有更多进程,跳出循环
|
||
if err == syscall.ERROR_NO_MORE_FILES {
|
||
break
|
||
}
|
||
globalLogger.Error("获取下一个进程失败: %v", err)
|
||
break
|
||
}
|
||
}
|
||
|
||
globalLogger.Info("helper.exe进程检查和关闭操作完成")
|
||
}
|
||
|
||
// 启动辅助程序的函数
|
||
func startHelperProgram() {
|
||
// 检查是否已经有辅助进程在运行
|
||
if helperProcess != nil {
|
||
// 尝试获取进程信息来验证它是否还在运行
|
||
process, err := os.FindProcess(helperProcess.Pid)
|
||
if err == nil && process != nil {
|
||
globalLogger.Info("辅助程序进程已在运行,PID: %d,无需再次启动", helperProcess.Pid)
|
||
return
|
||
}
|
||
// 进程句柄存在但无法找到进程,可能已终止,继续启动新进程
|
||
globalLogger.Info("检测到已终止的辅助程序进程句柄,准备启动新进程")
|
||
}
|
||
|
||
// 获取当前可执行文件路径
|
||
exePath, err := os.Executable()
|
||
if err != nil {
|
||
globalLogger.Error("无法获取可执行文件路径: %v", err)
|
||
return
|
||
}
|
||
|
||
// 获取当前目录
|
||
currentDir := filepath.Dir(exePath)
|
||
globalLogger.Debug("当前可执行文件目录: %s", currentDir)
|
||
|
||
// 构建辅助程序路径,优先使用自动客服修复版helper,避免旧helper.exe被系统占用时无法替换
|
||
helperPath := filepath.Join(currentDir, "helper_auto_reply.exe")
|
||
if _, err := os.Stat(helperPath); os.IsNotExist(err) {
|
||
helperPath = filepath.Join(currentDir, "helper.exe")
|
||
}
|
||
globalLogger.Debug("尝试启动辅助程序路径: %s", helperPath)
|
||
|
||
// 检查辅助程序是否存在
|
||
if _, err := os.Stat(helperPath); os.IsNotExist(err) {
|
||
globalLogger.Warn("32位辅助程序不存在: %s", helperPath)
|
||
return
|
||
}
|
||
|
||
// 在Windows平台上,使用更底层的Windows API来创建进程,确保完全隐藏窗口
|
||
if runtime.GOOS == "windows" {
|
||
// 使用Windows API直接创建进程,避免使用exec.Command可能导致的窗口闪烁
|
||
var si syscall.StartupInfo
|
||
var pi syscall.ProcessInformation
|
||
|
||
// 设置STARTUPINFO结构体以隐藏窗口
|
||
si.Cb = uint32(unsafe.Sizeof(si))
|
||
si.Flags = syscall.STARTF_USESHOWWINDOW
|
||
si.ShowWindow = syscall.SW_HIDE
|
||
|
||
// 定义Windows API常量
|
||
const (
|
||
CREATE_NO_WINDOW = 0x08000000
|
||
CREATE_NEW_PROCESS_GROUP = 0x00000200
|
||
DETACHED_PROCESS = 0x00000010
|
||
CREATE_BREAKAWAY_FROM_JOB = 0x01000000 // 新增标志,有助于完全独立于父进程
|
||
CREATE_DEFAULT_ERROR_MODE = 0x04000000 // 新增标志,避免显示系统错误对话框
|
||
)
|
||
|
||
// 进程创建标志组合 - 使用更多标志确保窗口完全隐藏
|
||
flags := uint32(CREATE_NO_WINDOW | CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS | CREATE_BREAKAWAY_FROM_JOB | CREATE_DEFAULT_ERROR_MODE)
|
||
|
||
// 调用Windows API创建进程
|
||
err := syscall.CreateProcess(
|
||
nil, // lpApplicationName
|
||
syscall.StringToUTF16Ptr(helperPath), // lpCommandLine
|
||
nil, // lpProcessAttributes
|
||
nil, // lpThreadAttributes
|
||
false, // bInheritHandles
|
||
flags, // dwCreationFlags
|
||
nil, // lpEnvironment
|
||
nil, // lpCurrentDirectory
|
||
&si, // lpStartupInfo
|
||
&pi, // lpProcessInformation
|
||
)
|
||
|
||
if err != nil {
|
||
globalLogger.Error("[辅助程序] 创建进程失败: %v", err)
|
||
} else {
|
||
// 保存进程句柄以便后续操作
|
||
helperProcess = &os.Process{Pid: int(pi.ProcessId)}
|
||
|
||
// 关闭不需要的句柄
|
||
syscall.CloseHandle(syscall.Handle(pi.ThreadId))
|
||
syscall.CloseHandle(syscall.Handle(pi.ProcessId))
|
||
|
||
globalLogger.Info("[辅助程序] 已成功启动,进程ID: %d", pi.ProcessId)
|
||
}
|
||
} else {
|
||
// 非Windows平台使用标准方法
|
||
cmd := exec.Command(helperPath)
|
||
if err := cmd.Start(); err != nil {
|
||
globalLogger.Error("[辅助程序] 启动失败: %v", err)
|
||
} else {
|
||
helperProcess = cmd.Process
|
||
globalLogger.Info("[辅助程序] 已成功启动,进程ID: %d", helperProcess.Pid)
|
||
}
|
||
}
|
||
|
||
// 添加延迟,确保辅助程序有足够时间启动
|
||
time.Sleep(1 * time.Second)
|
||
|
||
// 验证辅助进程是否仍在运行
|
||
if helperProcess != nil {
|
||
process, err := os.FindProcess(helperProcess.Pid)
|
||
if err != nil {
|
||
globalLogger.Error("无法查找辅助程序进程: %v", err)
|
||
} else {
|
||
globalLogger.Info("辅助程序进程验证成功: PID=%d", process.Pid)
|
||
}
|
||
} else {
|
||
globalLogger.Warn("辅助程序进程句柄为空")
|
||
}
|
||
}
|
||
|
||
// 优雅地关闭辅助程序
|
||
func shutdownHelperProgram() {
|
||
if helperProcess != nil {
|
||
globalLogger.Info("准备关闭辅助程序...")
|
||
|
||
// 在Windows平台上使用更健壮的方式终止进程
|
||
if runtime.GOOS == "windows" {
|
||
// 尝试通过Windows API打开进程
|
||
processHandle, err := syscall.OpenProcess(syscall.PROCESS_TERMINATE, false, uint32(helperProcess.Pid))
|
||
if err != nil {
|
||
globalLogger.Error("无法打开辅助程序进程: %v", err)
|
||
} else {
|
||
defer syscall.CloseHandle(processHandle)
|
||
// 先尝试使用TerminateProcess终止进程
|
||
if err := syscall.TerminateProcess(processHandle, 0); err != nil {
|
||
globalLogger.Error("使用Windows API终止辅助程序失败: %v", err)
|
||
} else {
|
||
globalLogger.Info("使用Windows API成功终止辅助程序")
|
||
// 等待一小段时间确保进程完全终止
|
||
time.Sleep(300 * time.Millisecond)
|
||
return
|
||
}
|
||
}
|
||
}
|
||
|
||
// 通用方法:先尝试优雅地终止进程(发送终止信号)
|
||
err := helperProcess.Signal(syscall.SIGTERM)
|
||
if err != nil {
|
||
globalLogger.Error("无法发送终止信号到辅助程序: %v", err)
|
||
// 如果优雅关闭失败,强制终止进程
|
||
err = helperProcess.Kill()
|
||
if err != nil {
|
||
globalLogger.Error("无法强制终止辅助程序: %v", err)
|
||
} else {
|
||
globalLogger.Info("已强制终止辅助程序")
|
||
}
|
||
} else {
|
||
globalLogger.Info("已发送终止信号到辅助程序")
|
||
// 等待一段时间让辅助程序有机会进行清理
|
||
time.Sleep(500 * time.Millisecond)
|
||
}
|
||
} else {
|
||
globalLogger.Info("没有检测到运行中的辅助程序")
|
||
}
|
||
}
|
||
|
||
func main() {
|
||
// 初始化全局日志器
|
||
var err error
|
||
// 不要使用os.Args[0],因为在Wails构建过程中它会返回临时文件名
|
||
// 直接使用固定的应用程序名称
|
||
exeName := "qiweimanager"
|
||
// 添加调试信息,显示使用的exeName
|
||
fmt.Fprintf(os.Stderr, "使用的应用程序名称(exeName): %s\n", exeName)
|
||
globalLogger, err = logger.NewLogger(exeName, logEnabled, logLevel)
|
||
if err != nil {
|
||
// 如果初始化失败,使用标准错误输出
|
||
os.Stderr.WriteString(fmt.Sprintf("初始化日志系统失败: %v\n", err))
|
||
// 创建一个简单的控制台日志器作为备用
|
||
globalLogger = &logger.Logger{
|
||
Logger: log.New(os.Stderr, "", log.LstdFlags),
|
||
LogLevel: logLevel,
|
||
LogEnabled: true,
|
||
}
|
||
} else {
|
||
defer globalLogger.Close()
|
||
// 记录日志文件路径信息
|
||
fmt.Fprintf(os.Stderr, "日志系统初始化成功\n")
|
||
}
|
||
|
||
// 打印系统信息和架构
|
||
globalLogger.Debug("系统架构: %s", runtime.GOARCH)
|
||
globalLogger.Debug("操作系统: %s", runtime.GOOS)
|
||
|
||
// 初始化全局配置
|
||
if err := config.InitGlobalConfig(exeName, globalLogger); err != nil {
|
||
globalLogger.Error("初始化配置失败: %v", err)
|
||
} else {
|
||
globalLogger.Info("配置系统初始化成功")
|
||
}
|
||
|
||
// 启动日志清理调度器,每天清理超过30天的旧日志
|
||
logDir := globalLogger.GetLogDir()
|
||
globalLogger.Info("启动日志清理调度器,日志目录: %s", logDir)
|
||
logger.StartLogCleanupScheduler(logDir, 30, 24*time.Hour)
|
||
|
||
// 检查并关闭系统中已存在的helper.exe进程
|
||
checkAndCloseHelperProcess()
|
||
|
||
// 尝试启动32位辅助程序
|
||
go startHelperProgram()
|
||
|
||
// 允许应用程序在任何架构下运行
|
||
globalLogger.Debug("允许应用程序在当前架构下运行")
|
||
|
||
// 打印调试信息
|
||
if globalLogger != nil {
|
||
globalLogger.Info("Starting qiweimanager application...")
|
||
} else {
|
||
fmt.Fprintf(os.Stderr, "Warning: globalLogger is nil\n")
|
||
}
|
||
|
||
// Create an instance of the app structure
|
||
globalLogger.Info("创建应用程序实例...")
|
||
app := NewApp()
|
||
globalLogger.Info("应用程序实例创建成功")
|
||
|
||
// Create application with options
|
||
globalLogger.Info("准备启动Wails应用...")
|
||
err = wails.Run(&options.App{
|
||
Title: "灵泽万川企微售后客服",
|
||
Width: 800,
|
||
Height: 600,
|
||
AssetServer: &assetserver.Options{
|
||
Assets: assets,
|
||
},
|
||
// 使用更安全的背景色设置
|
||
BackgroundColour: &options.RGBA{R: 240, G: 240, B: 240, A: 255},
|
||
OnStartup: app.startup,
|
||
OnBeforeClose: app.confirmArchivePendingAfterSalesBeforeClose,
|
||
OnShutdown: func(ctx context.Context) {
|
||
globalLogger.Info("Application is shutting down...")
|
||
|
||
// 关闭辅助程序
|
||
shutdownHelperProgram()
|
||
},
|
||
OnDomReady: func(ctx context.Context) {
|
||
globalLogger.Info("DOM is ready!")
|
||
},
|
||
Windows: &windows.Options{
|
||
// 设置有效的WebView2用户数据路径,避免初始化失败
|
||
WebviewUserDataPath: filepath.Join(os.TempDir(), "qiweimanager-webview"),
|
||
// 禁用透明背景,提高稳定性
|
||
WebviewIsTransparent: false,
|
||
},
|
||
// 可选:添加一些优化设置
|
||
SingleInstanceLock: &options.SingleInstanceLock{
|
||
UniqueId: "starbot-pro-application",
|
||
},
|
||
Bind: []interface{}{
|
||
app,
|
||
},
|
||
})
|
||
|
||
// 确保辅助程序在主程序退出时也退出
|
||
shutdownHelperProgram()
|
||
|
||
if err != nil {
|
||
globalLogger.Error("应用程序启动失败: %v", err)
|
||
} else {
|
||
globalLogger.Info("应用程序正常退出")
|
||
}
|
||
}
|