修复: 版本更新与设置面板走查问题

- 更新链路: 检查记录远端SHA256,下载完成事件回传,安装时带哈希校验
- 续传: 服务器忽略Range返回200时截断重下防追加损坏,文件名去query
- 定时器: 自动检查可重入,配置变更免重启生效,关闭即停,下载加互斥
- 前端: 支持跳过此版本并按版本去重,手动检查不受门禁限制,启动未就绪延时重试
- 更新面板: 新增自动检查开关/间隔/地址配置与进度条状态映射
- 版本历史: cache参数强制刷新与加载超时重试兜底
- 配置: 侧边栏分区全链路持久化,保存请求复用api类型防字段丢失
- 侧边栏: 区块定位改显式类名,不再依赖排序位置
- 绑定: 重新生成并与镜像目录同步
This commit is contained in:
lxy
2026-10-03 10:18:27 +08:00
parent 81e245eade
commit c7978a5bfd
25 changed files with 589 additions and 286 deletions
+20 -15
View File
@@ -24,9 +24,10 @@ func NewConfigAPI() (*ConfigAPI, error) {
// GetAppConfigResponse 获取应用配置响应
type GetAppConfigResponse struct {
Tabs []AppTabDefinition `json:"tabs"`
VisibleTabs []string `json:"visibleTabs"`
DefaultTab string `json:"defaultTab"`
Tabs []AppTabDefinition `json:"tabs"`
VisibleTabs []string `json:"visibleTabs"`
DefaultTab string `json:"defaultTab"`
SidebarSections []string `json:"sidebarSections"`
}
// AppTabDefinition 应用 Tab 定义(前端格式)
@@ -39,9 +40,10 @@ type AppTabDefinition struct {
// SaveAppConfigRequest 保存应用配置请求(前端格式)
type SaveAppConfigRequest struct {
Tabs []AppTabDefinition `json:"tabs"`
VisibleTabs []string `json:"visibleTabs"`
DefaultTab string `json:"defaultTab"`
Tabs []AppTabDefinition `json:"tabs"`
VisibleTabs []string `json:"visibleTabs"`
DefaultTab string `json:"defaultTab"`
SidebarSections []string `json:"sidebarSections"`
}
// GetAppConfig 获取应用配置
@@ -73,9 +75,10 @@ func (api *ConfigAPI) GetAppConfig() (map[string]interface{}, error) {
return map[string]interface{}{
"success": true,
"data": GetAppConfigResponse{
Tabs: tabs,
VisibleTabs: tabConfig.VisibleTabs,
DefaultTab: tabConfig.DefaultTab,
Tabs: tabs,
VisibleTabs: tabConfig.VisibleTabs,
DefaultTab: tabConfig.DefaultTab,
SidebarSections: tabConfig.SidebarSections,
},
}, nil
}
@@ -116,9 +119,10 @@ func (api *ConfigAPI) SaveAppConfig(req SaveAppConfigRequest) (map[string]interf
}
tabConfig := &service.TabConfig{
AvailableTabs: availableTabs,
VisibleTabs: req.VisibleTabs,
DefaultTab: req.DefaultTab,
AvailableTabs: availableTabs,
VisibleTabs: req.VisibleTabs,
DefaultTab: req.DefaultTab,
SidebarSections: req.SidebarSections,
}
// 保存配置
@@ -201,8 +205,9 @@ func (api *ConfigAPI) MigrateTabConfig() error {
}
return api.configService.SaveTabConfig(&service.TabConfig{
AvailableTabs: newTabs,
VisibleTabs: newVisible,
DefaultTab: defaultTab,
AvailableTabs: newTabs,
VisibleTabs: newVisible,
DefaultTab: defaultTab,
SidebarSections: config.SidebarSections,
})
}
+34 -7
View File
@@ -2,15 +2,20 @@ package api
import (
"context"
"u-desk/internal/service"
"sync/atomic"
"time"
"u-desk/internal/service"
)
// UpdateAPI 版本更新 API
type UpdateAPI struct {
updateService *service.UpdateService
ctx context.Context
eventEmitter func(name string, data ...any)
updateService *service.UpdateService
ctx context.Context
eventEmitter func(name string, data ...any)
configChanged func() // 更新配置变更回调(重置自动检查定时器)
expectedSHA256 string // 最近一次检查得到的远端包 SHA256
downloading atomic.Bool // 下载进行中标志(并发互斥)
}
// NewUpdateAPI 创建版本更新 API
@@ -25,6 +30,11 @@ func (api *UpdateAPI) SetEventEmitter(emitter func(name string, data ...any)) {
api.eventEmitter = emitter
}
// SetConfigChangedCallback 设置配置变更回调(由 App 注入,用于重置自动检查定时器)
func (api *UpdateAPI) SetConfigChangedCallback(cb func()) {
api.configChanged = cb
}
// SetContext 设置上下文(用于事件推送)
func (api *UpdateAPI) SetContext(ctx context.Context) {
api.ctx = ctx
@@ -54,6 +64,9 @@ func (api *UpdateAPI) CheckUpdate() (map[string]interface{}, error) {
return errorResponse(err.Error()), nil
}
// 记录远端包哈希,供下载完成事件比对
api.expectedSHA256 = result.SHA256
return successResponse(result), nil
}
@@ -99,6 +112,11 @@ func (api *UpdateAPI) SetUpdateConfig(autoCheckEnabled bool, checkIntervalMinute
return errorResponse(err.Error()), nil
}
// 配置已变更,通知重置自动检查定时器(免重启生效)
if api.configChanged != nil {
api.configChanged()
}
return successResponse(map[string]interface{}{
"message": "配置保存成功",
}), nil
@@ -110,7 +128,14 @@ func (api *UpdateAPI) DownloadUpdate(downloadURL string) (map[string]interface{}
return errorResponse("下载地址不能为空"), nil
}
// 并发互斥:已有下载任务进行中则拒绝
if !api.downloading.CompareAndSwap(false, true) {
return errorResponse("已有下载任务进行中"), nil
}
go func() {
defer api.downloading.Store(false)
progressCallback := func(progress float64, speed float64, downloaded int64, total int64) {
progressInfo := map[string]interface{}{
"progress": progress,
@@ -129,9 +154,11 @@ func (api *UpdateAPI) DownloadUpdate(downloadURL string) (map[string]interface{}
api.emit("download-complete", errorInfo)
} else {
resultInfo := map[string]interface{}{
"success": true,
"file_path": result.FilePath,
"file_size": result.FileSize,
"success": true,
"file_path": result.FilePath,
"file_size": result.FileSize,
"sha256": result.SHA256Hash,
"expected_sha256": api.expectedSHA256,
}
api.emit("download-complete", resultInfo)
}
+15 -5
View File
@@ -33,11 +33,15 @@ type TabDefinition struct {
// TabConfig Tab 配置
type TabConfig struct {
AvailableTabs []TabDefinition `json:"available_tabs"`
VisibleTabs []string `json:"visible_tabs"`
DefaultTab string `json:"default_tab"`
AvailableTabs []TabDefinition `json:"available_tabs"`
VisibleTabs []string `json:"visible_tabs"`
DefaultTab string `json:"default_tab"`
SidebarSections []string `json:"sidebar_sections"`
}
// 默认侧边栏分区
var defaultSidebarSections = []string{"server", "favorites", "help"}
// 默认 Tab 配置
var defaultTabConfig = TabConfig{
AvailableTabs: []TabDefinition{
@@ -45,8 +49,9 @@ var defaultTabConfig = TabConfig{
{Key: "markdown-editor", Title: "Markdown", Enabled: true},
{Key: "version", Title: "版本历史", Enabled: true},
},
VisibleTabs: []string{"file-system", "markdown-editor", "version"},
DefaultTab: "file-system",
VisibleTabs: []string{"file-system", "markdown-editor", "version"},
DefaultTab: "file-system",
SidebarSections: defaultSidebarSections,
}
const (
@@ -79,6 +84,11 @@ func (s *ConfigService) GetTabConfig() (*TabConfig, error) {
return &defaultTabConfig, nil
}
// 侧边栏分区为空时回退默认值(兼容旧配置)
if len(tabConfig.SidebarSections) == 0 {
tabConfig.SidebarSections = defaultSidebarSections
}
return &tabConfig, nil
}
+3
View File
@@ -30,6 +30,7 @@ type RemoteVersionInfo struct {
ForceUpdate bool `json:"force_update"`
ReleaseDate string `json:"release_date"`
FileSize int64 `json:"file_size"`
SHA256 string `json:"sha256"`
}
// UpdateCheckResult 更新检查结果
@@ -42,6 +43,7 @@ type UpdateCheckResult struct {
ForceUpdate bool `json:"force_update"`
ReleaseDate string `json:"release_date"`
FileSize int64 `json:"file_size"`
SHA256 string `json:"sha256"`
}
// InstallResult 安装结果
@@ -99,6 +101,7 @@ func (s *UpdateService) CheckUpdate() (*UpdateCheckResult, error) {
ForceUpdate: remoteInfo.ForceUpdate,
ReleaseDate: remoteInfo.ReleaseDate,
FileSize: remoteInfo.FileSize,
SHA256: remoteInfo.SHA256,
}, nil
}
+17 -3
View File
@@ -11,6 +11,7 @@ import (
"net/http"
"os"
"path/filepath"
"strings"
"time"
"u-desk/internal/common"
@@ -41,9 +42,13 @@ func DownloadUpdate(downloadURL string, progressCallback DownloadProgress) (*Dow
return nil, fmt.Errorf("创建下载目录失败: %v", err)
}
// 从 URL 提取文件名
filename := filepath.Base(downloadURL)
if filename == "" || filename == "." {
// 从 URL 提取文件名(去除 query 参数与 # 片段)
cleanURL := downloadURL
if idx := strings.IndexAny(cleanURL, "?#"); idx != -1 {
cleanURL = cleanURL[:idx]
}
filename := filepath.Base(cleanURL)
if filename == "" || filename == "." || filename == "/" {
filename = fmt.Sprintf("update-%d.exe", time.Now().Unix())
}
@@ -127,6 +132,15 @@ func DownloadUpdate(downloadURL string, progressCallback DownloadProgress) (*Dow
return nil, fmt.Errorf("服务器返回错误状态码: %d", resp.StatusCode)
}
// 服务器忽略 Range 返回 200 时,本地已下载部分不能追加,必须截断从头写,否则文件损坏
if resp.StatusCode == http.StatusOK && downloadedSize > 0 {
if err := file.Truncate(0); err != nil {
return nil, fmt.Errorf("重置下载文件失败: %v", err)
}
downloadedSize = 0
log.Printf("[下载] 服务器不支持断点续传(返回 200),从头下载")
}
// 获取文件总大小
contentLength := resp.ContentLength
gotTotalFromRange := false