初始化项目

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,13 @@
package mysqlSync
/*
提供数据同步到mysql的方法。基本逻辑如下
1、对外接收数据以追加的方式保存到大文件中。数据的格式为header(4bytes)+content。
2、启动独立的goroutine来从大文件中读取数据并保存到数据库中。
3、使用syncInfo.txt文件保存当前已经处理的文件的路径以及下一次将要读取的文件的Offset。为了降低向syncInfo.txt文件中写入失败
导致需要从头开始同步数据,所以采用了在指定数目的范围内以追加形式来写入数据的方式;只有达到了指定数量才会将整个文件清空。
对于错误的处理方式,分为以下两种:
1、文件错误由于文件系统是本系统的核心所以如果出现文件的读写出错则需要终止整个进程所以需要抛出panic。
2、数据库错误当数据库不可访问时为了不影响整个外部进程的运行故而不抛出panic而只是通过monitorNewMgr.Report的方式来报告故障。
*/

View File

@@ -0,0 +1,100 @@
package mysqlSync
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"goutil/fileUtil"
"goutil/logUtil"
)
var (
// 记录错误sql命令的文件名
con_Error_FileName = "errorFile.txt"
)
// 定义处理错误命令的文件对象
type errorFile struct {
// 错误文件
file *os.File
// 文件路径
filePath string
// 同步数据对象的唯一标识,用于进行重复判断
identifier string
}
// 保存命令到错误文件
// command: sql命令
func (this *errorFile) SaveCommand(command string) {
this.open()
defer this.close()
// 覆盖写入
this.file.Seek(0, 0)
// 写入命令
_, err := this.file.WriteString(command)
if err != nil {
prefix := fmt.Sprintf("%s-%s", this.identifier, "errorFile.SaveCommand")
err = fmt.Errorf("%s-Write %s to file failed:%s", prefix, command, err)
logUtil.ErrorLog(err.Error())
panic(err)
}
// 清理残留数据
this.file.Truncate(int64(len(command)))
}
// 读取文件中命令
func (this *errorFile) ReadCommand() string {
this.open()
defer this.close()
this.file.Seek(0, 0)
content, err := ioutil.ReadAll(this.file)
if err != nil {
prefix := fmt.Sprintf("%s-%s", this.identifier, "errorFile.ReadCommand")
err = fmt.Errorf("%s-Read command failed:%s", prefix, err)
logUtil.ErrorLog(err.Error())
panic(err)
}
return string(content)
}
// 打开文件
func (this *errorFile) open() {
// 打开errorFile文件, 如果没有就创建
var err error
this.file, err = os.OpenFile(this.filePath, os.O_CREATE|os.O_RDWR, os.ModePerm|os.ModeTemporary)
if err != nil {
prefix := fmt.Sprintf("%s-%s", this.identifier, "errorFile.newErrorFile.os.OpenFile")
err = fmt.Errorf("%s-Open File failed:%s", prefix, err)
logUtil.ErrorLog(err.Error())
panic(err)
}
}
// 关闭文件
func (this *errorFile) close() {
this.file.Close()
}
// 删除文件
func (this *errorFile) Delete() {
fileUtil.DeleteFile(this.filePath)
}
// 构造错误文件对象
// _dirPath:文件路径
// _identifier:唯一标识
func newErrorFile(_dirPath string, _identifier string) *errorFile {
_filePath := filepath.Join(_dirPath, con_Error_FileName)
return &errorFile{
filePath: _filePath,
identifier: _identifier,
}
}

View File

@@ -0,0 +1,88 @@
package logSqlSync
import (
"database/sql"
"fmt"
"time"
"goutil/logUtil"
)
// 错误信息记录表是否已经初始化
var ifSyncErrorInfoTableInited bool = false
// 同步的错误信息处理对象
type syncErrorInfo struct {
// 数据库连接对象
db *sql.DB
}
// 初始化表信息
func (this *syncErrorInfo) init() error {
// 初始化表结构
if ifSyncErrorInfoTableInited == false {
err := this.initTable(this.db)
if err == nil {
ifSyncErrorInfoTableInited = true
}
return err
}
return nil
}
// 把同步信息更新到数据库
// data:待更新的数据
// 返回值:
// error:错误信息
func (this *syncErrorInfo) AddErrorSql(tran *sql.Tx, data string, errMsg string) error {
updateSql := "INSERT INTO `sync_error_info` (`SqlString`,`ExecuteTime`,`RetryCount`,`ErrMessage`) VALUES(?,?,?,?);"
var err error
if tran != nil {
_, err = tran.Exec(updateSql, data, time.Now(), 0, errMsg)
} else {
_, err = this.db.Exec(updateSql, data, time.Now(), 0, errMsg)
}
if err != nil {
logUtil.ErrorLog(fmt.Sprintf("logSqlSync/syncErrorInfo.AddErrorSql Error:%s", err.Error()))
}
return err
}
// 初始化同步信息表结构
// db:数据库连接对象
func (this *syncErrorInfo) initTable(db *sql.DB) error {
// 创建同步信息表
createTableSql := `CREATE TABLE IF NOT EXISTS sync_error_info (
Id bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增Id',
SqlString varchar(1024) NOT NULL COMMENT '执行的sql',
ExecuteTime datetime NOT NULL COMMENT '最近一次执行时间',
RetryCount int NOT NULL COMMENT '重试次数',
ErrMessage text NULL COMMENT '执行错误的信息',
PRIMARY KEY (Id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='未执行成功的sql数据';`
if _, err := db.Exec(createTableSql); err != nil {
logUtil.ErrorLog(fmt.Sprintf("logSqlSync/syncErrorInfo.initTable Error:%s", err.Error()))
return err
}
return nil
}
// 创建同步信息对象
// _db:数据库连接对象
// 返回值:
// 同步信息对象
func newSyncErrorInfoObject(_db *sql.DB) (result *syncErrorInfo, err error) {
result = &syncErrorInfo{
db: _db,
}
err = result.init()
return result, err
}

