package main import ( "bufio" "context" "encoding/json" "fmt" "gopkg.in/yaml.v3" "image" "image/color" "io" "io/ioutil" "net" "net/http" "os" "os/exec" "path/filepath" "strings" "syscall" "time" "unsafe" "gioui.org/app" "gioui.org/io/key" "gioui.org/layout" "gioui.org/op" "gioui.org/op/clip" "gioui.org/op/paint" "gioui.org/unit" "gioui.org/widget" "gioui.org/widget/material" ) // ============================ 常量定义 ============================ const ( ColorReset = "\033[0m" ColorRed = "\033[31m" ColorGreen = "\033[32m" ColorYellow = "\033[33m" ColorBlue = "\033[34m" ColorMagenta = "\033[35m" ColorCyan = "\033[36m" ColorWhite = "\033[37m" ColorGray = "\033[90m" ColorBoldRed = "\033[1;31m" ColorBoldGreen = "\033[1;32m" ColorBoldYellow = "\033[1;33m" ColorBoldBlue = "\033[1;34m" ColorBgRed = "\033[41m" ColorBgGreen = "\033[42m" ) // ============================ 结构体定义 ============================ type VersionConfig struct { CsvVersion string `json:"csv"` KongfzVersion string `json:"kongfz"` LoggerVersion string `json:"logger"` ModuleErpVersion string `json:"module-erp"` ModuleKongfzVersion string `json:"module-kongfz"` ModuleTaskPoolVersion string `json:"module-taskPool"` ModuleVerifyPriceVersion string `json:"module-verifyPrice"` ModuleCenterBookVersion string `json:"module-centerBook"` ModuleLoginVersion string `json:"module-login"` PicToolVersion string `json:"picTool"` ProxyVersion string `json:"proxy"` } type VerifyPriceYAML struct { VerifyPriceLatestVersion string `yaml:"verifyPriceLatestVersion"` } type VersionInfo struct { LatestVersion string `json:"latestVersion"` HistoricalVersions []string `json:"historicalVersions"` VerifyPriceLatestVersion string `json:"verifyPriceLatestVersion"` } // ============================ Gio GUI 结构体 ============================ type UpdaterApp struct { window *app.Window theme *material.Theme // 小部件 progress float32 statusText string actionBtn widget.Clickable showConsole bool consoleText string logEntries []string isPaused bool isUpdating bool isComplete bool isChecking bool isDownloading bool totalSize int64 downloaded int64 currentSpeed float64 startTime time.Time // 更新状态 currentFile string totalFiles int completedFiles int // 数据 versionsJSON *VersionConfig verifyPriceVersion *VersionInfo localConfig VerifyPriceYAML currentDir string yamlPath string // 布局 ops op.Ops scroll widget.List } // ============================ 控制台输出函数 ============================ func printInfo(format string, v ...interface{}) { message := fmt.Sprintf(format, v...) fmt.Printf(ColorCyan + "[信息] " + message + ColorReset + "\n") if globalApp != nil { globalApp.addLog("[信息] " + message) } } func printSuccess(format string, v ...interface{}) { message := fmt.Sprintf(format, v...) fmt.Printf(ColorGreen + "[成功] " + message + ColorReset + "\n") if globalApp != nil { globalApp.addLog("[成功] " + message) } } func printWarning(format string, v ...interface{}) { message := fmt.Sprintf(format, v...) fmt.Printf(ColorYellow + "[警告] " + message + ColorReset + "\n") if globalApp != nil { globalApp.addLog("[警告] " + message) } } func printError(format string, v ...interface{}) { message := fmt.Sprintf(format, v...) fmt.Printf(ColorRed + "[错误] " + message + ColorReset + "\n") if globalApp != nil { globalApp.addLog("[错误] " + message) } } func printDebug(format string, v ...interface{}) { message := fmt.Sprintf(format, v...) fmt.Printf(ColorGray + "[调试] " + message + ColorReset + "\n") if globalApp != nil { globalApp.addLog("[调试] " + message) } } func printBanner(format string, v ...interface{}) { message := fmt.Sprintf(format, v...) fmt.Printf(ColorBoldBlue + "== " + message + " ==\n" + ColorReset) if globalApp != nil { globalApp.addLog("== " + message + " ==") } } func printDownload(format string, v ...interface{}) { message := fmt.Sprintf(format, v...) fmt.Printf(ColorBoldGreen + "[下载] " + message + ColorReset + "\n") if globalApp != nil { globalApp.addLog("[下载] " + message) } } func printVersionInfo(label, version string) { message := fmt.Sprintf("%-30s: %s", label, version) fmt.Printf(ColorBoldYellow+"%-30s: "+ColorCyan+"%s"+ColorReset+"\n", label, version) if globalApp != nil { globalApp.addLog(message) } } // ============================ 全局变量 ============================ var globalApp *UpdaterApp // ============================ Gio GUI 方法 ============================ // NewUpdaterApp 创建新的更新器应用 func NewUpdaterApp() *UpdaterApp { // 使用新的 Window 创建方式 window := new(app.Window) // 设置窗口选项 window.Option( app.Title("核价软件更新器"), app.Size(unit.Dp(800), unit.Dp(600)), app.MinSize(unit.Dp(600), unit.Dp(400)), ) // 创建主题 theme := material.NewTheme() appNew := &UpdaterApp{ window: window, theme: theme, progress: 0.0, statusText: "初始化更新器...", isPaused: false, isUpdating: true, startTime: time.Now(), showConsole: true, totalFiles: 12, // DLL文件数量 + 主程序 scroll: widget.List{ List: layout.List{ Axis: layout.Vertical, }, }, } globalApp = appNew return appNew } // addLog 添加日志条目 func (u *UpdaterApp) addLog(message string) { u.logEntries = append(u.logEntries, message) // 保持最后100条日志 if len(u.logEntries) > 100 { u.logEntries = u.logEntries[len(u.logEntries)-100:] } u.window.Invalidate() } // updateStatus 更新状态文本 func (u *UpdaterApp) updateStatus(status string) { u.statusText = status u.addLog(status) u.window.Invalidate() } // updateProgress 更新进度 func (u *UpdaterApp) updateProgress(progress float32) { u.progress = progress u.window.Invalidate() } // updateFileProgress 更新文件进度 func (u *UpdaterApp) updateFileProgress(currentFile string, downloaded, totalSize int64) { u.currentFile = currentFile u.downloaded = downloaded u.totalSize = totalSize if totalSize > 0 { u.progress = float32(downloaded) / float32(totalSize) } // 计算速度 elapsed := time.Since(u.startTime).Seconds() if elapsed > 0 && downloaded > 0 { u.currentSpeed = float64(downloaded) / elapsed / 1024 / 1024 } u.window.Invalidate() } // updateFileCount 更新文件计数 func (u *UpdaterApp) updateFileCount(completed int) { u.completedFiles = completed // 计算总体进度(文件计数占30%,文件内进度占70%) fileProgress := float32(completed) / float32(u.totalFiles) overallProgress := 0.3*fileProgress + 0.7*u.progress u.updateProgress(overallProgress) u.window.Invalidate() } // Run 运行GUI主循环 func (u *UpdaterApp) Run() error { // 启动更新过程 go u.startUpdateProcess() var ops op.Ops for { ev := u.window.Event() switch e := ev.(type) { case app.DestroyEvent: return e.Err case app.FrameEvent: gtx := app.NewContext(&ops, e) u.handleEvents(gtx) u.drawUI(gtx) e.Frame(gtx.Ops) case key.Event: if e.Name == "C" && e.Modifiers.Contain(key.ModCtrl) { u.showConsole = !u.showConsole u.window.Invalidate() } } } } // handleEvents 处理用户事件 func (u *UpdaterApp) handleEvents(gtx layout.Context) { // 处理按钮点击 if u.actionBtn.Clicked(gtx) { if u.isComplete { // 启动核价软件 u.startVerifyPriceApp() } else if u.isPaused { // 继续更新 u.isPaused = false u.updateStatus("继续更新...") } else { // 暂停更新 u.isPaused = true u.updateStatus("更新已暂停") } } } // drawUI 绘制用户界面 func (u *UpdaterApp) drawUI(gtx layout.Context) layout.Dimensions { // 背景色 paint.Fill(gtx.Ops, color.NRGBA{R: 245, G: 245, B: 245, A: 255}) return layout.Flex{ Axis: layout.Vertical, Spacing: layout.SpaceBetween, }.Layout(gtx, // 标题栏 layout.Rigid(u.drawHeader), // 主内容区域 layout.Flexed(1, u.drawMainContent), // 底部控制栏 layout.Rigid(u.drawFooter), ) } // drawHeader 绘制标题栏 func (u *UpdaterApp) drawHeader(gtx layout.Context) layout.Dimensions { return layout.Inset{ Top: unit.Dp(10), Bottom: unit.Dp(10), Left: unit.Dp(20), Right: unit.Dp(20), }.Layout(gtx, func(gtx layout.Context) layout.Dimensions { return layout.Flex{ Axis: layout.Horizontal, Spacing: layout.SpaceBetween, Alignment: layout.Middle, }.Layout(gtx, layout.Rigid(func(gtx layout.Context) layout.Dimensions { title := material.H4(u.theme, "核价软件更新器") title.Color = color.NRGBA{R: 0, G: 100, B: 200, A: 255} return title.Layout(gtx) }), layout.Rigid(func(gtx layout.Context) layout.Dimensions { // 状态文本和颜色的逻辑保持不变 statusText := "就绪" if u.isChecking { statusText = "检查中" } else if u.isDownloading { statusText = "下载中" } else if u.isPaused { statusText = "已暂停" } else if u.isComplete { statusText = "已完成" } // 根据状态设置文本颜色 statusLabel := material.Body2(u.theme, statusText) if u.isPaused { statusLabel.Color = color.NRGBA{R: 241, G: 196, B: 15, A: 255} // 黄色 } else if u.isDownloading { statusLabel.Color = color.NRGBA{R: 52, G: 152, B: 219, A: 255} // 蓝色 } else if u.isChecking { statusLabel.Color = color.NRGBA{R: 155, G: 89, B: 182, A: 255} // 紫色 } else if u.isComplete { statusLabel.Color = color.NRGBA{R: 46, G: 204, B: 113, A: 255} // 绿色 } else { statusLabel.Color = color.NRGBA{R: 120, G: 120, B: 120, A: 255} // 灰色 } return material.Body2(u.theme, statusText).Layout(gtx) }), ) }) } // drawMainContent 绘制主内容区域 func (u *UpdaterApp) drawMainContent(gtx layout.Context) layout.Dimensions { return layout.Flex{ Axis: layout.Vertical, Spacing: layout.SpaceSides, }.Layout(gtx, // 状态信息区域 layout.Rigid(u.drawStatusInfo), // 进度条区域 layout.Rigid(u.drawProgressBar), // 文件信息区域 layout.Rigid(u.drawFileInfo), // 日志区域(可折叠) layout.Flexed(1, u.drawLogArea), ) } // drawStatusInfo 绘制状态信息 func (u *UpdaterApp) drawStatusInfo(gtx layout.Context) layout.Dimensions { return layout.Inset{ Left: unit.Dp(20), Right: unit.Dp(20), Top: unit.Dp(10), }.Layout(gtx, func(gtx layout.Context) layout.Dimensions { return layout.Flex{ Axis: layout.Vertical, Spacing: layout.SpaceEnd, }.Layout(gtx, layout.Rigid(func(gtx layout.Context) layout.Dimensions { status := material.H6(u.theme, u.statusText) status.Color = color.NRGBA{R: 60, G: 60, B: 60, A: 255} return status.Layout(gtx) }), layout.Rigid(func(gtx layout.Context) layout.Dimensions { if u.currentFile != "" { fileInfo := material.Caption(u.theme, "当前文件: "+u.currentFile) return fileInfo.Layout(gtx) } return layout.Dimensions{} }), ) }) } // drawProgressBar 绘制进度条 func (u *UpdaterApp) drawProgressBar(gtx layout.Context) layout.Dimensions { return layout.Inset{ Left: unit.Dp(20), Right: unit.Dp(20), Top: unit.Dp(15), Bottom: unit.Dp(15), }.Layout(gtx, func(gtx layout.Context) layout.Dimensions { // 进度条背景 barHeight := gtx.Dp(unit.Dp(30)) barWidth := gtx.Constraints.Max.X // 圆角矩形背景 rrect := clip.UniformRRect( image.Rect(0, 0, barWidth, barHeight), gtx.Dp(unit.Dp(6)), ) paint.FillShape(gtx.Ops, color.NRGBA{R: 230, G: 230, B: 230, A: 255}, rrect.Op(gtx.Ops)) // 进度条前景 progressWidth := int(float32(barWidth) * u.progress) if progressWidth > 0 { progressRrect := clip.UniformRRect( image.Rect(0, 0, progressWidth, barHeight), gtx.Dp(unit.Dp(6)), ) // 根据进度选择颜色 var barColor color.NRGBA if u.isComplete { barColor = color.NRGBA{R: 46, G: 204, B: 113, A: 255} // 绿色 } else if u.progress < 0.3 { barColor = color.NRGBA{R: 231, G: 76, B: 60, A: 255} // 红色 } else if u.progress < 0.7 { barColor = color.NRGBA{R: 241, G: 196, B: 15, A: 255} // 黄色 } else { barColor = color.NRGBA{R: 52, G: 152, B: 219, A: 255} // 蓝色 } paint.FillShape(gtx.Ops, barColor, progressRrect.Op(gtx.Ops)) } // 使用 Flex 布局来居中文本 return layout.Flex{ Axis: layout.Vertical, Spacing: layout.SpaceSides, Alignment: layout.Middle, // 水平居中 }.Layout(gtx, // 占位空间,让文本垂直居中 layout.Flexed(1, func(gtx layout.Context) layout.Dimensions { return layout.Dimensions{} }), // 文本行 layout.Rigid(func(gtx layout.Context) layout.Dimensions { return layout.Flex{ Axis: layout.Horizontal, Spacing: layout.SpaceSides, Alignment: layout.Middle, // 水平居中 }.Layout(gtx, // 占位空间,让文本水平居中 layout.Flexed(1, func(gtx layout.Context) layout.Dimensions { return layout.Dimensions{} }), // 实际文本 layout.Rigid(func(gtx layout.Context) layout.Dimensions { text := material.Body1(u.theme, fmt.Sprintf("%.1f%%", u.progress*100)) if u.progress < 0.5 { text.Color = color.NRGBA{R: 60, G: 60, B: 60, A: 255} } else { text.Color = color.NRGBA{R: 255, G: 255, B: 255, A: 255} } return text.Layout(gtx) }), // 占位空间,让文本水平居中 layout.Flexed(1, func(gtx layout.Context) layout.Dimensions { return layout.Dimensions{} }), ) }), // 占位空间,让文本垂直居中 layout.Flexed(50, func(gtx layout.Context) layout.Dimensions { return layout.Dimensions{} }), ) }) } // drawFileInfo 绘制文件信息 func (u *UpdaterApp) drawFileInfo(gtx layout.Context) layout.Dimensions { return layout.Inset{ Left: unit.Dp(20), Right: unit.Dp(20), Bottom: unit.Dp(10), }.Layout(gtx, func(gtx layout.Context) layout.Dimensions { return layout.Flex{ Axis: layout.Horizontal, Spacing: layout.SpaceAround, Alignment: layout.Middle, }.Layout(gtx, layout.Rigid(func(gtx layout.Context) layout.Dimensions { fileCount := fmt.Sprintf("文件: %d/%d", u.completedFiles, u.totalFiles) return material.Caption(u.theme, fileCount).Layout(gtx) }), layout.Rigid(func(gtx layout.Context) layout.Dimensions { var sizeInfo string if u.totalSize > 0 { sizeInfo = fmt.Sprintf("大小: %.2f/%.2f MB", float64(u.downloaded)/(1024*1024), float64(u.totalSize)/(1024*1024)) } else { sizeInfo = "大小: 计算中..." } return material.Caption(u.theme, sizeInfo).Layout(gtx) }), layout.Rigid(func(gtx layout.Context) layout.Dimensions { speedInfo := fmt.Sprintf("速度: %.2f MB/s", u.currentSpeed) return material.Caption(u.theme, speedInfo).Layout(gtx) }), ) }) } // drawLogArea 绘制日志区域 func (u *UpdaterApp) drawLogArea(gtx layout.Context) layout.Dimensions { if !u.showConsole { return layout.Dimensions{} } return layout.Inset{ Left: unit.Dp(20), Right: unit.Dp(20), Top: unit.Dp(10), Bottom: unit.Dp(10), }.Layout(gtx, func(gtx layout.Context) layout.Dimensions { return material.List(u.theme, &u.scroll).Layout(gtx, len(u.logEntries), func(gtx layout.Context, index int) layout.Dimensions { return layout.Inset{ Bottom: unit.Dp(4), }.Layout(gtx, func(gtx layout.Context) layout.Dimensions { logText := material.Body2(u.theme, u.logEntries[index]) logText.Color = color.NRGBA{R: 100, G: 100, B: 100, A: 255} // 根据日志类型设置颜色 logTextStr := u.logEntries[index] if strings.Contains(logTextStr, "[错误]") { logText.Color = color.NRGBA{R: 231, G: 76, B: 60, A: 255} } else if strings.Contains(logTextStr, "[警告]") { logText.Color = color.NRGBA{R: 241, G: 196, B: 15, A: 255} } else if strings.Contains(logTextStr, "[成功]") { logText.Color = color.NRGBA{R: 46, G: 204, B: 113, A: 255} } else if strings.Contains(logTextStr, "[信息]") { logText.Color = color.NRGBA{R: 52, G: 152, B: 219, A: 255} } else if strings.Contains(logTextStr, "[下载]") { logText.Color = color.NRGBA{R: 155, G: 89, B: 182, A: 255} } return logText.Layout(gtx) }) }) }) } // drawFooter 绘制底部控制栏 func (u *UpdaterApp) drawFooter(gtx layout.Context) layout.Dimensions { return layout.Inset{ Top: unit.Dp(10), Bottom: unit.Dp(15), Left: unit.Dp(20), Right: unit.Dp(20), }.Layout(gtx, func(gtx layout.Context) layout.Dimensions { return layout.Flex{ Axis: layout.Horizontal, Spacing: layout.SpaceBetween, Alignment: layout.Middle, }.Layout(gtx, //layout.Rigid(func(gtx layout.Context) layout.Dimensions { // hint := material.Caption(u.theme, "提示: 按 Ctrl+C 切换日志显示") // hint.Color = color.NRGBA{R: 150, G: 150, B: 150, A: 255} // return hint.Layout(gtx) //}), layout.Rigid(func(gtx layout.Context) layout.Dimensions { var btnText string var btnColor color.NRGBA if u.isComplete { btnText = "启动核价软件" btnColor = color.NRGBA{R: 46, G: 204, B: 113, A: 255} } else if u.isPaused { btnText = "继续更新" btnColor = color.NRGBA{R: 46, G: 204, B: 113, A: 255} } else { btnText = "暂停更新" btnColor = color.NRGBA{R: 241, G: 196, B: 15, A: 255} } btn := material.Button(u.theme, &u.actionBtn, btnText) btn.CornerRadius = unit.Dp(8) btn.TextSize = unit.Sp(14) btn.Background = btnColor btn.Color = color.NRGBA{R: 255, G: 255, B: 255, A: 255} btn.Inset = layout.UniformInset(unit.Dp(12)) return btn.Layout(gtx) }), ) }) } // ============================ 更新过程 ============================ func (u *UpdaterApp) startUpdateProcess() { // 初始化 u.updateStatus("正在初始化...") u.updateProgress(0.05) // 获取当前目录 currentDir, err := os.Getwd() if err != nil { u.updateStatus(fmt.Sprintf("获取当前目录失败: %v", err)) return } u.currentDir = currentDir u.yamlPath = filepath.Join(currentDir, "version.yaml") // 步骤1: 检查核价软件版本 u.isChecking = true u.updateStatus("正在检查核价软件版本...") u.updateProgress(0.1) u.verifyPriceVersion, err = getVerifyPriceVersionJSON() if err != nil { u.updateStatus(fmt.Sprintf("读取核价软件版本失败: %v", err)) u.isChecking = false return } u.isChecking = false // 步骤2: 读取本地配置 u.updateStatus("正在读取本地配置...") u.updateProgress(0.15) data, err := ioutil.ReadFile(u.yamlPath) if err == nil { yaml.Unmarshal(data, &u.localConfig) } else { u.localConfig = VerifyPriceYAML{} } // 步骤3: 检查并下载主程序 u.updateStatus("检查核价软件主程序...") u.updateProgress(0.2) err = u.checkAndDownloadMainApp() if err != nil { u.updateStatus(fmt.Sprintf("主程序处理失败: %v", err)) } // 步骤4: 检查DLL组件版本 u.isChecking = true u.updateStatus("正在检查DLL组件版本...") u.updateProgress(0.3) u.versionsJSON, err = getVersionsJSON() if err != nil { u.updateStatus(fmt.Sprintf("读取DLL版本失败: %v", err)) u.isChecking = false return } u.isChecking = false // 步骤5: 下载所有DLL文件 u.isDownloading = true u.updateStatus("开始更新DLL组件...") u.updateProgress(0.4) err = u.updateAllDLLs() if err != nil { u.updateStatus(fmt.Sprintf("DLL更新失败: %v", err)) } u.isDownloading = false // 步骤6: 完成 u.updateStatus("所有更新已完成!") u.updateProgress(1.0) u.isComplete = true u.isUpdating = false // 自动启动核价软件(3秒后) go func() { time.Sleep(3 * time.Second) if u.isComplete { u.startVerifyPriceApp() } }() } func (u *UpdaterApp) checkAndDownloadMainApp() error { exePath := filepath.Join(u.currentDir, "VerifyPriceApp.exe") if _, err := os.Stat(exePath); os.IsNotExist(err) { // 文件缺失,下载 u.updateStatus("发现核价软件缺失,开始下载...") url := fmt.Sprintf("https://newverifyprice.buzhiyushu.cn/exe/VerifyPriceApp_%s.exe", u.verifyPriceVersion.VerifyPriceLatestVersion) err = u.downloadWithProgress(url, exePath, "VerifyPriceApp.exe") if err != nil { return fmt.Errorf("下载失败: %v", err) } u.updateStatus("核价软件下载完成") u.localConfig.VerifyPriceLatestVersion = u.verifyPriceVersion.VerifyPriceLatestVersion writeYAML(u.yamlPath, u.localConfig) } else { // 检查版本 if u.verifyPriceVersion.VerifyPriceLatestVersion != u.localConfig.VerifyPriceLatestVersion { u.updateStatus("发现新版本,开始更新...") url := fmt.Sprintf("https://newverifyprice.buzhiyushu.cn/exe/VerifyPriceApp_%s.exe", u.verifyPriceVersion.VerifyPriceLatestVersion) err = u.downloadWithProgress(url, exePath, "VerifyPriceApp.exe") if err != nil { return fmt.Errorf("更新失败: %v", err) } u.updateStatus("核价软件更新完成") u.localConfig.VerifyPriceLatestVersion = u.verifyPriceVersion.VerifyPriceLatestVersion writeYAML(u.yamlPath, u.localConfig) } else { u.updateStatus("核价软件已是最新版本") } } u.completedFiles++ u.updateFileCount(u.completedFiles) return nil } func (u *UpdaterApp) updateAllDLLs() error { dlls := []struct { name string version string checker func(string) (string, error) }{ {"csv.dll", u.versionsJSON.CsvVersion, csvDllVersion}, {"kongfz.dll", u.versionsJSON.KongfzVersion, kongfzDllVersion}, {"logger.dll", u.versionsJSON.LoggerVersion, loggerDllVersion}, {"module-centerBook.dll", u.versionsJSON.ModuleCenterBookVersion, moduleCenterBookDllVersion}, {"module-login.dll", u.versionsJSON.ModuleLoginVersion, moduleLoginDllVersion}, {"module-erp.dll", u.versionsJSON.ModuleErpVersion, moduleErpDllVersion}, {"module-kongfz.dll", u.versionsJSON.ModuleKongfzVersion, moduleKongfzDllVersion}, {"module-taskPool.dll", u.versionsJSON.ModuleTaskPoolVersion, moduleTaskPoolDllVersion}, {"module-verifyPrice.dll", u.versionsJSON.ModuleVerifyPriceVersion, moduleVerifyPriceDllVersion}, {"picTool.dll", u.versionsJSON.PicToolVersion, picToolDllVersion}, {"proxy.dll", u.versionsJSON.ProxyVersion, proxyDllVersion}, } for i, dll := range dlls { // 检查是否暂停 for u.isPaused { time.Sleep(100 * time.Millisecond) } u.updateStatus(fmt.Sprintf("检查 %s...", dll.name)) u.currentFile = dll.name // 检查当前版本 currentVersion, err := dll.checker(u.currentDir) if err != nil { u.updateStatus(fmt.Sprintf("检查 %s 失败: %v", dll.name, err)) continue } // 如果需要更新 if dll.version != currentVersion && currentVersion != "" { u.updateStatus(fmt.Sprintf("更新 %s...", dll.name)) dllPath := filepath.Join(u.currentDir, "dll", dll.name) err = downloadDLL("http://36.212.20.113:53300/api/downLoad", dllPath, dll.name, "") if err != nil { u.updateStatus(fmt.Sprintf("更新 %s 失败: %v", dll.name, err)) } else { u.updateStatus(fmt.Sprintf("%s 更新完成", dll.name)) } } else { u.updateStatus(fmt.Sprintf("%s 已是最新版本", dll.name)) } // 更新进度 u.completedFiles++ progress := 0.4 + 0.5*(float32(i+1)/float32(len(dlls))) u.updateProgress(progress) u.updateFileCount(u.completedFiles) // 短暂暂停以避免UI卡顿 time.Sleep(100 * time.Millisecond) } return nil } func (u *UpdaterApp) downloadWithProgress(url, filePath, fileName string) error { u.currentFile = fileName u.startTime = time.Now() u.downloaded = 0 // 创建目录 os.MkdirAll(filepath.Dir(filePath), 0755) // 发送请求 client := &http.Client{Timeout: 30 * time.Minute} resp, err := client.Get(url) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("HTTP错误: %d", resp.StatusCode) } // 获取文件大小 u.totalSize = resp.ContentLength // 创建临时文件 tempPath := filePath + ".tmp" file, err := os.Create(tempPath) if err != nil { return err } defer file.Close() // 下载文件 buf := make([]byte, 32*1024) // 32KB缓冲区 for { // 检查是否暂停 for u.isPaused { time.Sleep(100 * time.Millisecond) } n, err := resp.Body.Read(buf) if n > 0 { _, writeErr := file.Write(buf[:n]) if writeErr != nil { return writeErr } u.downloaded += int64(n) u.updateFileProgress(fileName, u.downloaded, u.totalSize) } if err != nil { if err == io.EOF { break } return err } } // 关闭文件 file.Close() // 重命名为最终文件 if err := os.Rename(tempPath, filePath); err != nil { // 如果重命名失败,尝试复制 if err := copyFile(tempPath, filePath); err != nil { return err } } // 清理临时文件 os.Remove(tempPath) return nil } func (u *UpdaterApp) startVerifyPriceApp() { exePath := filepath.Join(u.currentDir, "VerifyPriceApp.exe") u.updateStatus("正在启动核价软件...") cmd := exec.Command(exePath) cmd.Dir = u.currentDir err := cmd.Start() if err != nil { u.updateStatus(fmt.Sprintf("启动失败: %v", err)) u.updateStatus("请手动运行 VerifyPriceApp.exe") } else { u.updateStatus("核价软件已启动,即将退出...") time.Sleep(2 * time.Second) os.Exit(0) } } // ============================ 主函数 ============================ func main() { //// 隐藏控制台窗口(仅Windows) //hideConsoleWindow() // 检查是否以控制台模式运行 if len(os.Args) > 1 && os.Args[1] == "--console" { runConsoleMode() return } // GUI模式 go func() { app := NewUpdaterApp() if err := app.Run(); err != nil { fmt.Fprintf(os.Stderr, "错误: %v\n", err) os.Exit(1) } }() app.Main() } //// hideConsoleWindow 隐藏控制台窗口(仅Windows有效) //func hideConsoleWindow() { // // 仅在Windows平台下生效 // kernel32 := syscall.NewLazyDLL("kernel32.dll") // getConsoleWindow := kernel32.NewProc("GetConsoleWindow") // showWindow := syscall.NewLazyDLL("user32.dll").NewProc("ShowWindow") // // if hwnd, _, _ := getConsoleWindow.Call(); hwnd != 0 { // showWindow.Call(hwnd, 0) // 0 = SW_HIDE // } //} func runConsoleMode() { // 控制台模式(原main.go的逻辑) printBanner("核价软件更新器") printWarning("更新时请勿操作!!!") // 获取当前工作目录 currentDir, err := os.Getwd() if err != nil { printError("获取当前目录失败: %v", err) return } printInfo("当前目录: %v", currentDir) // 获取选品中心中verify_price_version.json printInfo("正在检查核价软件版本...") verifyPriceVersionJSON, err := getVerifyPriceVersionJSON() if err != nil { printError("读取核价软件版本信息失败: %v", err) return } // 直接读取yaml文件 yamlPath := filepath.Join(currentDir, "version.yaml") data, err := ioutil.ReadFile(yamlPath) if err != nil { printError("读取YAML配置文件失败: %v", err) } // 解析 YAML var config VerifyPriceYAML err = yaml.Unmarshal(data, &config) if err != nil { printError("解析 YAML 配置失败: %v", err) } printVersionInfo("当前核价软件版本号", config.VerifyPriceLatestVersion) // 检查并下载核价软件主程序 printInfo("检查核价软件主程序...") if _, err := os.Stat(filepath.Join(currentDir, "VerifyPriceApp.exe")); os.IsNotExist(err) { printDownload("发现核价软件缺失,开始下载...") url := fmt.Sprintf("https://newverifyprice.buzhiyushu.cn/exe/VerifyPriceApp_%s.exe", verifyPriceVersionJSON.VerifyPriceLatestVersion) err = downloadEXE(url, currentDir, "VerifyPriceApp.exe") if err != nil { if err.Error() == "The process cannot access the file because it is being used by another process." { printWarning("文件被占用,请重新启动与书同行.exe软件") } else { printError("下载核价软件失败: %v", err) } } else { printSuccess("核价软件下载完成") } //修改yaml文件 config.VerifyPriceLatestVersion = verifyPriceVersionJSON.VerifyPriceLatestVersion err = writeYAML(yamlPath, config) if err != nil { printError("更新YAML配置文件失败: %v", err) } } else { if verifyPriceVersionJSON.VerifyPriceLatestVersion != config.VerifyPriceLatestVersion || config.VerifyPriceLatestVersion == "" { printDownload("发现新版本核价软件,开始更新...") url := fmt.Sprintf("https://newverifyprice.buzhiyushu.cn/exe/VerifyPriceApp_%s.exe", verifyPriceVersionJSON.VerifyPriceLatestVersion) err = downloadEXE(url, currentDir, "VerifyPriceApp.exe") if err != nil { if err.Error() == "The process cannot access the file because it is being used by another process." { printWarning("文件被占用,请重新启动与书同行.exe软件") } else { printError("下载核价软件失败: %v", err) } } else { printSuccess("核价软件更新完成") } //修改yaml文件 config.VerifyPriceLatestVersion = verifyPriceVersionJSON.VerifyPriceLatestVersion err = writeYAML(yamlPath, config) if err != nil { printError("更新YAML配置文件失败: %v", err) } } else { printSuccess("核价软件已是最新版本") } } // 获取ES2中version.json printInfo("正在检查DLL组件版本...") versionsJSON, err := getVersionsJSON() if err != nil { printError("读取DLL版本信息失败: %v", err) return } // 打印版本信息标题 printBanner("版本信息") // 打印所有版本信息 printVersionInfo("最新核价软件版本", verifyPriceVersionJSON.VerifyPriceLatestVersion) printVersionInfo("csv.dll版本号:", versionsJSON.CsvVersion) printVersionInfo("kongfz.dll版本号:", versionsJSON.KongfzVersion) printVersionInfo("logger.dll版本号:", versionsJSON.LoggerVersion) printVersionInfo("module-centerBook.dll版本号:", versionsJSON.ModuleCenterBookVersion) printVersionInfo("module-login.dll版本号:", versionsJSON.ModuleLoginVersion) printVersionInfo("module-erp.dll版本号:", versionsJSON.ModuleErpVersion) printVersionInfo("module-kongfz.dll版本号:", versionsJSON.ModuleKongfzVersion) printVersionInfo("module-taskPool.dll版本号:", versionsJSON.ModuleTaskPoolVersion) printVersionInfo("module-verifyPrice.dll版本号:", versionsJSON.ModuleVerifyPriceVersion) printVersionInfo("picTool.dll版本号:", versionsJSON.PicToolVersion) printVersionInfo("proxy.dll版本号:", versionsJSON.ProxyVersion) // 验证并更新DLL文件 printBanner("组件检查与更新") // 验证csv.dll文件版本 printInfo("检查csv.dll...") csvVersion, err := csvDllVersion(currentDir) if err != nil { printWarning("获取csv.dll版本失败: %v", err) } if versionsJSON.CsvVersion != csvVersion && csvVersion != "" { printDownload("csv.dll需要更新") err = downloadDLL("http://36.212.20.113:53300/api/downLoad", filepath.Join(currentDir, "dll", "csv.dll"), "csv.dll", "") if err != nil { printError("下载csv.dll失败: %v", err) } else { printSuccess("csv.dll更新完成") } } else { printSuccess("csv.dll已是最新版本") } // 验证kongfz.dll文件版本 printInfo("检查kongfz.dll...") kongfzVersion, err := kongfzDllVersion(currentDir) if err != nil { printWarning("获取kongfz.dll版本失败: %v", err) } if versionsJSON.KongfzVersion != kongfzVersion && kongfzVersion != "" { printDownload("kongfz.dll需要更新") err = downloadDLL("http://36.212.20.113:53300/api/downLoad", filepath.Join(currentDir, "dll", "kongfz.dll"), "kongfz.dll", "") if err != nil { printError("下载kongfz.dll失败: %v", err) } else { printSuccess("kongfz.dll更新完成") } } else { printSuccess("kongfz.dll已是最新版本") } // 验证logger.dll文件版本 printInfo("检查logger.dll...") loggerVersion, err := loggerDllVersion(currentDir) if err != nil { printWarning("获取logger.dll版本失败: %v", err) } if versionsJSON.LoggerVersion != loggerVersion && loggerVersion != "" { printDownload("logger.dll需要更新") err = downloadDLL("http://36.212.20.113:53300/api/downLoad", filepath.Join(currentDir, "dll", "logger.dll"), "logger.dll", "") if err != nil { printError("下载logger.dll失败: %v", err) } else { printSuccess("logger.dll更新完成") } } else { printSuccess("logger.dll已是最新版本") } // 验证module-centerBook.dll文件版本 printInfo("检查module-centerBook.dll...") moduleCenterBookVersion, err := moduleCenterBookDllVersion(currentDir) if err != nil { printWarning("获取module-centerBook.dll版本失败: %v", err) } if versionsJSON.ModuleCenterBookVersion != moduleCenterBookVersion && moduleCenterBookVersion != "" { printDownload("module-centerBook.dll需要更新") err = downloadDLL("http://36.212.20.113:53300/api/downLoad", filepath.Join(currentDir, "dll", "module-centerBook.dll"), "module-centerBook.dll", "") if err != nil { printError("下载module-centerBook.dll失败: %v", err) } else { printSuccess("module-centerBook.dll更新完成") } } else { printSuccess("module-centerBook.dll已是最新版本") } // 验证module-login.dll文件版本 printInfo("检查module-login.dll...") moduleLoginVersion, err := moduleLoginDllVersion(currentDir) if err != nil { printWarning("获取module-login.dll版本失败: %v", err) } if versionsJSON.ModuleLoginVersion != moduleLoginVersion && moduleLoginVersion != "" { printDownload("module-login.dll需要更新") err = downloadDLL("http://36.212.20.113:53300/api/downLoad", filepath.Join(currentDir, "dll", "module-login.dll"), "module-login.dll", "") if err != nil { printError("下载module-login.dll失败: %v", err) } else { printSuccess("module-login.dll更新完成") } } else { printSuccess("module-login.dll已是最新版本") } // 验证module-erp.dll文件版本 printInfo("检查module-erp.dll...") moduleErpVersion, err := moduleErpDllVersion(currentDir) if err != nil { printWarning("获取module-erp.dll版本失败: %v", err) } if versionsJSON.ModuleErpVersion != moduleErpVersion && moduleErpVersion != "" { printDownload("module-erp.dll需要更新") err = downloadDLL("http://36.212.20.113:53300/api/downLoad", filepath.Join(currentDir, "dll", "module-erp.dll"), "module-erp.dll", "") if err != nil { printError("下载module-erp.dll失败: %v", err) } else { printSuccess("module-erp.dll更新完成") } } else { printSuccess("module-erp.dll已是最新版本") } // 验证module-kongfz.dll文件版本 printInfo("检查module-kongfz.dll...") moduleKongfzVersion, err := moduleKongfzDllVersion(currentDir) if err != nil { printWarning("获取module-kongfz.dll版本失败: %v", err) } if versionsJSON.ModuleKongfzVersion != moduleKongfzVersion && moduleKongfzVersion != "" { printDownload("module-kongfz.dll需要更新") err = downloadDLL("http://36.212.20.113:53300/api/downLoad", filepath.Join(currentDir, "dll", "module-kongfz.dll"), "module-kongfz.dll", "") if err != nil { printError("下载module-kongfz.dll失败: %v", err) } else { printSuccess("module-kongfz.dll更新完成") } } else { printSuccess("module-kongfz.dll已是最新版本") } // 验证module-taskPool.dll文件版本 printInfo("检查module-taskPool.dll...") moduleTaskPoolVersion, err := moduleTaskPoolDllVersion(currentDir) if err != nil { printWarning("获取module-taskPool.dll版本失败: %v", err) } if versionsJSON.ModuleTaskPoolVersion != moduleTaskPoolVersion && moduleTaskPoolVersion != "" { printDownload("module-taskPool.dll需要更新") err = downloadDLL("http://36.212.20.113:53300/api/downLoad", filepath.Join(currentDir, "dll", "module-taskPool.dll"), "module-taskPool.dll", "") if err != nil { printError("下载module-taskPool.dll失败: %v", err) } else { printSuccess("module-taskPool.dll更新完成") } } else { printSuccess("module-taskPool.dll已是最新版本") } // 验证module-verifyPrice.dll文件版本 printInfo("检查module-verifyPrice.dll...") moduleVerifyPriceVersion, err := moduleVerifyPriceDllVersion(currentDir) if err != nil { printWarning("获取module-verifyPrice.dll版本失败: %v", err) } if versionsJSON.ModuleVerifyPriceVersion != moduleVerifyPriceVersion && moduleVerifyPriceVersion != "" { printDownload("module-verifyPrice.dll需要更新") err = downloadDLL("http://36.212.20.113:53300/api/downLoad", filepath.Join(currentDir, "dll", "module-verifyPrice.dll"), "module-verifyPrice.dll", "") if err != nil { printError("下载module-verifyPrice.dll失败: %v", err) } else { printSuccess("module-verifyPrice.dll更新完成") } } else { printSuccess("module-verifyPrice.dll已是最新版本") } // 验证picTool.dll文件版本 printInfo("检查picTool.dll...") picToolVersion, err := picToolDllVersion(currentDir) if err != nil { printWarning("获取picTool.dll版本失败: %v", err) } if versionsJSON.PicToolVersion != picToolVersion && picToolVersion != "" { printDownload("picTool.dll需要更新") err = downloadDLL("http://36.212.20.113:53300/api/downLoad", filepath.Join(currentDir, "dll", "picTool.dll"), "picTool.dll", "") if err != nil { printError("下载picTool.dll失败: %v", err) } else { printSuccess("picTool.dll更新完成") } } else { printSuccess("picTool.dll已是最新版本") } // 验证proxy.dll文件版本 printInfo("检查proxy.dll...") proxyVersion, err := proxyDllVersion(currentDir) if err != nil { printWarning("获取proxy.dll版本失败: %v", err) } if versionsJSON.ProxyVersion != proxyVersion && proxyVersion != "" { printDownload("proxy.dll需要更新") err = downloadDLL("http://36.212.20.113:53300/api/downLoad", filepath.Join(currentDir, "dll", "proxy.dll"), "proxy.dll", "") if err != nil { printError("下载proxy.dll失败: %v", err) } else { printSuccess("proxy.dll更新完成") } } else { printSuccess("proxy.dll已是最新版本") } printWarning("更新完成!按回车键打开核价软件...") // 等待用户输入 bufio.NewReader(os.Stdin).ReadBytes('\n') // 启动核价软件 verifyPriceAppPath := filepath.Join(currentDir, "VerifyPriceApp.exe") if _, err := os.Stat(verifyPriceAppPath); os.IsNotExist(err) { printError("核价软件主程序不存在: %s", verifyPriceAppPath) } else { cmd := exec.Command(verifyPriceAppPath) cmd.Dir = currentDir err := cmd.Start() if err != nil { printError("启动核价软件失败: %v", err) } else { printSuccess("核价软件已启动 (PID: %d)", cmd.Process.Pid) } } printBanner("程序执行完毕") } // ============================ 原main.go的函数 ============================ func writeYAML(filePath string, config VerifyPriceYAML) error { yamlData, err := yaml.Marshal(config) if err != nil { return fmt.Errorf("序列化YAML失败: %v", err) } backupFilePath := filePath + ".bak" err = backupFile(filePath, backupFilePath) if err != nil { printDebug("创建备份文件失败: %v", err) } err = ioutil.WriteFile(filePath, yamlData, 0755) if err != nil { return fmt.Errorf("写入文件失败: %v", err) } return nil } func backupFile(originalPath, backupPath string) error { data, err := ioutil.ReadFile(originalPath) if err != nil { return err } return ioutil.WriteFile(backupPath, data, 0755) } // 下载exe文件 func downloadEXE(url, folderPath, filename string) error { if err := os.MkdirAll(folderPath, 0755); err != nil { return fmt.Errorf("创建文件夹失败: %v", err) } filePath := filepath.Join(folderPath, filename) printDebug("下载到: %s", filePath) resp, err := http.Get(url) if err != nil { return fmt.Errorf("请求失败: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("下载失败,状态码: %d", resp.StatusCode) } file, err := os.Create(filePath) if err != nil { return fmt.Errorf("创建文件失败: %v", err) } defer file.Close() _, err = io.Copy(file, resp.Body) if err != nil { return fmt.Errorf("写入文件失败: %v", err) } return nil } func getVerifyPriceVersionJSON() (*VersionInfo, error) { url := "https://newverifyprice.buzhiyushu.cn/verify_price_version.json" client := &http.Client{Timeout: 30 * time.Second} resp, err := client.Get(url) if err != nil { return nil, fmt.Errorf("请求失败: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("HTTP请求失败,状态码: %d", resp.StatusCode) } body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("读取响应失败: %v", err) } var versionInfo VersionInfo err = json.Unmarshal(body, &versionInfo) if err != nil { return nil, fmt.Errorf("解析JSON失败: %v", err) } return &versionInfo, nil } func getVersionsJSON() (*VersionConfig, error) { url := "http://36.212.20.113:53300/version.json" client := &http.Client{Timeout: 30 * time.Second} resp, err := client.Get(url) if err != nil { return nil, fmt.Errorf("请求失败: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("HTTP请求失败,状态码: %d", resp.StatusCode) } body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("读取响应失败: %v", err) } var versionConfig VersionConfig err = json.Unmarshal(body, &versionConfig) if err != nil { return nil, fmt.Errorf("解析JSON失败: %v", err) } return &versionConfig, nil } func cStr(ptr uintptr) string { if ptr == 0 { return "" } var b []byte for { c := *(*byte)(unsafe.Pointer(ptr)) if c == 0 { break } b = append(b, c) ptr++ } return string(b) } func csvDllVersion(currentDir string) (string, error) { dllPath := filepath.Join(currentDir, "dll", "csv.dll") _, err := os.Stat(dllPath) if err != nil { if os.IsNotExist(err) { printDownload("csv.dll文件缺失,开始下载...") err = downloadDLL("http://36.212.20.113:53300/api/downLoad", dllPath, "csv.dll", "") if err != nil { return "", fmt.Errorf("下载 csv.dll 文件失败: %v", err) } return "", nil } } dll, err := syscall.LoadDLL(dllPath) if err != nil { return "", fmt.Errorf("加载csv.dll文件失败: %v", err) } proc, err := dll.FindProc("GetVersion") if err != nil { return "", fmt.Errorf("查找csv.dll GetVersion 函数失败: %v", err) } ret, _, err := proc.Call() if err != nil && err.Error() != "The operation completed successfully." { return "", fmt.Errorf("调用csv.dll GetVersion 函数失败: %v", err) } str := cStr(ret) if proc, err = dll.FindProc("FreeCString"); err == nil { defer proc.Call(ret) } return str, nil } func kongfzDllVersion(currentDir string) (string, error) { dllPath := filepath.Join(currentDir, "dll", "kongfz.dll") _, err := os.Stat(dllPath) if err != nil { if os.IsNotExist(err) { printDownload("kongfz.dll文件缺失,开始下载...") err = downloadDLL("http://36.212.20.113:53300/api/downLoad", dllPath, "kongfz.dll", "") if err != nil { return "", fmt.Errorf("下载 kongfz.dll 文件失败: %v", err) } return "", nil } } dll, err := syscall.LoadDLL(dllPath) if err != nil { return "", fmt.Errorf("加载kongfz.dll文件失败: %v", err) } proc, err := dll.FindProc("GetVersion") if err != nil { return "", fmt.Errorf("查找kongfz.dll GetVersion 函数失败: %v", err) } ret, _, err := proc.Call() if err != nil && err.Error() != "The operation completed successfully." { return "", fmt.Errorf("调用kongfz.dll GetVersion 函数失败: %v", err) } str := cStr(ret) if proc, err = dll.FindProc("FreeCString"); err == nil { defer proc.Call(ret) } return str, nil } func loggerDllVersion(currentDir string) (string, error) { dllPath := filepath.Join(currentDir, "dll", "logger.dll") _, err := os.Stat(dllPath) if err != nil { if os.IsNotExist(err) { printDownload("logger.dll文件缺失,开始下载...") err = downloadDLL("http://36.212.20.113:53300/api/downLoad", dllPath, "logger.dll", "") if err != nil { return "", fmt.Errorf("下载 logger.dll 文件失败: %v", err) } return "", nil } } dll, err := syscall.LoadDLL(dllPath) if err != nil { return "", fmt.Errorf("加载logger.dll文件失败: %v", err) } proc, err := dll.FindProc("GetVersion") if err != nil { return "", fmt.Errorf("查找logger.dll GetVersion 函数失败: %v", err) } ret, _, err := proc.Call() if err != nil && err.Error() != "The operation completed successfully." { return "", fmt.Errorf("调用logger.dll GetVersion 函数失败: %v", err) } str := cStr(ret) if proc, err = dll.FindProc("FreeCString"); err == nil { defer proc.Call(ret) } return str, nil } func moduleCenterBookDllVersion(currentDir string) (string, error) { dllPath := filepath.Join(currentDir, "dll", "module-centerBook.dll") _, err := os.Stat(dllPath) if err != nil { if os.IsNotExist(err) { printDownload("module-centerBook.dll文件缺失,开始下载...") err = downloadDLL("http://36.212.20.113:53300/api/downLoad", dllPath, "module-centerBook.dll", "") if err != nil { return "", fmt.Errorf("下载 module-centerBook.dll 文件失败: %v", err) } return "", nil } } dll, err := syscall.LoadDLL(dllPath) if err != nil { return "", fmt.Errorf("加载module-centerBook.dll文件失败: %v", err) } proc, err := dll.FindProc("GetVersion") if err != nil { return "", fmt.Errorf("查找module-centerBook.dll GetVersion 函数失败: %v", err) } ret, _, err := proc.Call() if err != nil && err.Error() != "The operation completed successfully." { return "", fmt.Errorf("调用module-centerBook.dll GetVersion 函数失败: %v", err) } str := cStr(ret) if proc, err = dll.FindProc("FreeCString"); err == nil { defer proc.Call(ret) } return str, nil } func moduleLoginDllVersion(currentDir string) (string, error) { dllPath := filepath.Join(currentDir, "dll", "module-login.dll") _, err := os.Stat(dllPath) if err != nil { if os.IsNotExist(err) { printDownload("module-login.dll文件缺失,开始下载...") err = downloadDLL("http://36.212.20.113:53300/api/downLoad", dllPath, "module-login.dll", "") if err != nil { return "", fmt.Errorf("下载 module-login.dll 文件失败: %v", err) } return "", nil } } dll, err := syscall.LoadDLL(dllPath) if err != nil { return "", fmt.Errorf("加载module-login.dll文件失败: %v", err) } proc, err := dll.FindProc("GetVersion") if err != nil { return "", fmt.Errorf("查找module-login.dll GetVersion 函数失败: %v", err) } ret, _, err := proc.Call() if err != nil && err.Error() != "The operation completed successfully." { return "", fmt.Errorf("调用module-login.dll GetVersion 函数失败: %v", err) } str := cStr(ret) if proc, err = dll.FindProc("FreeCString"); err == nil { defer proc.Call(ret) } return str, nil } func moduleErpDllVersion(currentDir string) (string, error) { dllPath := filepath.Join(currentDir, "dll", "module-erp.dll") _, err := os.Stat(dllPath) if err != nil { if os.IsNotExist(err) { printDownload("module-erp.dll文件缺失,开始下载...") err = downloadDLL("http://36.212.20.113:53300/api/downLoad", dllPath, "module-erp.dll", "") if err != nil { return "", fmt.Errorf("下载 module-erp.dll 文件失败: %v", err) } return "", nil } } dll, err := syscall.LoadDLL(dllPath) if err != nil { return "", fmt.Errorf("加载module-erp.dll文件失败: %v", err) } proc, err := dll.FindProc("GetVersion") if err != nil { return "", fmt.Errorf("查找module-erp.dll GetVersion 函数失败: %v", err) } ret, _, err := proc.Call() if err != nil && err.Error() != "The operation completed successfully." { return "", fmt.Errorf("调用module-erp.dll GetVersion 函数失败: %v", err) } str := cStr(ret) if proc, err = dll.FindProc("FreeCString"); err == nil { defer proc.Call(ret) } return str, nil } func moduleKongfzDllVersion(currentDir string) (string, error) { dllPath := filepath.Join(currentDir, "dll", "module-kongfz.dll") _, err := os.Stat(dllPath) if err != nil { if os.IsNotExist(err) { printDownload("module-kongfz.dll文件缺失,开始下载...") err = downloadDLL("http://36.212.20.113:53300/api/downLoad", dllPath, "module-kongfz.dll", "") if err != nil { return "", fmt.Errorf("下载 module-kongfz.dll 文件失败: %v", err) } return "", nil } } dll, err := syscall.LoadDLL(dllPath) if err != nil { return "", fmt.Errorf("加载module-kongfz.dll文件失败: %v", err) } proc, err := dll.FindProc("GetVersion") if err != nil { return "", fmt.Errorf("查找module-kongfz.dll GetVersion 函数失败: %v", err) } ret, _, err := proc.Call() if err != nil && err.Error() != "The operation completed successfully." { return "", fmt.Errorf("调用module-kongfz.dll GetVersion 函数失败: %v", err) } str := cStr(ret) if proc, err = dll.FindProc("FreeCString"); err == nil { defer proc.Call(ret) } return str, nil } func moduleTaskPoolDllVersion(currentDir string) (string, error) { dllPath := filepath.Join(currentDir, "dll", "module-taskPool.dll") _, err := os.Stat(dllPath) if err != nil { if os.IsNotExist(err) { printDownload("module-taskPool.dll文件缺失,开始下载...") err = downloadDLL("http://36.212.20.113:53300/api/downLoad", dllPath, "module-taskPool.dll", "") if err != nil { return "", fmt.Errorf("下载 module-taskPool.dll 文件失败: %v", err) } return "", nil } } dll, err := syscall.LoadDLL(dllPath) if err != nil { return "", fmt.Errorf("加载module-taskPool.dll文件失败: %v", err) } proc, err := dll.FindProc("GetVersion") if err != nil { return "", fmt.Errorf("查找module-taskPool.dll GetVersion 函数失败: %v", err) } ret, _, err := proc.Call() if err != nil && err.Error() != "The operation completed successfully." { return "", fmt.Errorf("调用module-taskPool.dll GetVersion 函数失败: %v", err) } str := cStr(ret) if proc, err = dll.FindProc("FreeCString"); err == nil { defer proc.Call(ret) } return str, nil } func moduleVerifyPriceDllVersion(currentDir string) (string, error) { dllPath := filepath.Join(currentDir, "dll", "module-verifyPrice.dll") _, err := os.Stat(dllPath) if err != nil { if os.IsNotExist(err) { printDownload("module-verifyPrice.dll文件缺失,开始下载...") err = downloadDLL("http://36.212.20.113:53300/api/downLoad", dllPath, "module-verifyPrice.dll", "") if err != nil { return "", fmt.Errorf("下载 module-verifyPrice.dll 文件失败: %v", err) } return "", nil } } dll, err := syscall.LoadDLL(dllPath) if err != nil { return "", fmt.Errorf("加载module-verifyPrice.dll文件失败: %v", err) } proc, err := dll.FindProc("GetVersion") if err != nil { return "", fmt.Errorf("查找module-verifyPrice.dll GetVersion 函数失败: %v", err) } ret, _, err := proc.Call() if err != nil && err.Error() != "The operation completed successfully." { return "", fmt.Errorf("调用module-verifyPrice.dll GetVersion 函数失败: %v", err) } str := cStr(ret) if proc, err = dll.FindProc("FreeCString"); err == nil { defer proc.Call(ret) } return str, nil } func picToolDllVersion(currentDir string) (string, error) { dllPath := filepath.Join(currentDir, "dll", "picTool.dll") _, err := os.Stat(dllPath) if err != nil { if os.IsNotExist(err) { printDownload("picTool.dll文件缺失,开始下载...") err = downloadDLL("http://36.212.20.113:53300/api/downLoad", dllPath, "picTool.dll", "") if err != nil { return "", fmt.Errorf("下载 picTool.dll 文件失败: %v", err) } return "", nil } } dll, err := syscall.LoadDLL(dllPath) if err != nil { return "", fmt.Errorf("加载picTool.dll文件失败: %v", err) } proc, err := dll.FindProc("GetVersion") if err != nil { return "", fmt.Errorf("查找picTool.dll GetVersion 函数失败: %v", err) } ret, _, err := proc.Call() if err != nil && err.Error() != "The operation completed successfully." { return "", fmt.Errorf("调用picTool.dll GetVersion 函数失败: %v", err) } str := cStr(ret) if proc, err = dll.FindProc("FreeCString"); err == nil { defer proc.Call(ret) } return str, nil } func proxyDllVersion(currentDir string) (string, error) { dllPath := filepath.Join(currentDir, "dll", "proxy.dll") _, err := os.Stat(dllPath) if err != nil { if os.IsNotExist(err) { printDownload("proxy.dll文件缺失,开始下载...") err = downloadDLL("http://36.212.20.113:53300/api/downLoad", dllPath, "proxy.dll", "") if err != nil { return "", fmt.Errorf("下载 proxy.dll 文件失败: %v", err) } return "", nil } } dll, err := syscall.LoadDLL(dllPath) if err != nil { return "", fmt.Errorf("加载proxy.dll文件失败: %v", err) } proc, err := dll.FindProc("GetVersion") if err != nil { return "", fmt.Errorf("查找proxy.dll GetVersion 函数失败: %v", err) } ret, _, err := proc.Call() if err != nil && err.Error() != "The operation completed successfully." { return "", fmt.Errorf("调用proxy.dll GetVersion 函数失败: %v", err) } str := cStr(ret) if proc, err = dll.FindProc("FreeCString"); err == nil { defer proc.Call(ret) } return str, nil } func downloadDLL(url, outputPath, filename, version string) error { // 1. 确保目录存在 dir := filepath.Dir(outputPath) if err := os.MkdirAll(dir, 0755); err != nil { return fmt.Errorf("创建目录失败: %v", err) } // 2. 创建临时文件路径 tempPath := outputPath + ".tmp" // 3. 清理可能存在的旧临时文件 if _, err := os.Stat(tempPath); err == nil { os.Remove(tempPath) } // 4. 检查目标文件是否存在 if _, err := os.Stat(outputPath); err == nil { backupPath := outputPath + ".bak" if err := os.Rename(outputPath, backupPath); err != nil { // 重命名失败,尝试直接删除 for i := 0; i < 3; i++ { if err := os.Remove(outputPath); err == nil { break } if i == 2 { if strings.Contains(err.Error(), "Access is denied") { return fmt.Errorf("文件被其他程序占用,请关闭可能使用此文件的程序后再试: %s", outputPath) } return fmt.Errorf("无法删除已存在的文件: %v", err) } time.Sleep(1 * time.Second) } } else { // 异步清理备份文件 go func() { time.Sleep(30 * time.Second) if _, err := os.Stat(backupPath); err == nil { os.Remove(backupPath) } }() } } // 5. 打印下载信息 if version == "" { printDownload("正在下载: %s (版本: 最新版本)", filename) } else { printDownload("正在下载: %s (版本: %s)", filename, version) } // 6. 首先尝试获取文件大小信息(HEAD请求) headClient := &http.Client{ Timeout: 30 * time.Second, } headReq, err := http.NewRequest("HEAD", url, nil) if err != nil { printError("创建HEAD请求失败,使用默认超时设置: %v", err) } else { // 添加查询参数 q := headReq.URL.Query() q.Add("filename", filename) if version != "" { q.Add("version", version) } headReq.URL.RawQuery = q.Encode() headReq.Header.Set("User-Agent", "DLL-Downloader/1.0") resp, err := headClient.Do(headReq) if err == nil && resp.StatusCode == http.StatusOK { if contentLength := resp.ContentLength; contentLength > 0 { fmt.Printf(ColorGray+"获取到文件大小: %.2f MB\n"+ColorReset, float64(contentLength)/1024/1024) } resp.Body.Close() } } // 7. 创建长时间下载的HTTP客户端 transport := &http.Transport{ Proxy: http.ProxyFromEnvironment, DialContext: (&net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, DualStack: true, }).DialContext, ForceAttemptHTTP2: true, MaxIdleConns: 100, MaxIdleConnsPerHost: 10, IdleConnTimeout: 90 * time.Second, TLSHandshakeTimeout: 15 * time.Second, ExpectContinueTimeout: 5 * time.Second, ResponseHeaderTimeout: 30 * time.Second, ReadBufferSize: 128 * 1024, // 128KB WriteBufferSize: 128 * 1024, } client := &http.Client{ Transport: transport, } // 8. 创建下载请求 ctx, cancel := context.WithTimeout(context.Background(), 3*time.Hour) defer cancel() req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { return fmt.Errorf("创建请求失败: %v", err) } // 添加查询参数 q := req.URL.Query() q.Add("filename", filename) if version != "" { q.Add("version", version) } req.URL.RawQuery = q.Encode() // 添加请求头 req.Header.Set("User-Agent", "DLL-Downloader/1.0") req.Header.Set("Accept", "*/*") req.Header.Set("Accept-Encoding", "gzip, deflate") req.Header.Set("Connection", "keep-alive") // 9. 发送请求 resp, err := client.Do(req) if err != nil { if strings.Contains(err.Error(), "context deadline exceeded") { return fmt.Errorf("连接超时,请检查网络连接: %v", err) } return fmt.Errorf("请求失败: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(io.LimitReader(resp.Body, 1024)) return fmt.Errorf("服务器返回错误: %d - %s", resp.StatusCode, string(body)) } // 10. 获取文件大小 totalSize := resp.ContentLength if totalSize > 0 { fmt.Printf(ColorGray+"文件大小: %.2f MB\n"+ColorReset, float64(totalSize)/1024/1024) // 根据文件大小提供预估时间 minSpeed := 50.0 * 1024 estimatedTime := time.Duration(float64(totalSize)/minSpeed) * time.Second if estimatedTime > 30*time.Second { fmt.Printf(ColorGray+"预估下载时间: %v (基于50KB/s最低速度)\n"+ColorReset, estimatedTime.Round(time.Second)) } } else { fmt.Println(ColorGray + "文件大小: 未知" + ColorReset) } // 11. 创建临时文件 out, err := os.OpenFile(tempPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755) if err != nil { return fmt.Errorf("创建临时文件失败: %v", err) } defer out.Close() // 12. 确保下载失败时清理临时文件 downloadSuccess := false defer func() { if !downloadSuccess { os.Remove(tempPath) } }() // 13. 活动检测机制 activityCh := make(chan time.Time, 100) var lastActivity time.Time lastActivity = time.Now() // 创建活动检测的reader activityReader := &activityReader{ reader: resp.Body, activityCh: activityCh, activityPtr: &lastActivity, } // 启动活动检测goroutine activityCtx, activityCancel := context.WithCancel(context.Background()) defer activityCancel() go func() { ticker := time.NewTicker(30 * time.Second) defer ticker.Stop() for { select { case <-activityCtx.Done(): return case activityTime := <-activityCh: lastActivity = activityTime case <-ticker.C: if time.Since(lastActivity) > 60*time.Second { printError("下载活动超时,60秒内没有收到数据") cancel() return } } } }() // 14. 下载进度显示 startTime := time.Now() var downloaded int64 = 0 var lastUpdate time.Time lastUpdate = time.Now() // 15. 创建缓冲区 buf := make([]byte, 128*1024) // 显示初始进度 fmt.Print(ColorCyan + "下载进度: 0.00% (0.00/0.00 MB)" + ColorReset) for { select { case <-ctx.Done(): return fmt.Errorf("下载被取消: %v", ctx.Err()) default: } n, err := activityReader.Read(buf) if n > 0 { if _, writeErr := out.Write(buf[:n]); writeErr != nil { return fmt.Errorf("写入文件失败: %v", writeErr) } downloaded += int64(n) if time.Since(lastUpdate) > 1*time.Second || err != nil { if totalSize > 0 { percent := float64(downloaded) / float64(totalSize) * 100 elapsed := time.Since(startTime).Seconds() speed := 0.0 if elapsed > 0 { speed = float64(downloaded) / elapsed / 1024 / 1024 } remaining := time.Duration(0) if speed > 0 && downloaded > 0 { remainingSecs := float64(totalSize-downloaded) / (float64(downloaded) / elapsed) remaining = time.Duration(remainingSecs) * time.Second } fmt.Printf("\r\033[K"+ColorCyan+"下载进度: %.2f%% (%.2f/%.2f MB) 速度: %.2f MB/s 剩余: %v"+ColorReset, percent, float64(downloaded)/1024/1024, float64(totalSize)/1024/1024, speed, remaining.Round(time.Second)) } else { elapsed := time.Since(startTime).Seconds() speed := 0.0 if elapsed > 0 { speed = float64(downloaded) / elapsed / 1024 / 1024 } fmt.Printf("\r\033[K"+ColorCyan+"已下载: %.2f MB 速度: %.2f MB/s"+ColorReset, float64(downloaded)/1024/1024, speed) } lastUpdate = time.Now() } } if err != nil { if err == io.EOF { break } if ctx.Err() != nil { return fmt.Errorf("下载中断: %v", ctx.Err()) } return fmt.Errorf("读取数据失败: %v", err) } } // 16. 下载完成,显示最终进度 if totalSize > 0 && downloaded < totalSize { downloaded = totalSize } if totalSize > 0 { fmt.Printf("\r\033[K"+ColorGreen+"下载完成: 100.00%% (%.2f/%.2f MB)\n"+ColorReset, float64(downloaded)/1024/1024, float64(totalSize)/1024/1024) } else { fmt.Printf("\r\033[K"+ColorGreen+"下载完成: %.2f MB\n"+ColorReset, float64(downloaded)/1024/1024) } activityCancel() // 17. 验证下载的文件大小 if totalSize > 0 { fi, err := os.Stat(tempPath) if err != nil { return fmt.Errorf("无法验证下载文件: %v", err) } if fi.Size() != totalSize { return fmt.Errorf("文件大小不匹配: 期望 %d 字节, 实际 %d 字节", totalSize, fi.Size()) } } // 18. 将临时文件重命名为目标文件 maxRetries := 5 for i := 0; i < maxRetries; i++ { if err := os.Rename(tempPath, outputPath); err == nil { downloadSuccess = true break } if i == maxRetries-1 { if err := copyFile(tempPath, outputPath); err != nil { return fmt.Errorf("保存文件失败: %v", err) } downloadSuccess = true } else { time.Sleep(500 * time.Millisecond * time.Duration(i+1)) } } // 19. 验证最终文件 fi, err := os.Stat(outputPath) if err != nil { return fmt.Errorf("最终文件验证失败: %v", err) } elapsed := time.Since(startTime) speed := float64(fi.Size()) / elapsed.Seconds() / 1024 / 1024 printSuccess("下载完成: %s (大小: %.2f MB, 耗时: %v, 平均速度: %.2f MB/s)", filepath.Base(outputPath), float64(fi.Size())/1024/1024, elapsed.Round(time.Second), speed) return nil } type activityReader struct { reader io.Reader activityCh chan<- time.Time activityPtr *time.Time } func (r *activityReader) Read(p []byte) (n int, err error) { n, err = r.reader.Read(p) if n > 0 { now := time.Now() r.activityCh <- now if r.activityPtr != nil { *r.activityPtr = now } } return } func copyFile(src, dst string) error { source, err := os.Open(src) if err != nil { return err } defer source.Close() destination, err := os.Create(dst) if err != nil { return err } defer destination.Close() _, err = io.Copy(destination, source) return err }