Commit d4f07a25 authored by chenweijian's avatar chenweijian

user服

parents
Pipeline #1721 canceled with stages
MODE=local
.idea
/docs/
/protocol/biz/
/protocol/userProxy/
/protocol/video/
/protocol/userCenter/
\ No newline at end of file
swag:
swag init
proto:
protoc --go_out=./ --go-grpc_out=. ./protocol/*.proto
\ No newline at end of file
# hilo游戏中心
+ 接入SudMGP
# 项目架构
+ .env 项目环境配置,正服/测服会用服务器上的配置覆盖
+ run.sh 守护脚本
+ Makefile 构建文件
+ main.go 入口文件
+ *.ini 配置文件,测服用debug.ini,正服用release.ini
+ 目录分层说明(`着重注意命名规则`,下划线_分割职责)
+ test: 单元测试目录
+ _const: 常量
+ 子目录 enum,放业务定义常量
+ 命名规则 `*_e`,如game_e
+ 子目录 redis_key,放redis的key
+ 命名规则 `*_k`,如game_k
+ cron: 定时任务
+ 统一入口cron.go
+ 子目录命名规则 `*_cron`,如game_cron
+ mycontext: 上下文
+ myerr: 错误码
+ 子目录 bizerr,放业务错误
+ mylogrus: 日志包
+ req: 请求参数
+ request.go: 定义一些常用方法
+ 子目录jwt: 鉴权相关
+ 子目录
+ 命名规则 `*_req`,如game_req
+ resp: 返回结构体,通用结构体
+ cv: 客户端需要结构体
+ 子目录
+ 命名规则 `*_cv`,如game_cv
+ route: 路由定义
+ 根目录
+ errorHandler.go 错误处理
+ middleHandle.go 中间件
+ router.go 路由定义
+ util.go 工具包
+ 子目录,业务路由定义
+ 命名规则 `*_r`,如game_r
+ resource: 资源层
+ config: 配置相关
+ consul: 注册中心
+ mysql: 数据库
+ redisCli: 缓存
+ domain: 领域层
+ ctx.go: 定义ctxAndDb
+ model.go: 通用model
+ event.go: 抽象定义event
+ cache: 缓存层
+ moduleRedis.go 带有model的通用redis方法
+ 子目录
+ 命名规则 `*_c`,如user_c
+ event: 事件层
+ base.go: 定义事件base
+ 子目录
+ 命名规则 `*_ev`,如game_ev
+ model: 模型层
+ repo.go 持久化
+ 子目录
+ 命名规则 `*_m`,如game_m/user_m
+ service: 服务层,主要是开启事务和发事件
+ service.go: 事务/事件封装
+ 子目录
+ 命名规则 `*_s`,如game_s
# 交互流程
1. 给App一个登录获取code的接口
2. 给GameServer获取sstoken的接口
1. get_sstoken
2. update_sstoken
3. get_userinfo
3. 给GameServer同步游戏状态接口
1. report_game_info
\ No newline at end of file
package diamond_e
import "hilo-user/resource/mysql"
type StatusAccount = mysql.Type
const (
Normal StatusAccount = 1
//冻结,只是限制减少,不限制增加
Frozen StatusAccount = 2
)
type OperateType = mysql.Type
const (
//注册
/* REGISTER OperateType = 1
//建立融云会话
SessionPay OperateType = 2
//购买钻石
BuyDiamond OperateType = 3
//发送礼物
SendGift OperateType = 4
//接受礼物
ReceiveGift OperateType = 5
//匹配条件
MatchCondition OperateType = 6*/
SendGift OperateType = 1 //发送礼物
MatchCondition OperateType = 2 //匹配条件
SessionPay OperateType = 3 //建立融云会话
BuyDiamond OperateType = 4 //购买钻石
ReceiveGift OperateType = 5 //接受礼物
REGISTER OperateType = 6 //注册
MgrSend OperateType = 8 //平台赠送
VideoCost OperateType = 9 //1对1视频花费
MgrReduce OperateType = 10 //平台扣除
ActivityBillboard OperateType = 11 //活动榜单奖励
ExchangeBean OperateType = 12 //钻石兑换豆子
ActivityGroupBillboard OperateType = 13 //活动榜单奖励
DailyInAppVip OperateType = 14 //每日登陆领取钻石(VIP)
GroupIMMass OperateType = 15 //群中IM群发
DailyInAppCommon OperateType = 16 //每日登陆领取钻石(普通用户)
GroupSupportAdmin OperateType = 17 //群组支持(利益分配者)
GroupSupportMgr OperateType = 18 //群主支持(助手)
MgrBillDiamond OperateType = 19 //管理人单据送钻石
Headwear OperateType = 20 //送头饰扣费
Property OperateType = 21 //送坐骑扣费
LuckyWheelJoin OperateType = 22 //参与转盘扣费
LuckyWheelCancel OperateType = 23 //转盘取消
LuckyWheelWin OperateType = 24 //转盘奖励
LuckWheelGroupOwer OperateType = 25 //转盘群主抽成
DealerTransfer OperateType = 26 //币商转账
GroupCustomTheme OperateType = 27 //购买群组自定义主题
RocketAward OperateType = 28 //火箭奖励
LuckyboxBuy OperateType = 29 //幸运盒子购买
LuckyboxAward OperateType = 30 //幸运盒子奖励
PrivateGift OperateType = 31 // 私聊送礼物
PrivateGiftReturn OperateType = 32 // 私聊送礼物退款
ActivityTriggerAward OperateType = 33 //活动触发奖励
VideoTradeUnionGift OperateType = 34 // 视频送礼物
VideoTradeUnionGiftReturn OperateType = 35 // 视频送礼物退款
GlobalBroadcast OperateType = 36 //全球发布消息
TaskAward OperateType = 37 //任务奖励
FruitMachineAward OperateType = 38 // 水果机奖励
FruitMachineBet OperateType = 39 // 水果机投注
Noble OperateType = 40 //购买/赠送贵族
FruitTycoonAward OperateType = 41 // 水果大亨奖励
Checkout OperateType = 42 //checkout购买
LuckyboxCycle OperateType = 43 //幸运盒子回收奖励
ActivityRechargeFirst OperateType = 44 //首次充值奖励
NewUserInvite OperateType = 45 // 新用户奖励活动
GeneralActivity OperateType = 46 // 一般性活动奖励
PowerSupportOwner OperateType = 47 // 势力支持(势力主)
PowerSupportAssistant OperateType = 48 // 势力支持(助手)
VideoMinute OperateType = 49 //1对1视频(分钟扣费)
MatchMinute OperateType = 50 //匹配视频(第一分钟扣费)
VideoMinuteBack OperateType = 51 //1对1视频(分钟扣费,返回)
VideoMinuteTotal OperateType = 52 //1对1视频(分钟扣费,返回)
GroupActivity OperateType = 53 //创建群组活动
GroupActivityReward OperateType = 54 //群组活动奖励
PayerMax OperateType = 55 //payerMax/茄子支付购买
BuyPinkDiamond OperateType = 56 //购买粉钻
VideoCostPink OperateType = 57 //1对1视频送礼(粉钻)
MatchMinutePink OperateType = 58 //匹配视频(第一分钟扣费)(粉钻)
VideoMinuteTotalPink OperateType = 59 //1对1视频(分钟扣费,返回)(粉钻)
SendPinkGift OperateType = 60 //送粉钻礼物 ---占位---暂不开放 数据库配置表中暂未配置
MatchMinuteGiftPink OperateType = 61 //匹配视频送礼(粉钻)
MatchMinuteGiftPinkTime OperateType = 62 //匹配视频加时送礼(粉钻)
JoinGroupCost OperateType = 63 //加入群组扣费
JoinGroupAdd OperateType = 64 //加入群组,群主得黄钻
GameJoin OperateType = 65 //加入游戏扣费
GameAward OperateType = 66 //游戏结算奖励
GameRefund OperateType = 67 //游戏退费
)
const (
DiamondYellow mysql.Type = 1
DiamondPink mysql.Type = 2
)
package user_e
type ReportType string
type GameType int
type GameOpt int
type GameStatus uint8
type GamerStatus uint8
type GamerPlayerStatus uint8
type GameMode = int
const (
ReportTypeGameStart ReportType = "user_start" // 战斗开始通知
ReportTypeGameSettle ReportType = "user_settle" // 战斗结算通知
GameAutoMatchYes = 1 // 游戏自动匹配
GameAutoMatchNo = 0 // 游戏非自动匹配
GameTypeLudo GameType = 1 // ludo
GameTypeUno GameType = 2 // uno
GameModeQuick GameMode = 0
GameModeClassic GameMode = 1
GameOptCreate GameOpt = 0 // 创建游戏
GameOptJoin GameOpt = 1 // 加入游戏,自动准备
GameOptExit GameOpt = 2 // 退出游戏
GameOptClose GameOpt = 3 // 创建者关闭游戏
//GameOptReady GameOpt = 2 // 准备游戏
//GameOptCancelReady GameOpt = 3 // 取消准备游戏
GameStatusNoStart GameStatus = 0 // 未开始
GameStatusGaming GameStatus = 1 // 游戏中
GameStatusEnd GameStatus = 2 // 游戏结束
GamerStatusUnready GamerStatus = 0 // 未开始
GamerStatusGaming GamerStatus = 1 // 游戏中
GamerStatusEnd GamerStatus = 2 // 游戏结束
GamerExit GamerStatus = 3 // 自己退出游戏
GamerClose GamerStatus = 4 // 创建者关闭游戏
//GamerStatusReady GamerStatus = 1 // 准备
//GamerExit GamerStatus = 4 // 自己退出游戏
MgIdLudo = "1468180338417074177"
MgIdUno = "1472142559912517633"
)
package groupPower_e
import "hilo-user/resource/mysql"
// 国家势力状态
type GroupPowerStatus = mysql.Type
const (
//上架
GroupPowerUserHas GroupPowerStatus = 1
//未上架
GroupPowerUserNo GroupPowerStatus = 2
//解散
GroupPowerDissolve GroupPowerStatus = 3
)
// 国家势力用户角色
type GroupPowerUserRole = mysql.Type
const (
//普通用户
GroupPowerUserRoleUser GroupPowerUserRole = 1
//势力主
GroupPowerUserRoleMgr GroupPowerUserRole = 2
)
// 国家势力日志操作类型
type GroupPowerUserLogType = mysql.Type
const (
//加入
GroupPowerUserLogTypeUserJoin GroupPowerUserLogType = 1
//用户自己离开
GroupPowerUserLogTypeUserLeave GroupPowerUserLogType = 2
//运营平台赋予管理者
GroupPowerUserLogTypeOwerJoin GroupPowerUserLogType = 3
//管理人让用户离开
GroupPowerUserLogTypeMgrLeave GroupPowerUserLogType = 4
//管理人解散
GroupPowerUserLogDissolve GroupPowerUserLogType = 5
)
type GroupPowerDiamondLogType = mysql.Type
const (
//群组原因加入
GroupPowerDiamondLogTypeByGroup GroupPowerDiamondLogType = 1
//群主原因加入
GroupPowerDiamondLogTypeByGroupOwer GroupPowerDiamondLogType = 2
)
type PowerSupportAwardState = uint
const (
PowerSuppportNo PowerSupportAwardState = 0 // 未达到要求
PowerSuppportAwarded PowerSupportAwardState = 1 // 已经领取
PowerSuppportWaiting PowerSupportAwardState = 2 // 待领取
)
package group_e
import "hilo-user/resource/mysql"
// 信令消息(不显示公屏工,不记入消息历史,不影响未读数)
type TypeSignalMsg = mysql.Type
const (
GroupEditProfileSignal TypeSignalMsg = 1
GroupRoleChangeSignal TypeSignalMsg = 2
GroupMicChangeSignal TypeSignalMsg = 3 //保留
GroupMsgBannedSignal TypeSignalMsg = 4
GroupMemberRemoveSignal TypeSignalMsg = 5
GroupGiftSignal TypeSignalMsg = 6 //礼物
GroupMicInSignal TypeSignalMsg = 7
GroupMicOutSignal TypeSignalMsg = 8
GroupMicLockSignal TypeSignalMsg = 9
GroupMicUnLockSignal TypeSignalMsg = 10
GroupMicSpeechOpenSignal TypeSignalMsg = 11
GroupMicSpeechCloseSignal TypeSignalMsg = 12
GroupKickOut TypeSignalMsg = 13 //保留
GroupSocketMicOutSignal TypeSignalMsg = 14 //保留
GroupInviteMicInSignal TypeSignalMsg = 15 //邀请上麦保留
GroupInSignal TypeSignalMsg = 16 //进入房间
GroupMicEmoji TypeSignalMsg = 17 //麦上表情
GroupLuckyWheel TypeSignalMsg = 18 //转盘的通知信令
GroupOutSignal TypeSignalMsg = 19 //离开房间
GroupRocketState TypeSignalMsg = 20 //火箭状态变化
GroupOnlineUser TypeSignalMsg = 21 //房间在线用户信息
GroupMicChange TypeSignalMsg = 22 //房间麦位上的变量
GroupMemberInvite TypeSignalMsg = 23 //房间-邀请用户成为会员
GroupRoleChange TypeSignalMsg = 24 //房间-用户群组身份变化
GroupGameInfoLudo TypeSignalMsg = 200 // 房间-游戏信息-ludo
GroupGameSettleLudo TypeSignalMsg = 201 // 房间-游戏结算信息-ludo
GroupGameInfoUno TypeSignalMsg = 202 // 房间-游戏信息-uno
GroupGameSettleUno TypeSignalMsg = 203 // 房间-游戏结算信息-uno
)
// 群组麦位数量类型
type GroupMicNumType = mysql.Type
const (
OneMicNumType GroupMicNumType = 5
TwoMicNumType GroupMicNumType = 10
ThreeMicNumType GroupMicNumType = 3
FourMicNumType GroupMicNumType = 4
SixMicNumType GroupMicNumType = 6
SevenMicNumType GroupMicNumType = 7
EightMicNumType GroupMicNumType = 8
NineMicNumType GroupMicNumType = 9
ElevenMicNumType GroupMicNumType = 11
TwelveMicNumType GroupMicNumType = 12
ThirteenMicNumType GroupMicNumType = 13
FourteenMicNumType GroupMicNumType = 14
FifteenMicNumType GroupMicNumType = 15
SixteenMicNumType GroupMicNumType = 16
SeventeenMicNumType GroupMicNumType = 17
EighteenMicNumType GroupMicNumType = 18
NineteenMicNumType GroupMicNumType = 19
TwentyMicNumType GroupMicNumType = 20
//5个麦位
FiveMicNumType GroupMicNumType = 1
//10个麦位
TenMicNumType GroupMicNumType = 2
//
)
// 公屏消息
type TypePublicScreenMsg = mysql.Type
const (
UserJoinPublicScreenMsg TypePublicScreenMsg = 1 // 加入群组
UserKickPublicScreenMsg TypePublicScreenMsg = 2 // 踢出房间
UserBannedPublicScreenMsg TypePublicScreenMsg = 3 // 拉黑用户
RoleAssignedPublicScreenMsg TypePublicScreenMsg = 4 // 添加角色
RoleRemovedPublicScreenMsg TypePublicScreenMsg = 5 // 移除角色
ClientSendMsgLocal TypePublicScreenMsg = 6 // 客户端占用
RollDiceMsg TypePublicScreenMsg = 7 // 掷骰子结果
GroupGiftMsg TypePublicScreenMsg = 8 //全服礼物
GroupSupportH5 TypePublicScreenMsg = 9 //群组支持H5
JumpMessage TypePublicScreenMsg = 10 // 可跳转的公屏消息
RocketAwardMsg TypePublicScreenMsg = 11 // 火箭获奖消息
LockyboxAwardMsg TypePublicScreenMsg = 12 // 幸运盒子公屏中奖
FruitMachineAwardMsg TypePublicScreenMsg = 13 // 水果机中奖
EnterRoomMsg TypePublicScreenMsg = 14 // 用户进入房间
GameLudoPubMsg TypePublicScreenMsg = 101 // Ludo游戏公屏
GameUnoPubMsg TypePublicScreenMsg = 102 // uno游戏公屏
)
package match_e
import "hilo-user/resource/mysql"
/****************************/
type MatchDetailDataChange mysql.NumAll
const (
AddDataChange MatchDetailDataChange = 1
ReduceDataChange MatchDetailDataChange = 2
ZeroDataChange MatchDetailDataChange = 0
)
/******元素分数***************/
/*type EnumCountType mysql.Type
const (
PriorityEnumCountType EnumCountType = 1
ExcellentEnumCountType EnumCountType = 2
RelationEnumCountType EnumCountType = 3
)*/
/*const (
//接收到礼物的数量
ReceiveGiftNumCountType EnumCountType = 1
//接收到礼物的频率
ReceiveGiftFCountType EnumCountType = 2
//被举报
BeReportCountType EnumCountType = 3
//被拉黑
BeBlackCountType EnumCountType = 4
//支付次数
PayCountType EnumCountType = 5
//被喜欢的次数
LikeMeType EnumCountType = 6
//新用户
NewUserType EnumCountType = 7
//连续被拒
AgainRefuse EnumCountType = 8
//每天新登陆用户
DailyUserType EnumCountType = 9
//活跃等级
ActiveGrade EnumCountType = 10
//连续同性
AgainSameSex EnumCountType = 11
//匹配推送次数
MatchSuccessNum EnumCountType = 12
//匹配推送接受次数
MatchConfirmNum EnumCountType = 13
//接通率
MatchConfirmRate EnumCountType = 14
//免费加时
MatchFreeTimeNum EnumCountType = 15
//礼物加时
MatchGiftTimeNum EnumCountType = 16
//加时率
MatchAddTimeRate EnumCountType = 17
//工会成员
TradeUnion EnumCountType = 18
//匹配拒绝
RelationMatchRefuse EnumCountType = 19
//匹配用户
RelationMatchUser EnumCountType = 20
)*/
// 优先度排序
type PriorityUserFormOriginType mysql.Type
const (
//钻石余额
PriorityDiamond PriorityUserFormOriginType = 1
//首充
PriorityFirstCharge PriorityUserFormOriginType = 2
//财富等级
PriorityWealthGrade PriorityUserFormOriginType = 3
//被举报
PriorityBeReport PriorityUserFormOriginType = 4
//被拉黑
PriorityBeBlack PriorityUserFormOriginType = 5
//近期充值
PriorityRecentlyPay PriorityUserFormOriginType = 6
//新用户
PriorityNewUserType PriorityUserFormOriginType = 7
//连续被拒
PriorityAgainRefuse PriorityUserFormOriginType = 8
//每天新登陆用户
PriorityDailyUserType PriorityUserFormOriginType = 9
//活跃等级
PriorityActiveGrade PriorityUserFormOriginType = 10
//工会
PriorityTradeUnion PriorityUserFormOriginType = 11
//多次匹配到同性
PriorityAgainSameSex PriorityUserFormOriginType = 12
)
var PriorityMap = map[PriorityUserFormOriginType]string{
PriorityDiamond: "钻石余额",
PriorityFirstCharge: "首充",
PriorityWealthGrade: "财富等级",
PriorityBeReport: "被举报",
PriorityBeBlack: "被拉黑",
PriorityRecentlyPay: "近期充值",
PriorityNewUserType: "新用户",
PriorityAgainRefuse: "连续被拒",
PriorityDailyUserType: "每天新登陆用户",
PriorityActiveGrade: "活跃等级",
PriorityTradeUnion: "工会",
PriorityAgainSameSex: "连续同性",
}
// 质量排序
type ExcellentUserFormOriginType mysql.Type
const (
//被喜欢的次数
ExcellentLikeMe ExcellentUserFormOriginType = 1
//接收礼物数量
ExcellentReceiveGiftNumChange ExcellentUserFormOriginType = 2
//接收礼物次数
ExcellentReceiveGiftFChange ExcellentUserFormOriginType = 3
//被投诉次数
ExcellentBeReport ExcellentUserFormOriginType = 4
//被拉黑的次数
ExcellentBeBlack ExcellentUserFormOriginType = 5
//魅力等级
ExcellentCharmGrade ExcellentUserFormOriginType = 6
//被付费建立融云会话
ExcellentSessionCreate ExcellentUserFormOriginType = 7
//接通率
ExcellentMatchConfirmRate ExcellentUserFormOriginType = 8
//加时率(视频通话加时次数(免费加时成功 + 礼物加时成功)/视频通话次数(匹配成功的次数))
ExcellentAddTimeRate ExcellentUserFormOriginType = 9
//工会成员
ExcellentTradeUnion ExcellentUserFormOriginType = 10
//匹配成功次数
ExcellentMatchSuccessNum ExcellentUserFormOriginType = 11
//匹配确认次数
ExcellentMatchConfirmNum ExcellentUserFormOriginType = 12
//匹配免费加时的次数
ExcellentMatchFreeTimeNum ExcellentUserFormOriginType = 13
//匹配礼物加时次数
ExcellentMatchGiftTimeNum ExcellentUserFormOriginType = 14
)
var ExcellentMap = map[ExcellentUserFormOriginType]string{
ExcellentLikeMe: "喜欢我",
ExcellentReceiveGiftNumChange: "接收礼物数量",
ExcellentReceiveGiftFChange: "接收礼物次数",
ExcellentBeReport: "被投诉次数",
ExcellentBeBlack: "被拉黑次数",
ExcellentCharmGrade: "魅力等级",
ExcellentSessionCreate: "被付费建立融云会话",
ExcellentMatchConfirmRate: "接通率",
ExcellentAddTimeRate: "加时率",
ExcellentTradeUnion: "工会成员",
ExcellentMatchSuccessNum: "匹配成功次数",
ExcellentMatchConfirmNum: "匹配确认次数",
ExcellentMatchFreeTimeNum: "匹配免费加时次数",
ExcellentMatchGiftTimeNum: "匹配礼物加时次数",
}
// 关系排序
type RelationUserFormOriginType mysql.Type
const (
//喜欢的用户
RelationUserLike RelationUserFormOriginType = 1
//匹配拒绝用户
RelationMatchRefuse RelationUserFormOriginType = 2
//匹配过的用户
RelationMatchUser RelationUserFormOriginType = 3
//付费发送过消息但没进行过视频聊天
RelationSessionNoMatchConfirm RelationUserFormOriginType = 4
//上一次匹配的用户
RelationMatchUserLastTime RelationUserFormOriginType = 5
)
var RelationMap = map[RelationUserFormOriginType]string{
RelationUserLike: "喜欢的用户",
RelationMatchRefuse: "匹配被拒绝",
RelationMatchUser: "匹配上的用户",
RelationSessionNoMatchConfirm: "付费发送过消息但没进行过视频聊天",
RelationMatchUserLastTime: "上一次匹配的用户",
}
/******匹配确认***********************************/
type MatchConfirmStatus mysql.Type
const (
Success MatchConfirmStatus = 1
Fail MatchConfirmStatus = 2
Temp MatchConfirmStatus = 3
)
type MatchConfirmUserAcceptRefuse mysql.Type
const (
AcceptMatchConfirmUser MatchConfirmUserAcceptRefuse = 1
RefuseMatchConfirmUser MatchConfirmUserAcceptRefuse = 2
)
type MatchCharmUserScoreDetailType = mysql.Type
const (
GiftReceiveCharmType MatchCharmUserScoreDetailType = 1
GiftReceiveVipCharmType MatchCharmUserScoreDetailType = 2
)
type MatchWealthUserScoreDetailType = mysql.Type
const (
GiftReceiveWealthType MatchWealthUserScoreDetailType = 1
GiftReceiveVipWealthType MatchWealthUserScoreDetailType = 2
)
package msg_e
// 公屏消息
type TypePublicScreenMsg uint8
const (
UserJoinPublicScreenMsg TypePublicScreenMsg = 1 // 加入群组
UserKickPublicScreenMsg TypePublicScreenMsg = 2 // 踢出房间
UserBannedPublicScreenMsg TypePublicScreenMsg = 3 // 拉黑用户
RoleAssignedPublicScreenMsg TypePublicScreenMsg = 4 // 添加角色
RoleRemovedPublicScreenMsg TypePublicScreenMsg = 5 // 移除角色
ClientSendMsgLocal TypePublicScreenMsg = 6 // 客户端占用
RollDiceMsg TypePublicScreenMsg = 7 // 掷骰子结果
GroupGiftMsg TypePublicScreenMsg = 8 //全服礼物
GroupSupportH5 TypePublicScreenMsg = 9 //群组支持H5
JumpMessage TypePublicScreenMsg = 10 // 可跳转的公屏消息
RocketAwardMsg TypePublicScreenMsg = 11 // 火箭获奖消息
LockyboxAwardMsg TypePublicScreenMsg = 12 // 幸运盒子公屏中奖
FruitMachineAwardMsg TypePublicScreenMsg = 13 // 水果机中奖
EnterRoomMsg TypePublicScreenMsg = 14 // 用户进入房间
)
package res_e
import "hilo-user/resource/mysql"
type MsgIdType = uint
const (
DEFAULT_LANG = "en"
MSG_ID_GAME_CREATE MsgIdType = 1001
MSG_ID_GAME_JOIN MsgIdType = 1002
)
type ResMedalType = mysql.Type
const (
Wealth ResMedalType = 1
Charm ResMedalType = 2
LoveForAll ResMedalType = 3
StartForAll ResMedalType = 4
MoonForAll ResMedalType = 5
MarryMe ResMedalType = 6
RoomRocket ResMedalType = 7
Actity ResMedalType = 8
FruitKing ResMedalType = 9
BoxKing ResMedalType = 10
Helicopter ResMedalType = 11
Roadster ResMedalType = 12
Watermelon ResMedalType = 13
Kiss ResMedalType = 14
Love ResMedalType = 15
Chick ResMedalType = 16
SportsCar ResMedalType = 17
Rocket ResMedalType = 18
Tower ResMedalType = 19
Eagle ResMedalType = 20
Lion ResMedalType = 21
Vacation ResMedalType = 22
RomanticNight ResMedalType = 23
SweetCouple ResMedalType = 24
Castle ResMedalType = 25
WeddingCar ResMedalType = 26
VideoChat ResMedalType = 27
)
type ResMedalScope = mysql.Type
const (
//私有,只能时管理人发放
Private ResMedalScope = 1
//公有,自己获取
Public ResMedalScope = 2
)
type ResNameplateType = mysql.Type
type ResNameplateObtainType = mysql.Type
type ResNameplateScope = mysql.Type
package user_e
import "hilo-user/resource/mysql"
type ThirdPartyType = mysql.Type
const (
Phone ThirdPartyType = 1
Google ThirdPartyType = 2
Facebook ThirdPartyType = 3
Apple ThirdPartyType = 4
WeChat ThirdPartyType = 5
)
type UserStatus mysql.Type
const (
//正常
UserStatusNormal UserStatus = 1
//冻结
UserStatusFrozen UserStatus = 2
)
type UserVipType = mysql.Type
const (
// 男
UserMan = 1
// 女
UserWomen = 2
)
package _const
const (
TRACEID = "traceId"
USERID = "userId"
EXTERNAL_ID = "externalId"
CODE = "code"
NICK = "nick"
AVATAR = "avatar"
COUNTRY = "country"
EXTERNALID1 = "externalId1"
EXTERNALID2 = "externalId2"
MGRID = "mgrId"
DEVICETYPE = "deviceType"
DEVICEVERSION = "deviceVersion"
APP_VERSION = "appVersion"
ACTION_RESULt = "actionResult"
URL = "url"
METHOD = "method"
IMEI = "imei"
LANGUAGE = "language"
)
package user_k
import (
"fmt"
)
const (
GameAdd = "user:add:%s"
GameLock = "user:lock:%s"
)
func GetGameAddKey(txGroupId string) string {
return fmt.Sprintf(GameAdd, txGroupId)
}
package redis_key
import (
"fmt"
"os"
)
// 替换keyFmt中的${var}变量s
// param keyFmt 如 a_b_${var1}_${var2}
// param arg 是个数组,按照下标替换${var},越界就用var1字样
// return a_b_var1_var2
func ReplaceKey(keyFmt string, arg ...string) string {
n := len(arg)
if n <= 0 {
return keyFmt
}
var i int
return os.Expand(keyFmt, func(s string) (r string) {
if i >= n {
return s
}
r = arg[i]
i++
return
})
}
func GetUserMedalMerge(userId uint64) string {
return fmt.Sprintf(UserMedalMerge, userId)
}
package redis_key
const (
// 用户
UserMedalMerge = "user:medalMerge:%d" // 勋章
)
package robot_k
import "fmt"
const (
RobotGroupCd = "robot:group:cd:%s"
)
// 机器人上群cd
func GetRobotGroupCdKey(txGroupId string) string {
return fmt.Sprintf(RobotGroupCd, txGroupId)
}
package user_k
import (
"fmt"
"hilo-user/_const/redis_key"
"hilo-user/resource/mysql"
)
const (
UserPrefix = "user:"
UserTinyInfoStr = UserPrefix + "tiny:${userId}" // value:json(cv.CvUserTiny)
UserExternalIdToUIdStr = UserPrefix + "externalId:${externalId}" // value:userId
UserCodeToUIdStr = UserPrefix + "code:${externalId}" // value:userId
UserTradeUnionIds = "user:trade:union:ids" // type:string,value json(uids)
)
// 获取用户简要信息缓存key
func GetUserTinyKey(userId mysql.ID) string {
return redis_key.ReplaceKey(UserTinyInfoStr, fmt.Sprintf("%d", userId))
}
// 获取externalId到userId
func GetExternalIdToUidKey(externalId mysql.Str) string {
return redis_key.ReplaceKey(UserExternalIdToUIdStr, externalId)
}
// 获取code到userId
func GetCodeToUidKey(code mysql.Str) string {
return redis_key.ReplaceKey(UserCodeToUIdStr, code)
}
package _const
import (
"encoding/json"
"fmt"
"hilo-user/mylogrus"
"hilo-user/resource/config"
"net"
"runtime/debug"
"strings"
"time"
)
const DEFAULT_LANG = "en"
const DATETIME_FORMAT = "2006-01-02 15:04:05"
const DATE_FORMAT = "2006-01-02"
const DefaultAvatarMan = "hilo/manager/ea48b62d54a24a709de3c38702c89995.png"
func GetZeroTime(t time.Time) time.Time {
return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
}
// 取最近的一个星期n
func GetLastDayOfWeek(t time.Time, n time.Weekday) time.Time {
weekDay := t.Weekday()
// 校正日期
if weekDay < n {
weekDay = 7 - n + weekDay
} else {
weekDay = weekDay - n
}
return t.AddDate(0, 0, -(int(weekDay)))
}
// 补全url,区分处理oss和aws两种情况
func MakeFullUrl(url string) string {
if strings.HasPrefix(url, config.GetConfigOss().OSS_CDN) || strings.HasPrefix(url, config.GetConfigAws().AWS_CDN) {
return url
} else if strings.HasPrefix(url, "nextvideo/") {
return config.GetConfigOss().OSS_CDN + url
} else if strings.HasPrefix(url, config.GetConfigAws().AWS_DIR) {
return config.GetConfigAws().AWS_CDN + url
} else {
return url
}
}
// 去除slice中的重复元素
func UniqueSliceUInt64(sliceIn []uint64) []uint64 {
sliceOut := make([]uint64, 0, len(sliceIn))
m := make(map[uint64]struct{}, len(sliceIn))
for _, i := range sliceIn {
if _, ok := m[i]; !ok {
m[i] = struct{}{}
sliceOut = append(sliceOut, i)
}
}
return sliceOut
}
func ToString(s interface{}) (string, error) {
b, err := json.Marshal(s)
if err != nil {
return "", nil
}
return string(b), nil
}
func SliceToMapUInt64(s []uint64) map[uint64]struct{} {
m := make(map[uint64]struct{}, len(s))
for _, i := range s {
m[i] = struct{}{}
}
return m
}
func GetClientIp() (string, error) {
addrs, err := net.InterfaceAddrs()
if err != nil {
return "", err
}
for _, address := range addrs {
// 检查ip地址判断是否回环地址
if ipNet, ok := address.(*net.IPNet); ok && ipNet.IP.IsGlobalUnicast() {
//if ipNet, ok := address.(*net.IPNet); ok && !ipNet.IP.IsLoopback() {
if ipNet.IP.To4() != nil {
return ipNet.IP.String(), nil
}
}
}
return "", fmt.Errorf("can not find the client ip address")
}
func CheckGoPanic() {
if r := recover(); r != nil {
//打印错误堆栈信息
mylogrus.MyLog.Errorf("ACTION PANIC: %v, stack: %v", r, string(debug.Stack()))
}
}
[DATABASE]
MYSQL_HOST=47.244.34.27:3306
MYSQL_USERNAME=root
MYSQL_PASSWORD=yX0jPAhO0I4s2zlA
MYSQL_DB=hilo
[DATABASECODE]
MYSQL_HOST=47.244.34.27:3306
MYSQL_USERNAME=root
MYSQL_PASSWORD=yX0jPAhO0I4s2zlA
MYSQL_DB=hilo_code
[REDIS]
REDIS_HOST=47.244.34.27:6379
REDIS_PASSWORD=8QZ9JD1zLvPR3yHf
[JWT]
SECRET=hilo1632
ISSUER_API=hiloApi
ISSUER_Mgr=hiloMgr
EXPIRE=240h
[GAMEJWT]
SECRET=hilo1632
ISSUER_CLIENT=hiloClient
ISSUER_SERVER=hiloServer
EXPIRE=240h
[APP]
MASTER=true
BIZ_SECRET=biz
WEB_SECRET=webHilo1258
OPERATION_SECRET=operation1258236
SUPERUSER=2701,2831
OFFICIAL_STAFF=2701,2831,3411,2511
OFFICIAL_GROUP=@TGS#3FDW3MPHZ
MINIMAL_VERSION_ANDROID=22001
MINIMAL_VERSION_IOS=22000
MODERATE=TENCENT
[OSS]
OSS_ACCESS_KEY_ID=LTAIxdazV2pCuV3T
OSS_ACCESS_KEY_SECRET=zuAnreAXQ6vlAKnvvmolFLfb1N5w5S
OSS_ROLE_ARN=acs:ram::1509841556585969:role/aliyunosstokengeneratorrole
OSS_END_POINT=http://oss-accelerate.aliyuncs.com
OSS_BUCKET=starvoice
OSS_CDN=https://oss.chathot.me/
OSS_EXPIRED_TIME=3600
OSS_STS_POINT=me-east-1
OSS_STS=sts-faceline-demo
OSS_STS_AES=484194d4d0f968a7
[AWS]
AWS_BUCKET=starchat
AWS_CDN=https://image.whoisamy.shop/
AWS_DIR=hilo/
CONFIDENCE=80
[RONGYUN]
RONG_CLOUD_APP_KEY=pvxdm17jpe9tr
RONG_CLOUD_APP_SECRET=rI4giiKWaBS4
RONG_CLOUD_URL=https://api-sg01.ronghub.com
[TENCENTYUN]
TENCENTYUN_APP_ID=1400548270
TENCENTYUN_KEY=321bd60f73096b059c7350f1cd97d51028850b34fa58c5c0d26bb4a19e783de8
TX_OVERSEA_APP_ID=40000066
TX_OVERSEA_KEY=3ab68ea5bddc8774d90b8c764ae71188914bd5fd06f30b28790c51e44ca7885c
[EMAS]
REGION_ID=cn-hangzhou
ACCESS_KEY_ID=LTAIdQZv5H1kNZp5
ACCESS_KEY_SECRET=UnwY0ClDkqBMLwPx3OJJiLYyk9xYLO
ANDROID_APP_KEY=30250713
ANDROID_APP_SECRET=cae7b9a9d3e54577d2c3b60bf6d23047
IOS_APP_KEY=30790728
IOS_APP_SECRET=4fd69ca084c67d4b5a8d15452f0af26a
APNS=DEV
[AGORA]
APP_ID=fc3e087f701b4f788099e1924c3cc7b0
APP_CERTIFICATE=ff29c100a613433db82324e8411eabc8
CUSTOMER_KEY=6b132c0ff7164560a2bc53fda06ea85a
CUSTOMER_SECRET=eedad2cd16d24834990d5450ace9f1ce
CALLBACK_SECRET=n_ZizS_N8
[CHECKOUT]
AUTHORIZATION=sk_test_9b5e771c-5a3f-4a8d-a4da-31b19bd43d83
URL=https://api.sandbox.checkout.com/hosted-payments
H5=http://test.chathot.me/action/hiloHtml/22_05_30_recharge/topup.html
HILO_SECRET_KEY=sk_test_dfbaa3b6-135d-4376-9996-2089b7d8a086
[MATCH]
MATCH_FREE_TIME=60
MATCH_FREE_TIME_VIP=60
MATCH_ADD_TIME_FREE=90
MATCH_AGORA_TIME=30
MATCH_CYCLE=8
MATCH_USER_EXPIRES=480
MATCH_SUCCESS_WAIT_DURATION=10
MATCH_SUCCESS_SINGLE_WAIT_TIME_IN_SEC=12
MATCH_SUCCESS_DUAL_WAIT_TIME_IN_SEC=15
[ONLINE]
ONLINE_CYCLE=600
ONLINE_USER_EXPIRES=259200
[VIDEO]
VIDEO_DAILY_FREE_NUM=20
VIDEO_FREE_TIME=60
VIDEO_FREE_TIME_VIP=300
VIDEO_ADD_TIME_FREE=60
VIDEO_AGORA_TIME=30
VIDEO_MINUTE_NORMAL=30
VIDEO_MINUTE_UNION=30
[SESSION]
SESSION_DAILY_FREE_NUM=50
GUILD_USER_HELLO_DAY=30
[BEAN]
DIAMOND_BEAN_RATE=90
[H5]
USER_LEVEL=http://test.chathot.me/action/hiloHtml/hiloUserLevel/index.html
GROUP_SUPPORT=http://test.chathot.me/action/activityhtml/21_12_06/page.html
LUCKY_WHEEL=https://h5.whoisamy.shop/action/activityhtml/21_12_30/page.html
WEEKLY_STAR=http://test.chathot.me/action/hiloHtml/lxt_h5/page.html
WEEKLY_CP=https://test.chathot.me/action/hiloHtml/Valentines_22_1_18/page.html
COUNTRY_STAR=https://test.chathot.me/action/hiloHtml/22_08_18_nation_star/page.html
NOBLE_BUY_IOS=https://h5.whoisamy.shop/action/hiloHtml/22_05_26_buy_nobility/page.html
GUILD_DATA_URL=https://test.chathot.me/action/hiloHtml/22_10_18_app_data_coins/index.html
MGR_GUILD_DATA_URL=https://test.chathot.me/action/hiloHtml/22_10_18_app_data_coins/union.html
RANKING_PINK_DIAMOND_URL=https://test.chathot.me/action/activitiesPage/2022_10_17HiloLiveH5/index.html
[GROUPIM]
MSG_SORT_EXPIRE=21600
MSG_SORT_SNAP=300
MSG_PARALLEL_SIZE=10
[GRADE]
CHARM_SPEED_VIP=15
ACTITY_SPEED_VIP=15
WEALTH_SPEED_VIP=15
[LIKE]
I_LIKE_NUM=30
I_LIKE_NUM_VIP=300
I_LIKE_NUM_NOBLE=1000
[APPLEPAY]
PASSWORD=38702750a05c4cb09c9d6ca646835634
[REGISTER]
IMEI_TOTAL=3
IMEI_OAUTH=2
ACCOUNT_IP=100
ACCOUNT_IP_DURATION=21600
[BANNER]
GIFT_BANNER_LEVEL1=500
GIFT_BANNER_LEVEL2=2000
GIFT_BANNER_LEVEL3=5000
[DIAMOND]
DAILY_LOGIN_IMEI_LIMIT=2
DAILY_LOGIN_IP_LIMIT=5
PRIVATE_GIFT_RETURN=1440
[LUCKY_WHEEL]
MINIMAL_PARTICIPANT=2
WAIT_TIMELONG=10
WINNER_DIAMOND_BANNER=10
[GROUP_CUSTOM_THEME]
PIC_LIMIT=5
DAY=10
[GIFT]
WALL_DIAMOND=10
[DAILY]
LOGIN_COMMON=5
LOGIN_VIP=300
[FRUIT_TYCOON]
POOL_RATIO=80
WATERMELON_RATIO=24
[RISK_CONTROL]
USER_QPS_LIMIT=60
[PAYER_MAX]
URL=https://pay-gate-uat.payermax.com/aggregate-pay-gate/api/gateway
KEY=d50d149a883b8bb6
MERCHANT_ID=SP11018326
BIZ_TYPE=CUSTOMIZE
VERSION=2.3
FRONT_CALLBACK_URL=https://www.hiloconn.com
SHOW_RESULT=1
EXPIRE_TIME=1800
LANGUAGE=en
[SUD]
API_LIST=https://sim-asc.sudden.ltd
[URL]
BIZ_HTTP=https://test.apiv1.faceline.live
\ No newline at end of file
package cache
import (
"encoding/json"
"hilo-user/domain"
"math/rand"
"time"
)
func init() {
rand.Seed(time.Now().UnixNano())
}
const (
DEFAULT_TTL = time.Hour
)
// 获取默认随机过期ttl
func GetDefaultTTL() time.Duration {
return time.Duration(rand.Intn(5))*time.Second + DEFAULT_TTL
}
// 设置redis json结构体
func SetJSON(model *domain.Model, key string, obj interface{}) (err error) {
defer model.Log.Infof("SetJson key:%v,obj:%v,err:%v", key, obj, err)
value, err := json.Marshal(obj)
if err != nil {
return err
}
return model.Redis.Set(model.Context, key, value, DEFAULT_TTL).Err()
}
// 获取redis中json结构体
// return err: may redisV8.Nil
func GetJSON(model *domain.Model, key string, obj interface{}) (err error) {
var value []byte
defer func() {
model.Log.Infof("GetJson key:%v,value:%s,obj:%v,err:%v", key, value, obj, err)
}()
value, err = model.Redis.Get(model.Context, key).Bytes()
if err != nil {
// may redisV2.Nil
return
}
err = json.Unmarshal(value, &obj)
if err != nil {
return err
}
return nil
}
// 尝试指定时间内获取锁
// acquireTimeout: 尝试获取锁的时间
// expireTimeout: 上锁的时间
// return: true上锁成功 false失败
func TryLock(model *domain.Model, key string, acquireTimeout, expireTimeout time.Duration) bool {
deadline := time.Now().Add(acquireTimeout)
for {
if time.Now().After(deadline) {
return false
}
flag, err := model.Redis.SetNX(model, key, "1", expireTimeout).Result()
if err != nil || !flag {
time.Sleep(time.Millisecond * 10)
} else {
return true
}
}
}
// 解锁
func UnLock(model *domain.Model, key string) {
model.Redis.Del(model, key)
}
package user_c
import (
redisV8 "github.com/go-redis/redis/v8"
"github.com/jinzhu/copier"
"hilo-user/_const/redis_key/user_k"
"hilo-user/domain"
"hilo-user/domain/cache"
"hilo-user/domain/model/user_m"
"hilo-user/myerr"
"hilo-user/myerr/bizerr"
"hilo-user/resource/mysql"
)
// 获取用户简要信息
// param userId 用户id
func GetUserTinyById(model *domain.Model, userId mysql.ID) (*user_m.UserTiny, error) {
userTiny := new(user_m.UserTiny)
key := user_k.GetUserTinyKey(userId)
err := cache.GetJSON(model, key, userTiny)
if err != nil && err != redisV8.Nil {
return nil, err
}
if err == redisV8.Nil {
// cache miss,get from mysql
if user, err := user_m.GetUser(model, userId); err != nil {
return nil, err
} else {
copier.Copy(userTiny, user)
err = cacheUserTiny(model, user)
if err != nil {
return nil, err
}
}
}
return userTiny, nil
}
// 获取用户简要信息By ExternalId
func GetUserByExternalId(model *domain.Model, externalId mysql.Str) (*user_m.UserTiny, error) {
userId, err := ToUserId(model, externalId)
if err != nil {
return nil, err
}
return GetUserTinyById(model, userId)
}
// externalId 到 userId
func ToUserId(model *domain.Model, externalId mysql.Str) (mysql.ID, error) {
if externalId == "" {
return 0, myerr.NewSysError("externalId 不能为空")
}
userId, err := model.Redis.Get(model.Context, user_k.GetExternalIdToUidKey(externalId)).Int64()
if err != nil && err != redisV8.Nil {
return 0, err
}
// cache miss,sync from db
if err == redisV8.Nil {
if user, err := user_m.GetUserByExtId(model, externalId); err != nil {
return 0, bizerr.ExternalIdNoExist
} else {
if err = cacheUserTiny(model, user); err != nil {
return 0, err
}
userId = int64(user.ID)
}
}
return mysql.ID(userId), nil
}
// code 到 userId
func ToUserIdByCode(model *domain.Model, code mysql.Str) (mysql.ID, error) {
if code == "" {
return 0, myerr.NewSysError("code 不能为空")
}
userId, err := model.Redis.Get(model.Context, user_k.GetCodeToUidKey(code)).Int64()
if err != nil && err != redisV8.Nil {
return 0, err
}
// cache miss,sync from db
if err == redisV8.Nil {
if user, err := user_m.GetUserByCode(model, code); err != nil {
return 0, bizerr.CodeNoExist
} else {
if err = cacheUserTiny(model, user); err != nil {
return 0, err
}
userId = int64(user.ID)
}
}
return mysql.ID(userId), nil
}
// 缓存userTiny
// 顺手缓存externalId->userId
// 顺手缓存code->userId
// param user: 已经在上层获取的db User结构
func cacheUserTiny(model *domain.Model, user *user_m.User) error {
userTiny := new(user_m.UserTiny)
if err := copier.Copy(userTiny, user); err != nil {
return err
}
// cache externalId->userId
if err := model.Redis.SetEX(model.Context, user_k.GetExternalIdToUidKey(user.ExternalId), user.ID, cache.GetDefaultTTL()).Err(); err != nil {
return err
}
// cache code->userId
if err := model.Redis.SetEX(model.Context, user_k.GetCodeToUidKey(user.Code), user.ID, cache.GetDefaultTTL()).Err(); err != nil {
return err
}
if err := cache.SetJSON(model, user_k.GetUserTinyKey(user.ID), userTiny); err != nil {
return err
}
return nil
}
package domain
import (
"github.com/go-redis/redis/v8"
"gorm.io/gorm"
"hilo-user/mycontext"
)
type CtxAndDb struct {
Db *gorm.DB
*mycontext.MyContext
Redis *redis.Client
}
package domain
//异步执行的接口
type AsyncEvent interface {
AsyncDo(model *Model, eventData interface{}, n int) error
AsyncSize() int
AsyncNoTxDo(model *Model, eventData interface{}, n int) error
AsyncNoTxSize() int
}
package event
import "hilo-user/domain"
// 程序内部事件
type Base struct {
//同步执行
SyncList []func(model *domain.Model, event interface{}) error
//异步执行
AsyncList []func(model *domain.Model, event interface{}) error
}
package user_ev
import (
"hilo-user/_const"
"hilo-user/domain"
"hilo-user/domain/event"
"hilo-user/resource/mysql"
)
//注册监听
var userEditEvent = new(event.Base)
/**
注册事件
*/
type GameEditEvent struct {
GameId mysql.ID `json:"userId"` // 游戏id
TxGroupId mysql.Str `json:"txGroupId"` // 群组id
}
//添加领域事件,在每个领域模型中init中添加,因为这是静态业务,非动态的。
func AddGameEditEventSync(callback func(model *domain.Model, event interface{}) error) {
userEditEvent.SyncList = append(userEditEvent.SyncList, callback)
}
//加入到异步操作中
func AddGameEditEventAsync(callback func(model *domain.Model, event interface{}) error) {
userEditEvent.AsyncList = append(userEditEvent.AsyncList, callback)
}
//领域事件发布
func PublishGameEditEvent(model *domain.Model, event interface{}) error {
//执行同步的领域事件
for _, callback := range userEditEvent.SyncList {
if err := callback(model, event); err != nil {
return err
}
}
// 执行异步的领域事件
if len(userEditEvent.AsyncList) > 0 {
go func() {
defer _const.CheckGoPanic()
for _, callback := range userEditEvent.AsyncList {
// 异步事件需要用新model,主要是db
var newModel = domain.CreateModelContext(model.MyContext)
if err := callback(newModel, event); err != nil {
model.Log.Errorf("userEditEvent aysnc fail:%v", err)
}
}
}()
}
return nil
}
package user_ev
import (
"hilo-user/_const"
"hilo-user/_const/enum/user_e"
"hilo-user/domain"
"hilo-user/domain/event"
"hilo-user/resource/mysql"
)
//注册监听
var reportGameInfoEvent = new(event.Base)
/**
注册事件
*/
type ReportGameInfoEvent struct {
UserId mysql.ID `json:"uid"` // 用户id,请求get_user_info 接口返回uid参数
ReportType user_e.ReportType `json:"report_type"` // 上报类型 user_start|user_settle
GameStartObject *GameStartObject `json:"user_start_object"` // user_start对应结构体
GameSettleObject *GameSettleObject `json:"user_settle_object"` // user_settle对应结构体
}
type GameStartObject struct {
MgId uint64 `json:"mg_id"` // 游戏id
MgIdStr string `json:"mg_id_str"` // 小游戏id数值型兼容字段(nodejs服务请使用当前字段)
RoomId string `json:"room_id"` // 接入方房间id
GameMode int32 `json:"user_mode"` // 游戏模式,设定游戏的一些功能(参与游戏的人数,出手时间,特定的玩法)
GameRoundId string `json:"user_round_id"` // 本局游戏的id (重复上报,使用该字段去重)
BattleStartAt int32 `json:"battle_start_at"` // 战斗开始时间(秒)
Players []PlayerObject `json:"players"` // player_object 数组
ReportGameInfoExtras string `json:"report_user_info_extras"` // 游戏上报信息扩展参数(透传),取值范围:长度不超过1024字节,超过则截断
ReportGameInfoKey string `json:"report_user_info_key"` // 游戏上报信息扩展参数(透传),取值范围:长度不超过64字节,超过则截断。接入方服务端可以根据这个字段来查询一局游戏的数据
}
type PlayerObject struct {
Uid string `json:"uid"` // 接入方uid,机器人为空字符
IsAi int32 `json:"is_ai"` // 0:普通用户,1:机器人
}
type GameSettleObject struct {
MgId uint64 `json:"mg_id"` // 游戏id
MgIdStr string `json:"mg_id_str"` // 小游戏id数值型兼容字段(nodejs服务请使用当前字段)
RoomId string `json:"room_id"` // 接入方房间id
GameMode int32 `json:"user_mode"` // 游戏模式
GameRoundId string `json:"user_round_id"` // 本局游戏的id (重复上报,使用该字段去重)
BattleStartAt uint32 `json:"battle_start_at"` // 战斗开始时间(秒)
BattleEndAt uint32 `json:"battle_end_at"` // 战斗结束时间(秒)
BattleDuration int32 `json:"battle_duration"` // 战斗总时间(秒)
Results []PlayerResultObject `json:"results"` // player_result_object 数组
ReportGameInfoExtras string `json:"report_user_info_extras"` // 游戏上报信息扩展参数(透传),取值范围:长度不超过1024字节,超过则截断
ReportGameInfoKey string `json:"report_user_info_key"` // 游戏上报信息扩展参数(透传),取值范围:长度不超过64字节,超过则截断。接入方服务端可以根据这个字段来查询一局游戏的数据
}
type PlayerResultObject struct {
Uid string `json:"uid"` // 接入方uid,机器人为空字符
Rank int32 `json:"rank"` // 排名从1开始,平局排名相同
IsEscaped int32 `json:"is_escaped"` // 0:正常,1:逃跑
IsAi int32 `json:"is_ai"` // 0:普通用户,1:机器人
Role int32 `json:"role"` // 0:表示没有角色信息,玩家在游戏中的角色 游戏role 说明
Score int32 `json:"score"` // 玩家当前局得到的分数
IsWin int32 `json:"is_win"` // 结果 0:表示没有信息,1:输,2:赢,3:平局
Award int32 `json:"award"` // 奖励
Extras string `json:"extras"` // 扩展参数扩展说明
IsManaged int32 `json:"is_managed"` // 是否托管 0:未托管 1:托管
Diamond int64 `json:"diamond"` // 钻石收益,有可能负数,后期计算
LudoExtras *LudoExtras
}
type LudoExtras struct {
Color int `json:"color"`
Steps int `json:"steps"`
}
//添加领域事件,在每个领域模型中init中添加,因为这是静态业务,非动态的。
func AddReportGameInfoEventSync(callback func(model *domain.Model, event interface{}) error) {
reportGameInfoEvent.SyncList = append(reportGameInfoEvent.SyncList, callback)
}
//加入到异步操作中
func AddReportGameInfoEventAsync(callback func(model *domain.Model, event interface{}) error) {
reportGameInfoEvent.AsyncList = append(reportGameInfoEvent.AsyncList, callback)
}
//领域事件发布
func PublishReportGameInfoEvent(model *domain.Model, event interface{}) error {
//执行同步的领域事件
for _, callback := range reportGameInfoEvent.SyncList {
if err := callback(model, event); err != nil {
return err
}
}
// 执行异步的领域事件
if len(reportGameInfoEvent.AsyncList) > 0 {
go func() {
defer _const.CheckGoPanic()
for _, callback := range reportGameInfoEvent.AsyncList {
// 异步事件需要用新model,主要是db
var newModel = domain.CreateModelContext(model.MyContext)
if err := callback(newModel, event); err != nil {
model.Log.Errorf("ReportGameInfoEvent aysnc fail:%v", err)
}
}
}()
}
return nil
}
package group_ev
import (
"hilo-user/_const"
"hilo-user/domain"
"hilo-user/domain/event"
"hilo-user/myerr"
"hilo-user/resource/mysql"
)
var groupInListen = new(event.Base)
// 进房事件
type GroupInEvent struct {
GroupId string // imGroupId
UserId mysql.ID
ExternalId string
Nick string
Avatar string
IsMember bool //是否永久成员
IsVip bool
NobleLevel uint16
}
func AddGroupInEventSync(callback func(model *domain.Model, event interface{}) error) {
groupInListen.SyncList = append(groupInListen.SyncList, callback)
}
func AddGroupInEventAsync(callback func(model *domain.Model, event interface{}) error) {
groupInListen.AsyncList = append(groupInListen.AsyncList, callback)
}
func PublishGroupInEvent(model *domain.Model, event interface{}) error {
// 执行同步的领域事件
for _, callback := range groupInListen.SyncList {
if err := callback(model, event); err != nil {
return myerr.WrapErr(err)
}
}
// 执行异步的领域事件
if len(groupInListen.AsyncList) > 0 {
go func() {
defer _const.CheckGoPanic()
for _, callback := range groupInListen.AsyncList {
// 异步事件需要用新model,主要是db
var newModel = domain.CreateModelContext(model.MyContext)
if err := callback(newModel, event); err != nil {
model.Log.Errorf("GroupInEvent aysnc fail:%v", err)
}
}
}()
}
return nil
}
package group_ev
import (
"hilo-user/_const"
"hilo-user/domain"
"hilo-user/domain/event"
"hilo-user/myerr"
"hilo-user/resource/mysql"
)
var groupLeaveListen = new(event.Base)
// 离房事件
type GroupLeaveEvent struct {
GroupId string
UserId mysql.ID
ExternalId string
}
func AddGroupLeaveEventSync(callback func(model *domain.Model, event interface{}) error) {
groupLeaveListen.SyncList = append(groupLeaveListen.SyncList, callback)
}
func AddGroupLeaveEventAsync(callback func(model *domain.Model, event interface{}) error) {
groupLeaveListen.AsyncList = append(groupLeaveListen.AsyncList, callback)
}
func PublishGroupLeaveEvent(model *domain.Model, event interface{}) error {
// 执行同步的领域事件
for _, callback := range groupLeaveListen.SyncList {
if err := callback(model, event); err != nil {
return myerr.WrapErr(err)
}
}
// 执行异步的领域事件
if len(groupLeaveListen.AsyncList) > 0 {
go func() {
defer _const.CheckGoPanic()
for _, callback := range groupLeaveListen.AsyncList {
// 异步事件需要用新model,主要是db
var newModel = domain.CreateModelContext(model.MyContext)
if err := callback(newModel, event); err != nil {
model.Log.Errorf("GroupInEvent aysnc fail:%v", err)
}
}
}()
}
return nil
}
package domain
import (
"gorm.io/gorm"
"hilo-user/mycontext"
"hilo-user/resource/mysql"
"hilo-user/resource/redisCli"
)
type Model struct {
*CtxAndDb `gorm:"-"`
}
func CreateModel(ctxAndDb *CtxAndDb) *Model {
return &Model{CtxAndDb: ctxAndDb}
}
func CreateModelContext(myContext *mycontext.MyContext) *Model {
return &Model{
CtxAndDb: &CtxAndDb{
Db: mysql.Db,
MyContext: myContext,
Redis: redisCli.GetRedis(),
},
}
}
func CreateModelNil() *Model {
return &Model{
CtxAndDb: &CtxAndDb{
Db: mysql.Db,
MyContext: mycontext.CreateMyContext(nil),
Redis: redisCli.GetRedis(),
},
}
}
func (m *Model) DB() *gorm.DB {
return m.Db.WithContext(m)
}
// 包装事务
// 注意:需要使用新的model
func (m *Model) Transaction(f func(*Model) error) error {
// 公用context
// 新的db
txModel := CreateModelContext(m.MyContext)
txModel.Db = m.Db.Begin().WithContext(m)
err := f(txModel)
if err != nil {
txModel.Db.Rollback()
return err
}
return txModel.Db.Commit().Error
}
package common
import (
"context"
"encoding/json"
"hilo-user/_const/redis_key"
"hilo-user/myerr"
"hilo-user/mylogrus"
"hilo-user/resource/mysql"
"hilo-user/resource/redisCli"
"time"
)
func GetUserMedalMergeCache(userId mysql.ID) ([]uint32, error) {
bData, err := GetCache(redis_key.GetUserMedalMerge(userId))
if err != nil {
return nil, myerr.WrapErr(err)
}
res := make([]uint32, 0)
err = json.Unmarshal(bData, &res)
if err != nil {
return nil, myerr.WrapErr(err)
}
return res, nil
}
func GetCache(key string) ([]byte, error) {
data, err := redisCli.GetRedis().Get(context.Background(), key).Bytes()
if err != nil {
return nil, err
}
return data, nil
}
func SetCache(key string, data interface{}, expire time.Duration) error {
jData, err := json.Marshal(data)
if err != nil {
return err
}
err = redisCli.GetRedis().Set(context.Background(), key, jData, expire).Err()
if err != nil {
mylogrus.MyLog.Errorf("SetCache key:%s, data:%v, err:%s", key, data, err)
return err
}
return nil
}
func SetUserMedalMergeCache(userId mysql.ID, data []uint32) error {
err := SetCache(redis_key.GetUserMedalMerge(userId), data, time.Hour*2)
if err != nil {
mylogrus.MyLog.Errorf("SetUserMedalMerge err:%s", err)
return myerr.WrapErr(err)
}
return nil
}
package diamond_m
import (
"hilo-user/_const/enum/diamond_e"
"hilo-user/domain"
"hilo-user/myerr"
"hilo-user/myerr/bizerr"
"hilo-user/resource/mysql"
"strconv"
"time"
)
type DiamondAccount struct {
mysql.Entity
*domain.Model `gorm:"-"`
UserId mysql.ID
DiamondNum mysql.Num
PinkDiamondNum mysql.Num
Status diamond_e.StatusAccount
}
//账号详情
type DiamondAccountDetail struct {
mysql.Entity
*domain.Model `gorm:"-"`
UserId mysql.ID
DiamondAccountId mysql.ID
OperateId mysql.ID
OperateType diamond_e.OperateType
OriginId mysql.ID
AddReduce mysql.AddReduce
Num mysql.Num
Remark mysql.Str
BefNum mysql.Num
AftNum mysql.Num
diamondAccount *DiamondAccount `gorm:"-"`
}
//账号操作配置
type DiamondOperateSet struct {
mysql.Entity
*domain.Model `gorm:"-"`
DiamondNum mysql.NumAll
FrequencyNum mysql.NumAll
FrequencyDay mysql.NumAll
DiamondMaxNum mysql.NumAll
AddReduce mysql.AddReduce
Type diamond_e.OperateType
Name mysql.Str
Status mysql.UserYesNo
DiamondType diamond_e.OperateType
}
//通过userId获取帐号
func GetDiamondAccountByUserId(model *domain.Model, userId mysql.ID) (*DiamondAccount, error) {
var diamondAccount DiamondAccount
if err := model.Db.WithContext(model).Where(&DiamondAccount{
UserId: userId,
}).First(&diamondAccount).Error; err != nil {
return nil, myerr.WrapErr(err)
}
diamondAccount.Model = model
return &diamondAccount, nil
}
//匹配条件扣费
func (diamondAccount *DiamondAccount) ChangeDiamondAccountDetail(operateType diamond_e.OperateType, originId mysql.ID, diamondNum mysql.Num) (*DiamondAccountDetail, error) {
return diamondAccount.addDiamondAccountDetail(operateType, originId, diamondNum)
}
//钻石操作记录,
func (diamondAccount *DiamondAccount) addDiamondAccountDetail(operateType diamond_e.OperateType, originId mysql.ID, diamondNum mysql.Num) (*DiamondAccountDetail, error) {
var diamondOperateSet DiamondOperateSet
var err error
if err = diamondAccount.Db.Where(&DiamondOperateSet{
Type: operateType,
Status: mysql.USER,
DiamondType: diamond_e.DiamondYellow,
}).First(&diamondOperateSet).Error; err != nil {
return nil, myerr.WrapErr(err)
}
//判断是增加,账号是否被冻结
if diamondAccount.Status == diamond_e.Frozen && diamondOperateSet.AddReduce == mysql.REDUCE {
return nil, bizerr.DiamondAccountFrozen
}
//无限,检查次数
var count int64
if diamondOperateSet.FrequencyDay == -1 {
if diamondOperateSet.FrequencyNum != -1 {
diamondAccount.Db.Model(&DiamondAccountDetail{}).Where(&DiamondAccountDetail{
UserId: diamondAccount.UserId,
OperateType: operateType,
}).Count(&count)
if count >= int64(diamondOperateSet.FrequencyNum) {
return nil, bizerr.DiamondFrequency
}
}
} else if diamondOperateSet.FrequencyDay == 1 {
beginTime, err := time.ParseInLocation("2006-01-02", time.Now().Format("2006-01-02"), time.Local)
if err != nil {
return nil, myerr.WrapErr(err)
}
//一天的次数
diamondAccount.Db.Model(&DiamondAccountDetail{}).Where(&DiamondAccountDetail{
UserId: diamondAccount.UserId,
OperateType: operateType,
}).Where("created_time >= ? ", beginTime).Count(&count)
if count >= int64(diamondOperateSet.FrequencyNum) {
return nil, bizerr.DiamondFrequency
}
//终极拦截,利用
diamondAccount.SetCheckUpdateCondition(" EXISTS (SELECT * from (SELECT COUNT(1) as n from diamond_account_detail d where d.user_id = " + strconv.FormatUint(diamondAccount.UserId, 10) + " and d.operate_type = " + strconv.FormatUint(uint64(operateType), 10) + " and d.created_time >= from_unixtime(" + strconv.FormatInt(getZeroTime(time.Now()).Unix(), 10) + ")) t where t.n < " + strconv.Itoa(diamondOperateSet.FrequencyNum) + " )")
}
//-1,代表值无效,由参数给与
var upateDiamondNum mysql.Num
if diamondOperateSet.DiamondNum == -1 {
upateDiamondNum = diamondNum
} else {
upateDiamondNum = mysql.Num(diamondOperateSet.DiamondNum)
}
var afterNum mysql.Num
if diamondOperateSet.AddReduce == mysql.ADD {
afterNum = diamondAccount.DiamondNum + upateDiamondNum
} else if diamondOperateSet.AddReduce == mysql.REDUCE {
if diamondAccount.DiamondNum < upateDiamondNum {
return nil, bizerr.DiamondNoEnough
}
afterNum = diamondAccount.DiamondNum - upateDiamondNum
} else {
return nil, myerr.NewSysError("AddReduce 值错误:" + mysql.TypeToString(diamondOperateSet.AddReduce))
}
diamondAccountDetail := &DiamondAccountDetail{
Model: diamondAccount.Model,
UserId: diamondAccount.UserId,
DiamondAccountId: diamondAccount.ID,
OperateId: diamondOperateSet.ID,
OperateType: diamondOperateSet.Type,
OriginId: originId,
AddReduce: diamondOperateSet.AddReduce,
Num: upateDiamondNum,
Remark: diamondOperateSet.Name,
BefNum: diamondAccount.DiamondNum,
AftNum: afterNum,
diamondAccount: diamondAccount,
}
return diamondAccountDetail, err
}
package diamond_m
import (
"hilo-user/_const/enum/diamond_e"
"hilo-user/domain"
"hilo-user/myerr/bizerr"
"hilo-user/resource/mysql"
)
func CheckEnoughDiamondFrozen(model *domain.Model, userId mysql.ID, diamondNum mysql.Num) (*DiamondAccount, error) {
diamondAccount, err := GetDiamondAccountByUserId(model, userId)
if err != nil {
return nil, err
}
if diamondAccount.DiamondNum < diamondNum {
return nil, bizerr.DiamondNoEnough
}
if diamondAccount.Status == diamond_e.Frozen {
return nil, bizerr.DiamondAccountFrozen
}
return diamondAccount, nil
}
package diamond_m
import (
"gorm.io/gorm"
"hilo-user/domain/model"
"hilo-user/myerr"
"hilo-user/mylogrus"
"hilo-user/resource/mysql"
"strconv"
)
func (diamondAccountDetail *DiamondAccountDetail) Persistent() error {
txDiamondAccount := diamondAccountDetail.Db.Model(diamondAccountDetail.diamondAccount)
if diamondAccountDetail.diamondAccount.CheckUpdateCondition() {
txDiamondAccount = txDiamondAccount.Where(diamondAccountDetail.diamondAccount.GetUpdateCondition())
}
if diamondAccountDetail.AddReduce == mysql.ADD {
//增加
txDiamondAccount.UpdateColumn("diamond_num", gorm.Expr("diamond_num + ?", diamondAccountDetail.Num))
} else if diamondAccountDetail.AddReduce == mysql.REDUCE {
//减少,保证不能扣成负数
txDiamondAccount.Where("diamond_num >= ?", diamondAccountDetail.Num).UpdateColumn("diamond_num", gorm.Expr("diamond_num - ?", diamondAccountDetail.Num))
} else {
myerr.NewSysError("addReduce 枚举错误 value:" + mysql.TypeToString(mysql.Type(diamondAccountDetail.AddReduce)))
}
if err := txDiamondAccount.Error; err != nil {
return myerr.WrapErr(err)
}
if txDiamondAccount.RowsAffected == 0 {
mylogrus.MyLog.Errorf("gorm condition update.RowsAffected = 0,AddReduce:%v", diamondAccountDetail.AddReduce)
return myerr.NewWaring("gorm condition update.RowsAffected = 0")
}
//持久化diamondAccountDetail
if err := model.Persistent(diamondAccountDetail.Db, diamondAccountDetail); err != nil {
return myerr.WrapErr(err)
}
//改变diamondAccount值
if diamondAccountDetail.diamondAccount == nil {
return myerr.NewSysError("持久化错误, 模型:DiamondAccountDetail 中没有diamondAccount, DiamondAccountDetail.Id =" + strconv.Itoa(int(diamondAccountDetail.ID)))
}
var newDiamondAccount DiamondAccount
if err := diamondAccountDetail.Db.First(&newDiamondAccount, diamondAccountDetail.diamondAccount.ID).Error; err != nil {
return myerr.WrapErr(err)
}
if newDiamondAccount.DiamondNum < 0 {
return myerr.NewSysError("diamond_account表中,diamond_num 不能小于0, diamondAccount.id = " + strconv.Itoa(int(newDiamondAccount.ID)))
}
return nil
}
package diamond_m
import "time"
func getZeroTime(t time.Time) time.Time {
return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
}
package event_m
import (
"gorm.io/gorm"
"hilo-user/domain"
"hilo-user/resource/mysql"
"time"
)
// 进房事件消息
type EventGroupIn struct {
mysql.Entity
*domain.Model `gorm:"-"`
Proto mysql.Type
Payload []byte
Mark mysql.YesNo
}
// 偏移值
type EventGroupInOffset struct {
mysql.Entity
*domain.Model `gorm:"-"`
MarkOffset mysql.ID
}
// 获取当前偏移值
func Offset(model *domain.Model) (*EventGroupInOffset, error) {
offset := new(EventGroupInOffset)
if err := model.Db.WithContext(model).First(offset).Error; err != nil {
if err != gorm.ErrRecordNotFound {
model.Log.Errorf("Offset fail:%v", err)
return nil, err
}
// gorm.ErrRecordNotFound
}
offset.Model = model
return offset, nil
}
// 批量获取进房事件
func FetchEventGroupIn(model *domain.Model, limit int) ([]*EventGroupIn, *EventGroupInOffset, error) {
offset, err := Offset(model)
if err != nil {
return nil, nil, err
}
var events []*EventGroupIn
if err := model.Db.WithContext(model).Model(EventGroupIn{}).
Where("id > ?", offset.MarkOffset).
Order("id asc").Limit(limit).Find(&events).Error; err != nil {
model.Log.Errorf("FetchEventGroupIn fail:%v", err)
return nil, nil, err
}
return events, offset, nil
}
// 标记已完成
func (p *EventGroupIn) MarkDone() error {
p.Mark = mysql.YES
return p.Db.WithContext(p.Model).Model(EventGroupIn{}).Where("id = ?", p.ID).Update("mark", p.Mark).Limit(1).Error
}
// 查询过去1小时-5分钟前未mark的事件
// 用来补偿
// 记得加limit
func FetchUnMarkEvents(model *domain.Model, limit int) ([]*EventGroupIn, error) {
offset, err := Offset(model)
if err != nil {
return nil, err
}
t := time.Now().Add(-time.Minute * 5)
lt := t.Add(-time.Hour)
var events []*EventGroupIn
if err := model.Db.WithContext(model).Model(EventGroupIn{}).
Where("mark = ?", mysql.NO).
Where("id <= ?", offset.MarkOffset).
Where("created_time < ?", t).
Where("created_time > ?", lt).
Order("id asc").Limit(limit).Find(&events).Error; err != nil {
model.Log.Errorf("FetchUnMarkEvents fail:%v", err)
return nil, err
}
return events, nil
}
// 补偿加上unmark的event
func AddUnMarkEvent(model *domain.Model, event *EventGroupIn) error {
return model.Db.WithContext(model).Create(event).Error
}
package event_m
import (
"gorm.io/gorm"
"hilo-user/domain"
"hilo-user/resource/mysql"
"time"
)
// 离房事件消息
type EventGroupLeave struct {
mysql.Entity
*domain.Model `gorm:"-"`
Proto mysql.Type
Payload []byte
Mark mysql.YesNo
}
// 偏移值
type EventGroupLeaveOffset struct {
mysql.Entity
*domain.Model `gorm:"-"`
MarkOffset mysql.ID
}
// 获取当前偏移值
func GroupLeaveOffset(model *domain.Model) (*EventGroupLeaveOffset, error) {
offset := new(EventGroupLeaveOffset)
if err := model.Db.WithContext(model).First(offset).Error; err != nil {
if err != gorm.ErrRecordNotFound {
model.Log.Errorf("Offset fail:%v", err)
return nil, err
}
// gorm.ErrRecordNotFound
}
offset.Model = model
return offset, nil
}
// 批量获取进房事件
func FetchEventGroupLeave(model *domain.Model, limit int) ([]*EventGroupLeave, *EventGroupLeaveOffset, error) {
offset, err := GroupLeaveOffset(model)
if err != nil {
return nil, nil, err
}
var events []*EventGroupLeave
if err := model.Db.WithContext(model).Model(EventGroupLeave{}).
Where("id > ?", offset.MarkOffset).
Order("id asc").Limit(limit).Find(&events).Error; err != nil {
model.Log.Errorf("FetchEventGroupLeave fail:%v", err)
return nil, nil, err
}
return events, offset, nil
}
// 标记已完成
func (p *EventGroupLeave) MarkDone() error {
p.Mark = mysql.YES
return p.Db.WithContext(p.Model).Model(EventGroupLeave{}).Where("id = ?", p.ID).Update("mark", p.Mark).Limit(1).Error
}
// 查询过去1小时-5分钟前未mark的事件
// 用来补偿
// 记得加limit
func FetchGroupLeaveUnMarkEvents(model *domain.Model, limit int) ([]*EventGroupLeave, error) {
offset, err := GroupLeaveOffset(model)
if err != nil {
return nil, err
}
t := time.Now().Add(-time.Minute * 5)
lt := t.Add(-time.Hour)
var events []*EventGroupLeave
if err := model.Db.WithContext(model).Model(EventGroupLeave{}).
Where("mark = ?", mysql.NO).
Where("id <= ?", offset.MarkOffset).
Where("created_time < ?", t).
Where("created_time > ?", lt).
Order("id asc").Limit(limit).Find(&events).Error; err != nil {
model.Log.Errorf("FetchUnMarkEvents fail:%v", err)
return nil, err
}
return events, nil
}
// 补偿加上unmark的event
func AddGroupLeaveUnMarkEvent(model *domain.Model, event *EventGroupLeave) error {
return model.Db.WithContext(model).Create(event).Error
}
package event_m
import "hilo-user/domain/model"
func (p *EventGroupInOffset) Persistence() error {
return model.Persistent(p.Db, p)
}
func (p *EventGroupLeaveOffset) Persistence() error {
return model.Persistent(p.Db, p)
}
package user_m
import (
"gorm.io/gorm"
"hilo-user/_const/enum/user_e"
"hilo-user/common"
"hilo-user/domain"
"hilo-user/domain/event/user_ev"
"hilo-user/myerr"
"hilo-user/myerr/bizerr"
"hilo-user/resource/mysql"
"math/rand"
"strconv"
"time"
)
type GameInfo struct {
Id uint64 `json:"id"`
MgId string `json:"mg_id"`
GameType user_e.GameType `json:"user_type"`
Mode int32 `json:"mode"`
Piece int32 `json:"piece"`
OnOff1 uint8 `json:"on_off1"`
Diamond uint64 `json:"diamond"`
CreateId uint64 `json:"create_id"`
Status uint8 `json:"status"` // '0.未开始 1.游戏中 2.结束'
TxGroupId string `json:"tx_group_id"`
GameRoundId string `json:"user_round_id"`
BattleStartAt uint32 `json:"battle_start_at"`
BattleEndAt uint32 `json:"battle_end_at"`
BattleDuration uint32 `json:"battle_duration"`
AutoMatch uint8 `json:"auto_match"` // 是否开启自动匹配,0否1是
CreatedTime time.Time `json:"created_time"`
UpdatedTime time.Time `json:"updated_time"`
}
type GamePlayer struct {
Id uint64 `json:"id"`
GameId uint64 `json:"user_id"`
UserId uint64 `json:"user_id"`
Status uint8 `json:"status"` // '0.未开始 1.游戏中 2.游戏结束 3.逃跑 4.创建者关闭游戏',
UserCode string `json:"user_code"`
ExternalId string `json:"external_id"`
Rank uint32 `json:"rank"`
IsEscaped uint8 `json:"is_escaped"`
IsAi uint8 `json:"is_ai"`
Role int32 `json:"role"`
Score int32 `json:"score"`
IsWin uint8 `json:"is_win"`
Award uint32 `json:"award"`
Extras string `json:"extras"`
IsManaged uint8 `json:"is_managed"`
EndAt uint32 `json:"end_at"`
SeatIdx int8 `json:"seat_idx"`
Diamond int64 `json:"diamond"` // 黄钻收益,有可能负数
CreatedTime time.Time `json:"created_time"`
UpdatedTime time.Time `json:"updated_time"`
}
type GameAddParam struct {
GameId uint64 `form:"userId"`
GameType user_e.GameType `form:"userType" binding:"required"`
Mode user_e.GameMode `form:"mode" binding:"required"`
Piece int `form:"piece" binding:"required"`
OnOff1 int `form:"onOff1" binding:"required"`
Diamond uint64 `form:"diamond" binding:"required"`
AutoMatch int `form:"autoMatch" binding:"required"`
TxGroupId string `form:"groupId" binding:"required"`
GameCode string `form:"userCode"`
}
type GamePlayerOptLog struct {
Id uint64 `json:"id"`
GameId uint64 `json:"user_id"`
UserId uint64 `json:"user_id"`
Opt uint8 `json:"opt"`
TxGroupId string `json:"tx_group_id"`
CreateAt time.Time `json:"create_at"`
}
type GameSdkReport struct {
Id uint64 `json:"id"`
GameRoundId string `json:"user_round_id"`
TxGroupId string `json:"tx_group_id"`
ReportType string `json:"report_type"`
ReportMsg string `json:"report_msg"`
ExternalId string `json:"external_id"`
SsToken string `json:"ss_token"`
CreateAt time.Time `json:"create_at"`
}
func GetGameInfo(model *domain.Model, userId uint64, txGroupId, roundId string, battleEndAt, status int64) (*GameInfo, error) {
res := new(GameInfo)
db := model.Db
if userId != 0 {
db = db.Where("id = ?", userId)
}
if txGroupId != "" {
db = db.Where("tx_group_id = ?", txGroupId)
}
if roundId != "" {
db = db.Where("user_round_id = ?", roundId)
}
if battleEndAt != -1 {
db = db.Where("battle_end_at = ?", battleEndAt)
}
if status != -1 {
db = db.Where("status = ?", status)
}
err := db.First(&res).Error
if err != nil && err != gorm.ErrRecordNotFound {
return nil, err
}
return res, nil
}
func GetGamingInfo(model *domain.Model, txGroupId string) (*GameInfo, error) {
res := new(GameInfo)
db := model.Db
if txGroupId != "" {
db = db.Where("tx_group_id = ?", txGroupId)
}
err := db.Where("status in (0,1)").First(&res).Error
if err != nil && err != gorm.ErrRecordNotFound {
return nil, err
}
return res, nil
}
func Add(model *domain.Model, info *GameInfo) error {
nowTime := time.Now()
info.CreatedTime = nowTime
info.UpdatedTime = nowTime
err := model.Db.Save(info).Error
if err != nil {
return myerr.WrapErr(err)
}
return nil
}
func Edit(model *domain.Model, id uint64, para *GameAddParam) error {
if id == 0 {
model.Log.Infof("GameDao Edit id = 0")
return nil
}
err := model.Db.Exec("update user_info set mode=?,piece=?,on_off1=?,diamond=?,auto_match=? where id = ? and battle_start_at = 0",
para.Mode, para.Piece, para.OnOff1, para.Diamond, para.AutoMatch, id).Error
if err != nil {
return myerr.WrapErr(err)
}
return nil
}
func GameCloseUpdate(model *domain.Model, info *GameInfo) error {
if info.Id == 0 {
model.Log.Infof("GameCloseUpdate Id = 0")
return bizerr.GameNotFound
}
result := model.Db.Exec("update user_info set status=?,battle_end_at=? where id = ? and status != 2",
user_e.GameStatusEnd, time.Now().Unix(), info.Id)
if result.Error != nil {
model.Log.Errorf("GameCloseUpdate info.Id:%v, err:%v", info.Id, result.Error)
return myerr.WrapErr(result.Error)
}
if result.RowsAffected <= 0 {
model.Log.Errorf("GameCloseUpdate info.Id:%v, err:%v", info.Id, result.Error)
return bizerr.GameCloseWrong
}
return nil
}
func GameCloseUpdatePlayer(model *domain.Model, info *GameInfo) error {
if info.Id == 0 {
model.Log.Infof("GameCloseUpdatePlayer Id = 0")
return nil
}
err := model.Db.Exec("update user_player set status=?,end_at=? where user_id = ?", user_e.GamerStatusEnd, time.Now().Unix(), info.Id).Error
if err != nil {
return myerr.WrapErr(err)
}
return nil
}
func GameStartUpdate(model *domain.Model, info *user_ev.GameStartObject) error {
//var initGameRoundId bool
userInfo := new(GameInfo)
infoKey, err := strconv.Atoi(info.ReportGameInfoKey)
if err != nil {
model.Log.Errorf("GameStartUpdate err:%v, info:%v", err, info)
return myerr.WrapErr(err)
}
err = model.Db.Where("id = ?", infoKey).First(&userInfo).Error
if err != nil {
model.Log.Errorf("GameStartUpdate err:%v, info:%v", err, info)
return myerr.WrapErr(err)
//if err != gorm.ErrRecordNotFound {
// model.Log.Errorf("GameStartUpdate err:%v, info:%v", err, info)
// return myerr.WrapErr(err)
//}
//userInfo = new(GameInfo)
//err = model.Db.Where("tx_group_id = ? and user_round_id = ''", info.RoomId).First(&userInfo).Error
//if err != nil {
// if err != gorm.ErrRecordNotFound {
// model.Log.Errorf("GameStartUpdate err:%v, info:%v", err, info)
// return myerr.WrapErr(err)
// }
// model.Log.Infof("GameStartUpdate 找不到可以更新的记录 info:%v", info)
// return nil
//}
//initGameRoundId = true
}
if userInfo.Id == 0 {
model.Log.Errorf("GameStartUpdate 记录id为0 userInfo:%v", userInfo)
return myerr.WrapErr(bizerr.GameNotFound)
}
//if initGameRoundId {
// err = model.Db.Exec("update user_info set mode=?,user_round_id=?,battle_start_at=? where id = ? and user_round_id='' limit 1",
// info.GameMode, info.GameRoundId, info.BattleStartAt, userInfo.Id).Error
//} else {
// err = model.Db.Exec("update user_info set mode=?,user_round_id=?,battle_start_at=? where id = ? limit 1",
// info.GameMode, info.GameRoundId, info.BattleStartAt, userInfo.Id).Error
//}
err = model.Db.Exec("update user_info set mode=?,user_round_id=?,battle_start_at=? where id = ? limit 1",
info.GameMode, info.GameRoundId, info.BattleStartAt, userInfo.Id).Error
if err != nil {
model.Log.Errorf("GameStartUpdate err:%v", err)
return myerr.WrapErr(err)
}
err = model.Db.Exec("update user_player set status=? where user_id = ?", user_e.GamerStatusGaming, userInfo.Id).Error
if err != nil {
model.Log.Errorf("GameStartUpdate err:%v", err)
return myerr.WrapErr(err)
}
return nil
}
// 标记游戏已经开始
func GameStartMark(model *domain.Model, userId mysql.ID) error {
if err := model.DB().Model(GameInfo{}).Where("id = ?", userId).Update("status", user_e.GameStatusGaming).Error; err != nil {
model.Log.Errorf("GameStartMark fail:userId:%v,err:%v", userId, err)
return err
}
return nil
}
// 标记游戏已经结束
func GameEndMark(model *domain.Model, userId mysql.ID) error {
if err := model.DB().Model(GameInfo{}).Where("id = ?", userId).Update("status", user_e.GameStatusEnd).Error; err != nil {
model.Log.Errorf("GameStartMark fail:userId:%v,err:%v", userId, err)
return err
}
return nil
}
func GameSettleUpdate(model *domain.Model, info *user_ev.GameSettleObject) error {
//if info.GameRoundId == "" {
// model.Log.Infof("GameSettleUpdate GameRoundId = 0")
// return nil
//}
infoKey, err := strconv.Atoi(info.ReportGameInfoKey)
if err != nil {
model.Log.Errorf("GameSettleUpdate err:%v, info:%v", err, info)
return myerr.WrapErr(err)
}
userInfo := new(GameInfo)
err = model.Db.Where("id = ?", infoKey).First(&userInfo).Error
if err != nil {
model.Log.Errorf("GameSettleUpdate err:%v", err)
return myerr.WrapErr(err)
}
result := model.Db.Exec("update user_info set status=?,battle_end_at=?,battle_duration=? where id = ? and status=1",
user_e.GameStatusEnd, info.BattleEndAt, info.BattleDuration, userInfo.Id)
if result.Error != nil {
model.Log.Errorf("GameSettleUpdate err:%v", err)
return myerr.WrapErr(err)
}
if result.RowsAffected <= 0 {
model.Log.Errorf("GameSettleUpdate 修改失败, userInfo.Id:%v", userInfo.Id)
return myerr.WrapErr(bizerr.GameSettleWrong)
}
for _, v := range info.Results {
err = model.Db.Exec("update user_player set status=?,`rank`=?,is_escaped=?,is_ai=?,`role`=?,score=?,is_win=?,"+
"award=?,extras=?,end_at=?,diamond=?,is_managed=? where user_id = ? and external_id = ?",
user_e.GamerStatusEnd, v.Rank, v.IsEscaped, v.IsAi, v.Role, v.Score, v.IsWin, v.Award, v.Extras, info.BattleEndAt,
v.Diamond, v.IsManaged, userInfo.Id, v.Uid).Error
if err != nil {
model.Log.Errorf("GameSettleUpdate err:%v", err)
return myerr.WrapErr(err)
}
}
err = model.Db.Exec("update user_player set status=?,end_at=? where user_id=? and end_at=0",
user_e.GamerStatusEnd, time.Now().Unix(), userInfo.Id).Error
if err != nil {
model.Log.Errorf("GameSettleUpdate err:%v", err)
return myerr.WrapErr(err)
}
return nil
}
func GetGamePlayers(model *domain.Model, userId uint64) ([]*GamePlayer, error) {
res := make([]*GamePlayer, 0)
err := model.Db.Where("user_id = ?", userId).Order("created_time").Find(&res).Error
if err != nil {
return nil, err
}
return res, nil
}
func DelGamePlayers(model *domain.Model, userId uint64) error {
if userId <= 0 {
return bizerr.InvalidParameter
}
err := model.Db.Exec("delete from user_player where user_id = ?", userId).Error
if err != nil {
return myerr.WrapErr(err)
}
return nil
}
func GetGamePlayersMap(model *domain.Model, userId uint64, userExtIds []string) (map[string]*GamePlayer, error) {
list := make([]*GamePlayer, 0, len(userExtIds))
err := model.Db.Where("user_id = ? and external_id in (?)", userId, userExtIds).Find(&list).Error
if err != nil && err != gorm.ErrRecordNotFound {
return nil, err
}
res := make(map[string]*GamePlayer, 0)
for _, v := range list {
res[v.ExternalId] = v
}
return res, nil
}
func GetGamePlayer(model *domain.Model, userId, userId uint64) (*GamePlayer, error) {
res := new(GamePlayer)
err := model.Db.Where("user_id = ? and user_id = ?", userId, userId).First(res).Error
if err != nil && err != gorm.ErrRecordNotFound {
return nil, err
}
return res, nil
}
func SaveGamePlayer(model *domain.Model, info *GamePlayer) error {
err := model.Db.Save(info).Error
if err != nil {
return myerr.WrapErr(err)
}
return nil
}
func DelGamePlayer(model *domain.Model, id uint64) error {
result := model.Db.Exec("delete from user_player where id = ? and status=0", id)
if result.Error != nil {
return result.Error
}
if result.RowsAffected <= 0 {
return myerr.WrapErr(bizerr.GameExitWrong)
}
return nil
}
func UpdateGamePlayerExit(model *domain.Model, id uint64) error {
err := model.Db.Exec("update user_player set end_at=? where id = ?", time.Now().Unix(), id).Error
if err != nil {
return err
}
return nil
}
func SaveGameOptLog(model *domain.Model, userId, userId uint64, opt user_e.GameOpt, txGroupId string) error {
defer common.CheckGoPanic()
err := model.Db.Save(&GamePlayerOptLog{GameId: userId, UserId: userId, Opt: uint8(opt), TxGroupId: txGroupId, CreateAt: time.Now()}).Error
if err != nil {
model.Log.Errorf("SaveGameOptLog err:%v", err)
return err
}
return nil
}
func SaveGameSdkReport(model *domain.Model, userRoundId, roomId, reportType, reportMsg, externalId, ssToken string) error {
defer common.CheckGoPanic()
err := model.Db.Save(&GameSdkReport{TxGroupId: roomId, GameRoundId: userRoundId, ReportType: reportType,
ReportMsg: reportMsg, ExternalId: externalId, SsToken: ssToken, CreateAt: time.Now()}).Error
if err != nil {
model.Log.Errorf("SaveGameSdkReport err:%v", err)
return err
}
return nil
}
// 获取一个正在进行游戏的房间id(房间没有密码,没有下注的游戏房间)
func GetGamingTxGroupId(model *domain.Model, userType user_e.GameType) (txGroupId string, err error) {
// 游戏类型,游戏未满人,游戏未开始
infos := make([]*GameInfo, 0)
sql := "select s1.* from (select * from user_info where user_type=? and status=0 and diamond=0 limit 50) s1 left join group_info on s1.tx_group_id=group_info.tx_group_id where group_info.password='' and group_info.tourist_mic=1"
err = model.Db.Raw(sql, userType).Find(&infos).Error
//err = model.Db.Where("status = 0 and user_type = ?", userType).Limit(10).Find(&infos).Error
if err != nil {
return
}
if len(infos) == 0 {
return
}
if len(infos) == 1 {
txGroupId = infos[0].TxGroupId
return
}
// 打乱
rand.Shuffle(len(infos), func(i, j int) {
t := infos[i]
infos[i] = infos[j]
infos[j] = t
})
txGroupId = infos[0].TxGroupId
return
}
// 获取用户在该房间已经加入的未结束的游戏
func GetGamingInfoByUserId(model *domain.Model, userId uint64, txGroupId string) (*GameInfo, error) {
res := new(GameInfo)
db := model.Db
err := db.Where("id in (select user_id from user_player where user_id=? and end_at=0) and tx_group_id = ? and status in (0,1)", userId, txGroupId).First(&res).Error
//err := db.Where("status in (0,1)").First(&res).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, nil
}
return nil, err
}
return res, nil
}
package user_m
import (
"hilo-user/_const/enum/user_e"
"hilo-user/domain"
"hilo-user/resource/mysql"
"sort"
"time"
)
type StartGamePlayer struct {
TxGroupId mysql.Str // 房间id
GameId mysql.ID // 游戏id
MgId mysql.Str // 游戏sdk id
UserId mysql.ID // 用户id
CreatedTime time.Time // 加入游戏时间
}
// 获取可以开始的游戏
// condition
// 1. 游戏还没开始
// 2. 游戏人数大于等于2个
// 3. 第二个加入的人距离现在已经超过15秒
func GetCanStartGames(model *domain.Model) []GameInfo {
// 1. 游戏还没开始
sql := "SELECT g.tx_group_id,p.user_id,g.mg_id,p.user_id,p.created_time FROM `user_info` g,`user_player` p where p.user_id = g.id AND g.`status` = ?;"
var startGamePlayers []StartGamePlayer
if err := model.DB().Raw(sql, user_e.GameStatusNoStart).Find(&startGamePlayers).Error; err != nil {
model.Log.Errorf("GetCanStartGames fail,sql:%v,err:%v", sql, err)
return nil
}
// userId -> userPlayer
var startGamePlayersMap = make(map[mysql.ID][]StartGamePlayer)
for i, p := range startGamePlayers {
startGamePlayersMap[p.GameId] = append(startGamePlayersMap[p.GameId], startGamePlayers[i])
}
var res []GameInfo
for userId, players := range startGamePlayersMap {
// 2. 游戏人数大于等于2个
if len(players) < 2 {
continue
}
model.Log.Infof("startGamePlayer,userId:%v,player:%+v", userId, players)
// 3. 第二个加入的人距离现在已经超过15秒
sort.Slice(players, func(i, j int) bool {
return players[i].CreatedTime.Before(players[j].CreatedTime)
})
// 客户端15秒,服务端延迟1秒
if time.Now().Sub(players[1].CreatedTime).Seconds() >= 16 {
res = append(res, GameInfo{
Id: userId,
MgId: players[1].MgId,
TxGroupId: players[1].TxGroupId,
})
}
}
return res
}
package user_m
import (
"encoding/json"
"fmt"
"github.com/pkg/errors"
"hilo-user/_const/enum/robot_e"
"hilo-user/_const/enum/user_e"
"hilo-user/domain"
"hilo-user/domain/cache/robot_c"
"hilo-user/domain/model/group_m"
"hilo-user/domain/model/user_m"
"hilo-user/resource/config"
"hilo-user/resource/mysql"
"hilo-user/rpc"
"time"
)
// 机器人
type GameRobot struct {
mysql.Entity
UserId mysql.ID // 用户id
State robot_e.RobotState // 机器人状态
OpStep robot_e.RobotOp // 当前所在步骤
GroupId string // 群组id
MicIndex int // 麦位
GameUid mysql.ID // 游戏id
MgId string // sdk游戏Id
LastUseTime time.Time // 上次使用的时间
model *domain.Model `gorm:"-"` // db、ctx
Game GameTiny `gorm:"-"` // 需要加入的游戏
User user_m.User `gorm:"-"` // 用户信息
Group *group_m.GroupInfo `gorm:"-"` // 群组信息
Token string `gorm:"-"` // jwt token
Over bool `gorm:"-"` // 是否结束
Err error `gorm:"-"` // 错误日志
// service层的GameOpt
GameOpt func(userId, userId uint64, code, ext, lang, userCode, txGroupId string, opt user_e.GameOpt, seatIdx int8, isAi bool) error `gorm:"-"`
}
// 机器人日志
type GameRobotLog struct {
mysql.Entity
RobotUid mysql.ID
Op robot_e.RobotOp // 机器人操作
DataId string // 房间号 or 麦位 or 游戏id
}
// 标记正在使用中
func (robot *GameRobot) MarkUsing(model *domain.Model) error {
updates := map[string]interface{}{
"state": robot_e.RobotStateUsing,
"last_use_time": time.Now(),
}
return model.Db.Model(GameRobot{}).Where("id = ?", robot.ID).
Updates(updates).Error
}
// 标记闲置
func (robot *GameRobot) MarkIdle(model *domain.Model) {
updates := map[string]interface{}{
"state": robot_e.RobotStateIdle,
"group_id": "",
"mic_index": -1,
"user_uid": 0,
"mg_id": "",
"op_step": robot_e.RobotOpNone,
}
if err := model.Db.Model(GameRobot{}).Where("id = ?", robot.ID).
Updates(updates).Error; err != nil {
robot.model.Log.Errorf("MarkIdle fail:%v", err)
}
}
// 机器人干活了
func (robot *GameRobot) Run() {
robot.model = domain.CreateModelNil()
robot.model.Log = robot.model.Log.WithField("GameRobotRun", robot.ID)
var err error
defer func() {
if err != nil {
robot.model.Log.Errorf("Run fail,robotId:%v,uid:%v,err:%v", robot.ID, robot.UserId, err)
// reset to idle
robot.MarkIdle(robot.model)
robot.Over = true
robot.Err = err
}
}()
// 检查游戏状态
if robot.Game.Status == user_e.GameStatusNoStart { // 未开始
go robot.heartbeat() // 心跳自愈
// 进入游戏流程
switch robot.OpStep {
case robot_e.RobotOpNone:
if err = robot.GroupIn(); err != nil {
return
}
fallthrough
case robot_e.RobotOpGroupIn:
if err = robot.MicIn(); err != nil {
return
}
fallthrough
case robot_e.RobotOpMicIn:
if err = robot.GameIn(); err != nil {
return
}
fallthrough
default:
robot.model.Log.Infof("may in other step:%v", robot.OpStep)
}
} else {
robot.model.Log.Warnf("user status not normal:%v", robot.Game)
}
}
// 进程重启后的机器人自愈
// 如果走过进入游戏了,则继续走心跳逻辑
// 否则,直接走
func (robot *GameRobot) ReRun() {
robot.model = domain.CreateModelNil()
robot.model.Log = robot.model.Log.WithField("GameRobotReRun", robot.ID)
if robot.GameUid > 0 {
go robot.heartbeat()
} else {
robot.Leave("ReRun")
}
}
// 进房
func (robot *GameRobot) GroupIn() error {
url := config.GetUrlConfig().BIZ_HTTP + "/v1/imGroup/in"
header := map[string]string{
"nonce": "hilo",
"token": robot.Token,
}
form := map[string]string{
"groupId": robot.Game.GroupId,
}
// http to biz
if resp, err := rpc.HttpPutForm(robot.model, url, header, form); err != nil {
return err
} else {
type GroupInResp struct {
Code int `json:"code"`
}
res := new(GroupInResp)
if err = json.Unmarshal(resp, res); err != nil {
return err
} else {
if res.Code != 200 {
return errors.New(fmt.Sprintf("Group In Not 200:%v", res))
} else {
// 复制群组id
robot.GroupId = robot.Game.GroupId
}
}
}
// no error
return robot.model.Transaction(func(txModel *domain.Model) error {
updates := map[string]interface{}{
"group_id": robot.GroupId,
"op_step": robot_e.RobotOpGroupIn,
}
err := txModel.Db.Model(GameRobot{}).Where("id = ?", robot.ID).
Updates(updates).Error
if err != nil {
return err
}
return txModel.Db.Create(&GameRobotLog{
RobotUid: robot.ID,
Op: robot_e.RobotOpGroupIn,
DataId: robot.GroupId,
}).Error
})
}
// 上麦
func (robot *GameRobot) MicIn() error {
url := config.GetUrlConfig().BIZ_HTTP + "/v1/imGroup/mic/in"
header := map[string]string{
"nonce": "hilo",
"token": robot.Token,
}
form := map[string]string{
"groupUuid": robot.GroupId,
"i": "", // 空则随意上一个空位置
}
// http to biz
if resp, err := rpc.HttpPostForm(robot.model, url, header, form); err != nil {
robot.model.Log.Errorf("Mic In fail:%v", err)
return err
} else {
type MicInResp struct {
Code int `json:"code"`
Data struct {
MicIndex int `json:"micIndex"`
} `json:"data"`
}
res := new(MicInResp)
if err = json.Unmarshal(resp, res); err != nil {
robot.model.Log.Errorf(fmt.Sprintf("Mic In json:%v", res))
return err
} else {
if res.Code != 200 || res.Data.MicIndex < 0 {
robot.model.Log.Errorf(fmt.Sprintf("Mic In Not 200:%v,groupId:%v", res, robot.GroupId))
if res.Code == 12008 {
// 群上麦失败,群先做cd,能上麦的情况太多
_ = robot_c.CDMicInGroup(robot.model, robot.GroupId)
}
return errors.New(fmt.Sprintf("Mic In Not 200:%v", res))
} else {
robot.MicIndex = res.Data.MicIndex // 赋值麦位
}
}
}
return robot.model.Transaction(func(txModel *domain.Model) error {
updates := map[string]interface{}{
"mic_index": robot.MicIndex,
"op_step": robot_e.RobotOpMicIn,
}
err := robot.model.Db.Model(GameRobot{}).Where("id = ?", robot.ID).
Updates(updates).Error
if err != nil {
return err
}
return txModel.Db.Create(&GameRobotLog{
RobotUid: robot.ID,
Op: robot_e.RobotOpMicIn,
DataId: fmt.Sprintf("%s-%d", robot.GroupId, robot.MicIndex),
}).Error
})
}
// 加入游戏
func (robot *GameRobot) GameIn() error {
if err := robot.GameOpt(robot.UserId, robot.Game.GameUid, robot.User.Code, robot.User.ExternalId, "en", "", robot.GroupId, user_e.GameOptJoin, -1, true); err != nil {
robot.model.Log.Errorf("Game In fail,userId:%v,err:%v", robot.UserId, err)
return err
}
err := robot.model.Transaction(func(txModel *domain.Model) error {
updates := map[string]interface{}{
"user_uid": robot.Game.GameUid,
"mg_id": robot.Game.MgId,
"op_step": robot_e.RobotOpGameIn,
}
err := robot.model.Db.Model(GameRobot{}).Where("id = ?", robot.ID).
Updates(updates).Error
if err != nil {
return err
}
// 赋值游戏id
robot.GameUid = robot.Game.GameUid
return txModel.Db.Create(&GameRobotLog{
RobotUid: robot.ID,
Op: robot_e.RobotOpGameIn,
DataId: fmt.Sprintf("%d", robot.GameUid),
}).Error
})
if err != nil {
return err
}
// GameIn成功之后腾讯云通知
robot.TxGroupInPush()
return nil
}
// 机器人离开
// 下麦+离开房间+退出游戏
func (robot *GameRobot) Leave(reason string) {
// 异常情况会直接leave,保护一下model未赋值
if robot.model == nil {
robot.model = domain.CreateModelNil()
}
robot.model.Log.Infof("robot leave,id:%v,reason:%v", robot.ID, reason)
robot.MicLeave()
robot.GroupLeave()
robot.GameOut()
robot.MarkIdle(robot.model)
}
// 下麦
func (robot *GameRobot) MicLeave() {
// 未上麦
if robot.MicIndex < 0 {
return
}
url := config.GetUrlConfig().BIZ_HTTP + "/v1/imGroup/mic/leave"
header := map[string]string{
"nonce": "hilo",
"token": robot.Token,
}
form := map[string]string{
"groupUuid": robot.GroupId,
"i": fmt.Sprintf("%d", robot.MicIndex), //
}
// http to biz
if resp, err := rpc.HttpPostForm(robot.model, url, header, form); err != nil {
robot.model.Log.Errorf("Mic Leave fail:%v", err)
return
} else {
type MicLeaveResp struct {
Code int `json:"code"`
}
res := new(MicLeaveResp)
if err = json.Unmarshal(resp, res); err != nil {
robot.model.Log.Errorf(fmt.Sprintf("Mic Leave json:%v", res))
return
} else {
if res.Code != 200 {
robot.model.Log.Errorf(fmt.Sprintf("Mic Leave Not 200:%v", res))
}
}
}
if err := robot.model.Db.Create(&GameRobotLog{
RobotUid: robot.ID,
Op: robot_e.RobotOpMicLeave,
DataId: fmt.Sprintf("%s-%d", robot.GroupId, robot.MicIndex),
}).Error; err != nil {
robot.model.Log.Errorf("log fail:%v", err)
}
}
// 离房
func (robot *GameRobot) GroupLeave() {
if len(robot.GroupId) <= 0 {
return
}
url := config.GetUrlConfig().BIZ_HTTP + "/v1/imGroup/leave"
header := map[string]string{
"nonce": "hilo",
"token": robot.Token,
}
form := map[string]string{
"groupId": robot.GroupId,
}
// http to biz
if resp, err := rpc.HttpPostForm(robot.model, url, header, form); err != nil {
robot.model.Log.Errorf("GroupLeave fail:%v", err)
return
} else {
type GroupLeaveResp struct {
Code int `json:"code"`
}
res := new(GroupLeaveResp)
if err = json.Unmarshal(resp, res); err != nil {
robot.model.Log.Errorf(fmt.Sprintf("Group Leave json :%v", res))
return
} else {
if res.Code != 200 {
robot.model.Log.Errorf(fmt.Sprintf("Group Leave Not 200:%v", res))
}
}
}
if err := robot.model.Db.Create(&GameRobotLog{
RobotUid: robot.ID,
Op: robot_e.RobotOpGroupLeave,
DataId: robot.GroupId,
}).Error; err != nil {
robot.model.Log.Errorf("log fail:%v", err)
}
}
// 离开游戏
func (robot *GameRobot) GameOut() {
if robot.GameUid <= 0 || len(robot.GroupId) <= 0 {
return
}
if err := robot.GameOpt(robot.UserId, robot.GameUid, robot.User.Code, robot.User.ExternalId, "en", "", robot.GroupId, user_e.GameOptExit, -1, true); err != nil {
robot.model.Log.Errorf("Game Out fail,userId:%v,err:%v", robot.UserId, err)
return
}
if err := robot.model.Db.Create(&GameRobotLog{
RobotUid: robot.ID,
Op: robot_e.RobotOpGameOut,
DataId: fmt.Sprintf("%d", robot.GameUid),
}).Error; err != nil {
robot.model.Log.Errorf("log fail:%v", err)
}
}
// 腾讯云麦位通知
func (robot *GameRobot) TxGroupInPush() {
if robot.Group == nil || robot.User.ID <= 0 {
return
}
url := config.GetUrlConfig().BIZ_HTTP + "/inner/micAllRPush"
header := map[string]string{
"nonce": "hilo",
"token": robot.Token,
}
form := map[string]string{
"imGroupId": robot.Group.ImGroupId,
"externalId": robot.User.ExternalId,
}
// http to biz
if resp, err := rpc.HttpPostForm(robot.model, url, header, form); err != nil {
robot.model.Log.Errorf("GroupLeave fail:%v", err)
return
} else {
type Resp struct {
Code int `json:"code"`
}
res := new(Resp)
if err = json.Unmarshal(resp, res); err != nil {
robot.model.Log.Errorf(fmt.Sprintf("TxGroupInPush json :%v", res))
return
} else {
if res.Code != 200 {
robot.model.Log.Errorf(fmt.Sprintf("TxGroupInPush Not 200:%v", res))
}
}
}
}
// 机器人心跳自愈
func (robot *GameRobot) heartbeat() {
// robot.GameUid是mysql/执行完加入游戏才有
userId := robot.GameUid
if userId <= 0 {
// 这里是未加入游戏前的userId
userId = robot.Game.GameUid
}
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for {
select {
case <-ticker.C:
user, err := getGameInfo(robot.model, userId)
if robot.Over || err != nil || user_e.GameStatus(user.Status) == user_e.GameStatusEnd {
robot.Leave("robot over")
return
}
}
}
}
// 根据id获取游戏信息
func getGameInfo(model *domain.Model, userUid mysql.ID) (*GameInfo, error) {
user := new(GameInfo)
err := model.DB().Model(user).Where("id = ?", userUid).First(user).Error
return user, err
}
package user_m
import (
"hilo-user/_const/enum/robot_e"
"hilo-user/_const/enum/user_e"
"hilo-user/domain"
"hilo-user/domain/cache/robot_c"
"hilo-user/resource/mysql"
"math/rand"
"time"
)
// 游戏结构体
type GameTiny struct {
GameUid mysql.ID
GameType user_e.GameType
GroupId string
MgId int64
Diamond int64
NeedRobots int // 需要机器人数量
Status user_e.GameStatus // 游戏进行状态,1:进行中、0:结束, 心跳实时刷新
UpdatedTime time.Time
}
// 获取需要机器人的游戏
func GetNeedRobotGames(model *domain.Model) ([]GameTiny, error) {
var resGames []GameTiny
// 获取准备开始的游戏
var users []GameTiny
sql := "SELECT user.id as user_uid,user.user_type,user.tx_group_id as group_id,mg_id,user.diamond,user.`status`,user.updated_time FROM user_info user,group_info g " +
"WHERE user.tx_group_id = g.tx_group_id AND user. STATUS = 0 AND user.auto_match = 1 AND user.battle_start_at = 0 AND g.`password` = '' AND g.tourist_mic = 1"
if err := model.DB().Raw(sql).Find(&users).Error; err != nil {
return nil, err
}
now := time.Now()
for _, user := range users {
// 使用user.UpdatedTime(非CreatedTime)去判断8秒,兼容编辑游戏的情况
if now.Sub(user.UpdatedTime).Seconds() <= 8 {
continue
}
// user players
var players []GamePlayer
if err := model.DB().Model(GamePlayer{}).
Where("user_id = ?", user.GameUid).Find(&players).Error; err == nil {
// 没有玩家,属于异常的user
if len(players) <= 0 {
model.Log.Errorf("GetNeedRobotGames no player exits,user:%v,players:%v", user, players)
continue
}
// 玩家大于2个就不需要机器人
if len(players) > 1 {
continue
}
// 机器人进群cd中
if robot_c.IsGroupInCd(model, user.GroupId) {
model.Log.Warnf("IsGroupInCd in cd,user:%v", user)
continue
}
var needRobots = 1 // 一个机器人
resGames = append(resGames, GameTiny{
GameUid: user.GameUid,
GroupId: user.GroupId,
MgId: user.MgId,
NeedRobots: needRobots,
Status: user.Status,
GameType: user.GameType,
Diamond: user.Diamond,
})
} else if err != nil {
model.Log.Errorf("GamePlayer fail:%v", err.Error())
}
}
return resGames, nil
}
// 获取正在使用中的机器人
func GetUsingRobots(model *domain.Model) ([]*GameRobot, error) {
var robots []*GameRobot
if err := model.DB().Model(GameRobot{}).Where("state = ?", robot_e.RobotStateUsing).Find(&robots).Error; err != nil {
return nil, err
}
return robots, nil
}
// 获取房间中的机器人
func GetRoomRobots(model *domain.Model, txGroupId mysql.Str) ([]*GameRobot, error) {
var robots []*GameRobot
if err := model.DB().Model(GameRobot{}).Where("group_id = ?", txGroupId).Find(&robots).Error; err != nil {
return nil, err
}
return robots, nil
}
// 获取异常的robot
// state = 0,但是状态没清空
func GetAbnormalRobots(model *domain.Model) ([]*GameRobot, error) {
var robots []*GameRobot
if err := model.DB().Model(GameRobot{}).Where("state = ? AND op_step != 0", robot_e.RobotStateIdle).Find(&robots).Error; err != nil {
return nil, err
}
return robots, nil
}
// 获取闲置中的机器人
// 并且设置为使用中
func GetSetIdleGameRobots(model *domain.Model, num int) ([]*GameRobot, error) {
if num <= 0 {
model.Log.Warnf("GetSetIdleGameRobots no need robot:%v", num)
return nil, nil
}
var robots []*GameRobot
var canUseRobots []*GameRobot
err := model.Transaction(func(txModel *domain.Model) error {
if err := txModel.Db.WithContext(txModel).Model(GameRobot{}).Where("state = ?", robot_e.RobotStateIdle).Limit(1000).Find(&robots).Error; err != nil {
return err
}
// 打乱1000个空闲的机器人
rand.Shuffle(len(robots), func(i, j int) {
robots[i], robots[j] = robots[j], robots[i]
})
for i, robot := range robots {
// 增加cd逻辑,60秒机器人不复用
if time.Now().Sub(robot.LastUseTime).Seconds() < 60 {
model.Log.Warnf("robot cannot frequency use:%v", robot)
continue
}
if err := robot.MarkUsing(txModel); err != nil {
return err
}
canUseRobots = append(canUseRobots, robots[i])
num--
if num <= 0 {
break
}
}
return nil
})
if err != nil {
model.Log.Errorf("GetIdleGameRobots fail:%v", err)
return nil, err
}
return canUseRobots, nil
}
// 游戏ai启动
func AddAi(model *domain.Model, robots []*GameRobot) {
for _, robot := range robots {
robot.Run() // 一个机器人一个协程
}
}
package groupPower_m
import (
"gorm.io/gorm"
"hilo-user/_const/enum/groupPower_e"
"hilo-user/domain"
"hilo-user/domain/model/group_m"
"hilo-user/myerr"
"hilo-user/resource/mysql"
)
type GroupPower struct {
mysql.Entity
*domain.Model `gorm:"-"`
GroupUid mysql.Str
Name mysql.Str
Status groupPower_e.GroupPowerStatus
}
type GroupPowerUser struct {
mysql.Entity
*domain.Model `gorm:"-"`
GroupPowerId mysql.ID
UserId mysql.ID
Role groupPower_e.GroupPowerUserRole
}
func GetGroupPowerOrErr(model *domain.Model, id uint64) (*GroupPower, error) {
groupPower := GroupPower{}
if err := model.Db.Model(&GroupPower{}).First(&groupPower, id).Error; err != nil {
return nil, myerr.WrapErr(err)
}
groupPower.Model = model
return &groupPower, nil
}
//获取用户所在的国家势力信息,不存在则为nil
func GetGroupPowerUserOrNil(model *domain.Model, userId mysql.ID) (*GroupPowerUser, error) {
groupPowerUser := GroupPowerUser{}
if err := model.Db.Where(&GroupPowerUser{
UserId: userId,
}).First(&groupPowerUser).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return nil, nil
} else {
return nil, myerr.WrapErr(err)
}
}
groupPowerUser.Model = model
return &groupPowerUser, nil
}
// 查询用户加入的国家势力ID及名称(势力绑定的群组的名称)
func GetUserGroupPower(model *domain.Model, userId uint64) (uint64, string, error) {
gpu, err := GetGroupPowerUserOrNil(model, userId)
if err != nil {
return 0, "", err
}
if gpu == nil || gpu.GroupPowerId == 0 {
return 0, "", nil
}
gp, err := GetGroupPowerOrErr(model, gpu.GroupPowerId)
if err != nil {
return 0, "", err
}
powerName := ""
if gp != nil && len(gp.GroupUid) > 0 {
gi, err := group_m.GetGroupInfo(model, gp.GroupUid)
if err != nil {
return 0, "", err
}
if gi != nil {
// 只要前15个字
s := []rune(gi.Name)
if len(s) <= 15 {
powerName = string(s)
} else {
powerName = string(s[0:15])
}
}
}
return gpu.GroupPowerId, powerName, nil
}
package group_m
import (
"context"
"encoding/json"
redisV8 "github.com/go-redis/redis/v8"
"gorm.io/gorm"
"hilo-user/common"
"hilo-user/common/rediskey"
"hilo-user/domain"
"hilo-user/myerr"
"hilo-user/myerr/bizerr"
"hilo-user/resource/mysql"
"hilo-user/resource/redisCli"
)
type GroupRoles struct {
mysql.Entity
UserId uint64
ImGroupId string
Role common.GroupRoleType
}
func (this *GroupInfo) TableName() string {
return "group_info"
}
//发言,注意(发言是在麦位上)
type MicUser struct {
model *domain.Model
//群组uuid
GroupUuid string
//麦位
I int
//麦中的人
ExternalId string
//
UserId uint64
//静音 true:静音,false:没有静音
Forbid bool
//上麦的的时间戳
Timestamp int64
}
//记录麦位上有谁。用于
type UserInMic struct {
//群组uuid
GroupUuid string
//麦位
I int
//userId
UserId uint64
}
// 查询用户在IM群组中的角色
func GetRoleInGroup(model *domain.Model, userId uint64, imGroupId string) (uint16, error) {
r := GroupRoles{}
err := model.Db.Where(&GroupRoles{
UserId: userId,
ImGroupId: imGroupId}).First(&r).Error
if err != nil {
if err != gorm.ErrRecordNotFound {
return 0, err
} else {
return 0, nil
}
}
return r.Role, nil
}
func GetByTxGroupId(model *domain.Model, txGroupId string) (*GroupInfo, error) {
if len(txGroupId) <= 0 {
return nil, bizerr.GroupNotFound
}
res := new(GroupInfo)
err := model.Db.Where(&GroupInfo{TxGroupId: txGroupId}).First(&res).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, myerr.WrapErr(bizerr.GroupNotFound)
} else {
return nil, myerr.WrapErr(err)
}
}
return res, nil
}
//获取用户在哪个麦位上。没有不在麦上,则是nil
func GetMicUserByExternalId(model *domain.Model, externalId string) (*MicUser, error) {
if str, err := redisCli.GetRedis().Get(context.Background(), rediskey.GetPrefixGroupUserInMic(externalId)).Result(); err != nil {
if err != redisV8.Nil {
return nil, myerr.WrapErr(err)
} else {
return nil, nil
}
} else {
if userInMic, err := strToUserInMic(str); err != nil {
return nil, err
} else {
return GetMicUser(model, userInMic.GroupUuid, userInMic.I)
}
}
}
//麦位上没人,返回nil
func GetMicUser(model *domain.Model, groupUuid string, i int) (*MicUser, error) {
if i < 1 || i > 30 {
return nil, myerr.NewSysErrorF("麦序不对,不在范围值内 i:%v", i)
}
str, err := redisCli.GetRedis().Get(context.Background(), rediskey.GetPrefixGroupMicUser(groupUuid, i)).Result()
if err != nil {
if err == redisV8.Nil {
return nil, nil
} else {
return nil, myerr.WrapErr(err)
}
}
var micUser MicUser
err = json.Unmarshal([]byte(str), &micUser)
if err != nil {
return nil, err
}
return &MicUser{
model: model,
GroupUuid: groupUuid,
I: i,
ExternalId: micUser.ExternalId,
UserId: micUser.UserId,
Forbid: micUser.Forbid,
Timestamp: micUser.Timestamp,
}, nil
}
func strToUserInMic(str string) (UserInMic, error) {
userInMic := UserInMic{}
if err := json.Unmarshal([]byte(str), &userInMic); err != nil {
return UserInMic{}, myerr.WrapErr(err)
}
return userInMic, nil
}
package group_m
import (
"gorm.io/gorm"
"hilo-user/_const/enum/group_e"
"hilo-user/domain"
"hilo-user/myerr/bizerr"
"time"
)
type GroupInfo struct {
Id int64
ImGroupId string
TxGroupId string
Type uint16
Code string
OriginCode string
Owner uint64
Name string
Introduction string
Notification string
FaceUrl string
Country string
ChannelId string
Password string
EntryLevel uint32 // obsolete
MicOn bool
LoadHistory bool
ThemeId int16
MicNumType group_e.GroupMicNumType
TouristMic uint8 // 游客是否能上麦1是2否
TouristSendMsg uint8 // 游客是否能发消息1是2否
TouristSendPic uint8 // 游客是否能发图片1是2否
MemberFee uint64 // 加入会员需要黄钻数
CreatedTime time.Time `gorm:"->"`
UpdatedTime time.Time `gorm:"->"`
}
func GetGroupInfo(model *domain.Model, groupId string) (*GroupInfo, error) {
if len(groupId) <= 0 {
return nil, bizerr.GroupNotFound
}
r := GroupInfo{}
err := model.Db.Where(&GroupInfo{ImGroupId: groupId}).First(&r).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, nil
} else {
return nil, err
}
}
return &r, nil
}
func GetGroupInfoByOwner(model *domain.Model, userId uint64) (*GroupInfo, error) {
if userId <= 0 {
return nil, bizerr.GroupNotFound
}
r := GroupInfo{}
err := model.Db.Where(&GroupInfo{Owner: userId}).First(&r).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, nil
} else {
return nil, err
}
}
return &r, nil
}
package group_m
import (
"hilo-user/domain"
"hilo-user/myerr/bizerr"
)
func ToTxGroupId(model *domain.Model, imGroupId string) (string, error) {
if len(imGroupId) <= 0 {
return "", nil
}
gi, err := GetGroupInfo(model, imGroupId)
if err != nil {
return "", err
}
if gi == nil {
return "", bizerr.GroupNotFound
}
return gi.TxGroupId, nil
}
package group_m
import (
"encoding/json"
"hilo-user/_const/enum/group_e"
"hilo-user/domain"
"hilo-user/domain/model/noble_m"
"hilo-user/domain/model/user_m"
"hilo-user/rpc"
)
// 信令消息
type GroupSystemMsg struct {
MsgId group_e.TypeSignalMsg `json:"msgId"`
Source string `json:"source"`
Target string `json:"target"`
Content string `json:"content"`
}
type HiloUserInfo struct {
WealthGrade uint32 `json:"wealthGrade"`
CharmGrade uint32 `json:"charmGrade"`
IsVip bool `json:"isVip"`
IsPretty bool `json:"isPretty"`
Medals []uint32 `json:"medals"`
PowerName string `json:"powerName"` // 用户加入的国家势力的绑定群组的名称
NobleLevel uint16 `json:"nobleLevel"`
SvipLevel int `json:"svipLevel"`
}
var GetGroupPowerNameByUserId func(model *domain.Model, userId uint64) (uint64, string, error)
//不用返回错误
func GetHiloUserInfo(model *domain.Model, extId string) string {
user, err := user_m.GetUserByExtId(model, extId)
if err != nil {
model.Log.Errorf("extId:%v, err:%+v", extId, err)
return ""
}
wealthGrade, _, err := user_m.GetWealthGrade(model, user.ID)
if err != nil {
model.Log.Errorf("extId:%v, err:%v", extId, err)
return ""
}
charmGrade, _, err := user_m.GetCharmGrade(model, user.ID)
if err != nil {
model.Log.Errorf("extId:%v, err:%v", extId, err)
return ""
}
isVip, _, err := user_m.IsVip(user.ID)
if err != nil {
model.Log.Errorf("extId:%v, err:%v", extId, err)
return ""
}
//
isPretty := user.IsPrettyCode()
//
medals, err := user_m.GetUserMedalMerge(model.Log, model.Db, user.ID)
if err != nil {
model.Log.Errorf("extId:%v, err:%v", extId, err)
return ""
}
_, powerName, err := GetGroupPowerNameByUserId(model, user.ID)
if err != nil {
model.Log.Errorf("extId:%v, err:%v", extId, err)
return ""
}
nobleLevel, err := noble_m.GetNobleLevel(model.Db, user.ID)
if err != nil {
model.Log.Errorf("extId:%v, err:%v", extId, err)
return ""
}
svip, _ := rpc.GetUserSvip(model, user.ID)
hilo := HiloUserInfo{
WealthGrade: wealthGrade,
CharmGrade: charmGrade,
IsVip: isVip,
IsPretty: isPretty,
Medals: medals,
PowerName: powerName,
NobleLevel: nobleLevel,
SvipLevel: svip.SvipLevel,
}
buf, err := json.Marshal(hilo)
if err != nil {
model.Log.Errorf("hilo:%+v, err:%v", hilo, err)
}
return string(buf)
}
package noble_m
import (
"gorm.io/gorm"
"hilo-user/resource/mysql"
"time"
)
type UserNoble struct {
mysql.Entity
UserId uint64
Level uint16
EndTime time.Time
}
func (ub *UserNoble) Create(db *gorm.DB) error {
return db.Create(ub).Error
}
func (ub *UserNoble) UpdateEndTime(db *gorm.DB, endTime time.Time) (int64, error) {
r := db.Model(&UserNoble{}).Where(ub).Update("end_time", endTime)
return r.RowsAffected, r.Error
}
// 查询用户未过期的贵族
func (ub *UserNoble) Find(db *gorm.DB) ([]UserNoble, error) {
rows := make([]UserNoble, 0)
if err := db.Where(ub).Where("end_time>=NOW()").Order("level DESC").Find(&rows).Error; err != nil {
return nil, err
}
return rows, nil
}
// 查询用户所有的贵族(包括已过期)
func (ub *UserNoble) FindAll(db *gorm.DB) ([]UserNoble, error) {
rows := make([]UserNoble, 0)
if err := db.Where(ub).Order("level DESC").Find(&rows).Error; err != nil {
return nil, err
}
return rows, nil
}
func RemoveNoble(db *gorm.DB, userId uint64, level uint16) error {
ub := UserNoble{
UserId: userId,
Level: level,
}
return ub.Delete(db)
}
func (ub *UserNoble) Delete(db *gorm.DB) error {
return db.Where(ub).Delete(&UserNoble{}).Error
}
// 查询用户未过期的贵族
func (ub *UserNoble) batchGet(db *gorm.DB, userIds []uint64) (map[uint64][]UserNoble, error) {
rows := make([]UserNoble, 0)
if err := db.Model(ub).Where("end_time>=NOW() AND user_id IN ?", userIds).Order("level DESC").Find(&rows).Error; err != nil {
return nil, err
}
result := make(map[uint64][]UserNoble, 0)
for _, i := range rows {
if _, ok := result[i.UserId]; !ok {
result[i.UserId] = make([]UserNoble, 0)
}
result[i.UserId] = append(result[i.UserId], i)
}
return result, nil
}
func FindActiveNoble(db *gorm.DB, userId uint64) (*UserNoble, error) {
ub := UserNoble{
UserId: userId,
}
records, err := ub.Find(db)
if err != nil {
return nil, err
}
if len(records) <= 0 {
return nil, nil
}
return &records[0], nil
}
func GetNobleLevel(db *gorm.DB, userId uint64) (uint16, error) {
noble, err := FindActiveNoble(db, userId)
if err != nil {
return 0, err
}
if noble == nil {
return 0, nil
} else {
return noble.Level, nil
}
}
package model
import (
"fmt"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"hilo-user/myerr"
"hilo-user/resource/mysql"
"time"
)
func Persistent(db *gorm.DB, t mysql.EntityI) error {
if t == nil {
return nil
}
if t.IsLazyLoad() {
return nil
}
//删除
if t.CheckDel() {
tx := db.Delete(t)
if err := tx.Error; err != nil {
return myerr.WrapErr(err)
}
if tx.RowsAffected == 0 {
return myerr.NewWaring("gorm delete.RowsAffected = 0")
}
//增加缓存行为记录(删除)
} else if t.GetID() == 0 {
//新增
if t.CheckOnDuplicateKeyUPDATE() {
if err := db.Set("gorm:insert_option", fmt.Sprintf("ON DUPLICATE KEY UPDATE `created_time` = '%s'", time.Now())).Create(t).Error; err != nil {
return myerr.WrapErr(err)
}
} else if t.CheckOnDuplicateKeyIGNORE() {
if err := db.Clauses(clause.Insert{Modifier: "IGNORE"}).Create(t).Error; err != nil {
return myerr.WrapErr(err)
}
} else {
if err := db.Create(t).Error; err != nil {
return myerr.WrapErr(err)
}
}
//增加缓存行为记录(新增)
} else {
//fixme: 更新条件,目前是互斥的,应该改成且。
//更新
if t.CheckUpdateVersion() {
//版本号。乐观锁更新,注意,空值不更新
tx := db.Model(t).Where("version = ? ", t.GetUpdateVersionBefore()).Updates(t)
if err := tx.Error; err != nil {
return myerr.WrapErr(err)
}
if tx.RowsAffected == 0 {
return myerr.NewWaring("gorm version update.RowsAffected = 0")
}
} else if t.CheckUpdateCondition() {
//条件更新
tx := db.Model(t).Where(t.GetUpdateCondition()).Updates(t)
if err := tx.Error; err != nil {
return myerr.WrapErr(err)
}
if tx.RowsAffected == 0 {
return myerr.NewWaring("gorm condition update.RowsAffected = 0")
}
} else if len(t.GetOmit()) > 0 {
if err := db.Model(t).Omit(t.GetOmit()...).Save(t).Error; err != nil {
return myerr.WrapErr(err)
}
} else {
if err := db.Model(t).Save(t).Error; err != nil {
return myerr.WrapErr(err)
}
}
//增加缓存行为记录(更新)
}
return nil
}
package res_m
import (
"gorm.io/gorm"
"hilo-user/_const/enum/res_e"
"hilo-user/domain"
"hilo-user/myerr"
"hilo-user/resource/mysql"
)
type ResCountry struct {
mysql.Entity
*domain.Model `gorm:"-"`
Name mysql.Str
ShortName mysql.Str
Code mysql.Str
Icon mysql.Str
Icon2 mysql.Str
Lang mysql.Str
IsCommon mysql.UserYesNo
IsDefault mysql.UserYesNo
Status mysql.UserYesNo
StandardShortName mysql.Str
AreaCode mysql.Str
AreaCodeName mysql.Str
}
type ResLanguage struct {
mysql.Entity
*domain.Model `gorm:"-"`
Name mysql.Str
Remark mysql.Str
ResCountryId mysql.ID
}
type ResMsgTranslate struct {
mysql.Entity
*domain.Model `gorm:"-"`
Language mysql.Str
Title mysql.Str
Content mysql.Str
IconUrl mysql.Str
MsgType mysql.Type
Type uint32
//跳转类型 0:无跳转 1:网页跳转, 2:跳转到用户 3:跳转到群组
ActionType mysql.Type
ActionUrl mysql.Str
}
// 复制消息并替换图片和URL
func (rmt *ResMsgTranslate) CopyRecord(db *gorm.DB, newType uint64, iconUrl, actionUrl string) error {
return db.Exec("REPLACE INTO res_msg_translate (language,title,content,?,msg_type,?,action_type,?) "+
"SELECT language,title,content,icon_url,msg_type,10001,action_type,action_url FROM res_msg_translate "+
"WHERE msg_type = ? and type = 39", iconUrl, newType, actionUrl, rmt.MsgType, rmt.Type).Error
}
//获取默认的国家
func GetCountryDefault(model *domain.Model) (*ResCountry, error) {
var resCountry ResCountry
if err := model.Db.Where(&ResCountry{
IsDefault: mysql.USER,
}).First(&resCountry).Error; err != nil {
return nil, myerr.WrapErr(err)
}
return &resCountry, nil
}
// 获取所有国家的icon信息
func GetAllCountries(model *domain.Model) (map[string]string, error) {
var countrys []ResCountry
if err := model.Db.Model(&ResCountry{}).Where(&ResCountry{
Status: mysql.USER,
}).Find(&countrys).Error; err != nil {
return nil, myerr.WrapErr(err)
}
result := make(map[string]string, 0)
for _, i := range countrys {
result[i.Name] = i.Icon
}
return result, nil
}
func GetAllCountryByFilter(model *domain.Model, shortNames []string) ([]ResCountry, error) {
var countrys []ResCountry
if err := model.Db.Model(&ResCountry{}).Where(&ResCountry{
Status: mysql.USER,
}).Find(&countrys).Error; err != nil {
return nil, myerr.WrapErr(err)
}
countryMap := map[string]ResCountry{}
//转成map
for i, _ := range countrys {
countryMap[countrys[i].ShortName] = countrys[i]
}
results := make([]ResCountry, 0, len(shortNames))
for i, _ := range shortNames {
if country, flag := countryMap[shortNames[i]]; flag {
results = append(results, country)
}
}
return results, nil
}
//通过standardShortName获取国家
func GetCountryByStandardShortName(model *domain.Model, standardShortName mysql.Str) (*ResCountry, error) {
var resCountry ResCountry
if err := model.Db.Where(&ResCountry{
StandardShortName: standardShortName,
Status: mysql.USER,
}).First(&resCountry).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return nil, nil
} else {
return nil, myerr.WrapErr(err)
}
}
resCountry.Model = model
return &resCountry, nil
}
//通过shortName获取国家
func GetCountryByShortName(model *domain.Model, shortName mysql.Str) (*ResCountry, error) {
var resCountry ResCountry
err := model.Db.Where(&ResCountry{
ShortName: shortName,
}).First(&resCountry).Error
return &resCountry, myerr.WrapErr(err)
}
//通过name获取国家
func GetCountryByName(model *domain.Model, name mysql.Str) (*ResCountry, error) {
var resCountry ResCountry
err := model.Db.WithContext(model.Context).Where(&ResCountry{
Name: name,
}).First(&resCountry).Error
return &resCountry, myerr.WrapErr(err)
}
//通过code获取国家
func GetCountryByCode(model *domain.Model, code mysql.Str) (*ResCountry, error) {
if code == "" {
return nil, nil
}
var resCountry ResCountry
err := model.Db.Where(&ResCountry{
Code: code,
}).First(&resCountry).Error
if err == nil {
return &resCountry, myerr.WrapErr(err)
} else if err == gorm.ErrRecordNotFound {
return nil, nil
} else {
return nil, myerr.WrapErr(err)
}
}
//通过id获取国家
func GetCountryById(model *domain.Model, id mysql.ID) (*ResCountry, error) {
var resCountry ResCountry
err := model.Db.First(&resCountry, id).Error
return &resCountry, myerr.WrapErr(err)
}
//通过语言,找到国家
func GetCountryByLanguage(model *domain.Model, name mysql.Str) (*ResCountry, error) {
if name == "" {
return nil, nil
}
var language ResLanguage
err := model.Db.Where(&ResLanguage{
Name: name,
}).First(&language).Error
if err == nil {
return GetCountryById(model, language.ResCountryId)
} else if err == gorm.ErrRecordNotFound {
return nil, nil
} else {
return nil, myerr.WrapErr(err)
}
}
//通过国家,找到对应的语言
func GetLangeByCountry(db *gorm.DB, country mysql.Str) (string, error) {
var r ResCountry
err := db.Where(&ResCountry{
Name: country,
}).First(&r).Error
if err == nil {
return r.Lang, nil
} else if err == gorm.ErrRecordNotFound {
return res_e.DEFAULT_LANG, nil
} else {
return "", myerr.WrapErr(err)
}
}
package res_m
import (
"gorm.io/gorm"
"hilo-user/_const/enum/res_e"
"hilo-user/resource/mysql"
)
type ResMedal struct {
mysql.Entity
Name string
PicUrl string
SvgaUrl string
Sort mysql.Num
NoPicUrl *string
//类型 (0:代表自身就是一种类型 > 0, 一种相关联的类型)
Type res_e.ResMedalType
Scope res_e.ResMedalScope
//限制 (-1, 没有限制)
Threshold mysql.Num
I mysql.Num
}
// 查勋章中的等级关系
func GetUserMedalLevelMap(db *gorm.DB) (map[uint64]uint8, map[uint8][]uint64, error) {
rows := make([]ResMedal, 0)
if err := db.Model(&ResMedal{}).Order("threshold ASC").Find(&rows).Error; err != nil {
return nil, nil, err
}
medalTypes := make(map[uint64]uint8, 0)
result := make(map[uint8][]uint64, 0)
for _, i := range rows {
medalTypes[i.ID] = i.Type
if i.Type != 0 {
if _, ok := result[i.Type]; !ok {
result[i.Type] = make([]uint64, 0)
}
result[i.Type] = append(result[i.Type], i.ID)
}
}
return medalTypes, result, nil
}
package res_m
import (
"hilo-user/_const/enum/res_e"
"hilo-user/resource/mysql"
)
type ResNameplate struct {
mysql.Entity
Name string
Sort mysql.Num
PicUrl string
SvgaUrl string
NoPicUrl *string
//类型 (0:代表自身就是一种类型 > 0, 一种相关联的类型)
Type res_e.ResNameplateType
Scope res_e.ResNameplateScope
//限制 (-1, 没有限制)
Threshold mysql.Num
I mysql.Num
}
package res_m
import (
"gorm.io/gorm"
"hilo-user/_const/enum/res_e"
"hilo-user/myerr"
"hilo-user/resource/mysql"
)
type ResMultiText struct {
mysql.Entity
MsgId uint
Language mysql.Str
Content mysql.Str
}
func (r *ResMultiText) Get(db *gorm.DB) error {
return db.Where(r).First(r).Error
}
func GetResMultiTextBy(db *gorm.DB, msgId uint, Language mysql.Str) (*ResMultiText, error) {
r := ResMultiText{}
if err := db.Where(&ResMultiText{
MsgId: msgId,
Language: Language,
}).First(&r).Error; err != nil {
if err == gorm.ErrRecordNotFound {
if err := db.Where(&ResMultiText{
MsgId: msgId,
Language: res_e.DEFAULT_LANG,
}).First(&r).Error; err != nil {
return nil, myerr.WrapErr(err)
}
} else {
return nil, myerr.WrapErr(err)
}
}
return &r, nil
}
package user_m
import (
"gorm.io/gorm"
"hilo-user/_const/enum/match_e"
"hilo-user/domain"
"hilo-user/myerr"
"hilo-user/resource/mysql"
)
/**
* 用户魅力分数
**/
type MatchCharmUserScore struct {
mysql.Entity
*domain.Model `gorm:"-"`
UserId mysql.ID
Score mysql.Num
Grade mysql.Num
}
//获取魅力等级
func GetCharmGrade(model *domain.Model, userId mysql.ID) (uint32, uint32, error) {
var charmUserScore MatchCharmUserScore
if err := model.Db.Model(&MatchCharmUserScore{}).Where(&MatchCharmUserScore{
UserId: userId,
}).First(&charmUserScore).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return 0, 0, nil
} else {
return 0, 0, myerr.WrapErr(err)
}
}
return charmUserScore.Grade, charmUserScore.Score, nil
}
type MatchCharmUserScoreDetail struct {
mysql.Entity
MatchCharmUserScoreId mysql.ID
UserId mysql.ID
BeforeScore mysql.Num
Score mysql.Num
AfterScore mysql.Num
Type match_e.MatchCharmUserScoreDetailType
OrginId mysql.ID
}
func addMatchCharmUserScoreDetail(model *domain.Model, matchCharmUserScoreId mysql.ID, userId mysql.ID, beforeScore mysql.Num, score mysql.Num, t match_e.MatchCharmUserScoreDetailType, orginId mysql.ID) error {
if err := model.Db.Save(&MatchCharmUserScoreDetail{
MatchCharmUserScoreId: matchCharmUserScoreId,
UserId: userId,
BeforeScore: beforeScore,
Score: score,
AfterScore: beforeScore + score,
Type: t,
OrginId: orginId,
}).Error; err != nil {
return myerr.WrapErr(err)
}
return nil
}
/**
* 获取的分数同等级关系
**/
type MatchCharmSetScoreGrade struct {
mysql.Entity
*domain.Model `gorm:"-"`
MinNum mysql.Num
MaxNum mysql.Num
Grade mysql.Num
}
package user_m
import (
"gorm.io/gorm"
"hilo-user/_const/enum/match_e"
"hilo-user/domain"
"hilo-user/myerr"
"hilo-user/resource/mysql"
)
/**
* 用户财富分数
**/
type MatchWealthUserScore struct {
mysql.Entity
*domain.Model `gorm:"-"`
UserId mysql.ID
Score mysql.Num
Grade mysql.Num
}
/**
* 获取的分数同等级关系
**/
type MatchWealthSetScoreGrade struct {
mysql.Entity
*domain.Model `gorm:"-"`
MinNum mysql.Num
MaxNum mysql.Num
Grade mysql.Num
}
type MatchWealthUserScoreDetail struct {
mysql.Entity
MatchWealthUserScoreId mysql.ID
UserId mysql.ID
BeforeScore mysql.Num
Score mysql.Num
AfterScore mysql.Num
Type match_e.MatchWealthUserScoreDetailType
OrginId mysql.ID
}
func addMatchWealthUserScoreDetail(model *domain.Model, matchWealthUserScoreId mysql.ID, userId mysql.ID, beforeScore mysql.Num, score mysql.Num, t match_e.MatchWealthUserScoreDetailType, orginId mysql.ID) error {
if err := model.Db.Save(&MatchWealthUserScoreDetail{
MatchWealthUserScoreId: matchWealthUserScoreId,
UserId: userId,
BeforeScore: beforeScore,
Score: score,
AfterScore: beforeScore + score,
Type: t,
OrginId: orginId,
}).Error; err != nil {
return myerr.WrapErr(err)
}
return nil
}
//获取财富等级
func GetWealthGrade(model *domain.Model, userId mysql.ID) (uint32, uint32, error) {
var wealthUserScore MatchWealthUserScore
if err := model.Db.Model(&MatchWealthUserScore{}).Where(&MatchWealthUserScore{UserId: userId}).First(&wealthUserScore).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return 0, 0, nil
} else {
return 0, 0, myerr.WrapErr(err)
}
}
return wealthUserScore.Grade, wealthUserScore.Score, nil
}
package user_m
import (
"github.com/sirupsen/logrus"
"gorm.io/gorm"
"hilo-user/domain"
"hilo-user/domain/model/common"
"hilo-user/domain/model/res_m"
"hilo-user/resource/mysql"
"time"
)
//用户道具
type UserMedal struct {
mysql.Entity
*domain.Model `gorm:"-"`
UserId mysql.ID
MedalId uint32
//MedalType res_m2.ResMedalType
//Scope res_m2.ResMedalScope
EndTime *time.Time
}
func GetUserMedalMerge(logger *logrus.Entry, db *gorm.DB, userId mysql.ID) ([]uint32, error) {
// 缓存拿
res, _ := common.GetUserMedalMergeCache(userId)
if res != nil {
return res, nil
}
// 数据库加载
result, err := GetUserMedal(db, userId)
if err != nil {
return nil, err
}
logger.Infof("GetUserMedalMerge, user %d, %+v", userId, result)
medalTypes, medalList, err := res_m.GetUserMedalLevelMap(db)
if err != nil {
return nil, err
}
logger.Infof("GetUserMedalLevelMap, user %d, medalMap %+v", medalTypes)
logger.Infof("GetUserMedalLevelMap, user %d, medalList %+v", medalList)
maxGrades := make(map[uint8]int, 0)
maxMedalIds := make(map[uint8]uint32, 0)
for _, m := range result {
mt := medalTypes[uint64(m)]
if mt > 0 {
if r, ok := medalList[mt]; ok {
for i, j := range r {
if j == uint64(m) {
if i+1 > maxGrades[mt] {
maxGrades[mt] = i + 1
maxMedalIds[mt] = m
logger.Infof("maxGrade of %d set to %d, due to %d", mt, i+1, m)
}
break
}
}
}
}
}
logger.Infof("maxGrade %+v", maxGrades)
logger.Infof("maxMedalIds %+v", maxMedalIds)
mIds := result
result = make([]uint32, 0)
for _, m := range mIds {
mt := medalTypes[uint64(m)]
if mt == 0 || maxMedalIds[mt] == m {
result = append(result, m)
}
}
// 写入缓存
common.SetUserMedalMergeCache(userId, result)
return result, nil
}
func GetUserMedal(db *gorm.DB, userId mysql.ID) ([]uint32, error) {
rows := make([]UserMedal, 0)
if err := db.Model(&UserMedal{}).Where("user_id = ? AND (end_time >= NOW() or end_time is null)", userId).Find(&rows).Error; err != nil {
return nil, err
}
result := make([]uint32, 0)
for _, i := range rows {
result = append(result, i.MedalId)
}
return result, nil
}
package user_m
import (
"hilo-user/domain"
"hilo-user/resource/mysql"
"time"
)
//用户道具
type UserNameplate struct {
mysql.Entity
*domain.Model `gorm:"-"`
UserId mysql.ID
NameplateId uint32
//NameplateType res_m2.ResNameplateType
//Scope res_m2.ResNameplateScope
EndTime *time.Time
}
package user_m
import (
"hilo-user/_const/enum/user_e"
"hilo-user/domain"
"hilo-user/myerr"
"hilo-user/myerr/bizerr"
"hilo-user/resource/mysql"
)
//用户信息
type User struct {
mysql.Entity
*domain.Model `gorm:"-"`
ExternalId mysql.Str
Avatar mysql.Str
DefaultAvatar bool
Nick mysql.Str
Sex mysql.Sex
Birthday mysql.Timestamp
Country mysql.Str
CountryIcon mysql.Str
Language mysql.Str
Description mysql.Str
Code mysql.Str
OriginCode mysql.Str
IsPush mysql.OpenClose
IsShowAge mysql.OpenClose
Status user_e.UserStatus
DeviceType mysql.Str
LogoutTime int64
}
type UserTiny struct {
ID uint64 `json:"id,omitempty"`
ExternalId string `json:"externalId"`
Avatar string `json:"avatar"`
Nick string `json:"nick"`
Sex uint8 `json:"sex"` // 1男2女
Code string `json:"code"`
Description string `json:"description"`
Country string `json:"country"`
CountryIcon string `json:"countryIcon"`
IsPrettyCode bool `json:"isPrettyCode"` // 是否靓号
IsLogout bool `json:"isLogout"` //是否注销 true:已经注销, false:没有注销
//生日,如果是其它人用户信息,年龄则按照是否展示显示,如果是本人,年龄则按照是否存在展示
Birthday *uint64 `json:"birthday"`
}
func (u User) IsPrettyCode() bool {
return u.Code != u.OriginCode
}
//获取用户
func GetUser(model *domain.Model, id mysql.ID) (*User, error) {
var user User
if err := model.Db.WithContext(model.Context).Where(&User{
Entity: mysql.Entity{ID: id},
}).First(&user).Error; err != nil {
return nil, myerr.WrapErr(err)
}
user.Model = model
return &user, nil
}
// 通过code获取用户
func GetUserByCode(model *domain.Model, code string) (*User, error) {
if code == "" {
return nil, bizerr.InvalidParameter
}
var user User
if err := model.Db.Where(&User{Code: code}).First(&user).Error; err != nil {
return nil, myerr.WrapErr(err)
}
user.Model = model
return &user, nil
}
//获取用户信息
func GetUsersByIds(model *domain.Model, userIds []mysql.ID) ([]User, error) {
var users []User
if err := model.Db.Model(User{}).Where("id IN (?)", userIds).Find(&users).Error; err != nil {
return nil, myerr.WrapErr(err)
}
return users, nil
}
func GetUserMapByIds(model *domain.Model, userIds []mysql.ID) (map[mysql.ID]User, error) {
rows, err := GetUsersByIds(model, userIds)
if err != nil {
return nil, err
}
result := make(map[mysql.ID]User, 0)
for _, i := range rows {
result[i.ID] = i
}
return result, nil
}
// 通过externalId获取用户
func GetUserByExtId(model *domain.Model, externalId string) (*User, error) {
var user User
if err := model.Db.Where(&User{ExternalId: externalId}).First(&user).Error; err != nil {
return nil, myerr.WrapErr(err)
}
user.Model = model
return &user, nil
}
package user_m
import (
"gorm.io/gorm"
"hilo-user/_const/enum/user_e"
"hilo-user/domain"
"hilo-user/resource/mysql"
"time"
)
//用户Vip
type UserVip struct {
mysql.Entity
*domain.Model `gorm:"-"`
UserId mysql.ID
ExpireAt time.Time //结束时间
Type user_e.UserVipType //来源类型
Platform mysql.Platform
VipSubscribeOrderId mysql.ID //最后的订单ID
}
// 检查某用户是否Vip
func IsVip(userId uint64) (bool, *int64, error) {
uv, err := GetVip(mysql.Db, userId)
if err != nil {
return false, nil, err
}
if uv == nil {
return false, nil, nil
}
ts := uv.ExpireAt.Unix()
return true, &ts, nil
}
func GetVip(db *gorm.DB, userId uint64) (*UserVip, error) {
rows := make([]UserVip, 0)
err := db.Where("user_id = ? AND expire_at >= NOW()", userId).Find(&rows).Error
if err != nil {
return nil, err
}
if len(rows) > 0 {
return &rows[0], nil
}
return nil, nil
}
package event_s
import (
"errors"
"hilo-user/_const/enum/user_e"
"hilo-user/domain"
"hilo-user/domain/event/group_ev"
"hilo-user/domain/event/user_ev"
"hilo-user/domain/model/groupPower_m"
"hilo-user/domain/model/group_m"
"hilo-user/domain/model/user_m"
"hilo-user/domain/service/user_s"
"hilo-user/mycontext"
"hilo-user/req/jwt"
"hilo-user/resource/config"
"hilo-user/resource/mysql"
)
func EventInit() {
GroupInEvents()
GameEventInit()
GroupLeaveEvents()
ModelFuncInit()
}
// 用户进房事件
func GroupInEvents() {
group_ev.AddGroupInEventSync(func(model *domain.Model, event interface{}) error {
e, ok := event.(*group_ev.GroupInEvent)
if !ok {
model.Log.Errorf("AddGroupInEventSync event type err")
return nil
}
txGroupId, err := group_m.ToTxGroupId(model, e.GroupId)
if err != nil {
model.Log.Errorf("AddGroupInEventSync err:%v, imGroupId:%v", err, e.GroupId)
return nil
}
return user_s.NewGameService(mycontext.CreateMyContext(model.Cxt)).PushGameInfo("", e.ExternalId, txGroupId, 0)
})
}
// 用户退房事件
func GroupLeaveEvents() {
group_ev.AddGroupLeaveEventAsync(func(model *domain.Model, event interface{}) error {
e, ok := event.(*group_ev.GroupLeaveEvent)
if !ok || e == nil {
model.Log.Errorf("AddGroupLeaveEventAsync event type err")
return nil
}
txGroupId, err := group_m.ToTxGroupId(model, e.GroupId)
if err != nil {
model.Log.Errorf("ToTxGroupId fail:%v-%v", e.GroupId, err)
}
if err := user_s.NewGameService(model.MyContext).GameOpt(e.UserId, 0, "", e.ExternalId, "", "", txGroupId, user_e.GameOptExit, 0, false); err != nil {
model.Log.Warnf("AddGroupLeaveEventAsync GameOpt fail,e%v,err:%v", *e, err)
}
return nil
})
}
// 游戏上报事件
func GameEventInit() {
user_ev.AddReportGameInfoEventSync(func(model *domain.Model, event interface{}) error {
e, ok := event.(*user_ev.ReportGameInfoEvent)
if !ok {
model.Log.Errorf("AddReportGameInfoEventSync 消息类型错误!event:%+v", event)
return nil
}
switch e.ReportType {
case user_e.ReportTypeGameStart:
// 更新游戏信息
return user_s.NewGameService(model.MyContext).GameStart(e.GameStartObject)
//return user_m.GameStartUpdate(model, e.GameStartObject)
case user_e.ReportTypeGameSettle:
return user_s.NewGameService(model.MyContext).GameSettle(e.GameSettleObject)
}
return nil
})
}
func ModelFuncInit() {
group_m.GetGroupPowerNameByUserId = func(model *domain.Model, userId uint64) (uint64, string, error) {
return groupPower_m.GetUserGroupPower(model, userId)
}
// 编辑游戏-清理机器人
user_ev.AddGameEditEventAsync(func(model *domain.Model, event interface{}) error {
e, ok := event.(*user_ev.GameEditEvent)
if !ok {
return errors.New("GameEditEvent asset fail")
}
robots, err := user_m.GetRoomRobots(model, e.TxGroupId)
if err != nil {
model.Log.Errorf("GetRoomRobots fail:%v", err)
return err
}
model.Log.Infof("AddGameEditEventAsync event:%v,robots:%v", *e, robots)
var userIds []mysql.ID
for _, robot := range robots {
userIds = append(userIds, robot.UserId)
}
users, err := user_m.GetUserMapByIds(model, userIds)
if err != nil {
model.Log.Errorf("GetUserMapByIds fail:%v", err)
return err
}
userService := user_s.NewGameService(model.MyContext)
for i, robot := range robots {
user, ok := users[robot.UserId]
if !ok {
model.Log.Errorf("robot userId not exits:%v", robot)
continue
}
// filled
robots[i].GameOpt = userService.GameOpt // service func -> model func
robots[i].User = users[robot.UserId] // user info
robots[i].Token, _ = jwt.GenerateToken(user.ID, user.ExternalId,
config.GetConfigJWT().ISSUER_API) // jwt
// 离开
robots[i].Leave("GameEdit")
}
return nil
})
}
package event_s
import (
"encoding/json"
"golang.org/x/sync/errgroup"
"hilo-user/domain"
"hilo-user/domain/event/group_ev"
"hilo-user/domain/model/event_m"
"hilo-user/domain/service"
"hilo-user/mycontext"
"hilo-user/resource/mysql"
"runtime/debug"
)
// 每次处理500条
const BatchCount = 500
type GroupInEventService struct {
svc *service.Service
}
func NewGroupInEventService(myContext *mycontext.MyContext) *GroupInEventService {
svc := service.CreateService(myContext)
return &GroupInEventService{svc}
}
//
func (s *GroupInEventService) Consume() error {
defer func() {
if err := recover(); err != nil {
s.svc.Log.Errorf("ExceptionHandle GroupInEventService Consume SYSTEM ACTION PANIC: %v, stack: %v", err, string(debug.Stack()))
}
}()
var model = domain.CreateModel(s.svc.CtxAndDb)
events, offset, err := event_m.FetchEventGroupIn(model, BatchCount)
if err != nil {
return err
}
var wg errgroup.Group
for k := range events {
cpEvent := &event_m.EventGroupIn{
Entity: mysql.Entity{
ID: events[k].ID,
CreatedTime: events[k].CreatedTime,
UpdatedTime: events[k].UpdatedTime,
},
Proto: events[k].Proto,
Payload: events[k].Payload,
Mark: events[k].Mark,
}
wg.Go(func() error {
if cpEvent.Mark == mysql.YES {
model.Log.Warnf("already consume msg :%v", cpEvent)
return nil
}
groupInEvent := new(group_ev.GroupInEvent)
if err := json.Unmarshal(cpEvent.Payload, groupInEvent); err != nil {
model.Log.Errorf("json msg fail,event:%v,err:%v", cpEvent, err)
return nil
}
// 发布事件
if err := group_ev.PublishGroupInEvent(model, groupInEvent); err != nil {
model.Log.Errorf("PublishGroupInEvent,event:%v,err:%v", cpEvent, err)
return err
}
// 标记已经处理
cpEvent.Model = model
err = cpEvent.MarkDone()
if err != nil {
model.Log.Errorf("consume msg fail,event:%v,err:%v", cpEvent, err)
}
return err
})
}
err = wg.Wait()
if err != nil {
model.Log.Errorf("batch consume msg has fail,event,err:%v", err)
// 暂时先允许丢数据,继续mark offset
}
// 最后一次提交offset
if len(events) > 0 {
offset.MarkOffset = events[len(events)-1].ID
return offset.Persistence()
}
return nil
}
package event_s
import (
"encoding/json"
"golang.org/x/sync/errgroup"
"hilo-user/domain"
"hilo-user/domain/event/group_ev"
"hilo-user/domain/model/event_m"
"hilo-user/domain/service"
"hilo-user/mycontext"
"hilo-user/resource/mysql"
"runtime/debug"
)
type GroupLeaveEventService struct {
svc *service.Service
}
func NewGroupLeaveEventService(myContext *mycontext.MyContext) *GroupLeaveEventService {
svc := service.CreateService(myContext)
return &GroupLeaveEventService{svc}
}
//
func (s *GroupLeaveEventService) Consume() error {
defer func() {
if err := recover(); err != nil {
s.svc.Log.Errorf("ExceptionHandle GroupLeaveEventService Consume SYSTEM ACTION PANIC: %v, stack: %v", err, string(debug.Stack()))
}
}()
var model = domain.CreateModel(s.svc.CtxAndDb)
events, offset, err := event_m.FetchEventGroupLeave(model, BatchCount)
if err != nil {
return err
}
var wg errgroup.Group
for k := range events {
cpEvent := &event_m.EventGroupLeave{
Entity: mysql.Entity{
ID: events[k].ID,
CreatedTime: events[k].CreatedTime,
UpdatedTime: events[k].UpdatedTime,
},
Proto: events[k].Proto,
Payload: events[k].Payload,
Mark: events[k].Mark,
}
wg.Go(func() error {
if cpEvent.Mark == mysql.YES {
model.Log.Warnf("already consume msg :%v", cpEvent)
return nil
}
groupLeaveEvent := new(group_ev.GroupLeaveEvent)
if err := json.Unmarshal(cpEvent.Payload, groupLeaveEvent); err != nil {
model.Log.Errorf("json msg fail,event:%v,err:%v", cpEvent, err)
return nil
}
// 发布事件
if err := group_ev.PublishGroupLeaveEvent(model, groupLeaveEvent); err != nil {
model.Log.Errorf("PublishGroupLeaveEvent,event:%v,err:%v", cpEvent, err)
return err
}
// 标记已经处理
cpEvent.Model = model
err = cpEvent.MarkDone()
if err != nil {
model.Log.Errorf("consume msg fail,event:%v,err:%v", cpEvent, err)
}
return err
})
}
err = wg.Wait()
if err != nil {
model.Log.Errorf("batch consume msg has fail,event,err:%v", err)
// 暂时先允许丢数据,继续mark offset
}
// 最后一次提交offset
if len(events) > 0 {
offset.MarkOffset = events[len(events)-1].ID
return offset.Persistence()
}
return nil
}
package user_s
import (
"encoding/json"
"fmt"
"github.com/jinzhu/copier"
"hilo-user/_const/enum/diamond_e"
"hilo-user/_const/enum/group_e"
"hilo-user/_const/enum/user_e"
"hilo-user/common"
"hilo-user/cv/msg_cv"
"hilo-user/cv/user_cv"
"hilo-user/domain"
"hilo-user/domain/cache"
"hilo-user/domain/cache/user_c"
"hilo-user/domain/event/user_ev"
"hilo-user/domain/model/diamond_m"
"hilo-user/domain/model/group_m"
"hilo-user/domain/model/noble_m"
"hilo-user/domain/model/res_m"
"hilo-user/domain/model/user_m"
"hilo-user/domain/service"
"hilo-user/domain/service/group_s"
"hilo-user/mycontext"
"hilo-user/myerr"
"hilo-user/myerr/bizerr"
"hilo-user/mylogrus"
"hilo-user/req/user_req"
"hilo-user/resource/config"
"hilo-user/resource/mysql"
"hilo-user/sdk/sud"
"hilo-user/sdk/tencentyun"
"math"
"sort"
"strconv"
"time"
)
type GameService struct {
svc *service.Service
}
func NewGameService(myContext *mycontext.MyContext) *GameService {
svc := service.CreateService(myContext)
return &GameService{svc}
}
// 生成App客户端code
// param userId 用户id
// condition
// 1.jwt生成,只带userId
// 2.入库
func (s *GameService) GenClientCode(userId mysql.ID, externalId mysql.Str) (string, error) {
var model = domain.CreateModelContext(s.svc.MyContext)
code, err := generateGameJwtToken(userId, externalId, config.GetConfigGameJWT().ISSUER_CLIENT)
if err != nil {
model.Log.Errorf("generateGameJwtToken fail:%v", err)
return "", err
}
err = s.svc.Transactional(func() error {
// todo 持久化
return nil
})
return code, err
}
// 生成游戏服务器用的ssToken
// return userId ssToken expireDateMs err
func (s *GameService) GetSsToken(code string) (mysql.ID, string, int64, error) {
var model = domain.CreateModelContext(s.svc.MyContext)
userId, externalId, expiresAt, err := ParseJwtToken(code, config.GetConfigGameJWT().ISSUER_CLIENT)
if err != nil {
model.Log.Errorf("ParseJwtToken fail:%v", err)
return userId, "", expiresAt, err
}
ssToken, err := generateGameJwtToken(userId, externalId, config.GetConfigGameJWT().ISSUER_SERVER)
if err != nil {
model.Log.Errorf("generateGameJwtToken fail:%v", err)
return userId, "", expiresAt, err
}
err = s.svc.Transactional(func() error {
// todo 持久化
return nil
})
return userId, ssToken, expiresAt, err
}
// 生成游戏服务器用的ssToken
// return userId ssToken expireDateMs err
func (s *GameService) UpdateSsToken(ssToken string) (mysql.ID, string, int64, error) {
var model = domain.CreateModelContext(s.svc.MyContext)
userId, externalId, expiresAt, err := ParseJwtToken(ssToken, config.GetConfigGameJWT().ISSUER_SERVER)
// err 不需要判断过期
if err != nil && err != bizerr.GameTokenExpire {
model.Log.Errorf("ParseJwtToken fail:%v", err)
return userId, "", expiresAt, err
}
newSsToken, err := generateGameJwtToken(userId, externalId, config.GetConfigGameJWT().ISSUER_SERVER)
if err != nil {
model.Log.Errorf("generateGameJwtToken fail:%v", err)
return userId, "", expiresAt, err
}
err = s.svc.Transactional(func() error {
// todo 持久化
return nil
})
return userId, newSsToken, expiresAt, err
}
// 游戏服务器信息上报
func (s *GameService) ReportGameInfo(userInfo user_req.ReportGameInfoReq) error {
model := domain.CreateModel(s.svc.CtxAndDb)
var (
userRoundId = ""
roomId = ""
)
reportMsg, err := json.Marshal(userInfo.ReportMsg)
if err != nil {
model.Log.Errorf("ReportGameInfo err:%v, userInfo:%v", err, userInfo)
return err
}
event := &user_ev.ReportGameInfoEvent{
ReportType: userInfo.ReportType,
}
if userInfo.ReportType == user_e.ReportTypeGameStart {
event.GameStartObject = new(user_ev.GameStartObject)
err = json.Unmarshal(reportMsg, event.GameStartObject)
if err != nil {
model.Log.Errorf("ReportGameInfo err:%v, userInfo:%v", err, userInfo)
return err
}
userRoundId = event.GameStartObject.GameRoundId
roomId = event.GameStartObject.RoomId
} else if userInfo.ReportType == user_e.ReportTypeGameSettle {
event.GameSettleObject = new(user_ev.GameSettleObject)
err = json.Unmarshal(reportMsg, event.GameSettleObject)
if err != nil {
model.Log.Errorf("ReportGameInfo err:%v, userInfo:%v", err, userInfo)
return err
}
userRoundId = event.GameSettleObject.GameRoundId
roomId = event.GameSettleObject.RoomId
}
// 持久化
err = user_m.SaveGameSdkReport(domain.CreateModelContext(s.svc.MyContext), userRoundId, roomId, string(userInfo.ReportType), string(reportMsg), userInfo.ExternalId, userInfo.SsToken)
if err != nil {
return err
}
return model.Transaction(func(model *domain.Model) error {
userId, err := user_c.ToUserId(model, userInfo.ExternalId)
if err != nil {
return err
}
event.UserId = userId
return user_ev.PublishReportGameInfoEvent(model, event)
})
}
func (s *GameService) GameAdd(userId mysql.ID, extId, code, lang string, para *user_m.GameAddParam) error {
var model = domain.CreateModelContext(s.svc.MyContext)
if !user_c.LockGame(model, para.TxGroupId) {
mylogrus.MyLog.Infof("GameAdd LockGame faild TxGroupId:%v", para.TxGroupId)
return bizerr.ReqTooFrequent
}
defer user_c.UnLockGame(model, para.TxGroupId)
info, err := group_m.GetByTxGroupId(model, para.TxGroupId)
if err != nil {
return myerr.WrapErr(err)
}
// 一个房间只能存在一个还未结束的游戏
// 房主/经理/管理员可以创建游戏,其它用户点击提示“仅房间管理可以创建游戏”
role, err := group_m.GetRoleInGroup(model, userId, info.ImGroupId)
if err != nil {
return err
}
if role < common.GROUP_ADMIN {
return bizerr.GameAddNoPermissions
}
// 在麦上才能创建游戏
micUser, err := group_m.GetMicUserByExternalId(model, extId)
if err != nil {
return err
}
if micUser == nil {
return bizerr.GameAddNotOnMic
}
mgId := ""
switch para.GameType {
case 1:
mgId = user_e.MgIdLudo
case 2:
mgId = user_e.MgIdUno
}
// 创建游戏
userInfo := &user_m.GameInfo{
MgId: mgId,
GameType: para.GameType,
Mode: int32(para.Mode),
Piece: int32(para.Piece),
OnOff1: uint8(para.OnOff1),
Diamond: para.Diamond,
CreateId: userId,
TxGroupId: info.TxGroupId,
AutoMatch: uint8(para.AutoMatch),
}
err = model.Transaction(func(model *domain.Model) error {
err = user_m.Add(model, userInfo)
if err != nil {
return myerr.WrapErr(bizerr.GameHaveNoEnd)
}
// 自己作为游戏成员加入游戏
err = user_m.SaveGamePlayer(model, &user_m.GamePlayer{GameId: userInfo.Id, UserId: userId, UserCode: code, ExternalId: extId, CreatedTime: time.Now(), SeatIdx: 0})
if err != nil {
model.Log.Errorf("CreateGame err:%v", err)
return myerr.WrapErr(bizerr.GameHaveNoEndGame)
}
if userInfo.Diamond > 0 {
// 扣费
diamondAccount, err := diamond_m.CheckEnoughDiamondFrozen(model, userId, mysql.Num(userInfo.Diamond))
if err != nil {
model.Log.Errorf("CreateGame err:%v", err)
return err
}
diamondAccountDetail, err := diamondAccount.ChangeDiamondAccountDetail(diamond_e.GameJoin, userInfo.Id, mysql.Num(userInfo.Diamond))
if err != nil {
model.Log.Errorf("CreateGame err:%v", err)
return err
}
err = diamondAccountDetail.Persistent()
if err != nil {
model.Log.Errorf("CreateGame err:%v", err)
return err
}
}
// 游戏相关操作
err = s.afterCreate(model, userInfo, para.GameCode, extId)
if err != nil {
model.Log.Errorf("CreateGame err:%v", err)
return err
}
return nil
})
if err != nil {
model.Log.Errorf("CreateGame err:%v", err)
return err
}
go s.SendGamePublicMsg(model, userId, info.TxGroupId, lang, userInfo.GameType, user_e.GameOptCreate)
go s.PushGameInfo("", extId, "", userInfo.Id)
return nil
}
func (s *GameService) afterCreate(model *domain.Model, userInfo *user_m.GameInfo, userCode, extId string) error {
// 清理房间游戏
err := sud.RoomClear(domain.CreateModelNil(), userInfo.MgId, sud.RoomClearReqData{RoomId: userInfo.TxGroupId})
if err != nil {
model.Log.Errorf("CreateGame err:%v", err)
return myerr.WrapErr(err)
}
mode := int32(0)
if userInfo.MgId == user_e.MgIdLudo {
mode = userInfo.Mode
}
// 加入游戏
err = sud.UserIn(model, userInfo.MgId, sud.UserInReqData{
Code: userCode,
RoomId: userInfo.TxGroupId,
Mode: mode,
Language: "zh-CN",
SeatIndex: -1,
})
// 设置游戏
if userInfo.MgId == user_e.MgIdLudo {
if err = sud.GameSetting(model, userInfo.MgId, sud.GameSettingReqData{
RoomId: userInfo.TxGroupId,
LudoRule: sud.LudoRule{Mode: int(userInfo.Mode), ChessNum: int(userInfo.Piece), Item: int(userInfo.OnOff1)},
}); err != nil {
model.Log.Errorf("CreateGame err:%v", err)
return myerr.WrapErr(err)
}
}
if err != nil {
model.Log.Errorf("CreateGame err:%v", err)
return myerr.WrapErr(err)
}
// 准备游戏
err = sud.UserReady(model, userInfo.MgId, sud.UserReadyReqData{ExternalId: extId, IsReady: true})
if err != nil {
model.Log.Errorf("CreateGame err:%v", err)
return myerr.WrapErr(err)
}
return nil
}
func (s *GameService) GameEdit(para *user_m.GameAddParam, userId uint64, extId, code string) error {
var model = domain.CreateModelContext(s.svc.MyContext)
// 查询游戏是否存在
userInfo, err := user_m.GetGameInfo(model, para.GameId, "", "", -1, -1)
if err != nil {
mylogrus.MyLog.Errorf("GameEdit err:%v", err)
return bizerr.InvalidParameter
}
if userInfo.Id == 0 {
mylogrus.MyLog.Errorf("GameEdit err:%v", err)
return bizerr.GameNotFound
}
// 加锁
if !user_c.LockGame(model, userInfo.TxGroupId) {
mylogrus.MyLog.Infof("GameEdit LockGame faild TxGroupId:%v", para.TxGroupId)
return bizerr.ReqTooFrequent
}
defer user_c.UnLockGame(model, userInfo.TxGroupId)
// 开始了不能编辑
if userInfo.BattleStartAt > 0 {
return bizerr.GameStart
}
// 钻石不能编辑
if userInfo.Diamond != para.Diamond {
return bizerr.GameDiamondCannotEdit
}
// 放入事务
err = model.Transaction(func(model *domain.Model) error {
// 编辑user
err = user_m.Edit(model, userInfo.Id, para)
if err != nil {
model.Log.Errorf("GameEdit err:%v", err)
return err
}
// 设置游戏
if userInfo.MgId == user_e.MgIdLudo {
if err = sud.GameSetting(domain.CreateModelNil(), userInfo.MgId, sud.GameSettingReqData{
RoomId: userInfo.TxGroupId,
LudoRule: sud.LudoRule{Mode: para.Mode, ChessNum: para.Piece, Item: para.OnOff1},
}); err != nil {
model.Log.Errorf("GameEdit err:%v", err)
return myerr.WrapErr(err)
}
}
return nil
})
//err = s.svc.Transactional(func() error {
// model := domain.CreateModelContext(s.svc.MyContext)
// // 编辑user
// err = user_m.Edit(model, userInfo.Id, para)
// if err != nil {
// model.Log.Errorf("GameEdit err:%v", err)
// return err
// }
// //// 钻石前后不一致
// //if userInfo.Diamond != para.Diamond {
// // // 所有用户退款
// // err = s.userRefund(model, userInfo)
// // if err != nil {
// // model.Log.Errorf("GameEdit err:%v", err)
// // return myerr.WrapErr(err)
// // }
// // if para.Diamond > 0 {
// // // 扣费
// // diamondAccount, err := diamond_m.CheckEnoughDiamondFrozen(model, userId, mysql.Num(para.Diamond))
// // if err != nil {
// // model.Log.Errorf("GameEdit err:%v", err)
// // return err
// // }
// // diamondAccountDetail, err := diamondAccount.ChangeDiamondAccountDetail(diamond_e.GameJoin, userInfo.Id, mysql.Num(para.Diamond))
// // if err != nil {
// // model.Log.Errorf("GameEdit err:%v", err)
// // return err
// // }
// // err = diamondAccountDetail.Persistent()
// // if err != nil {
// // model.Log.Errorf("GameEdit err:%v", err)
// // return err
// // }
// // }
// // // 编辑者重新加入游戏
// // err = user_m.SaveGamePlayer(model, &user_m.GamePlayer{GameId: userInfo.Id, UserId: userId, UserCode: code, ExternalId: extId, CreatedTime: time.Now()})
// // if err != nil {
// // model.Log.Errorf("GameEdit err:%v", err)
// // return err
// // }
// // // 游戏相关操作
// // userInfo.Mode, userInfo.Piece, userInfo.OnOff1 = int32(para.Mode), int32(para.Piece), uint8(para.OnOff1)
// // err = s.afterCreate(model, userInfo, para.GameCode, extId)
// // if err != nil {
// // model.Log.Errorf("CreateGame err:%v", err)
// // return err
// // }
// // // 发布事件
// // event := &user_ev.GameEditEvent{GameId: userInfo.Id, TxGroupId: userInfo.TxGroupId}
// // if err = user_ev.PublishGameEditEvent(model, event); err != nil {
// // model.Log.Errorf("PublishGameEditEvent,event:%v,err:%v", event, err)
// // return err
// // }
// //} else {
// // // 设置游戏
// // err = sud.GameSetting(domain.CreateModelNil(), userInfo.MgId, sud.GameSettingReqData{
// // RoomId: userInfo.TxGroupId,
// // LudoRule: sud.LudoRule{Mode: para.Mode, ChessNum: para.Piece, Item: para.OnOff1},
// // })
// //}
// // 设置游戏
// if userInfo.MgId == user_e.MgIdLudo {
// if err = sud.GameSetting(domain.CreateModelNil(), userInfo.MgId, sud.GameSettingReqData{
// RoomId: userInfo.TxGroupId,
// LudoRule: sud.LudoRule{Mode: para.Mode, ChessNum: para.Piece, Item: para.OnOff1},
// }); err != nil {
// model.Log.Errorf("GameEdit err:%v", err)
// return myerr.WrapErr(err)
// }
// }
// return nil
//})
go s.PushGameInfo("", "", "", userInfo.Id)
return nil
}
func (s *GameService) PushGameInfo(sourceExtId, targetExtId, txGroupId string, userId uint64) error {
defer common.CheckGoPanic()
var model = domain.CreateModelContext(s.svc.MyContext)
var userInfo *user_m.GameInfo
var err error
if userId > 0 {
userInfo, err = user_m.GetGameInfo(model, userId, "", "", -1, -1)
} else {
// 房间当前是否有未结束的游戏
userInfo, err = user_m.GetGamingInfo(model, txGroupId)
}
if err != nil {
model.Log.Errorf("PushGameInfo err:%v", err)
return err
}
if userInfo == nil || userInfo.Id == 0 {
return nil
}
// 获取玩家信息
userPlayers, err := user_m.GetGamePlayers(model, userInfo.Id)
if err != nil {
model.Log.Errorf("PushGameInfo err:%v", err)
return err
}
// 组装信令推送消息
var creatorExternalId string
playerMsg := make([]*user_cv.GamePlayerMsg, 0, len(userPlayers))
for _, v := range userPlayers {
tmp := &user_cv.GamePlayerMsg{Status: v.Status, IsEscaped: v.IsEscaped, SeatIdx: v.SeatIdx}
userTiny, err := user_c.GetUserTinyById(model, v.UserId)
if err != nil {
model.Log.Errorf("PushGameInfo err:%v", err)
return err
}
tmp.UserTiny = userTiny
playerMsg = append(playerMsg, tmp)
if v.UserId == userInfo.CreateId {
creatorExternalId = userTiny.ExternalId
}
}
msg := &user_cv.GameMsg{
GameId: userInfo.Id,
MgId: userInfo.MgId,
GameType: userInfo.GameType,
Mode: userInfo.Mode + 1, // 客户端那边是:1.quick 2.classic
Piece: userInfo.Piece,
OnOff1: userInfo.OnOff1,
Diamond: userInfo.Diamond,
Status: userInfo.Status,
AutoMatch: userInfo.AutoMatch,
ExternalId: creatorExternalId,
Players: playerMsg,
}
jsonMsg, err := json.Marshal(msg)
if err != nil {
model.Log.Errorf("PushGameInfo err:%v", err)
return err
}
msgId := group_e.GroupGameInfoLudo
if userInfo.MgId == user_e.MgIdUno {
msgId = group_e.GroupGameInfoUno
}
// 发送腾讯云信令
group_s.SendSignalMsg(model, "", userInfo.TxGroupId, group_m.GroupSystemMsg{
MsgId: msgId,
Source: sourceExtId,
Target: targetExtId,
Content: string(jsonMsg),
}, false)
return nil
}
func (s *GameService) GameOpt(userId, userId uint64, code, extId, lang, userCode, txGroupId string, opt user_e.GameOpt, seatIdx int8, isAi bool) (err error) {
var model = domain.CreateModelContext(s.svc.MyContext)
// log
go user_m.SaveGameOptLog(model, userId, userId, opt, txGroupId)
// 查询游戏是否存在
var userInfo *user_m.GameInfo
if opt == user_e.GameOptExit {
userInfo, err = user_m.GetGamingInfoByUserId(model, userId, txGroupId)
if err != nil {
return bizerr.InvalidParameter
}
if userInfo == nil {
return
}
} else {
userInfo, err = user_m.GetGameInfo(model, userId, "", "", -1, -1)
if err != nil {
return bizerr.InvalidParameter
}
}
if userInfo.Id == 0 {
model.Log.Errorf("GameOpt userInfo.Id = 0")
return bizerr.GameNotFound
}
// 玩家信息
userr, err := user_m.GetGamePlayer(model, userInfo.Id, userId)
if err != nil {
return err
}
switch opt {
case user_e.GameOptJoin:
err = s.joinGame(model, userr, userInfo, userId, code, extId, userCode, seatIdx, isAi)
if err != nil {
model.Log.Errorf("GameOpt joinGame err:%v", err)
return err
}
if !isAi { // 机器人没有加入腾讯云,发不了公屏
go s.SendGamePublicMsg(model, userId, userInfo.TxGroupId, lang, userInfo.GameType, user_e.GameOptJoin)
}
case user_e.GameOptExit:
if userInfo.Status == uint8(user_e.GameStatusNoStart) {
if userInfo.CreateId == userr.UserId {
err = s.OwnerGameClear(model, userInfo.Id)
if err != nil {
model.Log.Errorf("GameOpt err:%v", err)
return err
}
} else {
err = s.exitGame(model, userr, userInfo)
if err != nil {
model.Log.Errorf("GameOpt err:%v", err)
return err
}
}
} else if userInfo.Status == uint8(user_e.GameStatusGaming) && userr.IsAi != 1 { // 给sdk发退出游戏
err = sud.GameEnd(domain.CreateModelNil(), userInfo.MgId, sud.GameEndReqData{
RoomId: userInfo.TxGroupId, ExternalId: userr.ExternalId,
})
if err != nil {
model.Log.Errorf("GameOpt sud.GameEnd err:%v", err)
}
err = user_m.UpdateGamePlayerExit(model, userr.Id)
if err != nil {
model.Log.Errorf("GameOpt sud.GameEnd err:%v", err)
}
if userInfo.GameType == user_e.GameTypeUno {
// 如果只是剩下一个玩家,直接结束掉
if userPlayers, err := user_m.GetGamePlayers(model, userInfo.Id); err != nil {
model.Log.Errorf("GameOpt GetGamePlayers err:%v", err)
return err
} else {
var leftPlayer int
for _, player := range userPlayers {
if player.EndAt == 0 {
leftPlayer++
}
}
if leftPlayer <= 1 {
model.Log.Infof("GameOpt left player 1,roomClear:%v,players:%v", userInfo, userPlayers)
if err := sud.RoomClear(model, userInfo.MgId, sud.RoomClearReqData{RoomId: userInfo.TxGroupId}); err != nil {
model.Log.Infof("GameOpt RoomClear fail,user:%v:%v", *userInfo, err)
}
}
}
}
}
}
go s.PushGameInfo("", "", "", userInfo.Id)
return nil
}
func (s *GameService) joinGame(model *domain.Model, userr *user_m.GamePlayer, userInfo *user_m.GameInfo, userId uint64,
code, extId, userCode string, seatIdx int8, isAi bool) error {
if !isAi && userCode == "" {
model.Log.Errorf("joinGame err:%v", bizerr.InvalidParameter)
return bizerr.InvalidParameter
}
if userr.Id > 0 {
model.Log.Errorf("joinGame err:%v", bizerr.GameAlreadyJoin)
return bizerr.GameAlreadyJoin
}
if userInfo.Status != uint8(user_e.GameStatusNoStart) {
return bizerr.GameJoinFailed
}
// 在麦上才能加入游戏
micUser, err := group_m.GetMicUserByExternalId(model, extId)
if err != nil {
return err
}
if micUser == nil {
return bizerr.GameAddNotOnMic
}
if !user_c.LockGame(model, userInfo.TxGroupId) {
mylogrus.MyLog.Infof("joinGame LockGame faild TxGroupId:%v", userInfo.TxGroupId)
return bizerr.ReqTooFrequent
}
defer user_c.UnLockGame(model, userInfo.TxGroupId)
return model.Transaction(func(model *domain.Model) error {
var isAiInt uint8
if isAi {
isAiInt = 1
}
userInfoNew, err := user_m.GetGameInfo(model, userInfo.Id, "", "", -1, -1)
if err != nil {
return bizerr.InvalidParameter
}
if userInfoNew.Status != uint8(user_e.GameStatusNoStart) { // 游戏状态不对,不能加入
return bizerr.GameJoinFailed
}
if !isAi && userInfo.Diamond > 0 {
// 扣费
diamondAccount, err := diamond_m.CheckEnoughDiamondFrozen(model, userId, mysql.Num(userInfo.Diamond))
if err != nil {
model.Log.Errorf("joinGame err:%v", err)
return err
}
diamondAccountDetail, err := diamondAccount.ChangeDiamondAccountDetail(diamond_e.GameJoin, userInfo.Id, mysql.Num(userInfo.Diamond))
if err != nil {
model.Log.Errorf("joinGame err:%v", err)
return err
}
err = diamondAccountDetail.Persistent()
if err != nil {
model.Log.Errorf("joinGame err:%v", err)
return err
}
}
userr.GameId = userInfo.Id
userr.UserId = userId
userr.UserCode = code
userr.SeatIdx = seatIdx
userr.ExternalId = extId
userr.IsAi = isAiInt
userr.CreatedTime = time.Now()
// 保存游戏玩家信息
err = user_m.SaveGamePlayer(model, userr)
if err != nil {
mylogrus.MyLog.Infof("joinGame SaveGamePlayer faild:%v, err:%v", userr, err)
return bizerr.GameHaveNoEndGame
}
err = s.sendJoinToSDK(model, userr, userInfo, isAi, userCode, extId)
if err != nil {
model.Log.Errorf("joinGame err:%v", err)
return err
}
return nil
})
}
func (s *GameService) exitGame(model *domain.Model, userr *user_m.GamePlayer, userInfo *user_m.GameInfo) error {
lockKey := user_c.GetGameKey(userInfo.TxGroupId)
if !cache.TryLock(model, lockKey, time.Millisecond*150, time.Minute*5) {
mylogrus.MyLog.Infof("exitGame LockGame faild TxGroupId:%v", userInfo.TxGroupId)
return bizerr.ReqTooFrequent
}
defer cache.UnLock(model, lockKey)
//if !user_c.LockGame(model, userInfo.TxGroupId) {
// mylogrus.MyLog.Infof("exitGame LockGame faild TxGroupId:%v", userInfo.TxGroupId)
// return bizerr.ReqTooFrequent
//}
//defer user_c.UnLockGame(model, userInfo.TxGroupId)
return model.Transaction(func(model *domain.Model) error {
err := user_m.DelGamePlayer(model, userr.Id)
if err != nil {
model.Log.Errorf("GameOpt err:%v", err)
return err
}
// 玩家退费
err = s.userrRefund(model, userr, userInfo)
if err != nil {
model.Log.Errorf("GameOpt err:%v", err)
return err
}
// 判断游戏是否还有人
players, err := user_m.GetGamePlayers(model, userInfo.Id)
if err != nil {
model.Log.Errorf("GameOpt err:%v", err)
return err
}
// 是否都是机器人
allAi := true
for _, v := range players {
if v.IsAi != 1 {
allAi = false
break
}
}
if len(players) == 0 || allAi {
// 修改所有游戏玩家状态
err = user_m.GameCloseUpdatePlayer(model, userInfo)
if err != nil {
return err
}
err = sud.RoomClear(domain.CreateModelNil(), userInfo.MgId, sud.RoomClearReqData{
RoomId: userInfo.TxGroupId,
})
// 修改游戏状态
err = user_m.GameCloseUpdate(model, userInfo)
if err != nil {
return err
}
} else {
sud.UserReady(domain.CreateModelNil(), userInfo.MgId, sud.UserReadyReqData{
ExternalId: userr.ExternalId, IsReady: false,
})
//if err != nil {
// model.Log.Errorf("GameOpt err:%v", err)
// //return err
//}
sud.UserOut(domain.CreateModelNil(), userInfo.MgId, sud.UserOutReqData{
ExternalId: userr.ExternalId,
})
//if err != nil {
// model.Log.Errorf("GameOpt err:%v", err)
// //return err
//}
}
return nil
})
}
func (s *GameService) sendJoinToSDK(model *domain.Model, userr *user_m.GamePlayer, userInfo *user_m.GameInfo, isAi bool, userCode, extId string) (err error) {
if isAi {
user, err := user_c.GetUserTinyById(model, userr.UserId)
if err != nil {
model.Log.Errorf("sendJoinToSDK err:%v", err)
return err
}
gender := "male"
if user.Sex == 2 {
gender = "female"
}
return sud.AddAi(domain.CreateModelNil(), userInfo.MgId, sud.AiAddReqData{
RoomId: userInfo.TxGroupId,
AiPlayers: []sud.AiPlayer{{user.ExternalId, user.Avatar, user.Nick, gender}},
IsReady: 1,
})
}
mode := int32(0)
if userInfo.GameType == user_e.GameTypeLudo {
mode = userInfo.Mode
}
err = sud.UserIn(domain.CreateModelNil(), userInfo.MgId, sud.UserInReqData{
Code: userCode,
RoomId: userInfo.TxGroupId,
Mode: mode,
Language: "zh-CN",
SeatIndex: -1,
})
if err != nil {
model.Log.Errorf("sendJoinToSDK err:%v", err)
return myerr.WrapErr(bizerr.GameJoinFailed)
}
err = sud.UserReady(domain.CreateModelNil(), userInfo.MgId, sud.UserReadyReqData{
ExternalId: extId,
IsReady: true,
})
if err != nil {
model.Log.Errorf("sendJoinToSDK err:%v", err)
return myerr.WrapErr(bizerr.GameJoinFailed)
}
return nil
}
func (s *GameService) GameStart(userStart *user_ev.GameStartObject) error {
return s.svc.Transactional(func() error {
model := domain.CreateModel(s.svc.CtxAndDb)
return user_m.GameStartUpdate(model, userStart)
})
}
func (s *GameService) GameSettle(userSettle *user_ev.GameSettleObject) error {
model := domain.CreateModel(s.svc.CtxAndDb)
// 查询游戏
infoKey, err := strconv.Atoi(userSettle.ReportGameInfoKey)
if err != nil {
model.Log.Errorf("GameSettle err:%v", err)
return myerr.WrapErr(bizerr.GameNotFound)
}
userInfo, err := user_m.GetGameInfo(model, uint64(infoKey), "", "", -1, 1)
if err != nil {
model.Log.Errorf("GameSettle err:%v", err)
return myerr.WrapErr(bizerr.GameNotFound)
}
if userInfo.Id == 0 {
model.Log.Errorf("GameSettle err:%v, GameRoundId:%v", bizerr.GameNotFound, userSettle.GameRoundId)
return myerr.WrapErr(bizerr.GameNotFound)
}
err = model.Transaction(func(model *domain.Model) error {
// 计算排名奖励
userExtIds, winList, userSettle2, err := s.calcAwardDiamond(userSettle, userInfo)
if err != nil {
model.Log.Errorf("GameSettle err:%v", err)
return err
}
userSettle = userSettle2
if userInfo.Diamond > 0 {
userMap, err := user_m.GetGamePlayersMap(model, userInfo.Id, userExtIds)
if err != nil {
model.Log.Errorf("GameSettle err:%v", err)
return err
}
if userInfo.Status == 1 { // 避免多发
// 发奖励
err = s.sendAwardDiamond(model, userInfo, winList, userMap)
if err != nil {
model.Log.Errorf("GameSettle err:%v", err)
return err
}
}
}
// 更新游戏信息、玩家信息
err = user_m.GameSettleUpdate(model, userSettle)
if err != nil {
model.Log.Errorf("GameSettle err:%v", err)
return err
}
return nil
})
if err != nil {
model.Log.Errorf("GameSettle err:%v", err)
return err
}
if userInfo.Status == 1 { // 因为sdk可能有多个settle上报过来,所以这里只处理游戏状态还没转变的
// 组装信令推送消息
model := domain.CreateModelContext(model.MyContext)
go s.userSettlePushMsg(model, userSettle, userInfo.TxGroupId, userInfo.CreateId, userInfo.GameType)
}
return nil
}
func (s *GameService) calcAwardDiamond(userSettle *user_ev.GameSettleObject, userInfo *user_m.GameInfo) ([]string,
[]*user_ev.PlayerResultObject, *user_ev.GameSettleObject, error) {
// 计算奖励
userExtIds := make([]string, 0)
allDiamond := float64(userInfo.Diamond) * float64(len(userSettle.Results))
winList := make([]*user_ev.PlayerResultObject, 0, len(userSettle.Results))
diamondMap := make(map[string]int64)
for _, v := range userSettle.Results {
userExtIds = append(userExtIds, v.Uid)
// 逃跑不算win
if v.IsEscaped != 1 {
tmpWin := new(user_ev.PlayerResultObject)
copier.Copy(tmpWin, &v)
tmpWin.LudoExtras = new(user_ev.LudoExtras)
json.Unmarshal([]byte(tmpWin.Extras), &tmpWin.LudoExtras)
winList = append(winList, tmpWin)
}
}
switch userInfo.GameType {
case user_e.GameTypeLudo:
// 排序:isWin -> score -> steps
sort.Slice(winList, func(i, j int) bool {
if winList[i].IsWin == 2 && winList[j].IsWin != 2 {
return true
}
if winList[i].Score > winList[j].Score {
return true
}
if winList[i].LudoExtras.Steps >= winList[j].LudoExtras.Steps {
return true
}
return false
})
if len(userSettle.Results) > 2 { // 游戏人数大于2人,2人获胜瓜分
for i, v := range winList {
switch i {
case 0:
v.Diamond = int64(math.Floor(allDiamond * 0.65))
case 1:
v.Diamond = int64(math.Floor(allDiamond * 0.30))
default:
v.Diamond = int64(userInfo.Diamond) * -1
}
diamondMap[v.Uid] = v.Diamond
}
} else {
for i, v := range winList {
if i == 0 {
v.Diamond = int64(math.Floor(allDiamond * 0.95))
} else {
v.Diamond = int64(userInfo.Diamond) * -1
}
diamondMap[v.Uid] = v.Diamond
}
}
case user_e.GameTypeUno:
// 排序:isWin -> score
sort.Slice(winList, func(i, j int) bool {
if winList[i].IsWin == 2 && winList[j].IsWin != 2 {
return true
}
return winList[i].Score > winList[j].Score
})
if len(winList) > 0 {
// 第一名获得游戏分成的95%,系统抽成5%
diamond := int64(math.Floor(allDiamond * 0.95))
winList[0].Diamond = diamond
diamondMap[winList[0].Uid] = diamond
}
}
// 给客户端的返回结果赋值
for i, v := range userSettle.Results {
if winDia, ok := diamondMap[v.Uid]; ok {
userSettle.Results[i].Diamond = winDia
if winDia > 0 {
userSettle.Results[i].IsWin = 2
}
} else {
userSettle.Results[i].Diamond = int64(userInfo.Diamond) * -1
}
}
return userExtIds, winList, userSettle, nil
}
func (s *GameService) sendAwardDiamond(model *domain.Model, userInfo *user_m.GameInfo, winList []*user_ev.PlayerResultObject, userMap map[string]*user_m.GamePlayer) error {
if userInfo.Status != uint8(user_e.GamerStatusGaming) {
return nil
}
for _, v := range winList {
if v.Diamond <= 0 || v.IsAi == 1 {
continue
}
player, ok := userMap[v.Uid]
if !ok {
mylogrus.MyLog.Errorf("sendAwardDiamond 找不到用户 extId:%v, userMap:%v", v.Uid, userMap)
continue
}
diamondAccount, err := diamond_m.GetDiamondAccountByUserId(model, player.UserId)
if err != nil {
model.Log.Errorf("GameSettle err:%v", err)
return err
}
diamondAccountDetail, err := diamondAccount.ChangeDiamondAccountDetail(diamond_e.GameAward, userInfo.Id, mysql.Num(v.Diamond))
if err != nil {
model.Log.Errorf("GameSettle err:%v", err)
return err
}
err = diamondAccountDetail.Persistent()
if err != nil {
model.Log.Errorf("GameSettle err:%v", err)
return err
}
}
return nil
}
func (s *GameService) userSettlePushMsg(model *domain.Model, userSettle *user_ev.GameSettleObject, txGroupId string, createId uint64, userType user_e.GameType) {
defer common.CheckGoPanic()
if txGroupId == "" {
model.Log.Errorf("userSettlePushMsg txGroupId is null")
return
}
createExtId := ""
// 组装信令推送消息
playerMsg := make([]*user_cv.GameAwardPlayer, 0, len(userSettle.Results))
for _, v := range userSettle.Results {
tmp := &user_cv.GameAwardPlayer{Rank: v.Rank, IsWin: v.IsWin == 2, Diamond: v.Diamond}
userTiny, err := user_c.GetUserByExternalId(model, v.Uid)
if err != nil {
model.Log.Errorf("PushGameInfo err:%v,v:%v,userSettle:%v", err, v, *userSettle)
continue
}
tmp.UserTiny = userTiny
playerMsg = append(playerMsg, tmp)
if userTiny.ID == createId {
createExtId = userTiny.ExternalId
}
}
msg := &user_cv.GameAward{
OwnerId: createExtId,
Players: playerMsg,
}
jsonMsg, err := json.Marshal(msg)
if err != nil {
model.Log.Errorf("GameSettle err:%v", err)
return
}
//time.Sleep(time.Second * 2) // 延迟两秒,避免客户端棋子没有走完就弹结算
// 发送腾讯云信令
msgId := group_e.GroupGameSettleLudo
if userType == user_e.GameTypeUno {
msgId = group_e.GroupGameSettleUno
}
group_s.SendSignalMsg(model, "", txGroupId, group_m.GroupSystemMsg{
MsgId: msgId,
Content: string(jsonMsg),
}, false)
}
func (s *GameService) SendGamePublicMsg(model *domain.Model, userId uint64, txGroupId, lang string, userType user_e.GameType, opt user_e.GameOpt) error {
defer common.CheckGoPanic()
user, err := user_c.GetUserTinyById(model, userId)
if err != nil {
model.Log.Errorf("SendGamePublicMsg err:%v", err)
return err
}
nobleLevel, err := noble_m.GetNobleLevel(model.Db, userId)
if err != nil {
model.Log.Errorf("SendGamePublicMsg err:%v", err)
return err
}
// 公屏内容
userName := "Game"
switch userType {
case user_e.GameTypeLudo:
userName = "Ludo"
case user_e.GameTypeUno:
userName = "Uno"
}
msgTmp := "Take mic and create a user"
msgId := common.MSG_ID_GAME_CREATE
msgContent := msgTmp
if opt == user_e.GameOptJoin {
msgTmp = "I joined the %s"
msgId = common.MSG_ID_GAME_JOIN
msgContent = fmt.Sprintf(msgTmp, userName)
}
if resMul, _ := res_m.GetResMultiTextBy(model.Db, msgId, lang); resMul != nil {
msgContent = resMul.Content
if opt == user_e.GameOptJoin {
msgContent = fmt.Sprintf(resMul.Content, userName)
}
}
typeScreen := group_e.GameLudoPubMsg
if userType == user_e.GameTypeUno {
typeScreen = group_e.GameUnoPubMsg
}
msg := msg_cv.GamePublicMsg{CommonPublicMsg: msg_cv.CommonPublicMsg{Type: typeScreen, GameType: userType,
ExternalId: user.ExternalId, Nick: user.Nick, Avatar: user.Avatar, NobleLevel: nobleLevel},
Msg: msgContent}
body, err := json.Marshal(msg)
if err != nil {
return myerr.WrapErr(err)
}
//发送公屏消息,
u, err := tencentyun.SendCustomMsg(model.Log, txGroupId, &user.ExternalId, string(body), group_m.GetHiloUserInfo(domain.CreateModelContext(model.MyContext), user.ExternalId))
model.Log.Infof("SendGamePublicMsg result response.MsgSeq:%v, err:%v", u, err)
return err
}
func (s *GameService) GameQuickMatch(userId uint64, externalId string, userType user_e.GameType) (txGroupId, userCode string, err error) {
var model = domain.CreateModelContext(s.svc.MyContext)
txGroupId, err = user_m.GetGamingTxGroupId(model, userType)
if err != nil {
return "", "", myerr.WrapErr(err)
}
if txGroupId == "" {
// 获取自己房间的id
groupInfo, err := group_m.GetGroupInfoByOwner(model, userId)
if err != nil {
return "", "", myerr.WrapErr(err)
}
if groupInfo == nil { // 自己没有房间
return "", "", myerr.WrapErr(bizerr.GameHaveNoMyRoom)
}
txGroupId = groupInfo.TxGroupId
}
// 获取userCode
userCode, err = s.GenClientCode(userId, externalId)
if err != nil {
return "", "", myerr.WrapErr(err)
}
return
}
// 游戏房间主人清理
func (s *GameService) OwnerGameClear(model *domain.Model, userId uint64) error {
userInfo, err := user_m.GetGameInfo(model, userId, "", "", -1, -1)
if err != nil {
model.Log.Errorf("GameClear err:%v", err)
return myerr.WrapErr(err)
}
lockKey := user_c.GetGameKey(userInfo.TxGroupId)
if !cache.TryLock(model, lockKey, time.Millisecond*150, time.Minute*5) {
mylogrus.MyLog.Infof("OwnerGameClear LockGame faild TxGroupId:%v", userInfo.TxGroupId)
return bizerr.ReqTooFrequent
}
defer cache.UnLock(model, lockKey)
//if !user_c.LockGame(model, userInfo.TxGroupId) {
// mylogrus.MyLog.Infof("OwnerGameClear LockGame faild TxGroupId:%v", userInfo.TxGroupId)
// return bizerr.ReqTooFrequent
//}
//defer user_c.UnLockGame(model, userInfo.TxGroupId)
return model.Transaction(func(model *domain.Model) error {
// 修改游戏状态
err = user_m.GameCloseUpdate(model, userInfo)
if err != nil {
model.Log.Errorf("GameClear err:%v", err)
return err
}
// 用户退费
err = s.userRefund(model, userInfo)
if err != nil {
model.Log.Errorf("GameClear err:%v", err)
return myerr.WrapErr(err)
}
// 清理房间游戏
err = sud.RoomClear(domain.CreateModelNil(), userInfo.MgId, sud.RoomClearReqData{RoomId: userInfo.TxGroupId})
if err != nil {
model.Log.Errorf("GameClear err:%v", err)
return myerr.WrapErr(err)
}
return nil
})
}
// 游戏房间清理,所有人踢出游戏,退钻,游戏置为结束;拿到房间锁之后才能调用该方法
func (s *GameService) GameClear(model *domain.Model, userId uint64) error {
userInfo, err := user_m.GetGameInfo(model, userId, "", "", -1, -1)
if err != nil {
model.Log.Errorf("GameClear err:%v", err)
return myerr.WrapErr(err)
}
// 用户退费
err = s.userRefund(model, userInfo)
if err != nil {
model.Log.Errorf("GameClear err:%v", err)
return myerr.WrapErr(err)
}
// 修改游戏状态
err = user_m.GameCloseUpdate(model, userInfo)
if err != nil {
model.Log.Errorf("GameClear err:%v", err)
return err
}
// 清理房间游戏
err = sud.RoomClear(domain.CreateModelNil(), userInfo.MgId, sud.RoomClearReqData{RoomId: userInfo.TxGroupId})
if err != nil {
model.Log.Errorf("GameClear err:%v", err)
return myerr.WrapErr(err)
}
go s.PushGameInfo("", "", "", userInfo.Id)
return nil
}
// 游戏房间玩家退钻
func (s *GameService) userRefund(model *domain.Model, userInfo *user_m.GameInfo) (err error) {
var players []*user_m.GamePlayer
if userInfo.Diamond > 0 {
players, err = user_m.GetGamePlayers(model, userInfo.Id)
if err != nil {
model.Log.Errorf("GameRefund err:%v", err)
return myerr.WrapErr(err)
}
}
err = user_m.DelGamePlayers(model, userInfo.Id)
if err != nil {
model.Log.Errorf("GameRefund err:%v", err)
return myerr.WrapErr(err)
}
if userInfo.Diamond <= 0 {
return nil
}
if userInfo.Diamond > 0 {
for _, v := range players {
err = s.userrRefund(model, v, userInfo)
if err != nil {
model.Log.Errorf("GameRefund err:%v", err)
return err
}
}
}
return nil
}
// 游戏房间玩家退钻
func (s *GameService) userrRefund(model *domain.Model, userr *user_m.GamePlayer, userInfo *user_m.GameInfo) error {
if userr.UserId <= 0 || userr.IsAi == 1 || userInfo.Diamond <= 0 {
return nil
}
// 退费
diamondAccount, err := diamond_m.GetDiamondAccountByUserId(model, userr.UserId)
if err != nil {
model.Log.Errorf("GameRefund err:%v", err)
return err
}
diamondAccountDetail, err := diamondAccount.ChangeDiamondAccountDetail(diamond_e.GameRefund, userInfo.Id, mysql.Num(userInfo.Diamond))
if err != nil {
model.Log.Errorf("GameRefund err:%v", err)
return err
}
err = diamondAccountDetail.Persistent()
if err != nil {
model.Log.Errorf("GameRefund err:%v", err)
return err
}
return nil
}
package user_s
import (
"github.com/dgrijalva/jwt-go"
"hilo-user/myerr"
"hilo-user/myerr/bizerr"
"hilo-user/resource/config"
"hilo-user/resource/mysql"
"time"
)
// 载荷,增加用户别名
type Claims struct {
UserId uint64
ExternalId string
jwt.StandardClaims
}
// 生成App用的jwt token
// issuer 外面传
func generateGameJwtToken(userId uint64, externalId string, issuer string) (string, error) {
jwtConfig := config.GetConfigGameJWT()
duration, err := time.ParseDuration(jwtConfig.EXPIRE)
if err != nil {
return "", myerr.WrapErr(err)
}
expireTime := time.Now().Add(duration)
claims := Claims{
UserId: userId,
ExternalId: externalId,
StandardClaims: jwt.StandardClaims{
ExpiresAt: expireTime.UnixNano() / 1e6, //过期时间
Issuer: issuer, //签名的发行者
},
}
tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
token, err := tokenClaims.SignedString(getGameJWTSecret())
return token, myerr.WrapErr(err)
}
//解析jwt token
func ParseJwtToken(token, issuer string) (userId mysql.ID, externalId string, expiresAt int64, err error) {
tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return getGameJWTSecret(), nil
})
if err != nil {
return
}
if tokenClaims != nil {
claims, ok := tokenClaims.Claims.(*Claims)
if ok && tokenClaims.Valid {
if time.Now().Unix() > claims.ExpiresAt {
err = bizerr.GameTokenExpire
return
}
if claims.Issuer != issuer {
err = bizerr.GameTokenInvalid
return
}
// success
userId, externalId, expiresAt = claims.UserId, claims.ExternalId, claims.ExpiresAt
}
} else {
err = bizerr.GameTokenInvalid
}
return
}
func getGameJWTSecret() []byte {
return []byte(config.GetConfigGameJWT().SECRET)
}
package group_s
import (
"encoding/json"
"github.com/sirupsen/logrus"
"hilo-user/domain"
"hilo-user/domain/model/group_m"
"hilo-user/sdk/tencentyun"
"runtime/debug"
)
// 发送群信令。入参是内部imGroupId,这里做转换
func SendSignalMsg(model *domain.Model, imGroupId, txGroupId string, msg group_m.GroupSystemMsg, async bool) {
model.Log.WithField("imGroupId:", imGroupId)
model.Log.WithField("txGroupId:", txGroupId)
groupId := txGroupId
var err error
if len(groupId) == 0 {
groupId, err = group_m.ToTxGroupId(model, imGroupId)
if err != nil {
return
}
}
buffer, err := json.Marshal(msg)
if err == nil {
str := string(buffer)
model.Log.Infof("SendSignalMsg: %s, async = %v", str, async)
if async {
go func(logger *logrus.Entry) {
defer func() {
if r := recover(); r != nil {
//打印错误堆栈信息
logger.Errorf("SendSignalMsg SYSTEM ACTION PANIC: %v, stack: %v", r, string(debug.Stack()))
}
}()
if err = tencentyun.SendSystemMsg(logger, groupId, []string{}, str); err != nil {
logger.Errorf("SendSignalMsg aync failed for %s, msgId = %d, context:%v, err:%v", groupId, msg.MsgId, str, err)
} else {
logger.Infof("SendSignalMsg aync success for %s, msgId = %d, context:%v, err:%v", groupId, msg.MsgId, str)
}
}(model.Log)
} else if err = tencentyun.SendSystemMsg(model.Log, groupId, []string{}, str); err != nil {
model.Log.Errorf("SendSignalMsg sync failed for %s, msgId = %d, context:%v, err:%v", groupId, msg.MsgId, str, err)
} else {
model.Log.Infof("SendSignalMsg sync success for %s, msgId = %d, context:%v", groupId, msg.MsgId, str)
}
} else {
model.Log.Errorln("Marshall failure, msgId = %d : %s", msg.MsgId, err.Error())
}
}
package service
import (
"hilo-user/domain"
"hilo-user/mycontext"
"hilo-user/resource/mysql"
"hilo-user/resource/redisCli"
"runtime/debug"
)
type Service struct {
*domain.CtxAndDb
}
func (service *Service) getMyContext() *mycontext.MyContext {
return service.MyContext
}
/**
* 创建服务
* @param
* @return
**/
func CreateService(myContext *mycontext.MyContext) *Service {
if myContext == nil {
return &Service{CtxAndDb: &domain.CtxAndDb{
Db: mysql.Db,
MyContext: mycontext.CreateMyContext(nil),
Redis: redisCli.GetRedis(),
}}
} else {
return &Service{CtxAndDb: &domain.CtxAndDb{
Db: mysql.Db,
MyContext: myContext,
Redis: redisCli.GetRedis(),
}}
}
}
//事务钩子回调,遇到错误,异常则回调,写service都需要钩子回调
func (service *Service) Transactional(callback func() error) error {
//异常回调
defer func() {
if err := recover(); err != nil {
service.Log.Errorf("doTransactional SYSTEM ACTION PANIC: %v, stack: %v", err, string(debug.Stack()))
service.Db.Rollback()
//为了防止给controller层造成数据错误,继续抛恐慌
panic(err)
}
}()
service.CtxAndDb.Db = mysql.Db.Begin()
err := callback()
if err != nil {
service.Db.Rollback()
return err
}
//提交
return service.Db.Commit().Error
}
module hilo-user
go 1.17
require (
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/gin-gonic/gin v1.6.3
github.com/go-redis/redis/v8 v8.11.5
github.com/go-sql-driver/mysql v1.6.0
github.com/hashicorp/consul/api v1.7.0
github.com/jinzhu/copier v0.3.5
github.com/joho/godotenv v1.3.0
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible
github.com/pkg/errors v0.9.1
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5
github.com/satori/go.uuid v1.2.0
github.com/sirupsen/logrus v1.7.0
github.com/swaggo/gin-swagger v1.2.0
github.com/swaggo/swag v1.6.7
golang.org/x/sync v0.0.0-20190423024810-112230192c58
gopkg.in/ini.v1 v1.63.2
gorm.io/driver/mysql v1.4.3
gorm.io/gorm v1.23.8
)
require (
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fatih/color v1.9.0 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-openapi/jsonpointer v0.19.3 // indirect
github.com/go-openapi/jsonreference v0.19.3 // indirect
github.com/go-openapi/spec v0.19.4 // indirect
github.com/go-openapi/swag v0.19.5 // indirect
github.com/go-playground/locales v0.13.0 // indirect
github.com/go-playground/universal-translator v0.17.0 // indirect
github.com/go-playground/validator/v10 v10.2.0 // indirect
github.com/golang/protobuf v1.3.3 // indirect
github.com/hashicorp/go-cleanhttp v0.5.1 // indirect
github.com/hashicorp/go-hclog v0.12.0 // indirect
github.com/hashicorp/go-immutable-radix v1.0.0 // indirect
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/golang-lru v0.5.0 // indirect
github.com/hashicorp/serf v0.9.3 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/jonboulle/clockwork v0.3.0 // indirect
github.com/json-iterator/go v1.1.9 // indirect
github.com/leodido/go-urn v1.2.0 // indirect
github.com/lestrrat-go/strftime v1.0.6 // indirect
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e // indirect
github.com/mattn/go-colorable v0.1.6 // indirect
github.com/mattn/go-isatty v0.0.12 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.1.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/ugorji/go/codec v1.1.7 // indirect
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 // indirect
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
golang.org/x/text v0.3.6 // indirect
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/gzip v0.0.1 h1:ezvKOL6jH+jlzdHNE4h9h8q8uMpDQjyl0NN0Jd7jozc=
github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w=
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y=
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o=
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
github.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
github.com/go-openapi/spec v0.19.4 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOAo=
github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
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.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/hashicorp/consul/api v1.7.0 h1:tGs8Oep67r8CcA2Ycmb/8BLBcJ70St44mF2X10a/qPg=
github.com/hashicorp/consul/api v1.7.0/go.mod h1:1NSuaUUkFaJzMasbfq/11wKYWSR67Xn6r2DXKhuDNFg=
github.com/hashicorp/consul/sdk v0.6.0 h1:FfhMEkwvQl57CildXJyGHnwGGM4HMODGyfjGwNM1Vdw=
github.com/hashicorp/consul/sdk v0.6.0/go.mod h1:fY08Y9z5SvJqevyZNy6WWPXiG3KwBPAvlcdx16zZ0fM=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-hclog v0.12.0 h1:d4QkX8FRTYaKaCZBoXYY8zJX2BXjWxurN/GA2tkrmZM=
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY=
github.com/hashicorp/memberlist v0.2.2 h1:5+RffWKwqJ71YPu9mWsF7ZOscZmwfasdA8kbdC7AO2g=
github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
github.com/hashicorp/serf v0.9.3 h1:AVF6JDQQens6nMHT9OGERBvK0f8rPrAGILnsKLr6lzM=
github.com/hashicorp/serf v0.9.3/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg=
github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/jonboulle/clockwork v0.3.0 h1:9BSCMi8C+0qdApAp4auwX0RkLGUjs956h0EkuQymUhg=
github.com/jonboulle/clockwork v0.3.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8=
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is=
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible h1:Y6sqxHMyB1D2YSzWkLibYKgg+SwmyFU9dF2hn6MdTj4=
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible/go.mod h1:ZQnN8lSECaebrkQytbHj4xNgtg8CR7RYXnPok8e0EHA=
github.com/lestrrat-go/strftime v1.0.6 h1:CFGsDEt1pOpFNU+TJB0nhz9jl+K0hZSLE205AhTIGQQ=
github.com/lestrrat-go/strftime v1.0.6/go.mod h1:f7jQKgV5nnJpYgdEasS+/y7EsTb8ykN2z68n3TtcTaw=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.26 h1:gPxPSwALAeHJSjarOs00QjVdV9QoBvc1D2ujQUr5BzU=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 h1:mZHayPoR0lNmnHyvtYjDeq0zlVHn9K/ZXoy17ylucdo=
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5/go.mod h1:GEXHk5HgEKCvEIIrSpFI3ozzG5xOKA2DVlEX/gGnewM=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E=
github.com/swaggo/gin-swagger v1.2.0 h1:YskZXEiv51fjOMTsXrOetAjrMDfFaXD79PEoQBOe2W0=
github.com/swaggo/gin-swagger v1.2.0/go.mod h1:qlH2+W7zXGZkczuL+r2nEBR2JTT+/lX05Nn6vPhc7OI=
github.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+tv8Y=
github.com/swaggo/swag v1.6.7 h1:e8GC2xDllJZr3omJkm9YfmK0Y56+rMO3cg0JBKNz09s=
github.com/swaggo/swag v1.6.7/go.mod h1:xDhTyuFIujYiN3DKWC/H/83xcfHp+UE/IzWWampG7Zc=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go v1.1.5-pre/go.mod h1:FwP/aQVg39TXzItUBMwnWp9T9gPQnXw4Poh4/oBQZ/0=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ugorji/go/codec v1.1.5-pre/go.mod h1:tULtS6Gy1AE1yCENaw4Vb//HLH5njI2tfCQDUqRd8fI=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392 h1:ACG4HJsFiNMf47Y4PeRoebLNy/2lXT9EtprMuTFWt1M=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18 h1:xFbv3LvlvQAmbNJFCBKRv1Ccvnh9FVsW0FX2kTWWowE=
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
gopkg.in/ini.v1 v1.63.2 h1:tGK/CyBg7SMzb60vP1M03vNZ3VDu3wGQJwn7Sxi9r3c=
gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gorm.io/driver/mysql v1.4.3 h1:/JhWJhO2v17d8hjApTltKNADm7K7YI2ogkR7avJUL3k=
gorm.io/driver/mysql v1.4.3/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c=
gorm.io/gorm v1.23.8 h1:h8sGJ+biDgBA1AD1Ha9gFCx7h8npU7AsLdlkX0n2TpE=
gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
[DATABASE]
MYSQL_HOST=47.244.34.27:3306
MYSQL_USERNAME=root
MYSQL_PASSWORD=yX0jPAhO0I4s2zlA
MYSQL_DB=hilo
[DATABASECODE]
MYSQL_HOST=47.244.34.27:3306
MYSQL_USERNAME=root
MYSQL_PASSWORD=yX0jPAhO0I4s2zlA
MYSQL_DB=hilo_code
[REDIS]
REDIS_HOST=47.244.34.27:6379
REDIS_PASSWORD=8QZ9JD1zLvPR3yHf
[JWT]
SECRET=hilo1632
ISSUER_API=hiloApi
ISSUER_Mgr=hiloMgr
EXPIRE=240h
[GAMEJWT]
SECRET=hilo1632
ISSUER_CLIENT=hiloClient
ISSUER_SERVER=hiloServer
EXPIRE=240h
[APP]
MASTER=false
BIZ_SECRET=biz
WEB_SECRET=webHilo1258
OPERATION_SECRET=operation1258236
SUPERUSER=2701,2831
OFFICIAL_STAFF=2701,2831
OFFICIAL_GROUP=@TGS#3NC2ATRHS,@TGS#33W3KNLHK
MINIMAL_VERSION_ANDROID=212
MINIMAL_VERSION_IOS=100
MODERATE=AWS
[OSS]
OSS_ACCESS_KEY_ID=LTAIxdazV2pCuV3T
OSS_ACCESS_KEY_SECRET=zuAnreAXQ6vlAKnvvmolFLfb1N5w5S
OSS_ROLE_ARN=acs:ram::1509841556585969:role/aliyunosstokengeneratorrole
OSS_END_POINT=http://oss-accelerate.aliyuncs.com
OSS_BUCKET=starvoice
OSS_CDN=https://oss.chathot.me/
OSS_EXPIRED_TIME=3600
OSS_STS_POINT=me-east-1
OSS_STS=sts-faceline-demo
OSS_STS_AES=484194d4d0f968a7
[AWS]
AWS_BUCKET=starchat
AWS_CDN=https://image.whoisamy.shop/
AWS_DIR=hilo/
CONFIDENCE=80
[RONGYUN]
RONG_CLOUD_APP_KEY=pvxdm17jpe9tr
RONG_CLOUD_APP_SECRET=rI4giiKWaBS4
RONG_CLOUD_URL=https://api-sg01.ronghub.com
[TENCENTYUN]
TENCENTYUN_APP_ID=1400548270
TENCENTYUN_KEY=321bd60f73096b059c7350f1cd97d51028850b34fa58c5c0d26bb4a19e783de8
TX_OVERSEA_APP_ID=40000066
TX_OVERSEA_KEY=3ab68ea5bddc8774d90b8c764ae71188914bd5fd06f30b28790c51e44ca7885c
[EMAS]
REGION_ID=cn-hangzhou
ACCESS_KEY_ID=LTAI4FhNPzxdzD4w6bHirL9Z
ACCESS_KEY_SECRET=OQvUJpXDrjGi3g1F2aHiAIFWIvLdbP
ANDROID_APP_KEY=30250713
ANDROID_APP_SECRET=cae7b9a9d3e54577d2c3b60bf6d23047
IOS_APP_KEY=30240346
IOS_APP_SECRET=57f33ab9ca6a957a8c659f2b0b6d1205
APNS=DEV
[AGORA]
APP_ID=fc3e087f701b4f788099e1924c3cc7b0
APP_CERTIFICATE=ff29c100a613433db82324e8411eabc8
CUSTOMER_KEY=6b132c0ff7164560a2bc53fda06ea85a
CUSTOMER_SECRET=eedad2cd16d24834990d5450ace9f1ce
CALLBACK_SECRET=n_ZizS_N8
[CHECKOUT]
AUTHORIZATION=sk_test_9b5e771c-5a3f-4a8d-a4da-31b19bd43d83
URL=https://api.sandbox.checkout.com/hosted-payments
H5=http://test.chathot.me/action/hiloHtml/22_05_30_recharge/topup.html
HILO_SECRET_KEY=sk_test_dfbaa3b6-135d-4376-9996-2089b7d8a086
[MATCH]
MATCH_FREE_TIME=60
MATCH_FREE_TIME_VIP=300
MATCH_ADD_TIME_FREE=90
MATCH_AGORA_TIME=30
MATCH_CYCLE=8
MATCH_USER_EXPIRES=480
MATCH_SUCCESS_WAIT_DURATION=10
MATCH_SUCCESS_SINGLE_WAIT_TIME_IN_SEC=12
MATCH_SUCCESS_DUAL_WAIT_TIME_IN_SEC=15
[ONLINE]
ONLINE_CYCLE=600
ONLINE_USER_EXPIRES=259200
[VIDEO]
VIDEO_DAILY_FREE_NUM=20
VIDEO_FREE_TIME=60
VIDEO_FREE_TIME_VIP=300
VIDEO_ADD_TIME_FREE=60
VIDEO_AGORA_TIME=30
VIDEO_MINUTE_NORMAL=1000
VIDEO_MINUTE_UNION=2000
[SESSION]
SESSION_DAILY_FREE_NUM=50
[BEAN]
DIAMOND_BEAN_RATE=90
[H5]
USER_LEVEL=http://test.chathot.me/action/activityhtml/hiloUserLevel/index.html
GROUP_SUPPORT=http://test.chathot.me/action/activityhtml/21_12_06/page.html
LUCKY_WHEEL=https://h5.whoisamy.shop/action/activityhtml/21_12_30/page.html
NOBLE_BUY_IOS=https://h5.whoisamy.shop/action/hiloHtml/lxt_h5/page.html
[GROUPIM]
MSG_SORT_EXPIRE=43200
MSG_SORT_SNAP=300
[GRADE]
CHARM_SPEED_VIP=15
ACTITY_SPEED_VIP=15
WEALTH_SPEED_VIP=15
[LIKE]
I_LIKE_NUM=30
I_LIKE_NUM_VIP=100
I_LIKE_NUM_NOBLE=1000
[APPLEPAY]
PASSWORD=38702750a05c4cb09c9d6ca646835634
[REGISTER]
IMEI_TOTAL=3
IMEI_OAUTH=2
ACCOUNT_IP=100
ACCOUNT_IP_DURATION=21600
[BANNER]
GIFT_BANNER_LEVEL1=100
GIFT_BANNER_LEVEL2=2000
GIFT_BANNER_LEVEL3=5000
[DIAMOND]
DAILY_LOGIN_IMEI_LIMIT=200
DAILY_LOGIN_IP_LIMIT=5
PRIVATE_GIFT_RETURN=1440
NEW_USER_INVITE_AWARD=5000
[LUCKY_WHEEL]
MINIMAL_PARTICIPANT=2
WAIT_TIMELONG=10
WINNER_DIAMOND_BANNER=100
[GROUP_CUSTOM_THEME]
PIC_LIMIT=5
DAY=10
[GIFT]
WALL_DIAMOND=10
[DAILY]
LOGIN_COMMON=5
LOGIN_VIP=300
[DAILY]
LOGIN_COMMON=5
LOGIN_VIP=300
[FRUIT_TYCOON]
POOL_RATIO=20
WATERMELON_RATIO=70
[ACTIVITY]
COUNTRY_STAR_POOL_RATIO=20
COUNTRY_STAR_ORDINARY_RATIO=20
[PAYER_MAX]
URL=https://pay-gate-uat.payermax.com/aggregate-pay-gate/api/gateway
KEY=d50d149a883b8bb6
MERCHANT_ID=SP11018326
BIZ_TYPE=CUSTOMIZE
VERSION=2.3
FRONT_CALLBACK_URL=https://www.hiloconn.com
SHOW_RESULT=1
EXPIRE_TIME=1800
LANGUAGE=en
[SUD]
API_LIST=https://sim-asc.sudden.ltd
[URL]
BIZ_HTTP=https://test.apiv1.faceline.live
\ No newline at end of file
package main
import (
"fmt"
"hilo-user/resource/consul"
"hilo-user/route"
)
const (
PORT = 9030
)
func main() {
//cron.Init() // 开启定时任务
//event_s.EventInit() // 注册事件(内部事件+mysql拟kafka)
r := route.InitRouter() // 注册路由
consul.RegisterToConsul(PORT) // 服务注册
r.Run(fmt.Sprintf(":%d", PORT)) // 启动服务
}
package mycontext
import (
"context"
uuid "github.com/satori/go.uuid"
"github.com/sirupsen/logrus"
"hilo-user/_const"
"hilo-user/mylogrus"
"strconv"
"strings"
)
/**
* 主要是完成日志打印
* @param
* @return
**/
type MyContext struct {
context.Context
Log *logrus.Entry
Cxt map[string]interface{}
}
func CreateMyContextWith(traceId interface{}) *MyContext {
cxt := map[string]interface{}{}
cxt[_const.TRACEID] = traceId
return CreateMyContext(cxt)
}
func CreateMyContext(ctx map[string]interface{}) *MyContext {
var traceId string
if traceIdTemp, ok := ctx[_const.TRACEID]; ok {
traceId, ok = traceIdTemp.(string)
} else {
traceId = strings.Replace(uuid.NewV4().String(), "-", "", -1)
}
var userId string
if userIdTemp, ok := ctx[_const.USERID]; ok {
userId = strconv.FormatUint(userIdTemp.(uint64), 10)
}
var deviceTypeStr string
if deviceTypeTemp, ok := ctx[_const.DEVICETYPE]; ok {
deviceTypeStr, ok = deviceTypeTemp.(string)
}
var sAppVersion string
if appVersionTmp, ok := ctx[_const.APP_VERSION]; ok {
sAppVersion, ok = appVersionTmp.(string)
}
var url string
if urlTmp, ok := ctx[_const.URL]; ok {
url, ok = urlTmp.(string)
}
var method string
if methodTmp, ok := ctx[_const.METHOD]; ok {
method, ok = methodTmp.(string)
}
_ctx := context.WithValue(context.Background(), "traceId", traceId)
_ctx = context.WithValue(_ctx, "userId", userId)
return &MyContext{
Context: _ctx,
Log: CreateContextLog(userId, traceId, deviceTypeStr, sAppVersion, url, method),
Cxt: ctx,
}
}
/**
* 创建上下文的日志
**/
func CreateContextLog(userId string, traceId string, deviceType string, deviceVersion string, url string, method string) *logrus.Entry {
return mylogrus.MyLog.WithFields(logrus.Fields{
_const.USERID: userId,
_const.TRACEID: traceId,
_const.DEVICETYPE: deviceType,
_const.APP_VERSION: deviceVersion,
_const.URL: url,
_const.METHOD: method,
})
}
package bizerr
import (
"hilo-user/myerr"
)
var (
// 一般性错误
TokenInvalid = myerr.NewBusinessCode(1001, "token invalid", myerr.BusinessData{})
ExternalIdNoExist = myerr.NewBusinessCode(1003, "externalId no exist", myerr.BusinessData{})
CodeNoExist = myerr.NewBusinessCode(1005, "code no exist", myerr.BusinessData{})
ParaMissing = myerr.NewBusinessCode(1006, "parameter missing", myerr.BusinessData{})
InvalidParameter = myerr.NewBusinessCode(1009, "Invalid parameter", myerr.BusinessData{})
ReqTooFrequent = myerr.NewBusinessCode(1018, "Requests are too frequent", myerr.BusinessData{})
// 钻石
DiamondNoEnough = myerr.NewBusinessCode(4000, "Insufficient diamonds", myerr.BusinessData{})
DiamondFrequency = myerr.NewBusinessCode(4001, "Diamond operation frequency too high", myerr.BusinessData{})
DiamondAccountFrozen = myerr.NewBusinessCode(4004, "Diamond Account Frozen", myerr.BusinessData{})
// 游戏
GameInvalidParameter = myerr.NewGameError(10002, "invalid parameter")
GameTokenInvalid = myerr.NewGameError(1004, "user token invalid")
GameTokenExpire = myerr.NewGameError(1005, "user token expire")
// 群组
GroupNotFound = myerr.NewBusinessCode(14001, "Group not found", myerr.BusinessData{}) // 找不到该群
// 游戏服务的错误码,6位,50XXX
GameAddNoPermissions = myerr.NewBusinessCode(50100, "Only room administrators can create users", myerr.BusinessData{}) // 权限不足
GameAddNotOnMic = myerr.NewBusinessCode(50101, "Need on mic", myerr.BusinessData{}) // 需要在麦上才能创建、加入游戏
GameHaveNoEnd = myerr.NewBusinessCode(50102, "Group user have no end", myerr.BusinessData{}) // 房间还有未结束的游戏
GameNotFound = myerr.NewBusinessCode(50103, "Game not found", myerr.BusinessData{}) //
GameStart = myerr.NewBusinessCode(50104, "Gaming", myerr.BusinessData{}) //
GameAlreadyJoin = myerr.NewBusinessCode(50105, "Already Joined", myerr.BusinessData{}) // 已经加入了游戏
GameNotJoin = myerr.NewBusinessCode(50106, "Not Join user", myerr.BusinessData{}) // 还未加入游戏
GameCannotClose = myerr.NewBusinessCode(50107, "Have no power to close user", myerr.BusinessData{}) // 没有权限关闭游戏
GameCloseGaming = myerr.NewBusinessCode(50108, "Can't close a user in progress", myerr.BusinessData{}) // 不能关闭进行中的游戏
GamePlayerNumWrong = myerr.NewBusinessCode(50109, "Game player num wrong", myerr.BusinessData{}) // 玩家数量错误
GameHaveNoMyRoom = myerr.NewBusinessCode(50110, "Have no my room", myerr.BusinessData{}) // 自己没有房间
GameHaveNoEndGame = myerr.NewBusinessCode(50111, "The last user is not over yet, cannot create/join a user", myerr.BusinessData{}) // 已经加入了其他房间的游戏
GameExitWrong = myerr.NewBusinessCode(50112, "Can not exit user", myerr.BusinessData{}) // 离开游戏失败
GameDiamondCannotEdit = myerr.NewBusinessCode(50113, "Game diamond can not edit", myerr.BusinessData{}) //
GameSettleWrong = myerr.NewBusinessCode(50114, "Game settle wrong", myerr.BusinessData{}) // 结算修改错误
GameCloseWrong = myerr.NewBusinessCode(50115, "Game close wrong", myerr.BusinessData{}) // 关闭错误
GameJoinFailed = myerr.NewBusinessCode(50116, "Join failed", myerr.BusinessData{}) // 加入失败
)
package myerr
import (
"fmt"
"github.com/pkg/errors"
"hilo-user/mylogrus"
"strconv"
)
// 成功
type Success struct {
code uint16
message string
}
func (err *Success) Error() string {
return err.message
}
// 正确的标识符
var success = &Success{code: 200, message: "OK"}
func GetSuccessCode() uint16 {
return success.code
}
func GetSuccessMsg() string {
return success.message
}
func GetSuccess() Success {
return *success
}
// 系统错误
type SysError struct {
code uint16
message string
err error
}
var sysError = &SysError{
code: 500,
message: "",
}
func (err *SysError) Error() string {
return err.err.Error()
}
func NewSysError(msg string) *SysError {
return &SysError{
code: sysError.code,
message: msg,
err: errors.New("{code:" + strconv.Itoa(int(sysError.code)) + ",message:" + msg + "}"),
}
}
func NewSysErrorF(format string, args ...interface{}) *SysError {
return NewSysError(fmt.Sprintf(format, args...))
}
func GetSysErrorCode() uint16 {
return sysError.code
}
func (sysError *SysError) GetErr() error {
return sysError.err
}
func (sysError *SysError) GetCode() uint16 {
return sysError.code
}
func (sysError *SysError) GetMsg() string {
return sysError.message
}
// 警告错误
type WaringError struct {
code uint16
message string
err error
}
var waringError = &WaringError{
code: 300,
message: "",
}
func GetWaringCode() uint16 {
return waringError.code
}
func (err *WaringError) Error() string {
return err.err.Error()
}
func NewWaring(msg string) *WaringError {
return &WaringError{
code: waringError.code,
message: msg,
err: errors.New("{code:" + strconv.Itoa(int(waringError.code)) + ",message:" + msg + "}"),
}
}
func NewWaringErrorF(format string, args ...interface{}) *WaringError {
return NewWaring(fmt.Sprintf(format, args...))
}
func (err *WaringError) GetErr() error {
return err.err
}
func (err *WaringError) GetCode() uint16 {
return err.code
}
func (err *WaringError) GetMsg() string {
return err.message
}
// 业务错误
type BusinessError struct {
code uint16
message string
err error
data BusinessData
}
func (err *BusinessError) Error() string {
return err.err.Error()
}
func (err *BusinessError) GetErr() error {
return err.err
}
func (err *BusinessError) GetCode() uint16 {
return err.code
}
func (err *BusinessError) GetMsg() string {
return err.message
}
func (err *BusinessError) GetData() BusinessData {
return err.data
}
var codes = map[uint16]string{}
// 定义必须是明确的。不可以修改,字段等同于翻译中要替换的字符
type BusinessData struct {
//剩余秒
Second int `json:"second"`
//所需数量
Num int `json:"num"`
Code string `json:"code"`
Timestamp int64 `json:"timestamp"`
//官网充值地址
CheckOutUrl string `json:"checkOutUrl"`
}
func NewBusiness(err *BusinessError) *BusinessError {
return &BusinessError{
code: err.code,
message: err.message,
err: err.err,
data: err.data,
}
}
func NewBusinessCode(code uint16, msg string, data BusinessData) *BusinessError {
if _, ok := codes[code]; ok {
mylogrus.MyLog.Error(fmt.Sprintf("错误码 %d 已经存在,请更换一个", code))
return nil
}
codes[code] = msg
return &BusinessError{
code: code,
message: msg,
err: errors.New("{code:" + strconv.Itoa(int(code)) + ",message:" + msg + "}"),
data: data,
}
}
func NewBusinessCodeNoCheck(code uint16, msg string, data BusinessData) *BusinessError {
return &BusinessError{
code: code,
message: msg,
err: errors.New("{code:" + strconv.Itoa(int(code)) + ",message:" + msg + "}"),
data: data,
}
}
// 包装日志,让日志成堆栈状态
func WrapErrWithStr(err error, msg string) error {
if h, ok := err.(*BusinessError); ok {
h.err = errors.Wrap(h.err, msg)
return h
} else if h, ok := err.(*WaringError); ok {
h.err = errors.Wrap(h.err, msg)
return h
} else if h, ok := err.(*SysError); ok {
h.err = errors.Wrap(h.err, msg)
return h
} else {
return errors.Wrap(err, msg)
}
}
func WrapErr(err error) error {
if h, ok := err.(*BusinessError); ok {
h1 := NewBusiness(h)
h1.err = errors.Wrap(h1.err, "")
return h1
} else if h, ok := err.(*WaringError); ok {
h.err = errors.Wrap(h.err, "")
return h
} else if h, ok := err.(*SysError); ok {
h.err = errors.Wrap(h.err, "")
return h
} else {
return errors.Wrap(err, "")
}
}
func WrapGameErr(err error) error {
if err == nil {
return err
}
if h, ok := err.(*GameError); ok {
return h
}
return NewGameError(500, err.Error())
}
// 系统错误
type GameError struct {
code uint16
message string
err error
}
func (err *GameError) Error() string {
return err.err.Error()
}
func (err *GameError) Code() uint16 {
return err.code
}
func NewGameError(code uint16, msg string) *GameError {
return &GameError{
code: code,
message: msg,
err: errors.New("{code:" + strconv.Itoa(int(code)) + ",message:" + msg + "}"),
}
}
package mylogrus
import (
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
"github.com/rifflock/lfshook"
"github.com/sirupsen/logrus"
"io"
"os"
"path/filepath"
"time"
)
const logDir = "/var/log/hilo/"
var filenamePrefix string
var MyLog = logrus.New()
func Info(v interface{}) {
MyLog.Info("")
}
func init() {
filenamePrefix = logDir + filepath.Base(os.Args[0]) + "."
// stderr日志重定向
MyLog.SetOutput(os.Stdout)
RewriteStderrFile()
MyLog.SetFormatter(&logrus.TextFormatter{
ForceQuote: false,
DisableQuote: true,
TimestampFormat: "2006-01-02 15:04:05.000",
FullTimestamp: true,
})
hook := lfshook.NewHook(lfshook.WriterMap{
logrus.DebugLevel: getLevelWrite(logrus.DebugLevel),
logrus.InfoLevel: getLevelWrite(logrus.InfoLevel),
logrus.WarnLevel: getLevelWrite(logrus.WarnLevel),
logrus.ErrorLevel: getLevelWrite(logrus.ErrorLevel),
logrus.FatalLevel: getLevelWrite(logrus.FatalLevel),
logrus.PanicLevel: getLevelWrite(logrus.PanicLevel),
}, &logrus.TextFormatter{ForceQuote: false, DisableQuote: true, TimestampFormat: time.RFC3339Nano})
MyLog.AddHook(hook)
MyLog.SetLevel(logrus.InfoLevel)
MyLog.SetReportCaller(true)
}
func GetInfoLog() io.Writer {
return getLevelWrite(logrus.InfoLevel)
}
func getLevelWrite(level logrus.Level) io.Writer {
var name string
switch level {
case logrus.DebugLevel:
name = "debug.log"
case logrus.InfoLevel:
name = "info.log"
case logrus.WarnLevel:
name = "warn.log"
case logrus.ErrorLevel:
name = "error.log"
case logrus.FatalLevel:
name = "fatal.log"
case logrus.PanicLevel:
name = "panic.log"
}
name = filenamePrefix + name
writer, err := rotatelogs.New(
name+".%Y%m%d%H",
rotatelogs.WithLinkName(name), // 生成软链,指向最新日志文件
rotatelogs.WithMaxAge(7*24*time.Hour), // 文件最大保存时间
rotatelogs.WithRotationTime(time.Hour), // 日志切割时间间隔
)
if err != nil {
MyLog.Fatal("Failed to create log file:", err.Error())
}
return writer
}
func GetSqlLog() io.Writer {
var name string = "sql.log"
name = filenamePrefix + name
writer, err := rotatelogs.New(
name+".%Y%m%d%H",
rotatelogs.WithLinkName(name), // 生成软链,指向最新日志文件
rotatelogs.WithMaxAge(7*24*time.Hour), // 文件最大保存时间
rotatelogs.WithRotationTime(time.Hour), // 日志切割时间间隔
)
if err != nil {
MyLog.Fatal("Failed to create log file:", err.Error())
}
return writer
}
//go:build !windows
// +build !windows
package mylogrus
import (
"fmt"
"os"
"path/filepath"
"runtime"
"syscall"
"time"
)
var stdErrFileHandler *os.File
func RewriteStderrFile() {
filename := logDir + filepath.Base(os.Args[0]) + ".stderr.log"
//if runtime.GOOS == "darwin" { // mac本地调试
// filename = "./log/hilo/" + filepath.Base(os.Args[0]) + ".stderr.log"
//}
if exits, _ := pathExists(filename); exits {
os.Rename(filename, filename+"_"+time.Now().Format("20060102150405"))
}
file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
fmt.Println(err)
return
}
stdErrFileHandler = file //把文件句柄保存到全局变量,避免被GC回收
if err = syscall.Dup2(int(file.Fd()), int(os.Stderr.Fd())); err != nil {
fmt.Println(err)
return
}
// 内存回收前关闭文件描述符
runtime.SetFinalizer(stdErrFileHandler, func(fd *os.File) {
fd.Close()
})
return
}
func pathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
//go:build windows
// +build windows
package mylogrus
import (
"os"
"path/filepath"
"time"
)
func RewriteStderrFile() {
filename := logDir + filepath.Base(os.Args[0]) + ".stderr.log"
if exits, _ := pathExists(filename); exits {
os.Rename(filename, filename+"_"+time.Now().Format("20060102150405"))
}
file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
MyLog.Errorf("stderr log in:%v,err:%v", file, err)
}
func pathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
syntax = "proto3";
package biz;
option go_package = "protocol/biz";
/* id = 1 */
message BizMessage {
uint32 type = 2;
string payLoad = 3;
}
/* id = 2 */
message BizMessageRsp {
uint32 status = 1;
}
service Transmitter {
rpc process(BizMessage) returns (BizMessageRsp) {}
}
\ No newline at end of file
syntax = "proto3";
package userCenter;
option go_package = "protocol/userCenter";
/* id = 1 */
message RouteMessage {
uint64 uid = 1;
uint32 msgType = 2;
bytes payLoad = 3;
}
/* id = 2 */
message RouteMessageRsp {
uint32 status = 1;
}
/* id = 3 */
message LoginMessage {
string proxyAddr = 1; // userProxy的地址:ip:port
string token = 2;
string clientAddr = 3; // 客户端地址(websocket):ip:port
}
/* id = 4 */
message LoginMessageRsp {
uint32 status = 1;
uint64 uid = 2;
}
/* id = 5 */
message LogoutMessage {
string clientAddr = 1; // 客户端地址(websocket):ip:port
uint64 uid = 2;
}
/* id = 6 */
message LogoutMessageRsp {
uint32 status = 1;
}
/* id = 7 */
message MulticastMessage {
repeated uint64 uids = 1;
uint32 msgType = 2;
bytes payLoad = 3;
}
/* id = 8 */
message MulticastMessageRsp {
repeated uint64 failedUids = 1;
}
/* id = 9 */
message KickMessage {
uint64 uid = 1;
string addr = 2;
}
/* id = 10 */
message KickMessageRsp {
uint32 status = 1;
}
/* id = 11 */
message BroadcastMessage {
uint32 msgType = 2;
bytes payLoad = 3;
}
/* id = 12 */
message BroadcastMessageRsp {
repeated uint64 failedUids = 1;
}
/* id = 13 */
message BizMessage {
uint64 uid = 1;
uint32 msgType = 2;
string payLoad = 3;
}
/* id = 14 */
message BizMessageRsp {
uint32 status = 1;
}
service Router {
rpc route(RouteMessage) returns (RouteMessageRsp) {}
rpc kickUser(KickMessage) returns (KickMessageRsp) {}
}
service User {
rpc login(LoginMessage) returns (LoginMessageRsp) {}
rpc logout(LogoutMessage) returns (LogoutMessageRsp) {}
rpc multicast(MulticastMessage) returns (MulticastMessageRsp) {}
rpc broadcast(BroadcastMessage) returns (BroadcastMessageRsp) {}
rpc transmit(BizMessage) returns (BizMessageRsp) {}
}
\ No newline at end of file
syntax = "proto3";
package userProxy;
option go_package = "protocol/userProxy";
/* id = 1 登录*/
message Login {
string token = 1;
}
/* id = 2 登录的回应 */
message LoginRsp {
uint32 status = 1;
}
/* id = 3 客户端心跳 */
message HeartBeat {
string externalUid = 1;
}
/* id = 4 客户端心跳的回应 */
message HeartBeatRsp {
uint32 status = 1;
}
/* id = 7 客户端上行消息 */
message BizRequest {
uint32 type = 1;
string payLoad = 2;
}
/* id = 8 客户端上行消息的应答 */
message BizResponse {
uint32 status = 1;
}
/* id == 100 匹配结果通知 waitDuration:开始/下一个时间 matchUniqueId:匹配一对的唯一标识码, status:是否是落单 singleWaitTimeInSec:单方等待连接最长时间 dualWaitTimeInSec:双方连接中最长时间*/
message MatchSuccess {
string localUserId = 1;
string remoteUserId = 2;
uint32 waitDuration = 3;
string matchUniqueId = 4;
bool status = 5;
uint32 singleWaitTimeInSec = 6;
uint32 dualWaitTimeInSec = 7;
}
/* id == 101 匹配后用户选择结果通知, failType: 只有status=2 才有值,其它为0,failType=1:等待时间到了,拒绝 failType=2:主动拒绝 */
message MatchConfirm {
uint32 status = 1;
string channelId = 2;
string token = 3;
string localUserId = 4;
string remoteUserId = 5;
uint32 remoteAgoraId = 6;
uint32 callDuration = 7;
uint32 localAgoraId = 8;
uint32 diamondBalance = 9;
string matchUniqueId = 10;
uint32 failType = 11;
}
/* id == 102 视频通话准备 */
message CallReady {
uint64 startTimestamp = 1;
uint64 endTimestamp = 2;
uint64 callDuration = 3;
string channelId = 4;
uint64 remainDiamond = 5;
}
/* id == 103 礼物加时 */
message AddTimeGift {
uint32 giftId = 1;
string token = 2;
uint32 duration = 3;
uint64 endTimestamp = 4;
string channelId = 5;
bool isSender = 6;
uint32 giftNum = 7;
string iconUrl = 8;
string svgaUrl = 9;
string senderAvatar = 10;
string receiverAvatar = 11;
}
/* id == 104 免费加时 */
message AddTimeFree {
string token = 1;
uint32 duration = 2;
uint64 endTimestamp = 3;
string channelId = 4;
uint32 senderAgoraId = 5;
}
/* id == 105 退出 */
message ConnectsQuit {
uint64 from_user_id = 1;
}
/* id == 106 连接状态 */
message ConnectStatus {
uint64 from_user_id = 1;
float user_diamonds = 2;
bool diamonds_enough = 3;
}
/* id == 107 ??? */
message ConnectsCall {
uint64 from_user_id = 1;
string rong_room_name = 2;
bool is_join = 3;
}
/* id == 108 */
message ConnectCommon {
string rong_room_name = 1;
uint64 from_user_id = 2;
string extra = 3;
string message = 4;
}
/* id == 109 召回授权弹框 */
message RecallWindow {
}
/* id == 110 视频发送 status:(1:接收到邀请, 2:接收到对方同意, 3:双方拒绝(还没接通), 4:对方挂断(接通后)diamondBalance 只有status=2,才出现)*/
message Video {
string videoUniqueId = 1;
string channelId = 2;
uint32 localAgoraId = 3;
uint32 remoteAgoraId = 4;
string agoraToken = 5;
string sendUserId = 6;
string receiveUserId = 7;
uint32 status = 8;
uint32 diamondBalance = 9;
}
/* id == 111 视频通话准备 */
message VideoCallReady {
uint64 startTimestamp = 1;
uint64 endTimestamp = 2;
uint64 callDuration = 3;
string channelId = 4;
uint64 remainDiamond = 5;
}
/* id == 112 互相喜欢 */
message LikeEach {
string remoteUserId = 1;
}
/* id == 113 喜欢我 */
message LikeMe {
string remoteUserId = 1;
string remoteNick = 2;
string channelId = 3;
}
/* id == 114 日常进入app,获取钻石 */
message DailyInAppDiamond {
uint32 diamondNum = 1;
}
/* id == 115 横幅 */
message GlobalGiftBanner {
uint32 bannerLevel = 1;
uint64 giftId = 2;
uint32 giftNum = 3;
string sendUserId = 4;
string receiveUserId = 5;
string groupId = 6;
string sendUserCode = 7;
string sendUserAvatar = 8;
string sendUserNick = 9;
string receiveUserNick = 10;
string giftPicUrl = 11;
}
/* id == 116 横幅的回应,用来测量RTT */
message GlobalGiftBannerRsp {
uint32 bannerLevel = 1;
uint64 giftId = 2;
uint32 giftNum = 3;
string sendUserId = 4;
string receiveUserId = 5;
string groupId = 6;
}
/*id==117 幸运转盘通知,客户端重新拉取查询, type:客户端不用理*/
message LuckyWheel {
string groupId = 1;
uint32 type = 2;
}
/* id == 118 幸运转盘获胜者全服广播 */
message LuckyWheelBanner {
uint32 diamondNum = 1;
string sendUserId = 2;
string groupId = 3;
string nick = 4;
string code = 5;
string avatar = 6;
}
/* id == 119 幸运转盘钻石变化 */
message LuckyWheelDiamondChange {
string groupId = 1;
}
/* id == 120 服务器配置变更 */
message ConfigChange {
uint32 type = 1;
}
/* id == 121 全局火箭横幅 */
message GlobalRocketNotice {
string groupId = 1;
string period = 2;
uint32 round = 3;
uint32 stage = 4;
string topUserIcon = 5;
string nick = 6;
string code = 7;
string avatar = 8;
}
/* id == 122 群发功能弹窗 */
message GroupSendNotice {
string senderExtId = 1;
string senderCode = 2;
uint32 senderSex = 3;
string senderAvatar = 4;
string text = 5;
string groupName = 6;
string groupCode = 7;
string groupAvatar = 8;
uint32 userInNum = 9; // 最近进入房间的人数
string groupId = 10;
}
/* id == 123 全球消息 */
message GlobalBroadcast {
string senderExtId = 1;
string senderCode = 2;
uint32 senderSex = 3;
string senderAvatar = 4;
string senderNick = 5;
string msg = 6;
string groupId = 7;
uint32 senderNobleLevel = 8;
}
/* id == 124 全球消息 */
message MicTaskFinish {
string userId = 1;
uint32 diamond = 2;
}
/* id == 125 水果机开奖通知 */
message FruitMachine {
string date = 1;
uint32 round = 2;
}
/* id == 126 贵族变化 */
message NobleChange {
}
/* id == 127 加入群组成功 */
message JoinGroup {
string groupId = 1;
string externalId = 2;
}
/* id == 128 1对1视频1分钟加时成功 */
message VideoTimeMinuteSuccess {
string token = 1;
uint32 duration = 2;
uint64 endTimestamp = 3;
string channelId = 4;
uint32 senderAgoraId = 5;
string videoUniqueId = 6;
bool isSend = 7;
uint32 sendRemainDiamond = 8;
}
/* id == 129 1对1视频1分钟加时询问检查 */
message VideoTimeMinuteCheck {
string videoUniqueId = 1;
uint32 diamond = 2;
string uuid = 3;
}
/* id == 130 1对1视频,错过 */
message VideoMiss {
uint32 totalNum = 1;
}
/* id == 131 进房,群组活动信息 */
message GroupActivity {
string ActivityId = 1;// id
uint64 StartAt = 2; // 开始时间戳,东八区时间戳
uint64 EndAt = 3; // 结束时间戳,东八区时间戳
string Banner = 4; // banner url
int32 AcType = 5; // 类型1.游戏2.比赛3.排队4.诗歌
string Theme = 6; // 活动主题
int32 PersonNum = 7; // 订阅人数
bool IsSubscribe = 8; // 我是否订阅该活动
string GroupId = 9; // 群id
}
\ No newline at end of file
syntax = "proto3";
package video;
option go_package = "protocol/video";
/* id = 1 视频加时 */
message VideoMinuteConfirm {
string videoUid = 1;
string externalId = 2;
}
/* id = 2 视频加时的应答 */
message VideoMinuteConfirmRsp {
uint32 status = 1;
}
[DATABASE]
MYSQL_HOST=ua4papc3hmgqf351pbej-rw4rm.rwlb.dubai.rds.aliyuncs.com
MYSQL_USERNAME=nextvideo
MYSQL_PASSWORD=ihlUwI4nhi9W88MI
MYSQL_DB=hilo
[DATABASECODE]
MYSQL_HOST=ua4papc3hmgqf351pbej-rw4rm.rwlb.dubai.rds.aliyuncs.com
MYSQL_USERNAME=nextvideo
MYSQL_PASSWORD=ihlUwI4nhi9W88MI
MYSQL_DB=hilo_code
[REDIS]
REDIS_HOST=r-eb3btxn8vfdsuwdbuf.redis.dubai.rds.aliyuncs.com:6379
REDIS_PASSWORD=
[JWT]
SECRET=hilo1504
ISSUER_API=hiloApi
ISSUER_Mgr=hiloMgr
EXPIRE=720h
[GAMEJWT]
SECRET=hilo1504
ISSUER_CLIENT=hiloClient
ISSUER_SERVER=hiloServer
EXPIRE=720h
[APP]
MASTER=true
BIZ_SECRET=biz
OPERATION_SECRET=operation1258236
WEB_SECRET=webHilo1258
SUPERUSER=28201,23951,133101,41,2020531
OFFICIAL_STAFF=133101,435731,486461,41
OFFICIAL_GROUP=@TGS#33W3KNLHK,@TGS#3XA5RJ5HH,@TGS#3O6PKBTH6
MINIMAL_VERSION_ANDROID=22600
MINIMAL_VERSION_IOS=22600
ROOM_MODE=AVChatRoom
MODERATE=TENCENT
[OSS]
OSS_ACCESS_KEY_ID=LTAIxdazV2pCuV3T
OSS_ACCESS_KEY_SECRET=zuAnreAXQ6vlAKnvvmolFLfb1N5w5S
OSS_ROLE_ARN=acs:ram::1509841556585969:role/aliyunosstokengeneratorrole
OSS_END_POINT=https://oss-accelerate.aliyuncs.com
OSS_BUCKET=starvoice
OSS_CDN=https://oss.chathot.me/
OSS_EXPIRED_TIME=3600
OSS_STS_POINT=me-east-1
OSS_STS=sts-faceline-demo
OSS_STS_AES=484194d4d0f968a7
[AWS]
AWS_BUCKET=starchat
AWS_CDN=https://image.whoisamy.shop/
AWS_DIR=hilo/
CONFIDENCE=80
[RONGYUN]
RONG_CLOUD_APP_KEY=uwd1c0sxu5t41
RONG_CLOUD_APP_SECRET=vo9djozyBl9bZ
RONG_CLOUD_URL=https://api-sg01.ronghub.com
[TENCENTYUN]
TENCENTYUN_APP_ID=1400487464
TENCENTYUN_KEY=cb4c1f2e3398a88e0e9468b403f671e60d66a564df86f7db925c6ab4f18b66e5
TX_OVERSEA_APP_ID=40000066
TX_OVERSEA_KEY=3ab68ea5bddc8774d90b8c764ae71188914bd5fd06f30b28790c51e44ca7885c
[EMAS]
REGION_ID=cn-hangzhou
ACCESS_KEY_ID=LTAIdQZv5H1kNZp5
ACCESS_KEY_SECRET=UnwY0ClDkqBMLwPx3OJJiLYyk9xYLO
ANDROID_APP_KEY=30774987
ANDROID_APP_SECRET=297a0f231f1286a2de9aab097cc8ff5c
IOS_APP_KEY=30790728
IOS_APP_SECRET=4fd69ca084c67d4b5a8d15452f0af26a
APNS=PRODUCT
[AGORA]
APP_ID=6291d069123642d9929a49c734c50719
APP_CERTIFICATE=d5de40350aa54e60bcdce90c71e9598a
CUSTOMER_KEY=6b132c0ff7164560a2bc53fda06ea85a
CUSTOMER_SECRET=eedad2cd16d24834990d5450ace9f1ce
[CHECKOUT]
AUTHORIZATION=sk_fca6e213-b7df-4bd7-99f4-7c0a9f7c778c
URL=https://api.checkout.com/hosted-payments
H5=https://h5.whoisamy.shop/action/hiloHtml/22_05_30_recharge/topup.html
HILO_SECRET_KEY=sk_26806bf4-e6e3-45e2-a093-c72c5b53eaf5
[MATCH]
MATCH_FREE_TIME=60
MATCH_FREE_TIME_VIP=60
MATCH_ADD_TIME_FREE=90
MATCH_AGORA_TIME=30
MATCH_CYCLE=8
MATCH_USER_EXPIRES=480
MATCH_SUCCESS_WAIT_DURATION=10
MATCH_SUCCESS_SINGLE_WAIT_TIME_IN_SEC=12
MATCH_SUCCESS_DUAL_WAIT_TIME_IN_SEC=15
[ONLINE]
ONLINE_CYCLE=600
ONLINE_USER_EXPIRES=259200
[VIDEO]
VIDEO_DAILY_FREE_NUM=20
VIDEO_FREE_TIME=60
VIDEO_FREE_TIME_VIP=300
VIDEO_ADD_TIME_FREE=60
VIDEO_AGORA_TIME=30
VIDEO_MINUTE_NORMAL=60
VIDEO_MINUTE_UNION=60
[SESSION]
SESSION_DAILY_FREE_NUM=50
GUILD_USER_HELLO_DAY=30
[BEAN]
DIAMOND_BEAN_RATE=90
[H5]
USER_LEVEL=https://h5.whoisamy.shop/action/hiloHtml/hiloUserLevel/index.html
GROUP_SUPPORT=https://h5.whoisamy.shop/action/activityhtml/21_12_06/page.html
LUCKY_WHEEL=https://h5.whoisamy.shop/action/activityhtml/21_12_30/page.html
WEEKLY_STAR=https://h5.whoisamy.shop/action/hiloHtml/lxt_h5/page.html
WEEKLY_CP=https://h5.whoisamy.shop/action/hiloHtml/Valentines_22_1_18/page.html
COUNTRY_STAR=https://h5.whoisamy.shop/action/hiloHtml/22_08_18_nation_star/page.html
#NOBLE_BUY_IOS=https://h5.whoisamy.shop/action/hiloHtml/22_05_26_buy_nobility/page.html
NOBLE_BUY_IOS=https://h5.whoisamy.shop/action/hiloHtml/lxt_h5/page.html
GUILD_DATA_URL=https://h5.whoisamy.shop/action/hiloHtml/22_10_18_app_data_coins/index.html
MGR_GUILD_DATA_URL=https://h5.whoisamy.shop/action/hiloHtml/22_10_18_app_data_coins/union.html
RANKING_PINK_DIAMOND_URL=https://h5.whoisamy.shop/action/activitiesPage/2022_10_17HiloLiveH5/index.html
[GROUPIM]
MSG_SORT_EXPIRE=1209600
MSG_SORT_SNAP=300
MSG_PARALLEL_SIZE=10
[GRADE]
CHARM_SPEED_VIP=15
ACTITY_SPEED_VIP=15
WEALTH_SPEED_VIP=15
[LIKE]
I_LIKE_NUM=500
I_LIKE_NUM_VIP=1000
I_LIKE_NUM_NOBLE=5000
[APPLEPAY]
PASSWORD=38702750a05c4cb09c9d6ca646835634
[REGISTER]
IMEI_TOTAL=5
IMEI_OAUTH=2
ACCOUNT_IP=100
ACCOUNT_IP_DURATION=21600
[BANNER]
GIFT_BANNER_LEVEL1=1000
GIFT_BANNER_LEVEL2=3000
GIFT_BANNER_LEVEL3=5000
[DIAMOND]
DAILY_LOGIN_IMEI_LIMIT=5
DAILY_LOGIN_IP_LIMIT=30
PRIVATE_GIFT_RETURN=1440
NEW_USER_INVITE_AWARD=5000
[LUCKY_WHEEL]
MINIMAL_PARTICIPANT=2
WAIT_TIMELONG=10
WINNER_DIAMOND_BANNER=200
[GROUP_CUSTOM_THEME]
PIC_LIMIT=50
DAY=10
[GIFT]
WALL_DIAMOND=2000
[DAILY]
LOGIN_COMMON=10
LOGIN_VIP=1000
[FRUIT_TYCOON]
BIG_WINNER_THRESDHOLD=30000
BIG_WINNER_LOW=10000
BIG_WINNER_HIGH=20000
POOL_RATIO=5
WATERMELON_RATIO=24
[ACTIVITY]
COUNTRY_STAR_POOL_RATIO=20
COUNTRY_STAR_ORDINARY_RATIO=20
[RISK_CONTROL]
USER_QPS_LIMIT=128
USER_URL_QPS_LIMIT=64
[PAYER_MAX]
URL=https://pay-gate.payermax.com/aggregate-pay-gate/api/gateway
KEY=503a970695756efa
MERCHANT_ID=SP11018326
BIZ_TYPE=CUSTOMIZE
VERSION=2.3
FRONT_CALLBACK_URL=https://www.hiloconn.com
SHOW_RESULT=1
EXPIRE_TIME=1800
LANGUAGE=en
[SUD]
API_LIST=https://asc.sudden.ltd
[URL]
BIZ_HTTP=https://apiv1.faceline.live
\ No newline at end of file
package jwt
import (
"github.com/dgrijalva/jwt-go"
"hilo-user/myerr"
"hilo-user/resource/config"
"time"
)
// 载荷,增加用户别名
type Claims struct {
UserId uint64
ExternalId string
jwt.StandardClaims
}
//生成token
func GenerateToken(userId uint64, externalId string, issuer string) (string, error) {
jwtConfig := config.GetConfigJWT()
duration, err := time.ParseDuration(jwtConfig.EXPIRE)
if err != nil {
return "", myerr.WrapErr(err)
}
expireTime := time.Now().Add(duration)
claims := Claims{
UserId: userId,
ExternalId: externalId,
StandardClaims: jwt.StandardClaims{
ExpiresAt: expireTime.Unix(), //过期时间
Issuer: issuer, //签名的发行者
},
}
tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
token, err := tokenClaims.SignedString(GetJWTSecret())
return token, myerr.WrapErr(err)
}
func GetJWTSecret() []byte {
return []byte(config.GetConfigJWT().SECRET)
}
//解析token
func ParseToken(token string) (*Claims, error) {
tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return GetJWTSecret(), nil
})
if err != nil {
return nil, myerr.WrapErr(err)
}
if tokenClaims != nil {
claims, ok := tokenClaims.Claims.(*Claims)
if ok && tokenClaims.Valid {
return claims, nil
}
}
return nil, myerr.WrapErr(err)
}
package req
import (
"github.com/gin-gonic/gin"
"hilo-user/_const"
"hilo-user/domain"
"hilo-user/domain/cache/user_c"
"hilo-user/domain/model/res_m"
"hilo-user/mycontext"
"hilo-user/myerr/bizerr"
"hilo-user/resource/mysql"
)
func GetUserId(c *gin.Context) (mysql.ID, error) {
if userIdStr, ok := c.Keys[_const.USERID]; ok {
userId := userIdStr.(uint64)
return userId, nil
}
return 0, bizerr.ParaMissing
}
// 获取userId和externalId
func GetUserIdAndExtId(c *gin.Context, myContext *mycontext.MyContext) (mysql.ID, string, error) {
if userIdStr, ok := c.Keys[_const.USERID]; ok {
userId := userIdStr.(uint64)
externalId, ok := c.Get(_const.EXTERNAL_ID)
if ok {
return userId, externalId.(string), nil
} else {
user, err := user_c.GetUserTinyById(domain.CreateModelContext(myContext), userId)
if err != nil {
return 0, "", bizerr.ExternalIdNoExist
}
return userId, user.ExternalId, nil
}
}
return 0, "", bizerr.ParaMissing
}
// 获取userId和ExtId和code
func GetUserIdExtIdCode(c *gin.Context, myContext *mycontext.MyContext) (mysql.ID, string, string, error) {
if userIdStr, ok := c.Keys[_const.USERID]; ok {
userId := userIdStr.(uint64)
userCode, ok1 := c.Get(_const.CODE)
externalId, ok2 := c.Get(_const.EXTERNAL_ID)
if ok1 && ok2 {
return userId, externalId.(string), userCode.(string), nil
} else {
user, err := user_c.GetUserTinyById(domain.CreateModelContext(myContext), userId)
if err != nil {
return 0, "", "", bizerr.ExternalIdNoExist
}
return userId, user.ExternalId, user.Code, nil
}
}
return 0, "", "", bizerr.ParaMissing
}
// 获取userId和LANGUAGE
func GetUserIdLang(c *gin.Context, myContext *mycontext.MyContext) (mysql.ID, string, error) {
if userIdStr, ok := c.Keys[_const.USERID]; ok {
userId := userIdStr.(uint64)
lang, ok := c.Get(_const.LANGUAGE)
if ok {
return userId, lang.(string), nil
} else {
model := domain.CreateModelContext(myContext)
user, err := user_c.GetUserTinyById(model, userId)
if err != nil {
return 0, "", bizerr.ExternalIdNoExist
}
lang, err := res_m.GetLangeByCountry(model.Db, user.Country)
if err != nil {
return 0, "", err
}
return userId, lang, nil
}
}
return 0, "", bizerr.ParaMissing
}
package config
import (
"github.com/joho/godotenv"
"gopkg.in/ini.v1"
"hilo-user/mylogrus"
"os"
"runtime"
"strconv"
)
// 数据库的配置
type MysqlConfig struct {
MYSQL_HOST string
MYSQL_USERNAME string
MYSQL_PASSWORD string
MYSQL_DB string
}
type MysqlCodeConfig struct {
MYSQL_HOST string
MYSQL_USERNAME string
MYSQL_PASSWORD string
MYSQL_DB string
}
// redis配置
type RedisConfig struct {
REDIS_HOST string
REDIS_PASSWORD string
}
// jwt
type JwtConfig struct {
SECRET string
ISSUER_API string
ISSUER_MGR string
EXPIRE string
}
// jwt
type GameJwtConfig struct {
SECRET string
ISSUER_CLIENT string
ISSUER_SERVER string
EXPIRE string
}
// oss
type OssConfig struct {
OSS_ACCESS_KEY_ID string
OSS_ACCESS_KEY_SECRET string
OSS_ROLE_ARN string
OSS_END_POINT string
OSS_BUCKET string
OSS_CDN string
OSS_EXPIRED_TIME uint
OSS_STS_POINT string
OSS_STS string
OSS_STS_AES string
}
// aws
type AwsConfig struct {
AWS_BUCKET string
AWS_CDN string
AWS_DIR string
CONFIDENCE float32
}
// APP
type AppConfig struct {
BIZ_SECRET string
WEB_SECRET string
OPERATION_SECRET string
SUPERUSER string
OFFICIAL_GROUP string
MINIMAL_VERSION_ANDROID int
MINIMAL_VERSION_IOS int
MODERATE string
}
// googlePay 配置信息
type GooglePayConfig struct {
JsonKey []byte
}
// 融云
type RongyunConfig struct {
RONG_CLOUD_APP_KEY string
RONG_CLOUD_APP_SECRET string
RONG_CLOUD_URL string
}
// 腾讯云
type TencentyunConfig struct {
TENCENTYUN_APP_ID int
TENCENTYUN_KEY string
TX_OVERSEA_APP_ID int
TX_OVERSEA_KEY string
}
// emas
type EmasConfig struct {
ANDROID_APP_KEY string
ANDROID_APP_SECRET string
REGION_ID string
ACCESS_KEY_ID string
ACCESS_KEY_SECRET string
IOS_APP_KEY string
IOS_APP_SECRET string
APNS string
}
// 声网
type AgoraConfig struct {
APP_ID string
APP_CERTIFICATE string
CUSTOMER_KEY string
CUSTOMER_SECRET string
}
// 匹配的配置
type MatchConfig struct {
//一开始匹配的默认时长(单位:秒)
MATCH_FREE_TIME int
//一开始匹配的默认时长(单位:秒)VIP
MATCH_FREE_TIME_VIP int
//免费加时的时长 (单位:秒)
MATCH_ADD_TIME_FREE int
//匹配的声网的延迟加时(单位:秒)
MATCH_AGORA_TIME int
//匹配周期(单位:秒)
MATCH_CYCLE int
//过期时间(单位:秒),用户redisCache时间
MATCH_USER_EXPIRES int
//pb match_success中, wait_duration 开始/下一个时间(单位:秒)
MATCH_SUCCESS_WAIT_DURATION uint32
//pb match_success中, single_wait_time_in_sec 单方等待连接最长时间(单位:秒)
MATCH_SUCCESS_SINGLE_WAIT_TIME_IN_SEC uint32
//pb match_success中, dual_wait_time_in_sec 双方连接中最长时间(单位:秒)
MATCH_SUCCESS_DUAL_WAIT_TIME_IN_SEC uint32
}
// 在线
type OnlineConfig struct {
//在线周期
ONLINE_CYCLE int
//在线过期时间
ONLINE_USER_EXPIRES int
}
// 1对1视频
type VideoConfig struct {
VIDEO_DAILY_FREE_NUM int
//一开始匹配的默认时长(单位:秒)
VIDEO_FREE_TIME int
//一开始匹配的默认时长(单位:秒),vip
VIDEO_FREE_TIME_VIP int
//免费加时的时长 (单位:秒)
VIDEO_ADD_TIME_FREE int
//声网的延迟加时(单位:秒)
VIDEO_AGORA_TIME int
//1分钟视频,普通用户价格
VIDEO_MINUTE_NORMAL int
//1分钟视频,公会用户价格
VIDEO_MINUTE_UNION int
}
// 会话
type SessionConfig struct {
SESSION_DAILY_FREE_NUM int
GUILD_USER_HELLO_DAY int
}
type BeanConfig struct {
DIAMOND_BEAN_RATE int
}
type H5Config struct {
USER_LEVEL string
GROUP_SUPPORT string
LUCKY_WHEEL string
WEEKLY_STAR string
WEEKLY_CP string
COUNTRY_STAR string
NOBLE_BUY_IOS string
GUILD_DATA_URL string
MGR_GUILD_DATA_URL string
RANKING_PINK_DIAMOND_URL string
}
type GroupImConfig struct {
MSG_SORT_EXPIRE int
MSG_SORT_SNAP int
MSG_PARALLEL_SIZE int
}
type GradeConfig struct {
//魅力速度
CHARM_SPEED_VIP int
//活跃
ACTITY_SPEED_VIP int
//财富
WEALTH_SPEED_VIP int
}
type LikeConfig struct {
//喜欢人数
I_LIKE_NUM int
//喜欢人数VIP
I_LIKE_NUM_VIP int
//喜欢人数贵族
I_LIKE_NUM_NOBLE int
}
type ApplePayConfig struct {
PASSWORD string
}
type RegisterConfig struct {
IMEI_TOTAL int
IMEI_OAUTH int
ACCOUNT_IP int
ACCOUNT_IP_DURATION int
}
type BannerConfig struct {
GIFT_BANNER_LEVEL1 int
GIFT_BANNER_LEVEL2 int
GIFT_BANNER_LEVEL3 int
}
type DiamondConfig struct {
DAILY_LOGIN_IMEI_LIMIT int
DAILY_LOGIN_IP_LIMIT int
PRIVATE_GIFT_RETURN int
NEW_USER_INVITE_AWARD uint32
}
type LuckWheelConfig struct {
MINIMAL_PARTICIPANT int // 轮盘开始最少需要的参与人数
WAIT_TIMELONG int // 等待轮盘开始的时长(分钟)
WINNER_DIAMOND_BANNER int //全服广播钻石门槛
}
// 自定义主题
type GroupCustomThemeConfig struct {
PIC_LIMIT int //图片数量
DAY int //有效天数
}
type GiftConfig struct {
WALL_DIAMOND int //上礼物墙,礼物钻石金额
}
type DailyConfig struct {
LOGIN_COMMON int
LOGIN_VIP int
}
type FruitTycoonConfig struct {
BIG_WINNER_THRESDHOLD uint
BIG_WINNER_LOW uint
BIG_WINNER_HIGH uint
POOL_RATIO uint32
WATERMELON_RATIO uint32
}
type ActivityConfig struct {
COUNTRY_STAR_POOL_RATIO uint32
COUNTRY_STAR_ORDINARY_RATIO uint32
}
type CheckoutConfig struct {
URL string
AUTHORIZATION string
H5 string
HILO_SECRET_KEY string
}
type RiskControlConfig struct {
USER_QPS_LIMIT int64
USER_URL_QPS_LIMIT int64
}
type PayerMaxConfig struct {
URL string
KEY string
MERCHANT_ID string
BIZ_TYPE string
VERSION string
FRONT_CALLBACK_URL string
SHOW_RESULT string
EXPIRE_TIME string
LANGUAGE string
}
type SudConfig struct {
API_LIST string
}
type URLConfig struct {
BIZ_HTTP string
}
const (
LOCAL string = "local"
DEBUG string = "debug"
RELEASE string = "release"
)
var mysqlConfigData MysqlConfig
var mysqlCodeConfigData MysqlCodeConfig
var redisConfigData RedisConfig
var jwtConfigData JwtConfig
var userJwtConfigData GameJwtConfig
var appConfigData AppConfig
var ossConfigData OssConfig
var awsConfigData AwsConfig
var googlePayData GooglePayConfig
var rongyunData RongyunConfig
var tencentyunData TencentyunConfig
var emasData EmasConfig
var agora AgoraConfig
var matchData MatchConfig
var onlineData OnlineConfig
var sessionData SessionConfig
var videoData VideoConfig
var beanData BeanConfig
var h5Data H5Config
var groupImData GroupImConfig
var gradeData GradeConfig
var likeData LikeConfig
var applePayData ApplePayConfig
var registerData RegisterConfig
var bannerConfig BannerConfig
var diamondConfig DiamondConfig
var luckyWheelConfig LuckWheelConfig
var groupCustomThemeConfig GroupCustomThemeConfig
var giftConfig GiftConfig
var dailyConfig DailyConfig
var fruitTycoonConfig FruitTycoonConfig
var activityConfig ActivityConfig
var checkoutConfig CheckoutConfig
var riskControl RiskControlConfig
var payerMaxConfig PayerMaxConfig
var mode string
var master bool
var sudConfig SudConfig
var urlConfig URLConfig
func GetConfigMysql() MysqlConfig {
return mysqlConfigData
}
func GetConfigMysqlCode() MysqlCodeConfig {
return mysqlCodeConfigData
}
func GetConfigRedis() RedisConfig {
return redisConfigData
}
func GetConfigJWT() JwtConfig {
return jwtConfigData
}
func GetConfigGameJWT() GameJwtConfig {
return userJwtConfigData
}
func GetConfigApp() AppConfig {
return appConfigData
}
func GetConfigOss() OssConfig {
return ossConfigData
}
func GetConfigAws() AwsConfig {
return awsConfigData
}
func GetConfigGooglePay() GooglePayConfig {
return googlePayData
}
func GetMode() string {
return mode
}
func AppIsRelease() bool {
return GetMode() == RELEASE
}
func AppIsLocal() bool {
return GetMode() == LOCAL
}
func IsMaster() bool {
return master
}
func GetOssCDN() string {
return ossConfigData.OSS_CDN
}
func GetRongyunAppKey() string {
return rongyunData.RONG_CLOUD_APP_KEY
}
func GetRongyunAppSecret() string {
return rongyunData.RONG_CLOUD_APP_SECRET
}
func GetRongyunUrl() string {
return rongyunData.RONG_CLOUD_URL
}
func GetTencentyunAppId() int {
return tencentyunData.TENCENTYUN_APP_ID
}
func GetTencentyunKey() string {
return tencentyunData.TENCENTYUN_KEY
}
func GetTxOverSeaAppId() int {
return tencentyunData.TX_OVERSEA_APP_ID
}
func GetTxOverSeaAppKey() string {
return tencentyunData.TX_OVERSEA_KEY
}
func GetEmasRegionId() string {
return emasData.REGION_ID
}
func GetEmasAccessKeyId() string {
return emasData.ACCESS_KEY_ID
}
func GetEmasAccessKeySecret() string {
return emasData.ACCESS_KEY_SECRET
}
func GetEmasAndroidAppKey() string {
return emasData.ANDROID_APP_KEY
}
func GetEmasIosAppKey() string {
return emasData.IOS_APP_KEY
}
func GetEmasApns() string {
return emasData.APNS
}
func GetAgoraAppId() string {
return agora.APP_ID
}
func GetAgoraAppCertificate() string {
return agora.APP_CERTIFICATE
}
func GetAgoraCustomerKey() string {
return agora.CUSTOMER_KEY
}
func GetAgoraCustomerSecret() string {
return agora.CUSTOMER_SECRET
}
func GetMatchConfig() *MatchConfig {
return &matchData
}
func GetOnlineConfig() *OnlineConfig {
return &onlineData
}
func GetSessionConfig() SessionConfig {
return sessionData
}
func GetVideoConfig() VideoConfig {
return videoData
}
func GetBeanConfig() BeanConfig {
return beanData
}
func GetH5Config() H5Config {
return h5Data
}
func GetGroupImConfig() GroupImConfig {
return groupImData
}
func GetGradeConfig() GradeConfig {
return gradeData
}
func GetLikeConfig() LikeConfig {
return likeData
}
func GetApplePayConfig() ApplePayConfig {
return applePayData
}
func GetRegisterConfig() RegisterConfig {
return registerData
}
func GetBannerConfig() BannerConfig {
return bannerConfig
}
func GetDiamondConfig() DiamondConfig {
return diamondConfig
}
func GetLuckyWheelConfig() LuckWheelConfig {
return luckyWheelConfig
}
func GetGroupCustomThemeConfig() GroupCustomThemeConfig {
return groupCustomThemeConfig
}
func GetGiftConfig() GiftConfig {
return giftConfig
}
func GetDailyConfig() DailyConfig {
return dailyConfig
}
func GetFruitTycoonConfig() FruitTycoonConfig {
return fruitTycoonConfig
}
func GetActivityConfig() ActivityConfig {
return activityConfig
}
func GetCheckoutConfig() CheckoutConfig {
return checkoutConfig
}
func GetRiskControlConfig() RiskControlConfig {
return riskControl
}
func GetPayerMaxConfig() PayerMaxConfig {
return payerMaxConfig
}
func GetSudConfig() SudConfig {
return sudConfig
}
func GetUrlConfig() URLConfig {
return urlConfig
}
func init() {
str, _ := os.Getwd()
mylogrus.MyLog.Info(str)
envDir := ".env"
//加载环境变量
if err := godotenv.Load(envDir); err != nil {
mylogrus.MyLog.Fatalf("Error loading .env err:%v", err)
}
//获取环境变量
mode = os.Getenv("MODE")
var err error
master, _ = strconv.ParseBool(os.Getenv("MASTER"))
mylogrus.MyLog.Infof("My role is %t", master)
iniDir := mode + ".ini"
if runtime.GOOS == "darwin" { // mac本地调试
iniDir = "/var/log/hilo/" + iniDir
}
//根据环境变量获取具体的配置,实现多环境配置
//var conf *ini.File
conf, err := ini.LoadSources(ini.LoadOptions{IgnoreInlineComment: true}, iniDir)
if err != nil {
mylogrus.MyLog.Fatal(err)
}
//加载mysql的配置
if err := conf.Section("DATABASE").MapTo(&mysqlConfigData); err != nil {
mylogrus.MyLog.Fatal(err)
}
if err := conf.Section("DATABASECODE").MapTo(&mysqlCodeConfigData); err != nil {
mylogrus.MyLog.Fatal(err)
}
if err := conf.Section("REDIS").MapTo(&redisConfigData); err != nil {
mylogrus.MyLog.Fatal(err)
}
if err := conf.Section("JWT").MapTo(&jwtConfigData); err != nil {
mylogrus.MyLog.Fatal(err)
}
if err := conf.Section("GAMEJWT").MapTo(&userJwtConfigData); err != nil {
mylogrus.MyLog.Fatal(err)
}
if err := conf.Section("APP").MapTo(&appConfigData); err != nil {
mylogrus.MyLog.Fatal(err)
} else {
mylogrus.MyLog.Infof("APP: %+v", appConfigData)
}
if err := conf.Section("OSS").MapTo(&ossConfigData); err != nil {
mylogrus.MyLog.Fatal(err)
}
if err := conf.Section("AWS").MapTo(&awsConfigData); err != nil {
mylogrus.MyLog.Fatal(err)
} else {
if awsConfigData.CONFIDENCE <= 50 {
awsConfigData.CONFIDENCE = 80
}
mylogrus.MyLog.Infof("AWS: %+v", awsConfigData)
}
if err := conf.Section("RONGYUN").MapTo(&rongyunData); err != nil {
mylogrus.MyLog.Fatal(err)
}
if err := conf.Section("TENCENTYUN").MapTo(&tencentyunData); err != nil {
mylogrus.MyLog.Fatal(err)
} else {
mylogrus.MyLog.Info("TENCENTYUN: ", tencentyunData)
}
if err := conf.Section("EMAS").MapTo(&emasData); err != nil {
mylogrus.MyLog.Fatal(err)
}
if err := conf.Section("AGORA").MapTo(&agora); err != nil {
mylogrus.MyLog.Fatal(err)
}
if err := conf.Section("MATCH").MapTo(&matchData); err != nil {
mylogrus.MyLog.Fatal(err)
}
if err := conf.Section("ONLINE").MapTo(&onlineData); err != nil {
mylogrus.MyLog.Fatal(err)
}
if err := conf.Section("SESSION").MapTo(&sessionData); err != nil {
mylogrus.MyLog.Fatal(err)
}
if err := conf.Section("VIDEO").MapTo(&videoData); err != nil {
mylogrus.MyLog.Fatal(err)
}
if err := conf.Section("BEAN").MapTo(&beanData); err != nil {
mylogrus.MyLog.Fatal(err)
}
if err := conf.Section("H5").MapTo(&h5Data); err != nil {
mylogrus.MyLog.Fatal(err)
}
if err := conf.Section("GROUPIM").MapTo(&groupImData); err != nil {
mylogrus.MyLog.Fatal(err)
}
if err := conf.Section("GRADE").MapTo(&gradeData); err != nil {
mylogrus.MyLog.Fatal(err)
}
if err := conf.Section("LIKE").MapTo(&likeData); err != nil {
mylogrus.MyLog.Fatal(err)
}
if err := conf.Section("APPLEPAY").MapTo(&applePayData); err != nil {
mylogrus.MyLog.Fatal(err)
}
if err := conf.Section("REGISTER").MapTo(&registerData); err != nil {
mylogrus.MyLog.Fatal(err)
}
if err := conf.Section("BANNER").MapTo(&bannerConfig); err != nil {
mylogrus.MyLog.Fatal(err)
}
if err := conf.Section("DIAMOND").MapTo(&diamondConfig); err != nil {
mylogrus.MyLog.Fatal(err)
} else {
if diamondConfig.NEW_USER_INVITE_AWARD <= 0 {
diamondConfig.NEW_USER_INVITE_AWARD = 5000
}
}
if err := conf.Section("LUCKY_WHEEL").MapTo(&luckyWheelConfig); err != nil {
mylogrus.MyLog.Fatal(err)
}
if err := conf.Section("GROUP_CUSTOM_THEME").MapTo(&groupCustomThemeConfig); err != nil {
mylogrus.MyLog.Fatal(err)
}
if err := conf.Section("GIFT").MapTo(&giftConfig); err != nil {
mylogrus.MyLog.Fatal(err)
}
if err := conf.Section("DAILY").MapTo(&dailyConfig); err != nil {
mylogrus.MyLog.Fatal(err)
}
if err := conf.Section("CHECKOUT").MapTo(&checkoutConfig); err != nil {
mylogrus.MyLog.Fatal(err)
}
if err := conf.Section("PAYER_MAX").MapTo(&payerMaxConfig); err != nil {
mylogrus.MyLog.Fatal(err)
}
if err := conf.Section("FRUIT_TYCOON").MapTo(&fruitTycoonConfig); err != nil {
mylogrus.MyLog.Fatal(err)
} else {
// 防止未配置或配置错误
if fruitTycoonConfig.BIG_WINNER_LOW <= 0 {
fruitTycoonConfig.BIG_WINNER_LOW = 10000
}
if fruitTycoonConfig.BIG_WINNER_HIGH <= 0 {
fruitTycoonConfig.BIG_WINNER_HIGH = 20000
}
if fruitTycoonConfig.POOL_RATIO <= 0 || fruitTycoonConfig.POOL_RATIO > 100 {
fruitTycoonConfig.POOL_RATIO = 20
}
if fruitTycoonConfig.WATERMELON_RATIO <= 0 || fruitTycoonConfig.WATERMELON_RATIO > 100 {
fruitTycoonConfig.WATERMELON_RATIO = 70
}
mylogrus.MyLog.Infof("FRUIT_TYCOON: %+v", fruitTycoonConfig)
}
if err := conf.Section("ACTIVITY").MapTo(&activityConfig); err != nil {
mylogrus.MyLog.Fatal(err)
} else {
// 防止未配置或配置错误
if activityConfig.COUNTRY_STAR_POOL_RATIO <= 0 {
activityConfig.COUNTRY_STAR_POOL_RATIO = 20
}
if activityConfig.COUNTRY_STAR_ORDINARY_RATIO <= 0 {
activityConfig.COUNTRY_STAR_ORDINARY_RATIO = 20
}
mylogrus.MyLog.Infof("ACTIVITY: %+v", activityConfig)
}
if err := conf.Section("RISK_CONTROL").MapTo(&riskControl); err != nil {
mylogrus.MyLog.Fatal(err)
} else {
if riskControl.USER_QPS_LIMIT <= 0 {
riskControl.USER_QPS_LIMIT = 128
}
if riskControl.USER_URL_QPS_LIMIT <= 0 {
riskControl.USER_URL_QPS_LIMIT = 64
}
mylogrus.MyLog.Infof("RISK_CONTROL: %+v", riskControl)
}
if err := conf.Section("SUD").MapTo(&sudConfig); err != nil {
mylogrus.MyLog.Fatal(err)
}
if err := conf.Section("URL").MapTo(&urlConfig); err != nil {
mylogrus.MyLog.Fatal(err)
}
}
package consul
import (
"fmt"
"github.com/hashicorp/consul/api"
"hilo-user/_const"
"hilo-user/mylogrus"
"net/http"
"os"
"time"
)
const (
RegisterName = "hiloGame"
RegisterTag = "游戏中心"
)
// 异步注册到consul
func RegisterToConsul(port int) {
go register(port, false)
go selfCheck(port)
}
func consulCheck(w http.ResponseWriter, r *http.Request) {
_, _ = fmt.Fprintln(w, "consulCheck")
}
func register(port int, retry bool) {
client, err := api.NewClient(api.DefaultConfig()) //非默认情况下需要设置实际的参数
if err != nil {
mylogrus.MyLog.Errorf("RegisterToConsul Fail:%v", err)
return
}
if client == nil {
mylogrus.MyLog.Errorf("Fail to get consul client.")
return
}
mylogrus.MyLog.Infof("RegisterToConsul:%v-%v", client, err)
checkPort := port + 1000
registration := new(api.AgentServiceRegistration)
hostName, _ := os.Hostname()
registration.ID = fmt.Sprintf("%s-%s", RegisterName, hostName)
registration.Name = RegisterName
registration.Port = port
registration.Tags = []string{RegisterTag}
myIp, myNodeName := "", ""
if localIp, err := _const.GetClientIp(); err != nil {
mylogrus.MyLog.Fatalln("local ip not found", err)
} else {
myIp = localIp
}
mylogrus.MyLog.Infof("My ip is %s, nodeName: %s\n", myIp, myNodeName)
registration.Address = myIp
registration.Check = &api.AgentServiceCheck{
HTTP: fmt.Sprintf("http://localhost:%d%s", checkPort, "/check"),
Timeout: "3s",
Interval: "5s",
DeregisterCriticalServiceAfter: "30s", //check失败后30秒删除本服务
}
err = client.Agent().ServiceRegister(registration)
if err != nil {
mylogrus.MyLog.Errorf("register server error :%v ", err)
return
}
if !retry {
http.HandleFunc("/check", consulCheck)
if err = http.ListenAndServe(fmt.Sprintf(":%d", checkPort), nil); err != nil {
mylogrus.MyLog.Warnf("check server error :%v ", err)
return
}
}
}
// 自愈检查
// 启动后每一分钟检查一次
// 首次启动不执行
func selfCheck(port int) {
ticker := time.NewTicker(time.Minute)
defer ticker.Stop()
for {
select {
case <-ticker.C:
client, err := api.NewClient(api.DefaultConfig()) //非默认情况下需要设置实际的参数
if err != nil {
mylogrus.MyLog.Errorf("RegisterToConsul Fail:%v", err)
break
}
if client == nil {
mylogrus.MyLog.Errorf("Fail to get consul client.")
break
}
cataLog := client.Catalog()
if cataLog == nil {
mylogrus.MyLog.Errorf("No catalog.")
break
}
services, _, err := cataLog.Service(RegisterName, "", nil)
if err != nil {
mylogrus.MyLog.Errorf("%v", err)
break
}
if len(services) == 0 {
mylogrus.MyLog.Errorf("%s not found.", RegisterName)
go register(port, true) // 重新注册
} else {
mylogrus.MyLog.Infof("%s check success %v", RegisterName, services[0])
}
}
}
}
package consul
import (
"fmt"
consulapi "github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/api/watch"
"hilo-user/mylogrus"
"sync"
)
type ServiceCallback func(serviceStatus map[string]map[string][]string) // service->status->addrs
// 定义watcher
type Watcher struct {
Address string // consul agent 的地址:"127.0.0.1:8500"
Wp *watch.Plan // 总的Services变化对应的Plan
watchers map[string]*watch.Plan // 对已经进行监控的service作个记录
RWMutex *sync.RWMutex
}
// 将consul新增的service加入,并监控
func (w *Watcher) registerServiceWatcher(serviceName string, callback ServiceCallback) error {
// watch endpoint 的请求参数,具体见官方文档:https://www.consul.io/docs/dynamic-app-config/watches#service
wp, err := watch.Parse(map[string]interface{}{
"type": "service",
"service": serviceName,
})
if err != nil {
return err
}
// 定义service变化后所执行的程序(函数)handler
wp.Handler = func(idx uint64, data interface{}) {
switch d := data.(type) {
case []*consulapi.ServiceEntry:
var serviceStatus = make(map[string]map[string][]string)
for _, i := range d {
if data, ok := serviceStatus[i.Service.Service]; ok {
data[i.Checks.AggregatedStatus()] = append(data[i.Checks.AggregatedStatus()], fmt.Sprintf("%s:%d", i.Service.Address, i.Service.Port))
} else {
serviceStatus[i.Service.Service] = make(map[string][]string)
serviceStatus[i.Service.Service][i.Checks.AggregatedStatus()] = append(serviceStatus[i.Service.Service][i.Checks.AggregatedStatus()],
fmt.Sprintf("%s:%d", i.Service.Address, i.Service.Port))
}
}
// 回传到外面
callback(serviceStatus)
mylogrus.MyLog.Infof("consul service status: %s", serviceStatus)
}
}
// 启动监控
go wp.Run(w.Address)
// 对已启动监控的service作一个记录
w.RWMutex.Lock()
w.watchers[serviceName] = wp
w.RWMutex.Unlock()
return nil
}
func newWatcher(watchType string, opts map[string]string, consulAddr string, callback ServiceCallback) (*Watcher, error) {
var options = map[string]interface{}{
"type": watchType,
}
// 组装请求参数。(监控类型不同,其请求参数不同)
for k, v := range opts {
options[k] = v
}
wp, err := watch.Parse(options)
if err != nil {
return nil, err
}
w := &Watcher{
Address: consulAddr,
Wp: wp,
watchers: make(map[string]*watch.Plan),
RWMutex: new(sync.RWMutex),
}
wp.Handler = func(idx uint64, data interface{}) {
switch d := data.(type) {
// 这里只实现了对services的监控,其他监控的data类型判断参考:https://github.com/dmcsorley/avast/blob/master/consul.go
// services
case map[string][]string:
for i := range d {
// 如果该service已经加入到ConsulRegistry的services里监控了,就不再加入 或者i 为 "consul"的字符串
// 为什么会多一个consul,参考官方文档services监听的返回值:https://www.consul.io/docs/dynamic-app-config/watches#services
if _, ok := w.watchers[i]; ok || i == "consul" {
continue
}
w.registerServiceWatcher(i, callback)
}
// 从总的services变化中找到不再监控的service并停止
w.RWMutex.RLock()
watches := w.watchers
w.RWMutex.RUnlock()
// remove unknown services from watchers
for i, svc := range watches {
if _, ok := d[i]; !ok {
svc.Stop()
delete(watches, i)
}
}
default:
mylogrus.MyLog.Errorf("不能判断监控的数据类型: %v", &d)
}
}
return w, nil
}
func RegisterWatcher(watchType string, opts map[string]string, consulAddr string, callback ServiceCallback) error {
w, err := newWatcher(watchType, opts, consulAddr, callback)
if err != nil {
mylogrus.MyLog.Error(err)
return err
}
defer w.Wp.Stop()
if err = w.Wp.Run(consulAddr); err != nil {
mylogrus.MyLog.Errorf("RegisterWatcher err: %v", err)
return err
}
return nil
}
package mysql
import "time"
type EntityI interface {
GetID() ID
//用于判断数据是否进行持久化
IsLazyLoad() bool
//默认值为false true:代表要移除数据
CheckDel() bool
//检查是否唯一键冲突,依旧更新
CheckOnDuplicateKeyUPDATE() bool
//检查是否唯一键冲突,则不插入
CheckOnDuplicateKeyIGNORE() bool
//更新乐观锁 默认值为false true:乐观锁更新
CheckUpdateVersion() bool
//更新条件.
CheckUpdateCondition() bool
//获取版本号
GetUpdateVersionBefore() uint
//更新情况
GetUpdateCondition() string
//save 动作排除字段
GetOmit() []string
}
type Entity struct {
ID ID `gorm:"primary_key"`
CreatedTime time.Time `gorm:"->"`
UpdatedTime time.Time `gorm:"->"`
lazyLoad bool `gorm:"-"`
del bool `gorm:"-"`
onDuplicateKeyUPDATE bool `gorm:"-"`
onDuplicateKeyIGNORE bool `gorm:"-"`
updateVersionFlag bool `gorm:"-"`
updateVersionBefore uint `gorm:"-"`
updateCondition string `gorm:"-"`
omit []string `gorm:"-"` //更新排除
updateColumns map[string]interface{} `gorm:"-"` //更新字段
}
func (t *Entity) GetID() ID {
return t.ID
}
func (t *Entity) IsLazyLoad() bool {
return t.lazyLoad
}
func (t *Entity) SetLasyLoad() {
t.lazyLoad = true
}
func (t *Entity) SetDel() {
t.del = true
}
func (t *Entity) CheckDel() bool {
return t.del
}
func (t *Entity) SetOnDuplicateKeyUPDATE() {
t.onDuplicateKeyUPDATE = true
}
func (t *Entity) SetOnDuplicateKeyIGNORE() {
t.onDuplicateKeyIGNORE = true
}
func (t *Entity) CheckOnDuplicateKeyUPDATE() bool {
return t.onDuplicateKeyUPDATE
}
func (t *Entity) CheckOnDuplicateKeyIGNORE() bool {
return t.onDuplicateKeyIGNORE
}
func (t *Entity) SetCheckUpdateVersionBefore(versionBefore uint) {
t.updateVersionBefore = versionBefore
t.updateVersionFlag = true
}
func (t *Entity) SetCheckUpdateCondition(condition string) {
t.updateCondition = condition
}
func (t *Entity) CheckUpdateVersion() bool {
return t.updateVersionFlag
}
func (t *Entity) CheckUpdateCondition() bool {
return t.updateCondition != ""
}
func (t *Entity) GetUpdateCondition() string {
return t.updateCondition
}
func (t *Entity) GetUpdateVersionBefore() uint {
return t.updateVersionBefore
}
func (t *Entity) GetOmit() []string {
return t.omit
}
func (t *Entity) SetOmit(omit []string) {
t.omit = omit
}
func (t *Entity) SetUpdateColumns(updateColumns map[string]interface{}) {
t.updateColumns = updateColumns
}
func (t *Entity) GetUpdateColumns() map[string]interface{} {
return t.updateColumns
}
package mysql
import (
"context"
"fmt"
. "gorm.io/gorm/logger"
"gorm.io/gorm/utils"
"time"
)
func MyNew(writer Writer, config Config) Interface {
var (
infoStr = "%s[info] "
warnStr = "%s[warn] "
errStr = "%s[error] "
traceStr = "%s[%.3fms] [rows:%v] %s"
traceWarnStr = "%s %s[%.3fms] [rows:%v] %s"
traceErrStr = "%s %s[%.3fms] [rows:%v] %s"
)
//if config.Colorful {
// infoStr = Green + "%s\n" + Reset + Green + "[info] " + Reset
// warnStr = BlueBold + "%s\n" + Reset + Magenta + "[warn] " + Reset
// errStr = Magenta + "%s\n" + Reset + Red + "[error] " + Reset
// traceStr = Green + "%s\n" + Reset + Yellow + "[%.3fms] " + BlueBold + "[rows:%v]" + Reset + " %s"
// traceWarnStr = Green + "%s " + Yellow + "%s\n" + Reset + RedBold + "[%.3fms] " + Yellow + "[rows:%v]" + Magenta + " %s" + Reset
// traceErrStr = RedBold + "%s " + MagentaBold + "%s\n" + Reset + Yellow + "[%.3fms] " + BlueBold + "[rows:%v]" + Reset + " %s"
//}
myTraceStr := " traceId:%v userId:%v"
infoStr += myTraceStr
warnStr += myTraceStr
errStr += myTraceStr
traceStr += myTraceStr
traceWarnStr += myTraceStr
traceErrStr += myTraceStr
return &myLogger{
Writer: writer,
Config: config,
infoStr: infoStr,
warnStr: warnStr,
errStr: errStr,
traceStr: traceStr,
traceWarnStr: traceWarnStr,
traceErrStr: traceErrStr,
}
}
type myLogger struct {
Writer
Config
infoStr, warnStr, errStr string
traceStr, traceErrStr, traceWarnStr string
}
// LogMode log mode
func (l *myLogger) LogMode(level LogLevel) Interface {
newlogger := *l
newlogger.LogLevel = level
return &newlogger
}
// Info print info
func (l myLogger) Info(ctx context.Context, msg string, data ...interface{}) {
if l.LogLevel >= Info {
l.Printf(l.infoStr+msg, append([]interface{}{utils.FileWithLineNum()}, data...)...)
}
}
// Warn print warn messages
func (l myLogger) Warn(ctx context.Context, msg string, data ...interface{}) {
if l.LogLevel >= Warn {
l.Printf(l.warnStr+msg, append([]interface{}{utils.FileWithLineNum()}, data...)...)
}
}
// Error print error messages
func (l myLogger) Error(ctx context.Context, msg string, data ...interface{}) {
if l.LogLevel >= Error {
l.Printf(l.errStr+msg, append([]interface{}{utils.FileWithLineNum()}, data...)...)
}
}
// Trace print sql message
func (l myLogger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) {
traceId, userId := ctx.Value("traceId"), ctx.Value("userId")
if l.LogLevel > Silent {
elapsed := time.Since(begin)
switch {
case err != nil && l.LogLevel >= Error:
sql, rows := fc()
if rows == -1 {
l.Printf(l.traceErrStr, utils.FileWithLineNum(), err, float64(elapsed.Nanoseconds())/1e6, "-", sql, traceId, userId)
} else {
l.Printf(l.traceErrStr, utils.FileWithLineNum(), err, float64(elapsed.Nanoseconds())/1e6, rows, sql, traceId, userId)
}
case elapsed > l.SlowThreshold && l.SlowThreshold != 0 && l.LogLevel >= Warn:
sql, rows := fc()
slowLog := fmt.Sprintf("SLOW SQL >= %v", l.SlowThreshold)
if rows == -1 {
l.Printf(l.traceWarnStr, utils.FileWithLineNum(), slowLog, float64(elapsed.Nanoseconds())/1e6, "-", sql, traceId, userId)
} else {
l.Printf(l.traceWarnStr, utils.FileWithLineNum(), slowLog, float64(elapsed.Nanoseconds())/1e6, rows, sql, traceId, userId)
}
case l.LogLevel == Info:
sql, rows := fc()
if rows == -1 {
l.Printf(l.traceStr, utils.FileWithLineNum(), float64(elapsed.Nanoseconds())/1e6, "-", sql, traceId, userId)
} else {
l.Printf(l.traceStr, utils.FileWithLineNum(), float64(elapsed.Nanoseconds())/1e6, rows, sql, traceId, userId)
}
}
}
}
package mysql
import (
"fmt"
_ "github.com/go-sql-driver/mysql" //加载mysql驱动
_ "github.com/joho/godotenv/autoload"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/gorm/schema"
"hilo-user/mylogrus"
"hilo-user/resource/config"
"log"
"net/url"
"time"
)
var Db *gorm.DB
func init() {
var err error
mysqlConfigData := config.GetConfigMysql()
options := "?charset=utf8mb4&parseTime=True&loc=Local&time_zone=" + url.QueryEscape("'+8:00'")
dsn := "" + mysqlConfigData.MYSQL_USERNAME + ":" + mysqlConfigData.MYSQL_PASSWORD + "@(" + mysqlConfigData.MYSQL_HOST + ")/" + mysqlConfigData.MYSQL_DB + options
sqlLogger := logger.Default.LogMode(logger.Info)
if file := mylogrus.GetSqlLog(); file != nil {
//sqlLogger = logger.New(log.New(file, "\r\n", log.Ldate|log.Lmicroseconds), logger.Config{
sqlLogger = MyNew(log.New(file, "", log.Ldate|log.Lmicroseconds), logger.Config{
SlowThreshold: 200 * time.Millisecond,
LogLevel: logger.Info,
Colorful: false,
})
}
Db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: sqlLogger,
NamingStrategy: schema.NamingStrategy{
SingularTable: true,
},
})
if err != nil {
log.Fatalf("mysql connect error %v", err)
} else {
log.Println("mysql connect success")
}
if Db.Error != nil {
fmt.Printf("database error %v", Db.Error)
}
if d, err := Db.DB(); err == nil {
d.SetConnMaxLifetime(time.Minute * 30) // 连接可复用的最大时间。
d.SetMaxIdleConns(20) // 空闲连接数
d.SetMaxOpenConns(20) // 最大连接数
}
}
package mysql
import "strconv"
/**
基于PDM,建立统计的数据domain结构。注意,不要选择0, 因为go的int默认值为0
*/
//主键ID
type ID = uint64
//性别
type Sex = uint8
//加减
type AddReduce = uint8
//拥有
type YesNo = uint8
//短描述
type Str = string
//时间戳
type Time = int64
//状态
type UserYesNo = uint8
//平台
type Platform = uint8
//多枚举类型
type Type = uint8
//数量
type Num = uint32
//时间戳
type Timestamp = uint64
//排序
type Index = uint16
//数量,并且用到-1作为特殊标记位
type NumAll = int
//开启关闭
type OpenClose = uint8
//逻辑删除
type LogicDel = uint8
//设备
type Device = uint8
type PeriodType = uint8
type FinishYesNo = uint8
//性别
const (
MAN Sex = 1
WOMAN Sex = 2
EMPTY Sex = 0
)
//yes no
const (
YES YesNo = 1
NO YesNo = 2
)
const (
OPEN OpenClose = 1
CLOSE OpenClose = 2
)
//加,减
const (
ADD AddReduce = 1
REDUCE AddReduce = 2
NilAddREDUCE AddReduce = 3
)
const (
USER UserYesNo = 1
NOUSER UserYesNo = 2
)
func TypeToString(t Type) string {
return strconv.Itoa(int(t))
}
package redisCli
import (
"context"
"github.com/go-redis/redis/v8"
"hilo-user/mylogrus"
"hilo-user/resource/config"
)
var RedisClient *redis.Client
func init() {
RedisClient = redis.NewClient(&redis.Options{
Addr: config.GetConfigRedis().REDIS_HOST,
Password: config.GetConfigRedis().REDIS_PASSWORD, // no password set
DB: 0, // use default DB
PoolSize: 20,
MinIdleConns: 20,
})
mylogrus.MyLog.Infoln(config.GetConfigRedis().REDIS_HOST)
mylogrus.MyLog.Infoln(config.GetConfigRedis().REDIS_PASSWORD)
pong, err := RedisClient.Ping(context.Background()).Result()
if err != nil {
mylogrus.MyLog.Warn(err)
mylogrus.MyLog.Fatal("redis db0 connect fail")
} else {
mylogrus.MyLog.Info("redis db0 connection success - ", pong)
}
}
func GetRedis() *redis.Client {
return RedisClient
}
package resp
import (
"encoding/json"
"github.com/gin-gonic/gin"
"hilo-user/_const"
"hilo-user/mycontext"
"hilo-user/myerr"
"net/http"
)
type Response struct {
Code uint16 `json:"code"` // 错误码
Message interface{} `json:"message"` // 消息
MessageData interface{} `json:"messageData"` // 消息详情
OperationMessage interface{} `json:"operationMessage"` // 操作消息
Data interface{} `json:"data"` // 数据
}
type GameResponse struct {
RetCode uint16 `json:"ret_code"`
RetMsg string `json:"ret_msg"`
SdkErrorCode uint16 `json:"sdk_error_code"`
Data interface{} `json:"data"`
}
/**
* HTTP输出json信息
* param: *gin.Context
* param: error err
* param: interface{} data
*/
func ResponseOk(c *gin.Context, data interface{}) {
// always return http.StatusOK
response := Response{
Code: myerr.GetSuccessCode(),
Message: myerr.GetSuccessMsg(),
OperationMessage: myerr.GetSuccessMsg(),
Data: data,
}
printResponseBody(c, &response)
c.JSON(http.StatusOK, response)
}
func GameResponseOk(c *gin.Context, data interface{}) {
// always return http.StatusOK
response := GameResponse{
RetCode: 0,
RetMsg: myerr.GetSuccessMsg(),
SdkErrorCode: 0,
Data: data,
}
printResponseBody(c, &response)
c.JSON(http.StatusOK, response)
}
func GameResponseFail(c *gin.Context, err *myerr.GameError) {
// always return http.StatusOK
response := GameResponse{
RetCode: err.Code(),
RetMsg: err.Error(),
SdkErrorCode: err.Code(),
Data: nil,
}
printResponseBody(c, &response)
c.JSON(http.StatusOK, response)
}
func ResponseWaring(c *gin.Context, waringError *myerr.WaringError) {
response := Response{
Code: waringError.GetCode(),
Message: waringError.GetMsg(),
OperationMessage: waringError.GetMsg(),
Data: nil,
}
printResponseBody(c, &response)
c.JSON(http.StatusOK, response)
}
func ResponseSysError(c *gin.Context, sysError *myerr.SysError) {
response := Response{
Code: sysError.GetCode(),
Message: sysError.GetMsg(),
OperationMessage: sysError.GetMsg(),
Data: nil,
}
printResponseBody(c, &response)
c.JSON(http.StatusOK, response)
}
func ResponseBusiness(c *gin.Context, businessError *myerr.BusinessError) {
response := Response{
Code: businessError.GetCode(),
Message: businessError.GetMsg(),
MessageData: businessError.GetData(),
OperationMessage: businessError.GetMsg(),
Data: nil,
}
printResponseBody(c, &response)
c.JSON(http.StatusOK, response)
}
func ResponseErrWithString(c *gin.Context, msg interface{}) {
response := Response{
Code: myerr.GetSysErrorCode(),
Message: msg,
OperationMessage: msg,
Data: nil,
}
printResponseBody(c, &response)
c.JSON(http.StatusOK, response)
}
func printResponseBody(c *gin.Context, response interface{}) {
traceId, _ := c.Get(_const.TRACEID)
if _traceId, ok := traceId.(string); ok {
c.Header("X-Trace-ID", _traceId)
}
var userId uint64 = 0
if strUserId, ok := c.Get(_const.USERID); ok {
userId = strUserId.(uint64)
}
buf, err := json.Marshal(response)
body := ""
if len(buf) < 1024 {
body = string(buf)
} else {
body = string(buf[0:1024])
}
if err == nil {
mycontext.CreateMyContext(c.Keys).Log.Infof("request rsp url:%s, traceId:%v, userId:%d, body:%s", c.Request.RequestURI, traceId, userId, body)
} else {
mycontext.CreateMyContext(c.Keys).Log.Error("request rsp body Marshal fail traceId:%v, err:%v", traceId, err)
}
}
package route
import (
"github.com/gin-gonic/gin"
"hilo-user/_const"
"hilo-user/mycontext"
"hilo-user/myerr"
"hilo-user/req"
"hilo-user/resource/config"
"hilo-user/resp"
)
/**
* 主要是解决错误的统一处理
*/
/*
*
错误包装类,统一处理。
*/
type HandlerFunc func(c *gin.Context) (*mycontext.MyContext, error)
// 对错误进行处理,
func wrapper(handler HandlerFunc) func(c *gin.Context) {
return func(c *gin.Context) {
var err error
var myContext *mycontext.MyContext
myContext, err = handler(c)
//防止忘记返回myContext, 单myContext为nil的时候,发出恐慌,导致错误覆盖。
if myContext == nil {
myContext = mycontext.CreateMyContext(nil)
}
c.Set(_const.ACTION_RESULt, true)
if err != nil {
c.Set(_const.ACTION_RESULt, false)
reqUri := c.Request.RequestURI
method := c.Request.Method
userId, _ := req.GetUserId(c)
switch h := err.(type) {
case *myerr.GameError:
myContext.Log.Warnf("request user err -> url:%v, method:%v, userId:%v, err :%+v\n", reqUri, method, userId, h.Error())
resp.GameResponseFail(c, h)
case *myerr.BusinessError:
myContext.Log.Warnf("request err -> url:%v, method:%v, userId:%v, err :%+v\n", reqUri, method, userId, h.GetErr())
resp.ResponseBusiness(c, h)
case *myerr.WaringError:
myContext.Log.Warningf("request err -> url:%v, method:%v, userId:%v, err :%+v\n", reqUri, method, userId, h.GetErr())
if config.AppIsRelease() {
resp.ResponseOk(c, nil)
//ResponseErrWithStringOperation(c, nil, h.GetMsg())
} else {
resp.ResponseWaring(c, h)
}
case *myerr.SysError:
myContext.Log.Errorf("request err -> url:%v, method:%v, userId:%v, err :%+v\n", reqUri, method, userId, h.GetErr())
resp.ResponseSysError(c, h)
default:
// 注意这里,如果是原生的error, 可能打印不出来,使用errors.Wrap配合%+v可以打印堆栈信息,建议上游使用
myContext.Log.Errorf("request err -> url:%v, method:%v, userId:%v, err :%+v\n", reqUri, method, userId, err)
resp.ResponseErrWithString(c, err.Error())
}
}
return
}
}
package route
import (
"bytes"
"github.com/gin-gonic/gin"
"hilo-user/_const"
"hilo-user/mycontext"
"hilo-user/myerr/bizerr"
"hilo-user/mylogrus"
"hilo-user/req"
"hilo-user/req/jwt"
"hilo-user/resource/config"
"hilo-user/resp"
"io/ioutil"
"runtime/debug"
"strings"
"time"
)
/**
controller层全局异常处理
*/
// 等级最高,为了只为最后有返回值到前端
func ExceptionHandle(c *gin.Context) {
defer func() {
if r := recover(); r != nil {
//打印错误堆栈信息
mylogrus.MyLog.Errorf("ExceptionHandle SYSTEM ACTION PANIC: %v, stack: %v", r, string(debug.Stack()))
resp.ResponseErrWithString(c, r)
//终止后续接口调用,不加的话recover到异常后,还会继续执行接口里后续代码
c.Abort()
}
}()
c.Next()
}
// jwt解密
func JWTApiHandle(c *gin.Context) {
logger := mylogrus.MyLog.WithField("URL", c.Request.URL).WithField("METHOD", c.Request.Method)
token := c.GetHeader("token")
if token == "" {
logger.Warnf("token err is empty! %v", c.Request.Header)
resp.ResponseBusiness(c, bizerr.TokenInvalid)
c.Abort()
return
}
claims, err := jwt.ParseToken(token)
if err != nil {
logger.Warnf("token parsed err:%v", err)
resp.ResponseBusiness(c, bizerr.TokenInvalid)
c.Abort()
return
}
logger = logger.WithField("userId", claims.UserId)
if time.Now().Unix() > claims.ExpiresAt {
logger.Warnf("token expire err, now: %d, expiresAt %d", time.Now().Unix(), claims.ExpiresAt)
resp.ResponseBusiness(c, bizerr.TokenInvalid)
c.Abort()
return
}
if claims.Issuer != config.GetConfigJWT().ISSUER_API {
logger.Warnf("token err issuer:%s, configIssuer %s", claims.Issuer, config.GetConfigJWT().ISSUER_API)
resp.ResponseBusiness(c, bizerr.TokenInvalid)
c.Abort()
return
}
var newToken = token
// token 连续7天没玩,第八天回来后给新token(线上是30天过期)
if claims.ExpiresAt-time.Now().Unix() < 86400*7 {
logger.Infof("token nearly expire err, now:%d,expiresAt:%d", time.Now().Unix(), claims.ExpiresAt)
newToken, err = jwt.GenerateToken(claims.UserId, claims.ExternalId, config.GetConfigJWT().ISSUER_API)
if err != nil {
logger.Warnf("token generation failed, err:%v", err)
resp.ResponseBusiness(c, bizerr.TokenInvalid)
c.Abort()
return
}
}
c.Set(_const.USERID, claims.UserId)
c.Set(_const.EXTERNAL_ID, claims.ExternalId)
c.Writer.Header().Add("token", newToken)
c.Next()
}
// 日志Handle
func LoggerHandle(c *gin.Context) {
//开始时间
start := time.Now()
clientIp := c.ClientIP()
method := c.Request.Method
traceId := genTraceId()
reqUri := c.Request.RequestURI
c.Set(_const.TRACEID, traceId)
//
header := c.Request.Header
//类型
devicetype := header.Get("Devicetype")
c.Set(_const.DEVICETYPE, devicetype)
appVersion := header.Get("Appversion")
c.Set(_const.APP_VERSION, appVersion)
c.Set(_const.URL, reqUri)
c.Set(_const.METHOD, method)
userId, _ := req.GetUserId(c)
bodyStr := ""
contentType := c.Request.Header.Get("Content-Type")
//文件就不打印
if strings.Index(contentType, "multipart/form-data") == -1 {
data, err := c.GetRawData()
if err != nil {
mylogrus.MyLog.Errorf("handle log err:%v", err)
}
bodyStr = string(data)
//很关键
//把读过的字节流重新放到body
c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(data))
}
mycontext.CreateMyContext(c.Keys).Log.Infof("request start traceId:%v, clientIp:%v, url:%v, method:%v, userId:%v, body:%v, header:%v", traceId, clientIp, reqUri, method, userId, bodyStr, header)
//加载完 defer recover,继续后续接口调用
c.Next()
end := time.Now()
latency := end.Sub(start)
mycontext.CreateMyContext(c.Keys).Log.Infof("request end fullPath:%v,url:%v, method: %v, traceId:%v, latency:%v userId:%v", c.FullPath(), reqUri, method, traceId, latency, userId)
}
package redis_r
import (
"context"
"fmt"
"github.com/gin-gonic/gin"
"github.com/go-redis/redis/v8"
"hilo-user/domain"
"hilo-user/mycontext"
"hilo-user/mylogrus"
"hilo-user/resource/config"
"strconv"
"strings"
"time"
)
var stop = make(chan struct{})
var match = "match_relation_*"
func Scan(c *gin.Context) (*mycontext.MyContext, error) {
go func() {
return
myCtx := mycontext.CreateMyContext(c.Keys)
var model = domain.CreateModelContext(myCtx)
cursor := uint64(0)
first := true
var err error
var keys []string
for first || cursor != 0 {
first = false
select {
case <-stop:
return
default:
keys, cursor, err = model.Redis.Scan(model, cursor, match, 1000).Result()
if err != nil {
model.Log.Errorf("SCAN fail:%v", err)
return
}
var delKeys []string
for _, key := range keys {
arr := strings.Split(key, "_")
if len(arr) == 4 {
cycle, _ := strconv.ParseInt(arr[2], 10, 64)
if cycle > 0 && cycle < 208933198 { // 2022-12-20号
delKeys = append(delKeys, key)
}
}
}
if len(delKeys) > 0 {
cnt, err := model.Redis.Del(model, keys...).Result()
model.Log.Infof("del redis keys:%v,cnt:%v,err:%v", delKeys, cnt, err)
}
time.Sleep(time.Second)
}
}
}()
return mycontext.CreateMyContext(nil), nil
}
func ScanStop(c *gin.Context) (*mycontext.MyContext, error) {
return nil, nil
myCtx := mycontext.CreateMyContext(c.Keys)
stop <- struct{}{}
return myCtx, nil
}
func Hscan(c *gin.Context) (*mycontext.MyContext, error) {
RedisClient := redis.NewClient(&redis.Options{
Addr: config.GetConfigRedis().REDIS_HOST,
Password: config.GetConfigRedis().REDIS_PASSWORD, // no password set
DB: 1, // use default DB
PoolSize: 20,
MinIdleConns: 20,
})
cursor := uint64(0)
first := true
var err error
var keys []string
for first || cursor != 0 {
first = false
select {
case <-stop:
return nil, nil
default:
keys, cursor, err = RedisClient.HScan(context.Background(), "user", cursor, "*", 1000).Result()
if err != nil {
return nil, err
}
l := len(keys)
if l%2 != 0 {
mylogrus.MyLog.Errorf("Hscan keys err:%v", keys)
continue
}
for i := 0; i < len(keys); i += 2 {
if !strings.Contains(keys[i+1], "172.26.95.48:50050") && !strings.Contains(keys[i+1], "172.26.95.24:50050") {
res, err := RedisClient.HDel(context.Background(), "user", keys[i]).Result()
msg := fmt.Sprintf("hdel user %v,value:%v,res:%v,err:%v", keys[i], keys[i+1], res, err)
mylogrus.MyLog.Infof("%v", msg)
}
}
}
}
return nil, nil
}
package route
import (
"github.com/gin-gonic/gin"
ginSwagger "github.com/swaggo/gin-swagger"
"github.com/swaggo/gin-swagger/swaggerFiles"
_ "hilo-user/docs"
"hilo-user/route/redis_r"
"hilo-user/route/user_r"
)
func InitRouter() *gin.Engine {
var r = gin.Default()
r.GET("/user-swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
needLogin := r.Group("")
needLogin.Use(ExceptionHandle, LoggerHandle, JWTApiHandle)
v1 := needLogin.Group("/v1")
user := v1.Group("/user")
{
user.GET("/nameplate", wrapper(user_r.UserNameplate))
}
inner := r.Group("/inner")
inner.Use(ExceptionHandle, LoggerHandle)
//}
test := r.Group("/test")
{
//test.GET("/user", wrapper(user_r.Test))
test.GET("/scan/redis", wrapper(redis_r.Scan))
test.GET("/hscan/redis", wrapper(redis_r.Hscan))
test.GET("/scan/redis/stop", wrapper(redis_r.ScanStop))
}
return r
}
package user_r
import (
"github.com/gin-gonic/gin"
"hilo-user/domain"
"hilo-user/domain/model/res_m"
"hilo-user/domain/model/user_m"
"hilo-user/mycontext"
"hilo-user/myerr"
"hilo-user/myerr/bizerr"
"hilo-user/req"
"hilo-user/resource/mysql"
"hilo-user/resp"
)
type ReturnUserNameplatePublic struct {
Id uint64 `json:"id"`
Name string `json:"name"`
PicUrl string `json:"picUrl"`
SvgaUrl string `json:"svgaUrl"`
//类型 (0:代表自身就是一种类型 > 0, 一种相关联的类型)
//Type res_m2.ResMedalType `json:"type"`
//
//IsHas bool `json:"isHas"`
}
// @Tags 用户
// @Summary 获取用户的铭牌
// @Param code query string false "用户code,不传则获取自己的"
// @Success 200 {object} ReturnUserNameplatePublic
// @Router /v1/user/nameplate [get]
func UserNameplate(c *gin.Context) (*mycontext.MyContext, error) {
myContext := mycontext.CreateMyContext(c.Keys)
userId, err := req.GetUserId(c)
if err != nil {
return myContext, err
}
model := domain.CreateModelContext(myContext)
code := c.Query("code")
if code != "" {
u, err := user_m.GetUserByCode(model, code)
if err != nil {
return myContext, myerr.WrapErr(err)
}
if u.ID <= 0 {
return myContext, bizerr.InvalidParameter
}
userId = u.ID
}
//所有的勋章
var resNameplates []res_m.ResNameplate
if err := mysql.Db.Model(&res_m.ResNameplate{}).Order("sort asc, type asc, threshold asc").Find(&resNameplates).Error; err != nil {
return myContext, myerr.WrapErr(err)
}
resNameplateIds := make([]mysql.ID, 0, len(resNameplates))
for _, r := range resNameplates {
resNameplateIds = append(resNameplateIds, r.ID)
}
//获取用户的
var userNameplates []user_m.UserNameplate
if err := mysql.Db.Model(&user_m.UserNameplate{}).Where(&user_m.UserNameplate{
UserId: userId,
}).Where("(end_time >= NOW() or end_time is null)").Find(&userNameplates).Error; err != nil {
return myContext, myerr.WrapErr(err)
}
//转换成map
userNameplateMap := make(map[uint32]struct{})
for _, r := range userNameplates {
userNameplateMap[r.NameplateId] = struct{}{}
}
results := make([]ReturnUserNameplatePublic, 0, len(resNameplates))
for i, r := range resNameplates {
if _, flag := userNameplateMap[uint32(r.ID)]; flag {
results = append(results, ReturnUserNameplatePublic{
Id: resNameplates[i].ID,
Name: resNameplates[i].Name,
PicUrl: resNameplates[i].PicUrl,
SvgaUrl: resNameplates[i].SvgaUrl,
//Type: resNameplates[i].Type,
//IsHas: true,
})
}
}
resp.ResponseOk(c, results)
return myContext, nil
}
package route
import (
uuid "github.com/satori/go.uuid"
"strings"
)
func genTraceId() string {
traceId := strings.Replace(uuid.NewV4().String(), "-", "", -1)
traceId = traceId[:len(traceId)/2]
return traceId
}
#!/usr/bin/bash
if [ $# -lt 1 ]
then
echo "Parameters missing."
echo "Usage: $0 <executable name>"
exit
fi
while :
do
$1
printf "$(date) : $1 return $?, wait for 5 seconds to restart\n"
sleep 5
done
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment