多人游戏教程(二)登录

服务器代码

proto文件

syntax = "proto3";

package login;
option go_package = "../../gate/api/login";

message User {
  string username = 1;
  string password = 2;
  string roles = 3;
  int32 status = 4;
  int64 created_time = 5;
  int64 updated_time = 6;
}

message SignupRequest {
  string username = 1;
  string password = 2;
}

message SigninRequest {
  string username = 1;
  string password = 2;
}

message LoginResponse {
  int64 code = 1;
  int64 user_id = 2;
  string msg = 3;
}

service UserService {
  rpc Signup(SignupRequest) returns (LoginResponse);
  rpc Signin(SigninRequest) returns (LoginResponse);
}

代码生成命令

protoc --go_out=. --go-grpc_out=. --grpc_out=. --cpp_out=. --plugin=protoc-gen-grpc=你的\.build\Release\grpc_cpp_plugin.exe .\login.proto

Go 控制层代码逻辑

package login

import (
	"ask_server/services"
	"context"
	"github.com/sirupsen/logrus"
)

var Server = newLoginServer()

func newLoginServer() *loginServer {
	return &loginServer{}
}

type loginServer struct {
	*UnimplementedUserServiceServer
}

func (s *loginServer) mustEmbedUnimplementedUserServiceServer() {

}

func (s *loginServer) Signup(ctx context.Context, req *SignupRequest) (*LoginResponse, error) {
	user, err := services.UserService.SignUp(req.Username, req.Password)
	if err != nil {
		logrus.Errorf("Signup Err : [%s]", err.Error())
		return &LoginResponse{Code: -1, Msg: err.Error()}, nil
	}
	return &LoginResponse{
		Code:   0,
		UserId: user.Id,
	}, nil
}
func (s *loginServer) Signin(ctx context.Context, req *SigninRequest) (*LoginResponse, error) {
	user, err := services.UserService.SignIn(req.Username, req.Password)
	if err != nil {
		logrus.Errorf("Signin Err : [%s]", err.Error())
		return &LoginResponse{Code: -1, Msg: err.Error()}, nil
	}
	return &LoginResponse{
		Code:   0,
		UserId: user.Id,
	}, nil
}

Go服务层代码逻辑


// SignUp 注册
func (s *userService) SignUp(username, password string) (*model.User, error) {
	username = strings.TrimSpace(username)

	// 验证用户名
	if len(username) > 0 {
		if err := validate.IsUsername(username); err != nil {
			return nil, err
		}
		if s.isUsernameExists(username) {
			return nil, errors.New(username + " 已被占用")
		}
	}

	user := &model.User{
		Username:    sqls.SqlNullString(username),
		Password:    passwd.EncodePassword(password),
		Status:      constants.StatusOk,
		CreatedTime: dates.NowTimestamp(),
		UpdatedTime: dates.NowTimestamp(),
	}

	err := repo.UserRepo.Create(sqls.DB(), user)
	if err != nil {
		return nil, err
	}

	return user, nil
}

// SignIn 登录
func (s *userService) SignIn(username, password string) (*model.User, error) {
	if len(username) == 0 {
		return nil, errors.New("用户名不能为空")
	}
	if len(password) == 0 {
		return nil, errors.New("密码不能为空")
	}
	if err := validate.IsPassword(password); err != nil {
		return nil, err
	}

	var user *model.User = nil

	user = s.GetByUsername(username)

	if user == nil || user.Status != constants.StatusOk {
		return nil, errors.New("用户名不存在或被禁用")
	}

	if !passwd.ValidatePassword(user.Password, password) {
		return nil, errors.New("用户名或密码错误")
	}
	return user, nil
}

// GetByUsername 根据用户名查找
func (s *userService) GetByUsername(username string) *model.User {
	return repo.UserRepo.GetByUsername(sqls.DB(), username)
}

Go数据层代码逻辑(gorm)自行百度~

