Gin
Gin
认识Gin
Gin是什么?
- Gin:Go语言编写的Web框架,以更好的性能实现类似Martini框架的API
- Gin事一个golang的net/http库封装的web微框架,封装比较优雅,API友好,源码注释比较明确。具有快速灵活,容错方便等特点。
学习Web框架
- 如果假设我们这里选择的是:gin和gorm
- 用户在浏览器访问地址,这个地址其实是服务器提供。用于寻址一样,先找到服务器在哪里,我要访问服务器的什么
- 如果根据ip找到地址以后,通过port找到服务器的服务,就开始进入到程序中把对应的逻辑去执行(这个部分未来是使用gin定义路由的对应的要执行的事情)
- 在执行的逻辑中,我们肯定会调用gorm把数据存储起来的数据,查询出来
- 把查询出来的数据,和你定义好的模版开始用对应的模版语法进行渲染和碰撞
- 最后就呈现出来一个完整的静态页面
- 这是经典的:B/S架构。B代表的是Broswer浏览器,S代表的是:Server是服务端也就是我们学习的go
环境的准备和搭建
文档和go环境
-
Gin官方文档地址:https://gin-gonic.com/zh-cn/docs/
-
版本的要求:最低版本要求:1.18x,强烈推荐:1.19x
安装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的开发
新建目录:
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")
}
响应页面
创建以下文件目录
在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
- 返回普通数据类型
ginServer.POST("/json1", func(context *gin.Context) {
context.JSON(http.StatusOK, "success")
})
- 返回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!"
- 结构体
ginServer.POST("/json3", func(context *gin.Context) {
//用户结构体
user := models.User{"yama", "123456"}
context.JSON(http.StatusOK, user)
})
- 切片结构体
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类型
- 定一个自己的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")
}
}
}
- 注册全局路由,所有的请求都会经过这里来处理
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())
}
- 局部路由
ginServer.GET("/hello", filters.Myhandler(), func(context *gin.Context) {
context.JSON(http.StatusOK, gin.H{
"message": "hello Gin",
})
})
在对应的地方添加filters.Myhandler()
即可
gin默认中间件
gin.Default()
默认使用了Logger
和Recovery
中间件,其中:
Logger
中间件将日志写入gin.Defaultwriter
,即使配置了GIN_MODE=release
。Recovery
中间件会recover
任何panic
。如果有panic的话,会写入500响应码
如果不想使用上面两个默认的中间件,可以使用gin.New()
新建一个没有任何中间件的路由
session
session的基本原理
http协议是无状态的,就是说你在请求服务器同时,别人也在请求服务器,这时候服务器是不知道哪个请求是你,哪个请求是别人(除非携带了一些识别字段,不过这属于业务层内容,不再讨论范围,我们单说http协议)。
- 为了让服务器知道哪个请求是你的,哪个是别人的,这时候就有了session
- session和cookie是不分家的,我们每次说到session,其实默认就是要使用cookie
- 我们的请求在默认情况下是无状态的,所谓的无状态就是指,gin定义一个路由地址,在浏览器访问以后,也就是发起一个request,到response的过程,整个过程结束后,并不会在服务器端存储数据。这样就会造成一个问题,无法解决各路由请求之间数据的共享问题。
- 如何解决这个问题呢?session
- session是一种服务器端端存储技术,其实在底层就是一个全局的mapstring对象。它可以把一些需要各个路由间共享的数据进行存储在内存中,直到服务器关闭或者超时才会清除
- 有了session,为啥还要sessionid呢,因为要区分是哪个业务的 数据,因为底层是map,所以大部分情况下都会用session作为key
- 有了session,为什么还要cookie技术呢?cookie是一种客户端的存储技术,在创建session的时候,每次都会把这个sessionid写入到客户端浏览器的cookie中,后续给未来的每个路由请求都携带这个sessionid,到服务端端map中去匹配对应自己的数据信息
- 从而达到数据的共享
场景
第一次登录,服务器给客户端颁发一个唯一的sessionId,并通过http的响应头返回。客户端(浏览器)发现返回的数据中有cookie数据就把这个cookie数据存放到内存。下次发送http请求时,把内存中的cookie数据再塞到http请求头中,一并发给服务器,服务器在解析请求时,发现请求头中有cookie,就开始识别cookie中的sessionId,拿到sessionId,我们就知道这个请求你时由哪个客户端发送来的了。
gin配置session
gin框架在处理session时有专门的中间件,我们可以直接使用
go get github.com/gin-contrib/sessions
使用session中间件注意要点:
- session仓库其实就是一个map[interface]interface对象,所有session可以存储任意数据
- session使用的编解码器时自带的 gob,所有存储类型:struct、map这些对象时需要先注册对象,不然会报错
gob:type not registered for ...
- session存储引擎支持:cookie、内存、mongodb、redis、postgres、memstore、memcached以及gorm支持的各类数据库(mysql、sqlite)
- session在创建时有一个配置项,可以配置session过期时间、cookie、domain、secure、path等参数
- 调用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{})
跨域
什么是跨域
同源:指协议、域名、端口都相同
跨域是浏览器加载了当前域名、协议、端口不同另一站点下的资源。这与各大支持JS的浏览器的同源策略是违背的。所谓同源策略,它是由Netscape提出的一个著名的安全策略。现在所有支持JS的浏览器都会使用这个策略。
例如如下几个域名是同源的:
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) {}