Browse Source

feat: support ticker (#1)

jeessy2 4 years ago
parent
commit
e052e02125
12 changed files with 187 additions and 66 deletions
  1. 1 1
      Makefile
  2. 82 33
      client/backup.go
  3. 0 9
      client/index.go
  4. 6 3
      entity/config.go
  5. 7 0
      entity/config_backup.go
  6. 3 1
      main.go
  7. 22 0
      util/time_util.go
  8. 15 0
      util/time_util_test.go
  9. 0 15
      web/run.go
  10. 10 2
      web/save.go
  11. 1 1
      web/writing.go
  12. 40 1
      web/writing.html

+ 1 - 1
Makefile

@@ -18,7 +18,7 @@ build_image:
 	@$(DOCKER_CMD) build -f ./Dockerfile -t backup-x:$(VERSION) .
 
 test:
-	@$(GO) test ./...
+	@$(GO) test ./... -v
 
 test-race:
 	@$(GO) test -race ./...

+ 82 - 33
client/backup.go

@@ -11,15 +11,65 @@ import (
 	"os/exec"
 	"strconv"
 	"strings"
+	"sync"
 	"time"
 )
 
-// StartBackup start backup db
-func StartBackup() {
-	for {
-		RunOnce()
-		// sleep to tomorrow night
-		sleep()
+// backupLooper
+type backupLooper struct {
+	Wg      sync.WaitGroup
+	Tickers []*time.Ticker
+}
+
+var bl = &backupLooper{Wg: sync.WaitGroup{}}
+
+// RunLoop backup db loop
+func RunLoop() {
+	conf, err := entity.GetConfigCache()
+	if err != nil {
+		return
+	}
+
+	// clear
+	bl.Tickers = []*time.Ticker{}
+
+	for _, backupConf := range conf.BackupConfig {
+		if !backupConf.NotEmptyProject() {
+			continue
+		}
+
+		if !backupConf.CheckPeriod() {
+			log.Println(backupConf.ProjectName + "的周期值不正确")
+			continue
+		}
+
+		delay := util.GetDelaySeconds(backupConf.StartTime)
+		ticker := time.NewTicker(time.Second)
+		log.Printf("%s项目将在%.1f小时后运行\n", backupConf.ProjectName, delay.Hours())
+
+		bl.Wg.Add(1)
+		go func(backupConf entity.BackupConfig) {
+			defer bl.Wg.Done()
+			for {
+				<-ticker.C
+				run(conf, backupConf)
+				ticker.Reset(time.Minute * time.Duration(backupConf.Period))
+				log.Printf("%s项目将等待%d分钟后循环运行\n", backupConf.ProjectName, backupConf.Period)
+			}
+		}(backupConf)
+		bl.Tickers = append(bl.Tickers, ticker)
+	}
+
+	bl.Wg.Wait()
+
+}
+
+// StopRunLoop
+func StopRunLoop() {
+	for _, ticker := range bl.Tickers {
+		if ticker != nil {
+			ticker.Stop()
+		}
 	}
 }
 
@@ -29,27 +79,32 @@ func RunOnce() {
 	if err != nil {
 		return
 	}
-	// 迭代所有项目
+
 	for _, backupConf := range conf.BackupConfig {
-		if backupConf.NotEmptyProject() {
-			err := prepare(backupConf)
-			if err != nil {
-				log.Println(err)
-				continue
-			}
-			// backup
-			outFileName, err := backup(backupConf)
-			result := entity.BackupResult{ProjectName: backupConf.ProjectName, Result: "失败"}
-			if err == nil {
-				// webhook
-				result.FileName = outFileName.Name()
-				result.FileSize = fmt.Sprintf("%d MB", outFileName.Size()/1000/1000)
-				result.Result = "成功"
-				// send file to s3
-				go conf.UploadFile(backupConf.GetProjectPath() + string(os.PathSeparator) + outFileName.Name())
-			}
-			conf.ExecWebhook(result)
+		run(conf, backupConf)
+	}
+}
+
+// run
+func run(conf entity.Config, backupConf entity.BackupConfig) {
+	if backupConf.NotEmptyProject() {
+		err := prepare(backupConf)
+		if err != nil {
+			log.Println(err)
+			return
+		}
+		// backup
+		outFileName, err := backup(backupConf)
+		result := entity.BackupResult{ProjectName: backupConf.ProjectName, Result: "失败"}
+		if err == nil {
+			// webhook
+			result.FileName = outFileName.Name()
+			result.FileSize = fmt.Sprintf("%d MB", outFileName.Size()/1000/1000)
+			result.Result = "成功"
+			// send file to s3
+			go conf.UploadFile(backupConf.GetProjectPath() + string(os.PathSeparator) + outFileName.Name())
 		}
+		conf.ExecWebhook(result)
 	}
 }
 
@@ -69,11 +124,11 @@ func backup(backupConf entity.BackupConfig) (outFileName os.FileInfo, err error)
 	projectName := backupConf.ProjectName
 	log.Printf("正在备份项目: %s ...", projectName)
 
-	todayString := time.Now().Format("2006-01-02_03_04")
+	todayString := time.Now().Format("2006-01-02_15_04")
 	shellString := strings.ReplaceAll(backupConf.Command, "#{DATE}", todayString)
 
 	// create shell file
-	shellName := time.Now().Format("shell-2006-01-02-03-04-") + "backup.sh"
+	shellName := time.Now().Format("shell-2006-01-02-15-04-") + "backup.sh"
 
 	shellFile, err := os.Create(backupConf.GetProjectPath() + string(os.PathSeparator) + shellName)
 	shellFile.Chmod(0700)
@@ -130,9 +185,3 @@ func findBackupFile(backupConf entity.BackupConfig, todayString string) (backupF
 	err = errors.New("不能找到备份后的文件,没有找到包含 " + todayString + " 的文件名")
 	return
 }
-
-func sleep() {
-	sleepHours := 24 - time.Now().Hour()
-	log.Println("下次运行时间:", sleepHours, "hours")
-	time.Sleep(time.Hour * time.Duration(sleepHours))
-}

+ 0 - 9
client/index.go

@@ -1,9 +0,0 @@
-package client
-
-// RunCycle 周期运行
-func RunCycle() {
-	// delete old backup
-	go DeleteOldBackup()
-	// start client
-	go StartBackup()
-}

+ 6 - 3
entity/config.go

@@ -40,13 +40,13 @@ var cache = &cacheType{}
 // GetConfigCache 获得配置
 func GetConfigCache() (conf Config, err error) {
 
+	cache.Lock.Lock()
+	defer cache.Lock.Unlock()
+
 	if cache.ConfigSingle != nil {
 		return *cache.ConfigSingle, cache.Err
 	}
 
-	cache.Lock.Lock()
-	defer cache.Lock.Unlock()
-
 	// init config
 	cache.ConfigSingle = &Config{}
 
@@ -78,6 +78,9 @@ func GetConfigCache() (conf Config, err error) {
 
 // SaveConfig 保存配置
 func (conf *Config) SaveConfig() (err error) {
+	cache.Lock.Lock()
+	defer cache.Lock.Unlock()
+
 	byt, err := yaml.Marshal(conf)
 	if err != nil {
 		log.Println(err)

+ 7 - 0
entity/config_backup.go

@@ -5,6 +5,8 @@ type BackupConfig struct {
 	ProjectName string // 项目名称
 	Command     string // 命令
 	SaveDays    int
+	StartTime   int // 开始时间(0-23)
+	Period      int // 间隔周期(分钟)
 }
 
 // GetProjectPath 获得项目路径
@@ -16,3 +18,8 @@ func (backupConfig *BackupConfig) GetProjectPath() string {
 func (backupConfig *BackupConfig) NotEmptyProject() bool {
 	return backupConfig.Command != "" && backupConfig.ProjectName != ""
 }
+
+// CheckPeriod 检测周期
+func (backupConfig *BackupConfig) CheckPeriod() bool {
+	return backupConfig.StartTime < 24 && backupConfig.StartTime >= 0 && backupConfig.Period > 0
+}

+ 3 - 1
main.go

@@ -1,6 +1,7 @@
 package main
 
 import (
+	"backup-x/client"
 	"backup-x/web"
 	"embed"
 	"os"
@@ -29,7 +30,8 @@ func main() {
 	http.HandleFunc("/webhookTest", web.BasicAuth(web.WebhookTest))
 
 	// 运行
-	go web.Run()
+	go client.DeleteOldBackup()
+	go client.RunLoop()
 
 	if os.Getenv("port") != "" {
 		defaultPort = os.Getenv("port")

+ 22 - 0
util/time_util.go

@@ -0,0 +1,22 @@
+package util
+
+import (
+	"time"
+)
+
+// GetDelaySeconds 获取第一次启动的延时时间(秒)
+func GetDelaySeconds(startTime int) time.Duration {
+	now := time.Now().Truncate(time.Second)
+	midNightNow := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
+	midNightTom := midNightNow.Add(24 * time.Hour)
+
+	var seconds int
+	if now.Hour() >= startTime {
+		// tomorrow
+		seconds = int(midNightTom.Add(time.Hour * time.Duration(startTime)).Sub(now).Seconds())
+	} else {
+		seconds = int(midNightNow.Add(time.Hour * time.Duration(startTime)).Sub(now).Seconds())
+	}
+
+	return time.Second * time.Duration(seconds)
+}

+ 15 - 0
util/time_util_test.go

@@ -0,0 +1,15 @@
+package util
+
+import (
+	"testing"
+	"time"
+)
+
+// TestGetDelaySeconds
+func TestGetDelaySeconds(t *testing.T) {
+	hour := 1
+	runTime := time.Now().Add(GetDelaySeconds(hour))
+	if runTime.Hour() != hour || runTime.Minute() != 0 {
+		t.Error("GetDelaySeconds not correct")
+	}
+}

+ 0 - 15
web/run.go

@@ -1,15 +0,0 @@
-package web
-
-import (
-	"backup-x/client"
-)
-
-// Run run
-func Run() {
-	client.RunCycle()
-}
-
-// RunOnce run
-func RunOnce() {
-	client.RunOnce()
-}

+ 10 - 2
web/save.go

@@ -1,6 +1,7 @@
 package web
 
 import (
+	"backup-x/client"
 	"backup-x/entity"
 	"net/http"
 	"strconv"
@@ -18,12 +19,16 @@ func Save(writer http.ResponseWriter, request *http.Request) {
 	forms := request.PostForm
 	for index, projectName := range forms["ProjectName"] {
 		saveDays, _ := strconv.Atoi(forms["SaveDays"][index])
+		startTime, _ := strconv.Atoi(forms["StartTime"][index])
+		period, _ := strconv.Atoi(forms["Period"][index])
 		conf.BackupConfig = append(
 			conf.BackupConfig,
 			entity.BackupConfig{
 				ProjectName: projectName,
 				Command:     forms["Command"][index],
 				SaveDays:    saveDays,
+				StartTime:   startTime,
+				Period:      period,
 			},
 		)
 	}
@@ -41,10 +46,13 @@ func Save(writer http.ResponseWriter, request *http.Request) {
 	// 保存到用户目录
 	err := conf.SaveConfig()
 
-	// 没有错误,运行一次
+	// 没有错误
 	if err == nil {
 		conf.CreateBucketIfNotExist()
-		go RunOnce()
+		go client.RunOnce()
+		// 重新进行循环
+		client.StopRunLoop()
+		go client.RunLoop()
 	}
 
 	// 回写错误信息

+ 1 - 1
web/writing.go

@@ -29,7 +29,7 @@ func WritingConfig(writer http.ResponseWriter, request *http.Request) {
 	// 获得环境变量
 	backupConf := []entity.BackupConfig{}
 	for i := 0; i < 16; i++ {
-		backupConf = append(backupConf, entity.BackupConfig{SaveDays: 30})
+		backupConf = append(backupConf, entity.BackupConfig{SaveDays: 30, StartTime: 1, Period: 1440})
 	}
 	conf = entity.Config{
 		BackupConfig: backupConf,

+ 40 - 1
web/writing.html

@@ -1,4 +1,4 @@
-<html lang="zh">
+<html lang="zh-CN">
 
 <head>
   <meta charset="utf-8">
@@ -81,6 +81,45 @@
                     </div>
                   </div>
 
+                  <div class="form-group row">
+                    <label for="StartTime_{{$i}}" class="col-sm-2 col-form-label">备份起始时间</label>
+                    <div class="col-sm-10">
+                      <select class="form-control" name="StartTime" id="StartTime_{{$i}}" value="{{$v.StartTime}}">
+                        <option value="0" {{if eq $v.StartTime 0}}selected{{end}}>0:00</option>
+                        <option value="1" {{if eq $v.StartTime 1}}selected{{end}}>1:00</option>
+                        <option value="2" {{if eq $v.StartTime 2}}selected{{end}}>2:00</option>
+                        <option value="3" {{if eq $v.StartTime 3}}selected{{end}}>3:00</option>
+                        <option value="4" {{if eq $v.StartTime 4}}selected{{end}}>4:00</option>
+                        <option value="5" {{if eq $v.StartTime 5}}selected{{end}}>5:00</option>
+                        <option value="6" {{if eq $v.StartTime 6}}selected{{end}}>6:00</option>
+                        <option value="7" {{if eq $v.StartTime 7}}selected{{end}}>7:00</option>
+                        <option value="8" {{if eq $v.StartTime 8}}selected{{end}}>8:00</option>
+                        <option value="9" {{if eq $v.StartTime 9}}selected{{end}}>9:00</option>
+                        <option value="10" {{if eq $v.StartTime 10}}selected{{end}}>10:00</option>
+                        <option value="11" {{if eq $v.StartTime 11}}selected{{end}}>11:00</option>
+                        <option value="12" {{if eq $v.StartTime 12}}selected{{end}}>12:00</option>
+                        <option value="13" {{if eq $v.StartTime 13}}selected{{end}}>13:00</option>
+                        <option value="14" {{if eq $v.StartTime 14}}selected{{end}}>14:00</option>
+                        <option value="15" {{if eq $v.StartTime 15}}selected{{end}}>15:00</option>
+                        <option value="16" {{if eq $v.StartTime 16}}selected{{end}}>16:00</option>
+                        <option value="17" {{if eq $v.StartTime 17}}selected{{end}}>17:00</option>
+                        <option value="18" {{if eq $v.StartTime 18}}selected{{end}}>18:00</option>
+                        <option value="19" {{if eq $v.StartTime 19}}selected{{end}}>19:00</option>
+                        <option value="20" {{if eq $v.StartTime 20}}selected{{end}}>20:00</option>
+                        <option value="21" {{if eq $v.StartTime 21}}selected{{end}}>21:00</option>
+                        <option value="22" {{if eq $v.StartTime 22}}selected{{end}}>22:00</option>
+                        <option value="23" {{if eq $v.StartTime 23}}selected{{end}}>23:00</option>
+                      </select>
+                    </div>
+                  </div>
+
+                  <div class="form-group row">
+                    <label for="Period_{{$i}}" class="col-sm-2 col-form-label">备份周期(分钟)</label>
+                    <div class="col-sm-10">
+                      <input type="number" class="form-control" name="Period" id="Period_{{$i}}" value="{{$v.Period}}" min="1">
+                    </div>
+                  </div>
+
                 </div>
                 {{end}}
               </div>