View File

@@ -0,0 +1,204 @@
package logSqlSync
import (
"database/sql"
"fmt"
"os"
"path/filepath"
"time"
"Framework/dataSyncMgr/mysqlSync/sqlSync"
"goutil/logUtil"
)
// 同步对象定义
type SyncObject struct {
// 服务器组Id
serverGroupId int32
// 同步数据的存储路径
dirPath string
// 同步数据对象的唯一标识,用于进行重复判断
identifier string
// 数据库对象
dbObj *sql.DB
// 同步信息对象
syncingInfoObj *syncingInfo
// 错误处理对象
errorHandleObj *syncErrorInfo
// 同步对象
syncObj *sqlSync.SyncObject
}
// 初始化
// baseObj:基础同步对象
func (this *SyncObject) Init(baseObj *sqlSync.SyncObject) {
this.syncObj = baseObj
// 初始化同步信息对象
syncingInfoObj, err := newSyncingInfoObject(this.serverGroupId, this.dbObj)
if err != nil {
panic(err)
}
//// 初始化错误处理对象
errorHandleObj, err := newSyncErrorInfoObject(this.dbObj)
if err != nil {
panic(err)
}
this.syncingInfoObj = syncingInfoObj
this.errorHandleObj = errorHandleObj
// 初始化当前处理的文件
fileList := sqlSync.GetDataFileList(this.dirPath)
filePath, _ := this.syncingInfoObj.GetSyncingInfo()
if len(filePath) < 0 && len(fileList) > 0 {
this.syncingInfoObj.Update(fileList[0], 0, nil)
}
}
// 获取正在同步的信息
// filePath:文件路径
// offset:文件偏移量
func (this *SyncObject) GetSyncingInfo() (filePath string, offset int64) {
return this.syncingInfoObj.GetSyncingInfo()
}
// 更新
// filePath:文件路径
// offset:文件偏移量
// tran:事务对象
// 返回值:
// error:错误对象
func (this *SyncObject) Update(filePath string, offset int64, tx *sql.Tx) error {
return this.syncingInfoObj.Update(filePath, offset, tx)
}
// 同步一条sql语句
// command:待执行的命令
// filePath:保存路径
// offset:文件偏移量
// 返回值:
// error:错误信息
func (this *SyncObject) SyncOneSql(command string, filePath string, offset int64) {
var err error
for {
err = sqlSync.ExecuteByTran(this.dbObj, func(tran *sql.Tx) (isCommit bool, err error) {
// 保存sql到数据库
err = this.syncToMysql(command, tran)
if err != nil {
return
}
// 保存进度信息到数据库
err = this.syncingInfoObj.Update(filePath, offset, tran)
if err != nil {
return
}
isCommit = true
return
})
// 如果是连接出错,则仍然循环执行
if err != nil {
if sqlSync.CheckIfConnectionError(err.Error()) {
time.Sleep(5 * time.Second)
continue
}
}
// 如果不是数据库连接出错,则算是执行完成
break
}
// 如果存在错误,则循环尝试执行
if err != nil {
this.recordSqlError(command, filePath, offset, err.Error())
}
return
}
// 同步数据到mysql中
// command:sql语句
// tx:事务处理对象
// 返回值:
// error:错误信息
func (this *SyncObject) syncToMysql(command string, tx *sql.Tx) error {
_, err := tx.Exec(command)
if err != nil {
logUtil.ErrorLog(fmt.Sprintf("mysqlSync/logSqlSync/syncObject.syncToMysql error:%s", err.Error()))
return err
}
return nil
}
// 错误处理
// cmd:待执行的命令
// filePath:保存路径
// offset:文件偏移量
// errMsg:错误信息
func (this *SyncObject) recordSqlError(command string, filePath string, offset int64, errMsg string) {
errMsg = sqlSync.GetSimpleErrorMessage(errMsg)
for {
err := sqlSync.ExecuteByTran(this.dbObj, func(tran *sql.Tx) (isCommit bool, err error) {
// 保存sql到数据库
err = this.errorHandleObj.AddErrorSql(tran, command, errMsg)
if err != nil {
return
}
// 保存进度信息到数据库
err = this.syncingInfoObj.Update(filePath, offset, tran)
if err != nil {
return
}
isCommit = true
return
})
if err == nil {
return
}
time.Sleep(5 * time.Second)
}
}
// 创新新的mysql同步对象
// dirPath:存放数据的目录
// identifier:当前数据的唯一标识(可以使用数据库表名)
// dbObj:数据库对象
// syncingInfoObj:同步信息记录对象
// errorHandleObj:错误处理对象
// 返回值:
// mysql同步对象
func NewSyncObject(serverGroupId int32, dirPath, identifier string, dbObj *sql.DB) *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{
serverGroupId: serverGroupId,
dirPath: dirPath,
identifier: identifier,
dbObj: dbObj,
}
return result
}

