乐趣区

关于golang:MixGo-v11发布-Go-快速开发脚手架工具

Mix Go 是一个基于 Go 进行疾速开发的残缺零碎,相似前端的 Vue CLI,提供:

  • 通过 mix-go/mixcli 实现的交互式我的项目脚手架:

    • 能够生成 cli, api, web, grpc 多种我的项目代码
    • 生成的代码开箱即用
    • 可抉择是否须要 .env 环境配置
    • 可抉择是否须要 .yml, .json, .toml 等独立配置
    • 可抉择应用 gorm, xorm 的数据库
    • 可抉择应用 logrus, zap 的日志库
  • 通过 mix-go/xcli 实现的命令行原型开发。
  • 基于 mix-go/xdi 的 DI, IoC 容器。

Github

  • https://github.com/mix-go/mix

疾速开始

装置

go get github.com/mix-go/mixcli

创立我的项目

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

技术交换

知乎:https://www.zhihu.com/people/…
微博:http://weibo.com/onanying
官网 QQ 群:284806582, 825122875,敲门暗号:goer

编写一个 CLI 程序

首先咱们应用 mixcli 命令创立一个我的项目骨架:

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

生成骨架目录构造如下:

.
├── README.md
├── bin
├── commands
├── conf
├── configor
├── di
├── dotenv
├── go.mod
├── go.sum
├── logs
└── main.go

mian.go 文件:

  • xcli.AddCommand 办法传入的 commands.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() {xcli.SetName("app").
    SetVersion("0.0.0-alpha").
    SetDebug(dotenv.Getenv("APP_DEBUG").Bool(false))
  xcli.AddCommand(commands.Commands...).Run()}

commands/main.go 文件:

咱们能够在这里自定义命令,查看更多

  • RunI 定义了 hello 命令执行的接口,也能够应用 Run 设定一个匿名函数
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{},},
}

commands/hello.go 文件:

业务代码写在 HelloCommand 构造体的 main 办法中

  • 代码中能够应用 flag 获取命令行参数,查看更多
package commands

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

type HelloCommand struct {
}

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

接下来咱们编译下面的程序:

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

查看全副命令的帮忙信息:

$ 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)

查看下面编写的 hello 命令的帮忙信息:

$ ./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)

执行 hello 命令,并传入两个参数:

$ ./go_build_main_go hello --name=liujian --say=hello
liujian: hello

编写一个 Worker Pool 队列生产

队列生产是高并发零碎中最罕用的异步解决模型,通常咱们是编写一个 CLI 命令行程序在后盾执行 Redis、RabbitMQ 等 MQ 的队列生产,并将处理结果落地到 mysql 等数据库中,因为这类需要的标准化比拟容易,因而咱们开发了 mix-go/xwp 库来解决这类需要,基本上大部分异步解决类需要都可应用。

新建 commands/workerpool.go 文件:

  • workerpool.NewDispatcher(jobQueue, 15, NewWorker) 创立了一个调度器
  • NewWorker 负责初始化执行工作的工作协程
  • 工作数据会在 worker.Do 办法中触发,咱们只须要将咱们的业务逻辑写到该办法中即可
  • 当程序接管到过程退出信号时,调度器能平滑管制所有的 Worker 在执行完队列里全副的工作后再退出调度,保证数据的完整性
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() {if err := recover(); err != nil {logger := di.Logrus()
            logger.Error(err)
        }
    }()

    // 执行业务解决
    // ...
    
    // 将处理结果落地到数据库
    // ...
}

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

type WorkerPoolDaemonCommand struct {
}

