跳至主要內容
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?

4563博客

全新的繁體中文 WordPress 網站
  • 首頁
  • MixGo v1.1 发布, Go 快速开发脚手架工具
未分類
17 4 月 2021

MixGo v1.1 发布, Go 快速开发脚手架工具

MixGo v1.1 发布, Go 快速开发脚手架工具

資深大佬 : onanying 3

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 | Gitee

  • https://github.com/mix-go/mix
  • https://gitee.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/onanying
微博: 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: %sn", 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 服务

内容放不下,省略…

编写一个 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:server、grpc: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/p/11840305.html#%E5%AE%89%E8%A3%85-grpc

接下来我们开始编译 .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 结构体定义的 Run、RunI 中使用。

  • 使用日志,比如:logrus、zap
logger := di.Logrus() logger.Info("test") 
  • 使用数据库,比如:gorm、xorm
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/redis
  • https://github.com/jinzhu/configor
  • https://github.com/uber-go/zap
  • https://github.com/sirupsen/logrus
  • https://github.com/natefinch/lumberjack
  • https://github.com/lestrrat-go/file-rotatelogs
  • https://github.com/go-session/session
  • https://github.com/go-session/redis
  • https://github.com/dgrijalva/jwt-go
  • https://github.com/gorilla/websocket
  • https://github.com/golang/grpc
  • https://github.com/golang/protobuf

License

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

大佬有話說 (12)

  • 資深大佬 : cexll

    就这?

  • 資深大佬 : RyanTaro

    这框架也好意思发出来开源?

  • 資深大佬 : CEBBCAT

    @cexll
    @RyanTaro

    不太明白,是发生了什么吗?

  • 資深大佬 : SWYou

    卧槽,这上不懂得给点鼓励吗?

  • 資深大佬 : linvon

    @CEBBCAT #3
    @SWYou #4
    看一还是项目的 contributer,估计是自己人吧

  • 資深大佬 : TangMonk

    挺好的,赞一个

  • 資深大佬 : keer

    主的 MixPHP 也很牛逼呀。

  • 資深大佬 : airplayxcom

    都用 gin 、gorm 了, 为啥还要做胶水。

  • 資深大佬 : zh5e

    挺好的,赞一个

  • 資深大佬 : CheatEngine

    生成出来的搅屎棍代码,真的,有这个时间不如拿 go 造个轮子,而不是造毫无技术含量的代码生成器.
    这种代码哪有简洁性可言? “任何以代码生成器生成的侵入业务的外部代码都是垃圾”.如果不明白我这句话,可以看看隔壁的 beego 的注解路由.注解只是开发,运行的时候使用自动生成的代码.不入侵业务,因为 controller 里只有一个注解.

  • 主 資深大佬 : onanying

    @CheatEngine 你会用一个别人造的轮子,然后你哪里来的优越感?谁给你的勇气?

  • 主 資深大佬 : onanying

    生成的代码里使用了这些轮子,反正你也是看不到的,只看到了代码生成器。

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

文章導覽

上一篇文章
下一篇文章

AD

其他操作

  • 登入
  • 訂閱網站內容的資訊提供
  • 訂閱留言的資訊提供
  • WordPress.org 台灣繁體中文

51la

4563博客

全新的繁體中文 WordPress 網站
返回頂端
本站採用 WordPress 建置 | 佈景主題採用 GretaThemes 所設計的 Memory
4563博客
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?
在這裡新增小工具