目前 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
关系:default 内部也是调用的 New 但是会默认加上 Logger 和 Recovery中间件
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
}
关键方法
c.Query("key") (.var 会自动填充前面)
c.DefaultQuery("key","xxoo") 没有key 会给一个默认值
关键方法 测试选择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)
...
}
测试选择 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,
})
}
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>
是目前比较成熟的一套互联网应用程序的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
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")
像百度搜索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传递多个值逗号隔开即可。
地址: 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.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)
}
比如 前后 /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是服务器往客户端写的一些数据,可以实现自动登录等功能
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") }
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 }
不用再与复杂的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)
作用:操作审计 问题定位 追踪程序过程/数据变化 统计和性能分析 数据采集分析二次挖掘
关键方法 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() }
拓展安装:
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") }
思考: 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() }
简单易懂 。恰恰是 它的有点 。 高效核心 数据结构 算法 才是核心。