投注扣钱及结算扣钱

This commit is contained in:
liuxiaobo 2025-06-15 00:00:24 +08:00
parent fc13e7625c
commit f2133bfb80
23 changed files with 356 additions and 256 deletions

View File

@ -63,6 +63,10 @@ func (r *BaseRoom[Seat]) GameId() int {
return r.gameId
}
func (r *BaseRoom[Seat]) GetService() service.IService {
return r.srv
}
// 入座玩家数量
func (r *BaseRoom[Seat]) SeatPlayerNum(playerType PlayerType) int {
num := 0

View File

@ -41,6 +41,10 @@ func (r *HundredRoom) GameId() int {
return r.room.GameId()
}
func (r *HundredRoom) GetService() service.IService {
return r.room.GetService()
}
// 房间玩家数量
func (r *HundredRoom) GetPlayerNum(playerType PlayerType) int {
if playerType == PT_All {

View File

@ -9,6 +9,8 @@ import (
"github.com/fox/fox/log"
"github.com/go-redis/redis/v8"
"gorm.io/gorm"
"reflect"
"strconv"
"time"
)
@ -42,7 +44,23 @@ func (s *TableOp[T]) redisKey(id int64) string {
return fmt.Sprintf("%s:%d", s.tableName(), id)
}
// 查找并返回结构体
func (s *TableOp[T]) findByRedis(id int64) *T {
maps := s.findByRedisMaps(id)
if len(maps) == 0 {
return nil
}
us, err := serialization.MapToStruct[T](maps)
if err != nil {
log.ErrorF("serialization map to struct err: %v", err)
return nil
}
//log.DebugF("findByRedis redis-key:%v result:%v", s.redisKey(id), us)
return us
}
// 查找并返回map
func (s *TableOp[T]) findByRedisMaps(id int64) map[string]string {
if s.rds == nil {
return nil
}
@ -54,13 +72,7 @@ func (s *TableOp[T]) findByRedis(id int64) *T {
if len(maps) == 0 {
return nil
}
us, err := serialization.MapToStruct[T](maps)
if err != nil {
log.ErrorF("serialization map to struct err: %v", err)
return nil
}
//log.DebugF("findByRedis redis-key:%v result:%v", s.redisKey(id), us)
return us
return maps
}
func (s *TableOp[T]) writeRedis(id int64, t *T) {
@ -94,22 +106,6 @@ func (s *TableOp[T]) updateRedis(id int64, maps map[string]any) {
_ = s.rds.Expire(context.Background(), s.redisKey(id), TableExpire).Err()
}
func (s *TableOp[T]) addRedis(id int64, maps map[string]int64) map[string]int64 {
if s.rds == nil {
return nil
}
rets := map[string]int64{}
for k, v := range maps {
if ret, err := s.rds.HIncrBy(context.Background(), s.redisKey(id), k, v).Result(); err != nil {
log.ErrorF("redis-key:%v HIncrBy field err: %v", s.redisKey(id), k, err)
} else {
rets[k] = ret
}
}
_ = s.rds.Expire(context.Background(), s.redisKey(id), TableExpire).Err()
return rets
}
func (s *TableOp[T]) deleteRedis(id int64) {
if s.rds == nil {
return
@ -166,7 +162,64 @@ func (s *TableOp[T]) Update(id int64, updates map[string]any) (*T, pb.ErrCode) {
return &result.V, pb.ErrCode_OK
}
func (s *TableOp[T]) Add(id int64, res map[string]int64) (map[string]int64, pb.ErrCode) {
// 辅助函数将map的keys转为字符串slice
func keysToStringSlice(m map[string]int64) []string {
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
return keys
}
// 获取资源
func (s *TableOp[T]) GetInt(id int64, resName []string) (map[string]int64, pb.ErrCode) {
mapFields := s.findByRedisMaps(id)
// 查询更新后的值到map
updatedValues := make(map[string]int64)
if len(mapFields) != 0 {
for _, name := range resName {
v, _ := strconv.ParseInt(mapFields[name], 10, 64)
updatedValues[name] = v
}
return updatedValues, pb.ErrCode_OK
}
// redis中没有值从表中加载并写入redis
mapAny := make(map[string]any)
err := s.db.Model(new(T)).
Where("id = ?", id).
Take(&mapAny). // 扫描到map
Error
if err != nil {
log.ErrorF("query updated values table:%v id:%v err:%v", s.tableName(), id, err)
return nil, pb.ErrCode_SystemErr
}
s.updateRedis(id, mapAny)
// 查询更新后的值到map
updatedValues = make(map[string]int64)
for _, name := range resName {
if val, ok := mapAny[name]; ok {
var v64 int64
switch v := val.(type) {
case int64:
v64 = v
case int, int32, uint, uint32, uint64:
v64 = reflect.ValueOf(v).Int()
case float32, float64:
v64 = int64(reflect.ValueOf(v).Float())
default:
// 处理无法转换的情况
}
updatedValues[name] = v64
}
}
return updatedValues, pb.ErrCode_OK
}
// 增加或减少资源
func (s *TableOp[T]) AddInt(id int64, res map[string]int64) (map[string]int64, pb.ErrCode) {
addRes := map[string]any{}
for k, v := range res {
addRes[k] = gorm.Expr(fmt.Sprintf("%v + ?", k), v)
@ -177,8 +230,26 @@ func (s *TableOp[T]) Add(id int64, res map[string]int64) (map[string]int64, pb.E
log.ErrorF("add table:%v id:%v err:%v", s.tableName(), id, err)
return nil, pb.ErrCode_SystemErr
}
rets := s.addRedis(id, res)
return rets, pb.ErrCode_OK
// 查询更新后的值到map
updatedValues := make(map[string]int64)
err = s.db.Model(new(T)).
Select(keysToStringSlice(res)). // 只选择需要返回的字段
Where("id = ?", id).
Take(&updatedValues). // 扫描到map
Error
if err != nil {
log.ErrorF("query updated values table:%v id:%v err:%v", s.tableName(), id, err)
return nil, pb.ErrCode_SystemErr
}
mapAny := make(map[string]any)
for k, v := range updatedValues {
mapAny[k] = v
}
s.updateRedis(id, mapAny)
return updatedValues, pb.ErrCode_OK
}
func (s *TableOp[T]) Delete(id int64) (*T, pb.ErrCode) {

View File

@ -15,7 +15,7 @@ enum ErrCode
VersionTooLow = 115; //
Maintain = 120; //
GoldNotEnough = 125; //
GoldNotEnough = 125; //
NotBetCount = 126; //
NotLeaveRoom = 127; // ()
// color game

View File

@ -215,7 +215,7 @@ message NtfColorSettle
repeated UserBetAreaMul userAreaWin = 1; //
repeated ColorType threeDice = 2; //
int64 totalWin = 3; //
int64 totalWin = 3; // ,
int64 totalBet = 4; //
int64 totalWinBaseBet = 6; //
int64 tax = 5; //

View File

@ -37,3 +37,9 @@ message RspLeaveRoom
ErrCode code = 1;
}
//
message NtfPayoutFail
{
ErrCode code = 1;
}

View File

@ -18,6 +18,7 @@ enum MsgId
RspEnterRoomId = 1003;
ReqLeaveRoomId = 1004; //
RspLeaveRoomId = 1005;
NtfPayoutFailId = 1010; //
// 2000-2099
ReqChatId = 2000; //

View File

@ -247,6 +247,59 @@ func (x *RspLeaveRoom) GetCode() ErrCode {
return ErrCode_OK
}
// 加钱或扣钱失败通知
type NtfPayoutFail struct {
state protoimpl.MessageState `protogen:"open.v1"`
Code ErrCode `protobuf:"varint,1,opt,name=code,proto3,enum=pb.ErrCode" json:"code,omitempty"`
UserId int64 `protobuf:"varint,2,opt,name=userId,proto3" json:"userId,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *NtfPayoutFail) Reset() {
*x = NtfPayoutFail{}
mi := &file_common_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *NtfPayoutFail) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*NtfPayoutFail) ProtoMessage() {}
func (x *NtfPayoutFail) ProtoReflect() protoreflect.Message {
mi := &file_common_proto_msgTypes[5]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use NtfPayoutFail.ProtoReflect.Descriptor instead.
func (*NtfPayoutFail) Descriptor() ([]byte, []int) {
return file_common_proto_rawDescGZIP(), []int{5}
}
func (x *NtfPayoutFail) GetCode() ErrCode {
if x != nil {
return x.Code
}
return ErrCode_OK
}
func (x *NtfPayoutFail) GetUserId() int64 {
if x != nil {
return x.UserId
}
return 0
}
var File_common_proto protoreflect.FileDescriptor
const file_common_proto_rawDesc = "" +
@ -262,7 +315,10 @@ const file_common_proto_rawDesc = "" +
"\broomType\x18\x03 \x01(\x05R\broomType\"\x0e\n" +
"\fReqLeaveRoom\"/\n" +
"\fRspLeaveRoom\x12\x1f\n" +
"\x04code\x18\x01 \x01(\x0e2\v.pb.ErrCodeR\x04codeB\x11Z\x0fcommon/proto/pbb\x06proto3"
"\x04code\x18\x01 \x01(\x0e2\v.pb.ErrCodeR\x04code\"H\n" +
"\rNtfPayoutFail\x12\x1f\n" +
"\x04code\x18\x01 \x01(\x0e2\v.pb.ErrCodeR\x04code\x12\x16\n" +
"\x06userId\x18\x02 \x01(\x03R\x06userIdB\x11Z\x0fcommon/proto/pbb\x06proto3"
var (
file_common_proto_rawDescOnce sync.Once
@ -276,24 +332,26 @@ func file_common_proto_rawDescGZIP() []byte {
return file_common_proto_rawDescData
}
var file_common_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
var file_common_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
var file_common_proto_goTypes = []any{
(*NtfKickOutUser)(nil), // 0: pb.NtfKickOutUser
(*ReqEnterRoom)(nil), // 1: pb.ReqEnterRoom
(*RspEnterRoom)(nil), // 2: pb.RspEnterRoom
(*ReqLeaveRoom)(nil), // 3: pb.ReqLeaveRoom
(*RspLeaveRoom)(nil), // 4: pb.RspLeaveRoom
(ErrCode)(0), // 5: pb.ErrCode
(*NtfPayoutFail)(nil), // 5: pb.NtfPayoutFail
(ErrCode)(0), // 6: pb.ErrCode
}
var file_common_proto_depIdxs = []int32{
5, // 0: pb.NtfKickOutUser.code:type_name -> pb.ErrCode
5, // 1: pb.RspEnterRoom.code:type_name -> pb.ErrCode
5, // 2: pb.RspLeaveRoom.code:type_name -> pb.ErrCode
3, // [3:3] is the sub-list for method output_type
3, // [3:3] is the sub-list for method input_type
3, // [3:3] is the sub-list for extension type_name
3, // [3:3] is the sub-list for extension extendee
0, // [0:3] is the sub-list for field type_name
6, // 0: pb.NtfKickOutUser.code:type_name -> pb.ErrCode
6, // 1: pb.RspEnterRoom.code:type_name -> pb.ErrCode
6, // 2: pb.RspLeaveRoom.code:type_name -> pb.ErrCode
6, // 3: pb.NtfPayoutFail.code:type_name -> pb.ErrCode
4, // [4:4] is the sub-list for method output_type
4, // [4:4] is the sub-list for method input_type
4, // [4:4] is the sub-list for extension type_name
4, // [4:4] is the sub-list for extension extendee
0, // [0:4] is the sub-list for field type_name
}
func init() { file_common_proto_init() }
@ -308,7 +366,7 @@ func file_common_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_common_proto_rawDesc), len(file_common_proto_rawDesc)),
NumEnums: 0,
NumMessages: 5,
NumMessages: 6,
NumExtensions: 0,
NumServices: 0,
},

View File

@ -35,6 +35,7 @@ const (
MsgId_RspEnterRoomId MsgId = 1003
MsgId_ReqLeaveRoomId MsgId = 1004 // 离开房间
MsgId_RspLeaveRoomId MsgId = 1005
MsgId_NtfPayoutFailId MsgId = 1010 // 加减钱失败
// 聊天服 2000-2099
MsgId_ReqChatId MsgId = 2000 // 玩家聊天消息
MsgId_RspChatId MsgId = 2001 // 复用C2SChatMsg
@ -74,6 +75,7 @@ var (
1003: "RspEnterRoomId",
1004: "ReqLeaveRoomId",
1005: "RspLeaveRoomId",
1010: "NtfPayoutFailId",
2000: "ReqChatId",
2001: "RspChatId",
2100: "ReqUserLoginId",
@ -106,6 +108,7 @@ var (
"RspEnterRoomId": 1003,
"ReqLeaveRoomId": 1004,
"RspLeaveRoomId": 1005,
"NtfPayoutFailId": 1010,
"ReqChatId": 2000,
"RspChatId": 2001,
"ReqUserLoginId": 2100,
@ -163,7 +166,7 @@ var File_msgId_proto protoreflect.FileDescriptor
const file_msgId_proto_rawDesc = "" +
"\n" +
"\vmsgId.proto\x12\x02pb*\xaa\x05\n" +
"\vmsgId.proto\x12\x02pb*\xc0\x05\n" +
"\x05MsgId\x12\x0e\n" +
"\n" +
"MI_Unknown\x10\x00\x12\x12\n" +
@ -172,7 +175,8 @@ const file_msgId_proto_rawDesc = "" +
"\x0eReqEnterRoomId\x10\xea\a\x12\x13\n" +
"\x0eRspEnterRoomId\x10\xeb\a\x12\x13\n" +
"\x0eReqLeaveRoomId\x10\xec\a\x12\x13\n" +
"\x0eRspLeaveRoomId\x10\xed\a\x12\x0e\n" +
"\x0eRspLeaveRoomId\x10\xed\a\x12\x14\n" +
"\x0fNtfPayoutFailId\x10\xf2\a\x12\x0e\n" +
"\tReqChatId\x10\xd0\x0f\x12\x0e\n" +
"\tRspChatId\x10\xd1\x0f\x12\x13\n" +
"\x0eReqUserLoginId\x10\xb4\x10\x12\x13\n" +

View File

@ -165,7 +165,7 @@ func (m *UserBindService) HashServiceNode(typeId pb.ServiceTypeId, uid int64) (*
// 要查找的服务必须是状态服,无状态服不需要查找指定服务。
// 如果玩家是首次使用该服务,则随机一个服务并保存玩家该服务节点。下次查找时返回该节点。
func (m *UserBindService) FindServiceNode(userId int64, typeId pb.ServiceTypeId) (*etcd.ServiceNode, error) {
func (m *UserBindService) FindServiceNode(typeId pb.ServiceTypeId, userId int64) (*etcd.ServiceNode, error) {
if userId > 0 {
// 向redis中查询。redis中保留的服务节点不一定是可用的还需要向etcd中验证
sName := m.LoadFromRedis(userId, typeId)
@ -187,26 +187,24 @@ func (m *UserBindService) FindServiceNode(userId int64, typeId pb.ServiceTypeId)
return node, nil
}
// 查找玩家所有的玩法节点
func (m *UserBindService) GetAllUserInGameServiceNode(userId int64) ([]*etcd.ServiceNode, error) {
if userId > 0 {
maps, err := m.rdb.HGetAll(context.Background(), m.makeRedisKey(userId)).Result()
if err != nil && err != redis.Nil {
log.ErrorF("user:%v get all service error:%v", err.Error())
return nil, err
}
if len(maps) > 0 {
inGames := make([]*etcd.ServiceNode, 0)
m.etcdRegistry.GetNodes().Range(func(k, v interface{}) bool {
if node, ok := v.(etcd.ServiceNode); ok {
if _, ok = maps[node.Name]; ok && node.TypeId != int(pb.ServiceTypeId_STI_Gate) {
inGames = append(inGames, &node)
// 对玩家所在有所有节点做操作
func (m *UserBindService) RangeUserAllServiceNode(userId int64, proc func(node *etcd.ServiceNode) bool) {
maps, err := m.rdb.HGetAll(context.Background(), m.makeRedisKey(userId)).Result()
if err != nil && err != redis.Nil {
log.ErrorF("user:%v get all service error:%v", userId, err.Error())
return
}
if len(maps) > 0 {
m.etcdRegistry.GetNodes().Range(func(k, v interface{}) bool {
if node, ok := v.(etcd.ServiceNode); ok {
if _, ok = maps[node.Name]; ok {
if !proc(&node) {
return false
}
}
return true
})
return inGames, nil
}
}
return true
})
}
return nil, nil
return
}

View File

@ -2,8 +2,6 @@ package room
import (
"game/common/proto/pb"
"game/common/userBindService"
"game/server/colorgame/model"
"github.com/fox/fox/ipb"
)
@ -55,9 +53,7 @@ func (rm *ColorRoom) checkLeaveRoom(user *ColorPlayer, iMsg *ipb.InternalMsg, re
func (rm *ColorRoom) leaveRoom(user *ColorPlayer) {
rm.DelPlayer(user.Id())
rm.userMgr.Del(user.ID)
// 移除redis中玩家与本服务绑定关系
userService := userBindService.NewUserBindService(model.UserBindServiceRedis, nil)
userService.DelUserService(user.ID, pb.ServiceTypeId_STI_ColorGame)
rm.bindService.DelUserService(user.ID, pb.ServiceTypeId_STI_ColorGame)
}
func (rm *ColorRoom) onLeaveRoom(user *ColorPlayer, iMsg *ipb.InternalMsg, req *pb.ReqLeaveRoom) {

View File

@ -4,7 +4,10 @@ import (
"game/common/baseroom"
"game/common/config/game"
"game/common/jackpot"
modelUser "game/common/model/user"
"game/common/proto/pb"
"game/common/rpc"
"game/common/userBindService"
"game/server/chat/model"
"github.com/fox/fox/log"
"github.com/fox/fox/processor"
@ -13,7 +16,9 @@ import (
type ColorRoom struct {
*baseroom.HundredRoom
userMgr *baseroom.PlayerMgr
userMgr *baseroom.PlayerMgr
bindService *userBindService.UserBindService
gameNo string
roomCfg *game.ColorRoomConfig
timingCfg *game.ColorGameTiming
@ -32,11 +37,12 @@ type ColorRoom struct {
betAreaInfo []*pb.ColorBetAreaInfo // 本局每个投注区域信息
}
func newColorRoom(id, roomType int, srv service.IService, userMgr *baseroom.PlayerMgr) (baseroom.IRoom, pb.ErrCode) {
func newColorRoom(id, roomType int, srv service.IService, userMgr *baseroom.PlayerMgr, bindService *userBindService.UserBindService) (baseroom.IRoom, pb.ErrCode) {
gameId := int(pb.ServiceTypeId_STI_ColorGame)
rm := &ColorRoom{
HundredRoom: nil,
userMgr: userMgr,
bindService: bindService,
roomCfg: &game.ColorRoomConfig{},
timingCfg: &game.ColorGameTiming{},
status: 0,
@ -108,16 +114,42 @@ func (rm *ColorRoom) resetGameData() {
rm.kickoutUsers()
}
// 当前拥有金币
func (rm *ColorRoom) GetGold(user *ColorPlayer) int64 {
return user.Gold - user.totalBet
func (rm *ColorRoom) GameNo() string {
return rm.gameNo
}
// 加减金币
func (rm *ColorRoom) AddGold(user *ColorPlayer, add int64) (int64, bool) {
if user.Gold-user.totalBet+add < 0 {
return user.Gold - user.totalBet, false
}
user.totalBet += add
return user.Gold - user.totalBet, true
// 当前拥有金币
func (rm *ColorRoom) GetGold(user *ColorPlayer) int64 {
return user.Gold
}
// 加减金币 预加减没有真实向db服请求
func (rm *ColorRoom) AddGold(user *ColorPlayer, add int64) (int64, bool) {
if user.Gold+add < 0 {
return user.Gold, false
}
user.Gold += add
return user.Gold, true
}
// 从db服拿到最新数据重置玩家数据
func (rm *ColorRoom) setGold(user *ColorPlayer, gold int64) {
user.Gold = gold
}
// 加减金币 真实抠钱
func (rm *ColorRoom) rpcPayoutGold(user *ColorPlayer, add int64, reason string) (int64, bool) {
resName := "gold"
res, code := rpc.RpcAddUserRes(rm.bindService, rm.GetService(), user.Id(), &modelUser.AddUserRes{
GameId: rm.GameId(),
GameNo: rm.GameNo(),
Reason: reason,
AddRes: map[string]int64{
resName: add,
},
})
if code == pb.ErrCode_OK {
return res[resName], true
}
return 0, false
}

View File

@ -3,14 +3,15 @@ package room
import (
"game/common/baseroom"
"game/common/proto/pb"
"game/common/userBindService"
"github.com/fox/fox/service"
)
type RoomFactory struct {
}
func (r *RoomFactory) CreateRoom(id, roomType int, srv service.IService, userMgr *baseroom.PlayerMgr) (baseroom.IRoom, pb.ErrCode) {
return newColorRoom(id, roomType, srv, userMgr)
func (r *RoomFactory) CreateRoom(id, roomType int, srv service.IService, userMgr *baseroom.PlayerMgr, bindService *userBindService.UserBindService) (baseroom.IRoom, pb.ErrCode) {
return newColorRoom(id, roomType, srv, userMgr, bindService)
}
func GetPlayer(p baseroom.IPlayer) *ColorPlayer {

View File

@ -14,6 +14,11 @@ import (
"sync"
)
const (
ReasonBet = "bet" // 扣钱原因:投注
ReasonSettle = "settle" // 扣钱或加钱原因:结算
)
func (rm *ColorRoom) setStatus(status pb.ColorGameStatus) {
rm.status = status
rm.statusTime = xtime.Now().TimestampMilli()
@ -300,8 +305,8 @@ func (rm *ColorRoom) calculateUserScore(user *ColorPlayer, jpArea pb.ColorBetAre
gold = win2 * (100 - rm.roomCfg.Rate) / 100
// 算税()
msg.Tax += win2 - gold
// 加回投注本金
gold += user.totalBets[winArea.Area]
//// 加回投注本金
//gold += user.totalBets[winArea.Area]
msg.TotalWin += gold
// 统计赢区的下注总额
msg.TotalWinBaseBet += user.totalBets[winArea.Area]
@ -313,8 +318,8 @@ func (rm *ColorRoom) calculateUserScore(user *ColorPlayer, jpArea pb.ColorBetAre
gold = jpScore * (100 - rm.roomCfg.Rate) / 100
// 算税()
msg.Tax += win2 - gold
// 加回投注本金
gold += user.totalBets[winArea.Area]
//// 加回投注本金
//gold += user.totalBets[winArea.Area]
msg.TotalWin += gold
// 统计赢区的下注总额
msg.TotalWinBaseBet += user.totalBets[winArea.Area]
@ -337,22 +342,24 @@ func (rm *ColorRoom) calculateUserScore(user *ColorPlayer, jpArea pb.ColorBetAre
// 和平台做结算
func (rm *ColorRoom) settle() {
//var allWinner []*pb.ColorPinoyLiveBigWinner
rm.calculateAllUserScore()
wg := new(sync.WaitGroup)
rm.RangePlayer(func(u baseroom.IPlayer) bool {
wg.Add(1)
user := GetPlayer(u)
ksync.GoSafe(func() {
defer wg.Done()
if user.totalBet > 0 {
// 和平台做赢钱结算
//_, err := rm.TransInoutGameEnd(u, 0, u.SettleMsg.TotalWin, u.SettleMsg.Tax)
//if err != nil {
// log.Error(rm.Log(err.Error()))
//}
// 没有赢分
if user.settleMsg.TotalWin < 1 {
return true
}
ksync.GroupGo(wg, func() {
afterGold, ok := rm.rpcPayoutGold(user, user.settleMsg.TotalWin, ReasonSettle)
if !ok {
log.Error(rm.UserLog(user.ID, "add gold:%v fail", user.settleMsg.TotalWin))
} else {
rm.GetService().RunOnce(func() {
rm.setGold(user, afterGold)
})
}
}, nil)
})
return true
})
wg.Wait()
@ -476,3 +483,41 @@ func (rm *ColorRoom) getProtoUser(user *ColorPlayer) *pb.ColorUser {
}
return u
}
// 结束下注,扣除投注玩家的投注金额
func (rm *ColorRoom) endBetPayoutUser() {
var failer []*ColorPlayer
var mt sync.Mutex
wg := &sync.WaitGroup{}
rm.RangePlayer(func(u baseroom.IPlayer) bool {
user := GetPlayer(u)
// 没有下注
if user.totalBet < 1 {
return true
}
ksync.GroupGo(wg, func() {
afterGold, ok := rm.rpcPayoutGold(user, -user.totalBet, ReasonBet)
if !ok {
mt.Lock()
failer = append(failer, user)
mt.Unlock()
} else {
rm.GetService().RunOnce(func() {
rm.setGold(user, afterGold)
})
}
})
return true
})
wg.Wait()
// 移除扣钱失败玩家的投注信息
for _, u := range failer {
u.totalBet = 0
for pos := range u.totalBets {
u.totalBets[pos] = 0
}
rm.notifyPayoutFail(u, pb.ErrCode_GoldNotEnough)
}
}

View File

@ -22,6 +22,7 @@ func (rm *ColorRoom) gameStartBetting() {
func (rm *ColorRoom) gameEndBetting() {
rm.setStatus(pb.ColorGameStatus_CGS_BetEnd)
rm.endBetPayoutUser()
rm.updateEndBetAreaMul()
rm.notifyEndBetting()
rm.NewTimer(TtOpenThreeDices, time.Duration(rm.timingCfg.EndBetting)*time.Millisecond)

View File

@ -68,6 +68,11 @@ func (rm *ColorRoom) notifyKickoutUser(user *ColorPlayer, code pb.ErrCode) {
rm.SendMsg(user, pb.MsgId_NtfKickOutUserId, &pb.NtfKickOutUser{Code: code})
}
// 每秒更新各投注区域投注信息
func (rm *ColorRoom) notifyPayoutFail(user *ColorPlayer, code pb.ErrCode) {
rm.SendMsg(user, pb.MsgId_NtfPayoutFailId, &pb.NtfPayoutFail{Code: code})
}
// 推送房间信息给玩家
func (rm *ColorRoom) notifyColorRoomInfo(user *ColorPlayer) {
ntf := &pb.NtfColorRoomInfo{

View File

@ -20,7 +20,7 @@ func (s *ColorService) initProcessor() {
})
}
// 登录或注册
// 进房间
func (s *ColorService) onEnterRoom(iMsg *ipb.InternalMsg, req *pb.ReqMatchRoom) {
ksync.GoSafe(func() {
us, code := rpc.RpcGetGameUser(s.bindService, s, iMsg.UserId)

View File

@ -54,7 +54,7 @@ func newColorService(serviceId int) service.IService {
s := new(ColorService)
s.playerMgr = baseroom.NewPlayerMgr(nil)
factory := &room.RoomFactory{}
s.room, _ = factory.CreateRoom(int(pb.ServiceTypeId_STI_ColorGame), 0, s, s.playerMgr)
s.room, _ = factory.CreateRoom(int(pb.ServiceTypeId_STI_ColorGame), 0, s, s.playerMgr, s.bindService)
s.status = baseroom.SsWorking
//s.roomMgr = baseroom.NewRoomMgr(&room.RoomFactory{})

View File

@ -84,7 +84,7 @@ func (s *DbService) onGetUserByUid(iMsg *ipb.InternalMsg) *ipb.InternalMsg {
return iMsg
}
// 获取用户数据,没有则创建
// 获取用户资源数据,没有则创建
func (s *DbService) onGetUserResources(iMsg *ipb.InternalMsg) *ipb.InternalMsg {
operationDb[user.UserResources](iMsg, func(res *user.UserResources) (*user.UserResources, pb.ErrCode) {
if us1, code := operation.NewUserResourcesOp().Find(res.UID); code != pb.ErrCode_OK {
@ -96,7 +96,7 @@ func (s *DbService) onGetUserResources(iMsg *ipb.InternalMsg) *ipb.InternalMsg {
return iMsg
}
// 添加用户资源,负数为减少
// 添加用户资源,负数为减少。资源不能为负数
func (s *DbService) onAddUserResources(iMsg *ipb.InternalMsg) *ipb.InternalMsg {
addUserRes := &user.AddUserRes{}
err := json.Unmarshal(iMsg.Msg, addUserRes)
@ -105,7 +105,26 @@ func (s *DbService) onAddUserResources(iMsg *ipb.InternalMsg) *ipb.InternalMsg {
iMsg.RpcCode = int32(pb.ErrCode_SystemErr)
return iMsg
}
rets, code := operation.NewUserResourcesOp().Add(iMsg.UserId, addUserRes.AddRes)
// 获取资源的值并检查够不够操作
resOp := operation.NewUserResourcesOp()
var resNames []string
for name := range addUserRes.AddRes {
resNames = append(resNames, name)
}
userRes, code := resOp.GetInt(iMsg.UserId, resNames)
if code != pb.ErrCode_OK {
iMsg.RpcCode = int32(code)
return iMsg
}
for resName, resValue := range userRes {
// 如果add为负数则为扣除
if addUserRes.AddRes[resName]+resValue < 1 {
iMsg.RpcCode = int32(pb.ErrCode_GoldNotEnough)
return iMsg
}
}
rets, code := resOp.AddInt(iMsg.UserId, addUserRes.AddRes)
if code != pb.ErrCode_OK {
iMsg.RpcCode = int32(code)
return iMsg

View File

@ -2,6 +2,7 @@ package server
import (
"game/common/proto/pb"
"github.com/fox/fox/etcd"
"github.com/fox/fox/ipb"
"github.com/fox/fox/processor"
"github.com/fox/fox/service"
@ -15,7 +16,13 @@ func (s *LobbyService) initProcessor() {
// 玩家上线,告诉玩家在哪个玩法中
func (s *LobbyService) onUserOnline(iMsg *ipb.InternalMsg, msg *pb.NtfUserOnline) {
nodes, _ := s.bindService.GetAllUserInGameServiceNode(msg.UserId)
var nodes []*etcd.ServiceNode
s.bindService.RangeUserAllServiceNode(msg.UserId, func(node *etcd.ServiceNode) bool {
if node.TypeId != int(pb.ServiceTypeId_STI_Gate) {
nodes = append(nodes, node)
}
return true
})
ntf := &pb.NtfUserInService{}
for _, node := range nodes {
ntf.ServiceNames = append(ntf.ServiceNames, node.Name)

View File

@ -1,152 +0,0 @@
package model
//import (
// "errors"
// "github.com/fox/fox/log"
// "golang.org/x/crypto/bcrypt"
// "gorm.io/gorm"
// "time"
//)
//
//const (
// AccountNormal = 1 // 正常
// AccountFrozen = 2 // 冻结
// AccountBanned = 3 // 封禁
//)
//
//// 玩家账户表
//type UserAccount struct {
// gorm.Model
// Username string `gorm:"type:varchar(32);uniqueIndex;not null"` // 用户名
// Password string `gorm:"type:varchar(255);not null"` // 密码哈希
// Email string `gorm:"type:varchar(100)"` // 邮箱(可选)
// Phone string `gorm:"type:varchar(20)"` // 手机号(可选)
// DeviceID string `gorm:"type:varchar(64);index"` // 设备ID
// LastLoginIP string `gorm:"type:varchar(45)"` // 最后登录IP(支持IPv6)
// LastLoginTime time.Time // 最后登录时间
// Status int `gorm:"type:tinyint;default:1"` // 账号状态 1-正常 2-冻结 3-封禁
// RegisterIP string `gorm:"type:varchar(45)"` // 注册IP
// RegisterTime time.Time `gorm:"type:TIMESTAMP;default:CURRENT_TIMESTAMP"` // 注册时间
//}
//
//// 玩家登录记录表
//type UserLoginLog struct {
// gorm.Model
// PlayerID uint `gorm:"index"` // 关联玩家ID
// LoginIP string `gorm:"type:varchar(45);not null"` // 登录IP
// LoginTime time.Time `gorm:"type:TIMESTAMP;default:CURRENT_TIMESTAMP"` // 登录时间
// DeviceInfo string `gorm:"type:varchar(255)"` // 设备信息(JSON格式)
// LoginResult bool // 登录结果 true-成功 false-失败
// FailReason string `gorm:"type:varchar(100)"` // 失败原因
//}
//
//type UserLoginOp struct {
// db *gorm.DB
// logDb *gorm.DB
//}
//
//func NewUserLoginOp() *UserLoginOp {
// return &UserLoginOp{db: UserDB, logDb: LogDB}
//}
//
//var (
// ErrUserOrPassword = errors.New("user or password was error")
// ErrAccountFrozen = errors.New("account frozen")
// ErrAccountBanned = errors.New("account banned")
//)
//
//func (s *UserLoginOp) Login(username, password, ip, deviceID string) (*UserAccount, error) {
// var user UserAccount
// err := s.db.Where("username = ?", username).First(&user).Error
// if err != nil {
// return nil, err
// }
// // 验证密码
// if err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil {
// s.recordLoginLog(user.ID, ip, deviceID, false, ErrUserOrPassword.Error())
// return nil, ErrUserOrPassword
// }
// // 检查账号状态
// switch user.Status {
// case AccountNormal:
//
// case AccountFrozen:
// s.recordLoginLog(user.ID, ip, deviceID, false, ErrAccountFrozen.Error())
// return nil, ErrAccountFrozen
// case AccountBanned:
// s.recordLoginLog(user.ID, ip, deviceID, false, ErrAccountBanned.Error())
// return nil, ErrAccountBanned
// }
// // 更新最后登录信息
// user.LastLoginIP = ip
// user.LastLoginTime = time.Now()
// _ = s.db.Save(&user).Error
//
// // 记录成功登录日志
// s.recordLoginLog(user.ID, ip, deviceID, true, "")
//
// // 6. 生成访问令牌
// token, err := generateToken(user.ID, user.Username)
// if err != nil {
// return nil, err
// }
// user.Password = token
// return &user, err
//}
//
//// 注册新用户
//func (s *UserLoginOp) RegisterNewUser(username, password, ip, deviceID string) (*UserAccount, error) {
// // 密码加密
// hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
// if err != nil {
// return nil, err
// }
// user := UserAccount{
// Username: username,
// Password: string(hashedPassword),
// DeviceID: deviceID,
// RegisterIP: ip,
// Status: 1,
// LastLoginIP: ip,
// LastLoginTime: time.Now(),
// }
//
// if err := s.db.Create(&user).Error; err != nil {
// return nil, err
// }
//
// s.recordLoginLog(user.ID, ip, deviceID, true, "")
//
// // 生成访问令牌
// token, err := generateToken(user.ID, user.Username)
// if err != nil {
// return nil, err
// }
// user.Password = token
//
// return &user, nil
//}
//
//// 记录登录日志
//func (s *UserLoginOp) recordLoginLog(userID uint, ip, deviceID string, success bool, failReason string) {
// logEntry := UserLoginLog{
// PlayerID: userID,
// LoginIP: ip,
// DeviceInfo: deviceID,
// LoginResult: success,
// FailReason: failReason,
// }
//
// if err := s.logDb.Create(&logEntry).Error; err != nil {
// log.ErrorF("记录登录日志失败: %v", err)
// }
//}
//
//// 生成JWT令牌(简化版)
//func generateToken(userID uint, username string) (string, error) {
// _ = userID
// _ = username
// // 这里应该使用JWT库生成实际令牌
// // 简化实现实际项目中请使用安全的JWT实现
// return "generated-token-placeholder", nil
//}

View File

@ -24,7 +24,7 @@ func NewMatchMgr() *MatchMgr {
}
}
//func (mgr *MatchMgr) Add(us *MatchPlayer, sid pb.ServiceTypeId, roomType int32) {
//func (mgr *MatchMgr) AddInt(us *MatchPlayer, sid pb.ServiceTypeId, roomType int32) {
// users, _ := mgr.users[sid]
// for _, u := range users {
// if u.ID == us.ID {

View File

@ -18,7 +18,7 @@ func (s *MatchService) onMatchRoom(iMsg *ipb.InternalMsg, req *pb.ReqMatchRoom)
gameId := pb.ServiceTypeId(req.GameId)
switch gameId {
case pb.ServiceTypeId_STI_ColorGame:
node, err := s.bindService.FindServiceNode(iMsg.UserId, gameId)
node, err := s.bindService.FindServiceNode(gameId, iMsg.UserId)
if err != nil {
log.ErrorF("db service node error:%v", err)
s.SendServiceMsg(service.TopicEx(iMsg.ServiceName), iMsg.ConnId, iMsg.UserId,