From c7978a5bfd51fad51385b222e914c14f9b8dd05c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BB=9D=E5=B0=98?= <237809796@qq.com> Date: Sat, 3 Oct 2026 10:18:27 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D:=20=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=B8=8E=E8=AE=BE=E7=BD=AE=E9=9D=A2=E6=9D=BF?= =?UTF-8?q?=E8=B5=B0=E6=9F=A5=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 更新链路: 检查记录远端SHA256,下载完成事件回传,安装时带哈希校验 - 续传: 服务器忽略Range返回200时截断重下防追加损坏,文件名去query - 定时器: 自动检查可重入,配置变更免重启生效,关闭即停,下载加互斥 - 前端: 支持跳过此版本并按版本去重,手动检查不受门禁限制,启动未就绪延时重试 - 更新面板: 新增自动检查开关/间隔/地址配置与进度条状态映射 - 版本历史: cache参数强制刷新与加载超时重试兜底 - 配置: 侧边栏分区全链路持久化,保存请求复用api类型防字段丢失 - 侧边栏: 区块定位改显式类名,不再依赖排序位置 - 绑定: 重新生成并与镜像目录同步 --- app.go | 13 +-- app_config.go | 11 +- app_update.go | 47 +++++++- frontend/bindings/u-desk/app.ts | 6 +- frontend/bindings/u-desk/index.ts | 1 - .../bindings/u-desk/internal/api/index.ts | 3 +- .../bindings/u-desk/internal/api/models.ts | 53 +++++++++ frontend/bindings/u-desk/models.ts | 49 -------- frontend/src/App.vue | 10 +- .../FileSystem/components/Sidebar.vue | 14 +-- .../src/components/UpdateNotification.vue | 103 +++++++++-------- frontend/src/components/UpdatePanel.vue | 106 ++++++++++++++++-- frontend/src/components/VersionHistory.vue | 58 +++++++--- frontend/src/stores/config.ts | 2 +- frontend/src/stores/update.ts | 89 ++++++++++++--- .../src/wailsjs/v3-bindings/u-desk/app.ts | 85 ++++++++------ .../src/wailsjs/v3-bindings/u-desk/index.ts | 1 - .../v3-bindings/u-desk/internal/api/index.ts | 3 +- .../v3-bindings/u-desk/internal/api/models.ts | 53 +++++++++ .../src/wailsjs/v3-bindings/u-desk/models.ts | 49 -------- internal/api/config_api.go | 35 +++--- internal/api/update_api.go | 41 +++++-- internal/service/config_service.go | 20 +++- internal/service/update.go | 3 + internal/service/update_download.go | 20 +++- 25 files changed, 589 insertions(+), 286 deletions(-) diff --git a/app.go b/app.go index 122d63e..9590f38 100644 --- a/app.go +++ b/app.go @@ -30,6 +30,7 @@ type App struct { mainWindow *application.WebviewWindow updateAPI *api.UpdateAPI updateTicker *time.Ticker + updateCheckStop chan struct{} configAPI *api.ConfigAPI pdfAPI *api.PdfAPI filesystem *filesystem.FileSystemService @@ -172,6 +173,7 @@ func (a *App) ServiceStartup(ctx context.Context, _ application.ServiceOptions) a.mainWindow.EmitEvent(name, data...) } }) + a.updateAPI.SetConfigChangedCallback(a.resetAutoUpdateCheck) a.startAutoUpdateCheck() } }() @@ -200,9 +202,7 @@ func (a *App) ServiceStartup(ctx context.Context, _ application.ServiceOptions) // ServiceShutdown Wails v3 服务关闭生命周期(替代 v2 的 Shutdown) func (a *App) ServiceShutdown() error { - if a.updateTicker != nil { - a.updateTicker.Stop() - } + a.stopUpdateTicker() if a.unregisterHotkey != nil { a.unregisterHotkey() } @@ -273,13 +273,6 @@ type RenamePathRequest struct { NewPath string `json:"newPath"` } -// SaveAppConfigRequest 保存应用配置请求 -type SaveAppConfigRequest struct { - Tabs []api.AppTabDefinition `json:"tabs"` - VisibleTabs []string `json:"visibleTabs"` - DefaultTab string `json:"defaultTab"` -} - // SftpConnectRequest SFTP 连接请求 type SftpConnectRequest struct { Host string `json:"host"` diff --git a/app_config.go b/app_config.go index c4b1178..95aebcc 100644 --- a/app_config.go +++ b/app_config.go @@ -26,7 +26,8 @@ func (a *App) GetAppConfig() (map[string]interface{}, error) { } // SaveAppConfig 保存应用配置 -func (a *App) SaveAppConfig(req SaveAppConfigRequest) (map[string]interface{}, error) { +// 直接复用 api.SaveAppConfigRequest,避免 main 包副本转发时丢字段 +func (a *App) SaveAppConfig(req api.SaveAppConfigRequest) (map[string]interface{}, error) { if a.configAPI == nil { return nil, fmt.Errorf("配置服务正在初始化中") } @@ -41,13 +42,7 @@ func (a *App) SaveAppConfig(req SaveAppConfigRequest) (map[string]interface{}, e } } - apiReq := api.SaveAppConfigRequest{ - Tabs: req.Tabs, - VisibleTabs: req.VisibleTabs, - DefaultTab: req.DefaultTab, - } - - result, err := a.configAPI.SaveAppConfig(apiReq) + result, err := a.configAPI.SaveAppConfig(req) if err != nil { return result, err } diff --git a/app_update.go b/app_update.go index 76e46ff..00f6ee7 100644 --- a/app_update.go +++ b/app_update.go @@ -92,7 +92,7 @@ func (a *App) VerifyUpdateFile(filePath string, expectedHash string, hashType st return api.VerifyUpdateFile(filePath, expectedHash, hashType) } -// startAutoUpdateCheck 启动自动更新检查 +// startAutoUpdateCheck 启动自动更新检查(可重入:先停止旧定时器再按当前配置新建) func (a *App) startAutoUpdateCheck() { updateAPI, err := a.requireUpdateAPI() if err != nil { @@ -116,6 +116,8 @@ func (a *App) startAutoUpdateCheck() { autoCheckEnabled, ok := configData["auto_check_enabled"].(bool) if !ok || !autoCheckEnabled { + // 关闭自动检查时停止存量定时器 + a.stopUpdateTicker() return } @@ -124,16 +126,53 @@ func (a *App) startAutoUpdateCheck() { interval = 5 } + a.mu.Lock() + if a.updateCheckStop != nil { + close(a.updateCheckStop) + } + if a.updateTicker != nil { + a.updateTicker.Stop() + } + stop := make(chan struct{}) + a.updateCheckStop = stop + a.updateTicker = time.NewTicker(time.Duration(interval) * time.Minute) + ticker := a.updateTicker + a.mu.Unlock() + go a.checkUpdate() - a.updateTicker = time.NewTicker(time.Duration(interval) * time.Minute) go func() { - for range a.updateTicker.C { - a.checkUpdate() + for { + select { + case <-ticker.C: + a.checkUpdate() + case <-stop: + return + } } }() } +// stopUpdateTicker 停止自动更新检查定时器及其 goroutine +func (a *App) stopUpdateTicker() { + a.mu.Lock() + defer a.mu.Unlock() + if a.updateCheckStop != nil { + close(a.updateCheckStop) + a.updateCheckStop = nil + } + if a.updateTicker != nil { + a.updateTicker.Stop() + a.updateTicker = nil + } +} + +// resetAutoUpdateCheck 重置自动更新检查(更新配置变更后调用,免重启生效) +func (a *App) resetAutoUpdateCheck() { + a.stopUpdateTicker() + a.startAutoUpdateCheck() +} + // checkUpdate 执行更新检查 func (a *App) checkUpdate() { defer func() { diff --git a/frontend/bindings/u-desk/app.ts b/frontend/bindings/u-desk/app.ts index e2c1693..273955f 100644 --- a/frontend/bindings/u-desk/app.ts +++ b/frontend/bindings/u-desk/app.ts @@ -15,6 +15,9 @@ import { Call as $Call, CancellablePromise as $CancellablePromise, Create as $Cr import * as application$0 from "../github.com/wailsapp/wails/v3/pkg/application/models.js"; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore: Unused imports +import * as api$0 from "./internal/api/models.js"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: Unused imports import * as filesystem$0 from "./internal/filesystem/models.js"; // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -549,8 +552,9 @@ export function RestoreFromRecycleBin(recyclePath: string): $CancellablePromise< /** * SaveAppConfig 保存应用配置 + * 直接复用 api.SaveAppConfigRequest,避免 main 包副本转发时丢字段 */ -export function SaveAppConfig(req: $models.SaveAppConfigRequest): $CancellablePromise<{ [_ in string]?: any }> { +export function SaveAppConfig(req: api$0.SaveAppConfigRequest): $CancellablePromise<{ [_ in string]?: any }> { return $Call.ByID(1942219977, req).then(($result: any) => { return $$createType2($result); }); diff --git a/frontend/bindings/u-desk/index.ts b/frontend/bindings/u-desk/index.ts index 8e6f2ce..55eab53 100644 --- a/frontend/bindings/u-desk/index.ts +++ b/frontend/bindings/u-desk/index.ts @@ -11,7 +11,6 @@ export { OssConnectRequest, OssRenamePathRequest, RenamePathRequest, - SaveAppConfigRequest, SaveBase64FileRequest, SaveProfileRequest, SftpConnectRequest, diff --git a/frontend/bindings/u-desk/internal/api/index.ts b/frontend/bindings/u-desk/internal/api/index.ts index c016da9..2fb0108 100644 --- a/frontend/bindings/u-desk/internal/api/index.ts +++ b/frontend/bindings/u-desk/internal/api/index.ts @@ -2,5 +2,6 @@ // This file is automatically generated. DO NOT EDIT export { - AppTabDefinition + AppTabDefinition, + SaveAppConfigRequest } from "./models.js"; diff --git a/frontend/bindings/u-desk/internal/api/models.ts b/frontend/bindings/u-desk/internal/api/models.ts index 8b86ae2..44d6412 100644 --- a/frontend/bindings/u-desk/internal/api/models.ts +++ b/frontend/bindings/u-desk/internal/api/models.ts @@ -40,3 +40,56 @@ export class AppTabDefinition { return new AppTabDefinition($$parsedSource as Partial); } } + +/** + * SaveAppConfigRequest 保存应用配置请求(前端格式) + */ +export class SaveAppConfigRequest { + "tabs": AppTabDefinition[]; + "visibleTabs": string[]; + "defaultTab": string; + "sidebarSections": string[]; + + /** Creates a new SaveAppConfigRequest instance. */ + constructor($$source: Partial = {}) { + if (!("tabs" in $$source)) { + this["tabs"] = []; + } + if (!("visibleTabs" in $$source)) { + this["visibleTabs"] = []; + } + if (!("defaultTab" in $$source)) { + this["defaultTab"] = ""; + } + if (!("sidebarSections" in $$source)) { + this["sidebarSections"] = []; + } + + Object.assign(this, $$source); + } + + /** + * Creates a new SaveAppConfigRequest instance from a string or object. + */ + static createFrom($$source: any = {}): SaveAppConfigRequest { + const $$createField0_0 = $$createType1; + const $$createField1_0 = $$createType2; + const $$createField3_0 = $$createType2; + let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; + if ("tabs" in $$parsedSource) { + $$parsedSource["tabs"] = $$createField0_0($$parsedSource["tabs"]); + } + if ("visibleTabs" in $$parsedSource) { + $$parsedSource["visibleTabs"] = $$createField1_0($$parsedSource["visibleTabs"]); + } + if ("sidebarSections" in $$parsedSource) { + $$parsedSource["sidebarSections"] = $$createField3_0($$parsedSource["sidebarSections"]); + } + return new SaveAppConfigRequest($$parsedSource as Partial); + } +} + +// Private type creation functions +const $$createType0 = AppTabDefinition.createFrom; +const $$createType1 = $Create.Array($$createType0); +const $$createType2 = $Create.Array($Create.Any); diff --git a/frontend/bindings/u-desk/models.ts b/frontend/bindings/u-desk/models.ts index 01abeb1..5364242 100644 --- a/frontend/bindings/u-desk/models.ts +++ b/frontend/bindings/u-desk/models.ts @@ -5,10 +5,6 @@ // @ts-ignore: Unused imports import { Create as $Create } from "@wailsio/runtime"; -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore: Unused imports -import * as api$0 from "./internal/api/models.js"; - /** * BgmPlaylistItem 播放列表条目 */ @@ -134,46 +130,6 @@ export class RenamePathRequest { } } -/** - * SaveAppConfigRequest 保存应用配置请求 - */ -export class SaveAppConfigRequest { - "tabs": api$0.AppTabDefinition[]; - "visibleTabs": string[]; - "defaultTab": string; - - /** Creates a new SaveAppConfigRequest instance. */ - constructor($$source: Partial = {}) { - if (!("tabs" in $$source)) { - this["tabs"] = []; - } - if (!("visibleTabs" in $$source)) { - this["visibleTabs"] = []; - } - if (!("defaultTab" in $$source)) { - this["defaultTab"] = ""; - } - - Object.assign(this, $$source); - } - - /** - * Creates a new SaveAppConfigRequest instance from a string or object. - */ - static createFrom($$source: any = {}): SaveAppConfigRequest { - const $$createField0_0 = $$createType1; - const $$createField1_0 = $$createType2; - let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; - if ("tabs" in $$parsedSource) { - $$parsedSource["tabs"] = $$createField0_0($$parsedSource["tabs"]); - } - if ("visibleTabs" in $$parsedSource) { - $$parsedSource["visibleTabs"] = $$createField1_0($$parsedSource["visibleTabs"]); - } - return new SaveAppConfigRequest($$parsedSource as Partial); - } -} - /** * SaveBase64FileRequest 保存 Base64 编码的二进制文件 */ @@ -422,8 +378,3 @@ export class WriteFileRequest { return new WriteFileRequest($$parsedSource as Partial); } } - -// Private type creation functions -const $$createType0 = api$0.AppTabDefinition.createFrom; -const $$createType1 = $Create.Array($$createType0); -const $$createType2 = $Create.Array($Create.Any); diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 2061310..9eb9863 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -85,7 +85,6 @@ @@ -179,9 +178,12 @@ onMounted(() => { // 禁用浏览器默认右键菜单(桌面应用体验) document.addEventListener('contextmenu', e => e.preventDefault()) - // 延迟检查更新(启动后 3 秒,静默模式) - setTimeout(() => { - updateStore.checkForUpdates(true) + // 延迟检查更新(启动后 3 秒,静默模式;后端更新模块异步初始化,未就绪则 15 秒后重试一次) + setTimeout(async () => { + const ok = await updateStore.checkForUpdates(true) + if (!ok) { + setTimeout(() => { updateStore.checkForUpdates(true) }, 15000) + } }, 3000) }) diff --git a/frontend/src/components/FileSystem/components/Sidebar.vue b/frontend/src/components/FileSystem/components/Sidebar.vue index 0197cbb..3b7df7b 100644 --- a/frontend/src/components/FileSystem/components/Sidebar.vue +++ b/frontend/src/components/FileSystem/components/Sidebar.vue @@ -3,7 +3,7 @@