从表达迁移
#初学者 #go #gin

背景:为什么要开关?

nodejs使程序员更简单地使后端编程使您可以在JavaScript中编写服务。尽管Nodejs今天仍然很重要,但JavaScript作为编程语言并不理想写。微软帮助JavaScript社区引入了打字稿,但这并没有解决基本问题:该代码仍被转移到JavaScript。

我可以在JavaScript和Typescript上写一整篇文章。如果您像我这样做,请发表评论。继续前进

源代码

您可以在此处找到本文中的所有代码:GitHub

我强烈建议您仔细阅读代码以更好地理解事物。仅使用本文来理解存储库中决策背后的推理。

选择正确的框架

重点是编写HTTP服务器。 ExpressJS是使用JavaScript构建HTTP服务器的最受欢迎的框架。这里的基本假设将是您已经熟悉它,现在正在尝试切换。我不会专注于自己。重点将是如何在GO中复制相同的ExpressJS结构。对GO的熟悉会有所帮助。

Gin是Golang构建HTTP服务器最受欢迎的框架之一。我的一般建议是在选择您尚不满意的语言时,选择最受欢迎的框架和图书馆。掌握了默认配置和设置后,您可以专注于优化并为用例选择正确的框架。

基本项目设置和目录结构

良好的项目结构总是会随着项目的扩展而帮助您走上路。代码管理取决于编程语言本身。以下是Golang项目结构标准惯例的一些必读文章:

  1. 标准软件包布局:https://www.gobeyond.dev/standard-package-layout
  2. Golang标准 - 项目布局:https://github.com/golang-standards/project-layout

构建GO代码的标准实践

请注意,将所有代码放入GO中不是标准练习。以下目录几乎所有项目都必须:

cmd

这是应用程序的入口点。在JavaScript项目中相当于index.js。您不想在这里放很多代码;只是项目级结构/接口和基本初始化设置。

cmd目录不得包括任何实现逻辑。请注意,根目录不允许具有任何外部依赖关系。它必须只致电/启动各自的应用程序包。

pkg

这是您在应用程序中定义公共软件包的地方。这是您编写实际实施逻辑的地方。 pkg目录中的软件包应在您的应用程序之外共享;请记住。

internal

此目录类似于pkg目录,除了仅用于您的应用程序。这种布局模式由GO编译器本身强制执行。

internal目录还可以具有pkg子目录以更好地分离代码。 pkg子目录将包含在internal软件包中共享的软件包。

vendor

此目录包含您的应用程序依赖项。这是一个自动管理的目录,因此您不必在此处手动更改任何内容。您需要做的就是执行go mod vendor来创建和维护此目录。这里的基本假设是您正在使用GO模块来管理依赖关系。

请注意,您需要将-mod=vendor添加到go build命令中以构建项目。

configs

您的应用程序配置将进入此处。您可能会从环境变量中读取大多数配置。您可以使用此目录从环境变量提取配置并将其存储在结构中。

项目结构示例

cmd/
    __app_name__/
        init.go # entrypoint
pkg/
    __app_name__/
        users/
        auth/
internal/
    __app_name__/
        razorpay/
vendor/
    github.com/
    golang.org/
    google.golang.org/
configs/
    configs.go
build/
    deploy/
        Dockerfile
go.mod
go.sum

路线结构和中间件

cmd/__app_name__目录内的main.go文件将很简单。它的唯一目的是启动项目并按顺序进行设置。

package main

import (
    "fmt"
    "github.com/saranshabd/migrate-from-express/cmd/mfe/http"
    "os"

    "github.com/gin-gonic/gin"
    "github.com/saranshabd/migrate-from-express/cmd/mfe/logger"
    "github.com/saranshabd/migrate-from-express/configs"
)

