Initial qiwei secondary development handoff
This commit is contained in:
373
main.go
Normal file
373
main.go
Normal file
@@ -0,0 +1,373 @@
|
||||
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("应用程序正常退出")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user