初始化项目

This commit is contained in:
皮蛋13361098506
2025-01-06 16:01:02 +08:00
commit 1b77f62820
575 changed files with 69193 additions and 0 deletions

View File

@@ -0,0 +1,73 @@
package sqlSync
import (
"encoding/binary"
"errors"
"os"
"goutil/fileUtil"
"goutil/intAndBytesUtil"
)
const (
// 头部字节长度
con_Header_Length = 4
)
var (
// 字节的大小端顺序
byteOrder = binary.LittleEndian
)
// 按照指定方式读取文本内容
// fileObj:大文件对象
// data:待写入的数据
// 返回值:
// error:写入是否存在异常
func Write(fileObj *fileUtil.BigFile, data string) error {
// 获得数据内容的长度
dataLength := len(data)
// 将长度转化为字节数组
header := intAndBytesUtil.Int32ToBytes(int32(dataLength), byteOrder)
// 将头部与内容组合在一起
message := append(header, data...)
// 写入数据
return fileObj.WriteMessage(message)
}
// 从文件读取一条数据
// fileObj:文件对象
// 返回值:
// result:读取到的字符串
// err:错误信息
func Read(fileObj *os.File) (result string, readLen int64, err error) {
// 1. 读取头部内容
header := make([]byte, 4)
var n int
n, err = fileObj.Read(header)
if err != nil {
return
}
if n < con_Header_Length {
err = errors.New("can not read 4 byte for read len")
readLen = int64(n)
return
}
dataLength := intAndBytesUtil.BytesToInt32(header, byteOrder)
// 2. 读取指定长度的内容
data := make([]byte, dataLength)
n, err = fileObj.Read(data)
if err != nil {
return
}
readLen = int64(len(header) + int(dataLength))
result = string(data)
return
}

View File

@@ -0,0 +1,139 @@
package sqlSync
import (
"fmt"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"sync"
"goutil/fileUtil"
"goutil/logUtil"
)
const (
// 第一个文件名
con_Default_FileName = "00000000"
// 文件名后缀
con_FileName_Suffix = "data"
)
// 同步数据对象(用于往文件中写入sql语句)
type SqlFile struct {
// 存放同步数据的文件夹路径
dirPath string
// 同步数据对象的唯一标识,用于进行重复判断
identifier string
// 保存数据的大文件对象
bigFileObj *fileUtil.BigFile
// 数据同步对象
mutex sync.Mutex
}
// 将数据写入同步数据对象
// data:待写入的数据
func (this *SqlFile) Write(data string) {
this.mutex.Lock()
defer this.mutex.Unlock()
// 写入数据
err := Write(this.bigFileObj, data)
if err != nil {
prefix := fmt.Sprintf("%s-%s", this.identifier, "SqlFile.write.bigFileObj.WriteMessage")
err = fmt.Errorf("%s-Write message to big file object failed:%s", prefix, err)
logUtil.ErrorLog(err.Error())
panic(err)
}
}
// 获取大文件对象的文件绝对路径
func (this *SqlFile) FileFullName() string {
return filepath.Join(this.dirPath, this.bigFileObj.FileName())
}
// 当前读写的文件名
func (this *SqlFile) FileName() string {
return this.bigFileObj.FileName()
}
// 创建同步数据对象
// _dirPath:目录的路径
// _identifier:当前数据的唯一标识(可以使用数据库表名)
// _maxFileSize:每个大文件的最大写入值单位Byte
// 返回值:
// 同步数据对象
func NewSqlFile(dirPath, identifier, fileName string, maxFileSize int) *SqlFile {
result := &SqlFile{
dirPath: dirPath,
identifier: identifier,
}
// 初始化大文件对象
if fileName == "" {
fileName = con_Default_FileName
}
bigFileObj, err := fileUtil.NewBigFileWithNewFileNameFunc2(dirPath, "", fileName, maxFileSize, NewFileName)
if err != nil {
prefix := fmt.Sprintf("%s-%s", result.identifier, "SqlFile.newSqlFile.fileUtil.NewBigFileWithNewFileNameFunc")
err = fmt.Errorf("%s-Create big file object failed:%s", prefix, err)
logUtil.ErrorLog(err.Error())
panic(err)
}
result.bigFileObj = bigFileObj
return result
}
// 根据当前文件名生成下一个sql文件名
// prefix:文件名前缀
// path:当前文件的路径
// 返回值:
// string:下一个文件的完整路径
func NewFileName(prefix, path string) string {
fullName := filepath.Base(path)
curFileName := strings.Split(fullName, ".")[0]
curFileId, err := strconv.Atoi(curFileName)
if err != nil {
err = fmt.Errorf("%s-Convert newFileName:%s to int failed:%s", prefix, curFileName, err)
logUtil.ErrorLog(err.Error())
panic(err)
}
newFileId := curFileId + 1
newFileName := fmt.Sprintf("%08d", newFileId)
// 加上文件后缀
newFileName = fmt.Sprintf("%s.%s", newFileName, con_FileName_Suffix)
return newFileName
}
// 获取文件夹下所有的sql文件
// dirPath:指定要获取的文件夹路径
// 返回值:
// []string:sql文件列表
func GetDataFileList(dirPath string) []string {
// 获取当前目录中所有的数据文件列表
fileList, err := fileUtil.GetFileList2(dirPath, "", con_FileName_Suffix)
if err != nil {
if os.IsNotExist(err) {
} else {
err = fmt.Errorf("%s/*.%s-Get file list failed:%s", dirPath, con_FileName_Suffix, err)
logUtil.ErrorLog(err.Error())
panic(err)
}
}
// 如果文件数量大于1则进行排序以便于后续处理
if len(fileList) > 1 {
sort.Strings(fileList)
}
return fileList
}

