Mix Go is a complete system for rapid development based on Go, similar to the front-end Vue CLI, providing:

  • throughmix-go/mixcliImplementation of interactive project scaffolding:
    • Can be generatedcli.api.web.grpcMultiple project codes
    • The generated code is out of the box
    • Optional.envEnvironment configuration
    • Optional.yml..json..tomlEqual independent configuration
    • Optional usegorm.xormThe database of
    • Optional uselogrus.zapThe logging library
  • throughmix-go/xcliImplementation of command line prototype development.
  • Based on themix-go/xdiDI, IoC container.

Github | Gitee

  • github.com/mix-go/mix
  • gitee.com/mix-go/mix

Quick start

The installation

go get github.com/mix-go/mixcli
Copy the code

Create a project

$mixcli new hello Use the arrow keys to navigate: ↓ ↑ → ←? Select Project type: ▸ CLI API Web (contains the websocket) gRPCCopy the code

Technical communication

Zhihu: www.zhihu.com/people/onan… Weibo: weibo.com/onanying Official QQ group: 284806582, 825122875, knock password: GOer

Write a CLI program

First we create a project skeleton using the mixcli command:

$mixcli new hello Use the arrow keys to navigate: ↓ ↑ → ←? Select Project type: ▸ CLI API Web (contains the websocket) gRPCCopy the code

Generate skeleton directory structure as follows:

. ├ ─ ─ the README. Md ├ ─ ─ bin ├ ─ ─ commands ├ ─ ─ the conf ├ ─ ─ configor ├ ─ ─ di ├ ─ ─ dotenv ├ ─ ─. Mod ├ ─ ─. Sum ├ ─ ─ logs └ ─ ─ main. GoCopy the code

Mian. Go file:

  • xcli.AddCommandMethod passed incommands.CommandsDefines all commands
package main

import (
  "github.com/mix-go/cli-skeleton/commands"
  _ "github.com/mix-go/cli-skeleton/configor"
  _ "github.com/mix-go/cli-skeleton/di"
  _ "github.com/mix-go/cli-skeleton/dotenv"
  "github.com/mix-go/dotenv"
  "github.com/mix-go/xcli"
)

func main(a) {
  xcli.SetName("app").
    SetVersion("0.0.0 - alpha").
    SetDebug(dotenv.Getenv("APP_DEBUG").Bool(false)) xcli.AddCommand(commands.Commands...) .Run() }Copy the code

Commands/main. Go file:

We can customize commands here to see more

  • RunIDefines thehelloThe command execution interface can also be usedRunSet an anonymous function
package commands

import (
  "github.com/mix-go/xcli"
)

var Commands = []*xcli.Command{
  {
    Name:  "hello",
    Short: "\tEcho demo",
    Options: []*xcli.Option{
      {
        Names: []string{"n"."name"},
        Usage: "Your name",
      },
      {
        Names: []string{"say"},
        Usage: "\tSay ...",
      },
    },
    RunI: &HelloCommand{},
  },
}
Copy the code

Commands/hello. Go file:

The business code is written in the main method of the HelloCommand structure

  • Can be used in codeflagGets command line arguments,To view more
package commands

import (
  "fmt"
  "github.com/mix-go/xcli/flag"
)

type HelloCommand struct{}func (t *HelloCommand) Main(a) {
  name := flag.Match("n"."name").String("OpenMix")
  say := flag.Match("say").String("Hello, World!")
  fmt.Printf("%s: %s\n", name, say)
}
Copy the code

Next we compile the above program:

  • linux & macOS
go build -o bin/go_build_main_go main.go
Copy the code
  • win
go build -o bin/go_build_main_go.exe main.go
Copy the code

To view the help information about all commands:

$ cd bin $ ./go_build_main_go Usage: ./go_build_main_go [OPTIONS] COMMAND [opt...]  Global Options: -h, --help Print usage -v, --version Print version information Commands: hello Echo demo Run './go_build_main_go COMMAND --help' for more information on a command. Developed with Mix Go framework. (openmix.org/mix-go)Copy the code

