雲原生專案實踐DevOps(GitOps)+K8S+BPF+SRE,從0到1使用Golang開發生產級麻將遊戲伺服器—第4篇
阿新 • • 發佈:2021-02-17
![](https://img2020.cnblogs.com/blog/436453/202102/436453-20210217194414160-1510051665.jpg)
## 遊客登入鑑權之業務程式碼實戰
### 系列文章
1. [從0到1使用Golang開發生產級麻將遊戲伺服器—第1篇](https://mp.weixin.qq.com/s/Jyq_A1vehrnMwv6AdOtQ1w)
2. [從0到1使用Golang開發生產級麻將遊戲伺服器—第2篇](https://mp.weixin.qq.com/s/jnQaz08fAzQ3J7tZBdil4Q)
3. [從0到1使用Golang開發生產級麻將遊戲伺服器—第3篇](https://mp.weixin.qq.com/s/ktGxVD5VBoSzbBb-vwxxOg)
### 介紹
這將是一個完整的,完全踐行 `DevOps/GitOps` 與 `Kubernetes` 上雲流程的 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](https://github.com/Hacker-Linner/go-mahjong-server)
## VSCode REST Client 外掛
如果你是用 VSCode 作為 IDE,這個外掛不錯:
![](https://img2020.cnblogs.com/blog/436453/202102/436453-20210217194440209-1964410437.png)
## 遊客登入業務
### 業務分析
[從0到1使用Golang開發生產級麻將遊戲伺服器—第3篇](https://mp.weixin.qq.com/s/ktGxVD5VBoSzbBb-vwxxOg)
### 業務 E-R 圖
![](https://img2020.cnblogs.com/blog/436453/202102/436453-20210217194455282-159528682.png)
## API:查詢遊客登入是否啟用
### REST Client 測試 API
`Request`:
```sh
POST http://192.168.31.125:12307/v1/user/login/query HTTP/1.1
content-type: application/json
{
"channelId": "konglai",
"appId": "konglai"
}
```
`Response`:
```json
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
}
```
![](https://img2020.cnblogs.com/blog/436453/202102/436453-20210217194534315-934337761.png)
### 業務邏輯分析
1. 比較簡單,就是根據伺服器 `configs/config.toml` 檔案的配置,進行驗證:
```toml
...
[login]
guest = true
lists = ["test", "konglai"]
...
```
## API:遊客登入
### REST Client 測試 API
`Request`:
```sh
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`:
```json
{
"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`
```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 | 使用者資訊除錯 |
```go
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 三方平臺新增賬號)|
```go
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號`),確定當前遊客是否已經註冊
```go
user, err := db.QueryGuestUser(data.AppID, data.Device.IMEI)
```
`db.QueryGuestUser`,會從 `register` 或 `user` 表中去查詢使用者是否存在。
相關 `protocol` 的定義:
`protocol/login.go`
```golang
type LoginRequest struct {
AppID string `json:"appId"` //使用者來自於哪一個應用
ChannelID string `json:"channelId"` //使用者來自於哪一個渠道
IMEI string `json:"imei"`
Device Device `json:"device"`
}
```
`protocol/common.go`
```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
}
```
2. 如果沒有註冊,則生成一個新使用者,並且註冊一條使用者記錄
涉及到的相關 `db` 常量的定義:
`db/const.go`
```go
const (
StatusNormal = 1 //正常
StatusDeleted = 2 //刪除
StatusFreezed = 3 //凍結
StatusBound = 4 //繫結
)
const (
UserOffline = 1 //離線
UserOnline = 2 //線上
)
// Users表中role欄位的取值
const (
RoleTypeAdmin = 1 //管理員賬號
RoleTypeThird = 2 //三方平臺賬號
)
```
生成一個新使用者:
```go
const defaultCoin = 10 // 預設給的房卡數量是 10
user = &model.User{
Status: db.StatusNormal,
IsOnline: db.UserOffline,
Role: db.RoleTypeThird,
Coin: defaultCoin,
}
db.InsertUser(user)
```
註冊一條使用者記錄
```go
db.RegisterUserLog(user, data.Device, data.AppID, data.ChannelID, protocol.RegTypeThird) //註冊記錄
```
3. 構造 `login` 響應資料
相關 `protocol` 的定義:
`protocol/login.go`
```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`
```go
type (
ClubItem struct {
Id int64 `json:"id"`
Name string `json:"name"`
Desc string `json:"desc"`
Member int `json:"member"`
MaxMember int `json:"maxMember"`
}
// ....
)
```
4. 插入登入記錄,返回客戶端所需資料
```go
device := protocol.Device{
IP: ip(r.RemoteAddr),
Remote: r.RemoteAddr,
}
db.InsertLoginLog(user.Id, device, data.AppID, data.ChannelID)
return resp, nil
```
5. 一圖勝千言,秒懂
![](https://img2020.cnblogs.com/blog/436453/202102/436453-20210217194548557-808716901.jpg)
關於遊戲伺服器登入與 Nano 遊戲伺服器通訊相關程式碼實戰,我們下篇再詳細討論。
```
我是為少
微信:uuhells123
公眾號:黑客下午茶
加我微信(互相學習交流),關注公眾號(獲取更多學習資料~