红岩网校工作站2022春季后端期末考核:双人象棋
-
用户登陆注册更改密码
-
加入房间
- 玩家能主动开房,能够指定房间号加入房间
-
同一房间最多两人进入
-
房间内
- 玩家可以切换准备和未准备状态
-
两玩家都处于准备状态时可以自行开启游戏(本地)
-
游戏对战
- 以坐标的形式处理棋子位置信息
-
固定的棋盘大小
-
双方轮流着棋
-
判断获胜条件,在获胜条件出现后结束游戏
-
部署
- 使用Docker部署
-
客户端显示效果
- 使用Ebiten第三方库实现
- 房间
- 房间内玩家聊天
- 对低俗玩家踢出房间(说脏话超过三次)
- 可以多房间同时进行,一名用户也可以同时进入多个房间
- 服务拆分,用户中心使用gRPC重构,并使用etcd进行服务发现
- 使用redis缓存,使服务能够承受更高的负载
- 撰写Dockerfile生成仅有18m的镜像
- 使用pprof进行性能调优
- 使用Viper进行项目配置,并支持热重载配置
- 使用cron定时任务进行无用缓存的删除
https://www.postman.com/yuanxinhao/workspace/chess
42.192.155.29:6666/user/register
BODY
KEY | DESCRIPTION |
---|---|
username | 必填 |
password | 必填 |
question | 可选 |
answer | 可选 |
42.192.155.29:6666/user/login
BODY
KEY | DESCRIPTION |
---|---|
username | 必填 |
password | 必填 |
42.192.155.29:6666/user/password
HEADER
KEY | DESCRIPTION |
---|---|
TOKEN | 必填 |
BODY
KEY | DESCRIPTION |
---|---|
username | 必填 |
old_password | 必填 |
new_password | 必填 |
42.192.155.29:6666/ready/:room_id
HEADER
KEY | DESCRIPTION |
---|---|
TOKEN | 必填 |
PARAM
KEY | DESCRIPTION |
---|---|
room_id | 必填 |
ws://42.192.155.29:6666/?room_id=red
HEADER
KEY | DESCRIPTION |
---|---|
TOKEN | 必填 |
PARAM
KEY | DESCRIPTION |
---|---|
room_id | 必填 |
// 不合法信息3次,判断是否有不合法信息,没有进行信息发布
if c.limitNum >= 3 {
h.kickoutroom <- m
log.Println("素质太低,给你踢出去")
_ = c.ws.Close() //
} else //没有超过三次,可以继续
{
baseStr := "死傻操" //违法字符
testStr := string(msg[:])
for _, word := range testStr {
//遍历是否有违法字符
res := strings.Contains(baseStr, string(word))
if res == true {
c.limitNum += 1
c.forbiddenWord = true //禁言
//记录禁言开始时间
c.timeLog = time.Now().Unix()
h.warnings <- m
break
}
}
// 不禁言,消息合法 可以发送
if c.forbiddenWord != true {
// 通过所有检查,进行广播
m := message{msg, m.roomId, m.name, c}
h.broadcast <- m
}
}
撰写proto
文件
syntax = "proto3";
package user;
option go_package = "./user";
message RegisterReq{
string username = 1;
string password = 2;
string question = 3;
string answer = 4;
string uuid = 5;
}
message RegisterRes{
bool status = 1;
string description = 2;
}
message LoginReq{
string username = 1;
string password = 2;
}
message LoginRes{
bool status = 1;
string token = 2;
string description = 3;
}
message changeReq {
string old_password = 1;
string new_password = 2;
string username = 3;
}
message changeRes {
bool status = 1;
string description = 2;
}
service UserCenter{
rpc Register(RegisterReq) returns (RegisterRes);
rpc Login(LoginReq) returns(LoginRes);
rpc changePW (changeReq) returns (changeRes);
}
通过./一个cmd文件自动生成代码
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative ./user.proto
接下来就是进行分层,然后分别撰写server.go以及client.go并server中将服务加入到etcd中
在房间模块中使用redis,当你选择进入房间后会在redis中产生此房间的集合(并且最多能容纳两个人),退出时会将你从房间中删掉,定时任务会每个小时扫一遍,房间为空则删除。下次又有用户进入此房间的话则再次生成此房间集合
准备状态也是使用redis实现,也就是使用的最经典的点赞系统那套,点一次是准备,再点一次就取消,是根据集合中是否有你这个用户而实现的(依赖集合中成员不可重复的特性)
样例
//cron
_, err := c.AddFunc("@every 1h", func() {
err := redis.DeleteEmptyRoom()
if err != nil {
log.Println("cron err", err)
return
}
})
//redis
func DeleteEmptyRoom() error {
set, err := rdb.SMembers("room").Result()
if err != nil {
log.Println("len of room get error:", err)
return err
}
for _, v := range set {
es, err := rdb.SMembers("room_" + v).Result()
if err != nil {
log.Println(err)
return err
}
if len(es) <= 0 {
rdb.Del("room_" + v)
}
}
return nil
}
就是摈弃掉多余的内容,使用镜像
FROM golang:alpine AS builder
LABEL stage=gobuilder
ENV CGO_ENABLED 0
ENV GOPROXY https://goproxy.cn,direct
WORKDIR /build
ADD go.mod .
ADD go.sum .
RUN go mod download
COPY . .
RUN go build -ldflags="-s -w" -o /app/main ./main.go
FROM scratch
ENV TZ Asia/Shanghai
WORKDIR /app
COPY --from=builder /app/main /app/main
EXPOSE 6666
CMD ["./main"]
func InitPprofMonitor() {
go func() {
log.Println(http.ListenAndServe(":9990", nil))
}()
}
进入界面 http://localhost:9990/debug/pprof/
此节目可以查询详细参数,若想更加可视化一点可以安装graphviz,并在命令行中输入
$ go tool pprof -http=:8080 "http://localhost:9990/debug/pprof/heap //或其他
可以更加直观,或者进入火焰图界面,效果也很好
参考 https://link.juejin.cn/?target=https%3A%2F%2Fgithub.com%2Fwolfogre%2Fgo-pprof-practice
使用viper配置并进行热重载设置
配置模板 setting-dev.yaml
# settings-dev.yaml
name: "go-chess"
port: 1234
gorm:
name: "xx"
host: "xx.xx.xx.xx"
port: 3306
password: "xxxxxxxx"
dbName: "xx"
redis:
host: "xx.xx.xx.xx"
port: 6379
password: ":.xxxx@:xxx?Zx"
DB: 10
etcd:
addr: "xxxx.0.x:2379"
func InitConfig() {
// 实例化viper
v := viper.New()
//文件的路径如何设置
v.SetConfigFile("./setting-dev.yaml")
err := v.ReadInConfig()
if err != nil {
log.Println(err)
}
serverConfig := model.ServerConfig{}
//给serverConfig初始值
err = v.Unmarshal(&serverConfig)
if err != nil {
log.Println(err)
}
// 传递给全局变量
global.Settings = serverConfig
//热重载配置
v.OnConfigChange(func(e fsnotify.Event) {
log.Printf("config file:%s Op:%s\n", e.Name, e.Op)
})
v.WatchConfig()
若要在本地运行需要本地有Go C ETCD Redis环境
$ go clone https://github.com/L2ncE/go-chess
确保已配置setting-dev.yaml
$ go run main.go
$ cd ./rpc/user/server
$ etcd
$ go run userserver.go -addr 127.0.0.1:50001 //把ETCD跑起来
按照接口文档在同一个房间中加入两名用户
需要C编译器,因为 Ebitengine 不仅使用 Go,还使用 C。
$ apt install gcc
$ sudo apt install libc6-dev libglu1-mesa-dev libgl1-mesa-dev libxcursor-dev libxi-dev libxinerama-dev libxrandr-dev libxxf86vm-dev libasound2-dev pkg-config
PS:房间red中已有两名用户,可直接开启游戏进行测试
进入到chess包中运行main.go
若房间人满则可开始游戏
游戏逻辑以及棋盘设计参考 https://wangqianhong.com/tag/%e4%b8%ad%e5%9b%bd%e8%b1%a1%e6%a3%8b
每一次点击棋盘都会有坐标传出