Go 实现WebSocket,初步实操
什么是长连接和短连接?在HTTP/1.0中默认使用短连接。也就是说,客户端和服务器每进行一次HTTP操作,就建立一次连接,任务结束就中断连接。当客户端浏览器访问的某个HTML或其他类型的Web页中包含有其他的Web资源(如JavaScript文件、图像文件、CSS文件等),每遇到这样一个Web资源,浏览器就会重新建立一个HTTP会话。而从HTTP/1.1起,默认使用长连接,用以保持连接特性。使用长连接的HTTP协议,会在响应头加入这行代码:Connection:keep-alive在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。实现长连接需要客户端和服务端都支持长连接。HTTP协议的长连接和短连接,实质上是TCP协议的长连接和短连接。Go WebSocket今天我们会用Go语言使用WebSocket,经过多方收集资料,整理了后端的服务端+客户端,然后服务端和前端的链接两个示例,下面是代码,供大家研究。今天我们用的是Gin框架实现一个长链接。1、服务端+客户端(后端)服务端:package main import ( "fmt" "log" "net/http" "time" "github.com/gin-gonic/gin" "github.com/gorilla/websocket" ) var upgrader = websocket.Upgrader{} func main() { // 使用gin框架,和普通的http协议的服务器没什么不一样 s := gin.Default() s.GET("/sendMsg", echo) _ = s.Run("127.0.0.1:8090") } func echo(c *gin.Context) { //服务升级,对于来到的http连接进行服务升级,升级到ws cn, err := upgrader.Upgrade(c.Writer, c.Request, nil) defer cn.Close() if err != nil { panic(err) } for { mt, message, err := cn.ReadMessage() fmt.Println("接受到消息", message) if err != nil { log.Println("server read:", err) break } log.Printf("server recv msg: %s", message) msg := string(message) fmt.Println(msg, "msg") if msg == "clent" { message = []byte("客户端来了") } err = cn.WriteMessage(mt, message) if err != nil { log.Println(" server write err:", err) break } } } 客户端:package main import ( "log" "net/url" "os" "os/signal" "time" "github.com/gorilla/websocket" ) func main() { interrupt := make(chan os.Signal, 1) signal.Notify(interrupt, os.Interrupt) u := url.URL{Scheme: "ws", Host: "127.0.0.1:8090", Path: "/sendMsg"} log.Printf("client1 connecting to %s", u.String()) c, _, err := websocket.DefaultDialer.Dial(u.String(), nil) if err != nil { log.Fatal("dial server:", err) } defer c.Close() done := make(chan struct{}) go func() { defer close(done) for { _, message, err := c.ReadMessage() if err != nil { log.Println("client read err:", err) return } log.Printf("client recv msg: %s", message) } }() for { select { // if the goroutine is done , all are out case <-done: return case <-time.Tick(time.Second * 5): err := c.WriteMessage(websocket.TextMessage, []byte("clent")) if err != nil { log.Println("client write:", err) return } case <-interrupt: log.Println("client interrupt") err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) if err != nil { log.Println("client1 write close:", err) return } select { case <-done: case <-time.After(time.Second): } return } } } 分别启动服务端和客户端:服务端会根据客户端发来的消息回复2、服务端+前端(前后端)接下来,我们用前后端交互一下websocket前端页面:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>websocket测试</title> <script src="http://www.alingfeng.cn/js/jquery.min.js"></script> </head> <body> <button id="clickButton">点击测试websocket</button> <script> $("#clickButton").click(function () { var name = $("#name").val(); initWebpack(name); }); </script> <script> var planWebsocket = null; var planIP = "127.0.0.1"; // IP地址 var planPort = "8090"; // 端口号 function initWebpack(clickName) { //初始化websocket if ("WebSocket" in window) { planWebsocket = new WebSocket( "ws://" + planIP + ":" + planPort + "/sendMsg" ); // 通信地址 setInterval( (planWebsocket.onopen = function (event) { console.log("建立连接"); let sendData = { command: "sendMsg", data: [{ msg: "服务端,我发送了消息,注意查收!" }], }; planWebsocket.send(JSON.stringify(sendData)); // 发送获取数据的接口 }), 2000 ); planWebsocket.onmessage = function (event) { // console.log('收到消息:' + event.data) let data = JSON.parse(event.data); if (data.command == "sendMsg") { var planData = data.data; //返回的数据 console.log(planData); } else if (data.command == "getscenes") { // 其他命令 } }; planWebsocket.onclose = function (event) { console.log("连接关闭"); }; planWebsocket.onerror = function () { alert("websocket通信发生错误!"); }; } else { alert("该浏览器不支持websocket!"); } } // initWebpack(); //调用 </script> </body> </html> 服务:package main import ( "fmt" "log" "net/http" "time" "github.com/gin-gonic/gin" "github.com/gorilla/websocket" ) // var upgrader = websocket.Upgrader{} var upgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, // 解决跨域问题 CheckOrigin: func(r *http.Request) bool { return true }, } func main() { // 使用gin框架,和普通的http协议的服务器没什么不一样 s := gin.Default() s.GET("/sendMsg", echo) _ = s.Run("127.0.0.1:8090") } func echo(c *gin.Context) { //服务升级,对于来到的http连接进行服务升级,升级到ws cn, err := upgrader.Upgrade(c.Writer, c.Request, nil) defer cn.Close() if err != nil { panic(err) } for { mt, message, err := cn.ReadMessage() fmt.Println("接受到消息", message) if err != nil { log.Println("server read:", err) break } log.Printf("server recv msg: %s", message) msg := string(message) fmt.Println(msg, "msg") // if msg == "clent1" { // message = []byte("客户端1来了") // } else if msg == "clent2" { // message = []byte("你好客户端2") // } message = []byte("客户端,我已接收") for i := 0; i < 100; i++ { time.Sleep(1000000000) err = cn.WriteMessage(mt, message) } if err != nil { log.Println(" server write err:", err) break } } } 测试:我们初步了解Go语言做websocket就可以了。
查看详情点赞8评论收藏1浏览1272023-06-03 10:04:00Gin框架系列教程(4)- Router 路由封装
今天我们研究一下gin中的路由应该怎么配置:首先我们先测试一个简单的路由,也就是默认路由:package main import ( "github.com/gin-gonic/gin" ) func main(c *gin.Context) { r := gin.Default() r.GET("/", func(c *gin.Context) { c.String(200, "hello world") }) r.run('9000') 这个就是首页路由,很简短。那么gin框架中怎么封装路由呢,就是把多个路由按照一定规则进行拆分封装。下面代码展示:一、主入口文件初始化路由r := gin.Default() // 路由初始化 routers.Routers(r)二、新建routers文件夹,以及user,member路由文件夹,路由分组,三个示例文件(routers.go,member.go,user.go)routers.go//router.go package routers import ( "gin_alingfeng/routes/memberRouter" "gin_alingfeng/routes/userRouter" "github.com/gin-gonic/gin" ) func Routers(r *gin.Engine) { //api分组路由 api := r.Group("/api") memberRouter.MemberApiRouter(api) userRouter.UserApiRouter(api) //直接路由 memberRouter.MemberRouter(r) userRouter.UserRouter(r) }member.gopackage memberRouter import ( "gin_alingfeng/controllers/userController" "gin_alingfeng/middleware" "github.com/gin-gonic/gin" ) func MemberApiRouter(api *gin.RouterGroup) { api.GET("/member", func(c *gin.Context) { c.String(200, "hello world member") }) } func MemberRouter(r *gin.Engine) { r.GET("/member2", middleware.Cors(), middleware.Logs, userController.User) } user.gopackage memberRouter import ( "gin_alingfeng/controllers/userController" "gin_alingfeng/middleware" "github.com/gin-gonic/gin" ) func MemberApiRouter(api *gin.RouterGroup) { api.GET("/member", func(c *gin.Context) { c.String(200, "hello world member") }) } func MemberRouter(r *gin.Engine) { r.GET("/member2", middleware.Cors(), middleware.Logs, userController.User) } 结构如下:
查看详情点赞1评论收藏1浏览2582024-02-06 09:43:03Gin框架系列教程(5)- 项目集成swagger及注解简单应用
Swagger介绍:Swagger 是一个规范且完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。Swagger 的目标是对 REST API 定义一个标准且和语言无关的接口,可以让人和计算机拥有无须访问源码、文档或网络流量监测就可以发现和理解服务的能力。当通过 Swagger 进行正确定义,用户可以理解远程服务并使用最少实现逻辑与远程服务进行交互。与为底层编程所实现的接口类似,Swagger 消除了调用服务时可能会有的猜测。支持 API 自动生成同步的在线文档:使用 Swagger 后可以直接通过代码生成文档,不再需要自己手动编写接口文档了,对程序员来说非常方便,可以节约写文档的时间去学习新技术。提供 Web 页面在线测试 API:光有文档还不够,Swagger 生成的文档还支持在线测试。参数和格式都定好了,直接在界面上输入参数对应的值即可在线测试接口。安装Swagger:从 Go 1.17 开始,go get不推荐使用安装可执行文件。go install可以改用:go install github.com/swaggo/swag/cmd/swag@latest swag init //是否安装成功 swag -v项目使用:导入import ( _ "gin_alingfeng/docs" swaggerfiles "github.com/swaggo/files" ginSwagger "github.com/swaggo/gin-swagger" )主程序入口,以及swagger路由:// @host 127.0.0.1:9000 // @schemes http // @Title 接口文档 // @version v1.0 // @description 所有接口的方法,描述,参数,响应,文档,测试,仅供参考,有问题请及时联系管理员。 func main() { r := gin.Default() // 路由初始化 routers.Routers(r) r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler)) // 数据库初始化 models.Init() //启动 r.Run(":9000") }api方法注解:(参数,可以单独写,也可以用结构体一起放进去)// @Summary 新增用户 // @Description 新增用户 // @Tags User // @Accept json // @Param name body string true "姓名" example(不超过50字) // @Param email body string false "邮箱" example(不超过50字) // @Param account body string true "账号" example(不超过50字) // @Param data query modules.User true "这里是介绍" // @Produce json // @Success 200 {string} string "{"success":200,"msg":"新增成功"}" // @Failure 500 {string} Add 失败后返回值 // @Router /api/v1/user/add [post] func Add(c *gin.Context) { c.String(200, "user add") }注意:每次修改了注释都需要重新init一下:swag init //其他用法 swag init -dir ./cmd/ -output docs/api/ --pd true --dir main.go文件所在目录 --output 输出目录 --pd 从依赖关系中解析文件 默认false用法描述:// @Tags 每个API操作的标签列表,以逗号分隔,如分组 // @Summary 该操作的简短摘要,如api名称 // @Router 以空格分隔的路径定义。 格式:path,[httpMethod] // @Accept 请求体接收的格式,值为Mime类型 // @param 请求参数用空格分隔的参数。 param name,param type,data type,is mandatory?,comment,attribute(optional) 1.参数名,2.参数类型,3.参数数据类型,4.是否必须,5.参数描述,6.其他属性 其他属性详解: 最大最小值 minimum(0.01),maximum(100) 最大最小长度 minLength(1),maxLength(200) 枚举值 Enums(A, B),Enums(1, 2),Enums(1.1, 2.2) 示例 example(A) 默认值 default(A)-选填 // @Produce 返回体返回的格式,值为Mime类型 // @Success 以空格分隔的成功响应。 return code,{param type},data type,comment 1.返回状态码,2.返回参数类型,3.返回数据,4.参数描述 // @failure 以空格分隔的故障响应。 return code,{param type},data type,comment 1. 返回状态码,2.返回参数类型,3.返回数据,4.参数描述 // Response 定义业务处理响应类型 type Success_Response struct { Code RespCode `json:"code" example:"0"` Msg string `json:"msg" example:"操作成功"` Data RespData `json:"data" example:""` } // Response 定义业务处理响应类型 type failure_Response struct { Code RespCode `json:"code" example:"1"` Msg string `json:"msg" example:"操作失败"` Data RespData `json:"data example:""` } type类型:json: application/json, x-www-form-urlencoded: application/x-www-form-urlencoded, xml: text/xml, plain: text/plain, html: text/html, mpfd: multipart/form-data, json-api: application/vnd.api+json, json-stream: application/x-json-stream, octet-stream: application/octet-stream, png: image/png, jpeg: image/jpeg, gif: image/gif,数据类型:● string (string)● integer (int, uint, uint32, uint64)● number (float32)● boolean (bool)● user defined struct参数类型:● query● path● header● body● formData最后:访问地址:构建您的应用程序,然后访问http://localhost:8080/swagger/index.html,您将看到您的 Swagger UI。
查看详情点赞1评论收藏浏览872024-02-06 15:50:59Gin框架系列教程(6)- 连接Mysql,集成Gorm库
本文主要是了解一下Gin框架如何链接MySQL并集成Gorm库的。当然ORM库有很多,虽然gorm库被很多人吐槽,但是并不能否认它很强大,很完善,用的人最多。当然你也可以用其他的库。介绍:Object-Relationl Mapping,即对象关系映射,这里的Relationl指的是关系型数据库,它的作用是在关系型数据库和对象之间作一个映射,这样,我们在具体的操作数据库的时候,就不需要再去和复杂的SQL语句打交道,只要像平时操作对象一样操作它就可以了,Golang写的,GitHub上活跃度很高的orm库。特点:全功能ORM(几乎)关联(包含一个,包含多个,属于,多对多,多种包含)Callbacks(创建/保存/更新/删除/查找之前/之后)预加载(急加载)事务复合主键SQL Builder自动迁移日志可扩展,编写基于GORM回调的插件每个功能都有测试开发人员友好安装:go get gorm.io/driver/mysql go get gorm.io/gorm链接MySQL,迁移数据库,我这里用的是viper解析yaml文档,配置数据库的信息(就直接展示代码):链接并配置:config.yamlmysql: db_username: "root" db_password: "root" db_database: "demo_database" db_host: "127.0.0.1" db_port: "3306" logs: log_level: "debug"base.gopackage models import ( "time" "github.com/spf13/viper" "gorm.io/driver/mysql" "gorm.io/gorm" ) var DB *gorm.DB var err error func Init() { //读取配置 viper.SetConfigFile("config/config.yaml") errConfi := viper.ReadInConfig() if errConfi != nil { panic(errConfi) } db_username := viper.GetString("mysql.db_username") db_password := viper.GetString("mysql.db_password") db_database := viper.GetString("mysql.db_database") db_host := viper.GetString("mysql.db_host") db_port := viper.GetString("mysql.db_port") DB, err = gorm.Open(mysql.New(mysql.Config{ DSN: db_username + ":" + db_password + "@tcp(" + db_host + ":" + db_port + ")/" + db_database + "?charset=utf8mb4&parseTime=True&loc=Local", // DSN data source name DefaultStringSize: 256, // string 类型字段的默认长度 DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持 DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引 DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列 SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置 }), &gorm.Config{}) if err != nil { panic(err) } //迁移数据库,上线之后去掉 DB.Set("gorm:table_options", "ENGINE=InnoDB").AutoMigrate(&User{}, &Article{}, &ArticleCategory{}, &Role{}, &Menus{}) sqlDB, _ := DB.DB() // SetMaxIdleConns 设置空闲连接池中连接的最大数量 sqlDB.SetMaxIdleConns(100) // SetMaxOpenConns 设置打开数据库连接的最大数量。 sqlDB.SetMaxOpenConns(50) // SetConnMaxLifetime 设置了连接可复用的最大时间。 sqlDB.SetConnMaxLifetime(time.Hour) }模型下的一个User结构体,用于迁移数据库等:package models import ( "gorm.io/gorm" ) type User struct { gorm.Model Mame string `json:"name" form:"name" gorm:"column:name;comment:姓名;type:varchar(10);"` //姓名 Account string `json:"account" form:"account" gorm:"column:account;comment:账号;type:varchar(10);"` //账号 Password string `json:"password" form:"password" gorm:"column:password;comment:密码;type:varchar(32);"` //密码 Email string `json:"email" form:"email" gorm:"column:email;comment:邮箱;type:varchar(300);"` //邮箱 Image string `json:"image" form:"image" gorm:"column:image;comment:头像;type:varchar(300);"` //头像 IsEnabled int64 `json:"is_enabled" form:"is_enabled" gorm:"column:is_enabled;comment:是否启用;type:smallint;default:1"` //是否启用 } func (User) TableName() string { return "users" }最后注册进程序主入口中:// 数据库初始化 models.Init()结构如下:
查看详情点赞1评论收藏1浏览2842024-02-07 14:37:18Gin框架系列教程(7)- Gin集成JWT权限认证
不管是我们什么后台,系统,都会接触了用户登录Token认证,今天我们需要gin集成jwt功能。如今有很多将身份验证内置到API中的方法 -JSON Web令牌只是其中之一。JSON Web令牌(JWT)作为令牌系统而不是在每次请求时都发送用户名和密码,因此比其他方法(如基本身份验证)具有固有的优势。要了解更多信息,请直接进入jwt.io上的介绍,然后再直接学习。通过JSON形式作为Web应用中的令牌,用于在各方之间安全地将信息作为JSON对象传输,可以避免被别人篡改,以及在用户认证授权方面相对于传统Session减小了开销。接下来我们使用jwt:直接上代码package utils import ( "errors" "fmt" "time" "github.com/dgrijalva/jwt-go/v4" ) var JwtSecret string = "xxxxxxxxxxxxxx" type Users struct { Account string `json:"account"` Password string `json:"password"` } type CustomClaims struct { Users jwt.StandardClaims } var MySecret = []byte(JwtSecret) // 创建 Token func GenToken(user Users) (string, error) { claim := CustomClaims{ user, jwt.StandardClaims{ ExpiresAt: jwt.At(time.Now().Add(time.Minute * 120)), //2小时后过期 Issuer: "xxxxxxxx", //签发人 }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claim) return token.SignedString(MySecret) } // 解析 token func ParseToken(tokenStr string) (*CustomClaims, error) { token, err := jwt.ParseWithClaims(tokenStr, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) { return MySecret, nil }) if err != nil { fmt.Println(" token parse err:", err) return nil, err } if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid { return claims, nil } return nil, errors.New("invalid token") } // 刷新 Token func RefreshToken(tokenStr string) (string, error) { jwt.TimeFunc = func() time.Time { return time.Unix(0, 0) } token, err := jwt.ParseWithClaims(tokenStr, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) { return MySecret, nil }) if err != nil { return "", err } if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid { jwt.TimeFunc = time.Now claims.StandardClaims.ExpiresAt = jwt.At(time.Now().Add(time.Minute * 120)) return GenToken(claims.Users) } return "", errors.New("Cloudn't handle this token") } 上面这段是我们用户登录成功创建token。然后我们需要用一个jwt中间件来验证用户权限:func Jwt() gin.HandlerFunc { return func(c *gin.Context) { // var users utils.Users // users.Account = "admin" // users.Password = "12346" // str, err1 := utils.GenToken(users) // fmt.Println("str", str) // fmt.Println("err1", err1) tokenString := c.GetHeader("Authorization") fmt.Println("getHeaderToken", tokenString) if tokenString == "" { c.JSON(http.StatusUnauthorized, gin.H{"code": http.StatusUnauthorized, "msg": "权限不足"}) c.Abort() return } claims, err := utils.ParseToken(tokenString) if err != nil { c.JSON(http.StatusUnauthorized, gin.H{"code": http.StatusUnauthorized, "err": err, "msg": "权限不足"}) c.Abort() return } fmt.Println(claims) c.Next() } }好了,我们来测试一下,结果是没问题的。
查看详情点赞1评论收藏1浏览2312024-02-24 11:12:44Gin框架系列教程(8)- Gin图片&文件上传(单文件)
今天我们用Gin框架做一个单图片上传功能,多图上传同理就行。之后有时间可以测试一下。直接上代码:后端文件上传工具package utils import ( "errors" "os" "path" "strconv" "time" "github.com/gin-gonic/gin" ) func UploadFile(c *gin.Context) (string, error) { // 读取file文件 file, err := c.FormFile("file") if err != nil { return "", nil } //文件大小校验,限制300KB大小 size := file.Size if size/1024 > 300 { return "", errors.New("超过允许上传文件大小,最大300KB") } // 文件后缀校验 extName := path.Ext(file.Filename) allowExtMap := map[string]bool{ ".jpg": true, ".png": true, ".gif": true, ".jpeg": true, } if _, ok := allowExtMap[extName]; !ok { return "", errors.New("不允许上传此类文件") } // 创建图片文件目录 day := DateStr() dir := "./public/upload/" + day err = os.MkdirAll(dir, 0666) if err != nil { return "", err } //执行上传,毫秒级时间名称 now := time.Now() unixNano := now.UnixNano() / int64(time.Millisecond) createFileName := strconv.FormatInt(unixNano, 10) + extName pathDir := path.Join(dir, createFileName) c.SaveUploadedFile(file, pathDir) outerPath := "/" + pathDir return outerPath, nil } // 组装年月日字符串当做文件路径 func DateStr() string { template := "20060102" return time.Now().Format(template) } 后端调用:// @Summary 文件上传 // @Description 用于文件上传 // @Tags UploadFile // @Accept json // @Produce json // @Success 200 {string} string "{"success":200,"msg":"上传成功"}" // @Failure 500 {string} UploadFile 失败后返回值 // @Router /api/v1/upload [post] func UploadFile(c *gin.Context) { path, err := utils.UploadFile(c) if err != nil { response.Error(c, http.StatusInternalServerError, err, "上传失败") } else { response.Success(c, path, "上传成功") } }好了,测试图片上传成功。
查看详情点赞1评论收藏浏览2492024-03-25 15:19:52Gin框架系列教程(9)- gin+grpc,实现微服务数据交互
什么是rpc?1、定义:远程过程调用协议。和http请求类似,只不过协议不同,rpc使用的大多数为TCP,http请求使用的为HTTP协议。可以简单的理解为一个节点调用另外一个节点。2.组成:客户端:Client,服务调用方。客户端存根:Client Stub,存放服务端地址信息,将客户端的请求参数数据信息打包成网络消息,再通过网络传输发送给服务端网络传输模块:Network Service,底层传输,可以是 TCP 或 HTTP服务端存根:Server Stub,接收客户端发送过来的请求消息并进行解包,然后再调用本地服务进行处理。服务端等:server,服务的真正提供者。3.执行过程:Client 客户端通过本地调用的方式调用服务。客户端存根(Client Stub)接收到调用请求后负责将方法、入参等信息序列化(组装)成能够进行网络传输的消息体。客户端存根(Client Stub)找到远程的服务地址,并且将消息通过网络发送给服务端。网络传输服务端存根(Server Stub)收到消息后进行解码(反序列化操作)。服务端存根(Server Stub)根据解码结果调用本地的服务进行相关处理服务端(Server)本地服务业务处理。处理结果返回给服务端存根(Server Stub)。服务端存根(Server Stub)序列化结果。服务端存根(Server Stub)将结果通过网络发送至消费方。客户端存根(Client Stub)接收到消息,并进行解码(反序列化)。客户端得到最终结果。什么是grpc1、定义:是一个高性能、开源、通用的RPC框架。gRPC中采用的是HTTP2协议谷歌的产品gRPC默认使用protoBuf2.组成:类似rpc3.执行过程:类似rpc安装GRPC + protoc-gen-go使用以下命令安装protoc-gen-go插件,它用于将protocol buffer文件生成Go代码这里不做过多介绍,网上有很多教程。go get -u google.golang.org/grpc go get -u github.com/golang/protobuf/protoc-gen-go安装完成,检查是否安装成功。实际操作:我们接下来定义一个user服务,和一个article服务,然后用gin服务同时来连接两个服务,调用不同数据返回给接口。我们先定义两个proto文件,然后生成,创建服务端,然后用客户端连接并获取数据。这是大概思路。下面我们来简单实现一下代码:1、定义proto文件并生成定义user.protosyntax = "proto3"; package userBuf; option go_package = "./userBuf"; message UserAddReq { string name = 1; int64 age = 2; string sex = 3; } message UserAddResp { string msg = 1; string code = 2; } service UserService { rpc UserAdd (UserAddReq) returns (UserAddResp); }定义article.protosyntax = "proto3"; package articleBuf; option go_package = "./articleBuf"; message ArticleAddReq { string name = 1; int64 age = 2; string sex = 3; } message ArticleAddResp { string msg = 1; string code = 2; } service ArticleService { rpc ArticleAdd (ArticleAddReq) returns (ArticleAddResp); }生成protoBuf文件,命令:protoc --go_out=. --go-grpc_out=. user.proto protoc --go_out=. --go-grpc_out=. article.proto2、创建grpc服务端user.gopackage main import ( "context" "fmt" "gprc_demo/user/userBuf" "net" "google.golang.org/grpc" ) type UserServer struct { userBuf.UnimplementedUserServiceServer } func (s UserServer) UserAdd(ctx context.Context, in *userBuf.UserAddReq) (*userBuf.UserAddResp, error) { var aa userBuf.UserAddResp aa.Msg = in.Name aa.Code = "200" fmt.Println("user 进来了") return &aa, nil } func main() { // 创建grpc服务 s := grpc.NewServer() userBuf.RegisterUserServiceServer(s, UserServer{}) listen, err := net.Listen("tcp", "127.0.0.1:6001") fmt.Println("监听6001端口。。。") if err != nil { fmt.Println("网络错误") } s.Serve(listen) // grpc服务启动 } article.gopackage main import ( "context" "fmt" "gprc_demo/article/articleBuf" "net" "google.golang.org/grpc" ) type ArticleServer struct { articleBuf.UnimplementedArticleServiceServer } func (s ArticleServer) ArticleAdd(ctx context.Context, in *articleBuf.ArticleAddReq) (*articleBuf.ArticleAddResp, error) { var aa articleBuf.ArticleAddResp aa.Msg = in.Name aa.Code = "200" fmt.Println("article 进来了") return &aa, nil } func main() { // 创建grpc服务 s := grpc.NewServer() articleBuf.RegisterArticleServiceServer(s, ArticleServer{}) listen, err := net.Listen("tcp", "127.0.0.1:6002") fmt.Println("监听6002端口。。。") if err != nil { fmt.Println("网络错误") } s.Serve(listen) // grpc服务启动 } 3、创建Gin服务端,调用数据package main import ( "gprc_demo/article/articleBuf" "gprc_demo/user/userBuf" "net/http" "github.com/gin-gonic/gin" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" ) func main() { r := gin.Default() // 创建grpc连接,grpc客户端 connUser, err := grpc.Dial("127.0.0.1:6001", grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { panic(err) } defer connUser.Close() userClient := userBuf.NewUserServiceClient(connUser) connArticle, err := grpc.Dial("127.0.0.1:6002", grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { panic(err) } defer connArticle.Close() articleClient := articleBuf.NewArticleServiceClient(connArticle) // 定义一个Gin路由 r.POST("/hello", func(c *gin.Context) { name := "你好" name1 := "小明" // 调用gRPC服务 userReq := &userBuf.UserAddReq{Name: name} userResp, userErr := userClient.UserAdd(c, userReq) articleRreq := &articleBuf.ArticleAddReq{Name: name1} articleResp, articleErr := articleClient.ArticleAdd(c, articleRreq) if userErr != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": userErr}) return } if articleErr != nil { c.JSON(http.StatusInternalServerError, gin.H{"error2": articleErr}) return } c.JSON(http.StatusOK, gin.H{ "code": 200, "message": userResp.Msg + "," + articleResp.Msg, }) }) // 启动Gin服务器 if err := r.Run(":6000"); err != nil { panic(err) } } 最终我们的文件结构如图:上面我们同时连接user服务和article服务,分别传入“你好”,“小明”,两个数据,然后调用服务返回的数据返回给接口,我们看下效果。好了,数据测试没问题,当然我们用的是ip+端口直连,我们也可以用edcd,consul等工具来管理服务。
查看详情点赞1评论收藏浏览1722024-04-23 17:39:56Gin框架系列教程(10)- gin IP限流+令牌桶限流
我们经常有一些场景需要用到限流,比如,我们登录的时候会限制IP时间段内登录的次数,或者防止并发量突然增高时,服务器无法承受,保证了QPS的上限值。限流的方法有很多,下面我们来实现一下gin IP限流+令牌桶限流,参考网上多种方法测试代码如下:中间件:package middleware import ( "admin/services/commonService/response" "fmt" "net/http" "sync" "time" "github.com/gin-gonic/gin" "github.com/juju/ratelimit" ) type RequestInfo struct { LastAccessTime time.Time // 上次访问时间 RequestNum int // 请求计数 } var ( requestInfoMap = make(map[string]*RequestInfo) // IP到请求信息的映射 mutex = &sync.Mutex{} // 用于保护requestInfoMap的互斥锁 maxRequests = 2 // 允许的最大请求数 timeWindow = 1 * time.Second // 时间窗口 ) // IP限流器 func IpLimit(c *gin.Context) { ip := c.ClientIP() mutex.Lock() defer mutex.Unlock() // 检查IP是否在map中 info, exists := requestInfoMap[ip] // 如果IP不存在,初始化并添加到map中 if !exists { requestInfoMap[ip] = &RequestInfo{LastAccessTime: time.Now(), RequestNum: 1} return } // 如果IP存在,检查时间窗口 if time.Since(info.LastAccessTime) > timeWindow { // 如果超过时间窗口,重置请求计数 info.RequestNum = 1 info.LastAccessTime = time.Now() return } info.RequestNum++ // 如果在时间窗口内,增加请求计数 // 如果请求计数超过限制,禁止访问 if info.RequestNum > maxRequests { errMsg := fmt.Errorf("too many requests") response.Error(c, http.StatusTooManyRequests, errMsg, "请求过于频繁,请稍后再试!") c.Abort() return } // 更新最后访问时间 info.LastAccessTime = time.Now() c.Next() } // ratelimit限流器 func RateLimit(time time.Duration, originNum, pushNum int64) gin.HandlerFunc { bucket := ratelimit.NewBucketWithQuantum(time, originNum, pushNum) return func(c *gin.Context) { if bucket.TakeAvailable(1) < 1 { errMsg := fmt.Errorf("too many requests") response.Error(c, http.StatusTooManyRequests, errMsg, "请求过于频繁,请稍后再试!") c.Abort() return } c.Next() } } 路由中使用中间件:api.POST("/member/login", middleware.IpLimit, memberApi.MemberLogin) //登录 api.POST("/member/login", middleware.RateLimit(time.Second, 10, 10), memberApi.MemberLogin) //登录结果:经过测试上面两种方式都能达到限流的目的。
查看详情点赞1评论收藏1浏览1422024-04-24 17:13:07Gin框架系列教程(1)- gin框架介绍,环境搭建
(从 0-1 搭建你的Gin框架后台系统)一、gin框架介绍Gin 是一个用 Go (Golang) 编写的 web 框架。它是一个类似于 martini 但拥有更好性能的 API 框架,由于 httprouter,速度提高了近 40 倍,简单易用,是一个轻量级框架。如果你是性能和高效的追求者,你会爱上 Gin。二、为什么选择gin1.运行响应非常快2.快速开发3.文档齐全4.社区活跃三、特性1.快速:基于 Radix 树的路由,小内存占用。没有反射。可预测的 API 性能。2.支持中间件:传入的 HTTP 请求可以由一系列中间件和最终操作来处理。 例如:Logger,Authorization,GZIP,最终操作 DB。3.Crash 处理:Gin 可以 catch 一个发生在 HTTP 请求中的 panic 并 recover 它。这样,你的服务器将始终可用。例如,你可以向 Sentry 报告这个 panic!4.JSON 验证:Gin 可以解析并验证请求的 JSON,例如检查所需值的存在。5.路由组:更好地组织路由。是否需要授权,不同的 API 版本…… 此外,这些组可以无限制地嵌套而不会降低性能。6.错误管理:Gin 提供了一种方便的方法来收集 HTTP 请求期间发生的所有错误。最终,中间件可以将它们写入日志文件,数据库并通过网络发送。7.内置渲染:Gin 为 JSON,XML 和 HTML 渲染提供了易于使用的 API。8.可扩展性:新建一个中间件非常简单gin环境搭建注意:go1.9版本以上,很快将不再支持go1.7或go1.8。一、go环境安装1.go安装下载地址: https://go.dev/dl/这里选择你需要下载的版本,现在最新版是1.20.5,可以自己选择需要的版本。windows,和linux的安装方法稍有差异,不过大同小异,网上有很多方法,不过多介绍,可以多参考网上。这里我们直接安装windows版本(go1.19.4)。然后,我们安装windows软件一样,傻瓜式操作,直接下一步,下一步到底。2.环境配置:变量名:GOPATH 变量值:E:\go\workspace 你的工作路径变量名:GOROOT 变量值:E:\go\install go的安装路径变量名:Path 增加值:%GOROOT%\bin;%GOPATH%\bin;注意:删除自动添加的gopath和goroot注意添加完GOPATH,GOROOT两个变量值,还需要添加他们下面的bin执行目录。3.检查配置是否成功go env 查看GOPATH和GOROOT是否正确然后就可以看到安装的目录和版本是否正确,当显示下面截图,我们就安装成功了。二、工程管理:工作目录下新建三个文件夹1.src:用于以代码包的形式组织并保存go源码文件,2.pkg:用于存放经由go install命令构建的安装后的代码包,不需要手动创建3.bin:与pkg目录类似,在通过go install命令完成安装后,保存由go命令源码间生成的可执行文件三、安装gingin的安装就很简单了,直接命令拉取。go get -u github.com/gin-gonic/gin五、安装编辑工具(goland,vscode等都可以,看自己喜欢)第一节我们就到这里结束了,接下来我们就开始gin框架吧。hello world
查看详情点赞评论收藏浏览822023-06-19 15:36:49Gin框架系列教程(2)- Gin的第一次,hello world!
(从 0-1 搭建你的Gin框架后台系统)我们先新建一个文件,gin_demo,然后初始化一个go项目,我们用go mod来管理包,之后我们讲一下什么是modgo mod init gin_demo接下来新建main.go文件,引入Gin,开始第一个项目package main import "github.com/gin-gonic/gin" func main() { // g:=gin.New() g := gin.Default() g.GET("/", func(c *gin.Context) { c.String(200, "hello world") }) g.Run(":9000") }运行项目:go run main我们的第一个Gin项目就可以了。运行原理一、router:=gin.Default()初始化一个引擎,是gin.New()的升级二、router.GET1.RESTFUL风格的请求方法(method)2.有两个参数:relativePath:路由,string类型HandlerFunc:执行的函数3.可以使用router.Handle代替,多了个的method参数(字符串),method参数必须是大写的,如:GET三、执行的函数1.必须有个参数是gin.Context指针类型的注意:context是gin的一个重要组成部分。用来在中间层传递数据流。2.函数是个参数,不能调用四、router.Run启动http监听,有个address参数,字符串类型的,可以指定host和port注意:addr的host和port是用冒号分隔的只指定port,port前面必须要有冒号指定了host和port,host和port中间有冒号不能只指定hostg.Run(":9000")
查看详情点赞评论收藏浏览522023-06-19 16:49:00