总结网站推广策划思路的内容十大收益最好的自媒体平台

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

总结网站推广策划思路的内容,十大收益最好的自媒体平台,wordpress主题制作价格,建立手机个人网站【Gin】深度解析#xff1a;在Gin框架中优化应用程序流程的责任链设计模式(下) 大家好 我是寸铁#x1f44a; 【Gin】深度解析#xff1a;在Gin框架中优化应用程序流程的责任链设计模式(下)✨ 喜欢的小伙伴可以点点关注 #x1f49d; 前言 本次文章分为上下两部分#xf…【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.handlersc.index 调用 index 所指的函数并将当前的 Context 对象 c 传递给它。 循环继续直到 index 超过了 handlers 的长度或者某个中间件函数调用了 Next() 以停止后续执行。 图43 定义拦截索引 代码位置context.go的50-51行 abortIndex 是一个 int8 类型的常量。 math.MaxInt8 是 int8 类型能表示的最大整数通常为 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©最后打印 “FinalHandler 得到响应”。 图106 定义视图层代码 Home视图层部分 fmt.Println(Home Receiving……) 放在 Home 函数中的最开始这样在请求到达时会立即打印 “Home Receiving……”。 c.String(200, Home Receiving……) 在完成日志记录后立即向客户端发送 “Home Receiving……” 响应 责任链的构建和运行 图107 客户端构建责任链 解读: 在 main 函数中先创建了 Gin 引擎实例 r。再实例化了 Middleware1、Middleware2 和 FinalHandler。之后使用 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© 处理实际的业务逻辑。 Home© 会在控制台打印 “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稳定的有序遍历的方式