feat: add BackupJob and wire up routes and scheduling

This commit is contained in:
root 2026-04-26 19:40:13 +08:00
parent 3181d5805d
commit 50d3b2cd7e
4 changed files with 104 additions and 0 deletions

View file

@ -0,0 +1,28 @@
# Task Record
Date: 2026-04-26
Related Module: web/job, web/controller, web
Change Type: Add
## Background
Database backups were only possible manually via API. There was no scheduled backup mechanism, so users had to remember to create backups or rely on external cron.
## Changes
- Created `web/job/backup_job.go`: BackupJob struct with Run() and shouldRun() methods that check backup settings and execute CreateSnapshot when the configured schedule matches the current time
- Registered BackupController routes in `web/controller/server.go` initRouter() to expose backup API endpoints under `/panel/api/server/`
- Scheduled BackupJob via cron in `web/web.go` startTask() at `@every 1m` interval (the job internally checks whether it should actually run)
## Impact
- New file: `web/job/backup_job.go`
- Modified: `web/controller/server.go` (3 lines added in initRouter)
- Modified: `web/web.go` (2 lines added in startTask)
- No database schema changes, no API breaking changes, no config changes
## Verification
- `go build ./...` passed with no errors
- `gofmt -l -w .` produced no changes (files already properly formatted)
## Risks And Follow-Up
- BackupJob has zero-value service fields; methods rely on DB queries at runtime, same pattern as other jobs (CheckXrayRunningJob, LdapSyncJob)
- The job uses `logger.Warning` (not `logger.Warn`) — verify this matches the logger API
- Need to verify that Backups setting page in UI has the scheduling options (BackupEnabled, BackupFrequency, BackupHour) to make the job functional

View file

@ -60,6 +60,10 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) {
g.POST("/xraylogs/:count", a.getXrayLogs)
g.POST("/importDB", a.importDB)
g.POST("/getNewEchCert", a.getNewEchCert)
// Backup routes
backupCtrl := BackupController{}
backupCtrl.initRouter(g)
}
// refreshStatus updates the cached server status and collects CPU history.

69
web/job/backup_job.go Normal file
View file

@ -0,0 +1,69 @@
package job
import (
"time"
"github.com/mhsanaei/3x-ui/v2/logger"
"github.com/mhsanaei/3x-ui/v2/web/service"
)
// BackupJob handles scheduled database backups.
type BackupJob struct {
settingService service.SettingService
backupService service.BackupService
}
// NewBackupJob creates a new BackupJob instance.
func NewBackupJob() *BackupJob {
return &BackupJob{}
}
// Run executes the scheduled backup if enabled and time matches the configured frequency.
func (j *BackupJob) Run() {
enabled, err := j.settingService.GetBackupEnabled()
if err != nil || !enabled {
return
}
frequency, err := j.settingService.GetBackupFrequency()
if err != nil {
return
}
if !j.shouldRun(frequency) {
return
}
if err := j.backupService.CreateSnapshot(j.settingService); err != nil {
logger.Warning("scheduled backup failed:", err)
}
}
// shouldRun checks if the backup should run based on the configured frequency.
func (j *BackupJob) shouldRun(frequency string) bool {
now := time.Now()
switch frequency {
case "hourly":
return now.Minute() == 0
case "every12h":
return (now.Hour() == 0 || now.Hour() == 12) && now.Minute() == 0
case "daily":
hour, err := j.settingService.GetBackupHour()
if err != nil {
hour = 3
}
return now.Hour() == hour && now.Minute() == 0
case "weekly":
if now.Weekday() != time.Sunday {
return false
}
hour, err := j.settingService.GetBackupHour()
if err != nil {
hour = 3
}
return now.Hour() == hour && now.Minute() == 0
default:
return false
}
}

View file

@ -410,6 +410,9 @@ func (s *Server) startTask() {
} else {
s.cron.Remove(entry)
}
// Schedule database backup job (runs every minute, checks schedule internally)
s.cron.AddJob("@every 1m", job.NewBackupJob())
}
func (s *Server) startNodeLoops() {