go+mysql+cocos实现游戏搭建

盲目的学了一段时间了,刚开始从Box2d开始学习,明白了很多,Box2d是物理模型的基础,是我们在游戏中模拟现实的很重要的一个开源工具。后来在朋友的建议下学习了cocos,也是小程序开发的利器,而golang是一款高效的httprouter服务器,如果考虑安全肯定是没有tcp的安全。用起来太容易了,可以快速的游戏服务。

1.数据库连接:

数据库用的是gorm,连接数据库一个需要

_ "github.com/go-sql-driver/mysql"

"github.com/jinzhu/gorm"

mysql的驱动和gorm库支持。代码如下:

Go 复制代码
package mysqldb_test

import (
	"fmt"
	"log"
	mymodals "main/modals"
	"time"

	_ "github.com/go-sql-driver/mysql"
	"github.com/jinzhu/gorm"
	"github.com/userlll1986/main/config"
)

var Db *gorm.DB
var err error

// User 结构体声明
// type User struct {
// 	UserId    int64  `gorm:"primaryKey;autoIncrement"`
// 	UserName  string `gorm:"not null;type:varchar(32)"`
// 	UserPwd   string `gorm:"not null;type:varchar(128)"`
// 	UserPhone string `gorm:"unique;type:varchar(32)"`
// }

// 数据库配置
func InitDb(config *config.Config) {
	url := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
		config.Data.Username,
		config.Data.Password,
		config.Data.Ip,
		config.Data.Part,
		config.Data.DataBase,
	)
	// 这个地方要注意,不要写称 :=  写成 = 才对
	Db, err = gorm.Open(config.Data.Category, url)

	// 设置表前缀
	gorm.DefaultTableNameHandler = func(db *gorm.DB, defaultTableName string) string {
		//fmt.Println("db.DefaultTableNameHandler", config.Data.Prefix, defaultTableName)
		return config.Data.Prefix + defaultTableName
	}

	if err != nil {
		log.Fatalf("连接数据库【%s:%s/%s】失败, 失败的原因:【%s】", config.Data.Ip, config.Data.Part, config.Data.DataBase, err)
	}

	// db配置输出SQL语句
	Db.LogMode(config.Data.Sql)
	// 使用表名不适用复数
	Db.SingularTable(true)
	// 连接池配置
	Db.DB().SetMaxOpenConns(20)
	Db.DB().SetMaxIdleConns(10)
	Db.DB().SetConnMaxLifetime(10 * time.Second)
	// 判断是否需要用来映射结构体到到数据库
	// fmt.Println(config.Data.Init.Status)
	if config.Data.Init.Status {
		// 自动迁移数据库表结构
		// err := Db.AutoMigrate(&User{})
		// if err != nil {
		// 	fmt.Println("数据库表迁移失败!", err)
		// } else {
		// 	fmt.Println("数据库表迁移成功!")
		// }
		// 插入单条数据
		// var user = User{UserName: "wjj", UserPwd: "123", UserPhone: "111"}
		// Db.Create(&user)
		// var users = []User{
		// 	{UserName: "th", UserPwd: "123", UserPhone: "222"},
		// 	{UserName: "lhf", UserPwd: "123", UserPhone: "333"},
		// 	{UserName: "zcy", UserPwd: "123", UserPhone: "444"},
		// }
		// for _, user := range users {
		// 	Db.Create(&user)
		// }
		// 查询全部记录
		var users []mymodals.User
		Db.Find(&users)
		Db.Where("user_name = ?", "wjj").Find(&users)
		// Db.First(&users)
		// // 打印结果
		// fmt.Println(users)
		// 查询总数
		// var users []User
		// var totalSize int64
		// Db.Find(&users).Count(&totalSize)
		// fmt.Println("记录总数:", totalSize)
		// 查询user_id为1的记录
		// var stu User
		// Db.Where("user_id = ?", 1).Find(&stu)
		// // 修改stu姓名为wjj1
		// stu.UserName = "wjj1"
		// // 修改(按照主键修改)
		// Db.Save(&stu)
		// var stu User
		// Db.Model(&stu).Where("user_id = ?", 1).Update("user_name", "wjj2")
		// var fields = map[string]interface{}{"user_name": "WJJ", "user_pwd": "999"}
		// fmt.Println(fields)
		// Db.Model(&stu).Where("user_id = ?", 1).Updates(fields)
		// // 删除
		// var user = User{UserId: 1}
		// Db.Delete(&user)
		// 按照条件删除
		// Db.Where("user_id = ?", 10).Delete(&User{})
	}

	log.Printf("连接数据库【%s:%s/%s】成功", config.Data.Ip, config.Data.Part, config.Data.DataBase)
}

代码中注释的部分没有删除,有数据库自迁移和增删改查的基本操作在里面。

