package main

import (
	"bufio"
	"database/sql"
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"time"

	_ "github.com/go-sql-driver/mysql"
)

// 新增结构体用于保存文件信息
type fileInfo struct {
	path    string
	modTime time.Time
}

func main() {
	// 数据库配置
	dbConfig := struct {
		Username string
		Password string
		Host     string
		Port     string
		DBName   string
	}{
		Username: "caidoubao",
		Password: "Juzi1102",
		Host:     "222.211.75.129",
		Port:     "3306",
		DBName:   "caidoubao",
	}

	// 建立数据库连接
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8&parseTime=True",
		dbConfig.Username,
		dbConfig.Password,
		dbConfig.Host,
		dbConfig.Port,
		dbConfig.DBName)

	db, err := sql.Open("mysql", dsn)
	if err != nil {
		panic(fmt.Sprintf("数据库连接失败: %v", err))
	}
	defer db.Close()

	// 创建SQL收集文件
	outputFile, err := os.Create("sql_commands.sql")
	if err != nil {
		panic(fmt.Sprintf("创建输出文件失败: %v", err))
	}
	defer outputFile.Close()

	// 遍历日志目录
	logDir := "./logs"
	var files []fileInfo

	// 先收集所有日志文件信息
	err = filepath.Walk(logDir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if info.IsDir() || filepath.Ext(path) != ".log" {
			return nil
		}
		// 记录文件路径和修改时间
		files = append(files, fileInfo{
			path:    path,
			modTime: info.ModTime(),
		})
		return nil
	})
	if err != nil {
		panic(fmt.Sprintf("遍历目录失败: %v", err))
	}

	// 按修改时间排序(从旧到新)
	sort.Slice(files, func(i, j int) bool {
		return files[i].modTime.Before(files[j].modTime)
	})

	// 按顺序处理文件
	for _, f := range files {
		//fmt.Printf("正在处理文件: %s (修改时间: %s)\n", f.path, f.modTime.Format("2006-01-02 15:04:05"))
		processLogFile(f.path, db, outputFile)
	}
}

// 处理单个日志文件
func processLogFile(filePath string, db *sql.DB, outputFile *os.File) {
	file, err := os.Open(filePath)
	if err != nil {
		fmt.Printf("无法打开文件 %s: %v\n", filePath, err)
		return
	}
	defer file.Close()

	// 正则表达式(优化后的版本)
	sqlRegex := regexp.MustCompile(`(?is)\[sql\][^\[]*?\b(INSERT|UPDATE|DELETE)\b.*?\[ RunTime:`)
	filterRegex := regexp.MustCompile(`(?i)\b(jz_token|jz_captcha)\b`)

	// 创建事务
	//tx, err := db.Begin()
	//if err != nil {
	//	fmt.Printf("创建事务失败: %v\n", err)
	//	return
	//}

	var sqlCommands []string // 用于收集SQL语句

	scanner := bufio.NewScanner(file)
	lineNum := 0
	for scanner.Scan() {
		lineNum++
		line := scanner.Text()

		// 匹配SQL语句
		if matches := sqlRegex.FindStringSubmatch(line); len(matches) > 1 {
			// 提取SQL语句
			sqlStmt := regexp.MustCompile(`\[ RunTime:.*$`).ReplaceAllString(line, "")
			sqlStmt = regexp.MustCompile(`^.*?\[sql\]\s*`).ReplaceAllString(sqlStmt, "")

			// 过滤特定表
			if filterRegex.MatchString(sqlStmt) {
				//fmt.Printf("跳过涉及敏感表的SQL[L%d]: %s\n", lineNum, sqlStmt)
				continue
			}

			// 执行SQL
			//if _, err := tx.Exec(sqlStmt); err != nil {
			//	tx.Rollback()
			//	fmt.Printf("执行SQL失败[L%d]: %v\nSQL: %s\n", lineNum, err, sqlStmt)
			//	return
			//} else {
			//	fmt.Printf("执行SQL[L%d]: %v\nSQL: %s\n", lineNum, err, sqlStmt)
			//}
			// 收集成功执行的SQL
			sqlCommands = append(sqlCommands, sqlStmt)
			fmt.Printf("已处理SQL[L%d]: %s\n", lineNum, sqlStmt)

		}
	}

	// 将收集的SQL写入文件
	for _, cmd := range sqlCommands {
		_, err := fmt.Fprintf(outputFile, "%s;\n", cmd)
		if err != nil {
			fmt.Printf("写入文件失败: %v\n", err)
			return
		}
	}

	//if err := tx.Commit(); err != nil {
	//	fmt.Printf("提交事务失败: %v\n", err)
	//	return
	//}
	//
	//if err := scanner.Err(); err != nil {
	//	fmt.Printf("读取文件错误: %v\n", err)
	//}
}

先说一下前因:因某次业务服务器系统盘容量不足,手动扩容系统系统磁盘时不小心把系统盘格式化了 导致mysql数据丢失 也没异地保存!

!!!这里提醒各位铁子注意保存数据注意保存数据

!!!这里提醒各位铁子注意保存数据注意保存数据

!!!这里提醒各位铁子注意保存数据注意保存数据

下面说一下怎么通过日志还原:第一步 先确认你的thinkphp项目有没有runtime日志