View File

@@ -0,0 +1,186 @@
package logSqlSync
import (
"database/sql"
"fmt"
"time"
"goutil/logUtil"
)
// 同步信息表是否已经被初始化
var ifSyncingTableInited bool = false
// 同步信息项,保存已经处理过的文件的信息
type syncingModel struct {
// 服务器组Id
ServerGroupId int32
// 待处理文件的绝对路径
FilePath string
// 待处理文件的偏移量
FileOffset int64
// 更新时间
UpdateTime time.Time
}
// 同步信息对象
type syncingInfo struct {
// 服务器组Id
ServerGroupId int32
// 同步信息项
item *syncingModel
// 数据库连接对象
db *sql.DB
}
// 获取同步信息
// filePath:正在同步的文件
// fileOffset:同步到的位置
func (this *syncingInfo) GetSyncingInfo() (filePath string, fileOffset int64) {
return this.item.FilePath, this.item.FileOffset
}
// 更新正在同步的位置和文件信息
// filePath:文件路径
// offset:当前同步到的位置
// tran:事务对象可以为nil
// 返回值:
// error:处理的错误信息
func (this *syncingInfo) Update(filePath string, offset int64, tran *sql.Tx) error {
this.item.FilePath = filePath
this.item.FileOffset = offset
this.item.UpdateTime = time.Now()
// 更新到数据库
return this.update(this.item, tran)
}
// 初始化同步信息
// 返回值:
// error:错误信息
func (this *syncingInfo) init() error {
// 数据表初始化
if ifSyncingTableInited == false {
if err := this.initSyncingInfoTable(this.db); err == nil {
ifSyncingTableInited = true
} else {
return err
}
}
// 获取此表的同步信息
data, exist, err := this.get()
if err != nil {
return err
}
// 2. 如果同步信息不存在,则初始化一条到此表
if exist == false {
data = &syncingModel{
ServerGroupId: this.ServerGroupId,
FilePath: "",
FileOffset: 0,
UpdateTime: time.Now(),
}
}
this.item = data
return nil
}
// 初始化同步信息表结构
// db:数据库连接对象
func (this *syncingInfo) initSyncingInfoTable(db *sql.DB) error {
// 创建同步信息表
createTableSql := `CREATE TABLE IF NOT EXISTS syncing_info (
ServerGroupId int NOT NULL COMMENT '服务器组Id',
FilePath varchar(500) NOT NULL COMMENT '正在同步的文件路径',
FileOffset bigint(20) NOT NULL COMMENT '偏移量',
UpdateTime datetime NOT NULL COMMENT '最后一次更新时间',
PRIMARY KEY (ServerGroupId)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='正在同步的文件信息';`
if _, err := db.Exec(createTableSql); err != nil {
logUtil.ErrorLog(fmt.Sprintf("logSqlSync/syncingInfo.initSyncingInfoTable Error:%s", err.Error()))
return err
}
return nil
}
// 从数据库获取数据
// 返回值:
// data:获取到的数据
// exist:是否存在此数据
// err:错误信息
func (this *syncingInfo) get() (data *syncingModel, exist bool, err error) {
//// 从数据库查询
querySql := fmt.Sprintf("SELECT FilePath,FileOffset,UpdateTime FROM syncing_info WHERE ServerGroupId ='%v'", this.ServerGroupId)
var rows *sql.Rows
rows, err = this.db.Query(querySql)
if err != nil {
logUtil.ErrorLog(fmt.Sprintf("logSqlSync/syncingInfo.get.Query ServerGroupId:%v error:%s", this.ServerGroupId, err.Error()))
return
}
defer rows.Close()
if rows.Next() == false {
exist = false
return
}
exist = true
// 读取数据
data = &syncingModel{
ServerGroupId: this.ServerGroupId,
}
err = rows.Scan(&data.FilePath, &data.FileOffset, &data.UpdateTime)
if err != nil {
logUtil.ErrorLog(fmt.Sprintf("logSqlSync/syncingInfo.get.Query ServerGroupId:%v error:%s", this.ServerGroupId, err.Error()))
return
}
return
}
// 把同步信息更新到数据库
// data:待更新的数据
// tran:事务处理对象
// 返回值:
// error:错误信息
func (this *syncingInfo) update(data *syncingModel, tran *sql.Tx) error {
updateSql := "REPLACE INTO `syncing_info` SET `ServerGroupId` = ?, `FilePath` = ?,`FileOffset` = ?, `UpdateTime` = ?;"
var err error
if tran != nil {
_, err = tran.Exec(updateSql, data.ServerGroupId, data.FilePath, data.FileOffset, data.UpdateTime)
} else {
_, err = this.db.Exec(updateSql, data.ServerGroupId, data.FilePath, data.FileOffset, data.UpdateTime)
}
if err != nil {
logUtil.ErrorLog(fmt.Sprintf("logSqlSync/syncingInfo.update ServerGroupId:%v error:%s", this.ServerGroupId, err.Error()))
}
return err
}
// 创建同步信息对象
// _dirPath:目录的路径
// _identifier:当前数据的唯一标识(可以使用数据库表名)
// _db:数据库连接对象
// 返回值:
// 同步信息对象
func newSyncingInfoObject(serverGroupId int32, _db *sql.DB) (result *syncingInfo, err error) {
result = &syncingInfo{
ServerGroupId: serverGroupId,
db: _db,
}
err = result.init()
return result, err
}

