【Gin】深度解析:在Gin框架中优化应用程序流程的责任链设计模式(下)

【Gin】深度解析:在Gin框架中优化应用程序流程的责任链设计模式(下)

大家好 我是寸铁👊
【Gin】深度解析:在Gin框架中优化应用程序流程的责任链设计模式(下)✨
喜欢的小伙伴可以点点关注 💝

在这里插入图片描述


前言

本次文章分为上下两部分,上部分为对理论的介绍,下部分为具体的底层代码深度剖析和编程实践,感兴趣的伙伴不要错过哦~

责任链设计模式作为一种经典的行为设计模式,在现代软件开发中扮演着重要角色。特别是在高效的Web应用开发中,如Gin框架这样的轻量级Go语言Web框架,合理地应用责任链模式可以显著提升代码的可扩展性和灵活性。本文将深入探讨在Gin框架中责任链模式的实现原理、优化策略以及实际应用场景。
责任链模式通过将请求的发送者和接收者解耦,将多个对象连成一条链,并在链上传递请求,直到有对象处理该请求为止。在Gin框架中,利用责任链模式可以有效地处理请求的流程控制、中间件管理和异常处理,使得代码结构更加清晰和可维护。本文将探索如何在Gin框架中设计和优化责任链模式,以提升应用程序的性能和可维护性。


关键的类图和时序图

(1)类图
责任链模式包含以下几个主要角色:
Handler(抽象处理者):
声明一个处理请求的接口,通常包含一个指向下一个处理者的引用。

MiddlerWare(具体处理者):
Gin的中间件实现抽象处理者的处理方法,并根据自身的处理能力决定是否处理请求,如果不能处理则将请求传递给下一个处理者。

Client(客户端):
创建一个具体处理者对象的链,并向链上的第一个处理者对象发送请求。


在这里插入图片描述

图 28 责任链模式类图

由类图28可得:先声明一个处理请求的接口Handler,通常聚合一个指向下一个处理者的引用successor。声明处理请求的方法handlerRequest(),由具体的中间件责任方MiddleWare实现。接着Gin的中间件实现抽象处理者的处理方法,调用handlerRequest()方法根据自身的处理能力决定是否处理请求,如果不能处理则将请求传递给下一个处理者nextHandler。客户端创建一个具体处理者对象的链,并向链上的第一个处理者对象发送请求。


(2)时序图

在这里插入图片描述

图 29 责任链模式时序图

