feat: Admin 设置页 SQLite 一键备份与下载

复用 BackupService,表单导出后安全下载备份文件。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-30 01:30:16 +08:00
parent 4f93397ef3
commit c8533b6184
7 changed files with 51 additions and 2 deletions

View File

@@ -3,6 +3,7 @@ package web
import (
"fmt"
"net/http"
"path/filepath"
"strconv"
"strings"
"time"
@@ -434,6 +435,7 @@ type adminSettingsData struct {
MailReady bool
TestTo string
AsideWidgets []adminAsideWidgetRow
CanBackup bool
}
// AdminSettingsGet 设置页
@@ -468,6 +470,7 @@ func (d Deps) renderAdminSettings(ctx *webctx.Context, errMsg string) {
Mail: mail,
MailReady: d.Settings.MailReady(),
AsideWidgets: asideRows,
CanBackup: models.DialectorName() == "sqlite",
}
data.Error = errMsg
ctx.HTML(http.StatusOK, "admin/settings", data)
@@ -645,6 +648,37 @@ func (d Deps) AdminSettingsAsideWidgetsPost(c *gin.Context) {
ctx.Redirect("/admin/settings")
}
// AdminSettingsBackupPost 一键导出 SQLite 备份并跳转下载
func (d Deps) AdminSettingsBackupPost(c *gin.Context) {
ctx := d.ctx(c)
if !ctx.CheckCSRF() {
d.renderAdminSettings(ctx, "无效请求,请重试")
return
}
if d.Backup == nil {
d.renderAdminSettings(ctx, "备份服务未就绪")
return
}
path, err := d.Backup.ExportSQLite()
if err != nil {
d.renderAdminSettings(ctx, err.Error())
return
}
name := filepath.Base(path)
ctx.Redirect("/admin/settings/backup/download/" + name)
}
// AdminSettingsBackupDownload 下载 data 目录下的备份文件
func (d Deps) AdminSettingsBackupDownload(c *gin.Context) {
name := c.Param("name")
if !strings.HasPrefix(name, "jiang13_backup_") || !strings.HasSuffix(name, ".db") {
c.String(http.StatusBadRequest, "无效的备份文件名")
return
}
path := filepath.Join(d.DataDir, name)
c.FileAttachment(path, name)
}
// moveAsideWidget 解析 move 值「up:id」或「down:id」并交换相邻项
func moveAsideWidget(widgets []services.AsideWidget, move string) []services.AsideWidget {
parts := strings.SplitN(move, ":", 2)

View File

@@ -30,6 +30,7 @@ type Deps struct {
Report *services.ReportService
Captcha *services.CaptchaService
Notify *services.NotifyService
Backup *services.BackupService
}
// SitePageLink 导航/页脚站点单页链接

View File

@@ -76,6 +76,8 @@ func Register(r *gin.Engine, deps Deps, authMW *auth.AuthMiddleware) {
admin.POST("/settings/mail", deps.AdminSettingsMailPost)
admin.POST("/settings/mail/test", deps.AdminSettingsMailTestPost)
admin.POST("/settings/aside-widgets", deps.AdminSettingsAsideWidgetsPost)
admin.POST("/settings/backup", deps.AdminSettingsBackupPost)
admin.GET("/settings/backup/download/:name", deps.AdminSettingsBackupDownload)
admin.GET("/friend-links", deps.AdminFriendLinksGet)
admin.POST("/friend-links/settings", deps.AdminFriendLinksSettingsPost)
admin.POST("/friend-links/brand", deps.AdminFriendLinksBrandAddPost)