See help for the hello command written above:

$ ./go_build_main_go hello --help Usage: ./go_build_main_go hello [opt...]  Command Options: -n, --name Your name --say Say ... Developed with Mix Go framework. (openmix.org/mix-go)Copy the code

Execute the hello command with two arguments:

$ ./go_build_main_go hello --name=liujian --say=hello
liujian: hello
Copy the code

Write a Worker Pool queue to consume

Queue consumption is the most commonly used asynchronous processing model in high-concurrency systems. Usually, we write a CLI command line program to execute queue consumption of Redis, RabbitMQ and other MQ in the background, and then implement the processing results into mysql and other databases. Because the standardization of such requirements is relatively easy, So we developed the MiX-Go/XWP library to handle these requirements, and basically most of the asynchronous processing class requirements are available.

Create commands/ workerPool. go file

  • workerpool.NewDispatcher(jobQueue, 15, NewWorker)A scheduler is created
  • NewWorkerResponsible for initializing the work coroutine that executes the task
  • The mission data will be inworker.DoMethod, we just need to write our business logic to that method
  • When the program receives the process exit signal, the scheduler can smoothly control all workers to exit the schedule after completing all tasks in the queue to ensure data integrity
package commands

import (
    "context"
    "fmt"
    "github.com/mix-go/cli-skeleton/di"
    "github.com/mix-go/xwp"
    "os"
    "os/signal"
    "strings"
    "syscall"
    "time"
)

type worker struct {
    xwp.WorkerTrait
}

func (t *worker) Do(data interface{}) {
    defer func(a) {
        if err := recover(a); err ! =nil {
            logger := di.Logrus()
            logger.Error(err)
        }
    }()

    // Perform business processing
    // ...
    
    // Drop the result to the database
    // ...
}

func NewWorker(a) xwp.Worker {
    return &worker{}
}

