1 常用设计

1.1 单点登录:gin+cas认证

01.CAS认证客户端实现
    a.背景
        在使用Golang对接CAS认证时,发现网上的资料大多使用Golang的CAS客户端包:gopkg.in/cas.v2。
        然而,在接入CAS服务器后,出现了不断跳转和重定向的问题,无法继续执行认证成功后的操作。
        为了解决该问题,基于CAS认证原理,自行实现了一个CAS认证客户端。
    b.定义响应结构体
        a.结构体定义
            首先,需要定义CAS认证成功后的响应结构体:
            // model/cas.go
            type CasServiceResponse struct {
                XMLName xml.Name `xml:"serviceResponse"`
                Data    struct {
                    SFRZH      string `xml:"user"`
                    Attributes struct {
                        Uid      string `xml:"uid"`
                        UserName string `xml:"userName"`
                    } `xml:"attributes"`
                } `xml:"authenticationSuccess"`
            }
    c.编写CAS认证逻辑
        a.核心逻辑
            // utils/cas.go
            package utils

            import (
                "encoding/xml"
                "errors"
                "fmt"
                "github.com/gin-gonic/gin"
                "go.uber.org/zap"
                "io"
                "net/http"
                "roomlive-go/global"
                "roomlive-go/model/cas"
                "roomlive-go/model/user"
                "strings"
            )

            func IsAuthentication(w http.ResponseWriter, r *http.Request, casServerUrl string) (bool, *cas.CasServiceResponse) {
                if !hasTicket(r) {
                    redirectToCasServer(w, r, casServerUrl)
                    return false, nil
                }
                localUrl := getLocalUrl(r)
                ok, err, res := validateTicket(localUrl, casServerUrl)
                global.SYSLOG.Debug("cas validateTicket", zap.Bool("ok", ok), zap.Error(err), zap.Any("res", res))
                if !ok {
                    redirectToCasServer(w, r, casServerUrl)
                    return false, nil
                }
                global.SYSLOG.Info("user authenticated", zap.String("sfrzh", res.Data.SFRZH))
                return true, res
            }

            func redirectToCasServer(w http.ResponseWriter, r *http.Request, casServerUrl string) {
                casServerUrl = casServerUrl + "/login?service=" + getLocalUrl(r)
                http.Redirect(w, r, casServerUrl, http.StatusFound)
            }

            func validateTicket(localUrl, casServerUrl string) (bool, error, *cas.CasServiceResponse) {
                casServerUrl = casServerUrl + "/serviceValidate?service=" + localUrl
                res, err := http.Get(casServerUrl)
                if err != nil {
                    return false, err, nil
                }
                defer res.Body.Close()
                data, err := io.ReadAll(res.Body)
                if err != nil {
                    return false, err, nil
                }
                casRes, err := ParseCasUserInfo(data)
                if err != nil {
                    return false, err, nil
                }
                if casRes.Data.SFRZH == "" {
                    return false, errors.New("authentication failed"), nil
                }
                return true, nil, casRes
            }

            func getLocalUrl(r *http.Request) string {
                scheme := "http://"
                if r.TLS != nil {
                    scheme = "https://"
                }
                url := strings.Join([]string{scheme, r.Host, r.RequestURI}, "")
                fmt.Printf("url: %v\n", url)
                slice := strings.Split(url, "?")
                if len(slice) > 1 {
                    localUrl := slice[0]
                    urlParamStr := ensureOneTicketParam(slice[1])
                    url = localUrl + "?" + urlParamStr
                }
                return url
            }

            func ensureOneTicketParam(urlParams string) string {
                if len(urlParams) == 0 || !strings.Contains(urlParams, "ticket") {
                    return urlParams
                }
                sep := "&"
                params := strings.Split(urlParams, sep)
                newParams := ""
                ticket := ""
                for _, value := range params {
                    if strings.Contains(value, "ticket") {
                        ticket = value
                        continue
                    }
                    if len(newParams) == 0 {
                        newParams = value
                    } else {
                        newParams = newParams + sep + value
                    }
                }
                newParams = newParams + sep + ticket
                return newParams
            }

            func getTicket(r *http.Request) string {
                return r.FormValue("ticket")
            }

            func hasTicket(r *http.Request) bool {
                t := getTicket(r)
                return len(t) != 0
            }

            func ParseCasUserInfo(data []byte) (*cas.CasServiceResponse, error) {
                var casResponse cas.CasServiceResponse
                if err := xml.Unmarshal(data, &casResponse); err != nil {
                    return nil, err
                }
                return &casResponse, nil
            }

            func GetUser(c *gin.Context) (*user.User, error) {
                if res, exists := c.Get("casResponse"); !exists {
                    return nil, errors.New("cas authentication failed")
                } else {
                    casRes := res.(*cas.CasServiceResponse)
                    waitUser := &user.User{
                        UserName: casRes.Data.Attributes.UserName,
                        SFRZH:    casRes.Data.SFRZH,
                    }
                    return waitUser, nil
                }
            }
    d.在Gin中间件中应用
        a.中间件应用
            将CAS认证逻辑应用到Gin的中间件中:
            func CASMiddleware() gin.HandlerFunc {
                return func(c *gin.Context) {
                    isAuth, casResponse := utils.IsAuthentication(c.Writer, c.Request, utils.CASServer)
                    if !isAuth {
                        c.Abort()
                        return
                    }
                    c.Set("casResponse", casResponse)
                    c.Next()
                    return
                }
            }
    e.总结
        这里只根据CAS原理实现了一个基本的CAS客户端认证流程,包括了请求检查、重定向处理、票据验证和用户信息解析,并通过Gin中间件集成到了Web应用程序中。