func (t *WorkerPoolDaemonCommand) Main() {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() {
        <-ch
        d.Stop()}()

    go func() {
        for {res, err := redis.BRPop(context.Background(), 3*time.Second, "foo").Result()
            if err != nil {if strings.Contains(err.Error(), "redis: nil") {continue}
                fmt.Println(fmt.Sprintf("Redis Error: %s", err))
                d.Stop();
                return
            }
            // brPop 命令最初一个键才是值
            jobQueue <- res[1]
        }
    }()

    d.Run() // 阻塞代码,直到工作全副执行实现并且全副 Worker 进行}

接下来只须要把这个命令通过 xcli.AddCommand 注册到 CLI 中即可。

编写一个 API 服务

首先咱们应用 mixcli 命令创立一个我的项目骨架:

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

生成骨架目录构造如下:

.
├── README.md
├── bin
├── commands
├── conf
├── configor
├── controllers
├── di
├── dotenv
├── go.mod
├── go.sum
├── main.go
├── middleware
├── routes
└── runtime

mian.go 文件:

  • xcli.AddCommand 办法传入的 commands.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() {xcli.SetName("app").
    SetVersion("0.0.0-alpha").
    SetDebug(dotenv.Getenv("APP_DEBUG").Bool(false))
  xcli.AddCommand(commands.Commands...).Run()}

commands/main.go 文件:

咱们能够在这里自定义命令,查看更多

  • RunI 指定了命令执行的接口,也能够应用 Run 设定一个匿名函数
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{},},
}

commands/api.go 文件:

业务代码写在 APICommand 构造体的 main 办法中,生成的代码中曾经蕴含了:

  • 监听信号进行服务
  • 依据模式打印日志
  • 可选的后盾守护执行

基本上无需批改即可上线应用

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() {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() {
    <-ch
    logger.Info("Server shutdown")
    ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
    if err := server.Shutdown(ctx); err != nil {logger.Errorf("Server shutdown error: %s", err)
    }
  }()

  // logger
  if mode != 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)
  if err := server.ListenAndServe(); err != nil && !strings.Contains(err.Error(), "http: Server closed") {panic(err)
  }
}

routes/main.go 文件中配置路由:

曾经蕴含一些罕用实例,只须要在这里新增路由即可开始开发

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)
  })
}

接下来咱们编译下面的程序:

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

启动服务器

$ bin/go_build_main_go api
             ___         
 ______ ___  _ /__ ___ _____ ______ 
  / __ `__ \/ /\ \/ /__  __ `/  __ \
 / / / / / / / /\ \/ _  /_/ // /_/ /
/_/ /_/ /_/_/ /_/\_\  \__, / \____/ 
                     /____/


Server      Name:      mix-api
Listen      Addr:      :8080
System      Name:      darwin
Go          Version:   1.13.4
Framework   Version:   1.0.9
time=2020-09-16 20:24:41.515 level=info msg=Server start file=api.go:58

编写一个 Web 服务

首先咱们应用 mixcli 命令创立一个我的项目骨架:

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

生成骨架目录构造如下:

.
├── README.md
├── bin
├── commands
├── conf
├── configor
├── controllers
├── di
├── dotenv
├── go.mod
├── go.sum
├── main.go
├── middleware
├── public
├── routes
├── runtime
└── templates

mian.go 文件:

  • xcli.AddCommand 办法传入的 commands.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() {xcli.SetName("app").
    SetVersion("0.0.0-alpha").
    SetDebug(dotenv.Getenv("APP_DEBUG").Bool(false))
  xcli.AddCommand(commands.Commands...).Run()}

commands/main.go 文件:

咱们能够在这里自定义命令,查看更多

  • RunI 指定了命令执行的接口,也能够应用 Run 设定一个匿名函数
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{},},
}

commands/web.go 文件:

业务代码写在 WebCommand 构造体的 main 办法中,生成的代码中曾经蕴含了:

  • 监听信号进行服务
  • 依据模式打印日志
  • 可选的后盾守护执行

基本上无需批改即可上线应用

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() {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() {
    <-ch
    logger.Info("Server shutdown")
    ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
    if err := server.Shutdown(ctx); err != nil {logger.Errorf("Server shutdown error: %s", err)
    }
  }()

  // logger
  if mode != 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)
  if err := server.ListenAndServe(); err != nil && !strings.Contains(err.Error(), "http: Server closed") {panic(err)
  }
}

routes/main.go 文件中配置路由:

