参考
官方中文文档:https://gin-gonic.com/zh-cn/docs/introduction/ 但是示例截图少
https://www.kancloud.cn/shuangdeyu/gin_book/949411
https://www.topgoer.com/gin%E6%A1%86%E6%9E%B6/ 这个网站不光有gin框架 适合阅读
吉米老师的 :https://www.liwenzhou.com/posts/Go/Gin_framework/
他的其他链接:https://www.liwenzhou.com/posts/Go/golang-menu/
docker部署go项目:https://www.liwenzhou.com/posts/Go/how_to_deploy_go_app_using_docker/
官方网站:https://gin-gonic.github.io/gin/
Github地址:https://github.com/gin-gonic/gin
什么是框架
框架是指半成品的应用,一般需要填充少许代码或者无需填充代码就可以运行,只是这样的应用缺少业务逻辑。
我们使用框架开发,主要工作就是在框架上补充业务逻辑代码。所以,借助框架进行开发,不仅可以减少开发时间、提高效率,也有助于团队统一编码风格,形成 编程规范。
gin 框架是一个 Web 框架,它封装了 路由、Cookie、Session、参数处理、数据编解码以及中间件等功能,简单高效,降低了开发 Web 应用的难度。
gin 是一个使用 Go 语言编写的 Web 后端框架,具有简洁、轻量、支持高并发、封装优雅、API 友好、快速灵活、容错方便等特点。
gin 和 beego 是 Go 语言编写 Web 应用最常用的后端框架。
使用 Go 语言开发 Web 应用,对于框架的依赖要比其它编程语言要小。Go 语言内置的 net/http 包简单轻便,性能优良。而且,大多数的 Go 语言 Web 框架都是在其之上进行的封装。
安装与导入
$ go get -u github.com/gin-gonic/gin
import (
"net/http" //通常还需要导入 net/http 包,因为代码中一般需要一些常量,比如返回码 http.StatusOK。
"github.com/gin-gonic/gin"
)
学学这个排版。
Hello World
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
// 1.创建路由
engine := gin.Default()
// 2.绑定路由规则,执行的函数,这里写成了匿名函数的形式 也可单独写一个函数
// gin.Context,封装了request和response
//指定对什么网址进行相应,响应什么内容
engine.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "Hello World!")
})
// 3.监听端口,默认在8080
engine.Run()//相当于 engine.Run(":8080")
}
上面GET
方法可拆为:
helloHandler := func(context *gin.Context) {
context.String(http.StatusOK, "Hello World!")
}
func main() {
//···省略
engine.GET("/",helloHandler)
}
或
//GET is a shortcut for router.Handle("GET", path, handle)
helloHandler := func(context *gin.Context) {
fmt.Fprint(context.Writer, "Hello World!")
}
r.Handle("GET", "/hello", helloHandler)
打开浏览器,输入 http://localhost:8080,就可以看到浏览器输出:
Hello World!
如果我们不使用gin框架,原生的go也可以实现:
路由处理
什么是路由
在web开发中,“route”是指根据url分配到对应的处理程序。
路由(route)就是根据 HTTP 请求的 URL,设置由哪个函数来处理请求。路由是 Web 框架的核心功能。
路由通常这样实现:根据路由里的字符 “/”,把路由切分成多个字符串数组,然后构造成树状结构;寻址的时候,先把请求的 URL 按照 “/” 进行切分,然后遍历树进行寻址。
比如:定义了两个路由 /user/get,/user/delete,则会构造出拥有三个节点的路由树,根节点是 user,两个子节点分别是 get 和 delete。
gin 框架路由库
gin 框架中采用的路由库是基于 httprouter 开发的。
httprouter 项目地址:https://github.com/julienschmidt/httprouter。
gin 框架路由的范例
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
engine := gin.Default()
// 下面是两条路由
engine.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "编程教程")
})
engine.GET("/hello", func(c *gin.Context) {
c.String(http.StatusOK, "Hello World")
})
// 监听端口默认为8080
engine.Run(":8080")
}
运行程序,然后打开浏览器,输入 http://localhost:8080/
,可以看到浏览器输出:编程教程。然后输入 http://localhost:8080/hello
,可以看到浏览器输出:Hello World。
r.GET("/a",func(c *gin.Context) {})
r.POST("/b",func(c *gin.Context) {})
//此外,还有一个可以匹配所有请求方法的Any方法如下
r.Any("/c",func(c *gin.Context) {})
RESTAPI
web 应用程序,一般分为前端和后端两个部分。前后端通信通常需要一种统一机制,以方便不同的前端设备与后端进行通信。这种需求导致了 API 构架的流行,甚至出现 “API First” 的设计思想。
RESTful API 是目前比较成熟的一套 web 应用程序的 API 设计理论,用于 web 前后端的数据交互。
RESTful API 路径
RESTful API 的路径又称 “终点”(endpoint),表示 API 的具体网址。
在 RESTful 架构中,每个网址代表一种资源(resource),比如:有一个 API 提供动物园(zoo)的信息,还包括各种动物和雇员的信息,则它的路径应该设计成下面这样:
https://api.example.com/v1/zoos
https://api.example.com/v1/animals
https://api.example.com/v1/employees
RESTful API HTTP 动词
在 RESTful API 设计理论中,对于资源的操作,由 HTTP 动词表示。
常用的 HTTP 动词有下面五个(括号里是对应的数据库 SQL 命令)。
- GET(SELECT):从服务器取出资源(一项或多项)。
- POST(CREATE):在服务器新建一个资源。
- PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
- PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
- DELETE(DELETE):从服务器删除资源。
还有两个不常用的HTTP动词。
- HEAD:获取资源的元数据。
- OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。
比如
- GET /zoos:列出所有动物园
- POST /zoos:新建一个动物园
- GET /zoos/ID:获取某个指定动物园的信息
- PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)
- PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)
- DELETE /zoos/ID:删除某个动物园
- GET /zoos/ID/animals:列出某个指定动物园的所有动物
- DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物
下面是一些例子
gin 框架支持 RESTful 风格的 API。
RESTful 即 Representational State Transfer 的缩写。直接翻译的意思是 “表现层状态转化”,是一种互联网应用程序的API设计理念:URL 定位资源,用 HTTP 方法描述操作,例如:
- 获取文章 /blog/getXxx Get blog/Xxx
- 添加文章 /blog/addXxx POST blog/Xxx
- 修改文章 /blog/updateXxx PUT blog/Xxx
- 删除文章 /blog/delXxxx DELETE blog/Xxx
gin 框架路由的基本语法
Gin 的路由支持 HTTP 的 GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS 方法的请求,同时还有一个 Any 函数,可以同时支持以上的所有请求。
Gin 的路由通常的使用方法如下:
// 获取默认的 gin Engine,Engine 中包含了所有路由处理的接口
engine := gin.Default()
// Get 请求路由
engine.GET("/", func(context *gin.Context) {
context.String(http.StatusOK, "hello gin get method")
})
// Post 请求路由
engine.POST("/", func(context *gin.Context) {
context.String(http.StatusOK, "hello gin post method")
})
// Put 请求路由
engine.PUT("/", func(context *gin.Context) {
context.String(http.StatusOK, "hello gin put method")
})
// Delete 请求路由
engine.DELETE("/", func(context *gin.Context) {
context.String(http.StatusOK, "hello gin delete method")
})
// Patch 请求路由
engine.PATCH("/", func(context *gin.Context) {
context.String(http.StatusOK, "hello gin patch method")
})
// Head 请求路由
engine.HEAD("/", func(context *gin.Context) {
context.String(http.StatusOK, "hello gin head method")
})
// Options 请求路由
engine.OPTIONS("/", func(context *gin.Context) {
context.String(http.StatusOK, "hello gin options method")
})
gin路由分组
我们在使用 web 框架开发时,经常会根据业务逻辑给一个模块划分一组路由。
把一个模块相关的方法都写在一个路由下,主要好处是业务逻辑清晰,便于管理和查找相关的代码。
例如:goods 为商品模块,我们规划它的操作路由。
/goods/addGoods
添加商品
/goods/delGoods
删除商品
gin 框架支持路由分组(routes group),路由分组的关键词为 group。
engine.Group("/groupname")
写法1
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
func loginEndpoint(c *gin.Context){
fmt.Println("这是login方法")
}
func submitEndpoint(c *gin.Context){
fmt.Println("这是submit方法")
}
func readEndpoint(c *gin.Context){
fmt.Println("这是read方法")
}
func main() {
engine := gin.Default()
//v1组路由
// {} 是书写规范 这个语法是咋实现的? 是函数先返回一个结构体 再实例化的意思么?
v1 := engine.Group("/v1")
{
v1.GET("/login", loginEndpoint)
v1.GET("/submit", submitEndpoint)
v1.GET("/read", readEndpoint)
}
//v2组路由
v2: = engine.Group("/v2")
{
v2.GET("/login", loginEndpoint)
v2.GET("/submit", submitEndpoint)
v2.GET("/read", readEndpoint)
}
engine.Run()
}
打开浏览器,输入 http://localhost:8080,分别访问:
http://localhost:8080/v1/login
http://localhost:8080/v1/submit
http://localhost:8080/v1/read
http://localhost:8080/v2/login
http://localhost:8080/v2/submit
http://localhost:8080/v2/read
浏览器会输出对应的 API 内容。
写法2
也可以这样:
func main() {
r := gin.Default()
user := r.Group("/user")
user.GET("/index", func(c *gin.Context) {})
user.POST("/login", func(c *gin.Context) {})
r.Run()
}
区别,就在于不用再单独写路由了
参数处理
web 程序中经常需要处理各种形式的参数,参数是处理 HTTP 请求中很重要的工作,它是前端向后端提交数据的基本形式。
gin 框架内置了处理 HTTP 各种参数的方法,包括 API 参数,URL 参数 以及 表单参数的处理。
query参数处理(URL 参数处理)(get)
例如/name=admin&pwd=123456,我们想得到name和pwd的值
URL 参数可以通过 DefaultQuery() 或 Query() 方法获取。
DefaultQuery() 若参数不存在,则返回默认值,Query()若不存在,返回空串。
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/", func(c *gin.Context) {
name := c.DefaultQuery("name", "admin")
pwd := c.Query("pwd")
// fmt.Printf("name:%s ; pwd:%s",name,pwd)
c.JSON(http.StatusOK, gin.H{
"name": name,
"pwd": pwd,
})
})
r.Run()
}
表单Form参数处理 (post)
表单传输为post请求,http常见的传输格式为四种:
application/json
application/x-www-form-urlencoded
application/xml
multipart/form-data
表单参数可以通过 PostForm() 方法获取,该方法默认解析的是 x-www-form-urlencoded 或 from-data 格式的参数。
package main
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
engine := gin.Default()
engine.POST("/form", func(c *gin.Context) {
types := c.DefaultPostForm("type", "post")
username := c.PostForm("username")
password := c.PostForm("userpassword")
c.String(http.StatusOK, fmt.Sprintf("username:%s,password:%s,type:%s", username, password, types))
})
engine.Run()
}
从表单中获取了 types、username、password 三个参数。
API 参数处理
gin 框架中,可以通过 Context 的 Param 方法来获取 API 参数。
比如:提取 http://localhost:8080/user/zhangsan 的参数 zhangsan。
package main
import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
func main() {
engine := gin.Default()
engine.GET("/user/:name", func(c *gin.Context) {
name := c.Param("name")
c.String(http.StatusOK, "name=" + name)
})
// 监听8080端口
engine.Run(":8080")
}
运行程序,浏览器中输入:http://localhost:8080/user/zhangsan,浏览器会输出:name=zhangsan。
/user/:name/*action 这种也是api参数 后面会再讲
上传文件
简单来说 就是
FormFile("文件名")
<input type="file" name="file" >
上传单个文件
gin 框架中,multipart/form-data
格式用于文件上。
文件上传与原生的 net/http 方法类似,不同在于 gin 把原生的 request 封装到 c.Request 中。
前端 html 文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<form action="http://localhost:8080/upload" method="post" enctype="multipart/form-data">
上传文件:
<input type="file" name="file" >
<input type="submit" value="提交">
</form>
</body>
</html>
文件可以保存为 test.html。
后端 go 文件:
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
engine := gin.Default()
//限制上传最大尺寸
engine.MaxMultipartMemory = 8 << 20
engine.POST("/upload", func(c *gin.Context) {
file, err := c.FormFile("file")
if err != nil {
c.String(500, "上传图片出错")
}
// c.JSON(200, gin.H{"message": file.Header.Context})
c.SaveUploadedFile(file, file.Filename)
c.String(http.StatusOK, file.Filename)
})
engine.Run()
}
运行程序后,浏览器访问 test.html文件,就可以在浏览器中上传文件。
OR
前端页面代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>上传文件示例</title>
</head>
<body>
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="f1">
<input type="submit" value="上传">
</form>
</body>
</html>
后端Gin框架部分代码
package main
import (
"fmt"
"log"
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
// 处理multipart forms提交文件时默认的内存限制是32 MiB
// 可以通过下面的方式修改
// r.MaxMultipartMemory = 8 << 20 // 8 MiB
r.POST("/upload", func(c *gin.Context) {
// 单个文件
file, err := c.FormFile("f1")
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"message": err.Error(),
})
return
}
log.Println(file.Filename)
dst := fmt.Sprintf("C:/tmp/%s", file.Filename)
// 上传文件到指定的目录
c.SaveUploadedFile(file, dst)
c.JSON(http.StatusOK, gin.H{
"message": fmt.Sprintf("'%s' uploaded!", file.Filename),
})
})
r.Run()
}
上传特定文件
有的用户上传文件需要限制上传文件的类型以及上传文件的大小,但是 gin 框架暂时没有这些函数。因此,我们基于原生的函数写了一个可以限制大小以及文件类型的上传函数。
package main
import (
"fmt"
"log"
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
engine := gin.Default()
engine.POST("/upload", func(c *gin.Context) {
_, headers, err := c.Request.FormFile("file")
if err != nil {
log.Printf("Error when try to get file: %v", err)
}
//headers.Size 获取文件大小
if headers.Size > 1024*1024*2 {
fmt.Println("文件太大了")
return
}
//headers.Header.Get("Content-Type")获取上传文件的类型
if headers.Header.Get("Content-Type") != "image/png" {
fmt.Println("只允许上传png图片")
return
}
c.SaveUploadedFile(headers, "./video/"+headers.Filename)
c.String(http.StatusOK, headers.Filename)
})
engine.Run()
}
上传多个文件
前端 html 文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<form action="http://localhost:8080/upload" method="post" enctype="multipart/form-data">
上传文件:
<input type="file" name="files" multiple>
<input type="submit" value="提交">
</form>
</body>
</html>
文件可以保存为 test.html。
package main
import (
"github.com/gin-gonic/gin"
"net/http"
"fmt"
)
func main() {
engine := gin.Default()
// 限制表单上传大小 8MB,默认为32MB
engine.MaxMultipartMemory = 8 << 20
engine.POST("/upload", func(c *gin.Context) {
form, err := c.MultipartForm()
if err != nil {
c.String(http.StatusBadRequest, fmt.Sprintf("get err %s", err.Error()))
}
// 获取所有图片
files := form.File["files"]
// 遍历所有图片
for _, file := range files {
// 逐个保存
if err := c.SaveUploadedFile(file, file.Filename); err != nil {
c.String(http.StatusBadRequest, fmt.Sprintf("upload err %s", err.Error()))
return
}
}
c.String(200, fmt.Sprintf("upload ok %d files", len(files)))
})
engine.Run()
}
运行程序后,浏览器访问 test.html文件,就可以在浏览器中上传文件。
OR
package main
import (
"fmt"
"log"
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
// 处理multipart forms提交文件时默认的内存限制是32 MiB
// 可以通过下面的方式修改
// r.MaxMultipartMemory = 8 << 20 // 8 MiB
r.POST("/upload", func(c *gin.Context) {
// Multipart form
form, _ := c.MultipartForm()
files := form.File["file"]
for index, file := range files {
log.Println(file.Filename)
dst := fmt.Sprintf("C:/tmp/%s_%d", file.Filename, index)
// 上传文件到指定的目录
c.SaveUploadedFile(file, dst)
}
c.JSON(http.StatusOK, gin.H{
"message": fmt.Sprintf("%d files uploaded!", len(files)),
})
})
r.Run()
}
中间件
Gin框架允许开发者在处理请求的过程中,加入钩子函数,这个钩子函数就叫中间件。中间件适合处理一些公共的业务逻辑,比如登陆认证,权限校验,记录日志等。具体使用方法如下
package main
import (
"fmt"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
//定义一个中间键m1统计请求处理函数耗时
func m1(c *gin.Context) {
fmt.Println("m1 in...")
start := time.Now()
// c.Next() //调用后续的处理函数
c.Abort()//阻止调用后续的处理函数
cost := time.Since(start)
fmt.Printf("cost:%v\n", cost)
}
func index(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"msg": "ok",
})
}
func main() {
r := gin.Default()
r.GET("/", m1, index) //之前没有中间件的写法r.GET("/", index)
r.Run()
}
还没找到分类
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")
}
其中 方法一 等价
注意,结构体方法,变量名要大写。所以用tag
template
在一些前后端不分离的Web架构中,我们通常需要在后端将一些数据渲染到HTML文档中,从而实现动态的网页(网页的布局和样式大致一样,但展示的内容并不一样)效果。
我们这里说的模板可以理解为事先定义好的HTML文档文件,模板渲染的作用机制可以简单理解为文本替换操作–使用相应的数据去替换HTML文档中事先准备好的标记。
可以看这个:https://www.liwenzhou.com/posts/Go/go_template/