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

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 微服务框架 | 中间件

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

Spring MVC 重定向(Redirect)详解

Spring MVC 重定向(Redirect)详解 1. 核心概念与作用 重定向(Redirect) 是 Spring MVC 中一种客户端重定向机制,通过 HTTP 302 状态码(默认)将用户浏览器重定向到指定 URL。 主要用途&#xf…...

项目开发流程总结

目录 1. 项目启动阶段(需求分析) 2. 项目设计阶段 3. 开发阶段 4. 测试阶段 5. 打包和发布阶段 6. 运维和监控阶段 7. 版本迭代和维护阶段 项目生命周期中的管理要点: 总结: 一个完整的项目开发流程通常包括以下几个阶段…...

window上 docker使用ros2开发并usbip共享usb设备

曾经参考 https://blog.csdn.net/laoxue123456/article/details/138339029 来共享windows上的usb 发现没有办法成功总是出现 tcp 错误。telnet测试能够正常连接 很是奇怪,window上换成低版本的usbipd仍然是同样的错误,没有办法的情况下参考了docker官方文…...

基于MATLAB/simulink的信号调制仿真--AM调制

实验内容: 假设y(t)(20.5*2cos(2*pi*1000*t))*5cos(2*pi*2*1e4*t)调幅系统,请将一个频率为1000HZ的余弦波信号,通过进行AM调制,载波信号频率为20kHZ的余弦波,调制度ma0.…...

Vue3+Ts封装ToolTip组件(2.0版本)

