雲原生專案實踐DevOps(GitOps)+K8S+BPF+SRE,從0到1使用Golang開發生產級麻將遊戲伺服器—第4篇

為少發表於2021-02-17

遊客登入鑑權之業務程式碼實戰

系列文章

  1. 從0到1使用Golang開發生產級麻將遊戲伺服器—第1篇
  2. 從0到1使用Golang開發生產級麻將遊戲伺服器—第2篇
  3. 從0到1使用Golang開發生產級麻將遊戲伺服器—第3篇

介紹

這將是一個完整的,完全踐行 DevOps/GitOpsKubernetes 上雲流程的 Golang 遊戲伺服器開發的系列教程。

這個系列教程是對開源專案 Nanoserver 的完整拆解,旨在幫助大家快速上手 Golang(遊戲)伺服器後端開發。通過實踐去理解 Golang 開發的精髓 —— Share memory by communication(通過通訊共享記憶體)

同時這個專案可能還會涉及到 Linux 效能調優(BPF 相關的工具)和系統保障(SRE)的相關的工作。

Step-By-Step 開發 Mahjong Server

  • 單體架構理解 Mahjong Server 業務 -> Nano Distributed Game Server(分散式) + 微服務 改造。
  • Demo:go-mahjong-server

VSCode REST Client 外掛

如果你是用 VSCode 作為 IDE,這個外掛不錯:

遊客登入業務

業務分析

從0到1使用Golang開發生產級麻將遊戲伺服器—第3篇

業務 E-R 圖

API:查詢遊客登入是否啟用

REST Client 測試 API

Request

POST http://192.168.31.125:12307/v1/user/login/query HTTP/1.1
content-type: application/json

{
    "channelId": "konglai",
    "appId": "konglai"
}

Response

HTTP/1.1 200 OK
Access-Control-Allow-Headers: Origin, Content-Type, Authorization
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Origin: *
Content-Type: application/json; charset=utf-8
Date: Sun, 07 Feb 2021 15:00:16 GMT
Content-Length: 24
Connection: close

{
  "code": 0,
  "guest": true
}

業務邏輯分析

  1. 比較簡單,就是根據伺服器 configs/config.toml 檔案的配置,進行驗證:
...
[login]
guest = true
lists = ["test", "konglai"]
...

API:遊客登入

REST Client 測試 API

Request

POST http://127.0.0.1:12307/v1/user/login/guest HTTP/1.1
content-type: application/json

{
    "channelId": "konglai",
    "appId": "konglai",
    "imei": "c0a4ce912c48a3d0b17b59e6b97f1dca"
}

Response

{
	"code": 0,
	"name": "G1",
	"uid": 1,
	"headUrl": "http://wx.qlogo.cn/mmopen/s962LEwpLxhQSOnarDnceXjSxVGaibMRsvRM4EIWic0U6fQdkpqz4Vr8XS8D81QKfyYuwjwm2M2ibsFY8mia8ic51ww/0",
	"fangka": 10,
	"sex": 1,
	"ip": "192.168.31.125",
	"port": 33251,
	"playerIp": "192.168.31.125",
	"config": {
		"version": "1.9.3",
		"android": "https://fir.im/tand",
		"ios": "https://fir.im/tios",
		"heartbeat": 30,
		"forceUpdate": true,
		"title": "血戰到底",
		"desc": "純正四川玩法,快捷便利的掌上血戰,輕鬆組局,隨時隨地盡情遊戲",
		"daili1": "kefuweixin01",
		"daili2": "kefuweixin01",
		"kefu1": "kefuweixin01",
		"appId": "xxx",
		"appKey": "xxx"
	},
	"messages": ["系統訊息:健康遊戲,禁止賭博", "歡迎進入遊戲"],
	"clubList": [],
	"debug": 0
}

業務邏輯分析

DB Model

db/model/struct.go