View File

@@ -0,0 +1,62 @@
package main
import (
"fmt"
"github.com/go-sql-driver/mysql"
"github.com/jinzhu/gorm"
"Framework/dataSyncMgr/mysqlSync"
"goutil/logUtil"
)
var _ = mysql.DeregisterLocalFile
var (
connectionString = "root:moqikaka3309@tcp(10.1.0.10:3309)/develop_liujun?charset=utf8&parseTime=true&loc=Local&timeout=60s"
maxOpenConns = 10
maxIdleConns = 10
syncFileSize = 1024 * 1024
)
var (
// 数据库对象
dbObj *gorm.DB
// 同步管理对象
syncMgr *mysqlSync.SyncMgr
)
func init() {
// 初始化数据库连接
dbObj = initMysql()
// 构造同步管理对象
syncMgr = mysqlSync.NewLogSyncMgr(1, "Sync", syncFileSize, dbObj.DB())
}
// 初始化Mysql
func initMysql() *gorm.DB {
dbObj, err := gorm.Open("mysql", connectionString)
if err != nil {
panic(fmt.Errorf("初始化数据库:%s失败错误信息为%s", connectionString, err))
}
logUtil.DebugLog(fmt.Sprintf("连接mysql:%s成功", connectionString))
if maxOpenConns > 0 && maxIdleConns > 0 {
dbObj.DB().SetMaxOpenConns(maxOpenConns)
dbObj.DB().SetMaxIdleConns(maxIdleConns)
}
return dbObj
}
// 注册同步对象
func registerSyncObj(identifier string) {
syncMgr.RegisterSyncObj(identifier)
}
// 保存sql数据
func save(identifier string, command string) {
syncMgr.Save(identifier, command)
}

View File

@@ -0,0 +1,82 @@
package main
import (
"fmt"
"sync"
"time"
"goutil/stringUtil"
)
var (
wg sync.WaitGroup
)
func init() {
wg.Add(1)
}
func main() {
playerMgr := newPlayerMgr()
// insert
go func() {
for {
id := stringUtil.GetNewGUID()
name := fmt.Sprintf("Hero_%s", id)
obj := newPlayer(id, name)
playerMgr.insert(obj)
insert(obj)
time.Sleep(10 * time.Millisecond)
}
}()
/*
// update
go func() {
for {
obj := playerMgr.randomSelect()
if obj == nil {
continue
}
suffix := mathUtil.GetRandInt(1000)
newName := fmt.Sprintf("Hero_%d", suffix)
obj.resetName(newName)
update(obj)
time.Sleep(10 * time.Millisecond)
}
}()
// delete
go func() {
for {
obj := playerMgr.randomSelect()
if obj == nil {
continue
}
playerMgr.delete(obj)
clear(obj)
time.Sleep(10 * time.Millisecond)
}
}()
// errorFile
go func() {
for {
time.Sleep(1 * time.Hour)
id := stringUtil.GetNewGUID()
name := fmt.Sprintf("Hero_%s%s", id, id)
obj := newPlayer(id, name)
playerMgr.insert(obj)
print("errorFile")
insert(obj)
}
}()
*/
wg.Wait()
}

View File

@@ -0,0 +1,64 @@
package main
import (
"sync"
)
type player struct {
// 玩家id
Id string `gorm:"column:Id;primary_key"`
// 玩家名称
Name string `gorm:"column:Name"`
}
func (this *player) resetName(name string) {
this.Name = name
}
func (this *player) tableName() string {
return "player"
}
func newPlayer(id, name string) *player {
return &player{
Id: id,
Name: name,
}
}
type playerMgr struct {
playerMap map[string]*player
mutex sync.Mutex
}
func (this *playerMgr) insert(obj *player) {
this.mutex.Lock()
defer this.mutex.Unlock()
this.playerMap[obj.Id] = obj
}
func (this *playerMgr) delete(obj *player) {
this.mutex.Lock()
defer this.mutex.Unlock()
delete(this.playerMap, obj.Id)
}
func (this *playerMgr) randomSelect() *player {
this.mutex.Lock()
defer this.mutex.Unlock()
for _, obj := range this.playerMap {
return obj
}
return nil
}
func newPlayerMgr() *playerMgr {
return &playerMgr{
playerMap: make(map[string]*player),
}
}

View File