func main() {

    // Create a Gin server application with default configurations & middlewares
    app := gin.Default()

    // Load all the application HTTP endpoints
    http.InitRoutes(app)

    // Start listening to HTTP requests from the client
    logger.App.Info().Msgf("Firing it up on %d port number.", configs.Configs.Port)
    if err := app.Run(fmt.Sprintf(":%d", configs.Configs.Port)); err != nil {
        logger.App.Error().Err(err).Msg("Could not fire up Gin :/")
        os.Exit(1) // Kill the application
    }
}

所有路由处理程序都将基于上面讨论的目录结构进入internal/__app_name__/目录。

internal/
    middlewares/
    __route_name__/
        routes.go
        handler1.go
        handler2.go

首先,我们必须确保我们发送的所有HTTP响应均为默认情况下。我们可以为此目的创建一个简单的中间件。

package middlewares

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

func JSONResponse() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Writer.Header().Set("Content-Type", "application/json")
        c.Next()
    }
}

其次,我们需要一个地方来导入所有HTTP路由处理程序并将其映射到特定的端点。我们可以将此代码放在cmd目录中,因为我们要做的就是重要的一堆处理程序,并将其映射到各自的端​​点。

package http

import (
    "github.com/gin-gonic/gin"
    "github.com/saranshabd/migrate-from-express/internal/mfe/middlewares"
)

func InitRoutes(app *gin.Engine) {
    // Ensure that all the HTTP responses are by-default in the JSON format
    app.Use(middlewares.JSONResponse())

    // Load all the application HTTP routes
    // ...
}

就是这样!杜松子酒中的路由器和中间件设置与ExpressJS中的相似。我们可以从main.go文件调用此InitRoutes函数。

从不同来源提取请求参数

我们可以继续使用当前项目设置创建一堆HTTP端点。终点将主要集中于从不同来源提取请求参数,例如请求身体,查询和URI。请求参数将全部返回在响应对象中。

我创建了一堆简单的HTTP实用程序功能,以获得更好的代码可读性。这些功能与教程没有任何关系,因此我不会介绍细节。您可以在GitHub上找到所有源代码。

GO将功能视为一流的公民,因此您的HTTP设置将只是一堆功能。杜松子酒中的HTTP处理程序需要一个参数:杜松子酒上下文。上下文是GO的基本面之一。如果您还不熟悉它,那么我建议您在继续之前花一些时间来理解它们。

从请求主体提取

GO在开箱即用的基本验证中提供了一个漂亮的绑定软件包。可以将绑定模块与Nodej中的JOI -package进行比较。两者之间的主要区别在于,绑定是GO语言本身的一部分,因此我们不必单独定义验证逻辑。

这就是典型的http处理程序的样子:

type CreateExtractParamsArgs struct {
    Arg1 string `json:"arg1" binding:"required"`
    Arg2 int64  `json:"arg2" binding:"required"`
    Arg3 bool   `json:"arg3,omitempty"` // optional
}

func CreateExtractParams(c *gin.Context) {
    // Extract the parameters from the request.body & validate the object received
    var args CreateExtractParamsArgs
    if err := c.BindJSON(&args); err != nil {
        // Return HTTP 400 if invalid params are passed
        http.InvalidRequestParams(c)
        return
    }

    // Return the params object in response if everything is fine
    http.RespondOkay(c, "Request parameters extracted successfully", args)
}

请注意,我正在使用自定义http软件包,这只是我创建的实用程序软件包。您可以参考下面的RespondOkay功能:

func RespondOkay(c *gin.Context, message string, data any) {
    Respond(c, http.StatusOK, true, message, data)
}

func Respond(c *gin.Context, statusCode int, success bool, message string, data any) {
    c.JSON(statusCode, &gin.H{
        "success": success,
        "message": message,
        "data":    data,
    })
}

omitempty属性将在应对时删除该字段,让我们说json。字段的结构值将是类型的默认值。例如。如果您访问args.Arg3,那么您将在响应中收到false提防这种行为。这不是javascript!

从查询参数提取

