Files
ToNav-go/handlers/settings.go
openclaw efaf787981 feat: ToNav-go v1.0.0 - 内部服务导航系统
功能:
- 前台导航: 分类Tab切换、实时搜索、健康状态指示、响应式适配
- 后台管理: 服务/分类CRUD、系统设置、登录认证(bcrypt)
- 健康检查: 定时检测(5min)、独立检查URL、三态指示(在线/离线/未检测)
- 云端备份: WebDAV上传/下载/恢复/删除、定时自动备份、本地备份管理

技术栈: Go + Gin + GORM + SQLite
2026-02-14 05:09:23 +08:00

222 lines
5.8 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package handlers
import (
"fmt"
"log"
"net/http"
"path/filepath"
"time"
"tonav-go/database"
"tonav-go/models"
config "tonav-go/utils"
"github.com/gin-gonic/gin"
)
// GetSettings 获取设置
func GetSettings(c *gin.Context) {
var settings []models.Setting
database.DB.Find(&settings)
res := make(map[string]string)
for _, s := range settings {
res[s.Key] = s.Value
}
c.JSON(http.StatusOK, res)
}
// SaveSettings 保存设置
func SaveSettings(c *gin.Context) {
var input map[string]string
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, Response{Success: false, Message: err.Error()})
return
}
for k, v := range input {
database.DB.Save(&models.Setting{Key: k, Value: v})
}
c.JSON(http.StatusOK, Response{Success: true, Message: "设置已保存"})
}
// getWebDAVClient 从数据库配置构建 WebDAV 客户端
func getWebDAVClient() (*config.WebDAVClient, error) {
var url, user, pass models.Setting
database.DB.Where("key = ?", "webdav_url").First(&url)
database.DB.Where("key = ?", "webdav_user").First(&user)
database.DB.Where("key = ?", "webdav_password").First(&pass)
if url.Value == "" {
return nil, fmt.Errorf("未配置 WebDAV URL")
}
return config.NewWebDAVClient(url.Value, user.Value, pass.Value), nil
}
// RunCloudBackup 执行云端备份
func RunCloudBackup(c *gin.Context) {
client, err := getWebDAVClient()
if err != nil {
c.JSON(http.StatusBadRequest, Response{Success: false, Message: err.Error()})
return
}
// 获取数据库路径
cfg := config.LoadConfig()
backupPath, err := config.CreateBackup(cfg.DBPath)
if err != nil {
c.JSON(http.StatusInternalServerError, Response{Success: false, Message: "本地备份失败: " + err.Error()})
return
}
// 上传时只用文件名,不带路径
remoteName := filepath.Base(backupPath)
err = client.Upload(backupPath, remoteName)
if err != nil {
c.JSON(http.StatusInternalServerError, Response{Success: false, Message: "云端上传失败: " + err.Error()})
return
}
// 清理本地旧备份保留最近5份
config.CleanOldBackups(5)
c.JSON(http.StatusOK, Response{Success: true, Message: "备份成功: " + remoteName})
}
// ListCloudBackups 列出云端备份
func ListCloudBackups(c *gin.Context) {
client, err := getWebDAVClient()
if err != nil {
c.JSON(http.StatusOK, gin.H{"files": []interface{}{}})
return
}
files, err := client.List()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"files": files})
}
// DeleteCloudBackup 删除云端备份
func DeleteCloudBackup(c *gin.Context) {
name := c.Query("name")
if name == "" {
c.JSON(http.StatusBadRequest, Response{Success: false, Message: "缺少备份文件名"})
return
}
client, err := getWebDAVClient()
if err != nil {
c.JSON(http.StatusBadRequest, Response{Success: false, Message: err.Error()})
return
}
if err := client.Delete(name); err != nil {
c.JSON(http.StatusInternalServerError, Response{Success: false, Message: "删除失败: " + err.Error()})
return
}
c.JSON(http.StatusOK, Response{Success: true, Message: "已删除: " + name})
}
// RestoreCloudBackup 从云端备份恢复
func RestoreCloudBackup(c *gin.Context) {
name := c.Query("name")
if name == "" {
c.JSON(http.StatusBadRequest, Response{Success: false, Message: "缺少备份文件名"})
return
}
client, err := getWebDAVClient()
if err != nil {
c.JSON(http.StatusBadRequest, Response{Success: false, Message: err.Error()})
return
}
cfg := config.LoadConfig()
// 恢复前先备份当前数据库
preBackup, err := config.CreateBackup(cfg.DBPath)
if err != nil {
log.Printf("恢复前备份失败: %v", err)
} else {
log.Printf("恢复前已备份当前数据库: %s", preBackup)
}
// 下载云端备份到临时文件
tmpPath := "backups/restore_tmp.db"
if err := client.Download(name, tmpPath); err != nil {
c.JSON(http.StatusInternalServerError, Response{Success: false, Message: "下载备份失败: " + err.Error()})
return
}
// 关闭当前数据库连接
sqlDB, err := database.DB.DB()
if err == nil {
sqlDB.Close()
}
// 用下载的备份替换当前数据库
if err := config.ReplaceDB(tmpPath, cfg.DBPath); err != nil {
// 尝试重新连接原数据库
database.InitDB(cfg.DBPath)
c.JSON(http.StatusInternalServerError, Response{Success: false, Message: "替换数据库失败: " + err.Error()})
return
}
// 重新初始化数据库连接
database.InitDB(cfg.DBPath)
c.JSON(http.StatusOK, Response{Success: true, Message: fmt.Sprintf("已从 %s 恢复,恢复前备份: %s", name, filepath.Base(preBackup))})
}
// StartAutoBackup 启动定时自动备份
func StartAutoBackup() {
go func() {
for {
// 计算距离下一个凌晨3点的时间
now := time.Now()
next := time.Date(now.Year(), now.Month(), now.Day(), 3, 0, 0, 0, now.Location())
if next.Before(now) {
next = next.Add(24 * time.Hour)
}
timer := time.NewTimer(next.Sub(now))
<-timer.C
// 检查是否启用了自动备份
var setting models.Setting
database.DB.Where("key = ?", "auto_backup").First(&setting)
if setting.Value != "true" {
continue
}
log.Println("开始执行自动备份...")
doAutoBackup()
}
}()
}
func doAutoBackup() {
client, err := getWebDAVClient()
if err != nil {
log.Printf("自动备份失败WebDAV未配置: %v", err)
return
}
cfg := config.LoadConfig()
backupPath, err := config.CreateBackup(cfg.DBPath)
if err != nil {
log.Printf("自动备份失败(本地备份): %v", err)
return
}
remoteName := filepath.Base(backupPath)
if err := client.Upload(backupPath, remoteName); err != nil {
log.Printf("自动备份失败(上传): %v", err)
return
}
config.CleanOldBackups(5)
log.Printf("自动备份成功: %s", remoteName)
}