@@ -0,0 +1,29 @@
//package test
package main
import (
"fmt"
)
var (
con_player_tableName = "player"
)
func init() {
registerSyncObj(con_player_tableName)
}
func insert(obj *player) {
command := fmt.Sprintf("INSERT INTO `%s` (`Id`,`Name`) VALUES ('%v','%v') ", con_player_tableName, obj.Id, obj.Name)
save(con_player_tableName, command)
}
func update(obj *player) {
command := fmt.Sprintf("UPDATE `%s` SET `Name` = '%v' WHERE `Id` = '%v';", con_player_tableName, obj.Name, obj.Id)
save(con_player_tableName, command)
}
func clear(obj *player) {
command := fmt.Sprintf("DELETE FROM %s where Id = '%v';", con_player_tableName, obj.Id)
save(con_player_tableName, command)
}

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
}

View File

@@ -0,0 +1,119 @@
package mysqlSync
import (
"database/sql"
"fmt"
"sync"
"Framework/dataSyncMgr/mysqlSync/logSqlSync"
"Framework/dataSyncMgr/mysqlSync/sqlSync"
"goutil/debugUtil"
"goutil/logUtil"
)
// 数据同步管理
type SyncMgr struct {
// 服务器组Id
serverGroupId int32
// 同步数据的存储路径
dirPath string
// 大文件对象size
maxFileSize int
// 数据库对象
dbObj *sql.DB
// 同步对象集合
syncObjMap map[string]*sqlSync.SyncObject
// 同步对象锁
mutex sync.RWMutex
// 新建实例对象的函数
newInstanceFunc func(mgr *SyncMgr, identifier string) *sqlSync.SyncObject
}
// 注册同步对象
// identifier:当前数据的唯一标识(可以使用数据库表名)
func (this *SyncMgr) RegisterSyncObj(identifier string) {
this.mutex.Lock()
defer this.mutex.Unlock()
// 判断是否设置了相同的唯一标识,以免弄混淆
if _, exists := this.syncObjMap[identifier]; exists {
prefix := fmt.Sprintf("%s-%s", identifier, "SyncMgr.RegisterSyncObj")
err := fmt.Errorf("%s has already existed, please change another identifier", prefix)
logUtil.ErrorLog(err.Error())
panic(err)
}
syncObj := this.newInstanceFunc(this, identifier)
syncObj.Init(this.maxFileSize)
this.syncObjMap[identifier] = syncObj
if debugUtil.IsDebug() {
fmt.Printf("%s同步对象成功注册进SyncMgr, 当前有%d个同步对象\n", identifier, len(this.syncObjMap))
}
}
// 保存数据
// identifier:当前数据的唯一标识(可以使用数据库表名)
// command:sql命令
func (this *SyncMgr) Save(identifier string, command string) {
this.mutex.RLock()
defer this.mutex.RUnlock()
syncObj, exists := this.syncObjMap[identifier]
if !exists {
err := fmt.Errorf("syncObj:%s does not existed, please register first", identifier)
logUtil.ErrorLog(err.Error())
panic(err)
}
syncObj.Save(command)
}
// 构造同步管理对象
// serverGroupId:服务器组Id
// dirPath: 文件目录
// maxFileSize: 大文件对象大小
// survivalTime: 同步数据存活时间 (单位hour)
// dbObj: 数据库对象
func NewSyncMgr(serverGroupId int32, dirPath string, maxFileSize int, survivalTime int, dbObj *sql.DB) *SyncMgr {
result := &SyncMgr{
serverGroupId: serverGroupId,
dirPath: dirPath,
maxFileSize: maxFileSize,
dbObj: dbObj,
syncObjMap: make(map[string]*sqlSync.SyncObject),
newInstanceFunc: func(mgr *SyncMgr, identifier string) *sqlSync.SyncObject {
handler := newSyncObject(mgr.dirPath, identifier, mgr.dbObj)
return sqlSync.NewSyncObject(mgr.dirPath, identifier, mgr.dbObj, handler)
},
}
return result
}
// 新建日志同步管理对象
// serverGroupId:服务器组Id
// dirPath: 文件目录
// maxFileSize: 大文件对象大小
// dbObj: 数据库对象
func NewLogSyncMgr(serverGroupId int32, dirPath string, maxFileSize int, dbObj *sql.DB) *SyncMgr {
result := &SyncMgr{
serverGroupId: serverGroupId,
dirPath: dirPath,
maxFileSize: maxFileSize,
dbObj: dbObj,
syncObjMap: make(map[string]*sqlSync.SyncObject),
newInstanceFunc: func(mgr *SyncMgr, identifier string) *sqlSync.SyncObject {
handler := logSqlSync.NewSyncObject(mgr.serverGroupId, mgr.dirPath, identifier, mgr.dbObj)
return sqlSync.NewSyncObject(mgr.dirPath, identifier, mgr.dbObj, handler)
},
}
return result
}

View File