1.3 middleware/recover.go

00.原版
    package middleware
    
    import (
    	"fmt"
    	"net/http"
    	"runtime/debug"
    
    	"github.com/QuantumNous/new-api/common"
    	"github.com/gin-gonic/gin"
    )
    
    // 变体思路:
    // 1) 基础版:保持匿名函数工厂,直接返回 gin.HandlerFunc。
    // 2) 命名函数版:提取独立 handler(如 relayPanicRecover),工厂返回命名函数,便于链式或函数式组合。
    // 3) 配置版:工厂接受可选参数(日志输出函数、错误响应构造器),返回定制化 handler。
    // 4) 链式适配版:作为中间件适配器,接受/返回下游 handler 或 builder,方便在自定义链路中插入。
    // 5) 无状态纯函数版:移除包级依赖,纯输入输出,便于测试与函数式管线组合。
    // 6) 钩子注入版:支持注入日志/报警/指标回调或响应格式化器,实现可插拔扩展。
    func RelayPanicRecover() gin.HandlerFunc {
    	return func(c *gin.Context) {
    		defer func() {
    			if err := recover(); err != nil {
    				common.SysLog(fmt.Sprintf("panic detected: %v", err))
    				common.SysLog(fmt.Sprintf("stacktrace from panic: %s", string(debug.Stack())))
    				c.JSON(http.StatusInternalServerError, gin.H{
    					"error": gin.H{
    						"message": fmt.Sprintf("Panic detected, error: %v. Please submit a issue here: https://github.com/Calcium-Ion/new-api", err),
    						"type":    "new_api_panic",
    					},
    				})
    				c.Abort()
    			}
    		}()
    		c.Next()
    	}
    }

