盲目的学了一段时间了,刚开始从Box2d开始学习,明白了很多,Box2d是物理模型的基础,是我们在游戏中模拟现实的很重要的一个开源工具。后来在朋友的建议下学习了cocos,也是小程序开发的利器,而golang是一款高效的httprouter服务器,如果考虑安全肯定是没有tcp的安全。用起来太容易了,可以快速的游戏服务。
1.数据库连接:
数据库用的是gorm,连接数据库一个需要
_ "github.com/go-sql-driver/mysql"
"github.com/jinzhu/gorm"
mysql的驱动和gorm库支持。代码如下:
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.先分享一下主程序,这样看起来比较容易理解。
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,下面是跨域请求中间件:
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,限流中间件
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中间件
// 创建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端:
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验证。
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端协议的发送和接收
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;
}
}
}