当前位置: 首页 > news >正文

Go 微服务框架 | 中间件

文章目录

    • 定义中间件
    • 前置中间件
    • 后置中间件
    • 路由级别中间件

定义中间件

  • 中间件的作用是给应用添加一些额外的功能,但是不会影响原有应用的编码方式,想用的时候直接添加,不想用的时候也可以轻松去除,实现所谓的可插拔。
  • 中间件的实现位置在哪里?
    • 不能耦合在用户的代码中
    • 需要独立存在,但是又能拿到上下文并作出影响
    • 位置:在处理器的前后
  • 注意:中间件是一个调用链条,所以在处理真正的业务之前可能会经过多个中间件。
// 定义中间件
type MiddlewareFunc func(handleFunc HandleFunc) HandleFunc

前置中间件

// zj.gopackage zjgoimport ("fmt""log""net/http"
)const ANY = "ANY"// 定义处理响应函数
type HandleFunc func(ctx *Context)// 定义中间件
type MiddlewareFunc func(handleFunc HandleFunc) HandleFunc// 抽象出路由的概念
type routerGroup struct {name             string                           // 组名handleFuncMap    map[string]map[string]HandleFunc // 映射关系应该由每个路由组去维护handlerMethodMap map[string][]string              // 记录GET,POST等请求方式所记录的路由,实现对同一路由不同请求方式的支持TreeNode         *TreeNode                        // 记录该路由组下的路由前缀树preMiddlewares   []MiddlewareFunc                 // 定义前置中间件postMiddlewares  []MiddlewareFunc                 // 定义后置中间件
}// 增加前置中间件
func (routerGroup *routerGroup) PreHandle(middlewareFunc ...MiddlewareFunc) { // 在Go语言中,函数参数中的三个点...表示可变参数(variadic parameter),允许函数接受不定数量的同类型参数。routerGroup.preMiddlewares = append(routerGroup.preMiddlewares, middlewareFunc...)
}// 增加后置中间件
func (routerGroup *routerGroup) PostHandle(middlewareFunc ...MiddlewareFunc) {routerGroup.postMiddlewares = append(routerGroup.postMiddlewares, middlewareFunc...)
}func (routerGroup *routerGroup) methodHandle(handle HandleFunc, ctx *Context) {// 执行前置中间件if routerGroup.preMiddlewares != nil {for _, middlewareFunc := range routerGroup.preMiddlewares {handle = middlewareFunc(handle)}}handle(ctx)// 执行后置中间件}// 定义路由结构体
type router struct {routerGroups []*routerGroup // 路由下面应该维护着不同的组
}// 添加路由组
func (r *router) Group(name string) *routerGroup {routerGroup := &routerGroup{name:             name,handleFuncMap:    make(map[string]map[string]HandleFunc),handlerMethodMap: make(map[string][]string),TreeNode:         &TreeNode{name: "/", children: make([]*TreeNode, 0)},}r.routerGroups = append(r.routerGroups, routerGroup)return routerGroup
}// 给路由结构体添加一个添加路由功能的函数
// func (routerGroup *routerGroup) Add(name string, handleFunc HandleFunc) {
// 	routerGroup.handleFuncMap[name] = handleFunc
// }// 由于 ANY POST GET 都需要重复相同的逻辑代码,所以做一个提取操作
func (routerGroup *routerGroup) handleRequest(name string, method string, handleFunc HandleFunc) {if _, exist := routerGroup.handleFuncMap[name]; !exist {routerGroup.handleFuncMap[name] = make(map[string]HandleFunc)}if _, exist := routerGroup.handleFuncMap[name][method]; !exist {routerGroup.handleFuncMap[name][method] = handleFuncrouterGroup.handlerMethodMap[method] = append(routerGroup.handlerMethodMap[method], name)} else {panic("Under the same route, duplication is not allowed!!!")}routerGroup.TreeNode.Put(name)
}// Any代表支持任意的请求方式
func (routerGroup *routerGroup) Any(name string, handleFunc HandleFunc) {routerGroup.handleRequest(name, ANY, handleFunc)
}// POST代表支持POST请求方式
func (routerGroup *routerGroup) Post(name string, handleFunc HandleFunc) {routerGroup.handleRequest(name, http.MethodPost, handleFunc)
}// GET代表支持GET请求方式
func (routerGroup *routerGroup) Get(name string, handleFunc HandleFunc) {routerGroup.handleRequest(name, http.MethodGet, handleFunc)
}// DELETE代表支持DELETE请求方式
func (routerGroup *routerGroup) Delete(name string, handleFunc HandleFunc) {routerGroup.handleRequest(name, http.MethodDelete, handleFunc)
}// PUT代表支持PUT请求方式
func (routerGroup *routerGroup) Put(name string, handleFunc HandleFunc) {routerGroup.handleRequest(name, http.MethodPut, handleFunc)
}// PATCH代表支持PATCH请求方式
func (routerGroup *routerGroup) Patch(name string, handleFunc HandleFunc) {routerGroup.handleRequest(name, http.MethodPatch, handleFunc)
}// 只要实现 ServeHTTP 这个方法,就相当于实现了对应的 HTTP 处理器
// 结构体 Engine 实现了 ServeHTTP(w http.ResponseWriter, r *http.Request) 方法
// 所以它就自动实现了 http.Handler 接口,因此可以直接被用于 http.ListenAndServe
// Engine 实现了 ServeHTTP,它就是一个合法的 http.Handler,可以用 http.Handle 来绑定它到某个具体的路由路径上!
func (e *Engine) ServeHTTP(w http.ResponseWriter, r *http.Request) {e.httpRequestHandle(w, r)
}func (e *Engine) httpRequestHandle(w http.ResponseWriter, r *http.Request) {// 在 Go 的 net/http 包中,r *http.Request 代表了客户端发来的 HTTP 请求对象。// 可以通过 r.Method 来获取这次请求使用的是什么方法(Method),例如:GET、POST、PUT、DELETE 等。if r.Method == http.MethodGet {fmt.Fprintf(w, "这是一个 GET 请求!!! ")} else if r.Method == http.MethodPost {fmt.Fprintf(w, "这是一个 POST 请求!!! ")} else {fmt.Fprintf(w, "这是一个其他类型的请求:%s!!! ", r.Method)}for _, group := range e.routerGroups {routerName := SubStringLast(r.RequestURI, "/"+group.name)if node := group.TreeNode.Get(routerName); node != nil && node.isEnd {// 路由匹配上了ctx := &Context{W: w, R: r}// 先判断当前请求路由是否支持任意请求方式的Anyif handle, exist := group.handleFuncMap[node.routerName][ANY]; exist {group.methodHandle(handle, ctx)return}// 不支持 Any,去该请求所对应的请求方式对应的 Map 里面去找是否有对应的路由if handle, exist := group.handleFuncMap[node.routerName][r.Method]; exist {group.methodHandle(handle, ctx)return}// 没找到对应的路由,说明该请求方式不允许w.WriteHeader(http.StatusMethodNotAllowed)fmt.Fprintf(w, "%s %s not allowed!!!\n", r.Method, r.RequestURI)return}}w.WriteHeader(http.StatusNotFound)fmt.Fprintf(w, "%s %s not found!!!\n", r.Method, r.RequestURI)return
}// 定义一个引擎结构体
type Engine struct {router
}// 引擎结构体的初始化方法
func New() *Engine {return &Engine{router: router{},}
}// 引擎的启动方法
func (e *Engine) Run() {// for _, group := range e.routerGroups {// 	for name, value := range group.handleFuncMap {// 		http.HandleFunc("/"+group.name+name, value)// 	}// }// 把 e 这个http处理器绑定到对应路由下http.Handle("/", e)err := http.ListenAndServe(":3986", nil)if err != nil {log.Fatal(err)}
}
// main.gopackage mainimport ("fmt""net/http""github.com/ErizJ/ZJGo/zjgo"
)func main() {fmt.Println("Hello World!")// // 注册 HTTP 路由 /hello// http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {// 	fmt.Fprintf(w, "Hello Go!")// })// // 启动 HTTP 服务器// err := http.ListenAndServe("8111", nil)// if err != nil {// 	log.Fatal(err)// }engine := zjgo.New()g1 := engine.Group("user")g1.PreHandle(func(handleFunc zjgo.HandleFunc) zjgo.HandleFunc {return func(ctx *zjgo.Context) {fmt.Println("Pre Middleware ON!!!")handleFunc(ctx)}})g1.Get("/hello", func(ctx *zjgo.Context) {fmt.Fprintf(ctx.W, http.MethodGet+" Hello Go!——user/hello")})// 浏览器地址栏输入的都是 GET 请求// 需要用 curl 或 Postman 来发一个真正的 POST 请求,才会命中 Post handlerg1.Post("/info", func(ctx *zjgo.Context) {fmt.Println("HandleFunc ON!!!")fmt.Fprintf(ctx.W, http.MethodPost+" Hello Go!——user/info——POST")})g1.Get("/info", func(ctx *zjgo.Context) {fmt.Println("HandleFunc ON!!!")fmt.Fprintf(ctx.W, http.MethodGet+" Hello Go!——user/info——GET")})g1.Get("/get/:id", func(ctx *zjgo.Context) {fmt.Println("HandleFunc ON!!!")fmt.Fprintf(ctx.W, http.MethodGet+" Hello Go!——user/get/:id——GET")})g1.Get("/isEnd/get", func(ctx *zjgo.Context) {fmt.Println("HandleFunc ON!!!")fmt.Fprintf(ctx.W, http.MethodGet+" Hello Go!——user/isEnd/get——GET")})// 只要路由匹配,就会执行对应的处理函数g1.Any("/any", func(ctx *zjgo.Context) {fmt.Fprintf(ctx.W, " Hello Go!——user/any")})// g2 := engine.Group("order")// g2.Add("/hello", func(w http.ResponseWriter, r *http.Request) {// 	fmt.Fprintf(w, "Hello Go!——order/hello")// })// g2.Add("/info", func(w http.ResponseWriter, r *http.Request) {// 	fmt.Fprintf(w, "Hello Go!——order/info")// })fmt.Println("Starting...")engine.Run()}

后置中间件

// zj.gopackage zjgoimport ("fmt""log""net/http"
)const ANY = "ANY"// 定义处理响应函数
type HandleFunc func(ctx *Context)// 定义中间件
type MiddlewareFunc func(handleFunc HandleFunc) HandleFunc// 抽象出路由的概念
type routerGroup struct {name             string                           // 组名handleFuncMap    map[string]map[string]HandleFunc // 映射关系应该由每个路由组去维护handlerMethodMap map[string][]string              // 记录GET,POST等请求方式所记录的路由,实现对同一路由不同请求方式的支持TreeNode         *TreeNode                        // 记录该路由组下的路由前缀树preMiddlewares   []MiddlewareFunc                 // 定义前置中间件postMiddlewares  []MiddlewareFunc                 // 定义后置中间件
}// 增加前置中间件
func (routerGroup *routerGroup) PreHandle(middlewareFunc ...MiddlewareFunc) { // 在Go语言中,函数参数中的三个点...表示可变参数(variadic parameter),允许函数接受不定数量的同类型参数。routerGroup.preMiddlewares = append(routerGroup.preMiddlewares, middlewareFunc...)
}// 增加后置中间件
func (routerGroup *routerGroup) PostHandle(middlewareFunc ...MiddlewareFunc) {routerGroup.postMiddlewares = append(routerGroup.postMiddlewares, middlewareFunc...)
}func (routerGroup *routerGroup) methodHandle(handle HandleFunc, ctx *Context) {// 执行前置中间件if routerGroup.preMiddlewares != nil {for _, middlewareFunc := range routerGroup.preMiddlewares {handle = middlewareFunc(handle) // 难以理解的话可以看作语句叠加}}handle(ctx)// 执行后置中间件if routerGroup.postMiddlewares != nil {for _, middlewareFunc := range routerGroup.postMiddlewares {handle = middlewareFunc(handle)}}handle(ctx)
}// 定义路由结构体
type router struct {routerGroups []*routerGroup // 路由下面应该维护着不同的组
}// 添加路由组
func (r *router) Group(name string) *routerGroup {routerGroup := &routerGroup{name:             name,handleFuncMap:    make(map[string]map[string]HandleFunc),handlerMethodMap: make(map[string][]string),TreeNode:         &TreeNode{name: "/", children: make([]*TreeNode, 0)},}r.routerGroups = append(r.routerGroups, routerGroup)return routerGroup
}// 给路由结构体添加一个添加路由功能的函数
// func (routerGroup *routerGroup) Add(name string, handleFunc HandleFunc) {
// 	routerGroup.handleFuncMap[name] = handleFunc
// }// 由于 ANY POST GET 都需要重复相同的逻辑代码,所以做一个提取操作
func (routerGroup *routerGroup) handleRequest(name string, method string, handleFunc HandleFunc) {if _, exist := routerGroup.handleFuncMap[name]; !exist {routerGroup.handleFuncMap[name] = make(map[string]HandleFunc)}if _, exist := routerGroup.handleFuncMap[name][method]; !exist {routerGroup.handleFuncMap[name][method] = handleFuncrouterGroup.handlerMethodMap[method] = append(routerGroup.handlerMethodMap[method], name)} else {panic("Under the same route, duplication is not allowed!!!")}routerGroup.TreeNode.Put(name)
}// Any代表支持任意的请求方式
func (routerGroup *routerGroup) Any(name string, handleFunc HandleFunc) {routerGroup.handleRequest(name, ANY, handleFunc)
}// POST代表支持POST请求方式
func (routerGroup *routerGroup) Post(name string, handleFunc HandleFunc) {routerGroup.handleRequest(name, http.MethodPost, handleFunc)
}// GET代表支持GET请求方式
func (routerGroup *routerGroup) Get(name string, handleFunc HandleFunc) {routerGroup.handleRequest(name, http.MethodGet, handleFunc)
}// DELETE代表支持DELETE请求方式
func (routerGroup *routerGroup) Delete(name string, handleFunc HandleFunc) {routerGroup.handleRequest(name, http.MethodDelete, handleFunc)
}// PUT代表支持PUT请求方式
func (routerGroup *routerGroup) Put(name string, handleFunc HandleFunc) {routerGroup.handleRequest(name, http.MethodPut, handleFunc)
}// PATCH代表支持PATCH请求方式
func (routerGroup *routerGroup) Patch(name string, handleFunc HandleFunc) {routerGroup.handleRequest(name, http.MethodPatch, handleFunc)
}// 只要实现 ServeHTTP 这个方法,就相当于实现了对应的 HTTP 处理器
// 结构体 Engine 实现了 ServeHTTP(w http.ResponseWriter, r *http.Request) 方法
// 所以它就自动实现了 http.Handler 接口,因此可以直接被用于 http.ListenAndServe
// Engine 实现了 ServeHTTP,它就是一个合法的 http.Handler,可以用 http.Handle 来绑定它到某个具体的路由路径上!
func (e *Engine) ServeHTTP(w http.ResponseWriter, r *http.Request) {e.httpRequestHandle(w, r)
}func (e *Engine) httpRequestHandle(w http.ResponseWriter, r *http.Request) {// 在 Go 的 net/http 包中,r *http.Request 代表了客户端发来的 HTTP 请求对象。// 可以通过 r.Method 来获取这次请求使用的是什么方法(Method),例如:GET、POST、PUT、DELETE 等。if r.Method == http.MethodGet {fmt.Fprintf(w, "这是一个 GET 请求!!! ")} else if r.Method == http.MethodPost {fmt.Fprintf(w, "这是一个 POST 请求!!! ")} else {fmt.Fprintf(w, "这是一个其他类型的请求:%s!!! ", r.Method)}for _, group := range e.routerGroups {routerName := SubStringLast(r.RequestURI, "/"+group.name)if node := group.TreeNode.Get(routerName); node != nil && node.isEnd {// 路由匹配上了ctx := &Context{W: w, R: r}// 先判断当前请求路由是否支持任意请求方式的Anyif handle, exist := group.handleFuncMap[node.routerName][ANY]; exist {group.methodHandle(handle, ctx)return}// 不支持 Any,去该请求所对应的请求方式对应的 Map 里面去找是否有对应的路由if handle, exist := group.handleFuncMap[node.routerName][r.Method]; exist {group.methodHandle(handle, ctx)return}// 没找到对应的路由,说明该请求方式不允许w.WriteHeader(http.StatusMethodNotAllowed)fmt.Fprintf(w, "%s %s not allowed!!!\n", r.Method, r.RequestURI)return}}w.WriteHeader(http.StatusNotFound)fmt.Fprintf(w, "%s %s not found!!!\n", r.Method, r.RequestURI)return
}// 定义一个引擎结构体
type Engine struct {router
}// 引擎结构体的初始化方法
func New() *Engine {return &Engine{router: router{},}
}// 引擎的启动方法
func (e *Engine) Run() {// for _, group := range e.routerGroups {// 	for name, value := range group.handleFuncMap {// 		http.HandleFunc("/"+group.name+name, value)// 	}// }// 把 e 这个http处理器绑定到对应路由下http.Handle("/", e)err := http.ListenAndServe(":3986", nil)if err != nil {log.Fatal(err)}
}
// main.gopackage mainimport ("fmt""net/http""github.com/ErizJ/ZJGo/zjgo"
)func main() {fmt.Println("Hello World!")// // 注册 HTTP 路由 /hello// http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {// 	fmt.Fprintf(w, "Hello Go!")// })// // 启动 HTTP 服务器// err := http.ListenAndServe("8111", nil)// if err != nil {// 	log.Fatal(err)// }engine := zjgo.New()g1 := engine.Group("user")g1.PreHandle(func(handleFunc zjgo.HandleFunc) zjgo.HandleFunc {return func(ctx *zjgo.Context) {fmt.Println("Pre Middleware ON!!!")handleFunc(ctx)}})g1.PostHandle(func(handleFunc zjgo.HandleFunc) zjgo.HandleFunc {return func(ctx *zjgo.Context) {fmt.Println("Post Middleware ON!!!")}})g1.Get("/hello", func(ctx *zjgo.Context) {fmt.Fprintf(ctx.W, http.MethodGet+" Hello Go!——user/hello")})// 浏览器地址栏输入的都是 GET 请求// 需要用 curl 或 Postman 来发一个真正的 POST 请求,才会命中 Post handlerg1.Post("/info", func(ctx *zjgo.Context) {fmt.Println("HandleFunc ON!!!")fmt.Fprintf(ctx.W, http.MethodPost+" Hello Go!——user/info——POST")})g1.Get("/info", func(ctx *zjgo.Context) {fmt.Println("HandleFunc ON!!!")fmt.Fprintf(ctx.W, http.MethodGet+" Hello Go!——user/info——GET")})g1.Get("/get/:id", func(ctx *zjgo.Context) {fmt.Println("HandleFunc ON!!!")fmt.Fprintf(ctx.W, http.MethodGet+" Hello Go!——user/get/:id——GET")})g1.Get("/isEnd/get", func(ctx *zjgo.Context) {fmt.Println("HandleFunc ON!!!")fmt.Fprintf(ctx.W, http.MethodGet+" Hello Go!——user/isEnd/get——GET")})// 只要路由匹配,就会执行对应的处理函数g1.Any("/any", func(ctx *zjgo.Context) {fmt.Fprintf(ctx.W, " Hello Go!——user/any")})// g2 := engine.Group("order")// g2.Add("/hello", func(w http.ResponseWriter, r *http.Request) {// 	fmt.Fprintf(w, "Hello Go!——order/hello")// })// g2.Add("/info", func(w http.ResponseWriter, r *http.Request) {// 	fmt.Fprintf(w, "Hello Go!——order/info")// })fmt.Println("Starting...")engine.Run()}
  • 但是这里前置和后置中间件似乎有点多余了,因为在前置中间件中,执行完一系列前置中间件和主体业务函数后,就可以执行后置中间件了,不用其他冗余代码。
// zj.gopackage zjgoimport ("fmt""log""net/http"
)const ANY = "ANY"// 定义处理响应函数
type HandleFunc func(ctx *Context)// 定义中间件
type MiddlewareFunc func(handleFunc HandleFunc) HandleFunc// 抽象出路由的概念
type routerGroup struct {name             string                           // 组名handleFuncMap    map[string]map[string]HandleFunc // 映射关系应该由每个路由组去维护handlerMethodMap map[string][]string              // 记录GET,POST等请求方式所记录的路由,实现对同一路由不同请求方式的支持TreeNode         *TreeNode                        // 记录该路由组下的路由前缀树middlewares      []MiddlewareFunc                 // 定义中间件
}// 增加中间件
func (routerGroup *routerGroup) UseMiddleware(middlewareFunc ...MiddlewareFunc) { // 在Go语言中,函数参数中的三个点...表示可变参数(variadic parameter),允许函数接受不定数量的同类型参数。routerGroup.middlewares = append(routerGroup.middlewares, middlewareFunc...)
}func (routerGroup *routerGroup) methodHandle(handle HandleFunc, ctx *Context) {// 执行通用中间件,任何路由组都可以执行该方法if routerGroup.middlewares != nil {for _, middlewareFunc := range routerGroup.middlewares {handle = middlewareFunc(handle) // 难以理解的话可以看作语句叠加}}handle(ctx)
}// 定义路由结构体
type router struct {routerGroups []*routerGroup // 路由下面应该维护着不同的组
}// 添加路由组
func (r *router) Group(name string) *routerGroup {routerGroup := &routerGroup{name:             name,handleFuncMap:    make(map[string]map[string]HandleFunc),handlerMethodMap: make(map[string][]string),TreeNode:         &TreeNode{name: "/", children: make([]*TreeNode, 0)},}r.routerGroups = append(r.routerGroups, routerGroup)return routerGroup
}// 给路由结构体添加一个添加路由功能的函数
// func (routerGroup *routerGroup) Add(name string, handleFunc HandleFunc) {
// 	routerGroup.handleFuncMap[name] = handleFunc
// }// 由于 ANY POST GET 都需要重复相同的逻辑代码,所以做一个提取操作
func (routerGroup *routerGroup) handleRequest(name string, method string, handleFunc HandleFunc) {if _, exist := routerGroup.handleFuncMap[name]; !exist {routerGroup.handleFuncMap[name] = make(map[string]HandleFunc)}if _, exist := routerGroup.handleFuncMap[name][method]; !exist {routerGroup.handleFuncMap[name][method] = handleFuncrouterGroup.handlerMethodMap[method] = append(routerGroup.handlerMethodMap[method], name)} else {panic("Under the same route, duplication is not allowed!!!")}routerGroup.TreeNode.Put(name)
}// Any代表支持任意的请求方式
func (routerGroup *routerGroup) Any(name string, handleFunc HandleFunc) {routerGroup.handleRequest(name, ANY, handleFunc)
}// POST代表支持POST请求方式
func (routerGroup *routerGroup) Post(name string, handleFunc HandleFunc) {routerGroup.handleRequest(name, http.MethodPost, handleFunc)
}// GET代表支持GET请求方式
func (routerGroup *routerGroup) Get(name string, handleFunc HandleFunc) {routerGroup.handleRequest(name, http.MethodGet, handleFunc)
}// DELETE代表支持DELETE请求方式
func (routerGroup *routerGroup) Delete(name string, handleFunc HandleFunc) {routerGroup.handleRequest(name, http.MethodDelete, handleFunc)
}// PUT代表支持PUT请求方式
func (routerGroup *routerGroup) Put(name string, handleFunc HandleFunc) {routerGroup.handleRequest(name, http.MethodPut, handleFunc)
}// PATCH代表支持PATCH请求方式
func (routerGroup *routerGroup) Patch(name string, handleFunc HandleFunc) {routerGroup.handleRequest(name, http.MethodPatch, handleFunc)
}// 只要实现 ServeHTTP 这个方法,就相当于实现了对应的 HTTP 处理器
// 结构体 Engine 实现了 ServeHTTP(w http.ResponseWriter, r *http.Request) 方法
// 所以它就自动实现了 http.Handler 接口,因此可以直接被用于 http.ListenAndServe
// Engine 实现了 ServeHTTP,它就是一个合法的 http.Handler,可以用 http.Handle 来绑定它到某个具体的路由路径上!
func (e *Engine) ServeHTTP(w http.ResponseWriter, r *http.Request) {e.httpRequestHandle(w, r)
}func (e *Engine) httpRequestHandle(w http.ResponseWriter, r *http.Request) {// 在 Go 的 net/http 包中,r *http.Request 代表了客户端发来的 HTTP 请求对象。// 可以通过 r.Method 来获取这次请求使用的是什么方法(Method),例如:GET、POST、PUT、DELETE 等。if r.Method == http.MethodGet {fmt.Fprintf(w, "这是一个 GET 请求!!! ")} else if r.Method == http.MethodPost {fmt.Fprintf(w, "这是一个 POST 请求!!! ")} else {fmt.Fprintf(w, "这是一个其他类型的请求:%s!!! ", r.Method)}for _, group := range e.routerGroups {routerName := SubStringLast(r.RequestURI, "/"+group.name)if node := group.TreeNode.Get(routerName); node != nil && node.isEnd {// 路由匹配上了ctx := &Context{W: w, R: r}// 先判断当前请求路由是否支持任意请求方式的Anyif handle, exist := group.handleFuncMap[node.routerName][ANY]; exist {group.methodHandle(handle, ctx)return}// 不支持 Any,去该请求所对应的请求方式对应的 Map 里面去找是否有对应的路由if handle, exist := group.handleFuncMap[node.routerName][r.Method]; exist {group.methodHandle(handle, ctx)return}// 没找到对应的路由,说明该请求方式不允许w.WriteHeader(http.StatusMethodNotAllowed)fmt.Fprintf(w, "%s %s not allowed!!!\n", r.Method, r.RequestURI)return}}w.WriteHeader(http.StatusNotFound)fmt.Fprintf(w, "%s %s not found!!!\n", r.Method, r.RequestURI)return
}// 定义一个引擎结构体
type Engine struct {router
}// 引擎结构体的初始化方法
func New() *Engine {return &Engine{router: router{},}
}// 引擎的启动方法
func (e *Engine) Run() {// for _, group := range e.routerGroups {// 	for name, value := range group.handleFuncMap {// 		http.HandleFunc("/"+group.name+name, value)// 	}// }// 把 e 这个http处理器绑定到对应路由下http.Handle("/", e)err := http.ListenAndServe(":3986", nil)if err != nil {log.Fatal(err)}
}
// main.gopackage mainimport ("fmt""net/http""github.com/ErizJ/ZJGo/zjgo"
)func main() {fmt.Println("Hello World!")// // 注册 HTTP 路由 /hello// http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {// 	fmt.Fprintf(w, "Hello Go!")// })// // 启动 HTTP 服务器// err := http.ListenAndServe("8111", nil)// if err != nil {// 	log.Fatal(err)// }engine := zjgo.New()g1 := engine.Group("user")g1.UseMiddleware(func(handleFunc zjgo.HandleFunc) zjgo.HandleFunc {return func(ctx *zjgo.Context) {fmt.Println("Pre Middleware ON!!!")handleFunc(ctx)fmt.Println("Post Middleware ON!!!")}})g1.Get("/hello", func(ctx *zjgo.Context) {fmt.Fprintf(ctx.W, http.MethodGet+" Hello Go!——user/hello")})// 浏览器地址栏输入的都是 GET 请求// 需要用 curl 或 Postman 来发一个真正的 POST 请求,才会命中 Post handlerg1.Post("/info", func(ctx *zjgo.Context) {fmt.Println("HandleFunc ON!!!")fmt.Fprintf(ctx.W, http.MethodPost+" Hello Go!——user/info——POST")})g1.Get("/info", func(ctx *zjgo.Context) {fmt.Println("HandleFunc ON!!!")fmt.Fprintf(ctx.W, http.MethodGet+" Hello Go!——user/info——GET")})g1.Get("/get/:id", func(ctx *zjgo.Context) {fmt.Println("HandleFunc ON!!!")fmt.Fprintf(ctx.W, http.MethodGet+" Hello Go!——user/get/:id——GET")})g1.Get("/isEnd/get", func(ctx *zjgo.Context) {fmt.Println("HandleFunc ON!!!")fmt.Fprintf(ctx.W, http.MethodGet+" Hello Go!——user/isEnd/get——GET")})// 只要路由匹配,就会执行对应的处理函数g1.Any("/any", func(ctx *zjgo.Context) {fmt.Fprintf(ctx.W, " Hello Go!——user/any")})// g2 := engine.Group("order")// g2.Add("/hello", func(w http.ResponseWriter, r *http.Request) {// 	fmt.Fprintf(w, "Hello Go!——order/hello")// })// g2.Add("/info", func(w http.ResponseWriter, r *http.Request) {// 	fmt.Fprintf(w, "Hello Go!——order/info")// })fmt.Println("Starting...")engine.Run()}

路由级别中间件

// zj.gopackage zjgoimport ("fmt""log""net/http"
)const ANY = "ANY"// 定义处理响应函数
type HandleFunc func(ctx *Context)// 定义中间件
type MiddlewareFunc func(handleFunc HandleFunc) HandleFunc// 抽象出路由的概念
type routerGroup struct {name               string                                 // 组名handleFuncMap      map[string]map[string]HandleFunc       // 映射关系应该由每个路由组去维护handlerMethodMap   map[string][]string                    // 记录GET,POST等请求方式所记录的路由,实现对同一路由不同请求方式的支持middlewaresFuncMap map[string]map[string][]MiddlewareFunc // 定义路由级别中间件TreeNode           *TreeNode                              // 记录该路由组下的路由前缀树middlewares        []MiddlewareFunc                       // 定义通用中间件
}// 增加中间件
func (routerGroup *routerGroup) UseMiddleware(middlewareFunc ...MiddlewareFunc) { // 在Go语言中,函数参数中的三个点...表示可变参数(variadic parameter),允许函数接受不定数量的同类型参数。routerGroup.middlewares = append(routerGroup.middlewares, middlewareFunc...)
}func (routerGroup *routerGroup) methodHandle(name string, method string, handle HandleFunc, ctx *Context) {// 执行组通用中间件,任何路由组都可以执行该方法if routerGroup.middlewares != nil {for _, middlewareFunc := range routerGroup.middlewares {handle = middlewareFunc(handle) // 难以理解的话可以看作语句叠加}}// 路由级别组中间件if _, exist := routerGroup.middlewaresFuncMap[name][method]; exist {for _, middlewareFunc := range routerGroup.middlewaresFuncMap[name][method] {handle = middlewareFunc(handle)}}handle(ctx)
}// 定义路由结构体
type router struct {routerGroups []*routerGroup // 路由下面应该维护着不同的组
}// 添加路由组
func (r *router) Group(name string) *routerGroup {routerGroup := &routerGroup{name:               name,handleFuncMap:      make(map[string]map[string]HandleFunc),handlerMethodMap:   make(map[string][]string),middlewaresFuncMap: make(map[string]map[string][]MiddlewareFunc, 0),TreeNode:           &TreeNode{name: "/", children: make([]*TreeNode, 0)},}r.routerGroups = append(r.routerGroups, routerGroup)return routerGroup
}// 给路由结构体添加一个添加路由功能的函数
// func (routerGroup *routerGroup) Add(name string, handleFunc HandleFunc) {
// 	routerGroup.handleFuncMap[name] = handleFunc
// }// 由于 ANY POST GET 都需要重复相同的逻辑代码,所以做一个提取操作
// 组册路由
func (routerGroup *routerGroup) registerRoute(name string, method string, handleFunc HandleFunc, middlewareFunc ...MiddlewareFunc) {if _, exist := routerGroup.handleFuncMap[name]; !exist {routerGroup.handleFuncMap[name] = make(map[string]HandleFunc)routerGroup.middlewaresFuncMap[name] = make(map[string][]MiddlewareFunc)}if _, exist := routerGroup.handleFuncMap[name][method]; !exist {routerGroup.handleFuncMap[name][method] = handleFuncrouterGroup.handlerMethodMap[method] = append(routerGroup.handlerMethodMap[method], name)routerGroup.middlewaresFuncMap[name][method] = append(routerGroup.middlewaresFuncMap[name][method], middlewareFunc...)} else {panic("Under the same route, duplication is not allowed!!!")}routerGroup.TreeNode.Put(name)
}// Any代表支持任意的请求方式
func (routerGroup *routerGroup) Any(name string, handleFunc HandleFunc, middlewareFunc ...MiddlewareFunc) {routerGroup.registerRoute(name, ANY, handleFunc, middlewareFunc...)
}// POST代表支持POST请求方式
func (routerGroup *routerGroup) Post(name string, handleFunc HandleFunc, middlewareFunc ...MiddlewareFunc) {routerGroup.registerRoute(name, http.MethodPost, handleFunc, middlewareFunc...)
}// GET代表支持GET请求方式
func (routerGroup *routerGroup) Get(name string, handleFunc HandleFunc, middlewareFunc ...MiddlewareFunc) {routerGroup.registerRoute(name, http.MethodGet, handleFunc, middlewareFunc...)
}// DELETE代表支持DELETE请求方式
func (routerGroup *routerGroup) Delete(name string, handleFunc HandleFunc, middlewareFunc ...MiddlewareFunc) {routerGroup.registerRoute(name, http.MethodDelete, handleFunc, middlewareFunc...)
}// PUT代表支持PUT请求方式
func (routerGroup *routerGroup) Put(name string, handleFunc HandleFunc, middlewareFunc ...MiddlewareFunc) {routerGroup.registerRoute(name, http.MethodPut, handleFunc, middlewareFunc...)
}// PATCH代表支持PATCH请求方式
func (routerGroup *routerGroup) Patch(name string, handleFunc HandleFunc, middlewareFunc ...MiddlewareFunc) {routerGroup.registerRoute(name, http.MethodPatch, handleFunc, middlewareFunc...)
}// 只要实现 ServeHTTP 这个方法,就相当于实现了对应的 HTTP 处理器
// 结构体 Engine 实现了 ServeHTTP(w http.ResponseWriter, r *http.Request) 方法
// 所以它就自动实现了 http.Handler 接口,因此可以直接被用于 http.ListenAndServe
// Engine 实现了 ServeHTTP,它就是一个合法的 http.Handler,可以用 http.Handle 来绑定它到某个具体的路由路径上!
func (e *Engine) ServeHTTP(w http.ResponseWriter, r *http.Request) {e.httpRequestHandle(w, r)
}func (e *Engine) httpRequestHandle(w http.ResponseWriter, r *http.Request) {// 在 Go 的 net/http 包中,r *http.Request 代表了客户端发来的 HTTP 请求对象。// 可以通过 r.Method 来获取这次请求使用的是什么方法(Method),例如:GET、POST、PUT、DELETE 等。if r.Method == http.MethodGet {fmt.Fprintf(w, "这是一个 GET 请求!!! ")} else if r.Method == http.MethodPost {fmt.Fprintf(w, "这是一个 POST 请求!!! ")} else {fmt.Fprintf(w, "这是一个其他类型的请求:%s!!! ", r.Method)}for _, group := range e.routerGroups {routerName := SubStringLast(r.RequestURI, "/"+group.name)if node := group.TreeNode.Get(routerName); node != nil && node.isEnd {// 路由匹配上了ctx := &Context{W: w, R: r}// 先判断当前请求路由是否支持任意请求方式的Anyif handle, exist := group.handleFuncMap[node.routerName][ANY]; exist {group.methodHandle(node.routerName, ANY, handle, ctx)return}// 不支持 Any,去该请求所对应的请求方式对应的 Map 里面去找是否有对应的路由if handle, exist := group.handleFuncMap[node.routerName][r.Method]; exist {group.methodHandle(node.routerName, r.Method, handle, ctx)return}// 没找到对应的路由,说明该请求方式不允许w.WriteHeader(http.StatusMethodNotAllowed)fmt.Fprintf(w, "%s %s not allowed!!!\n", r.Method, r.RequestURI)return}}w.WriteHeader(http.StatusNotFound)fmt.Fprintf(w, "%s %s not found!!!\n", r.Method, r.RequestURI)return
}// 定义一个引擎结构体
type Engine struct {router
}// 引擎结构体的初始化方法
func New() *Engine {return &Engine{router: router{},}
}// 引擎的启动方法
func (e *Engine) Run() {// for _, group := range e.routerGroups {// 	for name, value := range group.handleFuncMap {// 		http.HandleFunc("/"+group.name+name, value)// 	}// }// 把 e 这个http处理器绑定到对应路由下http.Handle("/", e)err := http.ListenAndServe(":3986", nil)if err != nil {log.Fatal(err)}
}
// main.go
package mainimport ("fmt""net/http""github.com/ErizJ/ZJGo/zjgo"
)func Log(handleFunc zjgo.HandleFunc) zjgo.HandleFunc {return func(ctx *zjgo.Context) {fmt.Println("[LOG] Middleware START")handleFunc(ctx)fmt.Println("[LOG] Middleware END")}
}func main() {fmt.Println("Hello World!")// // 注册 HTTP 路由 /hello// http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {// 	fmt.Fprintf(w, "Hello Go!")// })// // 启动 HTTP 服务器// err := http.ListenAndServe("8111", nil)// if err != nil {// 	log.Fatal(err)// }engine := zjgo.New()g1 := engine.Group("user")g1.UseMiddleware(func(handleFunc zjgo.HandleFunc) zjgo.HandleFunc {return func(ctx *zjgo.Context) {fmt.Println("Pre Middleware ON!!!")handleFunc(ctx)fmt.Println("Post Middleware ON!!!")}})g1.Get("/hello", func(ctx *zjgo.Context) {fmt.Fprintf(ctx.W, http.MethodGet+" Hello Go!——user/hello")})// 浏览器地址栏输入的都是 GET 请求// 需要用 curl 或 Postman 来发一个真正的 POST 请求,才会命中 Post handlerg1.Post("/info", func(ctx *zjgo.Context) {fmt.Println("HandleFunc ON!!!")fmt.Fprintf(ctx.W, http.MethodPost+" Hello Go!——user/info——POST")})g1.Get("/info", func(ctx *zjgo.Context) {fmt.Println("HandleFunc ON!!!")fmt.Fprintf(ctx.W, http.MethodGet+" Hello Go!——user/info——GET")}, Log)g1.Get("/get/:id", func(ctx *zjgo.Context) {fmt.Println("HandleFunc ON!!!")fmt.Fprintf(ctx.W, http.MethodGet+" Hello Go!——user/get/:id——GET")})g1.Get("/isEnd/get", func(ctx *zjgo.Context) {fmt.Println("HandleFunc ON!!!")fmt.Fprintf(ctx.W, http.MethodGet+" Hello Go!——user/isEnd/get——GET")})// 只要路由匹配,就会执行对应的处理函数g1.Any("/any", func(ctx *zjgo.Context) {fmt.Fprintf(ctx.W, " Hello Go!——user/any")})// g2 := engine.Group("order")// g2.Add("/hello", func(w http.ResponseWriter, r *http.Request) {// 	fmt.Fprintf(w, "Hello Go!——order/hello")// })// g2.Add("/info", func(w http.ResponseWriter, r *http.Request) {// 	fmt.Fprintf(w, "Hello Go!——order/info")// })fmt.Println("Starting...")engine.Run()
}// 注意执行顺序,理解包装这个词的意思,把函数作为一个整体再往表面包装的这个概念!
[LOG] Middleware START
Pre Middleware ON!!!
HandleFunc ON!!!
Post Middleware ON!!!
[LOG] Middleware END

