文章

Gin

Gin

认识Gin

Gin是什么?

  • Gin:Go语言编写的Web框架,以更好的性能实现类似Martini框架的API
  • Gin事一个golang的net/http库封装的web微框架,封装比较优雅,API友好,源码注释比较明确。具有快速灵活,容错方便等特点。

学习Web框架

image-20230118192052682

  • 如果假设我们这里选择的是:gin和gorm
  • 用户在浏览器访问地址,这个地址其实是服务器提供。用于寻址一样,先找到服务器在哪里,我要访问服务器的什么
  • 如果根据ip找到地址以后,通过port找到服务器的服务,就开始进入到程序中把对应的逻辑去执行(这个部分未来是使用gin定义路由的对应的要执行的事情)
  • 在执行的逻辑中,我们肯定会调用gorm把数据存储起来的数据,查询出来
  • 把查询出来的数据,和你定义好的模版开始用对应的模版语法进行渲染和碰撞
  • 最后就呈现出来一个完整的静态页面
  • 这是经典的:B/S架构。B代表的是Broswer浏览器,S代表的是:Server是服务端也就是我们学习的go

环境的准备和搭建

文档和go环境

image-20230118224400519

安装gin

go get -u github.com/gin-gonic/gin

01-demo.go

package main

import (
   "fmt"
   "github.com/gin-gonic/gin"
   "net/http"
)

func main() {
   fmt.Println("Gin Server start ...")

   //1.创建ginserver
   ginServer := gin.Default()
   //2。创建服务
   ginServer.GET("/hello", func(context *gin.Context) {
      context.JSON(http.StatusOK, gin.H{
         "message": "hello Gin",
      })
   })
   //3。启动服务
   ginServer.Run(":8080")
}

RESTFul API

RESTFul:用URL定位资源、用HTTP动词(GET、POST、PUT、DELETE)描述操作

RESTful API就是REST风格的API,即rest是一种架构风格,跟编程语言无关,跟平台无关,采用HTTP做传输协议

REST的含义就是客户端与Web服务器之间进行交互的时候,使用HTTP协议的4个请求方式代表不同的动作。

  • GET用来获取资源
  • POST用来新建资源
  • PUT用来更新资源
  • DELETE用来删除资源

只要API程序遵循了REST风格,那就可以称其为RESTful API。目前在前后端分离的架构中,前后端基本都是通过RESTful API来进行交互。

以前写法

get /user
post /create_user
post /update_user
post /delete_user

Restful的写法

get /user
post /user
put /user
delete /user

Gin框架支持开发RESTful API的开发

新建目录:

image-20230119022411935

IndexController.go

package controllers

import (
   "github.com/gin-gonic/gin"
   "net/http"
)

func Index(ginServer *gin.Engine) {
   ginServer.GET("/hello", func(context *gin.Context) {
      context.JSON(http.StatusOK, gin.H{
         "message": "hello Gin",
      })
   })
}

UserController.go

package controllers

import (
   "github.com/gin-gonic/gin"
)

func User(ginServer *gin.Engine) {

   //get -- 查询
   ginServer.GET("/user", func(context *gin.Context) {
      context.JSON(200, gin.H{
         "message": "Get",
      })
   })
   //post -- 添加
   ginServer.POST("/user", func(context *gin.Context) {
      context.JSON(200, gin.H{
         "message": "POST",
      })
   })
   //del -- 删除
   ginServer.DELETE("/user", func(context *gin.Context) {
      context.JSON(200, gin.H{
         "message": "DELETE",
      })
   })
   //put -- 修改
   ginServer.PUT("/user", func(context *gin.Context) {
      context.JSON(200, gin.H{
         "message": "PUT",
      })
   })
}

01-demo.go

package main

import (
   "fmt"
   "github.com/gin-gonic/gin"
   "go/src/src/main/17_Gin/controllers"
)

func main() {
   fmt.Println("Gin Server start ...")

   //1.创建ginserver
   ginServer := gin.Default()
   //2。创建服务
   controllers.Index(ginServer)
   controllers.User(ginServer)
   //3。启动服务
   ginServer.Run(":8080")
}

响应页面

创建以下文件目录

image-20230119135257386

