mirror of
https://github.com/eiblog/eiblog.git
synced 2026-02-04 13:52:26 +08:00
chore: add backup app
This commit is contained in:
@@ -1,8 +1,54 @@
|
|||||||
// Package main provides ...
|
// Package main provides ...
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/eiblog/eiblog/pkg/config"
|
||||||
|
"github.com/eiblog/eiblog/pkg/core/backup/ping"
|
||||||
|
"github.com/eiblog/eiblog/pkg/core/backup/swag"
|
||||||
|
"github.com/eiblog/eiblog/pkg/core/backup/timer"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
fmt.Println("hello world!")
|
fmt.Println("Hi, it's App " + config.Conf.BackupApp.Name)
|
||||||
|
|
||||||
|
endRun := make(chan error, 1)
|
||||||
|
|
||||||
|
runTimer(endRun)
|
||||||
|
|
||||||
|
runHTTPServer(endRun)
|
||||||
|
fmt.Println(<-endRun)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runTimer(endRun chan error) {
|
||||||
|
go func() {
|
||||||
|
endRun <- timer.Start()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func runHTTPServer(endRun chan error) {
|
||||||
|
if !config.Conf.EiBlogApp.EnableHTTP {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Conf.RunMode == config.ModeProd {
|
||||||
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
}
|
||||||
|
e := gin.Default()
|
||||||
|
|
||||||
|
// swag
|
||||||
|
swag.RegisterRoutes(e)
|
||||||
|
|
||||||
|
// route
|
||||||
|
ping.RegisterRoutes(e)
|
||||||
|
|
||||||
|
// start
|
||||||
|
address := fmt.Sprintf(":%d", config.Conf.EiBlogApp.HTTPPort)
|
||||||
|
go func() {
|
||||||
|
endRun <- e.Run(address)
|
||||||
|
}()
|
||||||
|
fmt.Println("HTTP server running on: " + address)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,13 +19,13 @@ import (
|
|||||||
func main() {
|
func main() {
|
||||||
fmt.Println("Hi, it's App " + config.Conf.EiBlogApp.Name)
|
fmt.Println("Hi, it's App " + config.Conf.EiBlogApp.Name)
|
||||||
|
|
||||||
endRun := make(chan bool, 1)
|
endRun := make(chan error, 1)
|
||||||
|
|
||||||
runHTTPServer(endRun)
|
runHTTPServer(endRun)
|
||||||
<-endRun
|
fmt.Println(<-endRun)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runHTTPServer(endRun chan bool) {
|
func runHTTPServer(endRun chan error) {
|
||||||
if !config.Conf.EiBlogApp.EnableHTTP {
|
if !config.Conf.EiBlogApp.EnableHTTP {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -65,6 +65,8 @@ func runHTTPServer(endRun chan bool) {
|
|||||||
|
|
||||||
// start
|
// start
|
||||||
address := fmt.Sprintf(":%d", config.Conf.EiBlogApp.HTTPPort)
|
address := fmt.Sprintf(":%d", config.Conf.EiBlogApp.HTTPPort)
|
||||||
go e.Run(address)
|
go func() {
|
||||||
|
endRun <- e.Run(address)
|
||||||
|
}()
|
||||||
fmt.Println("HTTP server running on: " + address)
|
fmt.Println("HTTP server running on: " + address)
|
||||||
}
|
}
|
||||||
|
|||||||
17
conf/app.yml
17
conf/app.yml
@@ -2,7 +2,7 @@ appname: eiblog
|
|||||||
database:
|
database:
|
||||||
driver: postgres
|
driver: postgres
|
||||||
source: host=localhost port=5432 user=postgres dbname=eiblog sslmode=disable password=MTI3LjAuMC4x
|
source: host=localhost port=5432 user=postgres dbname=eiblog sslmode=disable password=MTI3LjAuMC4x
|
||||||
eshost: http://localhost:9200
|
eshost:
|
||||||
eiblogapp:
|
eiblogapp:
|
||||||
mode:
|
mode:
|
||||||
name: cmd-eiblog
|
name: cmd-eiblog
|
||||||
@@ -53,6 +53,15 @@ eiblogapp:
|
|||||||
username: deepzz # *后台登录用户名
|
username: deepzz # *后台登录用户名
|
||||||
password: deepzz # *登录明文密码
|
password: deepzz # *登录明文密码
|
||||||
backupapp:
|
backupapp:
|
||||||
name: cmd-backup
|
mode:
|
||||||
enablehttp: true
|
name: cmd-backup
|
||||||
httpport: 9001
|
enablehttp: true
|
||||||
|
httpport: 9001
|
||||||
|
backupto: qiniu # 备份到七牛云
|
||||||
|
interval: 7d # 多久备份一次
|
||||||
|
validity: 60d # 保存时长
|
||||||
|
qiniu: # 七牛OSS
|
||||||
|
bucket: backup
|
||||||
|
domain: st.deepzz.com
|
||||||
|
accesskey: MB6AXl_Sj_mmFsL-Lt59Dml2Vmy2o8XMmiCbbSeC
|
||||||
|
secretkey: BIrMy0fsZ0_SHNceNXk3eDuo7WmVYzj2-zrmd5Tf
|
||||||
|
|||||||
@@ -121,6 +121,16 @@ type EiBlogApp struct {
|
|||||||
Blogger Blogger `yaml:"blogger"`
|
Blogger Blogger `yaml:"blogger"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BackupApp config
|
||||||
|
type BackupApp struct {
|
||||||
|
Mode
|
||||||
|
|
||||||
|
BackupTo string `yaml:"backupto"`
|
||||||
|
Interval string `yaml:"interval"` // circle backup, default: 7d
|
||||||
|
Validity string `yaml:"validity"` // storage days, default: 60d
|
||||||
|
Qiniu Qiniu `yaml:"qiniu"` // qiniu config
|
||||||
|
}
|
||||||
|
|
||||||
// Config app config
|
// Config app config
|
||||||
type Config struct {
|
type Config struct {
|
||||||
RunMode string `yaml:"runmode"`
|
RunMode string `yaml:"runmode"`
|
||||||
@@ -128,7 +138,7 @@ type Config struct {
|
|||||||
Database Database `yaml:"database"`
|
Database Database `yaml:"database"`
|
||||||
ESHost string `yaml:"eshost"`
|
ESHost string `yaml:"eshost"`
|
||||||
EiBlogApp EiBlogApp `yaml:"eiblogapp"`
|
EiBlogApp EiBlogApp `yaml:"eiblogapp"`
|
||||||
BackupApp Mode `yaml:"backupapp"`
|
BackupApp BackupApp `yaml:"backupapp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// load config file
|
// load config file
|
||||||
|
|||||||
18
pkg/core/backup/ping/ping.go
Normal file
18
pkg/core/backup/ping/ping.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
// Package ping provides ...
|
||||||
|
package ping
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RegisterRoutes register routes
|
||||||
|
func RegisterRoutes(group gin.IRoutes) {
|
||||||
|
group.GET("/ping", handlePing)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handlePing ping
|
||||||
|
func handlePing(c *gin.Context) {
|
||||||
|
c.String(http.StatusOK, "it's ok")
|
||||||
|
}
|
||||||
15
pkg/core/backup/swag/swag.go
Normal file
15
pkg/core/backup/swag/swag.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
// Package swag provides ...
|
||||||
|
package swag
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "github.com/eiblog/eiblog/pkg/core/backup/docs" // docs
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
ginSwagger "github.com/swaggo/gin-swagger"
|
||||||
|
"github.com/swaggo/gin-swagger/swaggerFiles"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RegisterRoutes register routes
|
||||||
|
func RegisterRoutes(group gin.IRoutes) {
|
||||||
|
group.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
|
||||||
|
}
|
||||||
77
pkg/core/backup/timer/qiniu/qiniu.go
Normal file
77
pkg/core/backup/timer/qiniu/qiniu.go
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
// Package qiniu provides ...
|
||||||
|
package qiniu
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/eiblog/eiblog/pkg/config"
|
||||||
|
"github.com/eiblog/eiblog/pkg/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Storage qiniu storage
|
||||||
|
type Storage struct{}
|
||||||
|
|
||||||
|
// BackupData implements timer.Storage
|
||||||
|
func (s Storage) BackupData(now time.Time) error {
|
||||||
|
switch config.Conf.Database.Source {
|
||||||
|
case "mongodb":
|
||||||
|
return backupFromMongoDB(now)
|
||||||
|
default:
|
||||||
|
return errors.New("unsupported database source backup to qiniu")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func backupFromMongoDB(now time.Time) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Minute*20)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// dump
|
||||||
|
arg := fmt.Sprintf("mongodump -h %s -d eiblog -o /tmp",
|
||||||
|
config.Conf.Database.Source)
|
||||||
|
cmd := exec.CommandContext(ctx, "sh", "-c", arg)
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// tar
|
||||||
|
name := fmt.Sprintf("eiblog-%s.tar.gz", now.Format("2006-01-02"))
|
||||||
|
arg = fmt.Sprintf("tar czf %s /tmp/eiblog", name)
|
||||||
|
cmd = exec.CommandContext(ctx, "sh", "-c", arg)
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// upload file
|
||||||
|
f, err := os.Open("/tmp/eiblog/" + name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s, err := f.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
uploadParams := internal.UploadParams{
|
||||||
|
Name: name,
|
||||||
|
Size: s.Size(),
|
||||||
|
Data: f,
|
||||||
|
|
||||||
|
Conf: config.Conf.BackupApp.Qiniu,
|
||||||
|
}
|
||||||
|
_, err = internal.QiniuUpload(uploadParams)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// after days delete
|
||||||
|
deleteParams := internal.DeleteParams{
|
||||||
|
Name: name,
|
||||||
|
|
||||||
|
Conf: config.Conf.BackupApp.Qiniu,
|
||||||
|
}
|
||||||
|
return internal.QiniuDelete(deleteParams)
|
||||||
|
}
|
||||||
68
pkg/core/backup/timer/timer.go
Normal file
68
pkg/core/backup/timer/timer.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
// Package timer provides ...
|
||||||
|
package timer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/eiblog/eiblog/pkg/config"
|
||||||
|
"github.com/eiblog/eiblog/pkg/core/backup/timer/qiniu"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Start to backup with ticker
|
||||||
|
func Start() error {
|
||||||
|
var storage Storage
|
||||||
|
// backup instance
|
||||||
|
switch config.Conf.BackupApp.BackupTo {
|
||||||
|
case "qiniu":
|
||||||
|
storage = qiniu.Storage{}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return errors.New("timer: unknown backup to driver: " +
|
||||||
|
config.Conf.BackupApp.BackupTo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse duration
|
||||||
|
interval, err := ParseDuration(config.Conf.BackupApp.Interval)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
t := time.NewTicker(interval)
|
||||||
|
for now := range t.C {
|
||||||
|
err = storage.BackupData(now)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Error("timer: Start.BackupData: ", now, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseDuration parse string to duration
|
||||||
|
func ParseDuration(d string) (time.Duration, error) {
|
||||||
|
if len(d) == 0 {
|
||||||
|
return 0, errors.New("timer: incorrect duration input")
|
||||||
|
}
|
||||||
|
|
||||||
|
length := len(d)
|
||||||
|
switch d[length-1] {
|
||||||
|
case 's', 'm', 'h':
|
||||||
|
return time.ParseDuration(d)
|
||||||
|
case 'd':
|
||||||
|
di, err := strconv.Atoi(d[:length-1])
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return time.Duration(di) * time.Hour * 24, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, errors.New("timer: unsupported duration:" + d)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Storage backup backend
|
||||||
|
type Storage interface {
|
||||||
|
BackupData(now time.Time) error
|
||||||
|
}
|
||||||
@@ -467,7 +467,15 @@ func handleAPIQiniuUpload(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
filename := strings.ToLower(header.Filename)
|
filename := strings.ToLower(header.Filename)
|
||||||
url, err := internal.QiniuUpload(filename, s.Size(), file)
|
|
||||||
|
params := internal.UploadParams{
|
||||||
|
Name: filename,
|
||||||
|
Size: s.Size(),
|
||||||
|
Data: file,
|
||||||
|
|
||||||
|
Conf: config.Conf.EiBlogApp.Qiniu,
|
||||||
|
}
|
||||||
|
url, err := internal.QiniuUpload(params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Error("handleAPIQiniuUpload.QiniuUpload: ", err)
|
logrus.Error("handleAPIQiniuUpload.QiniuUpload: ", err)
|
||||||
c.String(http.StatusBadRequest, err.Error())
|
c.String(http.StatusBadRequest, err.Error())
|
||||||
@@ -491,7 +499,13 @@ func handleAPIQiniuDelete(c *gin.Context) {
|
|||||||
logrus.Error("handleAPIQiniuDelete.PostForm: 参数错误")
|
logrus.Error("handleAPIQiniuDelete.PostForm: 参数错误")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err := internal.QiniuDelete(name)
|
|
||||||
|
params := internal.DeleteParams{
|
||||||
|
Name: name,
|
||||||
|
|
||||||
|
Conf: config.Conf.EiBlogApp.Qiniu,
|
||||||
|
}
|
||||||
|
err := internal.QiniuDelete(params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Error("handleAPIQiniuDelete.QiniuDelete: ", err)
|
logrus.Error("handleAPIQiniuDelete.QiniuDelete: ", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,19 +13,28 @@ import (
|
|||||||
"github.com/qiniu/go-sdk/v7/storage"
|
"github.com/qiniu/go-sdk/v7/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// UploadParams upload params
|
||||||
|
type UploadParams struct {
|
||||||
|
Name string
|
||||||
|
Size int64
|
||||||
|
Data io.Reader
|
||||||
|
|
||||||
|
Conf config.Qiniu
|
||||||
|
}
|
||||||
|
|
||||||
// QiniuUpload 上传文件
|
// QiniuUpload 上传文件
|
||||||
func QiniuUpload(name string, size int64, data io.Reader) (string, error) {
|
func QiniuUpload(params UploadParams) (string, error) {
|
||||||
if config.Conf.EiBlogApp.Qiniu.AccessKey == "" ||
|
if params.Conf.AccessKey == "" ||
|
||||||
config.Conf.EiBlogApp.Qiniu.SecretKey == "" {
|
params.Conf.SecretKey == "" {
|
||||||
return "", errors.New("qiniu config error")
|
return "", errors.New("qiniu config error")
|
||||||
}
|
}
|
||||||
key := completeQiniuKey(name)
|
key := completeQiniuKey(params.Name)
|
||||||
|
|
||||||
mac := qbox.NewMac(config.Conf.EiBlogApp.Qiniu.AccessKey,
|
mac := qbox.NewMac(params.Conf.AccessKey,
|
||||||
config.Conf.EiBlogApp.Qiniu.SecretKey)
|
params.Conf.SecretKey)
|
||||||
// 设置上传策略
|
// 设置上传策略
|
||||||
putPolicy := &storage.PutPolicy{
|
putPolicy := &storage.PutPolicy{
|
||||||
Scope: config.Conf.EiBlogApp.Qiniu.Bucket,
|
Scope: params.Conf.Bucket,
|
||||||
Expires: 3600,
|
Expires: 3600,
|
||||||
InsertOnly: 1,
|
InsertOnly: 1,
|
||||||
}
|
}
|
||||||
@@ -42,20 +51,28 @@ func QiniuUpload(name string, size int64, data io.Reader) (string, error) {
|
|||||||
putExtra := &storage.PutExtra{}
|
putExtra := &storage.PutExtra{}
|
||||||
|
|
||||||
err := uploader.Put(context.Background(), ret, uploadToken,
|
err := uploader.Put(context.Background(), ret, uploadToken,
|
||||||
key, data, size, putExtra)
|
key, params.Data, params.Size, putExtra)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
url := "https://" + config.Conf.EiBlogApp.Qiniu.Domain + "/" + key
|
url := "https://" + params.Conf.Domain + "/" + key
|
||||||
return url, nil
|
return url, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// QiniuDelete 删除文件
|
// DeleteParams delete params
|
||||||
func QiniuDelete(name string) error {
|
type DeleteParams struct {
|
||||||
key := completeQiniuKey(name)
|
Name string
|
||||||
|
Days int
|
||||||
|
|
||||||
mac := qbox.NewMac(config.Conf.EiBlogApp.Qiniu.AccessKey,
|
Conf config.Qiniu
|
||||||
config.Conf.EiBlogApp.Qiniu.SecretKey)
|
}
|
||||||
|
|
||||||
|
// QiniuDelete 删除文件
|
||||||
|
func QiniuDelete(params DeleteParams) error {
|
||||||
|
key := completeQiniuKey(params.Name)
|
||||||
|
|
||||||
|
mac := qbox.NewMac(params.Conf.AccessKey,
|
||||||
|
params.Conf.SecretKey)
|
||||||
// 上传配置
|
// 上传配置
|
||||||
cfg := &storage.Config{
|
cfg := &storage.Config{
|
||||||
Zone: &storage.ZoneHuadong,
|
Zone: &storage.ZoneHuadong,
|
||||||
@@ -64,7 +81,10 @@ func QiniuDelete(name string) error {
|
|||||||
// manager
|
// manager
|
||||||
bucketManager := storage.NewBucketManager(mac, cfg)
|
bucketManager := storage.NewBucketManager(mac, cfg)
|
||||||
// Delete
|
// Delete
|
||||||
return bucketManager.Delete(config.Conf.EiBlogApp.Qiniu.Bucket, key)
|
if params.Days > 0 {
|
||||||
|
return bucketManager.DeleteAfterDays(params.Conf.Bucket, key, params.Days)
|
||||||
|
}
|
||||||
|
return bucketManager.Delete(params.Conf.Bucket, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// completeQiniuKey 修复路径
|
// completeQiniuKey 修复路径
|
||||||
|
|||||||
Reference in New Issue
Block a user