一般是根据年月日生成如图:

打开后时这样的:

我们按照修改日期排序后:

这个就是具体的日志顺序

随意打开一个文件可以知道前面这里时什么时间的 然后就要把你不需要还原的日志给全部删除了

执行底部的代码提取sql执行语句获取sql文件即可还原!大概原理就是提取INSERT|UPDATE|DELETE语句 过滤掉查询语句

package main

import (
	"bufio"
	"database/sql"
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"time"

	_ "github.com/go-sql-driver/mysql"
)

// 新增结构体用于保存文件信息
type fileInfo struct {
	path    string
	modTime time.Time
}

func main() {
	// 数据库配置
	dbConfig := struct {
		Username string
		Password string
		Host     string
		Port     string
		DBName   string
	}{
		Username: "输入mysql账号",
		Password: "输入mysql密码",
		Host:     "输入ip",
		Port:     "3306",
		DBName:   "数据库名",
	}

	// 建立数据库连接
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8&parseTime=True",
		dbConfig.Username,
		dbConfig.Password,
		dbConfig.Host,
		dbConfig.Port,
		dbConfig.DBName)

	db, err := sql.Open("mysql", dsn)
	if err != nil {
		panic(fmt.Sprintf("数据库连接失败: %v", err))
	}
	defer db.Close()

	// 创建SQL收集文件
	outputFile, err := os.Create("sql_commands.sql")
	if err != nil {
		panic(fmt.Sprintf("创建输出文件失败: %v", err))
	}
	defer outputFile.Close()

	// 遍历日志目录
	logDir := "./logs"
	var files []fileInfo

	// 先收集所有日志文件信息
	err = filepath.Walk(logDir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if info.IsDir() || filepath.Ext(path) != ".log" {
			return nil
		}
		// 记录文件路径和修改时间
		files = append(files, fileInfo{
			path:    path,
			modTime: info.ModTime(),
		})
		return nil
	})
	if err != nil {
		panic(fmt.Sprintf("遍历目录失败: %v", err))
	}

	// 按修改时间排序(从旧到新)
	sort.Slice(files, func(i, j int) bool {
		return files[i].modTime.Before(files[j].modTime)
	})

	// 按顺序处理文件
	for _, f := range files {
		//fmt.Printf("正在处理文件: %s (修改时间: %s)\n", f.path, f.modTime.Format("2006-01-02 15:04:05"))
		processLogFile(f.path, db, outputFile)
	}
}

// 处理单个日志文件
func processLogFile(filePath string, db *sql.DB, outputFile *os.File) {
	file, err := os.Open(filePath)
	if err != nil {
		fmt.Printf("无法打开文件 %s: %v\n", filePath, err)
		return
	}
	defer file.Close()

	// 正则表达式(优化后的版本)
	sqlRegex := regexp.MustCompile(`(?is)\[sql\][^\[]*?\b(INSERT|UPDATE|DELETE)\b.*?\[ RunTime:`)

    //这个是过滤的表 无需要可以删掉 
	filterRegex := regexp.MustCompile(`(?i)\b(jz_token|jz_captcha)\b`)

	// 创建事务
	//tx, err := db.Begin()
	//if err != nil {
	//	fmt.Printf("创建事务失败: %v\n", err)
	//	return
	//}

	var sqlCommands []string // 用于收集SQL语句

	scanner := bufio.NewScanner(file)
	lineNum := 0
	for scanner.Scan() {
		lineNum++
		line := scanner.Text()

		// 匹配SQL语句
		if matches := sqlRegex.FindStringSubmatch(line); len(matches) > 1 {
			// 提取SQL语句
			sqlStmt := regexp.MustCompile(`\[ RunTime:.*$`).ReplaceAllString(line, "")
			sqlStmt = regexp.MustCompile(`^.*?\[sql\]\s*`).ReplaceAllString(sqlStmt, "")

			// 过滤特定表  //如无需要可以删除
			if filterRegex.MatchString(sqlStmt) {
				//fmt.Printf("跳过涉及敏感表的SQL[L%d]: %s\n", lineNum, sqlStmt)
				continue
			}

			// 执行SQL
			//if _, err := tx.Exec(sqlStmt); err != nil {
			//	tx.Rollback()
			//	fmt.Printf("执行SQL失败[L%d]: %v\nSQL: %s\n", lineNum, err, sqlStmt)
			//	return
			//} else {
			//	fmt.Printf("执行SQL[L%d]: %v\nSQL: %s\n", lineNum, err, sqlStmt)
			//}
			// 收集成功执行的SQL
			sqlCommands = append(sqlCommands, sqlStmt)
			fmt.Printf("已处理SQL[L%d]: %s\n", lineNum, sqlStmt)

		}
	}

	// 将收集的SQL写入文件
	for _, cmd := range sqlCommands {
		_, err := fmt.Fprintf(outputFile, "%s;\n", cmd)
		if err != nil {
			fmt.Printf("写入文件失败: %v\n", err)
			return
		}
	}

	//if err := tx.Commit(); err != nil {
	//	fmt.Printf("提交事务失败: %v\n", err)
	//	return
	//}
	//
	//if err := scanner.Err(); err != nil {
	//	fmt.Printf("读取文件错误: %v\n", err)
	//}
}