2.先分享一下主程序,这样看起来比较容易理解。

Go 复制代码
port := os.Getenv("PORT")

	if port == "" {
		port = "8080"
		log.Printf("Defaulting to port %s", port)
	}

	// 读取配置文件
	myconfig := config.NewConfig()
	myconfig.ReadConfig()
	// 初始化数据库连接
	mysqldb_test.InitDb(myconfig)

	// Starts a new Gin instance with no middle-ware
	r := gin.New()

	// 使用 Recovery 中间件
	r.Use(gin.Recovery())

	// 使用 Logger 中间件
	r.Use(gin.Logger())

	// 使用 CORSMiddleware 中间件
	r.Use(corsMiddleware())

	// 使用限流中间件
	r.Use(limiter.Middleware)

	//使用数据库中间件
	// 将db作为中间件传递给路由处理函数
	r.Use(func(c *gin.Context) {
		c.Set("db", mysqldb_test.Db)

		c.Next()
	})
	// 在路由处理函数中可以通过c.MustGet("db").(*gorm.DB)获取到db对象,然后进行数据库操作

	// 创建Redis客户端
	redisClient := redis.NewClient(&redis.Options{
		Addr:     "127.0.0.1:54372", // Redis服务器地址
		Password: "123456",          // Redis密码
		DB:       0,                 // Redis数据库编号
	})
	// 使用Redis中间件
	r.Use(func(c *gin.Context) {
		// 在Gin的上下文中设置Redis客户端
		c.Set("redis", redisClient)
		// 继续处理后续的请求
		c.Next()
	})
	// 定义路由和处理函数
	r.GET("/get/:key", func(c *gin.Context) {
		// 从上下文中获取Redis客户端
		redisClient := c.MustGet("redis").(*redis.Client)

		// 从URL参数中获取键名
		key := c.Param("key")

		// 使用Redis客户端进行GET操作
		val, err := redisClient.Get(c, key).Result()
		if err == redis.Nil {
			c.JSON(200, gin.H{
				"result": fmt.Sprintf("Key '%s' not found", key),
			})
		} else if err != nil {
			c.JSON(500, gin.H{
				"error": err.Error(),
			})
		} else {
			c.JSON(200, gin.H{
				"result": val,
			})
		}
	})

	// 添加ES中间件,暂不使用
	//r.Use(ElasticSearchMiddleware())

	// 定义路由
	// r.GET("/", func(c *gin.Context) {
	// 	// 从上下文中获取ES客户端
	// 	esClient := c.MustGet("esClient").(*elastic.Client)

	// 	// 使用ES客户端进行查询
	// 	// 这里只是一个示例,具体的ES查询操作可以根据实际需求进行修改
	// 	_, _, err := esClient.Ping().Do(c.Request.Context())
	// 	if err != nil {
	// 		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to ping Elasticsearch"})
	// 		return
	// 	}

	// 	c.JSON(http.StatusOK, gin.H{"message": "Hello from Gin with Elasticsearch middleware!"})
	// })

	// 创建RabbitMQ连接
	conn, err := amqp.Dial("amqp://lafba13j4134:llhafaif99973@localhost:5672/")
	if err != nil {
		fmt.Println("连接RabbitMQ失败:", err)
		return
	}
	defer conn.Close()

	// 添加RabbitMQ中间件
	r.Use(RabbitMQMiddleware(conn, "my_queue"))

这里有数据库中间件(第一部分讲的),日志中间件,限流中间件,rabbitmq中间件等。

下面会每一部分加上代码,这些中间件很容易使用,加一些代码就能直接使用,很方便。

3,下面是跨域请求中间件:

Go 复制代码
func corsMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 允许所有的跨域请求
		c.Header("Access-Control-Allow-Origin", "*")
		c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
		c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization")
		c.Header("Access-Control-Max-Age", "86400") // 预检请求缓存时间,单位为秒

		// 处理预检请求
		if c.Request.Method == "OPTIONS" {
			c.AbortWithStatus(200)
			return
		}

		// 继续处理其他请求
		c.Next()
	}
}

4,限流中间件

Go 复制代码
var (
	limiter = NewLimiter(10, 1*time.Minute) // 设置限流器,允许每分钟最多请求10次
)

// NewLimiter 创建限流器
func NewLimiter(limit int, duration time.Duration) *Limiter {
	return &Limiter{
		limit:      limit,
		duration:   duration,
		timestamps: make(map[string][]int64),
	}
}

// Limiter 限流器
type Limiter struct {
	limit      int                // 限制的请求数量
	duration   time.Duration      // 时间窗口
	timestamps map[string][]int64 // 请求的时间戳
}