我们可以使用相同的模式使用绑定软件包来验证查询参数。我们将使用BindQuery功能而不是BindJSON。其他一切看起来都一样。

type GetExtractParamsArgs struct {
    Arg1 string `form:"arg1" json:"arg1" binding:"required"`
    Arg2 int64  `form:"arg2" json:"arg2" binding:"required"`
    Arg3 bool   `form:"arg3" json:"arg3,omitempty"` // optional
}

func GetExtractParams(c *gin.Context) {
    // Extract the parameters from the request.query & validate the object received
    var args GetExtractParamsArgs
    if err := c.BindQuery(&args); err != nil {
        // Return HTTP 400 if invalid params are passed
        http.InvalidRequestParams(c)
        return
    }

    // Return the params object in response if everything if fine
    http.RespondOkay(c, "Request parameres extracted successfully", args)
}

从URI提取

再次,我们可以扩展绑定软件包以验证路径参数,即/path/:arg/。您可以在杜松子酒中定义路径参数:

r.GET("/:specificPath/", GetSpecificExtractParams)

我们可以将BindQuery功能与BindUri交换,并保持所有相同的功能。到现在为止,您必须掌握GO。

type GetSpecificExtractParamsArgs struct {
    SpecificPath string `uri:"specificPath" json:"specificPath" binding:"required"`
}

func GetSpecificExtractParams(c *gin.Context) {
    // Extract the parameters from the request.params & validate the object received
    var args GetSpecificExtractParamsArgs
    if err := c.BindUri(&args); err != nil {
        // Return HTTP 400 if invalid params are passed
        http.InvalidRequestParams(c)
        return
    }

    // Return the params object in response if everything is fine
    http.RespondOkay(c, "Request parameters extracted successfully", args)
}

基于标题的授权

身份验证和授权是几乎所有其余API的基础。 auth通常建在HTTP标头的顶部。我们可以使用绑定软件包来验证在请求中收到的标头,并写入中间件以在此基础上添加自定义逻辑。

这是Auth标题的一个简单示例,用于检查传入请求的源:

type VerifiedSourcesArgs struct {
    VerifiedSource string `header:"verified-source" binding:"required"`
}

const (
    AcceptedSource = "accepted-source"
)

func IsVerifiedSource() gin.HandlerFunc {
    return func(c *gin.Context) {
        // Extract the header value from the HTTP request
        var args VerifiedSourcesArgs
        if err := c.ShouldBindHeader(&args); err != nil {
            http.InvalidHeaders(c)
            return
        }

        // Check if the source of the incoming request is accepted
        if args.VerifiedSource != AcceptedSource {
            http.InvalidHeaders(c)
            return
        }

        // Continue with the chain of middlewares/handlers
        c.Next()
    }
}

杜松子酒中的Next功能

Gin Middlewares看起来与Expressjs Middlewares非常相似。但是,两者之间有一个讨厌的区别。 Next功能。 您不必在杜松子酒中间件中调用Next功能。

这是一个重要的区别,因为您不能期望请求仅仅因为您从中间件返回而无需调用Next函数而终止。当您的中间件终止时,仍将调用下一个链函数。除非您中止请求。

func AbortAndRespond(c *gin.Context, statusCode int, message string, data any) {
    c.AbortWithStatusJSON(statusCode, &gin.H{
        "success": false,
        "message": message,
        "data":    data,
    })
}

而不是调用c.JSON,我们需要调用c.AbortWithStatusJSON函数以在中止请求之前设置状态代码和JSON响应。这是一个令人讨厌的区别,特别是对于来自Nodejs背景的程序员而言。当心!


就是这样!我们设置了HTTP路由,从不同来源验证了传入的请求数据,并为应用程序创建了AUTH MiddleWares。

这仅仅是开始。该应用程序没有与数据库或第三方服务进行交互;两者都是大多数服务的基础知识。这是针对系列的下一篇文章:)

如果我错过了或可以更好地解释的话,请发表评论。

欢呼! ð