type User struct {
	Id              int64
	Algo            string `xorm:"not null VARCHAR(16) default"`
	Hash            string `xorm:"not null VARCHAR(64) default"`
	Salt            string `xorm:"not null VARCHAR(64) default"`
	Role            int    `xorm:"not null TINYINT(3) default 1"`
	Status          int    `xorm:"not null TINYINT(3) default 1"`
	IsOnline        int    `xorm:"not null TINYINT(1) default 1"`
	LastLoginAt     int64  `xorm:"not null index BIGINT(11) default"`
	PrivKey         string `xorm:"not null VARCHAR(512) default"`
	PubKey          string `xorm:"not null VARCHAR(128) default"`
	Coin            int64  `xorm:"not null BIGINT(20) default 0"`
	RegisterAt      int64  `xorm:"not null index BIGINT(20) default 0"`
	FirstRechargeAt int64  `xorm:"not null index BIGINT(20) default 0"`
	Debug           int    `xorm:"not null index TINYINT(1) default 0"`
}
使用者表 描述
Id 自增ID
Algo 加密演算法
Hash 加密hash
Salt 加密撒鹽
Role 賬號型別(RoleTypeAdmin=1 管理員賬號,RoleTypeThird=2 三方平臺賬號)
Status 賬號狀態(StatusNormal=1 正常,StatusDeleted=2 刪除,StatusFreezed=3 凍結,StatusBound=4 繫結)
IsOnline 是否線上(UserOffline=1 離線,UserOnline=2 線上)
LastLoginAt 最後登入時間
PrivKey 賬號證書私鑰
PubKey 賬號證書公鑰
Coin 房卡數量
RegisterAt 註冊時間
FirstRechargeAt 首充時間
Debug 使用者資訊除錯

type Register struct {
	Id           int64
	Uid          int64  `xorm:"not null index BIGINT(20) default"`
	Remote       string `xorm:"not null VARCHAR(40) default"`
	Ip           string `xorm:"not null VARCHAR(40) default"`
	Imei         string `xorm:"not null VARCHAR(128) default"`
	Os           string `xorm:"not null VARCHAR(20) default"`
	Model        string `xorm:"not null VARCHAR(20) default"`
	AppId        string `xorm:"not null index VARCHAR(32) default"`
	ChannelId    string `xorm:"not null index VARCHAR(32) default"`
	RegisterAt   int64  `xorm:"not null index BIGINT(11) default"`
	RegisterType int    `xorm:"not null index TINYINT(8) default"`
}
使用者註冊記錄表 描述
Id 自增ID
Uid 使用者ID
Remote 外網IP
Ip 內網IP
Model 硬體型號
Imei 裝置的imei號
Os os版本號
AppId 應用id
ChannelId 渠道id
RegisterAt 註冊時間
RegisterType 註冊型別(RegTypeThird=5 三方平臺新增賬號)
type Login struct {
	Id        int64
	Uid       int64  `xorm:"not null index BIGINT(20) default"`
	Remote    string `xorm:"not null VARCHAR(40) default"`
	Ip        string `xorm:"not null VARCHAR(40) default"`
	Model     string `xorm:"not null VARCHAR(64) default"`
	Imei      string `xorm:"not null VARCHAR(32) default"`
	Os        string `xorm:"not null VARCHAR(64) default"`
	AppId     string `xorm:"not null VARCHAR(64) default"`
	ChannelId string `xorm:"not null VARCHAR(32) default"`
	LoginAt   int64  `xorm:"not null BIGINT(11) default"`
	LogoutAt  int64  `xorm:"not null BIGINT(11) default"`
}
使用者登入記錄表 描述
Id 自增ID
Uid 使用者ID
Remote 外網IP
Ip 內網IP
Model 硬體型號
Imei 裝置的imei號
Os os版本號
AppId 應用id
ChannelId 渠道id
LoginAt 登入時間
LogoutAt 登出時間
  1. 根據 AppID(使用者來自於哪一個應用) 與 Device.IMEI(裝置的imei號),確定當前遊客是否已經註冊
