简介
Gin是一个golang的微框架,封装比较优雅,,AP友好,源码注释比较明确。具有快速灵活,容错方便等特点。其实对于golang而言,web框架的依赖要远比Python,Java之类的要小。自身的net/http足够简单,性能也非常不错。框架更像是一些常用函数或者工具的集合。借助框架开发,不仅可以省去很多常用的封装带来的时间,也有助于团队的编码风格和形成规范。 Gin框架是开源的,可以在github 上下载其源码库,查看相应的说明。
github:https://github.com/gin-gonic/gin
文档:https://gin-gonic.com/zh-cn/docs/introduction/
特点
- 速度:基于 Radix 树的路由,小内存占用。没有反射。可预测的 API 性能
- 中间件:传入的 HTTP 请求可以由一系列中间件和最终操作来处理。 例如:Logger,Authorization,GZIP,最终操作 DB
- 路由:在gin中可以非常简单的实现路由解析的功能,并包含路由组解析功能
- 内置渲染:Gin 为 JSON,XML 和 HTML 渲染提供了易于使用的 API
安装
通过 go get 命令安装gin框架
go get -u github.com/gin-gonic/gin
安装完毕后,可以在当前系統的$GOPATH目录下的src/github.com目录中找到gin-gonic目录,该目录下存放的就是gin框架的源码。 安装完毕后,我们可以使用gin来写一个简单的demo程序。使用一下gin。
注意:如使用vscode编辑器安装框架时报错需在项目下创建go.mod文件
module demo
go 1.19 //go版本
GET请求
func main() {
r := gin.Default()
r.GET("/get", getMsg)
//r.Run("localhost:9090")
r.Run(":9090") //不指定IP地址,默认为本地
}
func getMsg(c *gin.Context) {
name := c.Query("name")
//字符串数据
//c.String(http.StatusOK, "欢迎你哈哈哈:%s", name)
//JSON数据
c.JSON(http.StatusOK, gin.H{
"code": http.StatusOK,
"msg": "成功",
"data": "欢迎你:" + name,
})
}
POST请求
func main() {
r := gin.Default() // 路由引擎
r.POST("/post", postMsg)
r.Run(":9090")
}
func postMsg(c *gin.Context) {
//name := c.Query("name") // 获取url中的数据 query
name := c.DefaultPostForm("name", "gin") // 方法1
fmt.Println(name) // hello
from, b := c.GetPostForm("name") // 方法2
fmt.Println(from, b) // hello true 值+是否存在
c.JSON(http.StatusOK, "欢迎您:"+name)
}
重定向
func main() {
r := gin.Default()
//一般重定向 重定向到外部网络
r.GET("/redirect1", func(c *gin.Context) {
url := "http://baidu.com"
//重定向状态码StatusMovedPermanently
c.Redirect(http.StatusMovedPermanently, url)
})
//路由重定向
r.GET("/redirect2", func(c *gin.Context) {
c.Request.URL.Path = "/TestRedirect"
r.HandleContext(c)
})
r.GET("/TestRedirect", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"code": http.StatusOK,
"msg": "成功",
})
})
r.Run(":9090")
}
返回第三方数据
func main() {
r := gin.Default()
r.GET("/GetOtherData", func(c *gin.Context) {
url := "https://res.eemu.cn/LightPicture/2022/09/75b07814633a9d8a.jpeg"
response, err := http.Get(url)
if err != nil || response.StatusCode != http.StatusOK {
c.Status(http.StatusServiceUnavailable)
return
}
body := response.Body
contentLength := response.ContentLength
contentType := response.Header.Get("Content-Type")
// 数据写入响应体
c.DataFromReader(http.StatusOK, contentLength, contentType, body, nil)
})
r.Run(":9090")
}
多形式渲染
func main() {
r := gin.Default()
//JSON渲染
r.GET("/json", func(c *gin.Context) {
c.JSON(200, gin.H{
"html": "<h1>hello 捕风阁</h1>",
})
})
//HTML渲染
r.GET("/html", func(c *gin.Context) {
c.PureJSON(200, gin.H{
"html": "<h1>hello 捕风阁</h1>",
})
})
//XML渲染
r.GET("/xml", func(c *gin.Context) {
type Message struct {
Name string
Msg string
Age int
}
info := Message{}
info.Name = "张三"
info.Msg = "hello"
info.Age = 23
c.XML(http.StatusOK, info)
})
//YAML渲染
r.GET("/yaml", func(c *gin.Context) {
c.YAML(http.StatusOK, gin.H{
"message": "YAML渲染",
"status": 200,
})
})
r.Run(":9090")
}
文件服务器
对于Client请求的内容,如果是视频、音频、图片等文件,该如何操作?
func main() {
r := gin.Default()
r.GET("/file", fileServer)
r.Run(":9090")
}
func fileServer(c *gin.Context) {
path := "./" // 路径
fileName := path + c.Query("name") // 获取文件名称
c.File(fileName)
}
http://localhost:9090/file?name=demo.jpg 即可看到当前目录下的图片
单文件上传
func main() {
r := gin.Default()
r.POST("/upload", func(c *gin.Context) {
file, err := c.FormFile("fileName")
if err != nil {
c.String(http.StatusBadRequest, "文件上传错误") // 返回400错误
}
//存储路径
dst := "D:/test"
err2 := c.SaveUploadedFile(file, dst+file.Filename)
if err2 != nil {
c.String(http.StatusBadRequest, "文件上传错误2") // 返回400错误
} //存储文件
c.String(http.StatusOK, fmt.Sprintf("%s 上传完成", file.Filename))
})
r.Run(":9090")
}
Postman上传调试
多文件上传
func main() {
r := gin.Default()
r.POST("/upload", func(c *gin.Context) {
form, err := c.MultipartForm() // 获取form
if err != nil {
c.String(http.StatusOK, "上传文件错误")
}
files := form.File["file_key"] // 上传的所有文件
dst := "./"
// 遍历文件
for _, file := range files {
c.SaveUploadedFile(file, dst+file.Filename)
}
c.String(http.StatusOK, fmt.Sprintf("%d 个文件上传完成", len(files)))
})
r.Run(":9090")
}
自定义中间件
可以通过中间件对路由到来的数据先进行处理,包括数据加载、过滤等
我们通过中间件处理年龄及用户名,如下:
func main() {
r := gin.Default() // 默认路由引擎 包括 Logger 和 Recovery 中间件
//r := gin.New() // 没有任何中间件的路由引擎
r.Use(Middleware())
r.GET("/middleware", func(c *gin.Context) {
fmt.Println("服务端开始处理...")
name := c.Query("name")
ageStr := c.Query("age")
age, _ := strconv.Atoi(ageStr)
log.Println(name, age)
res := struct {
Name string `json:"name"` // 为结构添加标签 在JSON中显示为name
Age int `json:"age"`
}{name, age}
c.JSON(http.StatusOK, res)
})
r.Run(":9090")
}
func Middleware() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("中间件开始处理...")
name := c.Query("name")
ageStr := c.Query("age")
age, err := strconv.Atoi(ageStr)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, "输入的数据错误,年龄不是整数")
return
}
if age < 0 || age > 100 {
c.AbortWithStatusJSON(http.StatusBadRequest, "输入的数据错误,年龄数据错误")
return
}
if len(name) < 6 || len(name) > 12 {
c.AbortWithStatusJSON(http.StatusBadRequest, "用户名只能是6-12位")
return
}
c.Next() // 执行后续操作
fmt.Println(name, age)
}
}
登陆中间件
gin框架同时提供了快速登陆验证中间件,可以完成登陆的验证
func main() {
r := gin.Default()
//路由使用gin.BasicAuth()中间件
r.Use(AuthMiddleware())
r.GET("/login", func(c *gin.Context) {
//获取用户,它是由BasicAuth 中间件设置的
user := c.MustGet(gin.AuthUserKey).(string)
c.JSON(http.StatusOK, "登陆成功"+user)
})
r.Run(":9090")
}
func AuthMiddleware() gin.HandlerFunc {
//初始化用户
accounts := gin.Accounts{ //gin.Accounts 是map[string]string类型
"admin": "adminpw",
"system": "systempw",
}
//动态添加用户
accounts["go"] = "123456789"
accounts["gin"] = "gin123"
// 将用户添加到登陆中间件中
auth := gin.BasicAuth(accounts)
return auth
}
同步异步
同步方法调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为。
异步方法调用更像一个消息传递,一旦开始,方法调用就会立即返回,调用者就可以继续后续的操作。而异步方法通常会在另外一个g0程 (goroutine) 过程,不会阻碍调用者的工作。
func main() {
r := gin.Default()
//同步
r.GET("/sync", func(c *gin.Context) {
sync(c)
c.JSON(200, "主程序(主go程)同步已经执行")
})
//异步 通过go程执行
r.GET("/async", func(c *gin.Context) {
for i := 0; i < 10; i++ {
cCp := c.Copy()
go async(cCp, i)
}
c.JSON(200, "主程序(主go程)异步已经执行")
})
r.Run(":9090")
}
func sync(c *gin.Context) {
println("开始执行同步任务:" + c.Request.URL.Path)
time.Sleep(time.Second * 3)
println("同步任务执行完成")
}
func async(cp *gin.Context, i int) {
fmt.Println("第" + strconv.Itoa(i) + "个go主程开始执行" + cp.Request.URL.Path)
time.Sleep(time.Second * 3)
println("第" + strconv.Itoa(i) + "个go主程开始执行结束")
}
多服务器程序运行
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"golang.org/x/sync/errgroup"
"net/http"
"time"
)
// 定义路由组
var g errgroup.Group
func main() {
//服务器1
server01 := &http.Server{
Addr: ":9091",
Handler: router01(),
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
//服务器2
server02 := &http.Server{
Addr: ":9092",
Handler: router02(),
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
// 开启服务
g.Go(func() error {
return server01.ListenAndServe()
})
g.Go(func() error {
return server02.ListenAndServe()
})
//阻塞主go程
if err := g.Wait(); err != nil {
fmt.Println("执行失败")
}
}
func router01() http.Handler {
r1 := gin.Default()
r1.GET("/MyServer", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"code": http.StatusOK,
"msg": "服务器程序1",
})
})
return r1
}
func router02() http.Handler {
r2 := gin.Default()
r2.GET("/MyServer", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"code": http.StatusOK,
"msg": "服务器程序1",
})
})
return r2
}
//TOD0:解洪包:golang.org/x/sync/errgroup 无法 goget的问题
//cd $GOPATH/trc/golang.org/x
//git clone https://github.com/golang/sync.git
//git clone https://github.com/golang/crypto.git
//git clone https://github.com/golang/sys.git
路由组
路由组可以方便的对路由进行分组和有效的分类,使路由对应的代码易阅读
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
type ResGroup struct { // 定义结构体
Data string
Path string
}
func main() {
router := gin.Default()
// 路由分组1
v1 := router.Group("/v1") // 分组1 1级路径
{
r := v1.Group("/user") // 路由分组(2级路径)
r.GET("/login", login) // 响应请求 /v1/user/login
r2 := r.Group("showInfo") // 路由分组(3级路径)
r2.GET("/abstract", abstract) // 相应请求 /v1/user/showInfo/abstract
r2.GET("/detail", detail)
}
// 路由分组2
v2 := router.Group("/v2")
{
v2.GET("/other", other) // 响应请求 /v2/other
}
router.Run(":9090")
}
func other(c *gin.Context) {
c.JSON(http.StatusOK, ResGroup{"other", c.Request.URL.Path})
}
func login(c *gin.Context) {
c.JSON(http.StatusOK, ResGroup{"login", c.Request.URL.Path})
}
func detail(c *gin.Context) {
c.JSON(http.StatusOK, ResGroup{"detail", c.Request.URL.Path})
}
func abstract(c *gin.Context) {
c.JSON(http.StatusOK, ResGroup{"abstract", c.Request.URL.Path})
}
暂无评论内容