View File

@@ -0,0 +1,271 @@
package sqlSync
import (
"database/sql"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"time"
"goutil/debugUtil"
"goutil/fileUtil"
"goutil/logUtil"
)
// 同步对象定义
type SyncObject struct {
// 同步数据的存储路径
dirPath string
// 同步数据对象的唯一标识,用于进行重复判断
identifier string
// 数据库对象
dbObj *sql.DB
// 处理数据写入的文件
sqlFileObj *SqlFile
// 同步处理对象
syncHandleObj SyncHandler
}
// 进行同步对象初始化
// maxFileSize:每个大文件的最大写入值单位Byte
func (this *SyncObject) Init(maxFileSize int) {
// 启动时同步所有数据(然后才能从数据库中查询数据,以免数据丢失)
this.syncHandleObj.Init(this)
// 构造同步数据对象
fileName, _ := this.syncHandleObj.GetSyncingInfo()
this.sqlFileObj = NewSqlFile(this.dirPath, this.identifier, fileName, maxFileSize)
// 当前没有正在同步的文件,则指向当前正在写的文件
if len(fileName) <= 0 {
this.syncHandleObj.Update(this.sqlFileObj.FileFullName(), 0, nil)
}
// 启动一个新goroutine来负责同步数据
go func() {
/* 此处不使用goroutineMgr.Monitor/ReleaseMonitor因为此处不能捕获panic需要让外部进程终止执行
因为此模块的文件读写为核心逻辑,一旦出现问题必须停止进程,否则会造成脏数据
*/
this.Sync()
}()
}
// 保存数据到本地文件
// command:待保存的指令
func (this *SyncObject) Save(command string) {
this.sqlFileObj.Write(command)
}
// 循环同步多个文件
func (this *SyncObject) Sync() {
// 开始循环同步
for {
// 同步当前文件
this.syncOneFile()
// 当前文件同步完成,记录同步日志
nowFilePath, _ := this.syncHandleObj.GetSyncingInfo()
// 删除已同步完成的文件
WaitForOk(func() bool {
fileExist, err := fileUtil.IsFileExists(nowFilePath)
if err != nil {
logUtil.ErrorLog(fmt.Sprintf("mysqlSync/syncObject IsFileExists error:%s", err.Error()))
return false
}
if fileExist == false {
return true
}
err = fileUtil.DeleteFile(nowFilePath)
if err != nil {
logUtil.ErrorLog(fmt.Sprintf("mysqlSync/syncObject delete file error:%s", err.Error()))
return false
}
return true
}, 10*time.Second)
// 当前文件同步完成,获取下个文件
nextFileName := NewFileName("", nowFilePath)
filePath := filepath.Join(this.dirPath, nextFileName)
exist, err := fileUtil.IsFileExists(filePath)
if err != nil {
logUtil.ErrorLog(fmt.Sprintf("mysqlSync/syncObject IsFileExists error:%s", err.Error()))
panic(err)
}
// 如果文件不存在,退出
if !exist {
// fmt.Println("协程退出了")
return
}
// 更新同步的位置信息 此处忽略错误是因为,哪怕是出错了,也不会影响整体逻辑
this.syncHandleObj.Update(filePath, 0, nil)
}
}
// 同步单个文件
func (this *SyncObject) syncOneFile() {
// 获取信息同步项对象
filePath, offset := this.syncHandleObj.GetSyncingInfo()
// 打开待读取的文件
f, exist := this.openFile(filePath)
if exist == false {
// logUtil.WarnLog(fmt.Sprintf("待同步的文件不存在,跳过此文件:%s", filePath),"")
return
}
defer f.Close()
for {
// 移动到需要读取的位置
if _, err := f.Seek(offset, io.SeekStart); err != nil {
prefix := fmt.Sprintf("%s-%s", this.identifier, "SyncObject.Seek")
err = fmt.Errorf("%s-Seek offset for header failed:%s", prefix, err)
logUtil.ErrorLog(err.Error())
panic(err)
}
command, readLen, err := Read(f)
if err != nil {
// 如果读取到文件末尾,判断是否等待
if err == io.EOF {
if this.sqlFileObj != nil && strings.Contains(filePath, this.sqlFileObj.FileName()) {
time.Sleep(20 * time.Millisecond)
continue
}
// 如果该文件是空文件,同步更新信息
return
}
prefix := fmt.Sprintf("%s-%s", this.identifier, "SyncObject.syncOneFile.f.Read")
err = fmt.Errorf("%s-Read header failed:%s", prefix, err)
logUtil.ErrorLog(err.Error())
panic(err)
}
// 3. 同步到mysql中,并更新同步位置
this.syncHandleObj.SyncOneSql(command, filePath, offset+readLen)
// 4. 更新内存中的同步位置
offset += readLen
}
}
// 打开待读取的文件
// filePath:待打开的文件
// 返回值:
// *os.File:文件句柄,
func (this *SyncObject) openFile(filePath string) (f *os.File, exist bool) {
var err error
for {
exist, err = fileUtil.IsFileExists(filePath)
if err != nil {
err = fmt.Errorf("check file error,filePath:%v error:%v", filePath, err.Error())
logUtil.ErrorLog(err.Error())
time.Sleep(time.Second * 5)
continue
}
if exist == false {
// 如果文件不存在,则跳过此文件
logUtil.WarnLog(fmt.Sprintf("file no exist, skip file:%v", filePath))
exist = false
return
}
exist = true
// 打开当前处理文件
f, err = os.OpenFile(filePath, os.O_RDONLY, os.ModePerm|os.ModeTemporary)
if err != nil {
prefix := fmt.Sprintf("%s-%s", this.identifier, "SyncObject.syncOneFile.os.OpenFile")
err = fmt.Errorf("%s-Open file:%s failed:%s", prefix, filePath, err)
logUtil.ErrorLog(err.Error())
time.Sleep(time.Second * 5)
continue
}
return
}
}
// 同步数据到mysql中
// command:sql语句
// tx:事务处理对象
// 返回值:
// error:错误信息
func (this *SyncObject) syncToMysql(command string, tx *sql.Tx) error {
_, err := tx.Exec(command)
if err != nil {
prefix := fmt.Sprintf("%s-%s", this.identifier, "SyncObject.syncToMysql")
err = fmt.Errorf("%s-%s Update to mysql failed:%s", prefix, command, err)
logUtil.ErrorLog(err.Error())
debugUtil.Printf("fatal Error:%v", err.Error())
return err
}
return nil
}
// 创新新的mysql同步对象
// dirPath:存放数据的目录
// identifier:当前数据的唯一标识(可以使用数据库表名)
// dbObj:数据库对象
// _syncHandleObj:同步处理对象
// 返回值:
// mysql同步对象
func NewSyncObject(dirPath, identifier string, dbObj *sql.DB, _syncHandleObj SyncHandler) *SyncObject {
dirPath = filepath.Join(dirPath, identifier)
// 创建更新目录
err := os.MkdirAll(dirPath, os.ModePerm|os.ModeTemporary)
if err != nil {
err = fmt.Errorf("%s-%s-make dir failed:%s", identifier, "SyncObject.newSyncObject.os.MkdirAll", err)
logUtil.ErrorLog(err.Error())
panic(err)
}
// 构造同步信息对象
result := &SyncObject{
dirPath: dirPath,
identifier: identifier,
dbObj: dbObj,
syncHandleObj: _syncHandleObj,
}
return result
}
// 同步处理接口
type SyncHandler interface {
// 初始化
Init(baseObj *SyncObject)
// 获取正在同步的信息
// filePath:文件路径
// offset:文件偏移量
GetSyncingInfo() (filePath string, offset int64)
// 更新
// filePath:文件路径
// offset:文件偏移量
// tran:事务对象
// 返回值:
// error:错误对象
Update(filePath string, offset int64, tran *sql.Tx) error
// 同步一条sql
// command:指令数据
// filePath:文件路径
// offset:文件偏移量
SyncOneSql(command string, filePath string, offset int64)
}

