登陆服

This commit is contained in:
liuxiaobo 2025-05-29 09:54:43 +08:00
parent e2f75e2db7
commit 0143d8a0ad
11 changed files with 173 additions and 131 deletions

View File

@ -11,11 +11,11 @@ enum MsgId
message InternalMsg
{
int32 service_name = 1; //
uint32 conn_id = 2; // user_idconn_id
int64 user_id = 3; // id
int32 msg_id = 4; // id
bytes msg = 5; //
string service_name = 1; //
uint32 conn_id = 2; // user_idconn_id
int64 user_id = 3; // id
int32 msg_id = 4; // id
bytes msg = 5; //
}

View File

@ -5,7 +5,12 @@ option go_package = "common/proto/pb";
enum ErrCode
{
OK = 0;
LoginDiffLoc = 1; //
SystemErr = 1; //
LoginDiffLoc = 100; //
LoginUserOrPwdErr = 102; //
AccountFrozen = 103; //
AccountBanned = 104; //
RegisterUserExist = 120; //
}

View File

@ -7,9 +7,11 @@ import "code.proto";
//
message C2SUserLogin
{
string username = 1; //
string token = 2; // token
string version = 3; //
string username = 1; //
string password = 2; // token
string ip = 3;
string device_id = 4; // id
string version = 10; //
}
message S2CUserLogin

View File