本组件支持hover和click两种触发方式,需要更多的触发方式,可自行去扩展!!! 1.传递三个参数: content:要展示的文本 position:文本出现的位置("top" | "t…...

Latex语法入门之数学公式

Latex是一种高质量的排版系统,尤其擅长于数学公式的排版。本文我将带大家深入了解Latex在数学公式排版中的应用。从基础的数学符号到复杂的公式布局,我们都会一一讲解,通过本文的学习,你将能够轻松编写出清晰、美观的数学公式&…...

shell脚本 - Linux定时温度监控-软硬件检测 - 服务器温度监控 - 写入日志

效果图 脚本 vi auto.sh (chmod x ./auto.sh) #!/bin/bash # 按照日期创建一个文件或目录 https://blog.csdn.net/shoajun_5243/article/details/83539069 datetimedate %Y%m%d-%H%M%S |cut -b1-20 dirpath/systemMonitor/$datetime file1$dirpath/sensors.log file2$dirpa…...

Linux驱动开发进阶(六)- 多线程与并发

文章目录 1、前言2、进程与线程3、内核线程4、底半步机制4.1、软中断4.2、tasklet4.3、工作队列4.3.1、普通工作项4.3.2、延时工作项4.3.3、工作队列 5、中断线程化6、进程6.1、内核进程6.2、用户空间进程 7、锁机制7.1、原子操作7.2、自旋锁7.3、信号量7.4、互斥锁7.5、comple…...

买不起了,iPhone 或涨价 40% ?

周知的原因,新关税对 iPhone 的打击,可以说非常严重。 根据 Rosenblatt Securities分析师的预测,若苹果完全把成本转移给消费者。 iPhone 16 标配版的价格,可能上涨43%。 iPhone 16 标配的价格是799美元,上涨43%&am…...

Axure 列表滚动:表头非常多(横向滚动方向)、分页(纵向滚动) | 基于动态面板的滚动方向和取消调整大小以适合内容两个属性进行实现

文章目录 引言I 列表滚动的操作说明see also共享原型引言 Axure RP9教程 【数据传输】(页面值传递)| 作用域 :全局变量、局部变量 https://blog.csdn.net/z929118967/article/details/147019839?spm=1001.2014.3001.5501 基于动态面板的滚动方向和取消调整大小以适合内容两…...

RBAC 权限控制:深入到按钮级别的实现

RBAC 权限控制:深入到按钮级别的实现 一、前端核心思路 1. 大致实现思路 后端都过SELECT连表查询把当前登录的用户对应所有的权限返回过来,前端把用户对应所有的权限 存起来to(vuex/pinia) 中 ,接着前端工程师需要知道每个按钮对应的权限代…...

大模型格式化输出的几种方法

大模型格式化输出的几种方法 在开发一些和LLM相关的应用的时候,如何从大模型的反馈中拿到结构化的输出数据是非常重要的,那么本文就记录几种常用的方法。 OpenAI提供的新方法 在 OpenAI 的 Python 库中,client.beta.chat.completions.parse 是一个用于生成结构化输出的方法…...

【区间贪心】合并区间 / 无重叠区间 / 用最少数量的箭引爆气球 / 俄罗斯套娃信封问题

⭐️个人主页&#xff1a;小羊 ⭐️所属专栏&#xff1a;贪心算法 很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~ 目录 合并区间无重叠区间用最少数量的箭引爆气球俄罗斯套娃信封问题 合并区间 合并区间 class Solution { public:vector<vecto…...

JBDC java数据库连接(2)

目录 JBDC建立 获得PrepareStatement执行sql语句 形式&#xff1a; PrepareStatement中的方法: 实例 PreparedStatement和Statement 基于以下的原因: JBDC建立 获得PrepareStatement执行sql语句 在sql语句中参数位置使用占位符,使用setXX方法向sql中设置参数 形式&…...

es --- 集群数据迁移

目录 1、需求2、工具elasticdump2.1 mac安装问题解决 2.2 elasticdump文档 3、迁移 1、需求 迁移部分新集群没有的索引和数据 2、工具elasticdump Elasticdump 的工作原理是将输入发送到输出 。两者都可以是 elasticsearch URL 或 File 2.1 mac安装 前置&#xff1a;已经安装…...

Redis高频面试题及深度解析(20大核心问题+场景化答案)

摘要&#xff1a;Redis作为高性能缓存与内存数据库&#xff0c;是后端开发的核心技术栈之一。本文整理20大高频Redis面试题&#xff0c;结合真实场景与底层源码逻辑&#xff0c;助你彻底掌握Redis核心机制。涵盖单线程模型、集群方案、分布式锁、持久化等核心知识点。 一、Redi…...

事件处理程序

事件处理程序 一、事件处理程序的定义 事件处理程序是一段代码&#xff0c;用于响应特定的事件。在网页开发中&#xff0c;事件是在文档或浏览器窗口中发生的特定交互瞬间&#xff0c;如用户点击按钮、页面加载完成等。事件处理程序则是针对这些事件执行的函数&#xff0c;它能…...

stable diffusion部署ubuntu

stable-diffusion webui: https://github.com/AUTOMATIC1111/stable-diffusion-webui python3.10 -m venv venv&#xff08;3.11的下torch会慢得要死&#xff09; source venv/bin/activate 下载checkpoint模型放入clip_version"/home/chen/软件/stable-diffusion-webu…...

Qt的window注册表读写以及删除

Qt的window注册表读写以及删除 1. 使用 QSettings&#xff08;Qt推荐方式&#xff09;基本操作关键点限制 2. 调用Windows原生API示例&#xff1a;创建/读取键值常用API注意事项 3. 高级场景(1) 递归删除键(2) 注册表权限修改 4. 安全性建议总结其他QT文章推荐 在Qt中操作Windo…...

聊一聊接口测试时遇到上下游依赖时该如何测试

目录 一、手工测试时的处理方法 1.1沟通协调法 1.2模拟数据法 二、自动化测试时的处理方法 2.1 数据关联法&#xff08;变量提取&#xff09; 2.2 Mock数据法 2.3自动化框架中的依赖管理 三、实施示例&#xff08;以订单接口测试为例&#xff09; 3.1Mock依赖接口&…...

C++ 排序(1)

以下是一些插入排序的代码 1.插入排序 1.直接插入排序 // 升序 // 最坏&#xff1a;O(N^2) 逆序 // 最好&#xff1a;O(N) 顺序有序 void InsertSort(vector<int>& a, int n) {for (int i 1; i < n; i){int end i - 1;int tmp a[i];// 将tmp插入到[0,en…...

【有啥问啥】深入浅出讲解 Teacher Forcing 技术

深入浅出讲解 Teacher Forcing 技术 在序列生成任务&#xff08;例如机器翻译、文本摘要、图像字幕生成等&#xff09;中&#xff0c;循环神经网络&#xff08;RNN&#xff09;以及基于 Transformer 的模型通常采用自回归&#xff08;autoregressive&#xff09;的方式生成输出…...

zk基础—zk实现分布式功能

1.zk实现数据发布订阅 (1)发布订阅系统一般有推模式和拉模式 推模式&#xff1a;服务端主动将更新的数据发送给所有订阅的客户端。 拉模式&#xff1a;客户端主动发起请求来获取最新数据(定时轮询拉取)。 (2)zk采用了推拉相结合来实现发布订阅 首先客户端需要向服务端注册自己关…...

mySQL数据库和mongodb数据库的详细对比

以下是 MySQL 和 MongoDB 的详细对比&#xff0c;涵盖优缺点及适用场景&#xff1a; 一、核心特性对比 特性MySQL&#xff08;关系型数据库&#xff09;MongoDB&#xff08;文档型 NoSQL 数据库&#xff09;数据模型结构化表格&#xff0c;严格遵循 Schema灵活的文档模型&…...

ubuntu wifi配置(命令行版本)

1、查询当前设备环境的wifi列表 nmcli dev wifi list2、连接wifi nmcli dev wifi connect "MiFi-SSID" password "Password" #其中MiFi-SSID是wifi的密码&#xff0c;Password是wifi的密码3、查看连接情况 nmcli dev status...

Docker与Kubernetes在ZKmall开源商城容器化部署中的应用

ZKmall开源商城作为高并发电商系统&#xff0c;其容器化部署基于DockerKubernetes技术栈&#xff0c;实现了从开发到生产环境的全流程标准化与自动化。以下是核心应用场景与技术实现&#xff1a; 一、容器化基础&#xff1a;Docker镜像与微服务隔离 ​服务镜像标准化 ​分层构建…...

华为AI-agent新作:使用自然语言生成工作流

论文标题 WorkTeam: Constructing Workflows from Natural Language with Multi-Agents 论文地址 https://arxiv.org/pdf/2503.22473 作者背景 华为&#xff0c;北京大学 动机 当下AI-agent产品百花齐放&#xff0c;尽管有ReAct、MCP等框架帮助大模型调用工具&#xff0…...

MYSQL数据库语法补充

一&#xff0c;DQL基础查询 DQL&#xff08;Data Query Language&#xff09;数据查询语言&#xff0c;可以单表查询&#xff0c;也可以多表查询 语法&#xff1a; select 查询结果 from 表名 where 条件&#xff1b; 特点&#xff1a; 查询结果可以是&#xff1a;表中的字段…...

Elasticsearch单节点安装手册

Elasticsearch单节点安装手册 以下是一份 Elasticsearch 单节点搭建手册&#xff0c;适用于 Linux 系统&#xff08;如 CentOS/Ubuntu&#xff09;&#xff0c;供学习和测试环境使用。 Elasticsearch 单节点搭建手册 1. 系统要求 操作系统&#xff1a;Linux&#xff08;Cent…...