曾经蕴含一些罕用实例,只须要在这里新增路由即可开始开发

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)
    },
  )
}

接下来咱们编译下面的程序:

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

命令行启动 web 服务器:

$ bin/go_build_main_go web
             ___         
 ______ ___  _ /__ ___ _____ ______ 
  / __ `__ \/ /\ \/ /__  __ `/  __ \
 / / / / / / / /\ \/ _  /_/ // /_/ /
/_/ /_/ /_/_/ /_/\_\  \__, / \____/ 
                     /____/


Server      Name:      mix-web
Listen      Addr:      :8080
System      Name:      darwin
Go          Version:   1.13.4
Framework   Version:   1.0.9
time=2020-09-16 20:24:41.515 level=info msg=Server start file=web.go:58

浏览器测试:

  • 首先浏览器进入 http://127.0.0.1:8080/login 获取 session

  • 提交表单后跳转到 http://127.0.0.1:8080/users/add 页面

编写一个 WebSocket 服务

WebSocket 是基于 http 协定实现握手的,因而咱们编写代码时,也是和编写 Web 我的项目是差不多的,差异就是申请过去后,咱们须要应用一个 WebSocket 的降级器,将申请降级为 WebSocket 连贯,接下来就是针对连贯的逻辑解决,从这个局部开始就和传统的 Socket 操作统一了。

routes/main.go 文件曾经定义了一个 WebSocket 的路由:

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

controllers/ws.go 文件:

  • 创立了一个 upgrader 的降级器,当申请过去时将会降级为 WebSocket 连贯
  • 定义了一个 WebSocketSession 的构造体负责管理连贯的整个生命周期
  • session.Start() 中启动了两个协程,别离解决音讯的读和写
  • 在音讯读取的协程中,启动了 WebSocketHandler 构造体的 Index 办法来解决音讯,在理论我的项目中咱们能够依据不同的音讯内容应用不同的构造体来解决,实现 Web 我的项目那种控制器的性能
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)
  if err != 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() {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() {go func() {logger := di.Logrus()
    for {msgType, msg, err := t.Conn.ReadMessage()
      if err != nil {if !websocket.IsCloseError(err, 1001, 1006) {logger.Error(err)
        }
        t.Stop()
        return
      }
      if msgType != websocket.TextMessage {continue}

      handler := WebSocketHandler{Session: t,}
      handler.Index(msg)
    }
  }()
  go func() {logger := di.Logrus()
    for {
      msg, ok := <-t.Send
      if !ok {return}
      if err := t.Conn.WriteMessage(websocket.TextMessage, msg); err != nil {logger.Error(err)
        t.Stop()
        return
      }
    }
  }()}

func (t *WebSocketSession) Stop() {defer func() {if err := recover(); 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!")
}

接下来咱们编译下面的程序:

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

在命令行启动 web 服务器:

$ bin/go_build_main_go web
             ___         
 ______ ___  _ /__ ___ _____ ______ 
  / __ `__ \/ /\ \/ /__  __ `/  __ \
 / / / / / / / /\ \/ _  /_/ // /_/ /
/_/ /_/ /_/_/ /_/\_\  \__, / \____/ 
                     /____/


Server      Name:      mix-web
Listen      Addr:      :8080
System      Name:      darwin
Go          Version:   1.13.4
Framework   Version:   1.0.9
time=2020-09-16 20:24:41.515 level=info msg=Server start file=web.go:58

浏览器测试:

  • 咱们应用现成的工具测试:http://www.easyswoole.com/wst…

编写一个 gRPC 服务、客户端

首先咱们应用 mixcli 命令创立一个我的项目骨架:

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

生成骨架目录构造如下:

.
├── README.md
├── bin
├── commands
├── conf
├── configor
├── di
├── dotenv
├── go.mod
├── go.sum
├── main.go
├── protos
├── runtime
└── services

mian.go 文件:

  • xcli.AddCommand 办法传入的 commands.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() {xcli.SetName("app").
    SetVersion("0.0.0-alpha").
    SetDebug(dotenv.Getenv("APP_DEBUG").Bool(false))
  xcli.AddCommand(commands.Commands...).Run()}

commands/main.go 文件:

咱们能够在这里自定义命令,查看更多

  • 定义了 grpc:servergrpc:client 两个子命令
  • RunI 指定了命令执行的接口,也能够应用 Run 设定一个匿名函数
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{},},
}

