从0开始的go+websocket构建五子棋对战系统
undefinedundefined
基本框架
直接照搬,不多解释。
dao为数据库处理层
po为实体类
middleware为中间件,cors处理跨域
app对request请求进行封装
router处理路由
service为服务层,进行数据处理逻辑
网络框架
使用gin作为整体的网络框架,文档在这里https://github.com/gin-gonic/gin
//main.go
func main() {
engine := gin.Default()
dao.Setup()
service.Setup()
router.Setup(engine)
err := engine.Run(fmt.Sprintf(":%v", "5521"))
if err != nil {
panic(err)
}
//user := po.User{1, "wxy", "2020"}
//service.Test(&user)
}
gin.default用来生成一个处理网络请求的实体。交由router进行处理
//router
func Setup(engine *gin.Engine) {
//处理cors
engine.Use(middleware.Cors())
//静态文件
//engine.Static("")
user := engine.Group("/user")
{
hub := service.ExUserService
user.POST("/test", app.HandlerFunc(hub.Test))
}
}
捕获路由并交给对应的函数(hub.test)处理。
数据持久化
使用gorm进行数据库管理。在dao中实现对应函数。例如存储一个用户信息方法实现如下
//dao/user.go
type UserDao struct {
Tx *gorm.DB
}
func (u UserDao) SaveUser(user *po.User) {
err := u.Tx.Save(user).Error
if err != nil {
panic(err)
}
}
在go中,会默认在将对象保存到类名(严格的讲这么说是不准确的,因为go并非面向对象语言)加s。如果一个user类的实体对象,保存后就会存储到users表中。
数据库的连接
db, err := gorm.Open(mysql.Open(fmt.Sprintf("%v:%v@tcp(%v:%v)/%v?charset=utf8mb4&parseTime=True&loc=Local",
"username", "password", "1.1.1.1", 3306, "dbname")), &gorm.Config{})
其中username,password分别填数据库的用户名密码。1.1.1.1替换成数据库ip,3306为端口,dbname是库名。
websocket架构
使用melody处理websocket请求。melody是基于github.com/gorilla/websocket 抽象出的websocket处理框架。可以直接使用。
文档在这里https://pkg.go.dev/gopkg.in/olahol/melody.v1#section-readme
下载该依赖需要换源,方法问谷歌。
实例化一个melody对象,然后为其设置收到msg时候的处理方法。最后在路由中指向该对象即可由其接收websocket请求。
//websocket.go
func InitMelody() *melody.Melody {
m = melody.New()
m.HandleMessage(Receive)
return m
}
func Receive(s *melody.Session, msg []byte) {
m.Broadcast(msg)
}
//router.go
engine.GET("/ws", func(c *gin.Context) {
m.HandleRequest(c.Writer, c.Request)
})
上面的函数简单的进行了一个消息的复读。测试一下

然后测试一下由服务器主动发送数据。

加上对于用户连接和用户断开的处理逻辑
//websocket.go
func InitMelody() *melody.Melody {
m = melody.New()
m.HandleMessage(Receive)
m.HandleConnect(Connect)
m.HandleDisconnect(DisConnect)
return m
}
func Connect(s *melody.Session) {
id := uuid.NewV4().String()
idMap.Store(id, s)
s.Set("Id", id)
if !service.ExPlayerService.Connect(id, "unNamed") {
log.Print("此id已连接")
} else {
log.Print(id, "连接成功")
}
}
func DisConnect(s *melody.Session) {
id, ok := s.Get("Id")
fmt.Println(id, ok)
if ok == false {
return
}
service.ExPlayerService.DisConnect(id.(string))
idMap.LoadAndDelete(id)
fmt.Println(id, "断开连接")
}
用idMap保存从id到s的映射。方便此后根据id进行消息的传送
然后在service层加上对应player的处理逻辑
//service.player
func (p PlayerService) Connect(id string, name string) bool {
_, ok := maPlayer[id]
if ok {
return false
}
player := po.Player{
Id: id,
Name: name,
Color: -1,
}
maPlayer[id] = player
return true
}
func (p PlayerService) DisConnect(id string) bool {
_, ok := maPlayer[id]
if !ok {
return false
}
delete(maPlayer, id)
return true
}
持续更新中...
夜空霓虹都是我不要的繁荣
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构