func (r *userRepo) GetByUsername(db *gorm.DB, username string) *model.User {
	return r.Take(db, "username = ?", username)
}

func (r *userRepo) Create(db *gorm.DB, t *model.User) (err error) {
	err = db.Create(t).Error
	return
}

客户端代码

LoginSerice.h

#pragma once

#include <grpcpp/channel.h>
#include "Proto/Login/login.grpc.pb.h"

using namespace login;

DECLARE_DELEGATE_TwoParams(FOnUsereDelegate, const bool&, const int64&);

class FLoginReactor
{
public:
	explicit FLoginReactor(UserService::Stub* InServiceStub);

	void Signin(const FString Username, const FString Password);
	void Signup(const FString Username, const FString Password);

	FOnUsereDelegate OnReadReceive;

protected:
	UserService::Stub* ServiceStub;
	TArray<grpc::ClientContext*> ClientContexts;
	std::shared_ptr<SigninRequest> SigninReq;
	std::shared_ptr<LoginResponse> LoginResp;
	std::shared_ptr<SignupRequest> SignupReq;
	
};


class ASK_CLIENT_API FLoginService
{
public:
	void Start();
	void Stop();

	FOnUsereDelegate& GetCallback() const {
		return LoginReactor->OnReadReceive;
	};

	void Signin(const FString& Username, const FString& Password) const
	{
		LoginReactor->Signin(Username, Password);
	};
	void Signup(const FString& Username, const FString& Password) const
	{
		LoginReactor->Signup(Username, Password);
	};

protected:
		
	std::unique_ptr<UserService::Stub> ServiceStub;

	std::unique_ptr<FLoginReactor> LoginReactor;
private:
	std::shared_ptr<grpc::Channel> Channel;
public:
	FORCEINLINE void SetChannel(const std::shared_ptr<grpc::Channel>& Chan) { Channel = Chan;}
};

LoginService.cpp

#include "Sys/Login/LoginService.h"

FLoginReactor::FLoginReactor(UserService::Stub* InServiceStub)
{
	ServiceStub = InServiceStub;

	SigninReq = std::make_shared<SigninRequest>();
	LoginResp = std::make_shared<LoginResponse>();
	SignupReq = std::make_shared<SignupRequest>();
}

void FLoginReactor::Signin(const FString Username, const FString Password)
{
	auto context = new grpc::ClientContext();
	ClientContexts.Add(context);
	SigninReq->set_username(TCHAR_TO_UTF8(*Username));
	SigninReq->set_password(TCHAR_TO_UTF8(*Password));

	ServiceStub->async()->Signin(context, SigninReq.get(), LoginResp.get(), [this, context](grpc::Status Status)
	{
		int32 index = ClientContexts.Find(context);
		delete ClientContexts[index];
		ClientContexts.RemoveAt(index);
		bool isSuccess = Status.ok();
		int64 user_id;
		if (isSuccess)
		{
			user_id = LoginResp->user_id();
			AsyncTask(ENamedThreads::GameThread, [=]()
			{
					OnReadReceive.ExecuteIfBound(isSuccess, user_id);
			});
		}
		else
		{
			AsyncTask(ENamedThreads::GameThread, [=]()
			{
				OnReadReceive.ExecuteIfBound(false, 0);
			});
		}
		
	});
}

void FLoginReactor::Signup(const FString Username, const FString Password)
{
	auto context = new grpc::ClientContext();
	ClientContexts.Add(context);
	SignupReq->set_username(TCHAR_TO_UTF8(*Username));
	SignupReq->set_password(TCHAR_TO_UTF8(*Password));

	ServiceStub->async()->Signup(context, SignupReq.get(), LoginResp.get(), [this, context](grpc::Status Status)
	{
		int32 index = ClientContexts.Find(context);
		delete ClientContexts[index];
		ClientContexts.RemoveAt(index);
		bool isSuccess = Status.ok();
		int64 user_id;
		if (isSuccess)
		{
			user_id = LoginResp->user_id();
		}
		else
		{
			user_id = 0;
		}
		AsyncTask(ENamedThreads::GameThread, [=]()
		{
			OnReadReceive.ExecuteIfBound(isSuccess, user_id);
		});
	});
}