01.变体1: 基础版(匿名函数工厂),这是你提供的原始版本,也是最常见和直接的实现方式。
    a.代码示例:
        ---
        package middleware

        import (
            "fmt"
            "net/http"
            "runtime/debug"

            "github.com/gin-gonic/gin"
            // 假设 common 包存在,并提供了 SysLog 函数
            // "your_project/pkg/common"
        )

        // 为了示例能独立运行,我们模拟一个 common.SysLog
        var common = new(mockCommon)
        type mockCommon struct{}
        func (m *mockCommon) SysLog(msg string) {
            fmt.Println(msg)
        }

        // RelayPanicRecover 是一个中间件工厂,返回一个用于恢复 panic 的 gin.HandlerFunc
        func RelayPanicRecover() gin.HandlerFunc {
            return func(c *gin.Context) {
                defer func() {
                    if err := recover(); err != nil {
                        // 记录 panic 信息和堆栈
                        common.SysLog(fmt.Sprintf("panic detected: %v", err))
                        common.SysLog(fmt.Sprintf("stacktrace from panic: %s", string(debug.Stack())))

                        // 返回统一的错误响应
                        c.JSON(http.StatusInternalServerError, gin.H{
                            "error": gin.H{
                                "message": fmt.Sprintf("Panic detected, error: %v. Please submit a issue here: https://github.com/Calcium-Ion/new-api", err),
                                "type":    "new_api_panic",
                            },
                        })
                        // 中断请求链
                        c.Abort()
                    }
                }()
                // 继续处理请求
                c.Next()
            }
        }

        // 使用示例
        func main_variant1() {
            router := gin.Default()
            router.Use(RelayPanicRecover())
            // ... 定义你的路由
        }
        ---
    b.代码解说:
        a.优点
            简洁明了,将逻辑封装在闭包内,易于理解。
        b.缺点
            恢复逻辑(func(c *gin.Context))是匿名的,不易在其他地方直接复用或测试。同时,硬编码了对 common.SysLog 的依赖。

02.变体2: 命名函数版,将处理逻辑提取到一个命名的函数中,使代码结构更清晰。
    a.代码示例:
        ---
        package middleware

        import (
            "fmt"
            "net/http"
            "runtime/debug"
            "github.com/gin-gonic/gin"
        )

        // relayPanicRecoverHandler 是具体的中间件处理函数
        func relayPanicRecoverHandler(c *gin.Context) {
            defer func() {
                if err := recover(); err != nil {
                    common.SysLog(fmt.Sprintf("panic detected: %v", err))
                    common.SysLog(fmt.Sprintf("stacktrace from panic: %s", string(debug.Stack())))
                    c.JSON(http.StatusInternalServerError, gin.H{
                        "error": gin.H{
                            "message": fmt.Sprintf("Panic detected, error: %v. Please submit a issue here: https://github.com/Calcium-Ion/new-api", err),
                            "type":    "new_api_panic",
                        },
                    })
                    c.Abort()
                }
            }()
            c.Next()
        }

        // RelayPanicRecoverFactory 工厂函数现在只返回命名函数
        func RelayPanicRecoverFactory() gin.HandlerFunc {
            return relayPanicRecoverHandler
        }

        // 使用示例
        func main_variant2() {
            router := gin.Default()
            // 使用工厂函数
            router.Use(RelayPanicRecoverFactory())

            // 或者直接使用命名函数(如果它符合 gin.HandlerFunc 类型)
            // router.Use(relayPanicRecoverHandler)
        }
        ---
    b.代码解说:
        a.优点
            relayPanicRecoverHandler 是一个独立的、可导出的函数(如果需要),便于单元测试和直接引用。代码职责分离更清晰:工厂负责创建,处理函数负责执行。
        b.缺点
            仍然存在对 common.SysLog 的硬编码依赖。

