Gin使用
Gin框架安装与使用
安装
下载并安装Gin:
go get -u "github.com/gin-gonic/gin"
-u 选项的作用是更新指定包及其依赖项到最新版本
第一个Gin示例:
package main
import (
"github.com/gin-gonic/gin"
)
func Test(c *gin.Context) {
c.JSON(200, gin.H{
"msg": "test",
})
}
func main() {
// 创建一个默认的路由引擎
r := gin.Default()
// GET:请求方式;/ping:请求的路径
// 当客户端以GET方法请求/ping路径时,会执行后面的匿名函数
// r.GET("/ping", func(c *gin.Context) {
// // c.JSON:返回JSON格式的数据
// c.JSON(200, gin.H{
// "msg": "test",
// })
// })
// 也可以在main函数外创建一个函数,直接在GET方法里调用这个函数
r.GET("/test", Test)
// 启动HTTP服务,默认在0.0.0.0:8080启动服务
r.Run("xx.xx.xx.xx:10005")
}
"xx.xx.xx.xx:10005"中,xx.xx.xx.xx是你自己运行的地址,可以是localhost也可以是服务器地址,在浏览器中运行时使用xx.xx.xx.xx:10005
RESTful API
REST与技术无关,代表的是一种软件架构风格,REST是Representational State Transfer的简称,中文翻译为“表征状态转移”或“表现层状态转化”。
要理解RESTful架构,最好的方法就是去理解Representational State Transfer这个词组到底是什么意思,它的每一个词代表了什么涵义。
一、资源(Resources)
REST的名称"表现层状态转化"中,省略了主语。"表现层"其实指的是"资源"(Resources)的"表现层"。
所谓"资源",就是网络上的一个实体,或者说是网络上的一个具体信息。 它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的实在。你可以用一个URI(统一资源定位符)指向它,每种资源对应一个特定的URI。要获取这个资源,访问它的URI就可以,因此URI就成了每一个资源的地址或独一无二的识别符。
所谓"上网",就是与互联网上一系列的"资源"互动,调用它的URI。
二 、表现层(Representation)
"资源"是一种信息实体,它可以有多种外在表现形式。我们把"资源"具体呈现出来的形式,叫做它的"表现层"(Representation)。
比如,文本可以用txt格式表现,也可以用HTML格式、XML格式、JSON格式表现,甚至可以采用二进制格式;图片可以用JPG格式表现,也可以用PNG格式表现。
URI只代表资源的实体,不代表它的形式。严格地说,有些网址最后的".html"后缀名是不必要的,因为这个后缀名表示格式,属于"表现层"范畴,而URI应该只代表"资源"的位置。它的具体表现形式,应该在HTTP请求的头信息中用Accept和Content-Type字段指定,这两个字段才是对"表现层"的描述。
三、状态转化(State Transfer)
访问一个网站,就代表了客户端和服务器的一个互动过程。在这个过程中,势必涉及到数据和状态的变化。
互联网通信协议HTTP协议,是一个无状态协议。这意味着,所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生"状态转化"(State Transfer)。而这种转化是建立在表现层之上的,所以就是"表现层状态转化"。
客户端用到的手段,只能是HTTP协议。具体来说,就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源。
四、综述
综合上面的解释,总结一下什么是RESTful架构:
(1)每一个URI代表一种资源;
(2)客户端和服务器之间,传递这种资源的某种表现层;
(3)客户端通过四个HTTP动词,对服务器端资源进行操作,实现"表现层状态转化"。
最常见的一种设计错误,就是URI包含动词。 因为"资源"表示一种实体,所以应该是名词,URI不应该有动词,动词应该放在HTTP协议中。
举例来说,某个URI是/posts/show/1,其中show是动词,这个URI就设计错了,正确的写法应该是/posts/1,然后用GET方法表示show。
另一个设计误区,就是在URI中加入版本号:因为不同的版本,可以理解成同一种资源的不同表现形式,所以应该采用同一个URI。版本号可以在HTTP请求头信息的Accept字段中进行区分
只要API程序遵循了REST风格,那就可以称其为RESTful API。目前在前后端分离的架构中,前后端基本都是通过RESTful API来进行交互。
开发RESTful API的时候我们通常使用Postman来作为客户端的测试工具。
渲染
JSON渲染
func main() {
r := gin.Default()
// gin.H 是map[string]interface{}的缩写
r.GET("/someJSON", func(c *gin.Context) {
// 方式一:自己拼接JSON
c.JSON(http.StatusOK, gin.H{"message": "Hello world!"})
})
r.GET("/moreJSON", func(c *gin.Context) {
// 方法二:使用结构体
var msg struct {
Name string `json:"user"`
Message string
Age int
}
msg.Name = "小王子"
msg.Message = "Hello world!"
msg.Age = 18
c.JSON(http.StatusOK, msg)
})
r.Run(":8080")
}
XML渲染
注意需要使用具名的结构体类型。
func main() {
r := gin.Default()
// gin.H 是map[string]interface{}的缩写
r.GET("/someXML", func(c *gin.Context) {
// 方式一:自己拼接JSON
c.XML(http.StatusOK, gin.H{"message": "Hello world!"})
})
r.GET("/moreXML", func(c *gin.Context) {
// 方法二:使用结构体
type MessageRecord struct {
Name string
Message string
Age int
}
var msg MessageRecord
msg.Name = "小王子"
msg.Message = "Hello world!"
msg.Age = 18
c.XML(http.StatusOK, msg)
})
r.Run(":8080")
}
YMAL渲染
r.GET("/someYAML", func(c *gin.Context) {
c.YAML(http.StatusOK, gin.H{"message": "ok", "status": http.StatusOK})
})
获取参数
获取querystring参数
querystring指的是URL中?后面携带的参数,例如:/user/search?username=小王子&address=沙河。 获取请求的querystring参数的方法如下:
key=value格式,多个key-value用&连接
r.GET("/web", func(c *gin.Context) {
// 获取浏览器发请求携带的query string 参数
name := c.Query("name")
age := c.Query("age")
// name := c.DefaultQuery("name", "test") // 没传值就取默认值test
// name, ok := c.GetQuery("name") // 取到返回(值,true),取不到返回("",false)
// if !ok {
// c.JSON(http.StatusBadRequest, gin.H{
// "err": http.StatusBadRequest,
// "msg": "name is required",
// })
// return
// }
c.JSON(http.StatusOK, gin.H{
"name": name,
"age": age,
})
})
获取form参数
当前端请求的数据通过form表单提交时,例如向/login发送一个POST请求,获取请求数据的方式如下:
r.POST("/login", func(c *gin.Context) {
// 获取form表单提交的数据
username := c.PostForm("username")
password := c.PostForm("password")
// DefaultPostForm取不到值时会返回指定的默认值
//username := c.DefaultPostForm("username", "小王子") // 没传值就取默认值test
// username, ok := c.GetPostForm("name") // 取到返回(值,true),取不到返回("",false)
// if !ok {
// c.JSON(http.StatusBadRequest, gin.H{
// "err": http.StatusBadRequest,
// "msg": "username is required",
// })
// return
// }
c.JSON(http.StatusOK, gin.H{
"username": username,
"password": password,
"msg": "login success",
})
})
获取JSON参数
当前端请求的数据通过JSON提交时,例如向/json发送一个JSON格式的POST请求,则获取请求参数的方式如下:
r.POST("/json", func(c *gin.Context) {
b, _ := c.GetRawData() // 从c.Request.Body读取请求数据
// 定义map或结构体
var m map[string]interface{}
// 反序列化
_ = json.Unmarshal(b, &m)
c.JSON(http.StatusOK, m)
})
获取path参数
请求的参数通过URL路径传递,例如:/user/小王子/沙河。 获取请求URL路径中的参数的方式如下。
r.GET("/user/:username/:address", func(c *gin.Context) {
// 获取路径参数
username := c.Param("username")
address := c.Param("address")
c.JSON(http.StatusOK, gin.H{
"username": username,
"address": address,
"msg": "success",
})
})
参数绑定
为了能够更方便的获取请求相关参数,提高开发效率,我们可以基于请求的Content-Type识别请求数据类型并利用反射机制自动提取请求中QueryString、form表单、JSON、XML等参数到结构体中。 下面的示例代码演示了.ShouldBind()强大的功能,它能够基于请求自动提取JSON、form表单和QueryString类型的数据,并把值绑定到指定的结构体对象。
type UserInfo struct {
Username string `json:"username"`
Password string `json:"password"`
}
r.POST("/loginJSON", func(c *gin.Context) {
var u UserInfo
// err := c.ShouldBind(&u)
// if err != nil {
// c.JSON(http.StatusBadRequest, gin.H{
// "err": err.Error(),
// })
// } else {
// fmt.Printf("%#v\n", u)
// c.JSON(http.StatusOK, gin.H{
// "msg": "login success",
// })
// }
if err := c.ShouldBind(&u); err == nil {
fmt.Printf("login info:%#v\n", u)
c.JSON(http.StatusOK, gin.H{
"user": u.Username,
"password": u.Password,
})
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
})
ShouldBind会按照下面的顺序解析请求中的数据完成绑定:
- 如果是
GET请求,只使用Form绑定引擎(query)。 - 如果是
POST请求,首先检查content-type是否为JSON或XML,然后再使用Form(form-data)。
重定向
HTTP重定向
HTTP 重定向很容易。 内部、外部重定向均支持。
r.GET("/redirect", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "http://www.baidu.com") // 跳转到百度一下页面
})
路由重定向
路由重定向,使用HandleContext:
r.GET("/handlecontext", func(c *gin.Context) {
// 跳转到/handletest对应的路由处理函数
c.Request.URL.Path = "/handletest" // 把请求的URI修改
r.HandleContext(c) // 继续后续的处理
})
r.GET("/handletest", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "handle test",
})
})
Gin路由
普通路由
r.GET("/index", func(c *gin.Context) {...})
r.GET("/login", func(c *gin.Context) {...})
r.POST("/login", func(c *gin.Context) {...})
此外,还有一个可以匹配所有请求方法的Any方法如下:
r.Any("/test", func(c *gin.Context) {
switch c.Request.Method {
case "GET":
c.JSON(http.StatusOK, gin.H{"method":"GET"})
case http.MethodPost:
c.JSON(http.StatusOK, gin.H{"method":"POST"})
//...
}
})
为没有配置处理函数的路由添加处理程序,默认情况下它返回404代码,下面的代码为没有匹配到路由的请求都返回views/404.html页面。
r.NoRoute(func(c *gin.Context) {
c.JSON(http.StatusNotFound, gin.H{
"msg": "page not found",
})
})
路由组
我们可以将拥有共同URL前缀的路由划分为一个路由组。习惯性一对{}包裹同组的路由,这只是为了看着清晰,你用不用{}包裹功能上没什么区别。
// 路由组
// 把公用的前缀提取出来,创建一个路由组
userGroup := r.Group("/usergroup")
{
userGroup.GET("/index", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "user index",
})
})
userGroup.POST("/username", func(c *gin.Context) {
username := c.PostForm("username")
c.JSON(http.StatusOK, gin.H{
"msg": "user username",
"username": username,
})
})
}
路由组也支持嵌套
路由原理
Gin框架中的路由使用的是httprouter这个库。
其基本原理就是构造一个路由地址的前缀树。
MVC设计模式
模型
视图
控制器
项目目录
基于MVC设计模式,设计项目目录
目录 说明
conf 项目配置文件及读取配置文件的相关功能
controllers 控制器目录,包括项目各个模块的控制器及业务处理
view 视图目录,主要包含首页前端文件
model 数据实体目录,主要定义了项目中各业务模块的实体对象
service 服务层目录,用于各个模块基础功能接口的定义和实现,是各个模块的数据层
static 配置项目的静态资源目录
util 提供通用的方法封装
main.go 项目程序主入口
学习总结
在学习Gin框架使用过程中,我觉得最重要的是动手实操,只有动手做,才会发现什么情况需要用什么样的方法。同时在实践的过程中可以熟悉各种方法的使用,也可以知道该在什么地方使用。可以从一个简单的后端项目出发,学习如何使用Gin框架完成一个项目,在这个项目中各种目录有什么作用,如何正确的组织一个项目的流程。