相关文章:

Go 微服务框架 | 中间件

文章目录 定义中间件前置中间件后置中间件路由级别中间件 定义中间件 中间件的作用是给应用添加一些额外的功能,但是不会影响原有应用的编码方式,想用的时候直接添加,不想用的时候也可以轻松去除,实现所谓的可插拔。中间件的实现…...

【HarmonyOS Next之旅】DevEco Studio使用指南(十二)

目录 1 -> Code Linter代码检查 2 -> 配置代码检查规则 3 -> 查看/处理代码检查结果 1 -> Code Linter代码检查 Code Linter针对ArkTS/TS代码进行最佳实践/编程规范方面的检查。 可根据扫描结果中告警提示手工修复代码缺陷,或者执行一键式自动修复…...

Java设计模式之桥接模式:从入门到架构级实践

1. 什么是桥接模式? 桥接模式(Bridge Pattern) 是一种结构型设计模式,其核心目标是将抽象部分与实现部分分离,使它们能够独立变化。通过这种方式,桥接模式解决了多层继承带来的复杂性,并增强了…...

Jupyter Lab 无法启动 Kernel 问题排查与解决总结

📄 Jupyter Lab 无法启动 Kernel 问题排查与解决总结 一、问题概述 🚨 现象描述: 用户通过浏览器访问远程服务器的 Jupyter Lab 页面(http://xx.xx.xx.xx:8891/lab)后,.ipynb 文件可以打开,但无…...

【LeetCode 热题100】73:矩阵置零(详细解析)(Go语言版)

🚀 力扣热题 73:矩阵置零(详解 多种解法) 📌 题目描述 给定一个 m x n 的整数矩阵 matrix,如果一个元素为 0,则将其所在行和列的所有元素都设为 0。请你 原地 使用常量空间解决。 &#x1f3a…...

OminiAdapt:学习跨任务不变性,实现稳健且环境-觉察的机器人操作

25年3月来自中科大、北理工和中科院自动化所的论文“OminiAdapt: Learning Cross-Task Invariance for Robust and Environment-Aware Robotic Manipulation”。 随着具身智能的快速发展,利用大规模人体数据对人形机器人进行高水平的模仿学习,成为学术界…...

Vue3中父组件将一个ref定义的对象类型传递给子组件的解包机制

在Vue3中,当父组件将一个ref定义的对象类型传递给子组件时,子组件接收到的不是原始的Ref类型,而是该ref的.value值,即被解包后的响应式对象。具体行为如下: 关键点解析: 自动解包机制: Vue3在模…...

批量将 SVG 转换为 jpg/png/Word/PDF/ppt 等其它格式

SVG(可缩放矢量图形)是一种广泛使用的图像格式,因其矢量特性在不同分辨率下都能保持清晰,但在某些情况下,用户可能需要将 SVG 格式的图片转换为更常见的位图格式,如 JPG、PNG 等,以适应不同平台…...

微服务篇——SpringCloud

服务注册 Spring Cloud5大组件有哪些? 服务注册和发现是什么意思?Spring Cloud如何实现服务注册发现? nacos与eureka的区别 负载均衡 如何实现负载均衡? Ribbon负载均衡的策略有哪些? 如何自定义负载均衡的策略&…...

Windows 11 家庭中文版 安装docker desktop 无法开启自启动问题处理

前言 我在某台Windows 11家庭中文版的电脑上安装Docker Desktop后,老是无法开机启动,已经按照Docker Desktop 设置调整的方式设置了开机启动,但是重启后发现还是无法自启动,需要手动点击启动。然后使用任务计划程序新建一个开机启…...

蓝桥杯备考

先浅学一遍数据结构,不会拉倒,找点简单题练练语法基础 然后边学边刷二分查找和双指针 递归和暴力,边学边刷 学习贪心,练个几十道 再去过下数据结构 开始算法:搜索,动态规划, 搜索很重要,深…...

Elasticsearch 系列专题 - 第一篇:Elasticsearch 入门

Elasticsearch 是一个功能强大的开源分布式搜索和分析引擎,广泛应用于日志分析、实时搜索、数据可视化等领域。本篇将带你了解 Elasticsearch 的基本概念、安装方法以及简单操作,帮助你快速上手。 1. 什么是 Elasticsearch? 1.1 Elasticsearch 的定义与核心概念 Elasticse…...

【LeetCode 题解】数据库:1321.餐馆营业额变化增长

一、问题描述 本题给定了一个名为 Customer 的表,记录了餐馆顾客的交易数据,包括顾客 ID、姓名、访问日期和消费金额。作为餐馆老板,我们的任务是分析营业额的变化增长情况,具体来说,就是计算以 7 天(某日…...

Apache Nifi安装与尝试

Apache NIFI中文文档 地址:https://nifichina.github.io/ 下载安装配置 1、环境准备 Nifi的运行需要依赖于java环境,所以本机上需要安装java环境,并配置环境变量。 1.1查看本机是否已经存在java环境 请先执行以下命令找出系统中真实可用…...

【Git 常用操作指令指南】

一、初始化与配置 1. 设置全局账户信息 git config --global user.name "用户名" # 设置全局用户名 git config --global user.email "邮箱" # 设置全局邮箱 --global 表示全局生效,若需针对单个仓库配置,可省略该参数 2.…...

Django 生成PDF文件

在这里,我们将学习如何使用Django视图设计和生成PDF文件。我们将使用ReportLab Python PDF库生成PDF,该库可以创建定制的动态PDF文件。 这是一个开源库,可以通过在Ubuntu中使用以下命令轻松下载。 $ pip install reportlab Python Copy …...

多账户使用Github的场景,设置 SSH 多账号使用特定 key

遇到多账户使用Github的场景,常难以管理ssh文件 解决方案: 你可以通过配置 ~/.ssh/config 文件,生成多个SSH key 让 Git 识别不同 key 来对应不同 GitHub 账号。 ✅ 正确的 key 类型有这些常见选项: rsa:老牌算法&a…...

js中this指向问题

在js中,this关键字的指向是一个比较重要的概念,它的值取决于函数的调用方式。 全局状态下 //全局状态下 this指向windowsconsole.log("this", this);console.log("thiswindows", this window); 在函数中 // 在函数中 this指向win…...

BabelDOC ,开源的 AI PDF 翻译工具

BabelDOC 是一款开源智能 PDF 翻译工具,专门为科学论文的翻译而设计。它能够在原文旁边生成翻译文本,实现双语对照,用户无需频繁切换窗口,极大提升了阅读的便利性。此外,BabelDOC 能够完整保留数学公式、表格和图形&am…...

Dify 生成提示词的 Prompt

Dify 生成提示词的 Prompt **第1次提示词****第2次提示词****第3次提示词**总结 Dify 生成提示词是,会和LLM进行3次交互,下面是和LLM进行交互是的Prompt。 以下是每次提示词的概要、目标总结以及原始Prompt: 第1次提示词 概要: …...

在nvim的snippet补全片段中增加函数注释的功能

一、补全片段路径 如果使用nvim,应当在nvim的snippet的插件中增加对应补全的片段,目前我所用的补全的片段路径如下: /home/zhaoky/.local/share/nvim/site/pack/packer/start/vim-snippets.git/snippets我当前补全的是c语言所以使用的片段是c.snippets…...

阿里云负载均衡为何费用高昂?——深度解析技术架构与市场定价策略

本文深度解析阿里云负载均衡(SLB)产品的定价体系,从技术架构、安全防护、合规成本三个维度揭示费用构成逻辑。通过2023年某跨国企业遭受的混合型DDoS攻击案例,结合Gartner最新安全支出报告,给出企业级负载均衡成本优化…...

大数据(7)Kafka核心原理揭秘:从入门到企业级实战应用

目录 一、大数据时代的技术革命1.1 消息中间件演进史1.2 Kafka核心设计哲学 二、架构深度解构2.1 核心组件拓扑2.1.1 副本同步机制(ISR) 2.2 生产者黑科技2.3 消费者演进路线 三、企业级应用实战3.1 金融行业实时风控3.2 物联网数据管道 四、生产环境优化…...

01背包 Java

① 记忆化搜索解法: import java.util.*; import java.io.*;public class Main {static int n, m;static int[] v, w;static int[][] memory; // 记忆化数组public static void main(String[] args) throws Exception {BufferedReader br new BufferedReader(new …...

【Kafka基础】消费者命令行完全指南:从基础到高级消费

Kafka消费者是消息系统的关键组成部分,掌握/export/home/kafka_zk/kafka_2.13-2.7.1/bin/kafka-console-consumer.sh工具的使用对于调试、测试和监控都至关重要。本文将全面介绍该工具的各种用法,帮助您高效地从Kafka消费消息。 1 基础消费模式 1.1 从最…...

Seq2Seq - 编码器(Encoder)和解码器(Decoder)

本节实现一个简单的 Seq2Seq(Sequence to Sequence)模型 的编码器(Encoder)和解码器(Decoder)部分。 重点把握Seq2Seq 模型的整体工作流程 理解编码器(Encoder)和解码器&#xff08…...

@SchedulerLock 防止分布式环境下定时任务并发执行

背景 在一个有多个服务实例的分布式系统中,如果你用 Scheduled 来定义定时任务,所有实例都会执行这个任务。ShedLock 的目标是只让一个实例在某一时刻执行这个定时任务。 使用步骤 引入依赖 当前以redisTemplate为例子,MongoDB、Zookeeper…...

【力扣hot100题】(077)跳跃游戏

我最开始的想法还是太单纯了,最开始想着用回溯法,然后想到上一题的经验又想到了动态规划,虽然知道贪心题不太可能会这么复杂但实在想不出别的办法……果然我的智商做贪心题的极限就只能达到找零问题那种水平…… 最开始的方法,击…...

多光谱相机:林业监测应用(病虫害、外来物种、森林防火识别)

随着气候变暖和人类活动的增加,森林火灾发生的频率和强度都有所上升,而我国森林防火基础设施薄弱,监测预警体系不够完善,扑救能力和应急响应能力有待提高。气候变化导致气温升高、降水分布不均等,影响了树木的生长和发…...

Dynamic Programming(LeetCode 740)

740. 删除并获得点数 相关企业提示给你一个整数数组 nums ,你可以对它进行一些操作。 每次操作中,选择任意一个 nums[i] ,删除它并获得 nums[i] 的点数。之后,你必须删除 所有 等于 nums[i] - 1 和 nums[i] 1 的元素。 开始你…...

虚拟列表react-virtualized使用(npm install react-virtualized)

1. 虚拟化列表 (List) // 1. 虚拟化列表 (List)import { List } from react-virtualized; import react-virtualized/styles.css; // 只导入一次样式// 示例数据 const list Array(1000).fill().map((_, index) > ({id: index,name: Item ${index},description: This is i…...

[特殊字符] 手机连接车机热点并使用 `iperf3` 测试网络性能

好的,以下是根据你的描述整理出来的步骤及解释: 📶 手机连接车机热点并使用 iperf3 测试网络性能 本文将通过 iperf3 来测试手机和车机之间的网络连接性能。我们会让车机作为服务端,手机作为客户端,进行 UDP 流量传输…...

C#,VB.NET正则表达式法替换代码

如何设置必须是MGBOX开头, msgbox这种注释自动跳过 在 Visual Studio 中使用 Ctrl H 进行替换操作时,若要确保仅替换以 MsgBox 开头的代码,同时跳过注释里的 MsgBox,可以利用正则表达式来实现。以下为你详细介绍操作步骤: 1. 打…...

从MySQL快速上手大数据Hive

从MySQL快速上手大数据Hive Hive简介 ​ hive是基于Hadoop构建的一套数据仓库分析系统,它提供了丰富的SQL查询方式(DML)来分析存储在Hadoop分布式文件系统中的数据: 可以将结构化的数据文件映射为一张数据库表,并提供完整的SQL查…...

基于华为云kubernetes的应用多活的示例

1 概述 为避免地域级别的故障,需要将单机房架构变成双地域架构(两个机房物理距离越远,网络延时越大,网延时是业务研发首先关注的)。单边写的多机房架构,是落地性比较大的一个方案,相对于单元化…...

Linux动态库 vs 静态库:创建步骤与优缺点对比

Linux系列 文章目录 Linux系列前言一、动静态库的概念引入1.1 库的基本概念1.2 静态库(Static Library)1.3 动态库(Dynamic Library)1.4 动静态库的核心区别 二、动静态库的实现2.1 静态库的创建及使用2.2 动态库的创建和使用三、…...

分析下HashMap容量和负载系数,它是怎么扩容的?

很好,我们继续深入分析 HashMap 中 容量(capacity) 和 负载因子(load factor),以及它是如何进行 扩容(resize) 的。 🧱 一、容量(capacity)与负载…...

Linux权限管理:从入门到实践

目录 引言 ​编辑一、Linux用户类型 二、文件访问者分类 三、文件类型和访问权限 (一)文件类型 (二)基本权限 四、文件访问权限设置方法 (一)chmod命令 (二)chown命令 &…...

计算机网络(1)

名称解析 名称解析:将名称解析成对应地址,名字-->IP 名称解析优点:便以记忆、解耦(断开直接的练习) 容器 mini的虚拟机,该容器地址是动态的、生命周期短暂;可实现登录功能 如果用户想要登录该…...

第十一天 - MySQL/SQLite操作 - 数据库备份脚本 - 练习:监控数据存储系统

数据库实战入门:从零构建监控数据存储系统 前言 在物联网和系统监控领域,数据存储是核心基础环节。本文将通过MySQL/SQLite操作、数据库备份脚本和监控数据存储实战三个模块,带领初学者快速掌握数据库在真实场景中的应用。文章包含25个代码…...

编写文生视频提示词,制作抖音爆款视频

编写文生视频提示词,制作抖音爆款视频 一、理解文生视频提示词1.1 定义提示词1.1.1 提示词与创作工具的关系1.1.2 文生视频的功能 1.2 提示词的组成1.2.1 主体(Subject)1.2.2 动作(Action)1.2.3 场景(Scene…...

Linux: 线程控制

目录 一 前言 二 线程控制 1. POSIX线程库(原生线程库) 2. 创建线程 2.1 pthread_create 2.2pthread_self()获取线程id 3.线程终止 3.1.return 方式 3.2 pthread_exit 4 线程等待 三 理解线程tid 一 前言 在上一篇文章中我们已经学习了线程的概念,线程的创…...

为什么 npm list -g 没显示 node_modules?✨

揭秘:为什么 npm list -g 没显示 node_modules?🕵️‍♂️✨ 嗨,各位代码探险家!👋 今天我们要破解一个 npm 小谜团:运行 npm list -g --depth0 时,为什么输出的路径里看不到 node_…...

华为数字芯片机考2025合集4已校正

单选 1. 题目内容 影响芯片成本的主要因素是 Die Size 和封装,但电源、时钟等因素,特别是功耗对解决方案的成本影响较大,因此低成本设计需要兼顾低功耗设计:() 1. 解题步骤 1.1 分析题目 Die Size&…...

达摩院Paraformer-ONNX模型:一站式高精度中文语音识别工业级解决方案

文章目录 核心技术创新三大部署方案对比1. Docker极简部署(推荐)2. Python API直连调用3. 客户端实时测试工具 高阶调优技巧典型应用场景高频问题解决方案参考 阿里达摩院推出的speech_paraformer-large-vad-punc_asr_nat-zh-cn-16k-common-vocab8404-on…...

Llama 4的争议

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…...

React七案例下

代码下载 登录模块 用户登录 页面结构 新建 Login 组件&#xff0c;对应结构: export default function Login() {return (<div className{styles.root}><NavHeader className{styles.header}>账号登录</NavHeader><form className{styles.form}>&…...

Rust包管理与错误处理

文章目录 包管理箱&#xff08;Crate&#xff09;包&#xff08;Package&#xff09;模块&#xff08;Module&#xff09;访问权限use关键字 错误处理不可恢复错误可恢复错误错误传递kind方法 包管理 Rust的包管理有三个重要的概念&#xff0c;分别是箱、包、模块 箱&#xf…...

关于gitee的readme文档中的图片问题

使用markdown编辑readme文档&#xff0c;需要注意 添加图片&#xff0c;但是不显示问题 1.记得连图片一起上传到仓库中&#xff0c;不能只是在本地markdown文件中复制就结束了&#xff0c;因为存储的是本地图片地址&#xff0c;上传后找不到的 2.注意使用网络地址&#xff0…...

记录vscode连接不上wsl子系统下ubuntu18.04问题解决方法

记录vscode连接不上wsl子系统下ubuntu18.04问题解决方法 报错内容尝试第一次解决方法尝试第二次解决方法注意事项参考连接 报错内容 Unable to download server on client side: Error: Request downloadRequest failed unexpectedly without providing any details… Will tr…...