03.变体3: 配置版,通过传递配置对象,使中间件的行为可定制。
    a.代码示例:
        ---
        package middleware

        import (
            "fmt"
            "net/http"
            "runtime/debug"
            "github.com/gin-gonic/gin"
        )

        // Logger 定义了一个简单的日志接口
        type Logger interface {
            Printf(format string, v ...interface{})
        }

        // ErrorResponseBuilder 定义了如何根据错误构建响应
        type ErrorResponseBuilder func(c *gin.Context, err interface{})

        // PanicRecoverConfig 用于配置 panic 恢复中间件
        type PanicRecoverConfig struct {
            Logger               Logger
            ResponseBuilder      ErrorResponseBuilder
        }

        // defaultResponseBuilder 是一个默认的响应构造器
        func defaultResponseBuilder(c *gin.Context, err interface{}) {
            c.JSON(http.StatusInternalServerError, gin.H{
                "error": gin.H{
                    "message": fmt.Sprintf("Internal server error: %v", err),
                    "type":    "server_panic",
                },
            })
        }

        // NewConfigurablePanicRecover 创建一个可配置的 panic 恢复中间件
        func NewConfigurablePanicRecover(config PanicRecoverConfig) gin.HandlerFunc {
            // 如果未提供 Logger,使用默认的 fmt.Printf
            if config.Logger == nil {
                config.Logger = &defaultLogger{}
            }
            // 如果未提供 ResponseBuilder,使用默认的
            if config.ResponseBuilder == nil {
                config.ResponseBuilder = defaultResponseBuilder
            }

            return func(c *gin.Context) {
                defer func() {
                    if err := recover(); err != nil {
                        config.Logger.Printf("panic detected: %v\nstacktrace: %s", err, string(debug.Stack()))
                        config.ResponseBuilder(c, err)
                        c.Abort()
                    }
                }()
                c.Next()
            }
        }

        // defaultLogger 实现了 Logger 接口
        type defaultLogger struct{}
        func (l *defaultLogger) Printf(format string, v ...interface{}) {
            fmt.Printf(format+"\n", v...)
        }

        // 使用示例
        func main_variant3() {
            router := gin.Default()

            // 使用自定义配置
            config := PanicRecoverConfig{
                Logger: &defaultLogger{},
                ResponseBuilder: func(c *gin.Context, err interface{}) {
                    c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "msg": "系统出现意外,请联系管理员"})
                },
            }
            router.Use(NewConfigurablePanicRecover(config))
        }
        ---
    b.代码解说:
        a.优点
            高度灵活。用户可以轻松替换日志实现(例如,从 fmt 换成 logrus 或 zap),并完全自定义错误响应的 JSON 结构,而无需修改中间件的源代码。
        b.缺点
            实现稍微复杂一些,需要定义额外的 Config 结构和接口。

04.变体4: 链式适配版(装饰器模式),创建一个函数,用于“装饰”或“包裹”另一个 gin.HandlerFunc,为其添加 panic 恢复能力。
    a.代码示例:
        ---
        package middleware

        import (
            "fmt"
            "net/http"
            "runtime/debug"
            "github.com/gin-gonic/gin"
        )

        // AdaptWithPanicRecovery 是一个适配器,它接收一个 handler 并为其添加 panic 恢复功能
        func AdaptWithPanicRecovery(handler gin.HandlerFunc) gin.HandlerFunc {
            return func(c *gin.Context) {
                defer func() {
                    if err := recover(); err != nil {
                        common.SysLog(fmt.Sprintf("panic in handler detected: %v", err))
                        common.SysLog(fmt.Sprintf("stacktrace from panic: %s", string(debug.Stack())))
                        c.JSON(http.StatusInternalServerError, gin.H{"error": "panic occurred"})
                        c.Abort()
                    }
                }()
                // 调用被包裹的原始 handler
                handler(c)
            }
        }

        // 使用示例
        func main_variant4() {
            router := gin.Default()

            // 假设我们有一个可能 panic 的 handler
            mightPanicHandler := func(c *gin.Context) {
                panic("something went wrong!")
            }

            // 使用适配器包裹这个 handler
            safeHandler := AdaptWithPanicRecovery(mightPanicHandler)

            // 注册包裹后的安全 handler
            router.GET("/test-panic", safeHandler)

            // 注意:这种模式通常用于单个路由,而不是全局 router.Use()。
            // 在 router.Use() 中,panic 恢复中间件应该调用 c.Next() 而不是特定的 handler。
            // 此处为了展示“装饰”特定函数的概念。
        }
        ---
    b.代码解说:
        a.优点
            清晰地体现了装饰器模式,非常适合在函数式编程风格中对特定处理函数进行功能增强,而不是全局应用。
        b.缺点
            c.Next() 不再被调用,这意味着这个适配器本身就是一个终点处理器(或者说它内部处理了调用链)。这与 Gin 的 router.Use() 期望的中间件行为(调用 c.Next() 以传递给下一个)不同,更适用于包裹独立的 HandlerFunc。