@@ -0,0 +1,210 @@
package mysqlSync
import (
"database/sql"
"fmt"
"os"
"path/filepath"
"time"
"Framework/dataSyncMgr/mysqlSync/sqlSync"
"goutil/debugUtil"
"goutil/logUtil"
)
// 同步对象定义
type SyncObject struct {
// 同步数据的存储路径
dirPath string
// 同步数据对象的唯一标识,用于进行重复判断
identifier string
// 数据库对象
dbObj *sql.DB
// 同步信息对象
syncingInfoObj *syncingInfo
// 错误处理对象
errorHandleObj *errorFile
// 同步对象
syncObj *sqlSync.SyncObject
}
// 进行同步对象初始化
// maxFileSize:每个大文件的最大写入值单位Byte
func (this *SyncObject) Init(baseObj *sqlSync.SyncObject) {
this.syncObj = baseObj
// 创建同步信息记录对象
syncingInfoObj, err := newSyncingInfoObject(this.identifier, this.dbObj)
if err != nil {
panic(err)
}
this.syncingInfoObj = syncingInfoObj
// 启动时同步所有数据(然后才能从数据库中查询数据,以免数据丢失)
this.syncOldData()
}
// 同步完成之前未同步完的数据
func (this *SyncObject) syncOldData() {
// 获取文件列表(有序的列表)
fileList := sqlSync.GetDataFileList(this.dirPath)
filePath, _ := this.syncingInfoObj.GetSyncingInfo()
// 判断是否有文件
if len(fileList) == 0 {
return
}
// 判断当前文件是否为空,如果为空则将第一个文件赋给它
if filePath == "" {
this.syncingInfoObj.Update(fileList[0], 0, nil)
}
// 开始同步数据
this.syncObj.Sync()
return
}
// 获取正在同步的信息
// filePath:文件路径
// offset:文件偏移量
func (this *SyncObject) GetSyncingInfo() (filePath string, offset int64) {
return this.syncingInfoObj.GetSyncingInfo()
}
// 更新
// filePath:文件路径
// offset:文件偏移量
// tran:事务对象
// 返回值:
// error:错误对象
func (this *SyncObject) Update(filePath string, offset int64, tran *sql.Tx) error {
return this.syncingInfoObj.Update(filePath, offset, tran)
}
// 同步一条sql语句
// command:待执行的命令
// filePath:保存路径
// offset:文件偏移量
// 返回值:
// error:错误信息
func (this *SyncObject) SyncOneSql(command string, filePath string, offset int64) {
err := this.syncOneSqlDetail(command, filePath, offset)
if err == nil {
return
}
// 发送监控报警
this.handleError(command, filePath, offset, err)
return
}
// 同步一条sql语句的具体逻辑
// command:待执行的命令
// filePath:保存路径
// offset:文件偏移量
// 返回值:
// error:错误信息
func (this *SyncObject) syncOneSqlDetail(command string, filePath string, offset int64) error {
return sqlSync.ExecuteByTran(this.dbObj, func(tx *sql.Tx) (isCommit bool, err error) {
// 保存sql到数据库
err = this.syncToMysql(command, tx)
if err != nil {
return false, err
}
// 保存进度信息到数据库
err = this.syncingInfoObj.Update(filePath, offset, tx)
if err != nil {
return false, err
}
return true, nil
})
}
// 同步数据到mysql中
// command:待执行的命令
// 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
}
// 进行错误处理
// command:存在异常的数据
// filePath:文件路径
// offset:文件偏移量
// err:错误信息
func (this *SyncObject) handleError(command string, filePath string, offset int64, err error) {
defer this.errorHandleObj.Delete()
// 保存当前sql命令
this.errorHandleObj.SaveCommand(command)
// 循环处理当前命令,直到没有错误
beginTime := time.Now().Unix()
for {
// 每隔5分钟发送警报
if time.Now().Unix()-beginTime > 5*60 {
beginTime = time.Now().Unix()
}
// 每次循环休眠20秒
time.Sleep(5 * time.Second)
command = this.errorHandleObj.ReadCommand()
err = this.syncOneSqlDetail(command, filePath, offset)
if err != nil {
continue
}
break
}
}
// 创新新的mysql同步对象
// dirPath:存放数据的目录
// identifier:当前数据的唯一标识(可以使用数据库表名)
// dbObj:数据库对象
// syncingInfoObj:同步信息记录对象
// errorHandleObj:错误处理对象
// 返回值:
// mysql同步对象
func newSyncObject(dirPath, identifier string, dbObj *sql.DB) *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,
errorHandleObj: newErrorFile(dirPath, identifier),
}
return result
}

View File