View File

@@ -0,0 +1,99 @@
package sqlSync
import (
"database/sql"
"fmt"
"strings"
"time"
"goutil/logUtil"
)
// 以事务的方式执行
// db:数据库对象
// funcObj:对应的具体处理函数
// 返回值:
// error:处理是否存在错误
func ExecuteByTran(db *sql.DB, funcObj func(tran *sql.Tx) (isCommit bool, err error)) error {
tran, err := db.Begin()
if err != nil {
logUtil.ErrorLog(fmt.Sprintf("start transaction error:%v", err.Error()))
return err
}
// 事务处理
isCommit := false
defer func() {
if isCommit {
err = tran.Commit()
} else {
err = tran.Rollback()
}
if err != nil {
logUtil.ErrorLog(fmt.Sprintf("transaction end error:%v", err.Error()))
}
}()
isCommit, err = funcObj(tran)
return err
}
// 循环执行知道返回成功为止
// funcObj:待执行的函数
// interval:执行间隔时间
func WaitForOk(funcObj func() bool, interval time.Duration) {
for {
if funcObj() == false {
time.Sleep(interval)
}
break
}
}
// 检查是否是连接错误
// errMsg:错误信息
// 返回值:
// bool:true连接错误 false:其他异常
func CheckIfConnectionError(errMsg string) bool {
//// 连接被关闭
ifConnectionClose := strings.Contains(errMsg, "A connection attempt failed because the connected party did not properly respond")
if ifConnectionClose {
return true
}
// 使用过程中连接断开
ifConnectionClose = strings.Contains(errMsg, "No connection could be made")
if ifConnectionClose {
return true
}
// 事务处理过程中连接断开的提示
ifConnectionClose = strings.Contains(errMsg, "bad connection")
if ifConnectionClose {
return true
}
// socket压根儿连不上的处理
ifConnectionClose = strings.Contains(errMsg, "A socket operation was attempted to an unreachable network")
if ifConnectionClose {
return true
}
// 用户无法访问
return strings.Contains(errMsg, "Access denied for user")
}
// 获取比较简洁的错误信息
// errMsg:错误信息
// 返回值:
// string:比较简洁的错误信息
func GetSimpleErrorMessage(errMsg string) string {
if strings.Contains(errMsg, "Error 1064: You have an error in your SQL syntax") {
return "SqlError"
}
return errMsg
}