05.变体5: 无状态纯函数版,移除对全局或包级变量(如 common.SysLog)的依赖,所有依赖都通过参数传入。
    a.代码示例:
        ---
        package middleware

        import (
            "fmt"
            "net/http"
            "runtime/debug"
            "github.com/gin-gonic/gin"
        )

        // LogFunc 定义了日志函数的签名
        type LogFunc func(format string, args ...interface{})

        // NewStatelessPanicRecover 创建一个无状态的 panic 恢复中间件
        // 它显式地接收所有外部依赖(这里是日志函数)。
        func NewStatelessPanicRecover(log LogFunc) gin.HandlerFunc {
            return func(c *gin.Context) {
                defer func() {
                    if err := recover(); err != nil {
                        log("panic detected: %v", err)
                        log("stacktrace from panic: %s", string(debug.Stack()))
                        c.JSON(http.StatusInternalServerError, gin.H{
                            "error": gin.H{
                                "message": fmt.Sprintf("Panic detected, error: %v", err),
                                "type":    "stateless_panic",
                            },
                        })
                        c.Abort()
                    }
                }()
                c.Next()
            }
        }

        // 使用示例
        func main_variant5() {
            router := gin.Default()

            // 准备一个日志函数,可以是任何符合 LogFunc 签名的函数
            myLogger := func(format string, args ...interface{}) {
                fmt.Printf("[MyCustomLogger] "+format+"\n", args...)
            }

            // 将日志函数作为依赖注入
            router.Use(NewStatelessPanicRecover(myLogger))
        }
        ---
    b.代码解说:
        a.优点
            极易测试和复用。由于没有隐藏的依赖,你可以轻松地在单元测试中提供一个 mock 的日志函数来验证其行为。函数的行为完全由其输入决定,符合纯函数的思想。
        b.缺点
            如果依赖项很多,函数签名可能会变得很长。但对于只有一两个依赖项的情况,这是非常清晰的模式。

06.变体6: 钩子注入版,这是配置版的扩展,允许在 panic 发生时执行一系列自定义操作(钩子)。
    a.代码示例:
        ---
        package middleware

        import (
            "fmt"
            "net/http"
            "runtime/debug"
            "github.com/gin-gonic/gin"
        )

        // OnPanicHook 定义了 panic 发生时触发的钩子函数签名
        type OnPanicHook func(c *gin.Context, err interface{}, stack []byte)

        // PanicRecoverHooks 包含了一系列可注入的钩子
        type PanicRecoverHooks struct {
            Hooks []OnPanicHook
        }

        // NewPanicRecoverWithHooks 创建一个支持钩子注入的 panic 恢复中间件
        func NewPanicRecoverWithHooks(hooks PanicRecoverHooks) gin.HandlerFunc {
            return func(c *gin.Context) {
                defer func() {
                    if err := recover(); err != nil {
                        stack := debug.Stack()

                        // 依次执行所有注入的钩子
                        for _, hook := range hooks.Hooks {
                            hook(c, err, stack)
                        }

                        // 默认响应
                        if !c.IsAborted() {
                            c.JSON(http.StatusInternalServerError, gin.H{"error": "server panic"})
                            c.Abort()
                        }
                    }
                }()
                c.Next()
            }
        }

        // 使用示例
        func main_variant6() {
            router := gin.Default()

            // 1. 定义一个日志钩子
            logHook := func(c *gin.Context, err interface{}, stack []byte) {
                fmt.Printf("[LogHook] Panic: %v\nStack: %s\n", err, string(stack))
            }

            // 2. 定义一个告警钩子(例如,发送到 Sentry 或企业微信)
            alertHook := func(c *gin.Context, err interface{}, stack []byte) {
                // 在这里实现发送告警的逻辑
                fmt.Printf("[AlertHook] Sending alert for panic: %v\n", err)
            }

            // 3. 定义一个指标上报钩子
            metricsHook := func(c *gin.Context, err interface{}, stack []byte) {
                // 在这里实现 panic 次数+1 的监控指标上报
                fmt.Println("[MetricsHook] Incrementing panic counter.")
            }

            // 创建并注入钩子
            recoveryHooks := PanicRecoverHooks{
                Hooks: []OnPanicHook{logHook, alertHook, metricsHook},
            }
            router.Use(NewPanicRecoverWithHooks(recoveryHooks))
        }
        ---
    b.代码解说:
        a.优点
            极高的可扩展性。你可以将日志、告警、指标、自定义响应等不同职责的逻辑完全解耦,实现为独立的钩子函数。在需要添加新功能(如新的告警渠道)时,只需添加一个新的钩子,而无需修改中间件核心代码,符合“开闭原则”。
        b.缺点
            实现最为复杂,但对于大型、高可维护性要求的项目来说,这种模式的价值非常高。