@@ -0,0 +1,62 @@
package main
import (
"fmt"
"github.com/go-sql-driver/mysql"
"github.com/jinzhu/gorm"
"Framework/dataSyncMgr/mysqlSync"
"goutil/logUtil"
)
var _ = mysql.DeregisterLocalFile
var (
connectionString = "root:moqikaka3309@tcp(10.1.0.10:3309)/develop_liujun?charset=utf8&parseTime=true&loc=Local&timeout=60s"
maxOpenConns = 10
maxIdleConns = 10
syncFileSize = 1024 * 1024
)
var (
// 数据库对象
dbObj *gorm.DB
// 同步管理对象
syncMgr *mysqlSync.SyncMgr
)
func init() {
// 初始化数据库连接
dbObj = initMysql()
// 构造同步管理对象
syncMgr = mysqlSync.NewSyncMgr(1, "Sync", syncFileSize, 1, dbObj.DB())
}
// 初始化Mysql
func initMysql() *gorm.DB {
dbObj, err := gorm.Open("mysql", connectionString)
if err != nil {
panic(fmt.Errorf("初始化数据库:%s失败错误信息为%s", connectionString, err))
}
logUtil.DebugLog(fmt.Sprintf("连接mysql:%s成功", connectionString))
if maxOpenConns > 0 && maxIdleConns > 0 {
dbObj.DB().SetMaxOpenConns(maxOpenConns)
dbObj.DB().SetMaxIdleConns(maxIdleConns)
}
return dbObj
}
// 注册同步对象
func registerSyncObj(identifier string) {
syncMgr.RegisterSyncObj(identifier)
}
// 保存sql数据
func save(identifier string, command string) {
syncMgr.Save(identifier, command)
}

View File

@@ -0,0 +1,82 @@
package main
import (
"fmt"
"sync"
"time"
"goutil/mathUtil"
"goutil/stringUtil"
)
var (
wg sync.WaitGroup
)
func init() {
wg.Add(1)
}
func main() {
playerMgr := newPlayerMgr()
// insert
go func() {
for {
id := stringUtil.GetNewGUID()
name := fmt.Sprintf("Hero_%s", id)
obj := newPlayer(id, name)
playerMgr.insert(obj)
insert(obj)
time.Sleep(10 * time.Millisecond)
}
}()
// update
go func() {
for {
obj := playerMgr.randomSelect()
if obj == nil {
continue
}
suffix := mathUtil.GetRand().GetRandInt(1000)
newName := fmt.Sprintf("Hero_%d", suffix)
obj.resetName(newName)
update(obj)
time.Sleep(10 * time.Millisecond)
}
}()
// delete
go func() {
for {
obj := playerMgr.randomSelect()
if obj == nil {
continue
}
playerMgr.delete(obj)
clear(obj)
time.Sleep(10 * time.Millisecond)
}
}()
// errorFile
go func() {
for {
time.Sleep(1 * time.Hour)
id := stringUtil.GetNewGUID()
name := fmt.Sprintf("Hero_%s%s", id, id)
obj := newPlayer(id, name)
playerMgr.insert(obj)
print("errorFile")
insert(obj)
}
}()
wg.Wait()
}

View File

@@ -0,0 +1,64 @@
package main
import (
"sync"
)
type player struct {
// 玩家id
Id string `gorm:"column:Id;primary_key"`
// 玩家名称
Name string `gorm:"column:Name"`
}
func (this *player) resetName(name string) {
this.Name = name
}
func (this *player) tableName() string {
return "player"
}
func newPlayer(id, name string) *player {
return &player{
Id: id,
Name: name,
}
}
type playerMgr struct {
playerMap map[string]*player
mutex sync.Mutex
}
func (this *playerMgr) insert(obj *player) {
this.mutex.Lock()
defer this.mutex.Unlock()
this.playerMap[obj.Id] = obj
}
func (this *playerMgr) delete(obj *player) {
this.mutex.Lock()
defer this.mutex.Unlock()
delete(this.playerMap, obj.Id)
}
func (this *playerMgr) randomSelect() *player {
this.mutex.Lock()
defer this.mutex.Unlock()
for _, obj := range this.playerMap {
return obj
}
return nil
}
func newPlayerMgr() *playerMgr {
return &playerMgr{
playerMap: make(map[string]*player),
}
}

View File

@@ -0,0 +1,29 @@
//package test
package main
import (
"fmt"
)
var (
con_player_tableName = "player"
)
func init() {
registerSyncObj(con_player_tableName)
}
func insert(obj *player) {
command := fmt.Sprintf("INSERT INTO `%s` (`Id`,`Name`) VALUES ('%v','%v') ", con_player_tableName, obj.Id, obj.Name)
save(con_player_tableName, command)
}
func update(obj *player) {
command := fmt.Sprintf("UPDATE `%s` SET `Name` = '%v' WHERE `Id` = '%v';", con_player_tableName, obj.Name, obj.Id)
save(con_player_tableName, command)
}
func clear(obj *player) {
command := fmt.Sprintf("DELETE FROM %s where Id = '%v';", con_player_tableName, obj.Id)
save(con_player_tableName, command)
}

View File