protos/user.proto 数据结构文件:

客户端与服务器端代码中都须要应用 .proto 生成的 go 代码,因为单方须要应用该数据结构通信

  • .proto 是 gRPC 通信的数据结构文件,采纳 protobuf 协定
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;
}

而后咱们须要装置 gRPC 相干的编译程序:

  • https://www.cnblogs.com/oolo/…

接下来咱们开始编译 .proto 文件:

  • 编译胜利后会在当前目录生成 protos/user.pb.go 文件
cd protos
protoc --go_out=plugins=grpc:. user.proto

commands/server.go 文件:

服务端代码写在 GrpcServerCommand 构造体的 main 办法中,生成的代码中曾经蕴含了:

  • 监听信号进行服务
  • 可选的后盾守护执行
  • pb.RegisterUserServer 注册了一个默认服务,用户只须要扩大本人的服务即可
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() {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)
  if err != nil {panic(err)
  }
  listener = listener

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

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

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

services/user.go 文件:

服务端代码中注册的 services.UserService{} 服务代码如下:

只须要填充业务逻辑即可

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) {
  // 执行数据库操作
  // ...

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

commands/client.go 文件:

客户端代码写在 GrpcClientCommand 构造体的 main 办法中,生成的代码中曾经蕴含了:

  • 通过环境配置获取服务端连贯地址
  • 设定了 5s 的执行超时工夫
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() {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())
    if err != nil {panic(err)
    }
    defer func() {_ = conn.Close()
    }()
    cli := pb.NewUserClient(conn)
    req := pb.AddRequest{Name: "xiaoliu",}
    resp, err := cli.Add(ctx, &req)
    if err != nil {panic(err)
    }
    fmt.Println(fmt.Sprintf("Add User: %d", resp.UserId))
}

接下来咱们编译下面的程序:

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

首先在命令行启动 grpc: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:46

而后开启一个新的终端,执行上面的客户端命令与下面的服务器通信

$ bin/go_build_main_go grpc:client
Add User: 10001

如何应用 DI 容器中的 Logger、Database、Redis 等组件

我的项目中要应用的公共组件,都定义在 di 目录,框架默认生成了一些罕用的组件,用户也能够定义本人的组件,查看更多

  • 能够在哪里应用

能够在代码的任意地位应用,然而为了能够应用到环境变量和自定义配置,通常咱们在 xcli.Command 构造体定义的 RunRunI 中应用。

  • 应用日志,比方:logruszap
logger := di.Logrus()
logger.Info("test")
  • 应用数据库,比方:gormxorm
db := di.Gorm()
user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}
result := db.Create(&user)
fmt.Println(result)
  • 应用 Redis,比方:go-redis
rdb := di.GoRedis()
val, err := rdb.Get(context.Background(), "key").Result()
if err != nil {panic(err)
}
fmt.Println("key", val)

依赖

官网库

  • https://github.com/mix-go/mixcli
  • https://github.com/mix-go/xcli
  • https://github.com/mix-go/xdi
  • https://github.com/mix-go/xwp
  • https://github.com/mix-go/xfmt
  • https://github.com/mix-go/dotenv

第三方库

  • https://github.com/gin-gonic/gin
  • https://gorm.io
  • https://github.com/go-redis/r…
  • https://github.com/jinzhu/con…
  • https://github.com/uber-go/zap
  • https://github.com/sirupsen/l…
  • https://github.com/natefinch/…
  • https://github.com/lestrrat-g…
  • https://github.com/go-session…
  • https://github.com/go-session…
  • https://github.com/dgrijalva/…
  • https://github.com/gorilla/we…
  • https://github.com/golang/grpc
  • https://github.com/golang/pro…

License

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

退出移动版