Files
BackupX/server/internal/service/system_service.go
Wu Qing b336bebdb1 优化: 多模块功能修复与体验改进 (#34)
1. 保留策略清理后自动删除空文件夹(新增 StorageDirCleaner 接口)
2. 备份任务删除时清理远端文件但保留备份记录
3. 节点管理修复:本机 IP/版本检测、Heartbeat OS/Arch 修正、新增编辑功能
4. 审计日志规范化:统一格式、丰富详情、节点操作增加审计记录
5. 系统设置移除一键更新操作,仅保留版本检查
6. Rclone 配置项分层展示(必填 + 高级可选折叠)
7. DirectoryPicker 目录选择器样式优化
2026-04-05 11:23:46 +08:00

136 lines
3.9 KiB
Go

package service
import (
"context"
"encoding/json"
"fmt"
"net/http"
"path/filepath"
"runtime"
"strings"
"syscall"
"time"
"backupx/server/internal/config"
)
type SystemInfo struct {
Version string `json:"version"`
Mode string `json:"mode"`
StartedAt string `json:"startedAt"`
UptimeSeconds int64 `json:"uptimeSeconds"`
DatabasePath string `json:"databasePath"`
DiskTotal int64 `json:"diskTotal"`
DiskFree int64 `json:"diskFree"`
DiskUsed int64 `json:"diskUsed"`
}
type SystemService struct {
cfg config.Config
version string
startedAt time.Time
}
func NewSystemService(cfg config.Config, version string, startedAt time.Time) *SystemService {
return &SystemService{cfg: cfg, version: version, startedAt: startedAt}
}
// UpdateCheckResult 描述版本更新检查结果。
type UpdateCheckResult struct {
CurrentVersion string `json:"currentVersion"`
LatestVersion string `json:"latestVersion"`
HasUpdate bool `json:"hasUpdate"`
ReleaseURL string `json:"releaseUrl,omitempty"`
ReleaseNotes string `json:"releaseNotes,omitempty"`
PublishedAt string `json:"publishedAt,omitempty"`
DownloadURL string `json:"downloadUrl,omitempty"`
DockerImage string `json:"dockerImage,omitempty"`
}
const githubRepoAPI = "https://api.github.com/repos/Awuqing/BackupX/releases/latest"
// CheckUpdate 从 GitHub Releases 检查是否有新版本。
func (s *SystemService) CheckUpdate(ctx context.Context) (*UpdateCheckResult, error) {
result := &UpdateCheckResult{
CurrentVersion: s.version,
DockerImage: "awuqing/backupx",
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, githubRepoAPI, nil)
if err != nil {
return result, fmt.Errorf("create request: %w", err)
}
req.Header.Set("Accept", "application/vnd.github.v3+json")
req.Header.Set("User-Agent", "BackupX/"+s.version)
client := &http.Client{Timeout: 15 * time.Second}
resp, err := client.Do(req)
if err != nil {
return result, fmt.Errorf("fetch latest release: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return result, fmt.Errorf("github api returned %d", resp.StatusCode)
}
var release struct {
TagName string `json:"tag_name"`
HTMLURL string `json:"html_url"`
Body string `json:"body"`
Published string `json:"published_at"`
Assets []struct {
Name string `json:"name"`
BrowserDownloadURL string `json:"browser_download_url"`
} `json:"assets"`
}
if err := json.NewDecoder(resp.Body).Decode(&release); err != nil {
return result, fmt.Errorf("decode release: %w", err)
}
result.LatestVersion = release.TagName
result.ReleaseURL = release.HTMLURL
result.ReleaseNotes = release.Body
result.PublishedAt = release.Published
// 比较版本号(去 v 前缀后字符串比较)
current := strings.TrimPrefix(s.version, "v")
latest := strings.TrimPrefix(release.TagName, "v")
result.HasUpdate = latest > current && current != "dev"
// 匹配当前平台的下载链接
goos := runtime.GOOS
goarch := runtime.GOARCH
suffix := fmt.Sprintf("%s-%s.tar.gz", goos, goarch)
for _, asset := range release.Assets {
if strings.HasSuffix(asset.Name, suffix) {
result.DownloadURL = asset.BrowserDownloadURL
break
}
}
return result, nil
}
func (s *SystemService) GetInfo(_ context.Context) *SystemInfo {
now := time.Now().UTC()
info := &SystemInfo{
Version: s.version,
Mode: s.cfg.Server.Mode,
StartedAt: s.startedAt.Format(time.RFC3339),
UptimeSeconds: int64(now.Sub(s.startedAt).Seconds()),
DatabasePath: s.cfg.Database.Path,
}
dir := filepath.Dir(s.cfg.Database.Path)
if dir == "" {
dir = "."
}
var stat syscall.Statfs_t
if err := syscall.Statfs(dir, &stat); err == nil {
info.DiskTotal = int64(stat.Blocks) * int64(stat.Bsize)
info.DiskFree = int64(stat.Bavail) * int64(stat.Bsize)
info.DiskUsed = info.DiskTotal - info.DiskFree
}
return info
}