@@ -0,0 +1,222 @@
package mysqlSync
import (
"database/sql"
"fmt"
"time"
"goutil/logUtil"
)
var (
// 是否已经初始化了正在同步的表信息
ifSyncingInfoTableInited = false
// 表初始化错误信息
initTableError error = nil
// 表是否已经初始化
isTableInited bool = false
)
// 同步信息项,保存已经处理过的文件的信息
type syncingModel struct {
// 唯一标识
Identifier string
// 待处理文件的绝对路径
FilePath string
// 待处理文件的偏移量
FileOffset int64
// 更新时间
UpdateTime time.Time
}
// 同步信息对象
type syncingInfo struct {
// 同步数据对象的唯一标识,用于进行重复判断
identifier string
// 同步信息项
item *syncingModel
// 数据库连接对象
db *sql.DB
}
// 获取同步信息
// filePath:正在同步的文件
// fileOffset:同步到的位置
func (this *syncingInfo) GetSyncingInfo() (filePath string, fileOffset int64) {
return this.item.FilePath, this.item.FileOffset
}
// 更新正在同步的位置和文件信息
// filePath:文件路径
// offset:当前同步到的位置
// tran:事务对象可以为nil
// 返回值:
// error:处理的错误信息
func (this *syncingInfo) Update(filePath string, offset int64, tran *sql.Tx) error {
this.item.FilePath = filePath
this.item.FileOffset = offset
this.item.UpdateTime = time.Now()
// 更新到数据库
return this.update(this.item, tran)
}
// 初始化同步信息
// 返回值:
// error:错误信息
func (this *syncingInfo) init() error {
if ifSyncingInfoTableInited == false {
err := initSyncingInfoTable(this.db)
if err != nil {
return err
}
ifSyncingInfoTableInited = true
}
// 获取此表的同步信息
data, exist, err := this.get()
if err != nil {
return err
}
// 2. 如果同步信息不存在,则初始化一条到此表
if exist == false {
data = &syncingModel{
Identifier: this.identifier,
FilePath: "",
FileOffset: 0,
UpdateTime: time.Now(),
}
err = this.insert(data)
if err != nil {
return err
}
}
this.item = data
return nil
}
// 从数据库获取数据
// 返回值:
// data:获取到的数据
// exist:是否存在此数据
// err:错误信息
func (this *syncingInfo) get() (data *syncingModel, exist bool, err error) {
//// 从数据库查询
querySql := fmt.Sprintf("SELECT FilePath,FileOffset,UpdateTime FROM syncing_info WHERE Identifier ='%v';", this.identifier)
var rows *sql.Rows
rows, err = this.db.Query(querySql)
if err != nil {
logUtil.ErrorLog(fmt.Sprintf("mysqlSync/syncingInfo get.query error:%v", err.Error()))
return
}
defer rows.Close()
if rows.Next() == false {
exist = false
return
}
exist = true
// 读取数据
data = &syncingModel{
Identifier: this.identifier,
}
err = rows.Scan(&data.FilePath, &data.FileOffset, &data.UpdateTime)
if err != nil {
logUtil.ErrorLog(fmt.Sprintf("mysqlSync/syncingInfo get.scan error:%v", err.Error()))
return
}
return
}
// 把同步信息写入到数据库
// data:待插入的数据
// 返回值:
// error:错误信息
func (this *syncingInfo) insert(data *syncingModel) error {
insertSql := "INSERT INTO syncing_info(Identifier,FilePath,FileOffset,UpdateTime) VALUES(?,?,?,?);"
_, err := this.db.Exec(insertSql, data.Identifier, data.FilePath, data.FileOffset, data.UpdateTime)
if err != nil {
logUtil.ErrorLog(fmt.Sprintf("mysqlSync/syncingInfo insert error:%v", err.Error()))
}
return err
}
// 把同步信息更新到数据库
// data:待更新的数据
// tran:事务对象
// 返回值:
// error:错误信息
func (this *syncingInfo) update(data *syncingModel, tran *sql.Tx) error {
updateSql := "UPDATE syncing_info SET FilePath=?, FileOffset=?, UpdateTime=? WHERE Identifier=?;"
var err error
if tran != nil {
_, err = tran.Exec(updateSql, data.FilePath, data.FileOffset, data.UpdateTime, data.Identifier)
} else {
_, err = this.db.Exec(updateSql, data.FilePath, data.FileOffset, data.UpdateTime, data.Identifier)
}
if err != nil {
logUtil.ErrorLog(fmt.Sprintf("mysqlSync/syncingInfo update error:%v", err.Error()))
}
return err
}
// 创建同步信息对象
// _dirPath:目录的路径
// _identifier:当前数据的唯一标识(可以使用数据库表名)
// _db:数据库连接对象
// 返回值:
// 同步信息对象
func newSyncingInfoObject(identifier string, _db *sql.DB) (result *syncingInfo, err error) {
result = &syncingInfo{
identifier: identifier,
db: _db,
}
err = result.init()
return result, err
}
// 初始化同步信息表结构
// db:数据库连接对象
func initSyncingInfoTable(db *sql.DB) error {
if isTableInited {
return initTableError
}
defer func() {
isTableInited = true
}()
// 创建同步信息表
createTableSql := `CREATE TABLE IF NOT EXISTS syncing_info (
Identifier varchar(30) NOT NULL COMMENT '同步唯一标识(数据库表名)',
FilePath varchar(500) NOT NULL COMMENT '正在同步的文件路径',
FileOffset bigint NOT NULL COMMENT '文件偏移量',
UpdateTime datetime NOT NULL COMMENT '最后一次更新时间',
PRIMARY KEY (Identifier)
) COMMENT 'P表同步信息';`
if _, initTableError = db.Exec(createTableSql); initTableError != nil {
logUtil.ErrorLog(fmt.Sprintf("mysqlSync/syncingInfo initSyncingInfoTable error:%v", initTableError.Error()))
return initTableError
}
return nil
}