void FLoginService::Start()
{
	ServiceStub = UserService::NewStub(Channel);
	LoginReactor = std::make_unique<FLoginReactor>(ServiceStub.get());
}

void FLoginService::Stop()
{
	LoginReactor.release();
	ServiceStub.release();
}

LoginWidget.h

// uecosmic

#pragma once

#include "CoreMinimal.h"
#include "Widget/AskWidgetBase.h"
#include "LoginWidget.generated.h"

UCLASS()
class ASK_CLIENT_API ULoginWidget : public UAskWidgetBase
{
	GENERATED_BODY()
public:

	virtual void NativeConstruct() override;

	UFUNCTION()
	void OnHandleLoginClicked();
	
	UFUNCTION()
	void OnHandleRegisterClicked();
	
	UFUNCTION()
	void OnLoginClicked();
	
	UFUNCTION()
	void OnRegistryClicked();

private:

	UPROPERTY(meta = (BindWidget))
	class UButton* BtnLogin;
	
	UPROPERTY(meta = (BindWidget))
	class UButton* BtnBackLogin;
	
	UPROPERTY(meta = (BindWidget))
	class UButton* BtnRegistry;
	
	UPROPERTY(meta = (BindWidget))
	class UButton* BtnGoRegistry;
	
	UPROPERTY(meta = (BindWidget))
	class UEditableTextBox* TB_Username;

	UPROPERTY(meta = (BindWidget))
	class UEditableTextBox* TB_Password;
};

LoginWidget.cpp

// uecosmic


#include "Widget/LoginWidget.h"
#include "Components/Button.h"
#include "Components/EditableTextBox.h"
#include "Sys/AskGrpcSubsystem.h"

void ULoginWidget::NativeConstruct()
{
	Super::NativeConstruct();

	if (BtnLogin != nullptr)
	{
		BtnLogin->OnClicked.AddDynamic(this, &ULoginWidget::OnHandleLoginClicked);
	}
	if (BtnRegistry != nullptr)
	{
		BtnRegistry->OnClicked.AddDynamic(this, &ULoginWidget::OnHandleRegisterClicked);
	}
	
	if (BtnBackLogin != nullptr)
	{
		BtnBackLogin->OnClicked.AddDynamic(this, &ULoginWidget::OnLoginClicked);
	}
	
	if (BtnGoRegistry != nullptr)
	{
		BtnGoRegistry->OnClicked.AddDynamic(this, &ULoginWidget::OnRegistryClicked);
	}
}


void ULoginWidget::OnLoginClicked()
{
	BtnLogin->SetVisibility(ESlateVisibility::Visible);
	BtnBackLogin->SetVisibility(ESlateVisibility::Hidden);
	
	BtnRegistry->SetVisibility(ESlateVisibility::Hidden);
	BtnGoRegistry->SetVisibility(ESlateVisibility::Visible);
}

void ULoginWidget::OnRegistryClicked()
{
	BtnLogin->SetVisibility(ESlateVisibility::Hidden);
	BtnBackLogin->SetVisibility(ESlateVisibility::Visible);
	
	BtnRegistry->SetVisibility(ESlateVisibility::Visible);
	BtnGoRegistry->SetVisibility(ESlateVisibility::Hidden);
}


void ULoginWidget::OnHandleLoginClicked()
{
	const FString Username = TB_Username->GetText().ToString();
	const FString Password = TB_Password->GetText().ToString();
	const UAskGrpcSubsystem* Ass = GetGameInstance()->GetSubsystem<UAskGrpcSubsystem>();
	check(Ass);
	Ass->GetLoginService()->Signin(Username, Password);
	
}

