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)
}
This diff is collapsed.
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
}
This diff is collapsed.
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
}
This diff is collapsed.
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
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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)) // 启动服务
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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
}
This diff is collapsed.
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