- 更新链路: 检查记录远端SHA256,下载完成事件回传,安装时带哈希校验 - 续传: 服务器忽略Range返回200时截断重下防追加损坏,文件名去query - 定时器: 自动检查可重入,配置变更免重启生效,关闭即停,下载加互斥 - 前端: 支持跳过此版本并按版本去重,手动检查不受门禁限制,启动未就绪延时重试 - 更新面板: 新增自动检查开关/间隔/地址配置与进度条状态映射 - 版本历史: cache参数强制刷新与加载超时重试兜底 - 配置: 侧边栏分区全链路持久化,保存请求复用api类型防字段丢失 - 侧边栏: 区块定位改显式类名,不再依赖排序位置 - 绑定: 重新生成并与镜像目录同步
211 lines
5.5 KiB
Go
211 lines
5.5 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"log/slog"
|
|
"time"
|
|
|
|
"u-desk/internal/api"
|
|
"u-desk/internal/common"
|
|
"u-desk/internal/service"
|
|
"u-desk/internal/storage"
|
|
"u-desk/internal/storage/models"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// ========== 应用配置接口 ==========
|
|
|
|
// GetAppConfig 获取应用配置
|
|
func (a *App) GetAppConfig() (map[string]interface{}, error) {
|
|
if a.configAPI == nil {
|
|
return nil, fmt.Errorf("配置服务正在初始化中")
|
|
}
|
|
return a.configAPI.GetAppConfig()
|
|
}
|
|
|
|
// SaveAppConfig 保存应用配置
|
|
// 直接复用 api.SaveAppConfigRequest,避免 main 包副本转发时丢字段
|
|
func (a *App) SaveAppConfig(req api.SaveAppConfigRequest) (map[string]interface{}, error) {
|
|
if a.configAPI == nil {
|
|
return nil, fmt.Errorf("配置服务正在初始化中")
|
|
}
|
|
|
|
oldConfig, _ := a.configAPI.GetAppConfig()
|
|
var oldVisibleTabs []string
|
|
if success, ok := oldConfig["success"].(bool); ok && success {
|
|
if data, ok := oldConfig["data"].(map[string]interface{}); ok {
|
|
if vtInterface, ok := data["visibleTabs"].([]interface{}); ok {
|
|
oldVisibleTabs = common.InterfaceSliceToStringSlice(vtInterface)
|
|
}
|
|
}
|
|
}
|
|
|
|
result, err := a.configAPI.SaveAppConfig(req)
|
|
if err != nil {
|
|
return result, err
|
|
}
|
|
|
|
if success, ok := result["success"].(bool); ok && success {
|
|
a.handleNewlyEnabledModules(oldVisibleTabs, req.VisibleTabs)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (a *App) LoadConnectionProfiles() ([]map[string]interface{}, error) {
|
|
list, err := a.ensureProfileSvc().ListProfiles()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var result []map[string]interface{}
|
|
data, _ := json.Marshal(list)
|
|
if err := json.Unmarshal(data, &result); err != nil {
|
|
return nil, fmt.Errorf("解析连接配置失败: %w", err)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (a *App) SaveConnectionProfile(req SaveProfileRequest) (map[string]interface{}, error) {
|
|
p := &models.ConnectionProfile{
|
|
Name: req.Name, Host: req.Host, Port: req.Port,
|
|
Username: req.Username, Password: req.Password,
|
|
KeyPath: req.KeyPath, Type: req.Type, Provider: req.Provider, Token: req.Token,
|
|
AccessKey: req.AccessKey, SecretKey: req.SecretKey,
|
|
Bucket: req.Bucket, Region: req.Region, Endpoint: req.Endpoint,
|
|
}
|
|
if req.LastConnected != nil {
|
|
t := time.Unix(*req.LastConnected, 0)
|
|
p.LastConnected = &t
|
|
}
|
|
if req.ID != nil {
|
|
p.ID = *req.ID
|
|
}
|
|
if err := a.ensureProfileSvc().SaveProfile(p); err != nil {
|
|
return nil, err
|
|
}
|
|
return map[string]interface{}{"id": float64(p.ID), "success": true}, nil
|
|
}
|
|
|
|
func (a *App) DeleteConnectionProfile(id uint) error {
|
|
return a.ensureProfileSvc().DeleteProfile(id)
|
|
}
|
|
|
|
// BgmGetPlaylist 获取播放列表
|
|
func (a *App) BgmGetPlaylist() ([]BgmPlaylistItem, error) {
|
|
db := storage.GetDB()
|
|
if db == nil {
|
|
return nil, fmt.Errorf("数据库未初始化")
|
|
}
|
|
var rows []models.BgmPlaylist
|
|
if err := db.Order("sort ASC").Find(&rows).Error; err != nil {
|
|
return nil, fmt.Errorf("查询播放列表失败: %w", err)
|
|
}
|
|
items := make([]BgmPlaylistItem, len(rows))
|
|
for i, r := range rows {
|
|
items[i] = BgmPlaylistItem{Name: r.Name, Path: r.Path, ProfileID: r.ProfileID}
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
// BgmSavePlaylist 全量保存播放列表(前端调用时传完整列表)
|
|
func (a *App) BgmSavePlaylist(items []BgmPlaylistItem) error {
|
|
db := storage.GetDB()
|
|
if db == nil {
|
|
return fmt.Errorf("数据库未初始化")
|
|
}
|
|
return db.Transaction(func(tx *gorm.DB) error {
|
|
if err := tx.Exec("DELETE FROM bgm_playlist").Error; err != nil {
|
|
return err
|
|
}
|
|
sort := uint(0)
|
|
for _, item := range items {
|
|
if item.Name == "" || item.Path == "" {
|
|
continue
|
|
}
|
|
if err := tx.Create(&models.BgmPlaylist{Name: item.Name, Path: item.Path, ProfileID: item.ProfileID, Sort: sort}).Error; err != nil {
|
|
return err
|
|
}
|
|
sort++
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (a *App) ensureProfileSvc() *service.ProfileService {
|
|
a.mu.Lock()
|
|
defer a.mu.Unlock()
|
|
if a.profileSvc == nil {
|
|
a.profileSvc = service.NewProfileService()
|
|
}
|
|
return a.profileSvc
|
|
}
|
|
|
|
// getVisibleTabs 获取配置中的可见 Tabs
|
|
func (a *App) getVisibleTabs() []string {
|
|
config, err := a.configAPI.GetAppConfig()
|
|
if err != nil {
|
|
slog.Warn("读取配置失败,使用默认配置", "error", err)
|
|
return common.DefaultVisibleTabs
|
|
}
|
|
|
|
success, ok := config["success"].(bool)
|
|
if !ok || !success {
|
|
slog.Warn("配置读取失败,使用默认配置")
|
|
return common.DefaultVisibleTabs
|
|
}
|
|
|
|
data, ok := config["data"].(map[string]interface{})
|
|
if !ok {
|
|
return common.DefaultVisibleTabs
|
|
}
|
|
|
|
visibleTabsInterface, ok := data["visibleTabs"].([]interface{})
|
|
if !ok {
|
|
return common.DefaultVisibleTabs
|
|
}
|
|
|
|
visibleTabs := common.InterfaceSliceToStringSlice(visibleTabsInterface)
|
|
|
|
if len(visibleTabs) == 0 {
|
|
return common.DefaultVisibleTabs
|
|
}
|
|
|
|
return visibleTabs
|
|
}
|
|
|
|
// handleNewlyEnabledModules 处理新启用的模块
|
|
func (a *App) handleNewlyEnabledModules(oldTabs, newTabs []string) {
|
|
newlyEnabled := common.Difference(newTabs, oldTabs)
|
|
|
|
if len(newlyEnabled) == 0 {
|
|
return
|
|
}
|
|
|
|
slog.Info("检测到新启用的模块", "modules", newlyEnabled)
|
|
|
|
for _, tab := range newlyEnabled {
|
|
switch tab {
|
|
case common.TabFileSystem:
|
|
a.initFilesystemModule()
|
|
case common.TabDevice:
|
|
slog.Info("设备测试模块已启用")
|
|
}
|
|
}
|
|
}
|
|
|
|
// initModulesByConfig 根据配置初始化模块
|
|
func (a *App) initModulesByConfig(visibleTabs []string) error {
|
|
if common.Contains(visibleTabs, common.TabFileSystem) {
|
|
a.initFilesystemModule()
|
|
if a.filesystem == nil {
|
|
return fmt.Errorf("文件系统服务初始化失败")
|
|
}
|
|
} else {
|
|
slog.Info("跳过文件系统模块(未启用)")
|
|
}
|
|
|
|
return nil
|
|
}
|