1.2 internal/web/middleware/logger.md

01.代码
    a.middleware/logger.go
        package middleware

        import (
            "fmt"
            "time"

            "github.com/gin-gonic/gin"
        )

        // Logger 日志中间件
        func Logger() gin.HandlerFunc {
            return func(c *gin.Context) {
                // 开始时间
                startTime := time.Now()

                // 处理请求
                c.Next()

                // 结束时间
                endTime := time.Now()

                // 执行时间
                latencyTime := endTime.Sub(startTime)

                // 请求方式
                reqMethod := c.Request.Method

                // 请求路由
                reqUri := c.Request.RequestURI

                // 状态码
                statusCode := c.Writer.Status()

                // 请求IP
                clientIP := c.ClientIP()

                // 日志格式
                fmt.Printf("[GIN] %s | %3d | %13v | %15s | %-7s %s\n",
                    endTime.Format("2006-01-02 15:04:05"),
                    statusCode,
                    latencyTime,
                    clientIP,
                    reqMethod,
                    reqUri,
                )
            }
        }

02.Gin中间件开发笔记
    a.中间件的标准签名
        a.说明
            Gin 中间件本质上是一个 gin.HandlerFunc 类型的函数。一个标准的、可复用的中间件通常被包裹在一个返回 gin.HandlerFunc 的函数中。
        b.示例
            // Logger() 是一个工厂函数,返回真正的中间件处理函数
            func Logger() gin.HandlerFunc {
                // 这个匿名函数才是中间件的核心
                return func(c *gin.Context) {
                    // ... 逻辑
                }
            }
    b.c.Next():核心流程控制
        a.说明
            c.Next() 是中间件的“分水岭”。
        b.说明
            c.Next() 之前的代码:在请求到达业务处理函数(Handler)之前执行。适合做身份验证、参数预处理等。
            c.Next() 之后的代码:在业务处理函数执行完毕之后执行。适合做日志记录、响应数据处理、异常捕获等。
            这个机制构成了 Gin 的“洋葱模型”,请求一层层进入,响应一层层返回。
    c.gin.Context:数据和操作的载体
        a.说明
            *gin.Context (通常简写为 c) 是中间件中最重要的对象,它包含了所有与当前 HTTP 请求和响应相关的信息和方法。
        b.获取请求信息:
            1.HTTP方法: c.Request.Method
            2.请求URI: c.Request.RequestURI
            3.客户端IP: c.ClientIP()
        c.获取响应信息:
            1.HTTP状态码: c.Writer.Status() (注意:必须在 c.Next() 之后才能获取到最终状态)
        d.控制流程:
            1.c.Next(): 继续处理链。
            2.c.Abort(): 中断处理链。
    d.计算请求耗时
        a.说明
            通过在 c.Next() 前后分别记录时间,可以精确计算出整个请求(包括业务处理)的耗时。
        b.示例
            startTime := time.Now()
            c.Next() // 执行业务逻辑
            endTime := time.Now()
            latencyTime := endTime.Sub(startTime) // 计算差值
    e.如何使用中间件
        a.说明
            编写好的中间件需要注册到 Gin 引擎(Engine)或路由组(RouterGroup)中才能生效。
        b.示例
            // 创建一个 Gin 引擎
            router := gin.Default()

            // 注册为全局中间件,对所有路由生效
            router.Use(middleware.Logger())

            // 路由定义
            router.GET("/ping", func(c *gin.Context) {
                c.JSON(200, gin.H{"message": "pong"})
            })