user, err := db.QueryGuestUser(data.AppID, data.Device.IMEI)

db.QueryGuestUser,會從 registeruser 表中去查詢使用者是否存在。

相關 protocol 的定義:

protocol/login.go

type LoginRequest struct {
	AppID     string `json:"appId"`     //使用者來自於哪一個應用
	ChannelID string `json:"channelId"` //使用者來自於哪一個渠道
	IMEI      string `json:"imei"`
	Device    Device `json:"device"`
}

protocol/common.go

type Device struct {
	IMEI   string `json:"imei"`   //裝置的imei號
	OS     string `json:"os"`     //os版本號
	Model  string `json:"model"`  //硬體型號
	IP     string `json:"ip"`     //內網IP
	Remote string `json:"remote"` //外網IP
}
  1. 如果沒有註冊,則生成一個新使用者,並且註冊一條使用者記錄

涉及到的相關 db 常量的定義:

db/const.go

const (
	StatusNormal  = 1 //正常
	StatusDeleted = 2 //刪除
	StatusFreezed = 3 //凍結
	StatusBound   = 4 //繫結
)

const (
	UserOffline = 1 //離線
	UserOnline  = 2 //線上
)

// Users表中role欄位的取值
const (
	RoleTypeAdmin = 1 //管理員賬號
	RoleTypeThird = 2 //三方平臺賬號
)

生成一個新使用者:

const defaultCoin = 10 // 預設給的房卡數量是 10

user = &model.User{
  Status:   db.StatusNormal,
  IsOnline: db.UserOffline,
  Role:     db.RoleTypeThird,
  Coin:     defaultCoin,
}

db.InsertUser(user)

註冊一條使用者記錄

db.RegisterUserLog(user, data.Device, data.AppID, data.ChannelID, protocol.RegTypeThird) //註冊記錄
  1. 構造 login 響應資料

相關 protocol 的定義:

protocol/login.go

type LoginResponse struct {
	Code     int          `json:"code"`
	Name     string       `json:"name"`
	Uid      int64        `json:"uid"`
	HeadUrl  string       `json:"headUrl"`
	FangKa   int64        `json:"fangka"`
	Sex      int          `json:"sex"` //[0]未知 [1]男 [2]女
	IP       string       `json:"ip"`
	Port     int          `json:"port"`
	PlayerIP string       `json:"playerIp"`
	Config   ClientConfig `json:"config"`
	Messages []string     `json:"messages"`
	ClubList []ClubItem   `json:"clubList"`
	Debug    int          `json:"debug"`
}

type ClientConfig struct {
	Version     string `json:"version"`
	Android     string `json:"android"`
	IOS         string `json:"ios"`
	Heartbeat   int    `json:"heartbeat"`
	ForceUpdate bool   `json:"forceUpdate"`

	Title string `json:"title"` // 分享標題
	Desc  string `json:"desc"`  // 分享描述

	Daili1 string `json:"daili1"`
	Daili2 string `json:"daili2"`
	Kefu1  string `json:"kefu1"`

	AppId  string `json:"appId"`
	AppKey string `json:"appKey"`
}

protocol/club.go

type (
	ClubItem struct {
		Id        int64  `json:"id"`
		Name      string `json:"name"`
		Desc      string `json:"desc"`
		Member    int    `json:"member"`
		MaxMember int    `json:"maxMember"`
	}
  // ....
)
  1. 插入登入記錄,返回客戶端所需資料
device := protocol.Device{
		IP:     ip(r.RemoteAddr),
		Remote: r.RemoteAddr,
	}
db.InsertLoginLog(user.Id, device, data.AppID, data.ChannelID)

return resp, nil
  1. 一圖勝千言,秒懂

關於遊戲伺服器登入與 Nano 遊戲伺服器通訊相關程式碼實戰,我們下篇再詳細討論。

我是為少
微信:uuhells123
公眾號:黑客下午茶
加我微信(互相學習交流),關注公眾號(獲取更多學習資料~)

相關文章