diff --git a/trunk/.idea/trunk.iml b/trunk/.idea/trunk.iml index 7ee078d..5e764c4 100644 --- a/trunk/.idea/trunk.iml +++ b/trunk/.idea/trunk.iml @@ -1,4 +1,9 @@ - + + + + + + \ No newline at end of file diff --git a/trunk/.idea/workspace.xml b/trunk/.idea/workspace.xml index e8bf75e..2bdd48d 100644 --- a/trunk/.idea/workspace.xml +++ b/trunk/.idea/workspace.xml @@ -5,67 +5,35 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - + + + + + + + - - + { "associatedIndex": 3 @@ -99,50 +66,66 @@ - { + "keyToString": { + "DefaultGoTemplateProperty": "Go File", + "Docker.admincenter.redis: Compose 部署.executor": "Run", + "Docker.admincenter/Dockerfile.executor": "Run", + "Docker.center/admincenter/Dockerfile builder.executor": "Run", + "Go 构建.go build admincenter.executor": "Run", + "Go 构建.go build dbcenter.executor": "Run", + "Go 构建.go build logincenter.executor": "Debug", + "Go 构建.go build paycenter.executor": "Run", + "Go 构建.go build usercenter.executor": "Run", + "Go 测试.common/connection 中的 TestGetDBName.executor": "Run", + "Go 测试.dbcenter 中的 Test000.executor": "Debug", + "Go 测试.dbcenter 中的 Test001 (1).executor": "Debug", + "Go 测试.dbcenter 中的 Test001.executor": "Run", + "Go 测试.dbcenter 中的 Test002 (1).executor": "Run", + "Go 测试.dbcenter 中的 Test002.executor": "Run", + "RunOnceActivity.ShowReadmeOnStart": "true", + "RunOnceActivity.git.unshallow": "true", + "RunOnceActivity.go.formatter.settings.were.checked": "true", + "RunOnceActivity.go.migrated.go.modules.settings": "true", + "RunOnceActivity.go.modules.go.list.on.any.changes.was.set": "true", + "git-widget-placeholder": "master", + "go.import.settings.migrated": "true", + "go.sdk.automatically.set": "true", + "ignore.virus.scanning.warn.message": "true", + "last_opened_file_path": "D:/workspace/e2023/goProject/trunk", + "node.js.detected.package.eslint": "true", + "node.js.selected.package.eslint": "(autodetect)", + "nodejs_package_manager_path": "npm", + "run.configurations.included.in.services": "true", + "settings.editor.selected.configurable": "preferences.pluginManager" }, - "keyToStringList": { - "DatabaseDriversLRU": [ - "mysql_aurora" + "keyToStringList": { + "DatabaseDriversLRU": [ + "mysql_aurora" ] } -}]]> +} + - + + + + - + + + + @@ -161,6 +144,15 @@ + + + + + + + + + @@ -170,29 +162,6 @@ - - - - - - - - - - - - - - - - - - - - - - - @@ -205,21 +174,29 @@ + + + + + + + - + - - + + - diff --git a/trunk/center/admincenter/internal/admin/admin.go b/trunk/center/admincenter/internal/admin/admin.go index 4d89e30..bede9ec 100644 --- a/trunk/center/admincenter/internal/admin/admin.go +++ b/trunk/center/admincenter/internal/admin/admin.go @@ -2,14 +2,13 @@ package admin import ( "common/connection" - "time" ) func init() { //注册数据库 connection.RegisterDBModel(&Admin{}) - connection.RegisterDBModel(&RecordLoginOfWxUser{}) - connection.RegisterDBModel(&RecordWatchADOfWxUser{}) + connection.RegisterDBModel(&RecordAveragePlayTimes{}) + connection.RegisterDBModel(&RecordAverageWatchADNum{}) } type Admin struct { @@ -36,28 +35,71 @@ type Admin struct { type RecordLoginOfWxUser struct { ID int64 `gorm:"column:id;primary_key;comment:自增索引;autoIncrementIncrement" json:"id"` Uid int64 `gorm:"column:uid;comment:用户唯一Id" json:"uid"` + SeverId int32 `gorm:"column:severId;comment:区服Id" json:"severId"` + RecordDate int64 `gorm:"column:recorddate;comment:记录日期" json:"recorddate"` //只记录当天0点的时间戳,方便查询某一日的数据 LoginInTime int64 `gorm:"column:loginintime;comment:登录时间" json:"loginintime"` LoginOutTime int64 `gorm:"column:loginouttime;comment:登出时间" json:"loginouttime"` PlayTimes int64 `gorm:"column:playtimes;comment:游玩时长" json:"playtimes"` + //用于统计当日的总上线人数 0=否,1=是 + IsFirstLogin int32 `gorm:"column:isfirstlogin;comment:是否首次登录" json:"isfirstlogin"` } // 看广告相关记录 // 记录日期便于按天统计 type RecordWatchADOfWxUser struct { - ID int64 `gorm:"column:id;primary_key;comment:自增索引;autoIncrementIncrement" json:"id"` - RecordDate time.Time `gorm:"column:recorddate;type:date;comment:记录日期" json:"recorddate"` - Uid int64 `gorm:"column:uid;comment:用户唯一Id" json:"uid"` - WatchADNum int32 `gorm:"column:watchadnum;comment:看广告次数" json:"watchadnum"` + ID int64 `gorm:"column:id;primary_key;comment:自增索引;autoIncrementIncrement" json:"id"` + RecordDate int64 `gorm:"column:recorddate;comment:记录日期" json:"recorddate"` //只记录当天0点的时间戳,方便查询某一日的数据 + Uid int64 `gorm:"column:uid;comment:用户唯一Id" json:"uid"` + SeverId int32 `gorm:"column:severId;comment:区服Id" json:"severId"` + WatchADNum int32 `gorm:"column:watchadnum;comment:看广告次数" json:"watchadnum"` +} + +// 历史平均看广告次数的记录 +// 再次查询的时候就不用计算了 +type RecordAverageWatchADNum struct { + ID int64 `gorm:"column:id;primary_key;comment:自增索引;autoIncrementIncrement" json:"id"` + RecordDate int64 `gorm:"column:recorddate;comment:记录日期" json:"recorddate"` //只记录当天0点的时间戳,方便查询某一日的数据 + SeverId int32 `gorm:"column:severid;comment:区服ID" json:"severid"` + AverageWatchADNum float32 `gorm:"column:averageplaytimes;comment:平均看广告次数" json:"averageplaytimes"` + PlayerNum int64 `gorm:"column:playernum;comment:当日看广告人数人数" json:"playernum"` +} + +// 历史平均在线时长的记录 +// 再次查询的时候就不用计算了 +type RecordAveragePlayTimes struct { + ID int64 `gorm:"column:id;primary_key;comment:自增索引;autoIncrementIncrement" json:"id"` + RecordDate int64 `gorm:"column:recorddate;comment:记录日期" json:"recorddate"` //只记录当天0点的时间戳,方便查询某一日的数据 + SeverId int32 `gorm:"column:severid;comment:区服ID" json:"severid"` + AveragePlayTimes int64 `gorm:"column:averageplaytimes;comment:平均在线时长" json:"averageplaytimes"` + PlayerNum int64 `gorm:"column:playernum;comment:当日上线人数" json:"playernum"` +} + +// 记录当前为止的开服数 +type WxUserSeverList struct { + ID int64 `gorm:"column:id;primary_key;comment:自增索引;autoIncrementIncrement" json:"id"` + SeverId int32 `gorm:"column:severId;comment:区服Id" json:"severId"` } func (Admin) TableName() string { return "admin" } -func (RecordLoginOfWxUser) TableName() string { - return "recordloginofwxuser" +func (RecordAveragePlayTimes) TableName() string { + return "recordaverageplaytimes" +} + +func (RecordAverageWatchADNum) TableName() string { + return "recordaveragewatchadnum" +} + +func (WxUserSeverList) TableName() string { + return "wxuserseverlist" } func (RecordWatchADOfWxUser) TableName() string { return "recordwatchadofwxuser" } + +func (RecordLoginOfWxUser) TableName() string { + return "recordloginofwxuser" +} diff --git a/trunk/center/admincenter/internal/admin/api.go b/trunk/center/admincenter/internal/admin/api.go index c1d5f02..87ace60 100644 --- a/trunk/center/admincenter/internal/admin/api.go +++ b/trunk/center/admincenter/internal/admin/api.go @@ -203,55 +203,213 @@ func (a *AdminApi) Login(account string, password string) (responseObj *webServe return } -// 查询玩家登录相关记录 +// 获取某一天玩家的平均在线时长 func init() { moduleName := "AdminApi" - methodName := "QueryloginRecord" + methodName := "GetAveragePlaytimes" skipVerifyTokenPage := true - methodDesc := "查询玩家登录相关记录" + methodDesc := "获取某一天玩家的平均在线时长" methodAuthor := "youjinlan" methodMendor := "" methodDate := "2025-01-21 16:00:00" - methodInParam := []string{"int64:uid"} + methodInParam := []string{"int64 startTime, int64 endTime"} methodOutParam := ` { "Code '类型:int'": "响应结果的状态值", "Message '类型:string'": "响应结果的状态值所对应的描述信息", "Data '类型:interface{}'": "响应结果的数据" { - "FirstLoginTime '类型:int64'": "首次登录时间", - "PlayDayNum '类型:int32'": "生命周期", - "PlayTimes '类型:int64'": "在线时长", + "AveragePlaytimesForEachSever '类型:map[int32]map[int64]int64'": "各个服在该时间段内的各天平均在线时长", } }` - remark.RegisterMethodRemark(moduleName, methodName, methodDesc, methodAuthor, methodMendor, methodDate, methodInParam, methodOutParam, skipVerifyTokenPage) } -func (a *AdminApi) QueryloginRecord(uid int64) (responseObj *webServer.ResponseObject) { +func (a *AdminApi) GetAveragePlaytimes(startTime, endTime int64) (responseObj *webServer.ResponseObject) { responseObj = webServer.GetInitResponseObj() //验证参数 + nowTime := time.Now().Unix() + nowZero := mytime.ZeroTime(nowTime, 0) + if startTime == 0 || endTime == 0 { + responseObj.SetResultStatus(resultStatus.APIDataError) + return + } + if startTime > nowTime { + responseObj.SetResultStatus(resultStatus.APIDataError) + return + } + startZero := mytime.ZeroTime(startTime, 0) + endZero := mytime.ZeroTime(endTime, 0) + if endZero > nowZero { + endZero = nowZero + } + GetAveragePlaytimes() + totalMap := make(map[int32]map[int64]int64) + userSeverList, _ := GetUserSeverList() + date := startZero + for _, severlist := range userSeverList { + dateMap := make(map[int64]int64) + for { + var averagePlaytimes int64 + if date != mytime.ZeroTime(nowTime, 0) { + averagePlaytimes, _, _ = GetRecordAveragePlayTimes(date, severlist.SeverId) + if averagePlaytimes == 0 { + playtimes := GetUserTotalPlayTimes(date, severlist.SeverId) + count := GetUserTotalPlayerNum(date, severlist.SeverId) + if playtimes == 0 || count == 0 { + averagePlaytimes = 0 + } else { + averagePlaytimes = playtimes / count + } + //把今日之前的数据添加到数据库,因为数据不会再变化了,下次查询就不用计算了 + recordAveragePlayTimes := &RecordAveragePlayTimes{} + recordAveragePlayTimes.AveragePlayTimes = averagePlaytimes + recordAveragePlayTimes.PlayerNum = count + recordAveragePlayTimes.SeverId = severlist.SeverId + recordAveragePlayTimes.RecordDate = date + AddRecordAveragePlayTimes(recordAveragePlayTimes) + } + } else { + playtimes := GetUserTotalPlayTimes(date, severlist.SeverId) + count := GetUserTotalPlayerNum(date, severlist.SeverId) + if playtimes == 0 || count == 0 { + averagePlaytimes = 0 + } else { + averagePlaytimes = playtimes / count + } + } + dateMap[date] = averagePlaytimes + if date+86400 > endZero { + date = startZero + break + } + date = date + 86400 + } + totalMap[severlist.SeverId] = dateMap + } + resultMap := make(map[string]any) + resultMap["AveragePlaytimesForEachSever"] = totalMap + responseObj.SetData(resultMap) + return +} + +// 获取某一天玩家的平均看广告次数 +func init() { + moduleName := "AdminApi" + methodName := "GetAverageWatchADNum" + skipVerifyTokenPage := true + methodDesc := "获取某一天玩家的平均看广告次数" + methodAuthor := "youjinlan" + methodMendor := "" + methodDate := "2025-01-24 15:00:00" + methodInParam := []string{"int64 startTime, int64 endTime"} + methodOutParam := ` + { + "Code '类型:int'": "响应结果的状态值", + "Message '类型:string'": "响应结果的状态值所对应的描述信息", + "Data '类型:interface{}'": "响应结果的数据" + { + "AverageWatchADNumForEachSever '类型:map[int32]map[int64]int32'": "各个服在该时间段内的各天平均看广告次数", + } + }` + remark.RegisterMethodRemark(moduleName, methodName, methodDesc, methodAuthor, methodMendor, methodDate, methodInParam, methodOutParam, skipVerifyTokenPage) +} + +func (a *AdminApi) GetAverageWatchADNum(startTime, endTime int64) (responseObj *webServer.ResponseObject) { + responseObj = webServer.GetInitResponseObj() + //验证参数 + nowTime := time.Now().Unix() + nowZero := mytime.ZeroTime(nowTime, 0) + if startTime == 0 || endTime == 0 { + responseObj.SetResultStatus(resultStatus.APIDataError) + return + } + if startTime > nowTime { + responseObj.SetResultStatus(resultStatus.APIDataError) + return + } + startZero := mytime.ZeroTime(startTime, 0) + endZero := mytime.ZeroTime(endTime, 0) + if endZero > nowZero { + endZero = nowZero + } + GetAveragePlaytimes() + totalMap := make(map[int32]map[int64]float32) + userSeverList, _ := GetUserSeverList() + date := startZero + for _, severlist := range userSeverList { + dateMap := make(map[int64]float32) + for { + if date != mytime.ZeroTime(nowTime, 0) { + avgWatchADNum, _, _ := GetRecordAverageWatchADNum(date, severlist.SeverId) + if avgWatchADNum == 0 { + avgWatchADNum = GetUserAvgWatchADNum(date, severlist.SeverId) + count := GetUserWatchADPlayerNum(date, severlist.SeverId) + //把今日之前的数据添加到数据库,因为数据不会再变化了,下次查询就不用计算了 + recordAverageWatchADNum := &RecordAverageWatchADNum{} + recordAverageWatchADNum.AverageWatchADNum = avgWatchADNum + recordAverageWatchADNum.PlayerNum = count + recordAverageWatchADNum.SeverId = severlist.SeverId + recordAverageWatchADNum.RecordDate = date + AddRecordAverageWatchADNum(recordAverageWatchADNum) + } + } else { + avgWatchADNum := GetUserAvgWatchADNum(date, severlist.SeverId) + dateMap[date] = avgWatchADNum + if date+86400 > endZero { + date = startZero + break + } + date = date + 86400 + } + } + totalMap[severlist.SeverId] = dateMap + } + resultMap := make(map[string]any) + resultMap["AverageWatchADNumForEachSever"] = totalMap + responseObj.SetData(resultMap) + return +} + +// 获取某个玩家的生命周期 +func init() { + moduleName := "AdminApi" + methodName := "GetPlayerLiveNum" + skipVerifyTokenPage := true + methodDesc := "获取某个玩家的生命周期" + methodAuthor := "youjinlan" + methodMendor := "" + methodDate := "2025-01-24 17:00:00" + methodInParam := []string{"int64 uid"} + methodOutParam := ` + { + "Code '类型:int'": "响应结果的状态值", + "Message '类型:string'": "响应结果的状态值所对应的描述信息", + "Data '类型:interface{}'": "响应结果的数据" + { + "LiveNum '类型:int32'": "玩家的生命周期(玩了多少天)", + } + }` + remark.RegisterMethodRemark(moduleName, methodName, methodDesc, methodAuthor, methodMendor, methodDate, methodInParam, methodOutParam, skipVerifyTokenPage) +} + +func (a *AdminApi) GetPlayerLiveNum(uid int64) (responseObj *webServer.ResponseObject) { + responseObj = webServer.GetInitResponseObj() if uid == 0 { responseObj.SetResultStatus(resultStatus.APIDataError) return } - var userfirstRecord *RecordLoginOfWxUser - if userfirstRecord, _ = GetUserFirstRecord(uid); userfirstRecord == nil { + var userRecord *RecordLoginOfWxUser + if userRecord, _ = GetUserFirstRecord(uid); userRecord == nil { responseObj.SetResultStatus(resultStatus.PlayerNotExist) return } - userLastRecord, _ := GetUserLastRecord(uid) - firstLoginInTime := userfirstRecord.LoginInTime - var lastLoginOutTime int64 - if userLastRecord.LoginOutTime == 0 { - lastLoginOutTime = userLastRecord.LoginInTime - } - lastLoginOutTime = userLastRecord.LoginOutTime - playDayNum := mytime.DiffDays(lastLoginOutTime, firstLoginInTime) + firstDate := userRecord.RecordDate + userRecord, _ = GetUserLastRecord(uid) + lastDate := userRecord.RecordDate resultMap := make(map[string]any) - resultMap["FirstLoginTime"] = firstLoginInTime - resultMap["PlayDayNum"] = playDayNum - resultMap["PlayTimes"] = GetUserTotalPlayTime(uid) + liveNum := mytime.DiffDays(lastDate, firstDate) + resultMap["LiveNum"] = liveNum responseObj.SetData(resultMap) return } diff --git a/trunk/center/admincenter/internal/admin/logic.go b/trunk/center/admincenter/internal/admin/logic.go index fcee122..0587301 100644 --- a/trunk/center/admincenter/internal/admin/logic.go +++ b/trunk/center/admincenter/internal/admin/logic.go @@ -62,8 +62,82 @@ func GetUserLastRecord(uid int64) (*RecordLoginOfWxUser, error) { return userRecord, nil } -func GetUserTotalPlayTime(uid int64) int64 { +// 计算某日某服的玩家在线总时长 +func GetUserTotalPlayTimes(date int64, severId int32) int64 { var totalPlayTime int64 - connection.GetUserDB().Table("recordloginofwxuser").Where("uid = ?", uid).Select("SUM(playtimes)").Scan(&totalPlayTime) + connection.GetUserDB().Table("recordloginofwxuser").Where("recorddate = ? AND severid = ?", date, severId).Select("SUM(playtimes)").Scan(&totalPlayTime) return totalPlayTime } + +// 计算某日某服的玩家上线总人数 +func GetUserTotalPlayerNum(date int64, severId int32) int64 { + var count int64 + connection.GetUserDB().Table("recordloginofwxuser").Where("recorddate = ? AND severid = ? AND isfirstlogin = ?", date, severId, 1).Count(&count) + return count +} + +// 获取区服列表 +func GetUserSeverList() ([]*WxUserSeverList, error) { + var userSeverList []*WxUserSeverList + result := connection.GetUserDB().Find(&userSeverList) + if result.Error != nil { + logUtilPlus.ErrorLog("查询用户区服列表失败 错误信息:%s", result.Error.Error()) + return nil, result.Error + } + return userSeverList, nil +} + +// 添加玩家的平均在线时长数据到数据库 +func AddRecordAveragePlayTimes(recordAveragePlayTimes *RecordAveragePlayTimes) (int64, error) { + connection.CheckTableExists(connection.GetAdminDB(), &RecordAveragePlayTimes{}) + result := connection.GetAdminDB().Create(&recordAveragePlayTimes) // 通过数据的指针来创建 + if result.Error != nil { + logUtilPlus.ErrorLog("添加平均在线时长记录失败 错误信息:%s", result.Error) + } + return recordAveragePlayTimes.ID, nil +} + +// 获取某日某服的玩家平均在线时长记录和总上线人数 +func GetRecordAveragePlayTimes(date int64, severId int32) (int64, int64, error) { + var recordAveragePlayTimes *RecordAveragePlayTimes + result := connection.GetAdminDB().Where("recorddate = ? AND severid = ?", date, severId).First(&recordAveragePlayTimes) + if result.Error != nil { + logUtilPlus.ErrorLog("查询平均在线时长记录失败 错误信息:%s", result.Error) + return 0, 0, result.Error + } + return recordAveragePlayTimes.AveragePlayTimes, recordAveragePlayTimes.PlayerNum, nil +} + +// 计算某日某服的玩家平均看广告次数 +func GetUserAvgWatchADNum(date int64, severId int32) float32 { + var avgWatchADNum float32 + connection.GetUserDB().Table("recordwatchadofwxuser").Where("recorddate = ? AND severid = ?", date, severId).Select("AVG(watchadnum)").Scan(&avgWatchADNum) + return avgWatchADNum +} + +// 计算某日某服玩家的看广告总人数 +func GetUserWatchADPlayerNum(date int64, severId int32) int64 { + var count int64 + connection.GetUserDB().Table("recordwatchadofwxuser").Where("recorddate = ? AND severid = ?", date, severId).Count(&count) + return count +} + +// 添加玩家的平均看广告次数到数据库 +func AddRecordAverageWatchADNum(recordAverageWatchADNum *RecordAverageWatchADNum) (int64, error) { + result := connection.GetAdminDB().Create(&recordAverageWatchADNum) // 通过数据的指针来创建 + if result.Error != nil { + logUtilPlus.ErrorLog("添加平均看广告次数记录失败 错误信息:%s", result.Error) + } + return recordAverageWatchADNum.ID, nil +} + +// 获取某日某服玩家的平均看广告次数和总人数 +func GetRecordAverageWatchADNum(date int64, severId int32) (float32, int64, error) { + var recordAverageWatchADNum *RecordAverageWatchADNum + result := connection.GetAdminDB().Where("recorddate = ? AND severid = ?", date, severId).First(&recordAverageWatchADNum) + if result.Error != nil { + logUtilPlus.ErrorLog("查询平均看广告次数记录失败 错误信息:%s", result.Error) + return 0, 0, result.Error + } + return recordAverageWatchADNum.AverageWatchADNum, recordAverageWatchADNum.PlayerNum, nil +} diff --git a/trunk/center/admincenter/main.go b/trunk/center/admincenter/main.go index 5ce325c..951adb5 100644 --- a/trunk/center/admincenter/main.go +++ b/trunk/center/admincenter/main.go @@ -5,7 +5,6 @@ import ( "sync" _ "admincenter/internal/admin" - _ "common/resultStatus" "common/webServer" ) @@ -37,7 +36,6 @@ func loadConfig() { //设置数据类型 connection.SetModelDB(connection.GetAdminDB()) - connection.SetModelDB(connection.GetUserDB()) //构建数据库 connection.BuildDB() diff --git a/trunk/center/common/connection/dbHead.go b/trunk/center/common/connection/dbHead.go index a949da2..10fd709 100644 --- a/trunk/center/common/connection/dbHead.go +++ b/trunk/center/common/connection/dbHead.go @@ -17,6 +17,14 @@ var ( modelDB *gorm.DB ) +func init() { + + // 启动异步处理 + go ExecuteCreateChan() + go ExecuteSaveChan() + go ExecuteDeleteChan() +} + // RegisterDBModel 注册数据库模型到全局变量dbModelMap中。 // 这个函数接受一个interface{}类型的参数dbModel,表示数据库模型。 // 函数的目的是将传入的数据库模型添加到全局变量dbModelMap中, @@ -27,7 +35,7 @@ func RegisterDBModel(dbModel interface{}) { dbModelMap = append(dbModelMap, &dbModel) } -// 设置modelDB 类型 +// SetModelDB 设置modelDB 类型 func SetModelDB(db *gorm.DB) { modelDB = db } @@ -138,38 +146,120 @@ func Save(db *gorm.DB, value interface{}, dbIndex int32) *gorm.DB { return result } +// DBData 添加数据通道 +type DBData struct { + DB *gorm.DB + Value any +} + +var ( + + // 添加数据通道 + createChan = make(chan DBData) + + // 保存数据通道 + saveChan = make(chan DBData) + + // 删除数据通道 + deleteChan = make(chan DBData) +) + // AsyncCreate 异步创建数据 func AsyncCreate(db *gorm.DB, value interface{}) { - go func() { + createChan <- DBData{ + DB: db, + Value: value, + } +} - //检查表是否存在 - CheckTableExists(db, value) - result := db.Create(value) - if result.Error != nil { - logUtilPlus.ErrorLog("AsyncCreate is err: %v", result.Error) +// ExecuteCreateChan 执行添加数据通道 +func ExecuteCreateChan() { + defer func() { + if err := recover(); err != nil { + logUtilPlus.ErrorLog("AsyncCreate is err: %v", err) + + //停止程序 + panic(err) } }() + + for { + select { + case createData := <-createChan: + db := createData.DB + value := createData.Value + + //检查表是否存在 + CheckTableExists(db, value) + result := db.Create(value) + if result.Error != nil { + logUtilPlus.ErrorLog("AsyncCreate is err: %v", result.Error) + } + } + } } // AsyncSave 异步保存数据 func AsyncSave(db *gorm.DB, value interface{}) { - go func() { - result := db.Save(value) - if result.Error != nil { - logUtilPlus.ErrorLog("AsyncSave is err : %v", result.Error) + saveChan <- DBData{ + DB: db, + Value: value, + } +} + +// ExecuteSaveChan 执行保存数据通道 +func ExecuteSaveChan() { + + defer func() { + if err := recover(); err != nil { + logUtilPlus.ErrorLog("AsyncSave is err: %v", err) + panic(err) } }() + for { + select { + case saveData := <-saveChan: + db := saveData.DB + value := saveData.Value + + result := db.Save(value) + if result.Error != nil { + logUtilPlus.ErrorLog("AsyncSave is err : %v", result.Error) + } + } + } } // AsyncDelete 异步删除数据 func AsyncDelete(db *gorm.DB, value interface{}) { - go func() { - result := db.Delete(value) - if result.Error != nil { - logUtilPlus.ErrorLog("AsyncDelete is err : %v", result.Error) + deleteChan <- DBData{ + DB: db, + Value: value, + } +} + +// ExecuteDeleteChan 执行删除数据通道 +func ExecuteDeleteChan() { + + defer func() { + if err := recover(); err != nil { + logUtilPlus.ErrorLog("AsyncDelete is err: %v", err) + panic(err) } }() + for { + select { + case deleteData := <-deleteChan: + db := deleteData.DB + value := deleteData.Value + + result := db.Delete(value) + if result.Error != nil { + logUtilPlus.ErrorLog("AsyncDelete is err : %v", result.Error) + } + } + } } diff --git a/trunk/center/common/httpServer/apiHandler.go b/trunk/center/common/httpServer/apiHandler.go index 4bb1f4d..be46147 100644 --- a/trunk/center/common/httpServer/apiHandler.go +++ b/trunk/center/common/httpServer/apiHandler.go @@ -3,7 +3,7 @@ package httpServer import ( "net/http" - "common/resultStatus" + "common/resultstatus" "common/webServer" ) diff --git a/trunk/center/common/httpServer/reflect.go b/trunk/center/common/httpServer/reflect.go index e01a089..f0a6e8e 100644 --- a/trunk/center/common/httpServer/reflect.go +++ b/trunk/center/common/httpServer/reflect.go @@ -2,7 +2,7 @@ package httpServer import ( config "common/configsYaml" - "common/resultStatus" + "common/resultstatus" "common/webServer" "goutil/logUtilPlus" "reflect" diff --git a/trunk/center/common/httpServer/serverMux.go b/trunk/center/common/httpServer/serverMux.go index d188666..13cb013 100644 --- a/trunk/center/common/httpServer/serverMux.go +++ b/trunk/center/common/httpServer/serverMux.go @@ -2,7 +2,7 @@ package httpServer import ( config "common/configsYaml" - "common/resultStatus" + "common/resultstatus" "common/utils" "common/webServer" "encoding/json" diff --git a/trunk/center/common/mytime/timefuncs.go b/trunk/center/common/mytime/timefuncs.go index 2f56916..bd671dc 100644 --- a/trunk/center/common/mytime/timefuncs.go +++ b/trunk/center/common/mytime/timefuncs.go @@ -5,16 +5,7 @@ import ( "time" ) -/*func GetZeroTime(t int64, timezone int) int64 { - return t - (t+int64(timezone))%86400 -} - -func IsDiffDay(second, first int64, timezone int) int { - secondZeroTime := GetZeroTime(second, timezone) - firstZeroTime := GetZeroTime(first, timezone) - return int(secondZeroTime/86400 - firstZeroTime/86400) -}*/ - +// 计算两个时间戳之间间隔多少天 func DiffDays(new, old int64) int64 { newZeroTime := ZeroTime(new, 0) oldZeroTime := ZeroTime(old, 0) @@ -27,3 +18,9 @@ func ZeroTime(sec, nsec int64) int64 { t, _ := time.ParseInLocation("2006-01-02", dateStr, time.Local) return t.Unix() } + +func IsSameDay(first, second int64) bool { + firstZero := ZeroTime(first, 0) + secondZero := ZeroTime(second, 0) + return firstZero == secondZero +} diff --git a/trunk/center/common/resultstatus/resultStatusCode.go b/trunk/center/common/resultstatus/resultStatusCode.go index b610df0..c5f1e26 100644 --- a/trunk/center/common/resultstatus/resultStatusCode.go +++ b/trunk/center/common/resultstatus/resultStatusCode.go @@ -93,6 +93,8 @@ var ( // 玩家不存在 PlayerNotExist = NewResultStatus(-1110, "PlayerNotExist") + // 玩家uid和服务器id不匹配 + PlayerNotMatchSever = NewResultStatus(-1111, "PlayerNotMatchSever") // 没有合适的玩家 NotSuitablePlayer = NewResultStatus(-1155, "NotSuitablePlayer") diff --git a/trunk/center/common/timer/timer_handler.go b/trunk/center/common/timer/timer_handler.go new file mode 100644 index 0000000..2d89be2 --- /dev/null +++ b/trunk/center/common/timer/timer_handler.go @@ -0,0 +1,159 @@ +package timer + +import ( + "goutil/logUtilPlus" + "time" +) + +// 触发类型 +const ( + // TriggerTypeMinute 一分钟 + TriggerTypeMinute = iota + 1 + // TriggerTypeFiveMinute 五分钟 + TriggerTypeFiveMinute + // TriggerTypeTenMinute 十分钟 + TriggerTypeTenMinute + // TriggerTypeHalfHour 半小时 + TriggerTypeHalfHour + // TriggerTypeHour 一小时 + TriggerTypeHour + // TriggerTypeEightHour 八小时 + TriggerTypeEightHour + // TriggerTypeDay 一天 每天0点触发 + TriggerTypeDay + // TriggerTypeWeek 一周 (周日0点触发) + TriggerTypeWeek + // TriggerTypeMonth 每月1号0点触发 + TriggerTypeMonth +) + +// TimerHandlerFunc 定义可执行的函数类型 +type timerHandlerFunc func(nowTime time.Time) error + +// 时间触发管理 +var ( + timerHandlerMap = make(map[int][]timerHandlerFunc) + timerTypeMap = map[int]struct{}{ + TriggerTypeMinute: {}, + TriggerTypeFiveMinute: {}, + TriggerTypeTenMinute: {}, + TriggerTypeHalfHour: {}, + TriggerTypeHour: {}, + TriggerTypeEightHour: {}, + TriggerTypeDay: {}, + TriggerTypeWeek: {}, + TriggerTypeMonth: {}, + } +) + +// Register 注册时间触发器 +// @param timerType 时间触发类型 +// @param timerFunc 时间触发函数 +func Register(timerType int, timerFunc timerHandlerFunc) { + + // 检查timerType是否在常量中 + if _, ok := timerTypeMap[timerType]; !ok { + logUtilPlus.ErrorLog("注册时间触发器失败,类型不存在", "timerType", timerType) + return + } + + if _, ok := timerHandlerMap[timerType]; !ok { + timerHandlerMap[timerType] = make([]timerHandlerFunc, 0) + } + timerHandlerMap[timerType] = append(timerHandlerMap[timerType], timerFunc) +} + +// Execute 执行时间触发器 +// @param timerType 时间触发类型 +// @param nowTime 当前时间 +func Execute(timerType int, nowTime time.Time) { + if _, ok := timerHandlerMap[timerType]; !ok { + return + } + for _, timerHandlerFunc := range timerHandlerMap[timerType] { + if err := timerHandlerFunc(nowTime); err != nil { + logUtilPlus.ErrorLog("执行时间触发器失败", "timerType", timerType, "err", err) + } + } +} + +// 初始化init +func init() { + go timerHandler() +} + +// 定时触发函数 +func timerHandler() { + + // 每一分钟执行一次 + ticker := time.NewTicker(time.Minute) + defer ticker.Stop() + defer func() { + if err := recover(); err != nil { + logUtilPlus.ErrorLog("定时触发函数异常", "err", err) + restartTimerHandler() + } + }() + for { + select { + case nowTime := <-ticker.C: + go func() { + // 触发minute + Execute(TriggerTypeMinute, nowTime) + // 触发fiveMinute + if nowTime.Minute()%5 == 0 { + Execute(TriggerTypeFiveMinute, nowTime) + } + // 触发tenMinute + if nowTime.Minute()%10 == 0 { + Execute(TriggerTypeTenMinute, nowTime) + } + // 触发halfHour + if nowTime.Minute()%30 == 0 { + Execute(TriggerTypeHalfHour, nowTime) + } + // 触发hour + if nowTime.Minute() == 0 { + Execute(TriggerTypeHour, nowTime) + } + // 触发eightHour + if nowTime.Hour()%8 == 0 { + Execute(TriggerTypeEightHour, nowTime) + } + // 触发day 0点触发 + if nowTime.Hour() == 0 && nowTime.Minute() == 0 { + Execute(TriggerTypeDay, nowTime) + } + // 触发week 周日0点触发 + if nowTime.Weekday() == time.Sunday && nowTime.Hour() == 0 && nowTime.Minute() == 0 { + Execute(TriggerTypeWeek, nowTime) + } + // 触发month 1号0点触发 + if nowTime.Day() == 1 && nowTime.Hour() == 0 && nowTime.Minute() == 0 { + Execute(TriggerTypeMonth, nowTime) + } + }() + } + } +} + +// restartTimerHandler 重启定时器 +func restartTimerHandler() { + // 设置重试次数 + maxRetries := 5 + retryCount := 0 + + for { + select { + case <-time.After(5 * time.Minute): // 等待5分钟后重试 + if retryCount >= maxRetries { + logUtilPlus.ErrorLog("定时器重启失败,达到最大重试次数") + return + } + logUtilPlus.InfoLog("重新启动定时器,重试次数: %d", retryCount+1) + go timerHandler() + return + } + retryCount++ + } +} diff --git a/trunk/center/common/webserver/apiHandler.go b/trunk/center/common/webserver/apiHandler.go index b2f020f..c79948a 100644 --- a/trunk/center/common/webserver/apiHandler.go +++ b/trunk/center/common/webserver/apiHandler.go @@ -3,7 +3,7 @@ package webServer import ( "net/http" - "common/resultStatus" + "common/resultstatus" ) // 处理函数 diff --git a/trunk/center/common/webserver/reflect.go b/trunk/center/common/webserver/reflect.go index 854230a..3805faa 100644 --- a/trunk/center/common/webserver/reflect.go +++ b/trunk/center/common/webserver/reflect.go @@ -2,7 +2,7 @@ package webServer import ( config "common/configsYaml" - "common/resultStatus" + "common/resultstatus" "goutil/logUtilPlus" "reflect" "strconv" diff --git a/trunk/center/common/webserver/responseObject.go b/trunk/center/common/webserver/responseObject.go index 0b79498..4614a9c 100644 --- a/trunk/center/common/webserver/responseObject.go +++ b/trunk/center/common/webserver/responseObject.go @@ -1,7 +1,7 @@ package webServer import ( - "common/resultStatus" + "common/resultstatus" ) // ResponseObject diff --git a/trunk/center/common/webserver/serverMux.go b/trunk/center/common/webserver/serverMux.go index bcff6df..7079f3a 100644 --- a/trunk/center/common/webserver/serverMux.go +++ b/trunk/center/common/webserver/serverMux.go @@ -2,7 +2,7 @@ package webServer import ( config "common/configsYaml" - "common/resultStatus" + "common/resultstatus" "common/utils" "encoding/json" "fmt" diff --git a/trunk/center/paycenter/go.mod b/trunk/center/paycenter/go.mod index 9072b7f..e3bda08 100644 --- a/trunk/center/paycenter/go.mod +++ b/trunk/center/paycenter/go.mod @@ -10,6 +10,7 @@ replace ( require ( common v0.0.0-00010101000000-000000000000 + github.com/go-pay/gopay v1.5.108 github.com/wechatpay-apiv3/wechatpay-go v0.2.20 gopkg.in/yaml.v3 v3.0.1 goutil v0.0.0-20230425160006-b2d0b0a0b0b0 @@ -22,7 +23,9 @@ require ( github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/elastic/go-elasticsearch/v8 v8.0.0-20210916085751-c2fb55d91ba4 // indirect github.com/fatih/color v1.15.0 // indirect - github.com/go-pay/gopay v1.5.108 // indirect + github.com/go-pay/crypto v0.0.1 // indirect + github.com/go-pay/util v0.0.4 // indirect + github.com/go-pay/xlog v0.0.3 // indirect github.com/go-redis/redis/v8 v8.11.5 // indirect github.com/go-sql-driver/mysql v1.8.1 // indirect github.com/gomodule/redigo v1.8.9 // indirect @@ -32,6 +35,7 @@ require ( github.com/jinzhu/now v1.1.5 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.17 // indirect + github.com/streadway/amqp v1.1.0 // indirect golang.org/x/net v0.0.0-20210916014120-12bc252f5db8 // indirect golang.org/x/sys v0.6.0 // indirect golang.org/x/text v0.21.0 // indirect diff --git a/trunk/center/paycenter/go.sum b/trunk/center/paycenter/go.sum index 2607368..954327f 100644 --- a/trunk/center/paycenter/go.sum +++ b/trunk/center/paycenter/go.sum @@ -19,8 +19,14 @@ github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/go-pay/crypto v0.0.1 h1:B6InT8CLfSLc6nGRVx9VMJRBBazFMjr293+jl0lLXUY= +github.com/go-pay/crypto v0.0.1/go.mod h1:41oEIvHMKbNcYlWUlRWtsnC6+ASgh7u29z0gJXe5bes= github.com/go-pay/gopay v1.5.108 h1:hMhDfucGz+q/XLlz7uZ2LLLg2oJmahpcCUhi5ifEd/0= github.com/go-pay/gopay v1.5.108/go.mod h1:O41QrjYtfGfxyzDVJVrRDHG63cIqfZuv55Eo0NaGnWw= +github.com/go-pay/util v0.0.4 h1:TuwSU9o3Qd7m9v1PbzFuIA/8uO9FJnA6P7neG/NwPyk= +github.com/go-pay/util v0.0.4/go.mod h1:Tsdhs8Ib9J9b4+NKNO1PHh5hWHhlg98PthsX0ckq6PM= +github.com/go-pay/xlog v0.0.3 h1:avyMhCL/JgBHreoGx/am/kHxfs1udDOAeVqbmzP/Yes= +github.com/go-pay/xlog v0.0.3/go.mod h1:mH47xbobrdsSHWsmFtSF5agWbMHFP+tK0ZbVCk5OAEw= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= @@ -58,6 +64,8 @@ github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/streadway/amqp v1.1.0 h1:py12iX8XSyI7aN/3dUT8DFIDJazNJsVJdxNVEpnQTZM= +github.com/streadway/amqp v1.1.0/go.mod h1:WYSrTEYHOXHd0nwFeUXAe2G2hRnQT+deZJJf88uS9Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -70,8 +78,9 @@ github.com/wechatpay-apiv3/wechatpay-go v0.2.20 h1:gS8oFn1bHGnyapR2Zb4aqTV6l4kJW github.com/wechatpay-apiv3/wechatpay-go v0.2.20/go.mod h1:A254AUBVB6R+EqQFo3yTgeh7HtyqRRtN2w9hQSOrd4Q= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd h1:GGJVjV8waZKRHrgwvtH66z9ZGVurTD1MT0n1Bb+q4aM= golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20210916014120-12bc252f5db8 h1:/6y1LfuqNuQdHAm0jjtPtgRcxIxjVZgm5OTu8/QhZvk= diff --git a/trunk/center/paycenter/internal/alipay/alipay.go b/trunk/center/paycenter/internal/alipay/alipay.go new file mode 100644 index 0000000..9d56548 --- /dev/null +++ b/trunk/center/paycenter/internal/alipay/alipay.go @@ -0,0 +1,88 @@ +package alipay + +import ( + "context" + "errors" + "github.com/go-pay/gopay" + "github.com/go-pay/gopay/alipay/v3" + "github.com/go-pay/gopay/pkg/js" + "goutil/logUtilPlus" + "paycenter/internal/cert" + "strconv" +) + +var ( + ctx = context.Background() + client *alipay.ClientV3 + err error +) + +func init() { + // 初始化支付宝客V3户端 + // appid:应用ID + // privateKey:应用私钥,支持PKCS1和PKCS8 + // isProd:是否是正式环境,沙箱环境请选择新版沙箱应用。 + client, err = alipay.NewClientV3(cert.Appid, cert.PrivateKey, false) + if err != nil { + logUtilPlus.ErrorLog("new alipay client err:%s", err) + return + } + + // 自定义配置http请求接收返回结果body大小,默认 10MB + //client.SetBodySize() // 没有特殊需求,可忽略此配置 + + // Debug开关,输出/关闭日志 + client.DebugSwitch = gopay.DebugOn + + // 设置自定义RequestId生成方法 + //client.SetRequestIdFunc() + + // 设置biz_content加密KEY,设置此参数默认开启加密(目前不可用) + //client.SetAESKey("KvKUTqSVZX2fUgmxnFyMaQ==") + + // 传入证书内容 + err = client.SetCert(cert.AppPublicContent, cert.AlipayRootContent, cert.AlipayPublicContentRSA2) + if err != nil { + logUtilPlus.ErrorLog("set cert err:%s", err) + return + } +} + +// AliPayPlace 函数用于发起预支付请求。 +// 参数: +// +// outTradeNo: 商户订单号。 +// currency: 订单金额,单位为分。 +// storeId: 商户门店编号。 +// clientIp: 用户的客户端IP。 +// description: 订单描述。 +// +// 返回值: +// +// 成功时返回预支付ID和nil错误。 +// 失败时返回空字符串和错误对象。 +func AliPayPlace(outTradeNo int64, currency int64, storeId string, clientIp string, description string) (string, error) { + // 请求参数 + bm := make(gopay.BodyMap) + bm.Set("subject", "预创建创建订单"). + Set("out_trade_no", strconv.FormatInt(outTradeNo, 10)). + Set("total_amount", currency) + + rsp := new(struct { + OutTradeNo string `json:"out_trade_no"` + QrCode string `json:"qr_code"` + }) + // 创建订单 + res, err := client.DoAliPayAPISelfV3(ctx, alipay.MethodPost, alipay.V3TradePrecreate, bm, rsp) + if err != nil { + logUtilPlus.ErrorLog("client.TradePrecreate(), err:%v", err) + return "", err + } + logUtilPlus.DebugLog("aliRsp:%s", js.Marshal(rsp)) + if res.StatusCode != alipay.Success { + logUtilPlus.ErrorLog("aliRsp.StatusCode:%d", res.StatusCode) + return "", errors.New("aliRsp.StatusCode:" + strconv.Itoa(res.StatusCode)) + } + + return "Success", nil +} diff --git a/trunk/center/paycenter/internal/internal.go b/trunk/center/paycenter/internal/internal.go new file mode 100644 index 0000000..7c541cb --- /dev/null +++ b/trunk/center/paycenter/internal/internal.go @@ -0,0 +1,9 @@ +package internal + +import ( + _ "paycenter/internal/alipay" + _ "paycenter/internal/cert" + _ "paycenter/internal/mesqueue" + _ "paycenter/internal/pay" + _ "paycenter/internal/wxpay" +) diff --git a/trunk/center/paycenter/internal/mesqueue/game_msg.go b/trunk/center/paycenter/internal/mesqueue/game_msg.go index 91e6c71..56fbe0a 100644 --- a/trunk/center/paycenter/internal/mesqueue/game_msg.go +++ b/trunk/center/paycenter/internal/mesqueue/game_msg.go @@ -34,13 +34,13 @@ type GameMsg struct { var ( //消息队列 - msgQueue chan GameMsg = make(chan GameMsg, 100) + msgQueue = make(chan GameMsg, 100) fileName = "ErrPushMsg" ) func init() { - ConsumeQueue() + go ConsumeQueue() } // AddQueue 添加消息队列 @@ -50,42 +50,40 @@ func AddQueue(gameMsg GameMsg) { // ConsumeQueue 消费消息队列 func ConsumeQueue() { - go func() { - //捕获异常 - defer func() { - if err := recover(); err != nil { - //TODO 捕获异常 - logUtilPlus.ErrorLog("推送充值信息到game异常 err:%s", err) + //捕获异常 + defer func() { + if err := recover(); err != nil { + //TODO 捕获异常 + logUtilPlus.ErrorLog("推送充值信息到game异常 err:%s", err) - //重新开启 - restartConsumer() - } - }() - - for { - gameMsg := <-msgQueue - - url := fmt.Sprintf("http://www.game.com/pay %s", gameMsg.GameId) - - //消费消息队列 推送重置信息到game - result, err := webUtil.GetWebData(url, map[string]string{}) - if err != nil { - logUtilPlus.ErrorLog("推送充值信息到game异常 err:%s", err) - - //放入消息队列重新推送 - if gameMsg.pushCount < 3 { - msgQueue <- gameMsg - gameMsg.pushCount++ - } else { //加入文件放弃推送 - WriteErrPushMsg(url) - } - } - - if string(result) != "" { - - } + //重新开启 + restartConsumer() } }() + + for { + gameMsg := <-msgQueue + + url := fmt.Sprintf("http://www.game.com/pay %s", gameMsg.GameId) + + //消费消息队列 推送重置信息到game + result, err := webUtil.GetWebData(url, map[string]string{}) + if err != nil { + logUtilPlus.ErrorLog("推送充值信息到game异常 err:%s", err) + + //放入消息队列重新推送 + if gameMsg.pushCount < 3 { + msgQueue <- gameMsg + gameMsg.pushCount++ + } else { //加入文件放弃推送 + WriteErrPushMsg(url) + } + } + + if string(result) != "" { + + } + } } // WriteErrPushMsg 推送异常消息 写入文件 @@ -117,7 +115,7 @@ func restartConsumer() { return } logUtilPlus.InfoLog("重新启动消费者,重试次数: %d", retryCount+1) - ConsumeQueue() + go ConsumeQueue() return } retryCount++ diff --git a/trunk/center/paycenter/internal/pay/api.go b/trunk/center/paycenter/internal/pay/api.go index 89f0258..00d604a 100644 --- a/trunk/center/paycenter/internal/pay/api.go +++ b/trunk/center/paycenter/internal/pay/api.go @@ -2,12 +2,13 @@ package pay import ( "common/remark" - "common/resultStatus" + "common/resultstatus" "common/webServer" "goutil/logUtilPlus" "goutil/webUtil" "net/http" - "paycenter/internal" + "paycenter/internal/alipay" + "paycenter/internal/wxpay" "strconv" ) @@ -74,7 +75,7 @@ func (a *PayApi) PlaceAnOrder(orderId int64, modelID int32, currency int64, stor clientIp := webUtil.GetRequestIP(r) //下微信订单 - prepayId, err := internal.Prepay(orderId, currency, storeId, clientIp, "描述!!!!!!!!!!") + prepayId, err := wxpay.Prepay(orderId, currency, storeId, clientIp, "描述!!!!!!!!!!") if err != nil { responseObj.SetResultStatus(resultStatus.APIDataError) return @@ -139,7 +140,7 @@ func (a *PayApi) CallBack(orderIDStr string) (responseObj *webServer.ResponseObj } //查询订单状态 - statusStr, err := internal.QueryOrderByOutTradeNo(orderID) + statusStr, err := wxpay.QueryOrderByOutTradeNo(orderID) if err != nil { responseObj.SetResultStatus(resultStatus.DataError) return @@ -193,7 +194,7 @@ func (a *PayApi) AliPayPlaceAnOrder(orderId int64, modelID int32, currency int64 clientIp := webUtil.GetRequestIP(r) //下微信订单 - prepayId, err := internal.AliPayPlace(orderId, currency, storeId, clientIp, "描述!!!!!!!!!!") + prepayId, err := alipay.AliPayPlace(orderId, currency, storeId, clientIp, "描述!!!!!!!!!!") if err != nil { responseObj.SetResultStatus(resultStatus.APIDataError) return @@ -209,4 +210,5 @@ func (a *PayApi) AliPayPlaceAnOrder(orderId int64, modelID int32, currency int64 resultMap["orderID"] = order.OrderID resultMap["prepayId"] = order.PrepayId responseObj.SetData(resultMap) + return responseObj } diff --git a/trunk/center/paycenter/internal/pay/check_order.go b/trunk/center/paycenter/internal/pay/check_order.go index 8bf04d9..b66f92e 100644 --- a/trunk/center/paycenter/internal/pay/check_order.go +++ b/trunk/center/paycenter/internal/pay/check_order.go @@ -2,94 +2,68 @@ package pay import ( "common/connection" + "common/timer" "goutil/logUtilPlus" - "paycenter/internal" + "paycenter/internal/wxpay" "strconv" "time" ) func init() { - go CheckOrderStatus() + timer.Register(timer.TriggerTypeHalfHour, CheckOrderStatus) } -// CheckOrderStatus 查询订单状态 -func CheckOrderStatus() { +// CheckOrderStatus 检查订单状态 +// @return error +func CheckOrderStatus(nowTime time.Time) error { - //捕获异常 - defer func() { - if err := recover(); err != nil { - logUtilPlus.ErrorLog("CheckOrderStatus panic:", err) - restartConsumer() + //检索最近一个月的订单 + for i := 0; i < 2; i++ { + + //取i的负数 + dbDate := connection.GetToMonthAdd(int32(-i)) + var orders []Order // 使用切片存储查询结果 + + //这里使用原始sql + sql := "select * from order_" + strconv.Itoa(int(dbDate)) + " where order_status = 0" + dbResult := connection.GetPayDB().Exec(sql).Find(&orders) + if dbResult.Error != nil { + logUtilPlus.ErrorLog("查询订单状态失败", dbResult.Error.Error()) + continue } - }() - for { - //检索最近一个月的订单 - for i := 0; i < 2; i++ { + // 处理查询结果 + for _, order := range orders { - //取i的负数 - dbDate := connection.GetToMonthAdd(int32(-i)) - var orders []Order // 使用切片存储查询结果 - - //这里使用原始sql - sql := "select * from order_" + strconv.Itoa(int(dbDate)) + " where order_status = 0" - dbResult := connection.GetPayDB().Exec(sql).Find(&orders) - if dbResult.Error != nil { - logUtilPlus.ErrorLog("查询订单状态失败", dbResult.Error.Error()) + //查询订单状态 + statusStr, err := wxpay.QueryOrderByOutTradeNo(order.OrderID) + if err != nil { + logUtilPlus.ErrorLog("查询订单状态失败", err.Error()) continue } - - // 处理查询结果 - for _, order := range orders { - - //查询订单状态 - statusStr, err := internal.QueryOrderByOutTradeNo(order.OrderID) + if statusStr == "SUCCESS" { + //修改订单状态 + err = ChangeOrderStatus(order.OrderID, 1) if err != nil { - logUtilPlus.ErrorLog("查询订单状态失败", err.Error()) + logUtilPlus.ErrorLog("修改订单状态失败", err.Error()) continue } - if statusStr == "SUCCESS" { - //修改订单状态 - err = ChangeOrderStatus(order.OrderID, 1) - if err != nil { - logUtilPlus.ErrorLog("修改订单状态失败", err.Error()) - continue - } - } else if statusStr == "CLOSED" { //已关闭 - order.OrderStatus = 2 - //修改订单状态 - connection.AsyncSave(connection.GetPayDB(), &order) - } else if order.OrderTime.Add(time.Hour * 1).Before(time.Now()) { //超一个小时未支付 直接关闭订单 - //直接关闭订单 - internal.CloseOrder(order.OrderID) - order.OrderStatus = 2 - connection.AsyncSave(connection.GetPayDB(), &order) + } else if statusStr == "CLOSED" { //已关闭 + order.OrderStatus = 2 + //修改订单状态 + connection.AsyncSave(connection.GetPayDB(), &order) + } else if order.OrderTime.Add(time.Hour * 1).Before(time.Now()) { //超一个小时未支付 直接关闭订单 + //直接关闭订单 + err = wxpay.CloseOrder(order.OrderID) + if err != nil { + logUtilPlus.ErrorLog("关闭订单失败", err.Error()) + continue } + order.OrderStatus = 2 + connection.AsyncSave(connection.GetPayDB(), &order) } } - - //休息30分钟 - time.Sleep(time.Minute * 30) } -} -// restartConsumer 重启消费者 -func restartConsumer() { - // 设置重试次数 - maxRetries := 5 - retryCount := 0 - - for { - select { - case <-time.After(5 * time.Second): // 等待5秒后重试 - if retryCount >= maxRetries { - logUtilPlus.ErrorLog("查询订单状态,达到最大重试次数") - return - } - logUtilPlus.InfoLog("查询订单状态,重试次数: %d", retryCount+1) - go CheckOrderStatus() - return - } - retryCount++ - } + return nil } diff --git a/trunk/center/paycenter/internal/wxpay/config.go b/trunk/center/paycenter/internal/wxpay/config.go new file mode 100644 index 0000000..0f46cbb --- /dev/null +++ b/trunk/center/paycenter/internal/wxpay/config.go @@ -0,0 +1,64 @@ +package wxpay + +import ( + "gopkg.in/yaml.v3" + "goutil/yamlUtil" + "log" +) + +type WxPayConfig struct { + MchID string + MchCertificateSerialNumber string + MchAPIv3Key string + AppId string + NotifyUrl string +} + +var ( + wxPayConfig = &WxPayConfig{} +) + +func init() { + + //加载配置 + reloadConfig() + + //校验配置 + CheckConfig() +} + +// reloadConfig +// +// @description: reloadConfig +// +// parameter: +// return: +// +// @error: 错误信息 +func reloadConfig() error { + + yamlFile, err := yamlUtil.LoadFromFile("payconfig/wxpayconfig.yml") + if err != nil { + return err + } + + // 解析 YAML 文件 + err = yaml.Unmarshal(yamlFile, wxPayConfig) + if err != nil { + log.Fatalf("Error unmarshalling config file: %v", err) + return err + } + + return nil +} + +// CheckConfig 校验配置 +func CheckConfig() error { + + return nil +} + +// GetWxPayConfig 获取配置 +func GetWxPayConfig() *WxPayConfig { + return wxPayConfig +} diff --git a/trunk/center/paycenter/internal/wxpay/pay.go b/trunk/center/paycenter/internal/wxpay/pay.go new file mode 100644 index 0000000..ec8512a --- /dev/null +++ b/trunk/center/paycenter/internal/wxpay/pay.go @@ -0,0 +1,249 @@ +package wxpay + +import ( + "context" + "errors" + "fmt" + "github.com/wechatpay-apiv3/wechatpay-go/core" + "github.com/wechatpay-apiv3/wechatpay-go/core/option" + "github.com/wechatpay-apiv3/wechatpay-go/services/payments/app" + "github.com/wechatpay-apiv3/wechatpay-go/utils" + "goutil/logUtilPlus" + "log" + "strconv" + "time" +) + +var ( + mchID string = GetWxPayConfig().MchID // 商户号 + mchCertificateSerialNumber string = GetWxPayConfig().MchCertificateSerialNumber // 商户证书序列号 + mchAPIv3Key string = GetWxPayConfig().MchAPIv3Key // 商户APIv3密钥 + appId string = GetWxPayConfig().AppId // 应用ID + Address string = "成都市XXXXXXXXXXXXXXXXXXXXXXX" //公司地址 + wxPayApiUrl string = GetWxPayConfig().NotifyUrl //支付成功回调地址 +) + +// Prepay 函数用于发起预支付请求。 +// 参数: +// +// outTradeNo: 商户订单号。 +// currency: 订单金额,单位为分。 +// storeId: 商户门店编号。 +// clientIp: 用户的客户端IP。 +// description: 订单描述。 +// +// 返回值: +// +// 成功时返回预支付ID和nil错误。 +// 失败时返回空字符串和错误对象。 +func Prepay(outTradeNo int64, currency int64, storeId string, clientIp string, description string) (string, error) { + + // 使用 utils 提供的函数从本地文件中加载商户私钥,商户私钥会用来生成请求的签名 + mchPrivateKey, err := utils.LoadPrivateKeyWithPath("/path/to/merchant/apiclient_key.pem") + if err != nil { + logUtilPlus.ErrorLog("load merchant private key error") + return "", err + } + + ctx := context.Background() + // 使用商户私钥等初始化 client,并使它具有自动定时获取微信支付平台证书的能力 + opts := []core.ClientOption{ + option.WithWechatPayAutoAuthCipher(mchID, mchCertificateSerialNumber, mchPrivateKey, mchAPIv3Key), + } + client, err := core.NewClient(ctx, opts...) + if err != nil { + logUtilPlus.ErrorLog("new wechat pay client err:%s", err) + return "", err + } + + svc := app.AppApiService{Client: client} + resp, result, err := svc.Prepay(ctx, + app.PrepayRequest{ + Appid: core.String(appId), + Mchid: core.String(mchID), + Description: core.String(description), + OutTradeNo: core.String(strconv.FormatInt(outTradeNo, 10)), + TimeExpire: core.Time(time.Now().Add(time.Hour * 2)), //支付时效时间 2 小时后失效 + Attach: core.String(""), //附加数据 这里不需要,有个订单id 可以获取订单详细信息 + NotifyUrl: core.String(fmt.Sprintf(wxPayApiUrl, strconv.FormatInt(outTradeNo, 10))), //回调地址 + //GoodsTag: core.String("WXG"),//优惠标记 这里没用 + //LimitPay: []string{"LimitPay_example"}, + SupportFapiao: core.Bool(false), + Amount: &app.Amount{ + Currency: core.String("CNY"), + Total: core.Int64(currency), + }, + Detail: &app.Detail{ + //CostPrice: core.Int64(608800), + GoodsDetail: []app.GoodsDetail{app.GoodsDetail{ + GoodsName: core.String(storeId), //商品编号 + MerchantGoodsId: core.String(description), + Quantity: core.Int64(1), + UnitPrice: core.Int64(currency), + //WechatpayGoodsId: core.String("1001"), + }}, + //InvoiceId: core.String("wx123"), + }, + SceneInfo: &app.SceneInfo{ + //DeviceId: core.String("013467007045764"), + PayerClientIp: core.String(clientIp), + StoreInfo: &app.StoreInfo{ + Address: core.String(Address), + //AreaCode: core.String("440305"), + //Id: core.String("0001"), + //Name: core.String("腾讯大厦分店"), + }, + }, + SettleInfo: &app.SettleInfo{ + ProfitSharing: core.Bool(false), + }, + }, + ) + + if err != nil { + // 处理错误 + logUtilPlus.ErrorLog("call Prepay err:%s", err.Error()) + return "", err + } + + if result.Response.StatusCode != 200 { + errStr := fmt.Sprintf("status=%d resp=%s", result.Response.StatusCode, resp) + logUtilPlus.ErrorLog(errStr) + return "", errors.New(errStr) + } + + return *resp.PrepayId, nil +} + +// CloseOrder 关闭订单 +func CloseOrder(outTradeNo int64) error { + + // 使用 utils 提供的函数从本地文件中加载商户私钥,商户私钥会用来生成请求的签名 + mchPrivateKey, err := utils.LoadPrivateKeyWithPath("/path/to/merchant/apiclient_key.pem") + if err != nil { + log.Print("加载商家私钥错误") + } + + ctx := context.Background() + // 使用商户私钥等初始化 client,并使它具有自动定时获取微信支付平台证书的能力 + opts := []core.ClientOption{ + option.WithWechatPayAutoAuthCipher(mchID, mchCertificateSerialNumber, mchPrivateKey, mchAPIv3Key), + } + client, err := core.NewClient(ctx, opts...) + if err != nil { + log.Printf("新的 WeChat Pay 客户端 Err:%s", err) + } + + svc := app.AppApiService{Client: client} + _, err = svc.CloseOrder(ctx, + app.CloseOrderRequest{ + + //商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯 + OutTradeNo: core.String(strconv.FormatInt(outTradeNo, 10)), + + //直连商户的商户号,由微信支付生成并下发。 + Mchid: core.String(mchID), + }, + ) + + if err != nil { + // 处理错误 + logUtilPlus.ErrorLog("call CloseOrder err:%s", err) + return err + } + return nil +} + +// QueryOrderById 根据商户订单号查询订单 +func QueryOrderById() { + + // 使用 utils 提供的函数从本地文件中加载商户私钥,商户私钥会用来生成请求的签名 + mchPrivateKey, err := utils.LoadPrivateKeyWithPath("/path/to/merchant/apiclient_key.pem") + if err != nil { + log.Print("load merchant private key error") + } + + ctx := context.Background() + // 使用商户私钥等初始化 client,并使它具有自动定时获取微信支付平台证书的能力 + opts := []core.ClientOption{ + option.WithWechatPayAutoAuthCipher(mchID, mchCertificateSerialNumber, mchPrivateKey, mchAPIv3Key), + } + client, err := core.NewClient(ctx, opts...) + if err != nil { + log.Printf("new wechat pay client err:%s", err) + } + + svc := app.AppApiService{Client: client} + resp, result, err := svc.QueryOrderById(ctx, + app.QueryOrderByIdRequest{ + TransactionId: core.String("TransactionId_example"), + Mchid: core.String("Mchid_example"), + }, + ) + + if err != nil { + // 处理错误 + log.Printf("call QueryOrderById err:%s", err) + } else { + // 处理返回结果 + log.Printf("status=%d resp=%s", result.Response.StatusCode, resp) + } +} + +// QueryOrderByOutTradeNo 根据商户订单号查询订单 +func QueryOrderByOutTradeNo(outTradeNo int64) (string, error) { + + //循环查询次数 + var count int = 0 + +loop: + + // 使用 utils 提供的函数从本地文件中加载商户私钥,商户私钥会用来生成请求的签名 + mchPrivateKey, err := utils.LoadPrivateKeyWithPath("/path/to/merchant/apiclient_key.pem") + if err != nil { + log.Print("load merchant private key error") + return "", err + } + + ctx := context.Background() + // 使用商户私钥等初始化 client,并使它具有自动定时获取微信支付平台证书的能力 + opts := []core.ClientOption{ + option.WithWechatPayAutoAuthCipher(mchID, mchCertificateSerialNumber, mchPrivateKey, mchAPIv3Key), + } + client, err := core.NewClient(ctx, opts...) + if err != nil { + log.Printf("new wechat pay client err:%s", err) + return "", err + } + + svc := app.AppApiService{Client: client} + resp, result, err := svc.QueryOrderByOutTradeNo(ctx, + app.QueryOrderByOutTradeNoRequest{ + OutTradeNo: core.String(strconv.FormatInt(outTradeNo, 10)), + Mchid: &mchID, + }, + ) + + if err != nil { + // 处理错误 + log.Printf("call QueryOrderByOutTradeNo err:%s", err) + return "", err + } else { + // 处理返回结果 + log.Printf("status=%d resp=%s", result.Response.StatusCode, resp) + + //支付成功 + if resp.TradeState == core.String("SUCCESS") { + return "SUCCESS", nil + + } else if resp.TradeState == core.String("NOTPAY") && count < 3 { //未支付,循环查找订单 + //休息200毫秒 + time.Sleep(200 * time.Millisecond) + count++ + goto loop + } + + //支付失败 + return *resp.TradeState, nil + } +} diff --git a/trunk/center/usercenter/internal/wxuser/api.go b/trunk/center/usercenter/internal/wxuser/api.go index f9cf3a0..0e26483 100644 --- a/trunk/center/usercenter/internal/wxuser/api.go +++ b/trunk/center/usercenter/internal/wxuser/api.go @@ -54,7 +54,7 @@ func (a *WxuserApi) LoginByWechat(code string, severId int32) (responseObj *webS responseObj.SetResultStatus(resultStatus.APIDataError) return } - res, uid := GetWechatsdkService().GetAccessToken(code) + res, uid := GetWechatsdkService().GetAccessToken(code, severId) if res != resultStatus.Success { responseObj.SetResultStatus(res) } @@ -113,7 +113,7 @@ func init() { "Message '类型:string'": "响应结果的状态值所对应的描述信息", "Data '类型:interface{}'": "响应结果的数据" { - + "Uid '类型:int64'": "切服后的区服的唯一Id", } }` remark.RegisterMethodRemark(moduleName, methodName, methodDesc, methodAuthor, methodMendor, methodDate, methodInParam, methodOutParam, skipVerifyTokenPage) @@ -121,13 +121,17 @@ func init() { func (a *WxuserApi) SwitchSever(uid int64, oldseverId, newseverId int32) (responseObj *webServer.ResponseObject) { responseObj = webServer.GetInitResponseObj() - if uid == 0 { - logUtilPlus.ErrorLog("uid=0") + if uid == 0 || oldseverId == 0 || newseverId == 0 { + logUtilPlus.ErrorLog("WxuserApi.SwitchSever参数不正确") responseObj.SetResultStatus(resultStatus.APIDataError) return } - // RecordLoginOut(openid, oldseverId) - // RecordLoginIn(openid, newseverId) + if oldseverId == newseverId { + logUtilPlus.ErrorLog("WxuserApi.SwitchSever参数不正确") + responseObj.SetResultStatus(resultStatus.APIDataError) + return + } + SwitchSever(uid, oldseverId, newseverId) return } @@ -139,7 +143,7 @@ func init() { methodAuthor := "youjinlan" methodMendor := "" methodDate := "2025-01-22 10:40:00" - methodInParam := []string{"string openid,int32 severId"} + methodInParam := []string{"int64 uid"} methodOutParam := ` { "Code '类型:int'": "响应结果的状态值", @@ -155,7 +159,7 @@ func init() { func (a *WxuserApi) WatchAD(uid int64) (responseObj *webServer.ResponseObject) { responseObj = webServer.GetInitResponseObj() if uid == 0 { - logUtilPlus.ErrorLog("openid,openid为空") + logUtilPlus.ErrorLog("uid=0") responseObj.SetResultStatus(resultStatus.APIDataError) return } diff --git a/trunk/center/usercenter/internal/wxuser/logic.go b/trunk/center/usercenter/internal/wxuser/logic.go index c815496..4026551 100644 --- a/trunk/center/usercenter/internal/wxuser/logic.go +++ b/trunk/center/usercenter/internal/wxuser/logic.go @@ -8,12 +8,16 @@ import ( var ( // 用户缓存对象 - userInfoMap = make(map[string]*WxUserInfo) - rwmu sync.RWMutex - userRecordMap = make(map[int64]*RecordLoginOfWxUser) - rwmu2 sync.RWMutex - userADRecordMap = make(map[int64]*RecordWatchADOfWxUser) - rwmu3 sync.RWMutex + userInfoMap = make(map[string]*WxUserInfo) + rwmu sync.RWMutex + userRecordMap = make(map[int64]*RecordLoginOfWxUser) + rwmu2 sync.RWMutex + userADRecordMap = make(map[int64]*RecordWatchADOfWxUser) + rwmu3 sync.RWMutex + userSeverInfoMap = make(map[string]map[int32]int64) + rwmu4 sync.RWMutex + userUidMap = make(map[int64]*WxUserSeverInfo) + rwmu5 sync.RWMutex ) func GetUserByOpenId(openId string) (*WxUserInfo, error) { @@ -58,12 +62,17 @@ func AddUserCache(user *WxUserInfo) { } // 登录登出相关 + + + +// 添加用户登录登出表到缓存 func AddUserRecordCache(userrecord *RecordLoginOfWxUser) { rwmu2.Lock() defer rwmu2.Unlock() userRecordMap[userrecord.Uid] = userrecord } +// 获取最新一条登录记录 func GetUserRecord(uid int64) (*RecordLoginOfWxUser, error) { var userRecord *RecordLoginOfWxUser func() *RecordLoginOfWxUser { @@ -91,6 +100,7 @@ func GetUserRecord(uid int64) (*RecordLoginOfWxUser, error) { return userRecord, nil } +// 添加用户登录登出记录 func AddUserRecord(userrecord *RecordLoginOfWxUser) (int64, error) { //处理一些验证 @@ -103,6 +113,7 @@ func AddUserRecord(userrecord *RecordLoginOfWxUser) (int64, error) { return userrecord.ID, nil } +// 保存添加用户登录登出记录 func SaveUserRecord(userrecord *RecordLoginOfWxUser) (int64, error) { //处理一些验证 @@ -116,12 +127,17 @@ func SaveUserRecord(userrecord *RecordLoginOfWxUser) (int64, error) { } // 看广告相关 + + + +// 添加用户看广告记录到缓存 func AddUserADRecordCache(userADrecord *RecordWatchADOfWxUser) { rwmu3.Lock() defer rwmu3.Unlock() userADRecordMap[userADrecord.Uid] = userADrecord } +// 获取最新一条看广告记录 func GetUserADRecord(uid int64) (*RecordWatchADOfWxUser, error) { var userADRecord *RecordWatchADOfWxUser func() *RecordWatchADOfWxUser { @@ -149,6 +165,7 @@ func GetUserADRecord(uid int64) (*RecordWatchADOfWxUser, error) { return userADRecord, nil } +// 添加用户看广告记录到数据库 func AddUserADRecord(userADrecord *RecordWatchADOfWxUser) (int64, error) { //处理一些验证 @@ -161,6 +178,7 @@ func AddUserADRecord(userADrecord *RecordWatchADOfWxUser) (int64, error) { return userADrecord.ID, nil } +// 保存用户看广告记录到数据库 func SaveUserADRecord(userADrecord *RecordWatchADOfWxUser) (int64, error) { //处理一些验证 @@ -174,6 +192,88 @@ func SaveUserADRecord(userADrecord *RecordWatchADOfWxUser) (int64, error) { } // 区服相关 + + + +// 添加用户的区服信息到缓存 以openid为key的map +func AddUserSeverInfoCache(openid string, severid int32, uid int64) { + rwmu4.Lock() + defer rwmu4.Unlock() + newMap := make(map[int32]int64) + newMap[severid] = uid + if _, ok := userSeverInfoMap[openid]; !ok { + userSeverInfoMap[openid] = newMap + return + } + if _, ok := userSeverInfoMap[openid][severid]; !ok { + userSeverInfoMap[openid][severid] = uid + } +} + +// 添加用户的区服信息到缓存 以uid为key +func AddUserUidCache(wxUserSeverInfo *WxUserSeverInfo) { + rwmu5.Lock() + defer rwmu5.Unlock() + userUidMap[wxUserSeverInfo.Uid] = wxUserSeverInfo +} + +// 通过uid来获取用户的区服信息 +func GetUserSeverInfoByUid(uid int64) (*WxUserSeverInfo, error) { + var userSeverInfoRecord *WxUserSeverInfo + func() *WxUserSeverInfo { + rwmu5.RLock() + defer rwmu5.RUnlock() + ok := true + if userSeverInfoRecord, ok = userUidMap[uid]; ok { + return userSeverInfoRecord + } + return nil + }() + if userSeverInfoRecord == nil { + result := connection.GetUserDB().Where("uid = ?", uid).First(&userSeverInfoRecord) + if result.Error != nil { + return nil, result.Error + } + } + func() { + rwmu5.Lock() + defer rwmu5.Unlock() + userUidMap[uid] = userSeverInfoRecord + }() + return userSeverInfoRecord, nil +} + +// 通过openid和severid来获取uid +func GetUserUid(openId string, severId int32) (int64, error) { + var uid int64 + func() int64 { + rwmu4.RLock() + defer rwmu4.RUnlock() + ok := true + if _, ok = userSeverInfoMap[openId]; !ok { + return 0 + } + if uid, ok = userSeverInfoMap[openId][severId]; ok { + return uid + } + return 0 + }() + if uid == 0 { + var userSeverInfoRecord *WxUserSeverInfo + result := connection.GetUserDB().Where("openId = ? AND severId = ?", openId, severId).First(&userSeverInfoRecord) + if result.Error != nil { + return 0, result.Error + } + if userSeverInfoRecord.Uid == 0 { + userSeverInfoRecord.Uid = userSeverInfoRecord.ID + 100000000 + SaveUserSeverInfo(userSeverInfoRecord) + } + return userSeverInfoRecord.Uid, nil + } + return uid, nil +} + +// 添加用户区服信息到数据库 func AddUserSeverInfo(userSeverInfo *WxUserSeverInfo) (int64, error) { //处理一些验证 @@ -186,6 +286,7 @@ func AddUserSeverInfo(userSeverInfo *WxUserSeverInfo) (int64, error) { return userSeverInfo.ID, nil } +// 保存用户区服信息到数据库 func SaveUserSeverInfo(userSeverInfo *WxUserSeverInfo) (int64, error) { //处理一些验证 @@ -198,20 +299,25 @@ func SaveUserSeverInfo(userSeverInfo *WxUserSeverInfo) (int64, error) { return userSeverInfo.ID, nil } -func GetUserSeverInfo() (*WxUserSeverInfo, error) { - var userSeverInfoRecord *WxUserSeverInfo - result := connection.GetUserDB().Last(&userSeverInfoRecord) +// 查询severid是否存在,即是否为新服 +func CheckSeverID(severid int32) bool { + var userSeverList *WxUserSeverList + result := connection.GetUserDB().Where("severId = ?", severid).First(&userSeverList) if result.Error != nil { - return nil, result.Error + return false } - return userSeverInfoRecord, nil + return true } -func GetUserSeverInfoByIds(openId string, severId int32) (*WxUserSeverInfo, error) { - var userSeverInfoRecord *WxUserSeverInfo - result := connection.GetUserDB().Where("openId = ? AND severId = ?", openId, severId).First(&userSeverInfoRecord) +// 添加新的severid到服务器列表 +func AddUserSeverList(userSeverList *WxUserSeverList) (int64, error) { + + //处理一些验证 + + // 写入到数据库 + result := connection.GetUserDB().Create(&userSeverList) // 通过数据的指针来创建 if result.Error != nil { - return nil, result.Error + logUtilPlus.ErrorLog("添加用户区服列表失败 错误信息:", result.Error.Error()) } - return userSeverInfoRecord, nil + return userSeverList.ID, nil } diff --git a/trunk/center/usercenter/internal/wxuser/user.go b/trunk/center/usercenter/internal/wxuser/user.go index 21cee40..6e61396 100644 --- a/trunk/center/usercenter/internal/wxuser/user.go +++ b/trunk/center/usercenter/internal/wxuser/user.go @@ -2,9 +2,6 @@ package wxuser import ( "common/connection" - "time" - - "github.com/jinzhu/gorm" ) func init() { @@ -13,6 +10,7 @@ func init() { connection.RegisterDBModel(&RecordLoginOfWxUser{}) connection.RegisterDBModel(&RecordWatchADOfWxUser{}) connection.RegisterDBModel(&WxUserSeverInfo{}) + connection.RegisterDBModel(&WxUserSeverList{}) } type WechatTokens struct { @@ -45,29 +43,40 @@ type WxUserSeverInfo struct { Uid int64 `gorm:"column:uid;comment:用户唯一Id" json:"uid"` } +// 记录当前为止的开服数 +type WxUserSeverList struct { + ID int64 `gorm:"column:id;primary_key;comment:自增索引;autoIncrementIncrement" json:"id"` + SeverId int32 `gorm:"column:severId;comment:区服Id" json:"severId"` +} + // 登录相关的记录 type RecordLoginOfWxUser struct { ID int64 `gorm:"column:id;primary_key;comment:自增索引;autoIncrementIncrement" json:"id"` Uid int64 `gorm:"column:uid;comment:用户唯一Id" json:"uid"` + SeverId int32 `gorm:"column:severId;comment:区服Id" json:"severId"` + RecordDate int64 `gorm:"column:recorddate;comment:记录日期" json:"recorddate"` //只记录当天0点的时间戳,方便查询某一日的数据 LoginInTime int64 `gorm:"column:loginintime;comment:登录时间" json:"loginintime"` LoginOutTime int64 `gorm:"column:loginouttime;comment:登出时间" json:"loginouttime"` PlayTimes int64 `gorm:"column:playtimes;comment:游玩时长" json:"playtimes"` + //用于统计当日的总上线人数 0=否,1=是 + IsFirstLogin int32 `gorm:"column:isfirstlogin;comment:是否首次登录" json:"isfirstlogin"` } // 看广告相关记录 // 记录日期便于按天统计 type RecordWatchADOfWxUser struct { - ID int64 `gorm:"column:id;primary_key;comment:自增索引;autoIncrementIncrement" json:"id"` - RecordDate time.Time `gorm:"column:recorddate;type:date;comment:记录日期" json:"recorddate"` - Uid int64 `gorm:"column:uid;comment:用户唯一Id" json:"uid"` - WatchADNum int32 `gorm:"column:watchadnum;comment:看广告次数" json:"watchadnum"` + ID int64 `gorm:"column:id;primary_key;comment:自增索引;autoIncrementIncrement" json:"id"` + RecordDate int64 `gorm:"column:recorddate;comment:记录日期" json:"recorddate"` //只记录当天0点的时间戳,方便查询某一日的数据 + Uid int64 `gorm:"column:uid;comment:用户唯一Id" json:"uid"` + SeverId int32 `gorm:"column:severId;comment:区服Id" json:"severId"` + WatchADNum int32 `gorm:"column:watchadnum;comment:看广告次数" json:"watchadnum"` } func (WxUserInfo) TableName() string { return "wxuserinfo" } -func (r RecordLoginOfWxUser) TableName(db *gorm.DB) string { +func (RecordLoginOfWxUser) TableName() string { return "recordloginofwxuser" } @@ -78,3 +87,7 @@ func (RecordWatchADOfWxUser) TableName() string { func (WxUserSeverInfo) TableName() string { return "wxuserseverinfo" } + +func (WxUserSeverList) TableName() string { + return "wxuserseverlist" +} diff --git a/trunk/center/usercenter/internal/wxuser/userrecord.go b/trunk/center/usercenter/internal/wxuser/userrecord.go index faa0fbc..ef54d36 100644 --- a/trunk/center/usercenter/internal/wxuser/userrecord.go +++ b/trunk/center/usercenter/internal/wxuser/userrecord.go @@ -1,24 +1,38 @@ package wxuser import ( + "common/mytime" "common/resultStatus" "goutil/logUtilPlus" "time" ) -func RecordLoginIn(uid int64) { +// 记录登录数据 +func RecordLoginIn(uid int64, severId int32) { nowTime := time.Now().Unix() - // if userLastRecord, _ := GetUserRecord(uid); userLastRecord != nil { - // if userLastRecord.LoginOutTime == 0 { - // return - // } - // } userRecord := &RecordLoginOfWxUser{} + userRecord.IsFirstLogin = 1 + if userLastRecord, _ := GetUserRecord(uid); userLastRecord != nil { + if userLastRecord.RecordDate == mytime.ZeroTime(nowTime, 0) { + userRecord.IsFirstLogin = 0 + } + } userRecord.LoginInTime = nowTime + userRecord.Uid = uid + userRecord.RecordDate = mytime.ZeroTime(nowTime, 0) + userRecord.SeverId = severId AddUserRecord(userRecord) AddUserRecordCache(userRecord) + //检测该服是否是新服 + if !CheckSeverID(severId) { + //若是新服,添加服务器id到服务器列表,方便统计的时候知道有多少个服 + newUserSeverList := &WxUserSeverList{} + newUserSeverList.SeverId = severId + AddUserSeverList(newUserSeverList) + } } +// 统计登出数据 func RecordLoginOut(uid int64) resultStatus.ResultStatus { var userRecord *RecordLoginOfWxUser if userRecord, _ = GetUserRecord(uid); userRecord == nil { @@ -30,51 +44,90 @@ func RecordLoginOut(uid int64) resultStatus.ResultStatus { } go func() { nowTime := time.Now().Unix() - userRecord.LoginOutTime = nowTime - playTime := nowTime - userRecord.LoginInTime - userRecord.PlayTimes += playTime - SaveUserRecord(userRecord) + if userRecord.RecordDate == mytime.ZeroTime(nowTime, 0) { + userRecord.LoginOutTime = nowTime + playTime := nowTime - userRecord.LoginInTime + userRecord.PlayTimes += playTime + SaveUserRecord(userRecord) + } else { + //如果跨天,要拆分成两条数据记录 + zeroTime := mytime.ZeroTime(nowTime, 0) + userRecord.LoginOutTime = zeroTime + playTime := zeroTime - userRecord.LoginInTime + userRecord.PlayTimes += playTime + SaveUserRecord(userRecord) + newUserRecord := &RecordLoginOfWxUser{} + newUserRecord.LoginInTime = zeroTime + newUserRecord.LoginOutTime = nowTime + playTime = nowTime - userRecord.LoginInTime + newUserRecord.PlayTimes += playTime + newUserRecord.RecordDate = mytime.ZeroTime(nowTime, 0) + newUserRecord.Uid = uid + newUserRecord.SeverId = userRecord.SeverId + newUserRecord.IsFirstLogin = 1 + AddUserRecord(newUserRecord) + } }() return resultStatus.Success } +// 记录看广告数据 func RecordWatchAD(uid int64) resultStatus.ResultStatus { - nowTime := time.Now() + todayZeroTime := mytime.ZeroTime(time.Now().Unix(), 0) userADRecord := &RecordWatchADOfWxUser{} + var userSeverInfoRecord *WxUserSeverInfo + if userSeverInfoRecord, _ = GetUserSeverInfoByUid(uid); userSeverInfoRecord == nil { + return resultStatus.PlayerNotExist + } + severId := userSeverInfoRecord.SeverId if userLastADRecord, _ := GetUserADRecord(uid); userLastADRecord != nil { - if nowTime.Format("2006-01-02") == userLastADRecord.RecordDate.Format("2006-01-02") { + if userLastADRecord.RecordDate == mytime.ZeroTime(todayZeroTime, 0) { + //数据按天记录,如果为同一天,就在原来的数据上加 userLastADRecord.WatchADNum++ SaveUserADRecord(userLastADRecord) return resultStatus.Success } } - userADRecord.RecordDate = nowTime + userADRecord.RecordDate = todayZeroTime userADRecord.WatchADNum += 1 + userADRecord.Uid = uid + userADRecord.SeverId = severId AddUserADRecord(userADRecord) AddUserADRecordCache(userADRecord) return resultStatus.Success } +// 根据openid和severid来创建uid func CreatUid(openId string, severId int32) int64 { - var lastId int64 - if userRecord, _ := GetUserSeverInfoByIds(openId, severId); userRecord != nil { - return userRecord.Uid - } - if userLastRecord, _ := GetUserSeverInfo(); userLastRecord != nil { - lastId = userLastRecord.ID + if uid, _ := GetUserUid(openId, severId); uid != 0 { + return uid } userSeverInfo := &WxUserSeverInfo{} userSeverInfo.OpenId = openId userSeverInfo.SeverId = severId - userSeverInfo.Uid = lastId + 1 + 100000000 - AddUserSeverInfo(userSeverInfo) - return userSeverInfo.Uid + ID, _ := AddUserSeverInfo(userSeverInfo) + //uid=数据库索引+1亿,先取索引再相加,避免重复 + Uid := ID + 100000000 + AddUserSeverInfoCache(openId, severId, Uid) + AddUserUidCache(userSeverInfo) + go func() { + userSeverInfo.Uid = Uid + SaveUserSeverInfo(userSeverInfo) + }() + return Uid } -// func init() { -// testRecordWatchAD() -// } - -func testRecordWatchAD() { - //RecordWatchAD("hell) +// 切服时记录数据 +func SwitchSever(uid int64, oldseverId, newseverId int32) (int64, resultStatus.ResultStatus) { + var userSeverInfoRecord *WxUserSeverInfo + if userSeverInfoRecord, _ = GetUserSeverInfoByUid(uid); userSeverInfoRecord == nil { + return 0, resultStatus.PlayerNotExist + } + if userSeverInfoRecord.SeverId != oldseverId { + return 0, resultStatus.PlayerNotMatchSever + } + RecordLoginOut(uid) + uidOfNewSever := CreatUid(userSeverInfoRecord.OpenId, newseverId) + go RecordLoginIn(uidOfNewSever, newseverId) + return uidOfNewSever, resultStatus.Success } diff --git a/trunk/center/usercenter/internal/wxuser/wechatsdkservice.go b/trunk/center/usercenter/internal/wxuser/wechatsdkservice.go index 5559b01..ed32055 100644 --- a/trunk/center/usercenter/internal/wxuser/wechatsdkservice.go +++ b/trunk/center/usercenter/internal/wxuser/wechatsdkservice.go @@ -17,7 +17,7 @@ type WechatsdkService struct { } // 通过code来换取assesstoken -func (w *WechatsdkService) GetAccessToken(code string) (resultStatus.ResultStatus, int64) { +func (w *WechatsdkService) GetAccessToken(code string, severId int32) (resultStatus.ResultStatus, int64) { weburl := fmt.Sprintf("%s%s", Url, "oauth2/access_token") //"https://api.weixin.qq.com/sns/oauth2/access_token" data := make(map[string]string) data["appid"] = configYaml.GetWxconfig().AppId @@ -49,7 +49,7 @@ func (w *WechatsdkService) GetAccessToken(code string) (resultStatus.ResultStatu w.GetUserInfo(tokens.AccessToken, tokens.OpenId) } }() - go RecordLoginIn(Uid) + go RecordLoginIn(Uid, severId) return resultStatus.Success, Uid }