// Middleware 限流中间件
func (l *Limiter) Middleware(c *gin.Context) {
	ip := c.ClientIP() // 获取客户端IP地址

	// 检查请求时间戳切片是否存在
	if _, ok := l.timestamps[ip]; !ok {
		l.timestamps[ip] = make([]int64, 0)
	}

	now := time.Now().Unix() // 当前时间戳

	// 移除过期的请求时间戳
	for i := 0; i < len(l.timestamps[ip]); i++ {
		if l.timestamps[ip][i] < now-int64(l.duration.Seconds()) {
			l.timestamps[ip] = append(l.timestamps[ip][:i], l.timestamps[ip][i+1:]...)
			i--
		}
	}

	// 检查请求数量是否超过限制
	if len(l.timestamps[ip]) >= l.limit {
		c.JSON(429, gin.H{
			"message": "Too Many Requests",
		})
		c.Abort()
		return
	}

	// 添加当前请求时间戳到切片
	l.timestamps[ip] = append(l.timestamps[ip], now)

	// 继续处理请求
	c.Next()
}

5,redis中间件

Go 复制代码
// 创建Redis客户端
	redisClient := redis.NewClient(&redis.Options{
		Addr:     "127.0.0.1:54372", // Redis服务器地址
		Password: "123456",          // Redis密码
		DB:       0,                 // Redis数据库编号
	})
	// 使用Redis中间件
	r.Use(func(c *gin.Context) {
		// 在Gin的上下文中设置Redis客户端
		c.Set("redis", redisClient)
		// 继续处理后续的请求
		c.Next()
	})

中间件等使用很简单,直接c.MustGet("redis").(*redis.Client)直接取出来就能使用,c是gin.Context类型,是请求传的参数。

6,go和cocos通讯

在gin端:

Go 复制代码
func login(c *gin.Context) {
	var req mymodals.AccountServiceRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	if req.Type == "AccountService" && req.Tag == "account_login" {
		if resp, err := myaccount.CallFunc("Account_login", c, req.Type, req.Tag, req.Body); err != nil {
			fmt.Println("Error:", err)
			var resp mymodals.AccountServiceResponse
			resp.Result = "error"
			resp.Body.Length = 1
			resp.Body.Detail = err.Error()
			c.JSON(http.StatusBadRequest, resp)
		} else {
		
			fmt.Println("Result of bar: ", resp[0].Interface())
			c.JSON(http.StatusOK, resp[0].Interface())
		}

	} else if req.Type == "AccountService" {
		if resp, err := myaccount.CallFunc(req.Tag, c, req.Type, req.Tag, req.Body); err != nil {
			fmt.Println("Error:", err)
			var resp mymodals.AccountServiceResponse
			resp.Result = "error"
			resp.Body.Length = 1
			resp.Body.Detail = err.Error()
			c.JSON(http.StatusBadRequest, resp)
		} else {
			fmt.Println("Result of bar:", resp[0].Interface())
			c.JSON(http.StatusOK, resp[0].Interface())
		}
	}
}

CallFunc用的是反射机制,通过传过来的参数调用函数。反射调用的具体函数如下,这里面调用了数据库中间价,做了token验证。

Go 复制代码
func Account_login(c *gin.Context, Type string, Tag string, Body map[string]interface{}) mymodals.AccountServiceResponse {
	// 这里进行实际的登录验证逻辑
	log.Printf("Account_login: %v,%v,%v", Type, Tag, Body)
	// 例如:检查用户名和密码是否匹配,验证码是否正确等
	var users []mymodals.User
	var db = c.MustGet("db").(*gorm.DB)
	db.Where("user_name = ?", Body["username"].(string)).Find(&users)

	var resp mymodals.AccountServiceResponse
	resp.Type = "AccountService"
	resp.Tag = "account_login"
	//增加token验证
	token, err := authjwt.GenerateToken(Body["username"].(string))

	if err != nil {
		// ctx.JSON(http.StatusInternalServerError, gin.H{
		// 	"code":    500, // Token生成错误
		// 	"message": "请重新登录",
		// })
		var resp mymodals.AccountServiceResponse
		resp.Result = "error"
		resp.Body.Length = 1
		resp.Body.Detail = err.Error()
		// c.JSON(http.StatusBadRequest, resp)
		return resp
	}

	if len(users) > 0 {
		// 验证密码是否正确
		hashedPassword := hashPassword(Body["password"].(string))
		log.Printf("hashedPassword: %s", hashedPassword)

		if users[0].UserPwd == Body["password"].(string) {
			// 登录成功, 记录登录日志
			// c.JSON(http.StatusOK, gin.H{"result": "ok"})
			resp.Result = "ok"
			resp.Body.Length = 1
			resp.Body.Token = token
			// return
		} else {
			resp.Result = "error"
			resp.Body.Length = 1
			resp.Body.Detail = "用户名或密码错误"
		}
	} else {
		resp.Result = "error"
		resp.Body.Length = 1
		resp.Body.Detail = "用户名或密码错误"
	}
	log.Printf("登录响应: %v", resp)
	return resp
	// 将响应数据发送给客户端
	// c.JSON(http.StatusOK, resp)
}