void ULoginWidget::OnHandleRegisterClicked()
{
	const FString Username = TB_Username->GetText().ToString();
	const FString Password = TB_Password->GetText().ToString();

	const UAskGrpcSubsystem* Ass = GetGameInstance()->GetSubsystem<UAskGrpcSubsystem>();
	check(Ass);
	Ass->GetLoginService()->Signup(Username, Password);
}

AskGrpcSubsystem.h(GrpcWrapper 插件在商城)

// uecosmic

#pragma once

#include "CoreMinimal.h"
#include "GrpcWrapperSubsystem.h"
#if PLATFORM_WINDOWS
#include "Windows/AllowWindowsPlatformAtomics.h"
#include "Windows/PreWindowsApi.h"

	// Add native Windows/gRPC headers here
	#include "Login/LoginService.h"

#include "Windows/PostWindowsApi.h"
#include "Windows/HideWindowsPlatformAtomics.h"
#endif
#include "AskGrpcSubsystem.generated.h"


DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnUserIdReveiveMessage, const bool, IsSuccess,const int64, UserId);


UCLASS()
class ASK_CLIENT_API UAskGrpcSubsystem : public UGrpcWrapperSubsystem
{
	GENERATED_BODY()

public:

	virtual void Initialize(FSubsystemCollectionBase& Collection) override;
	virtual void Deinitialize() override;

protected:
	std::shared_ptr<grpc::Channel> channel;

private:
	TUniquePtr<FLoginService> LoginService;

public:
	UPROPERTY(BlueprintAssignable)
	FOnCharacterReveiveMessage OnCharacterReveiveMessage;

	FORCEINLINE FLoginService* GetLoginService() const { return LoginService.Get();}
};

AskGrpcSubsystem.cpp

// uecosmic

#include "Sys/AskGrpcSubsystem.h"
#include <grpcpp/security/credentials.h>

#include "Game/AskGameInstance.h"

void UAskGrpcSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
	Super::Initialize(Collection);
	const FString& ServerUrl = "127.0.0.1:8009";
	const FString& Certificate = "";
	const FString& SslHostName = "";
	
	UE_LOG(LogTemp, Log, TEXT("StartService: ServerUrl=%s, Certificate=%s, SslHostName=%s"), *ServerUrl, *Certificate, *SslHostName);
	
	std::shared_ptr<grpc::ChannelCredentials> credentials;
	if (Certificate.IsEmpty())
	{
		credentials = grpc::InsecureChannelCredentials();
	}
	else
	{
		grpc::SslCredentialsOptions option;
		option.pem_root_certs = TCHAR_TO_UTF8(*Certificate);
		credentials = grpc::SslCredentials(option);
	}

	grpc::ChannelArguments channelArgs;
	channelArgs.SetSslTargetNameOverride(TCHAR_TO_UTF8(*SslHostName));
	std::shared_ptr<grpc::Channel> Chan = grpc::CreateCustomChannel(TCHAR_TO_UTF8(*ServerUrl), credentials, channelArgs);
	
	if (!LoginService.IsValid())
	{
		LoginService = MakeUnique<FLoginService>();
		LoginService->SetChannel(Chan);
		LoginService->Start();

		LoginService->GetCallback().BindLambda(
		[this](const bool& IsSuccess, const int64& User_id)
		{
			if (IsSuccess)
			{
				UAskGameInstance* Agi = CastChecked<UAskGameInstance>(GetGameInstance());
				Agi->UserId = User_id;
			}
			OnUserIdReveiveMessage.Broadcast(IsSuccess, User_id);
		});
	}
}

void UAskGrpcSubsystem::Deinitialize()
{
	Super::Deinitialize();
	if (LoginService.IsValid())
	{
		LoginService->Stop();
		LoginService = nullptr;
	}
}

蓝图

一会发