type WorkerPoolDaemonCommand struct{}func (t *WorkerPoolDaemonCommand) Main(a) {
    redis := globals.Redis()
    jobQueue := make(chan interface{}, 50)
    d := xwp.NewDispatcher(jobQueue, 15, NewWorker)

    ch := make(chan os.Signal)
    signal.Notify(ch, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
    go func(a) {
        <-ch
        d.Stop()
    }()

    go func(a) {
        for {
            res, err := redis.BRPop(context.Background(), 3*time.Second, "foo").Result()
            iferr ! =nil {
                if strings.Contains(err.Error(), "redis: nil") {
                    continue
                }
                fmt.Println(fmt.Sprintf("Redis Error: %s", err))
                d.Stop();
                return
            }
            // the last key of the brPop command is the value
            jobQueue <- res[1]
        }
    }()

    d.Run() // Block code until all tasks complete and all Worker stops
}
Copy the code

The next step is to register the command with the CLI through xcli.AddCommand.

Write an API service

First we create a project skeleton using the mixcli command:

$mixcli new hello Use the arrow keys to navigate: ↓ ↑ → ←? Select Project Type: CLI ▸ API Web (contains the websocket) gRPCCopy the code

Generate skeleton directory structure as follows:

. ├ ─ ─ the README. Md ├ ─ ─ bin ├ ─ ─ commands ├ ─ ─ the conf ├ ─ ─ configor ├ ─ ─ controllers ├ ─ ─ di ├ ─ ─ dotenv ├ ─ ─. Mod ├ ─ ─. Sum ├ ─ ─ ├── bass Exercises ── exercisesCopy the code

Mian. Go file:

  • xcli.AddCommandMethod passed incommands.CommandsDefines all commands
package main

import (
  "github.com/mix-go/api-skeleton/commands"
  _ "github.com/mix-go/api-skeleton/configor"
  _ "github.com/mix-go/api-skeleton/di"
  _ "github.com/mix-go/api-skeleton/dotenv"
  "github.com/mix-go/dotenv"
  "github.com/mix-go/xcli"
)

func main(a) {
  xcli.SetName("app").
    SetVersion("0.0.0 - alpha").
    SetDebug(dotenv.Getenv("APP_DEBUG").Bool(false)) xcli.AddCommand(commands.Commands...) .Run() }Copy the code

Commands/main. Go file:

We can customize commands here to see more

  • RunISpecifies the interface for executing the commandRunSet an anonymous function
package commands

import (
  "github.com/mix-go/xcli"
)

var Commands = []*xcli.Command{
  {
    Name:  "api",
    Short: "\tStart the api server",
    Options: []*xcli.Option{
      {
        Names: []string{"a"."addr"},
        Usage: "\tListen to the specified address",
      },
      {
        Names: []string{"d"."daemon"},
        Usage: "\tRun in the background",
      },
    },
    RunI: &APICommand{},
  },
}
Copy the code

Commands/API. Go file:

The business code is written in the main method of the APICommand structure, and the generated code already contains:

  • Listening signal stops service
  • Logs are printed based on the mode
  • Optional background daemon execution

Basically, it can be used online without modification

package commands

import (
  "context"
  "fmt"
  "github.com/gin-gonic/gin"
  "github.com/mix-go/api-skeleton/di"
  "github.com/mix-go/api-skeleton/routes"
  "github.com/mix-go/dotenv"
  "github.com/mix-go/xcli/flag"
  "github.com/mix-go/xcli/process"
  "os"
  "os/signal"
  "strings"
  "syscall"
  "time"
)

type APICommand struct{}func (t *APICommand) Main(a) {
  if flag.Match("d"."daemon").Bool() {
    process.Daemon()
  }

  logger := di.Logrus()
  server := di.Server()
  addr := dotenv.Getenv("GIN_ADDR").String(": 8080")
  mode := dotenv.Getenv("GIN_MODE").String(gin.ReleaseMode)

  // server
  gin.SetMode(mode)
  router := gin.New()
  routes.SetRoutes(router)
  server.Addr = flag.Match("a"."addr").String(addr)
  server.Handler = router

  // signal
  ch := make(chan os.Signal)
  signal.Notify(ch, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
  go func(a) {
    <-ch
    logger.Info("Server shutdown")
    ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
    iferr := server.Shutdown(ctx); err ! =nil {
      logger.Errorf("Server shutdown error: %s", err)
    }
  }()

  // logger
  ifmode ! = gin.ReleaseMode { handlerFunc := gin.LoggerWithConfig(gin.LoggerConfig{ Formatter:func(params gin.LogFormatterParams) string {
        return fmt.Sprintf("%s|%s|%d|%s",
          params.Method,
          params.Path,
          params.StatusCode,
          params.ClientIP,
        )
      },
      Output: logger.Out,
    })
    router.Use(handlerFunc)
  }

  // run
  welcome()
  logger.Infof("Server start at %s", server.Addr)
  iferr := server.ListenAndServe(); err ! =nil && !strings.Contains(err.Error(), "http: Server closed") {
    panic(err)
  }
}
Copy the code

Configure routes in the routes/main.go file:

Some common examples are already included. You just need to add routes here to start development

package routes

import (
  "github.com/gin-gonic/gin"
  "github.com/mix-go/api-skeleton/controllers"
  "github.com/mix-go/api-skeleton/middleware"
)

func SetRoutes(router *gin.Engine) {
  router.Use(gin.Recovery()) // error handle

  router.GET("hello",
    middleware.CorsMiddleware(),
    func(ctx *gin.Context) {
      hello := controllers.HelloController{}
      hello.Index(ctx)
    },
  )

  router.POST("users/add",
    middleware.AuthMiddleware(),
    func(ctx *gin.Context) {
      hello := controllers.UserController{}
      hello.Add(ctx)
    },
  )

  router.POST("auth".func(ctx *gin.Context) {
    auth := controllers.AuthController{}
    auth.Index(ctx)
  })
}
Copy the code

Next we compile the above program:

  • linux & macOS
go build -o bin/go_build_main_go main.go
Copy the code
  • win
go build -o bin/go_build_main_go.exe main.go
Copy the code

Start the server

$ bin/go_build_main_go api ___ ______ ___ _ /__ ___ _____ ______ / __ `__ \/ /\ \/ /__ __ `/ __ \ / / / / / / / /\ \/ _ /_/ // /_/ / /_/ /_/ /_/_/ /_/\_\ \__, / \____/ /____/ Server Name: mix-api Listen Addr: :8080 System Name: Darwin Go Version: 1.13.4 Framework Version: Level =info MSG =Server start file=api.go:58Copy the code

Write a Web service

First we create a project skeleton using the mixcli command:

$mixcli new hello Use the arrow keys to navigate: ↓ ↑ → ←? Select Project Type: CLI API ▸ Web (contains the websocket) gRPCCopy the code

Generate skeleton directory structure as follows:

. ├ ─ ─ the README. Md ├ ─ ─ bin ├ ─ ─ commands ├ ─ ─ the conf ├ ─ ─ configor ├ ─ ─ controllers ├ ─ ─ di ├ ─ ─ dotenv ├ ─ ─. Mod ├ ─ ─. Sum ├ ─ ─ └. Go └── ─ Middleware ├─ public └─ Routes ├─ Runtime ├─Copy the code

Mian. Go file:

  • xcli.AddCommandMethod passed incommands.CommandsDefines all commands
package main

import (
  "github.com/mix-go/web-skeleton/commands"
  _ "github.com/mix-go/web-skeleton/configor"
  _ "github.com/mix-go/web-skeleton/di"
  _ "github.com/mix-go/web-skeleton/dotenv"
  "github.com/mix-go/dotenv"
  "github.com/mix-go/xcli"
)

func main(a) {
  xcli.SetName("app").
    SetVersion("0.0.0 - alpha").
    SetDebug(dotenv.Getenv("APP_DEBUG").Bool(false)) xcli.AddCommand(commands.Commands...) .Run() }Copy the code

Commands/main. Go file:

We can customize commands here to see more

  • RunISpecifies the interface for executing the commandRunSet an anonymous function
package commands

import (
  "github.com/mix-go/xcli"
)

var Commands = []*xcli.Command{
  {
    Name:  "web",
    Short: "\tStart the web server",
    Options: []*xcli.Option{
      {
        Names: []string{"a"."addr"},
        Usage: "\tListen to the specified address",
      },
      {
        Names: []string{"d"."daemon"},
        Usage: "\tRun in the background",
      },
    },
    RunI: &WebCommand{},
  },
}
Copy the code

Commands/web. Go file:

The business code is written in the main method of the WebCommand structure, and the generated code already contains:

  • Listening signal stops service
  • Logs are printed based on the mode
  • Optional background daemon execution

Basically, it can be used online without modification

package commands

import (
  "context"
  "fmt"
  "github.com/gin-gonic/gin"
  "github.com/mix-go/dotenv"
  "github.com/mix-go/web-skeleton/di"
  "github.com/mix-go/web-skeleton/routes"
  "github.com/mix-go/xcli"
  "github.com/mix-go/xcli/flag"
  "github.com/mix-go/xcli/process"
  "os"
  "os/signal"
  "strings"
  "syscall"
  "time"
)

type WebCommand struct{}func (t *WebCommand) Main(a) {
  if flag.Match("d"."daemon").Bool() {
    process.Daemon()
  }

  logger := di.Logrus()
  server := di.Server()
  addr := dotenv.Getenv("GIN_ADDR").String(": 8080")
  mode := dotenv.Getenv("GIN_MODE").String(gin.ReleaseMode)

  // server
  gin.SetMode(mode)
  router := gin.New()
  routes.SetRoutes(router)
  server.Addr = flag.Match("a"."addr").String(addr)
  server.Handler = router

  // signal
  ch := make(chan os.Signal)
  signal.Notify(ch, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
  go func(a) {
    <-ch
    logger.Info("Server shutdown")
    ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
    iferr := server.Shutdown(ctx); err ! =nil {
      logger.Errorf("Server shutdown error: %s", err)
    }
  }()

  // logger
  ifmode ! = gin.ReleaseMode { handlerFunc := gin.LoggerWithConfig(gin.LoggerConfig{ Formatter:func(params gin.LogFormatterParams) string {
        return fmt.Sprintf("%s|%s|%d|%s",
          params.Method,
          params.Path,
          params.StatusCode,
          params.ClientIP,
        )
      },
      Output: logger.Out,
    })
    router.Use(handlerFunc)
  }

  // templates
  router.LoadHTMLGlob(fmt.Sprintf("%s/.. /templates/*", xcli.App().BasePath))

  // static file
  router.Static("/static", fmt.Sprintf("%s/.. /public/static", xcli.App().BasePath))
  router.StaticFile("/favicon.ico", fmt.Sprintf("%s/.. /public/favicon.ico", xcli.App().BasePath))

  // run
  welcome()
  logger.Infof("Server start at %s", server.Addr)
  iferr := server.ListenAndServe(); err ! =nil && !strings.Contains(err.Error(), "http: Server closed") {
    panic(err)
  }
}
Copy the code

Configure routes in the routes/main.go file:

Some common examples are already included. You just need to add routes here to start development

package routes

import (
  "github.com/gin-gonic/gin"
  "github.com/mix-go/web-skeleton/controllers"
  "github.com/mix-go/web-skeleton/middleware"
)

func SetRoutes(router *gin.Engine) {
  router.Use(gin.Recovery()) // error handle

  router.GET("hello".func(ctx *gin.Context) {
      hello := controllers.HelloController{}
      hello.Index(ctx)
    },
  )

  router.Any("users/add",
    middleware.SessionMiddleware(),
    func(ctx *gin.Context) {
      user := controllers.UserController{}
      user.Add(ctx)
    },
  )

  router.Any("login".func(ctx *gin.Context) {
    login := controllers.LoginController{}
    login.Index(ctx)
  })

  router.GET("websocket".func(ctx *gin.Context) {
      ws := controllers.WebSocketController{}
      ws.Index(ctx)
    },
  )
}
Copy the code

Next we compile the above program:

  • linux & macOS
go build -o bin/go_build_main_go main.go
Copy the code
  • win
go build -o bin/go_build_main_go.exe main.go
Copy the code

Command line to start the Web server:

$ bin/go_build_main_go web ___ ______ ___ _ /__ ___ _____ ______ / __ `__ \/ /\ \/ /__ __ `/ __ \ / / / / / / / /\ \/ _ /_/ // /_/ / /_/ /_/ /_/_/ /_/\_\ \__, / \____/ /____/ Server Name: mix-web Listen Addr: :8080 System Name: Darwin Go Version: 1.13.4 Framework Version: Level =info MSG =Server start file=web.go:58Copy the code

Browser tests:

  • The first browser to enter http://127.0.0.1:8080/login for the session
  • Submit the form after the jump to http://127.0.0.1:8080/users/add pages

Write a WebSocket service

WebSocket does the handshake over HTTP, so when we write code, it’s pretty much the same as when we write a Web project, except when the request comes in, we need to use a WebSocket upgrader to upgrade the request to a WebSocket connection, Next comes the logical handling of the connection, which is consistent with traditional Socket operations from here on out.

The routes/main.go file defines a WebSocket route:

router.GET("websocket".func(ctx *gin.Context) {
        ws := controllers.WebSocketController{}
        ws.Index(ctx)
    },
)
Copy the code

Controllers/ws. Go file:

  • Created aupgraderThe upgrader will upgrade to a WebSocket connection when the request comes in
  • Defines aWebSocketSessionIs responsible for managing the entire life cycle of the connection
  • session.Start()Two coroutines are started to handle message reading and writing respectively
  • In the message read coroutine, startedWebSocketHandlerStructure of theIndexMethod to process the message, in the actual project we can use different structure according to different message content to process, to achieve the controller function of Web project
package controllers

import (
  "github.com/gin-gonic/gin"
  "github.com/gorilla/websocket"
  "github.com/mix-go/web-skeleton/di"
  "github.com/mix-go/xcli"
  "net/http"
)

var upgrader = websocket.Upgrader{
  ReadBufferSize:  1024,
  WriteBufferSize: 1024,}type WebSocketController struct{}func (t *WebSocketController) Index(c *gin.Context) {
  logger := di.Logrus()
  if xcli.App().Debug {
    upgrader.CheckOrigin = func(r *http.Request) bool {
      return true
    }
  }
  conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
  iferr ! =nil {
    logger.Error(err)
    c.Status(http.StatusInternalServerError)
    c.Abort()
    return
  }

  session := WebSocketSession{
    Conn:   conn,
    Header: c.Request.Header,
    Send:   make(chan []byte.100),
  }
  session.Start()

  server := di.Server()
  server.RegisterOnShutdown(func(a) {
    session.Stop()
  })

  logger.Infof("Upgrade: %s", c.Request.UserAgent())
}

type WebSocketSession struct {
  Conn   *websocket.Conn
  Header http.Header
  Send   chan []byte
}

func (t *WebSocketSession) Start(a) {
  go func(a) {
    logger := di.Logrus()
    for {
      msgType, msg, err := t.Conn.ReadMessage()
      iferr ! =nil {
        if! websocket.IsCloseError(err,1001.1006) {
          logger.Error(err)
        }
        t.Stop()
        return
      }
      ifmsgType ! = websocket.TextMessage {continue
      }

      handler := WebSocketHandler{
        Session: t,
      }
      handler.Index(msg)
    }
  }()
  go func(a) {
    logger := di.Logrus()
    for {
      msg, ok := <-t.Send
      if! ok {return
      }
      iferr := t.Conn.WriteMessage(websocket.TextMessage, msg); err ! =nil {
        logger.Error(err)
        t.Stop()
        return(1)}}}}func (t *WebSocketSession) Stop(a) {
  defer func(a) {
    if err := recover(a); err ! =nil {
      logger := di.Logrus()
      logger.Error(err)
    }
  }()
  close(t.Send)
  _ = t.Conn.Close()
}

type WebSocketHandler struct {
  Session *WebSocketSession
}

func (t *WebSocketHandler) Index(msg []byte) {
  t.Session.Send <- []byte("hello, world!")}Copy the code

Next we compile the above program:

  • linux & macOS
go build -o bin/go_build_main_go main.go
Copy the code
  • win
go build -o bin/go_build_main_go.exe main.go
Copy the code

Start the Web server from the command line:

$ bin/go_build_main_go web ___ ______ ___ _ /__ ___ _____ ______ / __ `__ \/ /\ \/ /__ __ `/ __ \ / / / / / / / /\ \/ _ /_/ // /_/ / /_/ /_/ /_/_/ /_/\_\ \__, / \____/ /____/ Server Name: mix-web Listen Addr: :8080 System Name: Darwin Go Version: 1.13.4 Framework Version: Level =info MSG =Server start file=web.go:58Copy the code

Browser tests:

  • We use off-the-shelf tools test: www.easyswoole.com/wstool.html

Write a gRPC service, client

First we create a project skeleton using the mixcli command:

$mixcli new hello Use the arrow keys to navigate: ↓ ↑ → ←? Select Project Type: CLI API Web (contains the websocket) ▸ gRPCCopy the code

Generate skeleton directory structure as follows:

. ├ ─ ─ the README. Md ├ ─ ─ bin ├ ─ ─ commands ├ ─ ─ the conf ├ ─ ─ configor ├ ─ ─ di ├ ─ ─ dotenv ├ ─ ─. Mod ├ ─ ─. Sum ├ ─ ─ main. Go ├ ─ ─ Protos exercises ── exercisesCopy the code

Mian. Go file:

  • xcli.AddCommandMethod passed incommands.CommandsDefines all commands
package main

import (
  "github.com/mix-go/dotenv"
  "github.com/mix-go/grpc-skeleton/commands"
  _ "github.com/mix-go/grpc-skeleton/configor"
  _ "github.com/mix-go/grpc-skeleton/di"
  _ "github.com/mix-go/grpc-skeleton/dotenv"
  "github.com/mix-go/xcli"
)

func main(a) {
  xcli.SetName("app").
    SetVersion("0.0.0 - alpha").
    SetDebug(dotenv.Getenv("APP_DEBUG").Bool(false)) xcli.AddCommand(commands.Commands...) .Run() }Copy the code

Commands/main. Go file:

We can customize commands here to see more

  • Defines thegrpc:server,grpc:clientTwo subcommands
  • RunISpecifies the interface for executing the commandRunSet an anonymous function
package commands

import (
  "github.com/mix-go/xcli"
)

var Commands = []*xcli.Command{
  {
    Name:  "grpc:server",
    Short: "gRPC server demo",
    Options: []*xcli.Option{
      {
        Names: []string{"d"."daemon"},
        Usage: "Run in the background",
      },
    },
    RunI: &GrpcServerCommand{},
  },
  {
    Name:  "grpc:client",
    Short: "gRPC client demo",
    RunI:  &GrpcClientCommand{},
  },
}
Copy the code

Protos /user.proto data structure file:

The go code generated by.proto needs to be used in both client and server code because both sides need to communicate using this data structure

  • .proto 是 gRPCCommunication data structure file, usingprotobufagreement
syntax = "proto3"; package go.micro.grpc.user; option go_package = ".; protos"; service User { rpc Add(AddRequest) returns (AddResponse) {} } message AddRequest { string Name = 1; } message AddResponse { int32 error_code = 1; string error_message = 2; int64 user_id = 3; }Copy the code

Then we need to install the grPC-related compiler:

  • www.cnblogs.com/oolo/p/1184…

Next we compile the.proto file:

  • After a successful compilation, it is generated in the current directoryprotos/user.pb.gofile
cd protos
protoc --go_out=plugins=grpc:. user.proto
Copy the code

Commands/server. Go file:

The server code is written in the main method of the GrpcServerCommand structure. The generated code already contains:

  • Listening signal stops service
  • Optional background daemon execution
  • pb.RegisterUserServerBy registering a default service, users can simply extend their own service
package commands

import (
  "github.com/mix-go/dotenv"
  "github.com/mix-go/grpc-skeleton/di"
  pb "github.com/mix-go/grpc-skeleton/protos"
  "github.com/mix-go/grpc-skeleton/services"
  "github.com/mix-go/xcli/flag"
  "github.com/mix-go/xcli/process"
  "google.golang.org/grpc"
  "net"
  "os"
  "os/signal"
  "strings"
  "syscall"
)

var listener net.Listener

type GrpcServerCommand struct{}func (t *GrpcServerCommand) Main(a) {
  if flag.Match("d"."daemon").Bool() {
    process.Daemon()
  }

  addr := dotenv.Getenv("GIN_ADDR").String(": 8080")
  logger := di.Logrus()

  // listen
  listener, err := net.Listen("tcp", addr)
  iferr ! =nil {
    panic(err)
  }
  listener = listener

  // signal
  ch := make(chan os.Signal)
  signal.Notify(ch, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM)
  go func(a) {
    <-ch
    logger.Info("Server shutdown")
    iferr := listener.Close(); err ! =nil {
      panic(err)
    }
  }()

  // server
  s := grpc.NewServer()
  pb.RegisterUserServer(s, &services.UserService{})

  // run
  welcome()
  logger.Infof("Server run %s", addr)
  iferr := s.Serve(listener); err ! =nil && !strings.Contains(err.Error(), "use of closed network connection") {
    panic(err)
  }
}
Copy the code

Services/user. Go file:

The services.UserService{} service code registered in the server code is as follows:

Just populate the business logic

package services

import (
  "context"
  pb "github.com/mix-go/grpc-skeleton/protos"
)

type UserService struct{}func (t *UserService) Add(ctx context.Context, in *pb.AddRequest) (*pb.AddResponse, error) {
  // Perform database operations
  // ...

  resp := pb.AddResponse{
    ErrorCode:    0,
    ErrorMessage: "",
    UserId:       10001,}return &resp, nil
}
Copy the code

Commands/client. Go file:

The client code is written in the main method of the GrpcClientCommand structure. The generated code already contains:

  • Obtain the server connection address from environment configuration
  • Set the5sThe execution timeout of
package commands

import (
    "context"
    "fmt"
  "github.com/mix-go/dotenv"
  pb "github.com/mix-go/grpc-skeleton/protos"
    "google.golang.org/grpc"
    "time"
)

type GrpcClientCommand struct{}func (t *GrpcClientCommand) Main(a) {
  addr := dotenv.Getenv("GIN_ADDR").String(": 8080")
    ctx, _ := context.WithTimeout(context.Background(), time.Duration(5)*time.Second)
    conn, err := grpc.DialContext(ctx, addr, grpc.WithInsecure(), grpc.WithBlock())
    iferr ! =nil {
        panic(err)
    }
    defer func(a) {
        _ = conn.Close()
    }()
    cli := pb.NewUserClient(conn)
    req := pb.AddRequest{
        Name: "xiaoliu",
    }
    resp, err := cli.Add(ctx, &req)
    iferr ! =nil {
        panic(err)
    }
    fmt.Println(fmt.Sprintf("Add User: %d", resp.UserId))
}
Copy the code

Next we compile the above program:

  • linux & macOS
go build -o bin/go_build_main_go main.go
Copy the code
  • win
go build -o bin/go_build_main_go.exe main.go
Copy the code

Start GRPC from the command line :server

$ bin/go_build_main_go grpc:server ___ ______ ___ _ /__ ___ _____ ______ / __ `__ \/ /\ \/ /__ __ `/ __ \ / / / / / / / /\ \/ _ /_/ // /_/ / /_/ /_/ /_/_/ /_/\_\ \__, / \____/ /____/ Server Name: mix-grpc Listen Addr: :8080 System Name: Darwin Go Version: 1.13.4 Framework Version: 1.0.20 time=2020-11-09 15:08:17.544 level=info MSG =Server run :8080 file=server.go:46Copy the code

Then start a new terminal and execute the following client commands to communicate with the server above

$ bin/go_build_main_go grpc:client
Add User: 10001
Copy the code

How to use Logger, Database, Redis and other components in the DI container

The common components to be used in the project are defined in the DI directory. The framework generates some common components by default. Users can also define their own components

  • Where can I use it

It can be used anywhere in the code, but in order to use environment variables and custom configurations, we usually use it in Run and RunI defined by the xcli.mand structure.

  • Use logs, such as:logrus,zap
logger := di.Logrus()
logger.Info("test")
Copy the code
  • Use a database, such as:gorm,xorm
db := di.Gorm()
user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}
result := db.Create(&user)
fmt.Println(result)
Copy the code
  • Use Redis, for example:go-redis
rdb := di.GoRedis()
val, err := rdb.Get(context.Background(), "key").Result()
iferr ! =nil {
    panic(err)
}
fmt.Println("key", val)
Copy the code

Rely on

The official library

  • Github.com/mix-go/mixc…
  • github.com/mix-go/xcli
  • github.com/mix-go/xdi
  • github.com/mix-go/xwp
  • github.com/mix-go/xfmt
  • Github.com/mix-go/dote…

Third-party libraries

  • Github.com/gin-gonic/g…
  • gorm.io
  • Github.com/go-redis/re…
  • Github.com/jinzhu/conf…
  • github.com/uber-go/zap
  • Github.com/sirupsen/lo…
  • Github.com/natefinch/l…
  • Github.com/lestrrat-go…
  • Github.com/go-session/…
  • Github.com/go-session/…
  • Github.com/dgrijalva/j…
  • Github.com/gorilla/web…
  • github.com/golang/grpc
  • Github.com/golang/prot…

License

Apache License Version 2.0, www.apache.org/licenses/