由上图29可得:客户端发送请求:客户端调用具体处理者链的第一个处理者的handleRequest()方法,将请求request传递给链的起始点MiddleWare1
责任链中的处理者Midlleware处理请求:每个具体中间件处理者Midlleware收到请求后,根据自己的处理能力决定是否处理请求。
请求传递到合适的处理者:如果当前处理者能够处理请求,则处理完成;如果不能处理,则将请求传递给链中的下一个处理者Midlleware
链的末端处理:请求request会沿着链依次传递,直到被处理或者到达链的末端。如果末端处理者能够处理请求processing Request,则处理完成后,沿着责任链返回处理完后的响应信息response`。如果整个链上的处理者都不能处理请求,则请求最终未被处理。


主程序的流程

由下图30可知:一开始,Client客户端构建责任链后,开始发起请求,责任链的中间件MiddleWare1开始接收请求,然后处理请求,处理完毕后,返回响应给上层调用者,如果客户端接收到响应后,则程序退出。
接着如果责任链的中间件MiddleWare1无法处理请求,则将请求传递给下一层中间件MiddleWare2MiddleWare2处理请求,处理完毕后,返回响应给上层调用者,再由上层调用者沿责任链继续将响应信息返回给上层调用者,直至Client客户端。
如果责任链的中间件MiddleWare2无法处理请求,则将请求传递给下一层中间件MiddleWare……MiddleWare……处理请求,处理完毕后,返回响应给上层调用者,再由上层调用者沿责任链继续将响应信息返回给上层调用者,直至Client客户端。
如果责任链的中间件都无法处理请求,则将请求传递给最后一层中间件FinalMiddleWare处理请求,处理完毕后,返回响应给上层调用者,再由上层调用者沿责任链继续将响应信息返回给上层调用者,直至Client客户端。如果最后一层中间件FinalMiddleWare都无法处理请求,则责任链程序结束退出。

在这里插入图片描述

图 30 责任链模式主程序流程图

程序模块之间的调用关系

下图为程序各模块之间的调用关系:

在这里插入图片描述

图 31 责任链模式程序模块调用图
由上图31可知,责任链模式主要有如下3个角色:
(1) IRoutes(处理者):
定义处理请求的接口,通常包含一个处理方法(handle),其具体实现方(ConcreteHandler)实现处理请求的方式。每个处理者对象中通常会包含一个指向下一个处理者的引用(后继者)。


(2) CombineHandlers(具体处理者):
实现 IRoutes接口,处理请求的具体逻辑。如果自己能够处理请求,则处理;否则将请求传递给下一个处理者。当然,也可以设置拦截请求,将请求只在本层进行处理,不传递给下一层责任方进行处理。


(3) Client(客户端):
负责创建和提交请求对象。按照指定的顺序构建责任链,可以设置拦截器,用于拦截请求,最后将请求发送到链的起始点。


下面是对上图各层次调用关系的描述:

客户端调用r.GET进行按照指定的顺序构建责任链,GET方法实现IRoutes接口,GET方法将构建路由组对象的请求转发给真正的构建路由组对象的handle方法,该handle方法实现IRoutes接口。handle方法进一步转发构建GET中指定顺序的中间件责任链请求给具体处理者CombineHandlers,具体处理者CombineHandlers负责按照客户端指定的顺序构建中间件责任链。责任链中的每一个处理者存在Next()Abort()方法,调用Next()方法遍历责任链中的请求者对象,依次将请求传递给链中的具体处理者对象业务图见下图32。Abort()方法设置索引为超出合法范围的值,使得不将请求转发给下一个处理者,实现拦截效果,业务图见下图33。构建完后返回给上层的调用者HandlerHandler将路由组对象组装好后,再将组装好的路由组对象返回给GET方法,GET方法返回嵌入责任处理链的路由组对象给客户端(责任链的起始点)进行调用,客户端拿到对象后,做下一步的处理。

在这里插入图片描述

图 32 责任链Next处理业务图

在这里插入图片描述

图 33 责任链Abort拦截业务图

在上图的基础上,下面对各个模块的代码进行深入剖析:
在这里插入图片描述

图 34 责任链客户端代码

gin.Default() 创建了一个默认的 Gin 路由引擎实例 r。
r.GET("", middleWare1, middleWare2, Home) 定义了一个 GET 请求的路由,其中 middleWare1 middleWare2 是中间件函数,用于在请求到达最终处理函数 Home 之前执行预处理或者其他操作。
r.Run(":8080") 启动了 HTTP 服务器,监听在 8080 端口上。

在这里插入图片描述

图 35 客户端指定处理请求的中间件代码

函数签名和参数:
func middleWare1(c *gin.Context):这是一个函数签名,接收一个 *gin.Context 类型的参数 c,用于处理 HTTP 请求和响应。
处理逻辑:fmt.Println("M1 请求部分"):这行代码输出 “M1 请求部分”,表示这是中间件处理请求的部分,可以在这里执行一些预处理逻辑,如日志记录、权限检查等。
c.Next()
c.Next() 是 Gin 框架中用于将控制传递给链中的下一个处理程序的方法。在这里,它表示将控制权传递给下一个注册的中间件或路由处理函数。fmt.Println(“M1 响应部分”):这行代码输出 “M1 响应部分”,表示中间件处理请求后的响应部分,可以在这里执行一些后续处理逻辑,如记录响应时间、清理资源等。
注释的代码 c.Abort()
//c.Abort():这是一个被注释掉的代码片段。在 Gin 框架中,如果调用 c.Abort(),它将会中止当前请求链的执行,不会再继续执行后续的中间件或路由处理函数。如果取消注释,将会导致请求处理过程被中止,不再执行后续的处理函数。

执行流程:

当有一个HTTP请求到达与该中间件关联的路由时,这段代码的执行流程如下:当请求进入时,中间件输出 “M1 请求部分”,执行一些请求前的逻辑。
c.Next() 调用将控制权传递给下一个中间件或路由处理函数。如果注释掉的 c.Abort() 被取消注释,则请求的处理将在此中间件结束,不再继续向下执行。
如果没有调用 c.Abort() 或者注释掉了,请求将继续执行下一个中间件或者最终的路由处理函数。当控制返回给该中间件时(表示后续处理完成或者中间件链中断),输出 “M1 响应部分”,执行一些请求后的逻辑。


在这里插入图片描述

图 36 IRoutes接口

代码位置:RouterGroup.go的33-51行
方法解析:
Use
Use(...HandlerFunc) IRoutes
作用:注册一个或多个中间件函数,这些中间件函数会在后续的路由处理中被调用。返回值:返回 IRoutes 接口本身,以支持链式调用。
HTTP 方法相关路由
这些方法 (Handle, Any, GET, POST, DELETE, PATCH, PUT, OPTIONS, HEAD) 都接受路由路径作为第一个参数,后跟一个或多个 HandlerFunc,表示路由处理函数。每个方法都允许注册对应 HTTP 方法的路由处理器。
返回值:同样返回 IRoutes 接口,支持链式调用,以便在代码中可以连续调用多个路由注册方法。
Match
Match([]string, string, ...HandlerFunc) IRoutes
参数:第一个参数是 HTTP 方法的列表,第二个参数是路由路径模式,后跟一个或多个 HandlerFunc
作用:注册一个支持多种 HTTP 方法的路由处理器。
返回值:返回 IRoutes 接口。
静态文件服务相关方法:
StaticFile(string, string) IRoutes:注册单个静态文件的路由处理器。
StaticFileFS(string, string, http.FileSystem) IRoutes:注册单个静态文件的路由处理器,并指定文件系统。
Static(string, string) IRoutes:注册指定路径下所有文件的静态文件服务。
StaticFS(string, http.FileSystem) IRoutes:注册指定路径下所有文件的静态文件服务,并指定文件系统。

总结:
这个接口定义了一组方法,用于在一个路由处理器中注册路由和处理函数。通过IRoutes接口,可以方便地添加中间件、处理各种 HTTP 方法的请求,以及处理静态文件服务。这种设计使得路由的注册和处理能够保持清晰和模块化,符合常见的 Web 框架的路由管理模式。


在这里插入图片描述

图 37 GET方法代码

代码位置:routergroup.go的116-118行

函数签名:
GET(relativePath string, handlers ...HandlerFunc):这是一个方法定义,属于RouterGroup类型的接收者 group。它接收一个相对路径 relativePath 和一个或多个HandlerFunc类型的处理函数作为参数。
IRoutes 是一个接口,用于表示路由集合或路由的操作。
功能说明:
GET方法作为 RouterGroup 的一个方法,是为了注册一个处理 GET 请求的路由。
relativePath string 参数表示注册的路由的相对路径,如 “/”、“/users” 等。
handlers ...HandlerFunc 参数是一个变长参数,接收一个或多个 HandlerFunc 函数,用来处理请求。
方法调用:
roup.handle(http.MethodGet, relativePath, handlers):在 GET 方法内部,调用了 group.handle 方法,将 HTTP 方法名 “GET”、相对路径 relativePath handlers 函数传递给它。
http.MethodGet 是 Go 标准库中定义的常量,表示 HTTP GET 请求方法。
返回值:
return group.handle(...):GET 方法返回了group.handle(...)的结果。通常情况下,这个方法会返回路由集合或者支持路由操作的接口,以便可以进一步链式调用其他路由相关的方法。
执行流程:
在 Gin 框架中,RouterGroup 类型的 GET 方法是一个便捷方法,它内部调用了 group.handle 方法来处理注册 GET 请求的路由。具体的处理流程如下:当调用 GET 方法注册路由时,实际上是通过 group.handle 方法进行注册。group.handle 方法会将 “GET”、relativePath handlers 作为参数传递给底层的路由处理器进行处理和注册。这样做的好处是可以通过不同的 HTTP 方法(例如 GET, POST, PUT 等)来注册不同的路由处理逻辑,同时保持了代码的简洁性和可读性。


在这里插入图片描述

图 38 具体处理者代码

代码位置:routergroup.go的86-91行

函数签名:
handle(httpMethod, relativePath string, handlers HandlersChain):这是一个方法定义,属于 RouterGroup 类型的接收者 group。它接收三个参数:
httpMethod:表示 HTTP 请求方法,如 "GET""POST" 等。
relativePath:表示路由的相对路径,例如 “/”、“/users” 等。
handlers HandlersChain:是一个类型为 HandlersChain 的参数,表示一系列的处理函数链。
路径计算和处理器组合:
calculateAbsolutePath 方法确保生成正确的完整路径,考虑了路由组的前缀等因素。combineHandlers 方法可能用来将当前路由组的中间件与传入的处理函数链合并,确保请求能够按照正确的顺序执行。
路由注册:
addRoute 方法将最终确定的HTTP方法、路径和处理函数链注册到 Gin 框架的路由引擎中,以便后续能够根据请求的HTTP方法和路径找到对应的处理函数。
返回值:
returnObj 方法返回当前路由组的某个接口或对象,用于可能的链式调用或其他路由相关操作。
这段代码展示了 Gin 框架中路由注册的核心逻辑。它负责计算路由的绝对路径,合并处理函数链,最终将路由信息注册到底层的路由引擎中。通过这种设计,框架能够支持灵活的路由定义和中间件处理,同时保证了性能和可扩展性。


在这里插入图片描述

图 39 具体处理者真正构建责任链代码
代码位置:routergroup.go的241-248行

函数签名:
combineHandlers(handlers HandlersChain) HandlersChain:这是一个方法定义,属于RouterGroup类型的接收者group。它接收一个类型为 HandlersChain 的参数 handlers,表示一系列的处理函数链。返回类型为 HandlersChain,即处理函数链。
参数解释:
handlers HandlersChain:表示要合并到当前路由组 (group) 的处理函数链。HandlersChain 可能是一个类型为 []func(c *Context) 的切片,用于存储中间件和处理函数。
计算最终大小:
finalSize := len(group.Handlers) + len(handlers):计算合并后的处理函数链的总长度。group.Handlers 是当前路由组已有的处理函数链的长度,handlers 是传入的新的处理函数链的长度。
断言检查:
assert1(finalSize < int(abortIndex), "too many handlers"):使用 assert1 函数来断言 finalSize 必须小于 abortIndex,否则会输出 “too many handlers” 的错误信息。这是为了确保合并后的处理函数链不会超过某个预设的最大限制,避免潜在的内存溢出或其他问题。
创建合并后的处理函数链:
mergedHandlers := make(HandlersChain, finalSize):根据 finalSize 创建一个新的 HandlersChain,即 mergedHandlers,用于存储合并后的处理函数链。
复制处理函数:
copy(mergedHandlers, group.Handlers):将当前路由组 (group) 的已有处理函数链复制到 mergedHandlers ``的开头部分。 copy(mergedHandlers[len(group.Handlers):], handlers):将传入的新处理函数链 handlers 复制到 mergedHandlers 的末尾部分,从 group.Handlers 的长度位置开始复制。
返回合并后的处理函数链:
return mergedHandlers:返回合并后的 HandlersChain,即包含了当前路由组的处理函数链和传入的新处理函数链的完整链条。
处理函数链合并:
combineHandlers 方法用于将当前路由组的已有处理函数链与传入的新处理函数链进行合并,确保请求按照正确的顺序执行所有中间件和处理函数。
长度和断言检查:
在合并前,通过计算和断言来确保合并后的处理函数链不会过长,以保证系统的稳定性和性能。
返回值:
返回合并后的处理函数链,以便在路由注册时使用。
这段代码展示了 Gin 框架中如何处理路由组的处理函数链的合并逻辑。通过这种方式,框架能够支持在路由组中动态添加中间件和处理函数,保证了灵活性和可扩展性。


在这里插入图片描述

图 40 责任链的数据结构
代码位置:gin.go的54行

定义:定义了一个HandlerFunc请求处理者的切片,用于存储HandlerFunc请求处理者,构建责任链。


在这里插入图片描述

图 41 请求处理者的定义
代码位置:gin.go的48行

定义:定义HandlerFunc类型,用于创建具体请求处理对象。


在这里插入图片描述

图 42 责任链访问下一个责任方Next()代码
代码位置:context.go的182-188行

方法说明:
c.index 是 Context 结构体中的一个字段,用于跟踪当前执行的中间件或处理函数的位置。
c.handlers 是一个存储 HandlerFunc 的切片,这些函数是注册到当前路由处理器的中间件和处理函数。
c.index++ 将 index 递增,以准备执行下一个中间件或处理函数。
for 循环用来遍历 handlers 中的函数,从 index 所指的位置开始执行,直到数组末尾或者中途某个函数决定中断执行。
执行流程:
调用Next()方法会使 index 递增,从而将控制权交给下一个注册的中间件或处理函数。
每次循环,通过 c.handlers[c.index](c) 调用 index 所指的函数,并将当前的 Context 对象 c 传递给它。
循环继续,直到 index 超过了 handlers 的长度或者某个中间件函数调用了 Next() 以停止后续执行。


在这里插入图片描述

图43 定义拦截索引
代码位置:context.go的50-51行

abortIndex 是一个 int8 类型的常量。
math.MaxInt8int8 类型能表示的最大整数,通常为 127
>> 1 是位运算操作,表示将 math.MaxInt8 右移一位,即将其值除以 2,得到的结果约为 63(实际值取决于具体的整数大小和运算系统)。
这样设定的目的是使abortIndex成为一个比较大的数值,足以确保 c.index 大于等于 abortIndex 后可以立即停止后续的处理函数调用。

在这里插入图片描述

图 44 请求处理者设置拦截器
代码位置:context.go的199-201行
代码解释:
c.index Context 结构体中的一个字段,用于跟踪当前执行的中间件或处理函数的位置。
abortIndex 是一个常量或全局变量,用于表示中止处理流程的索引值。
Abort() 方法将 c.index 设置为 abortIndex,这样在接下来调用 Next() 方法时,循环将直接结束,不再执行后续的处理函数或中间件。


在这里插入图片描述

图 45 判断请求处理者是否设置拦截
代码位置:context.go的191-193行

代码解释:
c.index Context 结构体中的一个字段,用于跟踪当前执行的中间件或处理函数的位置。
abortIndex 是一个常量或全局变量,用于表示中止(abort)处理流程的索引值。
IsAborted() 方法通过比较 c.index 是否大于或等于abortIndex来判断当前处理流程是否已经被中止。
如果c.index大于或等于 abortIndex,则返回 true,表示当前处理已被中止;否则返回 false,表示未被中止。


责任链模式案例及调试分析

责任链模式案例编写如下:
下面分析一下每个部分的功能和调用流程:
结构定义和接口:
在这里插入图片描述

图101 定义Handler接口
Handler 接口:
Handle(c *gin.Context) 方法用于处理请求。
SetNext(handler Handler) 方法用于设置下一个责任链节点。


在这里插入图片描述

图102 定义Middleware中间件

Middleware 结构体:实现了 Handler 接口。


在这里插入图片描述

图103 定义中间件1的Handle方法

Handle(c *gin.Context) 方法中,打印 “M1 接收请求”,然后调用下一个处理者(如果存在),最后打印 “M1 得到响应”。


在这里插入图片描述

图104 定义中间件2的Handle方法

Handle(c *gin.Context) 方法中,打印 “M2 接收请求”,然后调用下一个处理者(如果存在),最后打印 “M2 得到响应”。


在这里插入图片描述

图105 定义末端中间件的Handle方法
FinalHandler 结构体:作为最终的处理者,实现了 Handler 接口。
Handle(c *gin.Context) 方法中,打印 “FinalHandler 接收请求”,然后调用具体的业务逻辑函数 Home(c),最后打印 “FinalHandler 得到响应”。

在这里插入图片描述

图106 定义视图层代码
Home视图层部分:

fmt.Println("Home Receiving……") 放在 Home 函数中的最开始,这样在请求到达时会立即打印 “Home Receiving……”。
c.String(200, "Home Receiving……") 在完成日志记录后立即向客户端发送 “Home Receiving……” 响应
责任链的构建和运行:


在这里插入图片描述

图107 客户端构建责任链

解读: 在 main 函数中:先创建了 Gin 引擎实例 r。再实例化了 Middleware1Middleware2FinalHandler。之后使用 SetNext 方法将它们串联起来,形成责任链:middleware1 -> middleware2 -> finalHandler。将middleware1.Handle方法作为 Gin 路由处理函数注册到了根路径 “”,这意味着当收到 GET 请求时,责任链会依次处理该请求。最后通过 r.Run(":8080") 启动 Gin 服务器,监听在 8080 端口上。
小结:责任链模式案例展示了如何使用 Go 和 Gin 框架构建一个简单的责任链,用于处理 HTTP 请求。每个中间件和最终处理者都负责一部分逻辑,并通过 SetNext 方法连接成链条,确保请求依次经过每个处理者,并且每个处理者都能在适当的时机打印日志和处理响应。
调试分析:
在执行时,假设收到一个 GET 请求:
Gin 路由会将该请求交给 middleware1.Handle 处理。
middleware1.Handle 中会打印 “M1 接收请求”,然后调用 middleware2.Handle。
middleware2.Handle 中会打印 “M2 接收请求”,然后调用 finalHandler.Handle。
finalHandler.Handle 中会打印 “FinalHandler 接收请求”,然后调用 Home(c) 处理实际的业务逻辑。
Home(c) 会在控制台打印 “Home Receiving……”,并向客户端返回 “Home Receiving……” 字符串。
控制流会逆序返回,最终 finalHandler.Handle 打印 “FinalHandler 得到响应”,然后依次是 middleware2.Handle 和 middleware1.Handle 的响应打印。
Gin引擎对象启动成功,代码无报错,责任链构建成功,正在监听8080端口,Demo启动成功!具体输出结果见测试结果部分。


在这里插入图片描述

图 108 成功启动责任链模式案例


责任链模式测试结果

APIfox测试工具监听向8080端口发送GET请求:显示Home Reciving……,说明责任链构建成功,且将信息Home Reciving……正确显示到客户端。
在这里插入图片描述

图132 Apifox发起GET测试请求


责任链模式测试结果进一步剖析如下:
在这里插入图片描述

图133 责任链模式测试结果剖析图


客户端构建责任链并发起监听8080端口请求,接下来分析控制台输出的顺序是否与调试分析的预测一致:
在这里插入图片描述

图134 Apifox发起GET请求
访问 http://localhost:8080/ ,向服务端发出一条GET请求,可以看到以下控制台输出:

(1) 请求发出:
M1 接收请求:Middleware1 接收请求。与分析图的序号①对应
M2 接收请求:Middleware2 接收请求。与分析图的序号②对应
FinalHandler 接收请求:FinalHandler 接收请求。与分析图的序号③对应
Home视图层 Home Receiving……:Home 函数处理请求,输出 “Home Receiving……”。与分析图的序号④对应
同时向客户端(APIfox发起的请求端)发送"Home Receiving……


(2) 接收响应:
FinalHandler 得到响应:FinalHandler 处理完请求,得到响应。与分析图的序号⑤对应
M2 得到响应:Middleware2 得到最终处理结果的响应。与分析图的序号⑥对应
M1 得到响应:Middleware1 得到 Middleware2 的响应,最终完成整个请求的处理。与分析图的序号⑦对应。
综上,无论是控制台输出的结果还是客户端显示的信息,都与整个调试分析的结果一致,责任链构建成功,测试通过!


在这里插入图片描述

图135服务端监听端口多次测试请求

持续发起多次请求,责任链也能够正常处理,不发生请求,则持续监听端口。
再测试一下拦截请求的效果,这里只需要将调用下一个处理者代码替换为c.Abort()即可,这样就能将请求拦截在Middleware2处理者,而
不会传给下一层的具体处理者。


在这里插入图片描述

图 136 进行请求的拦截

预期结果如下:
由于在Middleware2这一层拦截掉,请求不会转发给下一层处理,即最后处理者和Home业务函数的内容都不会输出,客户端也不会显示出Home Receiving…… 输出结果预期如下:
M1 接收请求 M2 接收请求 M2 得到响应 M1 得到响应
测试结果如下:
客户端发起请求,没有接收到Home发送的内容,测试结果与预期一致!


在这里插入图片描述

图137 客户端发起GET请求

再看一下控制台输出的内容:只输出M1和M2两个中间件有关的内容,无输出最后处理者的内容,测试结果与预期一致!

在这里插入图片描述

图138 服务端监听端口输出信息

结语

责任链模式作为一种优秀的设计模式,在Gin框架中展现了其强大的灵活性和可扩展性。通过本文的探讨,我们深入理解了责任链模式在处理请求流程、中间件管理和异常处理方面的应用。合理地利用责任链模式可以使代码更加模块化和可复用,从而提高了应用程序的设计质量和开发效率。希望本文能够为开发者提供实用的指导和启发,帮助他们在实际项目中充分发挥责任链模式的优势,构建更加健壮和高效的Web应用程序。


看到这里的小伙伴,恭喜你又掌握了一个技能👊
希望大家能取得胜利,坚持就是胜利💪
我是寸铁!我们下期再见💕


在这里插入图片描述

往期好文💕

保姆级教程

【保姆级教程】Windows11下go-zero的etcd安装与初步使用

【保姆级教程】Windows11安装go-zero代码生成工具goctl、protoc、go-zero

【Go-Zero】手把手带你在goland中创建api文件并设置高亮


报错解决

【Go-Zero】Error: user.api 27:9 syntax error: expected ‘:‘ | ‘IDENT‘ | ‘INT‘, got ‘(‘ 报错解决方案及api路由注意事项

【Go-Zero】Error: only one service expected goctl一键转换生成rpc服务错误解决方案

【Go-Zero】【error】 failed to initialize database, got error Error 1045 (28000):报错解决方案

【Go-Zero】Error 1045 (28000): Access denied for user ‘root‘@‘localhost‘ (using password: YES)报错解决方案

【Go-Zero】type mismatch for field “Auth.AccessSecret“, expect “string“, actual “number“报错解决方案

【Go-Zero】Error: user.api 30:2 syntax error: expected ‘)‘ | ‘KEY‘, got ‘IDENT‘报错解决方案

【Go-Zero】Windows启动rpc服务报错panic:context deadline exceeded解决方案


Go面试向

【Go面试向】defer与time.sleep初探

【Go面试向】defer与return的执行顺序初探

【Go面试向】Go程序的执行顺序

【Go面试向】rune和byte类型的认识与使用

【Go面试向】实现map稳定的有序遍历的方式

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/389525.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机毕业设计Hadoop+Spark旅游景点可视化 旅游景点推荐系统 景区游客满意度预测与优化 Apriori算法 景区客流量预测 旅游大数据 景点规划

### 开题报告 **论文题目&#xff1a;** 基于Spark的旅游景点可视化系统的设计与实现 **研究背景与意义&#xff1a;** 随着旅游业的快速发展&#xff0c;人们对旅游信息的获取和处理需求越来越高。传统的旅游信息系统虽然能够提供静态的数据查询和展示功能&#xff0c;但在…

C++(区别于C的)基础内容总结

参考&#xff1a; C 教程 | 菜鸟教程 (runoob.com) 简介 C 被认为是一种中级语言&#xff0c;它综合了高级语言和低级语言的特点。 C 是由 Bjarne Stroustrup 于 1979 年在新泽西州美利山贝尔实验室开始设计开发的。C 进一步扩充和完善了 C 语言&#xff0c;最初命名为带类的C&…

Java中类装载的执行过程

类装载的执行过程 类从加载到虚拟机中开始&#xff0c;直到卸载为止&#xff0c;它的整个生命周期包括了&#xff1a;加载、验证、准备、解析、初始化、使用和卸载这7个阶段。其中&#xff0c;验证、准备和解析这三个部分统称为连接&#xff08;linking&#xff09;。 1.加载 …

学习记录(9):Prompt提示词技巧

依旧照例先感谢前辈们的慷慨分享 今天学习的是这篇文章↓ 原文&#xff1a;转自公主号“博金斯的AI笔记” —《4篇Prompt论文的提示词技巧, 3 个 GPTs 实例测试》 文章目录 一、提示词框架二、逻辑链&#xff08;Chain of thought&#xff09;三、思维树&#xff08;Tree of th…

[渗透测试学习] PermX-HackTheBox

文章目录 PermX-HackTheBox信息搜集漏洞利用权限提升参考文章PermX-HackTheBox 信息搜集 nmap扫描一下端口 nmap -sC -v 10.10.11.23扫描结果如下 PORT STATE SERVICE 22/tcp open ssh | ssh-hostkey: | 256 e2:5c:5d:8c:47:3e:d8:72:f7:b4:80:03:49:86:6d:ef (ECDSA…

二维码直达App,Xinstall为你打通运营任督二脉

在移动互联网时代&#xff0c;App的推广和运营显得尤为重要。然而&#xff0c;许多企业在投入大量资源进行App推广和运营时&#xff0c;总会遇到一些棘手的问题&#xff0c;如用户转化率低、数据分析困难等。今天&#xff0c;我们要为大家揭秘一个神奇的助手——Xinstall&#…

Github2024-07-29 开源项目周报Top15

根据Github Trendings的统计,本周(2024-07-29统计)共有15个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Python项目2Java项目2HTML项目2C项目2TypeScript项目2JavaScript项目2非开发语言项目1Vue项目1Go项目1Dart项目1C++项目1Rust项目1Jupyter Note…

【Linux】-----进度条小程序

目录 前言 基本知识 Ⅰ、回车和换行 Ⅱ、缓冲区 两个有意思的现象 简单定义 刷新缓冲区 简易倒计时程序 进度条代码 多文件下makefile写法 一代(无任何场景) procs1.h代码 procs1.c代码 主函数main1.c 一代运行结果&#xff1a; 二代 (搭配下载场景) procs2.c代…

在jmeter中使用javascript脚本

工作上遇到一个压力测试的需求&#xff0c;需要测试几个考试相关的接口。其中有一个获取试题详情的接口&#xff0c;和一个提交答题信息的接口。后一个接口以上一接口的返回内容为参数&#xff0c;添加上用户的答案即可。jmeter提供了非常多的方式可以实现该需求&#xff0c;这…

MySQL分组查询有关知识总结

目录 4. 分组查询&#xff08;group by&#xff09; 4.1 概述 4.2 分组函数 4.2.1 单个使用 4.2.2 组合使用 4.2.3 注意&#xff01; 4.3 group by 4.3.1 单个字段 4.3.2 多个字段 4.3.3 提醒&#xff01; 4.4 having 4.5 分组查询演示 4. 分组查询&#xff08;…

稀疏矩阵和稠密矩阵

1.csr_matrix compress sparse row&#xff0c;因此csr是按行压缩的稀疏矩阵。 稀疏矩阵由于0值过多&#xff0c;仅记有值的矩阵位置索引&#xff0c;如下图打印所示。 更多矩阵介绍参考这篇博文【Scipy学习】Scipy中稀疏矩阵用法解析&#xff08;sp.csr_matrix&#xff1b;s…

国家网络身份个人认证方法

申领网络身份认证后&#xff0c;用户会得到一张虚拟的“网络身份证”&#xff0c;它可以向需要实名认证的互联网平台进行认证&#xff0c;不再需要输入姓名和身份证号等信息。 申请方式&#xff1a;各手机应用平台搜索国家网络身份认证即可&#xff08;必须支持NFC才能申请&am…

大模型三种模式Embedding、copilot、Agent

大模型的三种应用模式——Embedding、Copilot、Agent——代表了不同级别的智能化和自动化程度&#xff0c;以及与人类用户的交互方式。下面是每种模式的具体解释&#xff1a; 嵌入模式&#xff08;Embedding Mode&#xff09; 定义&#xff1a;在嵌入模式中&#xff0c;大模型…

每日一题——贪心算法

1005. K 次取反后最大化的数组和 - 力扣&#xff08;LeetCode&#xff09; 题解&#xff1a; 一开始有点理解错他的意思&#xff0c;以为是i是题目中会给出&#xff0c;所以一开始没有什么思路&#xff0c;然后当看了题解之后&#xff0c;就知道了原来i是自己订的&#xff0c…

认识经典蓝牙

现在BLE很流行&#xff0c;本人接触BLE也更多&#xff0c;而对经典蓝牙BR/EDR的开发知之甚少&#xff0c;而且网上关于经典蓝牙的资料也很少&#xff0c;所以&#xff0c;想要有更多了解。 参考&#xff1a; https://www.nordicsemi.cn/news/bluetoothle-and-bluetoothclassic/…

网页设计师必备!10个免费的设计素材网站推荐

当网页设计师使用网页设计材料时&#xff0c;他们会优先考虑那些免费和高质量的网页设计材料网站。找到一个免费和高质量的网页设计材料网站并不容易&#xff0c;有些网站要么需要打开材料网站成员&#xff0c;要么设计材料质量很差。即时设计总结了10个免费的网页设计材料网站…

WEB服务器的详解与部署

WEB服务器也称为网页服务器或HTTP服务器 WEB服务器使用的协议是HTTP或HTTPS HTTP协议默认端口号&#xff1a;TCP 80 HTTPS协议默认端口号&#xff1a;TCP 443 浏览器其实就是 HTTP 客户端 WEB服务器发布软件 微软&#xff1a;IIS(可以发布web网站和FTP站点)linux&#x…

本科阶段最后一次竞赛Vlog——2024年智能车大赛智慧医疗组准备全过程——2Yolo使用之ONNX模型准备

本科阶段最后一次竞赛Vlog——2024年智能车大赛智慧医疗组准备全过程——2Yolo使用之ONNX模型准备 ​ 大家好&#xff0c;因为板端BPU环境&#xff0c;可以加速目标检测的速度&#xff0c;所以今天在此先给大家带来如何准备一个模型&#xff0c;下一期会给大家带来如何在板端部…

PromQL全方位解读:监控与性能分析的关键技术

一、PromQL简介 Prometheus Query Language (PromQL) 是一个专为Prometheus监控系统设计的强大查询语言&#xff0c;它允许用户对收集的时间序列数据进行高效、灵活的查询和分析。PromQL的设计哲学在于提供简洁而强大的语法&#xff0c;以支持复杂的数据检索和实时监控场景。本…

7B 开源模型突破 IMO 形式化证明,霸榜数学竞赛

总览 去年底&#xff0c;著名数学家、菲尔兹奖获得者陶哲轩就提出 AI 将加速数学研究&#xff0c;成为数学家的可靠伙伴&#xff0c;并且在形式化语言 Lean 的帮助下&#xff0c;成功证明了多项式 Freiman-Ruzsa 猜想。在今年的国际数学奥林匹克竞赛&#xff08;IMO&#xff0…