Golang web开发常用的框架有Gin、Beego、Echo等。
Gin特点:
Beego特点:
下载Gin框架:
go install github.com/gin-gonic/gin@latest
go install github.com/gin-gonic/[email protected]
运行一个案例程序:
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
// 1.创建路由
r := gin.Default()
// 2.绑定路由规则
// gin.Context,封装了request和response
r.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "hello World!")
})
// 3.监听端口,默认在8080
r.Run(":8082")
}
go mod init ginDemo
go get github.com/gin-gonic/gingo run main.go
gin 框架中采用的路由库是基于httprouter做的(GitHub - julienschmidt/httprouter: A high performance HTTP request router that scales well)
Gin 支持 GET、POST、PUT、PATCH、DELETE、OPTIONS 等请求类型。
r.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "hello word")
})
// 具体实现可单独定义一个函数
r.POST("/xxxpost",posthandle)
r.PUT("/xxxput")
Path 路径中参数以:开头,如:/user/:name,匹配情况如下:
/user/123 匹配 123
/user/test 匹配 test
/user/123/test 不匹配
/user/ 不匹配
Path 路径中参数以*开头,如:/user/*action,匹配情况如下:
/user/123 匹配 /123
/user/test 匹配 /test
/user/123/test 匹配 /123/test
/user/ 匹配 /
demo:
// 2.绑定路由规则,执行的函数
r.GET("/user/:name/*action", func(c *gin.Context) {
name := c.Param("name")
action := c.Param("action")
// 截取/
action = strings.Trim(action, "/")
//c.String(http.StatusOK, name+" is "+action)
c.JSON(http.StatusOK, gin.H{
"message": name+" is "+action,
})
})
(1)普通参数
可通过 *gin.Context 的 Query 函数获取参数,如:/user?name=test。
可通过 *gin.Context 的 DefaultQuery 函数设置默认值,如:/user。
r.GET("/user", func(c *gin.Context) {
//指定默认值
name := c.DefaultQuery("name", "guest")
c.String(http.StatusOK, fmt.Sprintf("hello %s", name))
})
(2)表单参数
r.POST("/form", func(c *gin.Context) {
type := c.DefaultPostForm("type", "post")
username := c.PostForm("username")
password := c.PostForm("password")
c.JSON(200, gin.H{
"type": type,
"username": username,
"password": password,
})
})
(3)数组参数
可通过 *gin.Context 的 QueryMap 函数获取数组参数,如:/user?ids[a]=hello&ids[b]=word
r.GET("/user", func(c *gin.Context) {
ids := c.QueryMap("ids")
c.String(200, ids["a"]+" "+ids["b"])
})
(4)文件上传
可通过 *gin.Context 的 FormFile 函数获取数组参数。
r := gin.Default()
//限制上传最大尺寸
r.MaxMultipartMemory = 8 << 20
r.POST("/upload", func(c *gin.Context) {
file, err := c.FormFile("file")
if err != nil {
c.String(500, "上传图片出错")
}
c.SaveUploadedFile(file, file.Filename)
c.String(http.StatusOK, file.Filename)
})
r.Run()
其他上传限制:
_, file, err := c.Request.FormFile("file")
if err != nil {
log.Printf("Error when try to get file: %v", err)
}
if file.Size > 1024*1024*2 {
fmt.Println("文件太大了")
return
}
if file.Header.Get("Content-Type") != "image/png" {
fmt.Println("只允许上传png图片")
return
}
c.SaveUploadedFile(file, "./video/"+file.Filename)
将具有相同路由 URL 前缀的进行分类处理,常见于不同版本的分组,如:/api/v1
、/api/v2。
此外,还支持多层分组。
r := gin.Default()
// 路由组1 ,处理GET请求
v1 := r.Group("/v1")
// {} 是书写规范
{
v1.GET("/login", login)
v1.GET("submit", submit)
}
v2 := r.Group("/v2")
{
v2.POST("/login", login)
v2.POST("/submit", submit)
}
r.Run(":8000")
func helloHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "Hello www.topgoer.com!",
})
}
func main() {
r := gin.Default()
r.GET("/topgoer", helloHandler)
if err := r.Run(); err != nil {
fmt.Println("startup service failed, err:%v\n", err)
}
}
当项目的规模增大后就不太适合继续在项目的main.go文件中去实现路由注册相关逻辑了,我们会倾向于把路由部分的代码都拆分出来,形成一个单独的文件或包。
gin_demo
├── go.mod
├── go.sum
├── main.go
└── router.go
router.go
func helloHandler(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "Hello www.topgoer.com!",
})
}
func setupRouter() *gin.Engine {
r := gin.Default()
r.GET("/topgoer", helloHandler)
return r
}
main.go
func main() {
r := setupRouter()
if err := r.Run(); err != nil {
fmt.Println("startup service failed, err:%v\n", err)
}
}
gin_demo
├── go.mod
├── go.sum
├── main.go
└── routers
├── blog.go
└── shop.go
routers/shop.go
func LoadShop(e *gin.Engine) {
e.GET("/hello", helloHandler)
e.GET("/goods", goodsHandler)
e.GET("/checkout", checkoutHandler)
...
}
routers/blog.go
func LoadBlog(e *gin.Engine) {
e.GET("/post", postHandler)
e.GET("/comment", commentHandler)
...
}
main.go
func main() {
r := gin.Default()
routers.LoadBlog(r)
routers.LoadShop(r)
if err := r.Run(); err != nil {
fmt.Println("startup service failed, err:%v\n", err)
}
}
gin_demo
├── app
│ ├── blog
│ │ └── router.go
│ └── shop
│ └── router.go
├── go.mod
├── go.sum
├── main.go
└── routers
└── routers.go
其中app/blog/router.go用来定义post相关路由信息,具体内容如下:
func Routers(e *gin.Engine) {
e.GET("/post", postHandler)
e.GET("/comment", commentHandler)
}
app/shop/router.go用来定义shop相关路由信息,具体内容如下:
func Routers(e *gin.Engine) {
e.GET("/goods", goodsHandler)
e.GET("/checkout", checkoutHandler)
}
routers/routers.go中根据需要定义Include函数用来注册子app中定义的路由,Init函数用来进行路由的初始化操作:
type Option func(*gin.Engine)
var options = []Option{}
// 注册app的路由配置
func Include(opts ...Option) {
options = append(options, opts...)
}
// 初始化
func Init() *gin.Engine {
r := gin.New()
for _, opt := range options {
opt(r)
}
return r
}
main.go中按如下方式先注册子app中的路由,然后再进行路由的初始化:
func main() {
// 加载多个APP的路由配置
routers.Include(shop.Routers, blog.Routers)
// 初始化路由
r := routers.Init()
if err := r.Run(); err != nil {
fmt.Println("startup service failed, err:%v\n", err)
}
}
转自:路由拆分与注册 · Go语言中文文档
中间件分为全局中间件,单个路由中间件和群组中间件。
所有请求都要经过此中间件
// 定义中间
func MiddleWare() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("中间件开始执行了")
// 设置变量到Context的key中,可以通过Get()取
c.Set("request", "中间件")
status := c.Writer.Status()
fmt.Println("中间件执行完毕", status)
}
}
func myTime(c *gin.Context) {
start := time.Now()
c.Next()
// 统计时间
since := time.Since(start)
fmt.Println("程序用时:", since)
}
func main() {
// 1.创建路由
r := gin.Default()
// 默认使用了2个中间件Logger(), Recovery();可以使用r:= gin.New()不带任何中间件
// 注册中间件
r.Use(MiddleWare(),myTime)
{
r.GET("/ce", func(c *gin.Context) {
// 取值
req, _ := c.Get("request")
fmt.Println("request:", req)
// 页面接收
c.JSON(200, gin.H{"request": req})
})
}
r.Run()
}
如果连续注册几个中间件则会是按照顺序先进后出的执行,遇到c.Next()就去执行下一个中间件里的c.Next()前面方法。
c.Abort() 表示终止调用该请求的剩余处理程序
//单个路由中间件
r.GET("/user", MiddleWare(), func(c *gin.Context) {
// 取值
req, _ := c.Get("request")
fmt.Println("request:", req)
// 页面接收
c.JSON(200, gin.H{"request": req})
})
//群组中间件
v1 := r.Group("/v1", gin.Logger(), gin.Recovery())
{
v1.GET("/", func(c *gin.Context) {
c.JSON(200, gin.H{"name": "m1"})
})
v1.GET("/test", func(c *gin.Context) {
c.JSON(200, gin.H{"name": "m1 test"})
})
}
Golang Gin框架 中间件(二)常用中间件(JWT验证、限流) - 掘金
gin.BasicAuth默认验证中间件
1、 全局校验
r := gin.Default()
r.Use(gin.BasicAuth(gin.Accounts{
"admin": "123456",
}))
r.GET("/", func(c *gin.Context) {
c.JSON(200, "首页")
})
r.Run(":8080")
2、局部校验
r.GET("/", func(c *gin.Context) {
c.JSON(200, "首页")
})
adminGroup := r.Group("/admin")
adminGroup.Use(gin.BasicAuth(gin.Accounts{
"admin": "123456",
}))
其他内置中间件:
func BasicAuth(accounts Accounts) HandlerFunc
func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc
func Bind(val interface{}) HandlerFunc
func ErrorLogger() HandlerFunc
func ErrorLoggerT(typ ErrorType) HandlerFunc
func Logger() HandlerFunc
func LoggerWithConfig(conf LoggerConfig) HandlerFunc
func LoggerWithFormatter(f LogFormatter) HandlerFunc
func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc
func Recovery() HandlerFunc
func RecoveryWithWriter(out io.Writer) HandlerFunc
func WrapF(f http.HandlerFunc) HandlerFunc
func WrapH(h http.Handler) HandlerFunc
Gin提供了两类绑定方法:
- Must bind:
- Methods:
Bind, BindJSON, BindXML, BindQuery, BindYAML
- Should bind:
- Methods:
ShouldBind, ShouldBindJSON, ShouldBindXML, ShouldBindQuery, ShouldBindYAML
1)ShouldBindJSON进行JSON格式绑定
// 定义接收数据的结构体
type Login struct {
// binding:"required"修饰的字段,若接收为空值,则报错,是必须字段
User string `form:"username" json:"user" uri:"user" xml:"user" binding:"required"`
Pssword string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"`
}
func main() {
// 1.创建路由
r := gin.Default()
// JSON绑定
r.POST("login", func(c *gin.Context) {
// 声明接收的变量
var json Login
// 将request的body中的数据,自动按照json格式解析到结构体
if err := c.ShouldBindJSON(&json); err != nil {
// gin.H封装了生成json数据的工具
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// 判断用户名密码是否正确
if json.User != "root" || json.Pssword != "admin" {
c.JSON(http.StatusBadRequest, gin.H{"status": "304"})
return
}
c.JSON(http.StatusOK, gin.H{"status": "200"})
})
r.Run(":8000")
}
2)ShouldBindQuery
/bind/query?username=admin&password=123
r.GET("/bind/query", testQuery)
func testQuery(c *gin.Context) {
var login Login
if err := c.ShouldBindQuery(&login); err == nil {
c.JSON(http.StatusOK, login)
} else {
c.String(http.StatusBadRequest, "error: %v", err)
c.Abort()
return
}
}
3)Bind,ShouldBindWith进行form绑定
1)Bind
r.POST("/loginForm", func(c *gin.Context) {
var form Login
if err := c.Bind(&form); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
})
2)ShouldBindWith
r.POST("/bind/form", testFrom)
func testFrom(c *gin.Context) {
var form Login
if err := c.ShouldBindWith(&form, binding.Form); err == nil {
c.JSON(http.StatusOK, form)
} else {
c.String(http.StatusBadRequest, "error: %v", err)
c.Abort()
return
}
}
4)ShouldBindUri进行URL格式数据绑定
r.GET("/:user/:password", func(c *gin.Context) {
// 声明接收的变量
var login Login
if err := c.ShouldBindUri(&login); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// 1.json
r.GET("/someJSON", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "someJSON", "status": 200})
})
// 2. 结构体响应
r.GET("/someStruct", func(c *gin.Context) {
var msg struct {
Name string
Message string
Number int
}
msg.Name = "root"
msg.Message = "message"
msg.Number = 123
c.JSON(200, msg)
})
// 3.XML
r.GET("/someXML", func(c *gin.Context) {
c.XML(200, gin.H{"message": "abc"})
})
// 4.YAML响应
r.GET("/someYAML", func(c *gin.Context) {
c.YAML(200, gin.H{"name": "zhangsan"})
})
// 5.protobuf格式
r.GET("/someProtoBuf", func(c *gin.Context) {
reps := []int64{int64(1), int64(2)}
// 定义数据
label := "label"
// 传protobuf格式数据
data := &protoexample.Test{
Label: &label,
Reps: reps,
}
c.ProtoBuf(200, data)
})
//6.重定向
r.GET("/index", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "http://www.5lmh.com")
})
go文件代码:
r.LoadHTMLGlob("tem/**/*")
r.GET("/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "user/index.html", gin.H{"title": "我是测试", "address": "www.5lmh.com"})
})
user/index.html
文件代码:
{{ define "user/index.html" }}
{{template "public/header" .}}
{{.address}}
{{template "public/footer" .}}
{{ end }}
public/header.html
文件代码:
{{define "public/header"}}
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>{{.title}}title>
head>
<body>
{{end}}
public/footer.html
文件代码:
{{define "public/footer"}}