讲师:申专基本概览:

目前  golang使用最广泛的Web 微框架之一。具有高性能的优点,基于 httprouter,它提供了类似martini但更好性能(路由性能约快40倍)的API服务。

gin 特点和特性:

速度快性能好,

支持中间件操作方便编码处理

路由解析

内置渲染支持json xml html等

(需要go 1.6及以上)

官方地址:https://github.com/gin-gonic/gin

中文API:https://gin-gonic.com/zh-cn/docs/

案例:

go get -u github.com/gin-gonic/gin
或者 执行 go mod tidy

下载依赖import (
"github.com/gin-gonic/gin"
)

func main() {
r := gin.Default()                     //拿到一个 *gin.Engine
r.GET("ping", func(ctx *gin.Context) { //获取GET请求
 ctx.JSON(200, gin.H{   //响应页面
  "msg": "pong",
 })
})
r.Run() //开启服务  0.0.0.0:8080
}



addRoute


说明:gin. Default  还有gin. New

关系:default 内部也是调用的 New 但是会默认加上 Logger 和 Recovery中间件


HTTP请求 及 Handle

r.GET("ping", func(ctx *gin.Context) {//响应返回}

GET  POST  

   router.GET("/someGet", getting)
router.POST("/somePost", posting)
router.PUT("/somePut", putting)
router.DELETE("/someDelete", deleting)
router.PATCH("/somePatch", patching)
router.HEAD("/someHead", head)
router.OPTIONS("/someOptions", options)


func someGet(c *gin.Context) {
fmt.Println("someGet")
c.Writer.WriteString("this is someGet()")
}
func somePost(c *gin.Context) {
fmt.Println("somePost")
c.Writer.WriteString("this is somePost()")
}

func main() {
r := gin.Default()                     //拿到一个 *gin.Engine
r.GET("ping", func(ctx *gin.Context) { //获取GET请求
 ctx.JSON(200, gin.H{
  "msg": "pong",
 })
})

r.GET("/someGet", someGet)
r.POST("/somePost", somePost)
r.Run() //开启服务  0.0.0.0:8080
}


请求参数处理:Get请求URL后参数     /someGet?name=shenzhuan

关键方法

c.Query("key") (.var 会自动填充前面)

c.DefaultQuery("key","xxoo")  没有key 会给一个默认值


Post请求Form表单数据

关键方法  测试选择form-Data

c.PostForm()

c.DefaultPostFrom()

代码实现:

func getKey(c *gin.Context) {
s := c.Query("name")
ss := c.DefaultQuery("age", "xxoo")
c.String(200, "this is getKey() 获取到key=%s   %s", s, ss)
}

func postVal(c *gin.Context) {
s := c.PostForm("name")
ss := c.DefaultPostForm("age", "xxoo")
// c.HTML(200, "welcome.html", gin.H{
//  "name": s,
//  "age":  ss,
// })
c.String(200, "this is postVal() 获取到key=%s   %s", s, ss)
}

func main (){
r := gin.Default()  
   r.GET("/getKey", getKey)
r.POST("/postVal", postVal)
   ...
}


JSON请求数据

测试选择 raw  + JSON

{

 "name":"shenzhuan",

 "password":"123"

}

方案一:直接空结构体接收处理

func Login(c *gin.Context) {
json := make(map[string]interface{}) //注意该结构接受的内容
c.BindJSON(&json)
log.Printf("%v",&json)
c.JSON(http.StatusOK, gin.H{
 "name": json["name"],
 "password": json["password"],
})
}

方案二:定义结构体处理(推荐)

type User struct {
Name string `json:"name"`  //json:后面的字符串表示传递来的key值
Password int64 `json:"password"`
}
func Login(c *gin.Context) {
json := User{}

c.BindJSON(&json)

log.Printf("%v",&json)
c.JSON(http.StatusOK, gin.H{
 "name": json.Name,
 "password": json.Password,

})
}


方案三:数据绑定Form表单

c.ShouldBind(&user)

func toRegister(c *gin.Context) {
c.HTML(200, "register.html", nil)
}
func register(c *gin.Context) {
var user model.User
c.ShouldBind(&user)
c.String(200, "获取到 %s", user)
}

func main (){
r := gin.Default()    
r.GET("/toRegister", toRegister)
r.POST("/register", register)
r.Run() //开启服务  0.0.0.0:8080
}



type User struct {
UserName string   `form:"name"`//如果页面表单的字段名与结构体的一致 可以省略此
Password string   `form:"password"`
Hobby    []string `form:"hobby"`
}

<form action="/register" method="post">
       <input name="name"><br/>
       <input name="password"><br/>
       <input type="checkbox" name="hobby" value="GoLang" >GoLang
       <input type="checkbox" name="hobby" value="Java" >Java
       <input type="checkbox" name="hobby" value="C++" >C++
       <input type="submit">
   </form>


RestFul风格路径参数

是目前比较成熟的一套互联网应用程序的API设计理念,Rest是一组架构约束条件和原则,如何Rest约束条件和原则的架构,我们就称为Restful架构,Restful架构具有结构清晰、符合标准、易于理解以及扩展方便等特点,受到越来越多网站的采用

关键方法 c.Param("key")

e.GET("/hello/:username",testPathParam)

e.GET("/hello/:username/:age",testPathParam)   可多个

func getParam(c *gin.Context) {
usename := c.Param("usename")
age := c.Param("age")
c.String(200, "this is getKey() 获取到key=%s   %s", usename, age)
}
main方法中 :
 //r.GET("/getParam/:usename", getParam)
r.GET("/getParam/:usename/:age", getParam)

http://localhost:8080/getParam/xxx/18


引入模板引擎 HTML

1.编写:templates/welcome.html  

{{.}}

2.然后  e.LoadHTMLGlob("templates/*")    或者 LoadHTMLFiles("","","")

func postVal(c *gin.Context) {
s := c.PostForm("name")
ss := c.DefaultPostForm("age", "xxoo")
c.HTML(200, "welcome.html", gin.H{
 "name": s,
 "age":  ss,
})
//c.String(200, "this is postVal() 获取到key=%s   %s", s, ss)
}

并且在main方法中    地址是:
r.LoadHTMLGlob("src/gin/01/templates/*")
//router.LoadHTMLFiles("src/gin/01/templates/welcome.html", "src/gin/01/templates/xx.html")

综合应用 get与post都有

像百度搜索POST请求    goSearch跳到 搜索页面 结果分页的?过来

//比如:https://www.baidu.com/s?wd=xxoo页面的表单

welcome.html 页面加入表单:

<form method="post" action="/search?page=1">
       <input type="text" name="key">  <!-- 其他标签的值传递就不带大家复习了-->
       <input type="submit">
</form>
func search(c *gin.Context) {
page := c.DefaultQuery("page", "0")
key := c.PostForm("key")
age := c.PostForm("age")
hobby := c.PostFormArray("hobby")  //多个值传递
c.String(200, "this is search() 获取到 page:%s   key:%s   age:%s    hobby:%s", page, key, age, hobby)
}

main方法中
r.POST("/search", search)

postmain测试 :

hobby传递多个值逗号隔开即可。




引入静态资源及BootStrap

 地址: https://getbootstrap.com/

关键方法 :  e.Static("/页面访问","/实际路径")

 e.StaticFile("/页面访问","/实际路径")


代码实现:

index.HTML


<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <meta http-equiv="X-UA-Compatible" content="IE=edge">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <link rel="stylesheet" href="/assets/css/bootstrap.min.css">
   <script src="/assets/js/bootstrap.min.js"></script>
   <title>Document</title>
</head>
<body>

   <select class="form-select" aria-label="Default select example">
       <option selected>Open this select menu</option>
       <option value="1">One</option>
       <option value="2">Two</option>
       <option value="3">Three</option>
   </select>
   <button type="button" class="btn btn-primary">Primary</button>
   <button type="button" class="btn btn-secondary">Secondary</button>
   <button type="button" class="btn btn-success">Success</button>
   <button type="button" class="btn btn-danger">Danger</button>
   <button type="button" class="btn btn-warning">Warning</button>
   <button type="button" class="btn btn-info">Info</button>
   <button type="button" class="btn btn-light">Light</button>
   <button type="button" class="btn btn-dark">Dark</button>
</body>
</html>






package main
import (
"github.com/gin-gonic/gin"
)
func main() {
e := gin.Default()
e.Static("/assets", "src/gin/02/assets")
e.StaticFile("/favicon.ico", "src/gin/02/assets/favicon.ico")
e.GET("/index", index)
e.LoadHTMLGlob("src/gin/02/templates/*")
e.Run()
}
func index(c *gin.Context) {
c.HTML(200, "index.html", nil)
}



Gin使用中间件

请求拦截

gin.default实例化引擎,默认有两中间件 Logger 和 Recovery 分别处理日志和错误

自定义中间件  

关键方法: engine.Use()


package main

import (
"fmt"

"github.com/gin-gonic/gin"
)

func MyHandler(c *gin.Context) {
fmt.Println("MyHandler 。。。。 ")
}

func MyHandlerB() gin.HandlerFunc {
return func(ctx *gin.Context) {
 path := ctx.FullPath()
 method := ctx.Request.Method
 fmt.Println("MyHandlerB 。。。。 ", path, " ", method)
}
}

func main() {
e := gin.Default()
e.Use(MyHandler, MyHandlerB())
e.Static("../assets", "src/gin/02/assets")
e.StaticFile("/favicon.ico", "src/gin/02/assets/favicon.ico")
e.GET("/index", index)
e.LoadHTMLGlob("src/gin/02/templates/*")
e.Run()
}
func index(c *gin.Context) {
c.HTML(200, "index.html", nil)
}


使用BasicAuth中间件路由组权限管理

比如 前后    /admin/    web

 group:=e.Group()      group.Get()


package main import ( "net/http" "github.com/gin-gonic/gin" ) var adminUsers = gin.H{ "shenz":     gin.H{"email": "sgeb@123.com", "phone": "142341977555"}, "mashibing": gin.H{"email": "mashibing@123.com", "phone": "142341977555"}, "lianp":     gin.H{"email": "lianp@123.com", "phone": "142341977555"}, "glang":     gin.H{"email": "glang@123.com", "phone": "142341977555"}, } func main() { e := gin.Default() routerGroup := e.Group("/admin", gin.BasicAuth(gin.Accounts{ //理论上从数据库查 "shenz":     "123", "mashibing": "123", "lianp":     "123", "glang":     "123", })) //localhost:admin/secrept routerGroup.GET("/secrept", HandlerA) e.Run() } func HandlerA(c *gin.Context) { user := c.MustGet(gin.AuthUserKey).(string) if secret, ok := adminUsers[user]; ok { c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret}) } else { c.JSON(http.StatusOK, gin.H{"user": user, "secret": "No secret!"}) } }


Cookie & Session的使用

Cookie是服务器往客户端写的一些数据,可以实现自动登录等功能

context.Cookie("name")   context.SetCookie("",...)


package main import ( "github.com/gin-gonic/gin" ) func main() { e := gin.Default() e.GET("/testcookie", Handler) e.Run() } func Handler(c *gin.Context) { //获取cookie userName, err := c.Cookie("userName") if err != nil { userName = "shenz" c.SetCookie("userName", userName, 60*60, "/", "localhost", false, true) } c.String(200, "test cookie") } 因为http无状态 无连接,如何保持会话就需要  Session 会话  gin 本身没有session的支持 ,需要第三方中间件       可以打开 go.dev 官网 的packages 到里面去找找 gin session    .  可见 gin middleware   Use即可使用          而且也支持 Redis memcached MongoDB 等            go get github.com/gin-contrib/sessions      import "github.com/gin-contrib/sessions" 基本的实例:  Basic Examples package main import ( "github.com/gin-contrib/sessions" "github.com/gin-contrib/sessions/cookie" "github.com/gin-gonic/gin" ) func main() { r := gin.Default() //存储 store := cookie.NewStore([]byte("secret")) //注入中间件 r.Use(sessions.Sessions("mysession", store)) r.GET("/hello", func(c *gin.Context) { //实例化session session := sessions.Default(c) //获取session 值 if session.Get("hello") != "world" { //设置session值 session.Set("hello", "world") //保存 session.Save() } c.JSON(200, gin.H{"hello": session.Get("hello")}) }) r.Run(":8000") }



集成MySQL数据库:

go get -u github.com/go-sql-driver/mysql


import (

"database/sql"

_ "github.com/go-sql-driver/mysql"    //驱动引入 不需要调用 直接使用

)



package main import ( "database/sql" "fmt" "log" "strconv" "github.com/gin-gonic/gin" _ "github.com/go-sql-driver/mysql" ) func insertHandler(c *gin.Context) { cname := c.Query("cname") tid, _ := strconv.Atoi(c.Query("tid")) course := Course{0, cname, tid} insert(dbs, course) c.JSON(200, course) } func deleteHandler(c *gin.Context) { cid, _ := strconv.Atoi(c.Query("cid")) delete(dbs, cid) c.JSON(200, cid) } func updateHandler(c *gin.Context) { cid, _ := strconv.Atoi(c.Query("cid")) cname := c.Query("cname") tid, _ := strconv.Atoi(c.Query("tid")) course := Course{cid, cname, tid} update(dbs, course) c.JSON(200, course) } func queryHandler(c *gin.Context) { cid, _ := strconv.Atoi(c.Query("cid")) course := query(dbs, cid) c.JSON(200, course) } func queryRowsHandler(c *gin.Context) { cid, _ := strconv.Atoi(c.Query("cid")) courses := queryRows(dbs, cid) c.JSON(200, courses) } func main() { e := gin.Default() //testDB() initDB("root:1234@tcp(127.0.0.1:3307)/test") e.GET("/insert", insertHandler) e.GET("/delete", deleteHandler) e.GET("/update", updateHandler) e.GET("/query", queryHandler) e.GET("/queryRows", queryRowsHandler) fmt.Println() e.Run() } //测试连接 func testDB() { dsn := "root:1234@tcp(127.0.0.1:3307)/test" db, err := sql.Open("mysql", dsn) if err != nil { fmt.Println(err) } errA := db.Ping() if errA != nil { fmt.Println(errA) } fmt.Println("连接成功!~") } //封装方法 var dbs *sql.DB //数据库连接池 func initDB(str string) (err error) { dbs, err = sql.Open("mysql", str) if err != nil { fmt.Println(err) return err } //defer dbs.Close() //最大连接数 dbs.SetMaxOpenConns(30) //最大空闲数 dbs.SetMaxIdleConns(2) errA := dbs.Ping() if errA != nil { fmt.Println(errA) } fmt.Println("连接成功!~") return nil } type Course struct { Cid   int Cname string Tid   int } //新增 func insert(db *sql.DB, course Course) { db.Begin() stmt, err := db.Prepare("insert into course (cname,tid) values (?,?)") if err != nil { log.Println(err) } _, errA := stmt.Exec(course.Cname, course.Tid) if errA != nil { log.Println(errA) } stmt.Close() //defer db.Close() } func delete(db *sql.DB, cid int) { db.Begin() stmt, err := db.Prepare("delete from course where cid=?") if err != nil { log.Println(err) } _, errA := stmt.Exec(cid) if errA != nil { log.Println(errA) } stmt.Close() //defer db.Close() } func update(db *sql.DB, course Course) { db.Begin() stmt, err := db.Prepare("update course set cname=?,tid=? where cid=?") if err != nil { log.Println(err) } _, errA := stmt.Exec(course.Cname, course.Tid, course.Cid) if errA != nil { log.Println(errA) } stmt.Close() //defer db.Close() } func query(db *sql.DB, cid int) (course Course) { var tid int var cname string     //要注意scan的先后 rowObj := db.QueryRow("select tid,cname from course where cid=?", cid).Scan(&tid, &cname) if rowObj != nil { log.Println(rowObj) } else { fmt.Printf("查询到Course :名称%v, tid%v \n", cname, tid) } course = Course{cid, cname, tid} return course } func queryRows(db *sql.DB, cid int) []Course { rowsObj, err := db.Query("select cid,cname,tid from course where cid>?", cid) if err != nil { log.Println(err) }     //注意关闭 defer rowsObj.Close() var courses []Course for rowsObj.Next() { var course Course err := rowsObj.Scan(&course.Cid, &course.Cname, &course.Tid) if err != nil { log.Println(err) } else { fmt.Println(course.Cid, course.Cname, course.Tid) courses = append(courses, course) } } return courses }


GORM集成:

不用再与复杂的sql语句打交道,像平时操作对象一样操作即可

https://jasperxu.com/Programming/Golang/GORM/

go get -u github.com/jinzhu/gorm package main import ( "fmt" _ "github.com/go-sql-driver/mysql" "github.com/jinzhu/gorm" ) type Product struct { gorm.Model Code  string Price uint } func main() { //连接数据库 db, err := gorm.Open("mysql", "root:1234@tcp(localhost:3307)/test?charset=utf8&parseTime=True&loc=Local") if err != nil { panic("连接数据库失败") } defer db.Close() // 自动迁移模式 //仅仅会创建表(无则新建,有则是否需要改),缺少列和索引,并且不会改变现有列的类型或删除未使用的列以保护数据。 db.AutoMigrate(&Product{}) // 创建 //db.Create(&Product{Code: "L1212", Price: 1000}) // 读取 var product Product //db.First(&product, 1)                   // 查询id为1的product db.First(&product, "price = ?", 1000) // 查询code为l1212的product //如果查询price = 2000 会没有值 因为2000已的被删除 //db.Where("price = ? and id =?", 1000, 1).Find(&product) fmt.Println(product) // 更新 - 更新product的price为2000 //db.Model(&product).Update("Price", 2000) // 删除 - 删除product //db.Delete(&product) }



我这里用的 docker 环境 。当然你也可以本地 或者虚拟机

systemctl start docker

docker images

docker ps -a

docker start 7ee6703a5a58

docker ps

docker exec -it redis01 bash

redis-cli 【-h 127.0.0.1 -p 6379】

参考官网:

官网介绍:https://redis.io/topics/introduction

中文官网:http://www.redis.cn

命令参看:

http://redisdoc.com/index.html

http://doc.redisfans.com/


go get -u github.com/garyburd/redigo/redis


package main import ( "fmt" "github.com/garyburd/redigo/redis" ) func main() { conn, err := redis.Dial("tcp", "192.168.142.131:6379") if err != nil { panic(err) } fmt.Println(conn) }


查询:

func main() { conn, err := redis.Dial("tcp", "192.168.142.131:6379") defer conn.Close() conn.Do("Auth", "1234") //有密码的情况 if err != nil { panic(err) } r, err := redis.Int(conn.Do("Get", "key1")) if err != nil { panic(err) }           fmt.Println(r) } 其他类型 conn.Do("HSet", "u", "name", "zhang") r1, err1 := redis.String(conn.Do("HGet", "u", "name")) if err1 != nil { panic(err1) } fmt.Println(r1)



Gin日志中间件默认 Logger插件:

作用:操作审计  问题定位   追踪程序过程/数据变化   统计和性能分析     数据采集分析二次挖掘

关键方法 Logger() 记录日志

LoggerWithConfig/Formatter/Writer

package main import ( "fmt" "io" "os" "github.com/gin-gonic/gin" ) func main() { e := gin.Default() f, _ := os.Create("gin.log")                     //创建日志文件 gin.DefaultWriter = io.MultiWriter(f)            //赋值 DefultWriter写入文件 //gin.DefaultWriter = io.MultiWriter(f, os.Stdout) //写文件同时在控制台打印 fmt.Println("") e.Run() }


自定义日志中间件Logrus

拓展安装:

go get -u github.com/lestrrat-go/file-rotatelogs go get -u github.com/rifflock/lfshook go get -u github.com/sirupsen/logrus package main import ( "fmt" "net/http" "os" "path" "time" "github.com/gin-gonic/gin" rotatelogs "github.com/lestrrat-go/file-rotatelogs" "github.com/rifflock/lfshook" "github.com/sirupsen/logrus" ) var ( logFilePath = "./" logFileName = "system" ) func logerMiddleware() gin.HandlerFunc { // 日志文件 fileName := path.Join(logFilePath, logFileName) // 写入文件 src, err := os.OpenFile(fileName, os.O_APPEND|os.O_WRONLY, os.ModeAppend) if err != nil { fmt.Println("err", err) } // 实例化 logger := logrus.New() //设置日志级别 logger.SetLevel(logrus.DebugLevel) //设置输出 logger.Out = src // 设置 rotatelogs logWriter, err := rotatelogs.New( // 分割后的文件名称 fileName+".%Y%m%d.log", // 生成软链,指向最新日志文件 rotatelogs.WithLinkName(fileName), // 设置最大保存时间(7天) rotatelogs.WithMaxAge(7*24*time.Hour), // 设置日志切割时间间隔(1天) rotatelogs.WithRotationTime(24*time.Hour), ) writeMap := lfshook.WriterMap{ logrus.InfoLevel:  logWriter, logrus.FatalLevel: logWriter, logrus.DebugLevel: logWriter, logrus.WarnLevel:  logWriter, logrus.ErrorLevel: logWriter, logrus.PanicLevel: logWriter, } logger.AddHook(lfshook.NewHook(writeMap, &logrus.JSONFormatter{ TimestampFormat: "2006-01-02 15:04:05", })) return func(c *gin.Context) { //开始时间 startTime := time.Now() //处理请求 c.Next() //结束时间 endTime := time.Now() // 执行时间 latencyTime := endTime.Sub(startTime) //请求方式 reqMethod := c.Request.Method //请求路由 reqUrl := c.Request.RequestURI //状态码 statusCode := c.Writer.Status() //请求ip clientIP := c.ClientIP() // 日志格式 logger.WithFields(logrus.Fields{ "status_code":  statusCode, "latency_time": latencyTime, "client_ip":    clientIP, "req_method":   reqMethod, "req_uri":      reqUrl, }).Info() } } func main() { app := gin.Default() app.Use(logerMiddleware()) app.GET("/", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "message": "ok", }) }) app.Run(":8081") }



Gin框架原理分析及源码解读

思考: 1.request 请求怎么gin   net/http 关联之间的流转

2.gin的 路由设计 /算法

3.中间件  


流程图: 重点 https://cloud.fynote.com/share/d/7633

1.路由设计   httprouter 实现  路由模块。  routerGroup的 Handlers 存储了所有中间件

2. 高性能的Trees   基于 Radix Tree基数树   key就是URL 的字符串  ,value对应的 []HandlerFunc


源码解读:

 r := gin.Default()  

func Default() *Engine { debugPrintWARNINGDefault() //s1 实际调用 engine := New() //默认中间件 engine.Use(Logger(), Recovery()) return engine } 进入 New() func New() *Engine { debugPrintWARNINGNew() engine := &Engine{ //s1 关键1 :实例化RouterGroup,其中Handlers为中间件 数据 RouterGroup: RouterGroup{ Handlers: nil, basePath: "/", root:     true, }, FuncMap:                template.FuncMap{}, RedirectTrailingSlash:  true, RedirectFixedPath:      false, HandleMethodNotAllowed: false, ForwardedByClientIP:    true, RemoteIPHeaders:        []string{"X-Forwarded-For", "X-Real-IP"}, TrustedPlatform:        defaultPlatform, UseRawPath:             false, RemoveExtraSlash:       false, UnescapePathValues:     true, MaxMultipartMemory:     defaultMultipartMemory, //关键2 :trees 负责存储路由和handle方法的映射,采用类似字典树的结构 //s1 gin的高性能主要依靠trees trees:            make(methodTrees, 0, 9), delims:           render.Delims{Left: "{{", Right: "}}"}, secureJSONPrefix: "while(1);", trustedProxies:   []string{"0.0.0.0/0"}, trustedCIDRs:     defaultTrustedCIDRs, } engine.RouterGroup.engine = engine //这里采用 sync/pool 实现context池,减少频繁context实例化带来的资源消耗 engine.pool.New = func() interface{} { return engine.allocateContext() } return engine } //路由组 type RouterGroup struct { Handlers HandlersChain  //实际存 []HandlerFunc basePath string engine   *Engine //引擎 root     bool } // 引擎 最重要的在于路由 type Engine struct { //s1 路由组 代码逻辑分离 + 分组的设计 RouterGroup      //重定向固定路径 RedirectFixedPath bool     //关键 性能提升点  负责存储路由和handle方法的映射,采用类似字典树的结构     trees            methodTrees      。。。。。 } type methodTree struct { //key是 字符串类型的method相当于路由key method string //s1 value则是一个 *node ,里面存储的就是按顺序执行的中间件和handle控制器方法([]HandlerFunc),这里很重要 root *node } // 关键 路由tree 节点 type node struct { // 保存这个节点上的URL路径 // 例如所画流程图中的shen和sheng, 共同的parent节点的path="s" "h" "e" // 后面两个节点的path分别是"n"和"ng" path string // 和children[]对应, 保存的是分裂的分支的第一个字符 // 例如search和support, 那么s节点的indices对应的"eu" // 代表有两个分支, 分支的首字母分别是e和u indices string // 判断当前节点路径是不是参数节点, 例如上图的:post部分就是wildChild节点 wildChild bool // 节点类型包括static, root, param, catchAll // static: 静态节点, 例如上面分裂出来作为parent的s // root: 如果插入的节点是第一个, 那么是root节点 // catchAll: 有*匹配的节点 // param: 除上面外的节点 nType    nodeType priority uint32 // 保存孩子节点 children []*node // child nodes, at most 1 :param style node at the end of the array // 当前节点的处理函数 handlers HandlersChain //关键在于此[]HandlerFunc fullPath string }


看看 engine.Group()   路由分组


//初始化路由组 返回一个 新生成的RouterGroup指针,用来分开每个路由组加载不一样的中间件 // 返回一个 RouterGroup指针 func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup { return &RouterGroup{ //s1 cope一份全局中间件到新生成的RouterGroup.Handlers中, //接下来路由注册的时候就可以一起写入树节点中 Handlers: group.combineHandlers(handlers), //joinPaths basePath: group.calculateAbsolutePath(relativePath), engine:   group.engine, } } //组合处理器s func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain { finalSize := len(group.Handlers) + len(handlers) if finalSize >= int(abortIndex) { panic("too many handlers") } mergedHandlers := make(HandlersChain, finalSize) //从两个copy的顺序可以看出,group的handler高于自定义的handler。这里自定义的handler可以是多个, /*比如细节可面题: router.GET("/shenzhuan", nice(), func(c *gin.Context) {...}  两个handler处理逻辑? func (engine *Engine)handleHTTPRequest(c *Context)中 每个请求进来,匹配好路由之后,会获取这个路由最终combine的handlers,把它放在全局的context中, 然后通过调用 context.Next()来进行递归调用这个handlers。 当然在中间件里面需要记得调用context.Next() 把控制权还给Context */ copy(mergedHandlers, group.Handlers) copy(mergedHandlers[len(group.Handlers):], handlers) return mergedHandlers }

不论什么请求 都会

//不管哪种请求方式最终都会调用RouterGroup.handle,这个方法主要有三个作用 func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes { //1 处理路由的格式,将路由拼成 ‘/’ 字符开头的路由 absolutePath := group.calculateAbsolutePath(relativePath) //2 复制一份RouterGroup.Handlers,加上相应这次路由的handle方法,组成一个list放入树节点中 handlers = group.combineHandlers(handlers) //3 s1 最后调用trees.addRoute增加节点 group.engine.addRoute(httpMethod, absolutePath, handlers) return group.returnObj() }


简单易懂 。恰恰是 它的有点 。 高效核心 数据结构  算法   才是核心。