Thinkphp通过runtime日志还原mysql数据
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)
//}
}
本文是原创文章,采用 CC BY-NC-ND 4.0 协议,完整转载请注明来自 程序员小航
评论
匿名评论
隐私政策
你无需删除空行,直接评论以获取最佳展示效果