问题
很多情况下,由于当时创建数据库表字段时,没有准确的确定日志表的自增ID类型,或者说其他值的类型,有可能插入失败
- 主键超出范围
- varchar类型超出范围等
因此,通过错误日志信息,将数据导出,重新将丢失的数据写入数据库中
数据库使用sqlx
将日志数据上传到服务器上,通过定时任务,在数据库接收数据量少的时候进行脚本数据迁移,每次限定次数
import (
"bufio"
"encoding/json"
"fmt"
"github.com/jmoiron/sqlx"
"io/ioutil"
"math"
"os"
"recoverlog/config"
"recoverlog/db"
"strings"
"time"
"zonst/logging"
)
// 从日志中获取执行失败的SQL
type SQLQuery struct {
Query string `json:"query"`
MapArgs map[string]interface{} `json:"mapargs"`
}
var (
// 执行过程中,的日志文件信息
logfile = "../data/log/xxx.log"
// 需要恢复的日志文件位置
dir = "../data/log/xxxx/"
)
func RecoverCoinLog() {
if config.Node.Mode == "local" || config.Node.Mode == "dev" {
dir = "../log"
logfile = "../xxxlog.log"
}
if err := logging.SetLogFile(logfile); err != nil {
logging.Errorf("打开日志文件失败err:%v", err)
return
}
// 获取当前目录下所有文件
files, e := GetAllFile(dir)
if e != nil {
logging.Errorf("err:%v 获取目录信息失败\n", e)
return
}
for _, fileName := range files {
xxxLogs := make([]map[string]interface{}, 0, 100)
logging.Infoln("读取的文件名", fileName)
file, err := os.Open(fileName)
if err != nil {
logging.Errorf("open file err:%v\n", err)
return
}
buf := bufio.NewScanner(file)
for {
var sql SQLQuery
if !buf.Scan() {
break
}
line := buf.Text()
line = strings.TrimSpace(line)
// 此处根据实际情况,将日志进行拆分
if strings.Contains(line, "xxx") && (strings.Contains(line, "INSERT INTO") || strings.Contains(line, "insert into")) {
strs := strings.Split(line, "worker.go:139:")
if len(strs) > 1 {
jsonData := strs[1]
json.Unmarshal([]byte(jsonData), &sql)
if strings.Contains(sql.Query, "表名称") {
// 金币场日志信息
xxxLogs = append(xxxLogs, sql.MapArgs)
}
}
if len(strs) < 1 {
logging.Errorf("log err:line:%v\n", line)
}
}
}
// 批量插入
logging.Infof("插入文件:%v的数据\n", fileName)
logging.Infoln("len(xxxLogs)", len(xxxLogs))
if len(xxxLogs) != 0 {
e = BatchInsertxxxLog(db.DB, xxxLogs)
if e != nil {
logging.Errorf("InsertxxxxLog err:%v fileName:%v\n", e, fileName)
}
}
}
}
// 批量插入数据
func BatchInsertxxxLog(db *sqlx.DB, xxxLogs []map[string]interface{}) error {
query := "INSERT INTO xxxx(user_id, game_id, update_coin, start_coin, end_coin, op_type, playback_id, log_date, log_time, is_robot,start_coin64,end_coin64) VALUES (:userID, :gameID, :updateCoin, :startCoin, :endCoin, :opType, :playbackID, :logDate, :logTime, :isRobot,:startCoin64,:endCoin64)"
nums := len(xxxLogs)
//判断一共有多少数据,需要分多少次插入
ids := nums / 1000
fmt.Println(nums)
fmt.Println(time.Now())
// 一次批量插入1000 条数据
for i := 1; i <= ids; i++ {
start, end := SlicePage(i, 1000, nums)
_, e := db.NamedExec(query, xxxLogs[start:end])
if e != nil {
logging.Errorf("执行插入InsertUserCoinLog出错,err:%v", e)
// 保存执行失败的数据,以便下次执行
for _,v := range userCoinLogs[start:end]{
logging.Infoln(v)
}
}
time.Sleep(30 * time.Millisecond)
}
fmt.Println(time.Now())
return nil
}
// 切片分段(分页)
func SlicePage(page, pageSize, nums int) (sliceStart, sliceEnd int) {
if page < 0 {
page = 1
}
if pageSize < 0 {
pageSize = 20
}
if pageSize > nums {
return 0, nums
}
// 总页数
pageCount := int(math.Ceil(float64(nums) / float64(pageSize)))
if page > pageCount {
return 0, 0
}
sliceStart = (page - 1) * pageSize
sliceEnd = sliceStart + pageSize
if sliceEnd > nums {
sliceEnd = nums
}
return sliceStart, sliceEnd
}
// 读取文件夹下的所有文件
func GetAllFile(pathname string) ([]string, error) {
s := make([]string, 0)
rd, err := ioutil.ReadDir(pathname)
if err != nil {
fmt.Println("read dir fail:", err)
return s, err
}
for _, fi := range rd {
if !fi.IsDir() {
fullName := pathname + "/" + fi.Name()
s = append(s, fullName)
}
}
return s, nil
}