go 语言框架 gin中文文档
gin启动服务的三种方式
func main() {
engine := gin.Default()
engine.GET("/", func(context *gin.Context) {
context.JSON(200, gin.H{"msg": "OK2"})
})
// 启动http服务的3中方法
// 方法1
//engine.Run(":8000")
// 方法2
//http.ListenAndServe(":8000", engine)
// 方法3
server := &http.Server{
Addr: ":8000",
Handler: engine,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
server.ListenAndServe()
}
路由
路由参数
api 参数通过Context的Param方法来获取
# url请求:http://127.0.0.1:8000/你好啊
engine.GET("/:name", func(context *gin.Context) {
// 获取位置参数
fmt.Println(context.Param("name"))
context.JSON(200, gin.H{"name": context.Param("name")})
})
URL 参数通过 DefaultQuery 或 Query 方法获取
Query
# url请求:http://127.0.0.1:8000/?name=张三
engine.GET("/", func(context *gin.Context) {
fmt.Println(context.Query("name"))
context.JSON(200, gin.H{"name": context.Query("name")})
})
DefaultQuery
// url: http://127.0.0.1:8000
engine.GET("/", func(context *gin.Context) {
fmt.Println(context.DefaultQuery("name", "默认值"))
context.JSON(200, gin.H{"name": context.DefaultQuery("name", "默认值")})
})
GetQueryArray
// url: http://127.0.0.1:8000?hobby=篮球&hobby=乒乓球
engine.GET("/", func(context *gin.Context) {
if hobbies, ok := context.GetQueryArray("hobby"); ok {
fmt.Println(hobbies)
context.JSON(200, gin.H{"hobbies": hobbies})
}
})
表单参数通过PostForm方法获取
// url: http://127.0.0.1:8000, 从form中传递参数
engine.POST("/", func(context *gin.Context) {
fmt.Println(context.PostForm("name"))
context.JSON(http.StatusOK, gin.H{"name": context.PostForm("name")})
})
GetPostFormArray
// url: http://127.0.0.1:8000, form中传递多个相同的hobby参数
engine.POST("/", func(context *gin.Context) {
if hobbies, ok := context.GetPostFormArray("hobby"); ok {
fmt.Println(hobbies)
context.JSON(http.StatusOK, gin.H{"hobbies": hobbies})
}
})
路由群组
// 路由群组
userGroup := engine.Group("/user")
// url: http://127.0.0.1:8000/user/login, 记得需要加上路由组的前缀才能访问
userGroup.POST("/login", func(context *gin.Context) {
context.String(200, "OK")
})
控制器
数据解析绑定
模型绑定可以将请求体绑定给一个类型,目前支持绑定的类型有 JSON, XML 和标准表单数据 (foo=bar&boo=baz)。
要注意的是绑定时需要给字段设置绑定类型的标签。比如绑定 JSON 数据时,设置 json:"fieldname"。
使用绑定方法时,Gin 会根据请求头中 Content-Type 来自动判断需要解析的类型。如果你明确绑定的类型,你可以不用自动推断,而用 BindWith 方法。
你也可以指定某字段是必需的。如果一个字段被 binding:"required" 修饰而值却是空的,请求会失败并返回错误。
绑定json的例子:
type Login struct {
User string `json:"user" form:"user" binding:"required"`
Password string `json:"password" form:"password" binding:"required"`
}
func main() {
engine := gin.Default()
engine.HandleMethodNotAllowed = true // 开启方法不允许校验
// url: http://127.0.0.1:8000/login
engine.POST("/login", func(context *gin.Context) {
var login Login
if err := context.BindJSON(&login); err != nil {
log.Println(err)
context.String(400, "错误请求")
}else{
fmt.Println(login)
context.JSON(200, login)
}
})
engine.Run(":8000")
}
绑定普通表单的例子:根据请求头中的Content-Type自动推断
type Login struct {
User string `json:"user" form:"user" binding:"required"`
Password string `json:"password" form:"password" binding:"required"`
}
func main() {
engine := gin.Default()
engine.HandleMethodNotAllowed = true // 开启方法不允许校验
// url: http://127.0.0.1:8000/login
engine.POST("/login", func(context *gin.Context) {
var login Login
// 根据请求头中的Content-Type自动推断
if err := context.Bind(&login); err == nil {
if login.User == "mayanan" && login.Password == "123456" {
context.JSON(http.StatusOK, gin.H{"status": "you are login int"})
}else{
context.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
}
}else {
context.JSON(http.StatusBadRequest, gin.H{"err": err.Error(), "status": "failed"})
}
})
engine.Run(":8000")
}
绑定多媒体表单的例子:
// url: http://127.0.0.1:8000/login
engine.POST("/login", func(context *gin.Context) {
var login Login
// 可以显示的声明来绑定多媒体表单
if err := context.BindWith(&login, binding.Form); err == nil {
if login.User == "mayanan" && login.Password == "123456" {
context.JSON(200, gin.H{"status": "user login in"})
}else{
context.JSON(401, gin.H{"status": "unauthorized"})
}
}else{
context.JSON(400, gin.H{"err": err.Error()})
}
})
请求
- 请求头
- 请求参数
- cookies
- 上传文件
// url: http://127.0.0.1:8000/upload
engine.POST("/upload", func(context *gin.Context) {
fileHeader, err := context.FormFile("file")
if err != nil {
context.JSON(400, gin.H{"err": "参数错误"})
return
}
file, _ := fileHeader.Open()
defer file.Close()
newFile, _ := os.Create(fileHeader.Filename)
defer newFile.Close()
_, _ = io.Copy(newFile, file)
context.String(200, "Ok")
})
响应
- 响应头
- 附加cookie
- 字符串响应
- 字符串响应
c.String(http.StatusOK, "some string")
- JSON/YAML响应
// url: http://127.0.0.1:8000/moreJSON
engine.GET("/moreJSON", func(context *gin.Context) {
var msg struct{
Name string `json:"user" xml:"user"`
Msg string `xml:"msg"`
Number int `xml:"number"`
SS struct{
User string
Age int
}
}
msg.Name = "张三"
msg.Msg = "消息体"
msg.Number = 888
msg.SS.User = "李四"
msg.SS.Age = 18
//context.JSON(http.StatusOK, msg)
context.YAML(http.StatusOK, msg)
})
- 视图响应
先要使用LoadHTMLTemplates加载模板文件
func main() {
engine := gin.Default()
engine.HandleMethodNotAllowed = true // 开启方法不允许校验
// 加载模板文件
//engine.LoadHTMLGlob("html/*")
engine.LoadHTMLFiles("html/index.html", "html/user.html")
// url: http://127.0.0.1:8000
engine.GET("/index.html", func(context *gin.Context) {
context.HTML(http.StatusOK, "index.html", gin.H{"name": "张三"})
})
engine.GET("/user.html", func(context *gin.Context) {
var User struct{
User string `json:"user"`
Age int `json:"age"`
}
User.User = "李四"
User.Age = 18
data, _ := json.Marshal(&User)
m := make(map[string]any)
json.Unmarshal(data, &m)
context.HTML(http.StatusOK, "user.html", m)
})
engine.Run(":8000")
}
- 文件响应
func main() {
engine := gin.Default()
engine.HandleMethodNotAllowed = true // 开启方法不允许校验
// url: http://127.0.0.1:8000/index/user.html
//engine.Static("/index", "./html")
// url: http://127.0.0.1:8000/index/index.html
//engine.StaticFS("/index", gin.Dir("./html", false))
// url: http://127.0.0.1:8000/index
engine.StaticFile("/index", "./html/index.html")
engine.Run(":8000")
}
- 重定向
engine.GET("/redirect/mayanan", func(context *gin.Context) {
context.Redirect(http.StatusMovedPermanently, "http://mayanan.cn")
})
- 同步异步
goroutine机制可以方便的实现异步处理
func main() {
engine := gin.Default()
engine.HandleMethodNotAllowed = true // 开启方法不允许校验
engine.GET("/long_async", func(context *gin.Context) {
// Copy 返回可以在请求范围之外安全使用的当前上下文的副本。当必须将上下文传递给 goroutine 时,必须使用它。
// goroutine中只能使用上下文的副本
// 1. 异步
cp := context.Copy()
go func() {
time.Sleep(5 * time.Second)
// 注意:goroutine中必须使用上下文副本
log.Println("done! in path", cp.Request.URL.Path)
}()
})
engine.GET("/long_sync", func(context *gin.Context) {
// 同步:可以使用原始上下文,context
time.Sleep(5 * time.Second)
log.Println("done! int path", context.Request.URL.Path)
})
engine.Run(":8000")
}
中间件
- 分类使用方式
func main() {
router := gin.New()
router.HandleMethodNotAllowed = true // 开启方法不允许校验
// 1. 全局中间件
router.Use(gin.Logger(), gin.Recovery())
// 2. 单路由的中间件,可以加任意多个
router.GET("/index", MyMiddleWare(), benchEndPoint(), func(context *gin.Context) {
fmt.Println("/index 执行了")
context.String(200, "GET OK")
})
// 3. 群组路由的中间件
authorized := router.Group("/", MyMiddleWare())
// 或者这样使用
//authorized = router.Group("/")
//authorized.Use(MyMiddleWare())
{
authorized.POST("/login", func(context *gin.Context) {
fmt.Println("login 执行了")
context.String(200, "POST OK")
})
}
router.Run(":8000")
}
func MyMiddleWare() gin.HandlerFunc {
return func(context *gin.Context) {
fmt.Println("MyMiddleWare执行了")
}
}
func benchEndPoint() gin.HandlerFunc {
return func(context *gin.Context) {
fmt.Println("benchEndPoint执行了")
}
}
- 自定义中间件
func main() {
router := gin.New()
router.HandleMethodNotAllowed = true // 开启方法不允许校验
// 自定义中间件
router.Use(MyLogger1())
router.Use(MyLogger2())
router.GET("/index", func(context *gin.Context) {
log.Println("index执行了", context.Value("example1").(string), context.Value("example2").(string))
context.String(200, "OK")
})
router.Run(":8000")
}
func MyLogger1() gin.HandlerFunc {
return func(context *gin.Context) {
start := time.Now()
context.Set("example1", "123456")
// 请求前
log.Println("MyLogger1请求前执行了")
context.Next() // 处理请求
// 请求后
duration := time.Since(start)
log.Println("MyLogger1请求后执行了:", duration)
context.Writer.WriteHeader(500)
status := context.Writer.Status()
log.Println("MyLogger1, status: ", status)
}
}
func MyLogger2() gin.HandlerFunc {
return func(context *gin.Context) {
start := time.Now()
context.Set("example2", "987654")
// 请求前
log.Println("MyLogger2请求前执行了")
context.Next() // 处理请求
// 请求后
duration := time.Since(start)
log.Println("MyLogger2请求后执行了:", duration)
status := context.Writer.Status()
log.Println("MyLogger2, status: ", status)
}
}
输出结果:
2022/09/02 15:37:42 MyLogger1请求前执行了
2022/09/02 15:37:42 MyLogger2请求前执行了
2022/09/02 15:37:42 index执行了 123456 987654
2022/09/02 15:37:42 MyLogger2请求后执行了: 529.9µs
2022/09/02 15:37:42 MyLogger2, status: 200
2022/09/02 15:37:42 MyLogger1请求后执行了: 107.7419ms
[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 200 with 500
2022/09/02 15:37:42 MyLogger1, status: 500
- 中间件参数
- 内置中间件
简单认证-BasicAuth
func main() {
router := gin.New()
router.HandleMethodNotAllowed = true // 开启方法不允许校验
// 使用gin.BasicAuth中间件,设置授权用户
authorized := router.Group("/admin", gin.BasicAuth(gin.Accounts{
"张三": "123456",
"lisi": "456",
"wangwu": "7890",
}))
authorized.GET("/index", func(context *gin.Context) {
user := context.MustGet(gin.AuthUserKey).(string)
if info, ok := secret[user]; ok {
context.JSON(http.StatusOK, gin.H{"user": user, "info": info})
}else{
context.JSON(http.StatusOK, gin.H{"user": user, "info": "NO info"})
}
})
router.Run(":8000")
// 注意:要想认证通过,需要在请求头里增加:Authorization:b64
//str := "张三" + ":" + "123456"
//b64 := "Basic " + base64.StdEncoding.EncodeToString([]byte(str))
//fmt.Println(b64)
}