在templates文件夹中添加一个静态网页,然后在01-demo.go中使用ginServer.LoadHTMLGlob()进行加载该静态网页

package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"go/src/src/main/17_Gin/controllers"
)

func main() {
	fmt.Println("Gin Server start ...")

	//1.创建ginserver
	ginServer := gin.Default()
	//2。创建服务
	controllers.Index(ginServer)
	controllers.User(ginServer)

	//3.加载静态网页模版的位置
	ginServer.LoadHTMLGlob("src/main/17_Gin/templates/*")
	//4。启动服务
	ginServer.Run(":8080")
}

然后在IndexCOntroller.go中进行配置一个首页的地址

IndexController.go

//配置一个首页地址
ginServer.GET("/index", func(context *gin.Context) {
   context.HTML(http.StatusOK, "index.html", gin.H{
      "message": "Gin web comming"})
})

响应JSON

  1. 返回普通数据类型
ginServer.POST("/json1", func(context *gin.Context) {
   context.JSON(http.StatusOK, "success")
})
  1. 返回map
ginServer.POST("/json2", func(context *gin.Context) {
   //1.使用gin自身对gin.H
   //context.JSON(http.StatusOK, gin.H{"code": 200, "message": "success"})
   //2.自己定义map
   m := map[string]any{}
   m["code"] = 200
   m["message"] = "success!"
   
  1. 结构体
ginServer.POST("/json3", func(context *gin.Context) {
   //用户结构体
   user := models.User{"yama", "123456"}
   context.JSON(http.StatusOK, user)
})
  1. 切片结构体
ginServer.POST("/json4", func(context *gin.Context) {
   //用户结构体
   user1 := models.User{"yama", "123456"}
   user2 := models.User{"yama", "123456"}
   //切片结构体
   users := make([]models.User, 2)
   users[0] = user1
   users[1] = user2
   context.JSON(http.StatusOK, users)
})

获取请求参数

querystring获取url中?携带的k=v参数与值

//127.0.0.1:8080/params/info?userid=yama&username=1234
ginServer.GET("/params/info", func(context *gin.Context) {
   //1.获取传统的基于key-value query方法可以获取
   userid := context.Query("userid")
   username := context.Query("username")
   //2.返回
   context.JSON(http.StatusOK, gin.H{"userid": userid, "username": username})
})

接收restful风格的参数

ginServer.GET("/params/info/:userid/:username", func(context *gin.Context) {

   userid := context.Param("userid")
   username := context.Param("username")
   //2.返回
   context.JSON(http.StatusOK, gin.H{"userid": userid, "username": username})
})

接收表单提交的数据

ginServer.POST("/params/save", func(context *gin.Context) {
   //form表单的方式 使用PostForm
   username := context.PostForm("username")
   password := context.PostForm("password")
   context.JSON(http.StatusOK, gin.H{"username": username, "password": password})
})

接收json参数的方式

ginServer.POST("/params/json", func(context *gin.Context) {
   //GetRawData() 远离:就是从context.request.Body中获取数据,返回是一个[]byte
   data, _ := context.GetRawData()
   //开始定义map或者结构体
   var m map[string]any
   //把前台传递过来的json字符串转换map
   _ = json.Unmarshal(data, &m)
   //返回
   context.JSON(http.StatusOK, m)
})

路由

重定向

站外重定向

//站外重定向
ginServer.GET("/redirect", func(context *gin.Context) {
   context.Redirect(http.StatusMovedPermanently, "https://www.pomfret.cn")
})

站内重定向

//站内重定向
ginServer.GET("/login", func(context *gin.Context) {
   context.Redirect(http.StatusMovedPermanently, "/index")
})

路由重定向

ginServer.GET("/test", func(context *gin.Context) {
   context.Request.URL.Path = "/test2"
  ginServer.HandleContext(context)
})
ginServer.GET("/test2", func(context *gin.Context) {
  context.JSON(http.StatusOK,gin.H{"msg":"test2的msg"})
})

404页面

func Error(ginServer *gin.Engine) {
   ginServer.NoRoute(func(context *gin.Context) {
      //html
      context.HTML(http.StatusOK, "404.html", gin.H{"message": "页面找不到了!"})
      //json
      //context.JSON(http.StatusOK, gin.H{"message": "页面找不到了!"})
   })
}

路由组

可以将拥有共同URL前缀的路由划分为一个路由组,习惯性一对{}包裹同组的路由,这只是为了看着清晰,用不用{}包裹功能上没什么区别,也可以多重嵌套,用Group方法

func Course(ginServer *gin.Engine) {
   courseGroup := ginServer.Group("/course")
   {
      courseGroup.GET("/list", func(context *gin.Context) {
         context.JSON(http.StatusOK, gin.H{"page": "user-list"})
      })
      courseGroup.GET("/save", func(context *gin.Context) {
         context.JSON(http.StatusOK, gin.H{"page": "user-save"})
      })
      courseGroup.GET("/update", func(context *gin.Context) {
         context.JSON(http.StatusOK, gin.H{"page": "user-update"})
      })
      courseGroup.GET("/del", func(context *gin.Context) {
         context.JSON(http.StatusOK, gin.H{"page": "user-del"})
      })
   }
}

中间件

Gin框架允许开发者在处理请求的过程中,加入用户自己的HandlerFunc函数

适合处理一些公共的业务逻辑,比如登陆认证、权限校验、数据分页、记录日志、耗时统计等。

中间件必须是一个gin.HanderFunc类型

  1. 定一个自己的HandlerFunc
func myhandler() gin.HandlerFunc {
   return func(context *gin.Context) {
      //可以通过context.Set在请求上下文中设置值,后续的处理函数能够取到该值
     context.Set("userSession","userid-1")
     //context.Next()放行,默认就会放行
     context.Abort() //拦截,到这里就不会往下执行请求了
     fmt.Println("HandlerFunc-info")
      }
   }
}
  1. 注册全局路由,所有的请求都会经过这里来处理
func myhandler() gin.HandlerFunc {
   return func(context *gin.Context) {
      //可以通过两个方法来决定你的请求是继续Next还是终止Abort
      log.Println("middle start")
      context.Next()
      log.Println("start end")
      username := "yama"
      if username != "yama" {
         context.Next()
         log.Println("欢迎进入")
      } else {
         context.Abort()
         context.JSON(http.StatusOK, gin.H{"message": "你没有权限哦"})
         fmt.Println("无权限")
      }
   }
}
func RegFilter(ginServer *gin.Engine) {
	//注册中间件 --- 全局注册
	ginServer.Use(myhandler())
}
  1. 局部路由
ginServer.GET("/hello", filters.Myhandler(), func(context *gin.Context) {
   context.JSON(http.StatusOK, gin.H{
      "message": "hello Gin",
   })
})

在对应的地方添加filters.Myhandler()即可

gin默认中间件

gin.Default()默认使用了LoggerRecovery中间件,其中:

  • Logger中间件将日志写入gin.Defaultwriter,即使配置了GIN_MODE=release
  • Recovery中间件会recover任何panic。如果有panic的话,会写入500响应码

如果不想使用上面两个默认的中间件,可以使用gin.New()新建一个没有任何中间件的路由

session

session的基本原理

http协议是无状态的,就是说你在请求服务器同时,别人也在请求服务器,这时候服务器是不知道哪个请求是你,哪个请求是别人(除非携带了一些识别字段,不过这属于业务层内容,不再讨论范围,我们单说http协议)。

  • 为了让服务器知道哪个请求是你的,哪个是别人的,这时候就有了session
  • session和cookie是不分家的,我们每次说到session,其实默认就是要使用cookie

image-20230127195838638

  1. 我们的请求在默认情况下是无状态的,所谓的无状态就是指,gin定义一个路由地址,在浏览器访问以后,也就是发起一个request,到response的过程,整个过程结束后,并不会在服务器端存储数据。这样就会造成一个问题,无法解决各路由请求之间数据的共享问题。
  2. 如何解决这个问题呢?session
  3. session是一种服务器端端存储技术,其实在底层就是一个全局的mapstring对象。它可以把一些需要各个路由间共享的数据进行存储在内存中,直到服务器关闭或者超时才会清除
  4. 有了session,为啥还要sessionid呢,因为要区分是哪个业务的 数据,因为底层是map,所以大部分情况下都会用session作为key
  5. 有了session,为什么还要cookie技术呢?cookie是一种客户端的存储技术,在创建session的时候,每次都会把这个sessionid写入到客户端浏览器的cookie中,后续给未来的每个路由请求都携带这个sessionid,到服务端端map中去匹配对应自己的数据信息
  6. 从而达到数据的共享

场景

第一次登录,服务器给客户端颁发一个唯一的sessionId,并通过http的响应头返回。客户端(浏览器)发现返回的数据中有cookie数据就把这个cookie数据存放到内存。下次发送http请求时,把内存中的cookie数据再塞到http请求头中,一并发给服务器,服务器在解析请求时,发现请求头中有cookie,就开始识别cookie中的sessionId,拿到sessionId,我们就知道这个请求你时由哪个客户端发送来的了。

gin配置session

gin框架在处理session时有专门的中间件,我们可以直接使用

go get github.com/gin-contrib/sessions

使用session中间件注意要点:

  1. session仓库其实就是一个map[interface]interface对象,所有session可以存储任意数据
  2. session使用的编解码器时自带的 gob,所有存储类型:struct、map这些对象时需要先注册对象,不然会报错gob:type not registered for ...
  3. session存储引擎支持:cookie、内存、mongodb、redis、postgres、memstore、memcached以及gorm支持的各类数据库(mysql、sqlite)
  4. session在创建时有一个配置项,可以配置session过期时间、cookie、domain、secure、path等参数
  5. 调用session方法:Set()、Delete()、Clear()、方法后,必须调用一个Save()方法。否则session数据不会更新
func Lgoin(ginServer *gin.Engine) {
   ginServer.POST("/toLogin", func(context *gin.Context) {
      //1.获取登录用户信息
      username := context.PostForm("username")
      password := context.PostForm("password")
      //2。创建一个用户结构体
      user := models.User{"100", username, password}
      //3.把结构体放入到session
      session := sessions.Default(context)
      session.Set("user", user)
      session.Save() //在go服务端生产user--user{}
      //4。返回
      context.JSON(http.StatusOK, gin.H{"user": user, "status": 200})
   })
}
//创建cookie存储
store := cookie.NewStore([]byte("secret"))
//路由上加入session中间件
ginServer.Use(sessions.Sessions("mysession", store))
//把数据类型注册进来
gob.Register(models.User{})

跨域

什么是跨域

image-20230129173613194

同源:指协议、域名、端口都相同

跨域是浏览器加载了当前域名、协议、端口不同另一站点下的资源。这与各大支持JS的浏览器的同源策略是违背的。所谓同源策略,它是由Netscape提出的一个著名的安全策略。现在所有支持JS的浏览器都会使用这个策略。

例如如下几个域名是同源的:

https://www.pomfret.cn/

https://www.pomfret.cn:80/

https://www.pomfret.cn:path/file

它们都是具有相同的协议、相同的域名、相同的端口(不指定端口默认为80)

func CORS() gin.HandlerFunc {
   return func(context *gin.Context) {
      // 允许 Origin 字段中的域发送请求
      //context.Header("Access-Control-Allow-Origin", "http://localhost:8088")
      context.Header("Access-Control-Allow-Origin", "*")
      // 响应头表示是否可以将对请求对响应暴露给页面,返回true则可以,其他值均不可以
      context.Header("Access-Control-Allow-Credentials", "true")
      // 设置允许请求的方法
      context.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE, PATCH")
      // 设置允许请求的 Header
      context.Header("Access-Control-Allow-Headers", "Content-Type, Content-Length,Apitoken")
      // 设置拿到除基本字段外的其他字段,如上面的Apitoken, 这里通过引用Access-Control-Expose-Headers,进行配置,效果是一样的。
      context.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Headers")
      context.Next()
   }
}
func RegFilter(ginServer *gin.Engine) {
   //注册中间件-- 全局注册 -- cors跨域问题
   //ginServer.Use(CORS())
}
//局部注册的话,在对应的请求方式后面加甜即可,ginServer.GET("/index",filter.CORS(), func(context *gin.Context) {}
License:  CC BY 4.0 test