功能: - 前台导航: 分类Tab切换、实时搜索、健康状态指示、响应式适配 - 后台管理: 服务/分类CRUD、系统设置、登录认证(bcrypt) - 健康检查: 定时检测(5min)、独立检查URL、三态指示(在线/离线/未检测) - 云端备份: WebDAV上传/下载/恢复/删除、定时自动备份、本地备份管理 技术栈: Go + Gin + GORM + SQLite
222 lines
5.8 KiB
Go
222 lines
5.8 KiB
Go
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)
|
||
}
|