1、file——》go modeles——创建
2、设置代理:
setting——》go Modules
GOPROXY=https://mirrors.aliyun.com/goproxy/,direct;
3、在go.mod中引入gin
require github.com/gin-gonic/gin v1.6.3/
如果没有安装gin,在引入gin后使用go mod tidy就会自动下载(或者直接下载)
go get -u github.com/gin-gonic/gin
4、创建main.go
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
engine := gin.Default()
engine.GET("/helloworld", func(c *gin.Context){
c.String(http.StatusOK, "hello world")
})
//返回JSON格式的数据
engine.GET("/json", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"name": "admin",
"age": 18,
})
})
//返回xml格式的数据
engine.GET("/xml", func(c *gin.Context) {
c.XML(http.StatusOK, gin.H{
"name": "zhangsan",
"age": 19,
})
})
engine.Run()
}
5、运行
在Terminal中输入go run main.go,然后浏览器搜索http://127.0.0.1:8080/helloworld
2、静态文件package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
engine := gin.Default()
//加载templates下的模板文件
engine.LoadHTMLGlob("templates
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
)
//自定义中间件
func Test() gin.HandlerFunc{
return func(c *gin.Context) {
fmt.Println("当前的url是:", c.Request.URL)
}
}
func main() {
engine := gin.New()
//在此使用表示所有请求都经过中间件
//engine.Use(Test())
//engine.Use(Test(), gin.Logger()) //同时打印日志
engine.GET("/get", Test(), func(c *gin.Context) { //只对某一个请求使用中间件
//engine.GET("/get", func(c *gin.Context) {
c.String(http.StatusOK, "get")
})
engine.GET("/hello", func(c *gin.Context) {
c.String(http.StatusOK, "hello")
})
//对整个路由组使用中间件
api := engine.Group("/api", Test())
{
api.GET("/get_info", func(c *gin.Context) {
c.String(http.StatusOK, "api get_info")
})
api.GET("/get_user", func(c *gin.Context) {
c.String(http.StatusOK, "api get_user")
})
}
engine.Run()
}
二、gin框架详解
使用传统go处理请求
package main
import (
"fmt"
"net/http"
)
func sayHello(w http.ResponseWriter,r *http.Request){
//使用绝对路径
//b, _ := ioutil.ReadFile("E:\Go\workspace\study\go-study\static\txt\hello.txt")
//获取当前文件路径
cur, _ := os.Getwd()
filename := filepath.Join(cur, "static/txt/hello.txt")
b, _ := ioutil.ReadFile(filename)
_, _ = fmt.Fprintln(w, string(b))
//_, _ = fmt.Fprintln(w, "hello Golang")
}
func main() {
http.HandleFunc("/hello",sayHello)
err := http.ListenAndServe(":9090", nil)
if err!=nil {
fmt.Println("http serve failed, err:%vn", err)
return
}
}
Gin框架简单使用
下载并安装
go get -u github.com/gin-gonic/gin
第一个gin示例
package main
import (
"github.com/gin-gonic/gin"
)
func test(c *gin.Context) {
c.JSON(200, gin.H{
"message": "Hello golang",
})
}
func main() {
engine := gin.Default() //返回默认的路由引擎
//指定用户使用GET请求访问/hello时,执行test这个函数
engine.GET("/hello",test)
//启动服务
//engine.Run() //默认8080端口
engine.Run(":9090") //指定访问端口
}
api程序的REST风格
- Get用来获取资源(查询)
- POST用来新建资源(创建)
- PUT用来更新资源(更新)
- DELETE用来删除资源(删除)
Go语言内置了文本模板引擎text/template和用于HTML文档的html/template。模板文件中使用{{和}}包裹和标识需要传入的数据,传给模板的数据可以通过**{.}h或者{.FieldName}**来访问它的字段
解析模板文件
package main
import (
"fmt"
"html/template"
"net/http"
"os"
"path/filepath"
)
type User struct {
Name string
Gender string //首字母小写访问不到
Age int
}
func teplatetest(w http.ResponseWriter, r *http.Request){
//========解析模板========
//使用绝对路径
//template, err := template.ParseFiles("E:\Go\workspace\study\go-study\templates\hello.tmpl")
//获取当前文件路径
cur, _ := os.Getwd()
filename := filepath.Join(cur, "templates/hello.tmpl")
template, err := template.ParseFiles(filename)
if err != nil{
fmt.Println("Parse template failed,err:",err)
return
}
//===========渲染模板========
name := "静静"
//结构体
user1 := User{
Name: "张三",
Gender: "男",
Age: 18,
}
//map
map1 := map[string]interface{}{
"name": "张三",
"school": "西华",
}
//切片
hobbies := []string{
"唱",
"跳",
"rap",
}
//通过渲染传单个值
//err = template.Execute(w, name)
//通过渲染传多个值
err = template.Execute(w, map[string]interface{}{
"name" : name,
"user1" : user1,
"map1" : map1,
"hobby" : hobbies,
})
if err != nil{
fmt.Println("render template failed,err:",err)
return
}
}
func main() {
http.HandleFunc("/", teplatetest)
err := http.ListenAndServe(":9090", nil)
if err != nil{
fmt.Println("HTTP serve start failed,err:",err)
return
}
}
定义模板文件.tmpl(如templates文件夹下的hello.html)
Select
这是查询页面
{{}}
Hello: {{.name}}
user1: {{.user1.Name}},{{.user1.Gender}},{{.user1.Age}}
map1: {{.map1.name}},{{.map1.school}}
{{}}
{{ $v1 := 100 }}
{{ $name := .map1.name }}
{{}}
{{ if $v1 }} {{$v1}}
{{else}}
v1不存在
{{end}}
{{if $name}} {{$name}}
{{else}} age不存在
{{end}}
{{}}
{{if lt .user1.Age 22}} 好好上学
{{else}} 好好工作
{{end}}
{{}}
{{range $idx, $hobby :=.hobby}}
{{$idx}} - {{$hobby}}
{{else}} 没有爱好
{{end}}
{{}}
{{with .user1}}
{{.Name}},{{.Age}},{{.Gender}}
{{end}}
{{}}
{{}}
{{index .hobby 2}}
输出结果如下所示:
Gin框架模板渲染这是查询页面
Hello: 静静
user1: 张三,男,18
map1: 张三,西华
100
张三
好好上学
0 - 唱
1 - 跳
2 - rap
张三,18,男
rap
首先定义一个存放模板的templates文件夹,然后在其内部按照业务分别定义一个posts文件夹和一个users文件。里面存放index.tmpl
posts/index
{{.title}}
模板解析与渲染(包括加载静态文件与给模板添加自定义函数)
package main
import (
"github.com/gin-gonic/gin"
"html/template"
"net/http"
)
//静态文件:HTML页面上用到的样式文件:css,js,图片
func main() {
engine := gin.Default()
//加载静态文件
engine.Static("/xxx","./statics")
//Gin框架中给模板添加自定义函数
engine.SetFuncMap(template.FuncMap{
"safe" : func(str string) template.HTML{
return template.HTML((str))
},
})
//模板解析
//engine.LoadHTMLFiles("templates/index.tmpl","templates/users/index.html")
//加载文件夹下的所有文件(**表目录,*表文件)
engine.LoadHTMLGlob("templates*")
engine.GET("/posts/index", func(c *gin.Context) {
//HTTP请求
c.HTML(http.StatusOK, "index.tmpl", gin.H{ //模板渲染
"title" : "posts/index.html",
})
})
engine.GET("/users/index", func(c *gin.Context) {
//HTTP请求
c.HTML(http.StatusOK, "index.tmpl", gin.H{ //模板渲染
"title" : "百度一下",
})
})
//启动serve
engine.Run(":9090")
}
user/index.html 引用模板函数
{{}}
posts/index
{{}}
{{.title | safe}}
Gin框架路由组
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
engine := gin.Default()
//路由
//逐个设置请求的方式
engine.GET("/index", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"method" : "GET",
})
})
engine.POST("/index", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"method" : "POST",
})
})
//用Any方法可以匹配所有请求方式
engine.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)
engine.NoRoute(func(c *gin.Context) {
c.JSON(http.StatusNotFound, gin.H{"msg" : "该页面不存在,请重新输入!"})
})
//路由组
shopGroup := engine.Group("/shop")
{
shopGroup.GET("/index", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"msg" : "/shop/index"})
})
shopGroup.GET("/hello", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"msg" : "/shop/hello"})
})
}
engine.Run(":9090")
}
Gin框架中间件
Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数,这个钩子就叫做中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
//HandlerFunc
func indexHandler(c *gin.Context) {
fmt.Println("这是indexHandler")
//在上下文中获取值
name, ok := c.Get("name")
if !ok{
name = "匿名用户"
}
c.JSON(http.StatusOK, gin.H{
"msg1" : "index",
"name" : name,
})
}
//定义一个中间件
func m1(c *gin.Context) {
fmt.Println("这是中间件m1")
}
func m2(c *gin.Context) {
fmt.Println("m2 in ...")
//计时
start := time.Now()
//先调用后面需要处理的函数,再执行Next后面的代码
c.Next() //调用后续的处理函数
cost := time.Since(start)
fmt.Printf("调用耗费时间: %vn", cost)
fmt.Println("m2 out ...")
}
func m3(c *gin.Context) {
fmt.Println("m3 in..")
c.Next()
//c.Abort() //阻止调用后续处理函数
//在上下文中设置值
c.Set("name", "静静")
//如果想后续代码不执行,可以使用return返回
//return
fmt.Println("m3 out..")
}
func authMiddleware(doCheck bool)gin.HandlerFunc {
//可以做一些查询数据库、或者一些其他准备工作
return func(c *gin.Context) {
if doCheck{
//存放具体逻辑
}else{
c.Next() //放行
}
}
}
func main() {
engine := gin.Default()
//如果不想使用默认的中间件
//engine := gin.New()
//全局注册中间件
//engine.Use(m1, authMiddleware)
//engine.GET("/index", func(c *gin.Context) {
// c.JSON(http.StatusOK, gin.H{
// "msg" : "index",
// })
//})
engine.GET("/index", indexHandler) //与上面代码作用一致
//为某个路由单独注册中间件
engine.GET("/index_m1",m1, indexHandler) //调用中间件
engine.GET("/index_m2",m2, indexHandler)
engine.GET("/index_m3",m2,m3, indexHandler)
//为路由组注册中间件
//shopGroup := engine.Group("/shop", authMiddleware(true))
shopGroup := engine.Group("/shop")
engine.Use(authMiddleware(true))
{
shopGroup.GET("/index", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"msg" : "shop/index..."})
})
shopGroup.GET("/hello", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"msg" : "shop/hello..."})
})
}
engine.Run(":9090")
}
调用Abort后会阻止调用后面的中间件
调用输出结果:http://127.0.0.1:9090/index_m3
m2 in …
m3 in…
这是indexHandler
m3 out…
调用耗费时间: 0s
m2 out …
注意:当在Gin中间件或Handler中启动新的goroutine中,不能使用原始的上下文(c *gin.Context),必须使用其只读副本(c.Copy())
三、URL处理之URL匹配规则?匹配一个字符
* 匹配0个或多个字符
* *匹配0个或多个目录
2、例子
- /trip/apiapi/alie 匹配 /trip/api/alie,/trip/dax/api/alie;但不匹配 /trip/a/api
- *.htmlm 匹配所有以.htmlm结尾的路径
使用这种模式,创建的URL必须全匹配xxx。例如
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
// 可以匹配/user/dongshao这种模式, 但是不能匹配/user/或者/user
r.GET("/user/:name", func(c *gin.Context) {
name := c.Param("name")
c.String(http.StatusOK, "name is %s", name)
})
r.Run(":8000")
}
*xxx模式
使用这种模式时,创建的URL即可以匹配xxx,也可以不匹配xxx。例如
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
// 可以匹配/user, /user/, /user/dongshao, /user/dongshao/
r.GET("/user/*name", func(c *gin.Context) {
name := c.Param("name")
c.String(http.StatusOK, "name is %s", name)
})
r.Run(":8000")
}
:xxx模式和*xxx模式混合在同一个URL
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
// 只能匹配/user/dongshao/, /user/dongshao/20, /user/dongshao/20/
// 不能匹配/user/dongshao
r.GET("/user/:name/*age", func(c *gin.Context) {
name := c.Param("name")
age := c.Param("age")
message := name + " is " + age
c.String(http.StatusOK, message)
})
r.Run(":8000")
}
:xxx模式和*xxx模式混合分开使用
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
// /user/dongshao的URL都访问这个
r.GET("/user/:name", func(c *gin.Context) {
name := c.Param("name")
c.String(http.StatusOK, "name is %s", name)
})
// /user/dongshao/, /user/dongshao/18, /user/dongshao/18/ 的代码都访问这个
r.GET("/user/:name/*age", func(c *gin.Context) {
name := c.Param("name")
age := c.Param("age")
message := name + " is " + age
c.String(http.StatusOK, message)
})
r.Run(":8000")
}
四、路由冲突
panic: wildcard segment ':id' conflicts with existing children in path '/api/v1/devices/:id/sub-devices'
之前运行时出现conflict的错误,这种是因为路径和通配符冲突了
如:
GET /user/info/:name GET /user/:id //这种就会产生冲突,例如输出的路由字符串为/user/info,在/user/:id规则中,info会被解释成:id的值 GET /user/info/:name POST /user/:id //这种就不会产生冲突,两个热路由的HTTP Method不同


