bkup-db/BkupDB.go
2024-08-28 16:16:19 +08:00

319 lines
6.9 KiB
Go

package main
import (
"database/sql"
"fmt"
"log"
"os"
"strings"
"time"
_ "github.com/go-sql-driver/mysql"
"gopkg.in/yaml.v2"
)
type Cfg struct {
Settings Settings `yaml:"Settings"`
DBConfig DBConfig `yaml:"DBConfig"`
FormatColumns FormatColumns `yaml:"FormatColumns"`
}
type Settings struct {
Condition string `yaml:"Condition"`
PrimaryKey string `yaml:"PrimaryKey"`
DeleteSource bool `yaml:"DeleteSource"`
}
type FormatColumns struct {
Enablement bool `yaml:"Enablement"`
Reference string `yaml:"Reference"`
Year string `yaml:"Year"`
Month string `yaml:"Month"`
Day string `yaml:"Day"`
Week string `yaml:"Week"`
}
type DBConfig struct {
Username string `yaml:"Username"`
Password string `yaml:"Password"`
Server string `yaml:"Server"`
Port string `yaml:"Port"`
Database string `yaml:"Database"`
SourceTable string `yaml:"SourceTable"`
DestinationTable string `yaml:"DestinationTable"`
SourceColumns []string `yaml:"SourceColumns"`
DestinationColumns []string `yaml:"DestinationColumns"`
}
var cfg = Cfg{}
var dsn string
func main() {
log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds)
log.SetOutput(os.Stdout)
self := strings.TrimSuffix(os.Args[0], ".exe")
file := self + ".yml"
txt, err := os.ReadFile(file)
if err != nil {
log.Printf("[ERR] %s\r\n", err.Error())
os.Exit(1)
}
err = yaml.Unmarshal(txt, &cfg)
if err != nil {
log.Printf("[ERR] %s\r\n", err.Error())
os.Exit(1)
}
// srcCols := cfg.DBConfig.Columns
// dstCols := cfg.DBConfig.Columns
// if len(srcCols) != len(dstCols) {
// log.Println("[ERR] Mismatched Length Of Source Columns And Destination Columns.")
// os.Exit(1)
// }
condition := cfg.Settings.Condition
log.Printf("[MSG] Condition: %s\r\n", condition)
if condition == "" {
log.Println("[ERR] Missing Query Condition.")
os.Exit(1)
}
dsn = fmt.Sprintf("%s:%s@tcp(%s:%s)/%s",
cfg.DBConfig.Username,
cfg.DBConfig.Password,
cfg.DBConfig.Server,
cfg.DBConfig.Port,
cfg.DBConfig.Database,
)
qry, err := retrieveData(condition)
if err != nil {
log.Printf("[ERR] %v\r\n", err)
os.Exit(1)
}
log.Printf("[MSG] Selected Rows: %d\r\n", len(qry))
if len(qry) == 0 {
os.Exit(0)
}
err = backupData(qry)
if err != nil {
log.Printf("[ERR] %v\r\n", err)
os.Exit(1)
}
}
func retrieveData(condition string) ([]map[string]interface{}, error) {
rst := make([]map[string]interface{}, 0)
dbo, err := sql.Open("mysql", dsn)
if err != nil {
return rst, err
}
defer dbo.Close()
err = dbo.Ping()
if err != nil {
return rst, err
}
cols := ""
for _, c := range cfg.DBConfig.SourceColumns {
cols = cols + c + ","
}
cols = strings.TrimSuffix(cols, ",")
qry := fmt.Sprintf("SELECT %s FROM %s.%s",
cols,
cfg.DBConfig.Database,
cfg.DBConfig.SourceTable,
)
flt := fmt.Sprintf("WHERE %s;", condition)
if condition != "" {
qry = qry + " " + flt
}
lnth := len(cfg.DBConfig.SourceColumns)
vals := make([]interface{}, lnth)
valsPtr := make([]interface{}, lnth)
for i := 0; i < lnth; i++ {
valsPtr[i] = &vals[i]
}
rows, err := dbo.Query(qry)
if err != nil {
return rst, err
}
defer rows.Close()
for rows.Next() {
err := rows.Scan(valsPtr...)
if err != nil {
break
}
row := make(map[string]interface{}, 0)
for i, val := range vals {
key := cfg.DBConfig.SourceColumns[i]
var v interface{}
b, ok := val.([]byte)
if ok {
v = string(b)
} else {
v = val
}
row[key] = v
}
rst = append(rst, row)
}
return rst, nil
}
func backupData(data []map[string]interface{}) error {
dbo, err := sql.Open("mysql", dsn)
if err != nil {
return err
}
defer dbo.Close()
err = dbo.Ping()
if err != nil {
return err
}
cols := ""
for _, c := range cfg.DBConfig.DestinationColumns {
cols = cols + c + ","
}
// Append Formated Column Keys
if cfg.FormatColumns.Enablement {
cols = cols + strings.Join(
[]string{
cfg.FormatColumns.Year,
cfg.FormatColumns.Month,
cfg.FormatColumns.Day,
cfg.FormatColumns.Week,
},
",",
)
}
cols = strings.TrimSuffix(cols, ",")
var timeStr string = ""
var timeVal time.Time
for i, d := range data {
log.Printf("[MSG] #%d: %#v\r\n", i+1, d)
vals := ""
for _, c := range cfg.DBConfig.DestinationColumns {
for k, v := range d {
if k == c {
vals = vals + fmt.Sprintf("%#v,", v)
}
if k == cfg.FormatColumns.Reference {
timeStr = v.(string)
timeVal, err = time.Parse("2006-01-02 15:04:05", timeStr)
if err != nil {
log.Printf("[ERR] #%d: %#v\r\n", i+1, err)
continue
}
}
}
}
// Append Formated Column Values
if cfg.FormatColumns.Enablement {
vals = vals + strings.Join(
[]string{
fmt.Sprintf("%#v", timeVal.Format("2006")),
fmt.Sprintf("%#v", timeVal.Format("01")),
fmt.Sprintf("%#v", timeVal.Format("02")),
fmt.Sprintf("%#v", calculateWeek(timeVal)),
},
",",
)
}
vals = strings.TrimSuffix(vals, ",")
tx, err := dbo.Begin()
if err != nil {
tx.Rollback()
log.Printf("[ERR] txBegin: %v\r\n\r\n", err)
continue
}
stmt, err := tx.Prepare(fmt.Sprintf("INSERT INTO %s.%s (%s) VALUES (%s);",
cfg.DBConfig.Database,
cfg.DBConfig.DestinationTable,
cols,
vals,
))
if err != nil {
tx.Rollback()
log.Printf("[ERR] txPrepare_Insert: %v\r\n\r\n", err)
continue
}
defer stmt.Close()
_, err = stmt.Exec()
if err != nil {
tx.Rollback()
log.Printf("[ERR] txExecute_Insert: %v\r\n\r\n", err)
continue
}
log.Printf("[MSG] Backed Up: %s=%s\r\n", cfg.Settings.PrimaryKey, d[cfg.Settings.PrimaryKey])
if cfg.Settings.DeleteSource {
stmt, err = tx.Prepare(fmt.Sprintf("DELETE FROM %s.%s WHERE %s=%s;",
cfg.DBConfig.Database,
cfg.DBConfig.SourceTable,
cfg.Settings.PrimaryKey,
fmt.Sprintf("%#v", d[cfg.Settings.PrimaryKey]),
))
if err != nil {
tx.Rollback()
log.Printf("[ERR] txPrepare_Delete: %v\r\n\r\n", err)
continue
}
defer stmt.Close()
_, err = stmt.Exec()
if err != nil {
tx.Rollback()
log.Printf("[ERR] txExecute_Delete: %v\r\n\r\n", err)
continue
}
log.Printf("[MSG] Deleted Souce: %s=%s\r\n", cfg.Settings.PrimaryKey, d[cfg.Settings.PrimaryKey])
}
err = tx.Commit()
if err != nil {
tx.Rollback()
log.Printf("[ERR] txCommit: %v\r\n\r\n", err)
continue
}
log.Printf("[MSG] ********** Transaction Commited.\r\n\r\n")
time.Sleep(time.Millisecond * 100)
}
return nil
}
func calculateWeek(t time.Time) string {
yearDay := t.YearDay()
lastYearEndDay := t.AddDate(0, 0, -yearDay)
lastYearEndDayWeek := int(lastYearEndDay.Weekday())
firstWeekDays := 7
if lastYearEndDayWeek != 0 {
firstWeekDays = 7 - lastYearEndDayWeek
}
week := 0
if yearDay <= firstWeekDays {
week = 1
} else {
plusDay := 0
if (yearDay-firstWeekDays)%7 > 0 {
plusDay = 1
}
week = (yearDay-firstWeekDays)/7 + 1 + plusDay
}
return fmt.Sprintf("%02d", week)
}