03.Gin基础语法开发笔记
    a.包声明与导入
        a.package: 每个 Go 文件都必须在开头使用 package 声明其所属的包。它是 Go 组织代码的基本单元。
            package middleware
        b.import: 用于导入其他包。可以使用圆括号 () 将多个导入语句组合在一起,提高可读性。
            import (
                "fmt"  // 用于格式化 I/O
                "time" // 提供时间相关功能
            )
    b.函数:定义、返回与高阶用法
        a.函数定义
            使用 func 关键字,格式为 func 函数名(参数列表) 返回值类型 { ... }。
        b.高阶函数
            Go 中的函数是“一等公民”,可以作为参数传递,也可以作为另一个函数的返回值。下面的 Logger 函数就返回了一个函数类型 (gin.HandlerFunc)。
            -------------------------------------------------------------------------------------------------
            // Logger 函数返回一个函数
            func Logger() gin.HandlerFunc {
                // ...
            }
        c.匿名函数与闭包
            可以在代码中直接定义一个没有名字的函数,即匿名函数。当匿名函数被返回时,它可以访问并持有其外部作用域的变量,形成闭包。
            -------------------------------------------------------------------------------------------------
            return func(c *gin.Context) { // 这是一个匿名函数
                // 函数体
            }
    c.变量声明与赋值
        a.短变量声明 :=
            这是在函数内部最常用的变量声明和初始化方式。它会根据右侧的值自动推断变量类型。
            -------------------------------------------------------------------------------------------------
            // Go 自动推断 startTime 的类型为 time.Time
            startTime := time.Now()
        b.限制
            := 只能在函数内部使用,不能用于包级别的变量声明。
    d.结构体 (Struct) 的字段与方法调用
        a.说明
            Go 使用 . 操作符来访问结构体的字段或调用其方法。
            即使变量是指针类型,Go 也允许直接使用 . 来访问,它会自动进行解引用。
        b.示例
            // 访问字段 (c 是 *gin.Context 指针)
            reqMethod := c.Request.Method

            // 调用方法 (endTime 是 time.Time 结构体)
            latencyTime := endTime.Sub(startTime)
    e.fmt.Printf 格式化输出
        a.说明
            fmt.Printf 是一个非常强大的格式化输出函数,它使用占位符(Verb)来格式化数据。
        b.示例
            %s: 输出字符串。
            %d: 输出十进制整数。
            %v: 按默认格式输出值。
            可以在占位符中添加数字来控制宽度和对齐,如 %3d (宽度为3的整数)、%-7s (宽度为7,左对齐的字符串)。
    f.Go 特有的时间格式化
        a.说明
            在 Go 中,格式化或解析时间必须使用一个固定的参考时间点:2006-01-02 15:04:05 (可以记忆为:1月2日3点4分5秒,2006年,时区-0700)。
            你通过调整这个“模板”的布局来得到你想要的格式。
        b.示例
            // 将时间格式化为 "年-月-日 时:分:秒"
            endTime.Format("2006-01-02 15:04:05")