7.cocos端协议的发送和接收

Go 复制代码
import { _decorator, Component, Node,Button,EventMouse,EditBox,Label } from 'cc';
import { setGlobalData, getGlobalData } from './global';
const { ccclass, property } = _decorator;

@ccclass('login')
export class login extends Component {

    @property(Button)
    private btnLogin: Button | null = null; // 

    @property(Button)
    private btnLogout: Button | null = null; // 

    @property(EditBox)
    private editBoxUsername: EditBox | null = null; // 用户名输入框

    @property(EditBox)
    private editBoxPassword: EditBox | null = null; // 密码输入框

    @property(Label)
    private errorMessage: Label | null = null; // 用于显示错误信息的 Label

    start() {
        if (this.btnLogin) {
            // // 监听鼠标进入 Mask 事件
           
            this.btnLogin.node.on("click", this.onMouseClickMask, this);
           
        }
        if (this.errorMessage) {
            this.errorMessage.node.active = false;
        }
        if (this.btnLogout) {
            this.btnLogout.node.on("click", this.onMouseClickLogout, this);
        }
    }

    private onMouseClickLogout(event: EventMouse) {
        console.log('鼠标点击了 Logout 按钮');
        // 在这里添加你点击后想要执行的逻辑
        // 关闭当前游戏窗口
        cc.director.loadScene("EmptyScene");
        
    }

    update(deltaTime: number) {
        
    }

    private onMouseClickMask(event: EventMouse) {
        console.log('鼠标点击了 Mask 区域');
        // 在这里添加你点击后想要执行的逻辑
        const username = this.editBoxUsername ? this.editBoxUsername.string : "";
        const password = this.editBoxPassword ? this.editBoxPassword.string : "";
        const ip = "127.0.0.1"; // 获取客户端的 IP 地址,这里假设为固定的 IP
        const captcha = "12345"; // 从输入框获取验证码
        const proving = "北京市"; // 从输入框获取省份
        const machineCode = "machine123"; // 获取机器码
        const clientType = "ios"; // 客户端类型
        const data = JSON.stringify({
            type: "AccountService",
            tag: "account_login",
            body: {
                username: username,
                password: password,
                ip: ip,
                captcha: captcha,
                proving: proving,
                machineCode: machineCode,
                client_type: clientType
            }
        });
        fetch('http://localhost:8080/v2/login', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: data,
        })
        .then(response => response.json())
        .then(data => this.handleLoginResponse(data))
        .catch((error) => console.error('Error:', error));
    }

  
    handleLoginResponse(response: any): void {
        if (response.result === "ok") {
            console.log("登录成功",response,response.body.token);
            // 在这里可以进行登录成功的处理逻辑
            setGlobalData(response.body.token); // 获取登录成功后的 Token
            // 例如:跳转到游戏主界面
            cc.director.loadScene("hall");
        } else {
            console.log("登录失败:", response.body.detail);
            // 在这里可以进行登录失败的处理逻辑
            // 例如:显示错误提示
            if (this.errorMessage) {
                this.errorMessage.string = response.body.detail;
                this.errorMessage.node.active = true;
                // 设置 3 秒后自动隐藏错误信息
                setTimeout(() => {
                    this.errorMessage.node.active = false;
                }, 3000);
            }
            // cc.find('Canvas/ErrorMessage').getComponent(cc.Label).string = response.body.detail;
        }
    }
}
相关推荐
Tony Bai12 小时前
高并发后端:坚守 Go,还是拥抱 Rust?
开发语言·后端·golang·rust
lxysbly14 小时前
安卓玩MRP冒泡游戏:模拟器下载与使用方法
android·游戏
luoluoal15 小时前
基于python的小区监控图像拼接系统(源码+文档)
python·mysql·django·毕业设计·源码
stella·16 小时前
mysql的时区问题
数据库·mysql·timezone·时区
残雪飞扬18 小时前
MySQL 8.0安装
数据库·mysql
一只鹿鹿鹿18 小时前
网络信息与数据安全建设方案
大数据·运维·开发语言·网络·mysql
么么...18 小时前
深入理解数据库事务与MVCC机制
数据库·经验分享·sql·mysql
CodeCaptain19 小时前
Cocos Creator 3.8.0 官方文档明确支持 Tiled Editor v1.4 版本,也兼容 1.4.x 小版本(如1.4.3)
cocos2d
LYOBOYI12319 小时前
qml练习:实现游戏相机(3)
数码相机·游戏
哈里谢顿20 小时前
MySQL 和 Redis搭配使用指南
redis·mysql