@ -69,11 +69,11 @@ func (MsgId) EnumDescriptor() ([]byte, []int) {
type InternalMsg struct {
state protoimpl.MessageState `protogen:"open.v1"`
ServiceName int32 `protobuf:"varint,1,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` // 该服务类型下的具体的服务节点名,需要保证该消息是该服务节点发的。否则可能会导致客户端出现路由错误
ConnId uint32 `protobuf:"varint,2,opt,name=conn_id,json=connId,proto3" json:"conn_id,omitempty"` // 刚登陆时没有user_id只有conn_id
UserId int64 `protobuf:"varint,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` // 玩家id
MsgId int32 `protobuf:"varint,4,opt,name=msg_id,json=msgId,proto3" json:"msg_id,omitempty"` // 消息id
Msg []byte `protobuf:"bytes,5,opt,name=msg,proto3" json:"msg,omitempty"` // 消息
ServiceName string `protobuf:"bytes,1,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` // 该服务类型下的具体的服务节点名,需要保证该消息是该服务节点发的。否则可能会导致客户端出现路由错误
ConnId uint32 `protobuf:"varint,2,opt,name=conn_id,json=connId,proto3" json:"conn_id,omitempty"` // 刚登陆时没有user_id只有conn_id
UserId int64 `protobuf:"varint,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` // 玩家id
MsgId int32 `protobuf:"varint,4,opt,name=msg_id,json=msgId,proto3" json:"msg_id,omitempty"` // 消息id
Msg []byte `protobuf:"bytes,5,opt,name=msg,proto3" json:"msg,omitempty"` // 消息
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@ -108,11 +108,11 @@ func (*InternalMsg) Descriptor() ([]byte, []int) {
return file_internal_proto_rawDescGZIP(), []int{0}
}
func (x *InternalMsg) GetServiceName() int32 {
func (x *InternalMsg) GetServiceName() string {
if x != nil {
return x.ServiceName
}
return 0
return ""
}
func (x *InternalMsg) GetConnId() uint32 {
@ -149,7 +149,7 @@ const file_internal_proto_rawDesc = "" +
"\n" +
"\x0einternal.proto\x12\x03ipb\"\x8b\x01\n" +
"\vInternalMsg\x12!\n" +
"\fservice_name\x18\x01 \x01(\x05R\vserviceName\x12\x17\n" +
"\fservice_name\x18\x01 \x01(\tR\vserviceName\x12\x17\n" +
"\aconn_id\x18\x02 \x01(\rR\x06connId\x12\x17\n" +
"\auser_id\x18\x03 \x01(\x03R\x06userId\x12\x15\n" +
"\x06msg_id\x18\x04 \x01(\x05R\x05msgId\x12\x10\n" +

View File

@ -24,19 +24,34 @@ const (
type ErrCode int32
const (
ErrCode_OK ErrCode = 0
ErrCode_LoginDiffLoc ErrCode = 1 // 帐号在其它地方登陆
ErrCode_OK ErrCode = 0
ErrCode_SystemErr ErrCode = 1 // 系统错误
ErrCode_LoginDiffLoc ErrCode = 100 // 帐号在其它地方登陆
ErrCode_LoginUserOrPwdErr ErrCode = 102 // 帐号或密码错误
ErrCode_AccountFrozen ErrCode = 103 // 帐号已冻结
ErrCode_AccountBanned ErrCode = 104 // 帐号已封禁
ErrCode_RegisterUserExist ErrCode = 120 // 已有该帐号,无法注册
)
// Enum value maps for ErrCode.
var (
ErrCode_name = map[int32]string{
0: "OK",
1: "LoginDiffLoc",
0: "OK",
1: "SystemErr",
100: "LoginDiffLoc",
102: "LoginUserOrPwdErr",
103: "AccountFrozen",
104: "AccountBanned",
120: "RegisterUserExist",
}
ErrCode_value = map[string]int32{
"OK": 0,
"LoginDiffLoc": 1,
"OK": 0,
"SystemErr": 1,
"LoginDiffLoc": 100,
"LoginUserOrPwdErr": 102,
"AccountFrozen": 103,
"AccountBanned": 104,
"RegisterUserExist": 120,
}
)
@ -72,10 +87,15 @@ var File_code_proto protoreflect.FileDescriptor
const file_code_proto_rawDesc = "" +
"\n" +
"\n" +
"code.proto\x12\x02pb*#\n" +
"code.proto\x12\x02pb*\x86\x01\n" +
"\aErrCode\x12\x06\n" +
"\x02OK\x10\x00\x12\x10\n" +
"\fLoginDiffLoc\x10\x01B\x11Z\x0fcommon/proto/pbb\x06proto3"
"\x02OK\x10\x00\x12\r\n" +
"\tSystemErr\x10\x01\x12\x10\n" +
"\fLoginDiffLoc\x10d\x12\x15\n" +
"\x11LoginUserOrPwdErr\x10f\x12\x11\n" +
"\rAccountFrozen\x10g\x12\x11\n" +
"\rAccountBanned\x10h\x12\x15\n" +
"\x11RegisterUserExist\x10xB\x11Z\x0fcommon/proto/pbb\x06proto3"
var (
file_code_proto_rawDescOnce sync.Once

View File

@ -25,8 +25,10 @@ const (
type C2SUserLogin struct {
state protoimpl.MessageState `protogen:"open.v1"`
Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"` // 用户名
Token string `protobuf:"bytes,2,opt,name=token,proto3" json:"token,omitempty"` // 密码或token
Version string `protobuf:"bytes,3,opt,name=version,proto3" json:"version,omitempty"` // 版本
Password string `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"` // 密码或token
Ip string `protobuf:"bytes,3,opt,name=ip,proto3" json:"ip,omitempty"`
DeviceId string `protobuf:"bytes,4,opt,name=device_id,json=deviceId,proto3" json:"device_id,omitempty"` // 设备id
Version string `protobuf:"bytes,10,opt,name=version,proto3" json:"version,omitempty"` // 版本
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@ -68,9 +70,23 @@ func (x *C2SUserLogin) GetUsername() string {
return ""
}
func (x *C2SUserLogin) GetToken() string {
func (x *C2SUserLogin) GetPassword() string {
if x != nil {
return x.Token
return x.Password
}
return ""
}
func (x *C2SUserLogin) GetIp() string {
if x != nil {
return x.Ip
}
return ""
}
func (x *C2SUserLogin) GetDeviceId() string {
if x != nil {
return x.DeviceId
}
return ""
}
@ -318,11 +334,14 @@ var File_login_proto protoreflect.FileDescriptor
const file_login_proto_rawDesc = "" +
"\n" +
"\vlogin.proto\x12\x02pb\x1a\n" +
"code.proto\"Z\n" +
"code.proto\"\x8d\x01\n" +
"\fC2SUserLogin\x12\x1a\n" +
"\busername\x18\x01 \x01(\tR\busername\x12\x14\n" +
"\x05token\x18\x02 \x01(\tR\x05token\x12\x18\n" +
"\aversion\x18\x03 \x01(\tR\aversion\"^\n" +
"\busername\x18\x01 \x01(\tR\busername\x12\x1a\n" +
"\bpassword\x18\x02 \x01(\tR\bpassword\x12\x0e\n" +
"\x02ip\x18\x03 \x01(\tR\x02ip\x12\x1b\n" +
"\tdevice_id\x18\x04 \x01(\tR\bdeviceId\x12\x18\n" +
"\aversion\x18\n" +
" \x01(\tR\aversion\"^\n" +
"\fS2CUserLogin\x12\x1f\n" +
"\x04code\x18\x01 \x01(\x0e2\v.pb.ErrCodeR\x04code\x12\x17\n" +
"\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x14\n" +

View File

@ -1,6 +1,7 @@
package server
import (
"game/common/proto/ipb"
"game/common/proto/pb"
"game/common/topicName"
"github.com/fox/fox/processor"
@ -19,16 +20,16 @@ func (s *GateService) initProcessor() {
}
// 收到登陆成功消息,判断是否顶号
func (s *GateService) onUserLogin(conn ws.IConn, msg *pb.S2CUserLogin) {
func (s *GateService) onUserLogin(iMsg *ipb.InternalMsg, conn ws.IConn, msg *pb.S2CUserLogin) {
if conn == nil {
return
}
// 登陆失败,回传玩家
if msg.Code != pb.ErrCode_OK {
s.SendClientMsg(conn, int32(pb.MsgId_S2CUserLoginId), msg)
s.SendClientMsg(conn, iMsg.ServiceName, int32(pb.MsgId_S2CUserLoginId), msg)
return
}
s.SendClientMsg(conn, int32(pb.MsgId_S2CUserLoginId), msg)
s.SendClientMsg(conn, iMsg.ServiceName, int32(pb.MsgId_S2CUserLoginId), msg)
s.wss.SetUserId(conn.Id(), msg.UserId)
sName := s.bindService.LoadFromRedis(conn.UserId(), pb.ServiceTypeId_STI_Gate)
// 网关不同,说明玩家在其它网关上登陆,
@ -40,11 +41,11 @@ func (s *GateService) onUserLogin(conn ws.IConn, msg *pb.S2CUserLogin) {
}
// 收到登出消息
func (s *GateService) onUserLogout(conn ws.IConn, msg *pb.S2CUserLogout) {
func (s *GateService) onUserLogout(iMsg *ipb.InternalMsg, conn ws.IConn, msg *pb.S2CUserLogout) {
if conn == nil {
return
}
s.SendClientMsg(conn, int32(pb.MsgId_S2CUserLogoutId), msg)
s.SendClientMsg(conn, iMsg.ServiceName, int32(pb.MsgId_S2CUserLogoutId), msg)
// 登出的清理工作由WsOnDisconnect实现
conn.NotifyClose()
}

View File

@ -123,9 +123,9 @@ func (s *GateService) findConn(msg *ipb.InternalMsg) ws.IConn {
// 处理消息
func (s *GateService) connMessage(conn ws.IConn, iMsg *ipb.InternalMsg) {
if req, err := s.processor.Unmarshal(iMsg.MsgId, iMsg.Msg); err == nil {
err = s.processor.Dispatch(iMsg.MsgId, conn, req)
err = s.processor.Dispatch(iMsg.MsgId, iMsg, conn, req)
} else {
s.SendClientData(conn, iMsg.MsgId, iMsg.Msg)
s.SendClientData(conn, iMsg.ServiceName, iMsg.MsgId, iMsg.Msg)
}
//log.Debug(s.Log("on message:%v", string(msg)))
}
@ -185,32 +185,28 @@ func (s *GateService) WsOnMessage(conn ws.IConn, data []byte) {
// 向内部服务发送消息
func (s *GateService) SendServiceData(topic string, conn ws.IConn, msgId int32, data []byte) {
iMsg := &ipb.InternalMsg{ConnId: conn.Id(), UserId: conn.UserId(), MsgId: msgId, Msg: data}
iMsg := &ipb.InternalMsg{ServiceName: s.Name(), ConnId: conn.Id(), UserId: conn.UserId(), MsgId: msgId, Msg: data}
dMsg, _ := proto.Marshal(iMsg)
_ = s.Send(topic, dMsg)
}
// 向内部服务发送消息
func (s *GateService) SendServiceMsg(topic string, conn ws.IConn, msgId int32, msg proto.Message) {
iMsg := &ipb.InternalMsg{ConnId: conn.Id(), UserId: conn.UserId(), MsgId: msgId}
iMsg.Msg, _ = proto.Marshal(msg)
dMsg, _ := proto.Marshal(iMsg)
_ = s.Send(topic, dMsg)
data, _ := proto.Marshal(msg)
s.SendServiceData(topic, conn, msgId, data)
}
// 向玩家发送消息
func (s *GateService) SendClientData(conn ws.IConn, msgId int32, data []byte) {
cMsg := &pb.ClientMsg{MsgId: msgId, Data: data}
func (s *GateService) SendClientData(conn ws.IConn, serviceName string, msgId int32, data []byte) {
cMsg := &pb.ClientMsg{ServiceName: serviceName, MsgId: msgId, Data: data}
dMsg, _ := proto.Marshal(cMsg)
_ = conn.SendMsg(dMsg)
}
// 向玩家发送消息
func (s *GateService) SendClientMsg(conn ws.IConn, msgId int32, msg proto.Message) {
cMsg := &pb.ClientMsg{MsgId: msgId}
cMsg.Data, _ = proto.Marshal(msg)
dMsg, _ := proto.Marshal(cMsg)
_ = conn.SendMsg(dMsg)
func (s *GateService) SendClientMsg(conn ws.IConn, serviceName string, msgId int32, msg proto.Message) {
data, _ := proto.Marshal(msg)
s.SendClientData(conn, serviceName, msgId, data)
}
func (s *GateService) WsOnDisconnect(conn ws.IConn) {

View File

@ -94,6 +94,39 @@ func (s *UserLoginOp) Login(username, password, ip, deviceID string) (*UserAccou
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,
}
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{
@ -111,6 +144,8 @@ func (s *UserLoginOp) recordLoginLog(userID uint, ip, deviceID string, success b
// 生成JWT令牌(简化版)
func generateToken(userID uint, username string) (string, error) {
_ = userID
_ = username
// 这里应该使用JWT库生成实际令牌
// 简化实现实际项目中请使用安全的JWT实现
return "generated-token-placeholder", nil

View File

@ -1,93 +1,57 @@
package server
import (
"errors"
"game/common/proto/ipb"
"game/common/proto/pb"
"game/common/topicName"
"github.com/fox/fox/log"
"game/server/login/model"
"github.com/fox/fox/processor"
"github.com/fox/fox/service"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
"time"
)
func (s *LoginService) initProcessor() {
s.processor.RegisterMessages(processor.RegisterMetas{
//pb.MsgId_C2SChatId: {pb.C2SChat{}, s.onChat},
pb.MsgId_C2SUserLogoutId: {pb.C2SUserLogin{}, s.onLoginOrRegister},
})
}
// 登录或注册
func (s *LoginService) LoginOrRegister(req *pb.C2SUserLogin) {
// 1. 尝试查找用户
var user models.UserAccount
err := s.db.Where("username = ?", req.Username).First(&user).Error
func (s *LoginService) checkLoginOrRegister(req *pb.C2SUserLogin) (user *model.UserAccount, code pb.ErrCode) {
op := model.NewUserLoginOp()
var err error
user, err = op.Login(req.Username, req.Password, req.Ip, req.DeviceId)
if err != nil {
// 如果是用户不存在错误,则注册新用户
if errors.Is(err, gorm.ErrRecordNotFound) {
return s.registerNewUser(req)
}
// 其他数据库错误
return nil, err
}
// 2. 验证密码
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password)); err != nil {
// 记录失败登录日志
s.recordLoginLog(user.ID, req.IP, req.DeviceID, false, "密码错误")
return nil, errors.New("用户名或密码错误")
}
// 3. 检查账号状态
if user.Status != 1 {
var reason string
switch user.Status {
case 2:
reason = "账号已冻结"
case 3:
reason = "账号已封禁"
}
s.recordLoginLog(user.ID, req.IP, req.DeviceID, false, reason)
return nil, errors.New(reason)
}
// 4. 更新最后登录信息
user.LastLoginIP = req.IP
user.LastLoginTime = time.Now()
if err := s.db.Save(&user).Error; err != nil {
return nil, err
}
// 5. 记录成功登录日志
s.recordLoginLog(user.ID, req.IP, req.DeviceID, true, "")
// 6. 生成访问令牌
token, err := generateToken(user.ID, user.Username)
if err != nil {
return nil, err
}
return &models.LoginResponse{
UserID: user.ID,
Username: user.Username,
Token: token,
}, nil
}
// 收到登陆成功消息,判断是否顶号
func (s *LoginService) onLogin(uid int64, msg *pb.C2SUserLogin) {
switch msg.Type {
case pb.ChatType_CT_Private:
sName, err := s.bindService.FindServiceName(msg.DstUser.UserId, pb.ServiceTypeId_STI_Gate)
if err != nil {
log.DebugF("find user:%v in gate err: %v", uid, err)
user, err = op.RegisterNewUser(req.Username, req.Password, req.Ip, req.DeviceId)
if err != nil {
code = pb.ErrCode_RegisterUserExist
return
}
} else if errors.Is(err, model.ErrUserOrPassword) {
code = pb.ErrCode_LoginUserOrPwdErr
return
} else if errors.Is(err, model.ErrAccountFrozen) {
code = pb.ErrCode_AccountFrozen
return
} else if errors.Is(err, model.ErrAccountBanned) {
code = pb.ErrCode_AccountBanned
return
} else {
code = pb.ErrCode_SystemErr
}
s.SendServiceMsg(service.TopicEx(sName), msg.DstUser.UserId, int32(pb.MsgId_S2CChatId), msg)
default:
s.SendServiceMsg(service.TopicEx(topicName.WorldMessage), uid, int32(pb.MsgId_S2CChatId), msg)
}
return user, code
}
// 登录或注册
func (s *LoginService) onLoginOrRegister(iMsg *ipb.InternalMsg, req *pb.C2SUserLogin) {
user, code := s.checkLoginOrRegister(req)
userId := int64(0)
rsp := &pb.S2CUserLogin{Code: code}
if user != nil && code == pb.ErrCode_OK {
rsp.UserId = int64(user.ID)
rsp.Token = user.Password
userId = rsp.UserId
}
s.SendServiceMsg(service.TopicEx(iMsg.ServiceName), iMsg.ConnId, userId, int32(pb.MsgId_S2CUserLoginId), rsp)
}

View File

@ -93,22 +93,22 @@ func (s *LoginService) OnMessage(data []byte) error {
return err
}
if req, err := s.processor.Unmarshal(iMsg.MsgId, iMsg.Msg); err == nil {
err = s.processor.Dispatch(iMsg.MsgId, iMsg.UserId, req)
err = s.processor.Dispatch(iMsg.MsgId, iMsg, req)
}
//log.Debug(s.Log("on message:%v", string(msg)))
return nil
}
// 向内部服务发送消息
func (s *LoginService) SendServiceData(topic string, userId int64, msgId int32, data []byte) {
iMsg := &ipb.InternalMsg{ConnId: 0, UserId: userId, MsgId: msgId, Msg: data}
func (s *LoginService) SendServiceData(topic string, connId uint32, userId int64, msgId int32, data []byte) {
iMsg := &ipb.InternalMsg{ConnId: connId, UserId: userId, MsgId: msgId, Msg: data}
dMsg, _ := proto.Marshal(iMsg)
_ = s.Send(topic, dMsg)
}
// 向内部服务发送消息
func (s *LoginService) SendServiceMsg(topic string, userId int64, msgId int32, msg proto.Message) {
iMsg := &ipb.InternalMsg{ConnId: 0, UserId: userId, MsgId: msgId}
func (s *LoginService) SendServiceMsg(topic string, connId uint32, userId int64, msgId int32, msg proto.Message) {
iMsg := &ipb.InternalMsg{ConnId: connId, UserId: userId, MsgId: msgId}
iMsg.Msg, _ = proto.Marshal(msg)
dMsg, _ := proto.Marshal(iMsg)
_ = s.Send(topic, dMsg)