MixGo v1.1 发布, Go 快速开发脚手架工具 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
onanying
V2EX    Go 编程语言

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

  •  2
     
  •   onanying 2021-04-13 18:33:58 +08:00 3109 次点击
    这是一个创建于 1730 天前的主题,其中的信息可能已经有所发展或是发生改变。

    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

    快速开始

    安装

    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: %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.om/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: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 代码,因为双方需要使用该数据结构通讯

    • .protogRPC 通信的数据结构文件,采用 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 相关的编译程序:

    接下来我们开始编译 .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", "damon").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) 

    依赖

    官方库

    第三方库

    License

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

    12 条回复    2021-04-16 13:27:27 +08:00
    cexll
        1
    cexll  
       2021-04-13 18:38:55 +08:00
    就这?
    RyanTaro
        2
    RyanTaro  
       2021-04-13 18:43:05 +08:00
    这框架也好意思发出来开源?
    CEBBCAT
        3
    CEBBCAT  
       2021-04-13 18:57:50 +08:00 via Android
    @cexll
    @RyanTaro

    不太明白,是发生了什么吗?
    SWYou
        4
    SWYou  
       2021-04-13 19:00:03 +08:00 via iPhone
    卧槽,这楼上不懂得给点鼓励吗?
    linvon
        5
    linvon  
       2021-04-13 19:04:11 +08:00
    @CEBBCAT #3
    @SWYou #4
    看一楼还是项目的 contributer,估计是自己人吧
    TangMonk
        6
    TangMonk  
       2021-04-13 19:04:12 +08:00 via iPhone
    挺好的,赞一个
    keer
        7
    keer  
       2021-04-13 19:07:40 +08:00
    楼主的 MixPHP 也很牛逼呀。
    airplayxcom
        8
    airplayxcom  
       2021-04-13 21:48:42 +08:00
    都用 gin 、gorm 了, 为啥还要做胶水。
    zh5e
        9
    zh5e  
       2021-04-14 09:45:17 +08:00
    挺好的,赞一个
    CheatEngine
        10
    CheatEngine  
       2021-04-15 06:37:49 +08:00
    生成出来的搅屎棍代码,真的,有这个时间不如拿 go 造个轮子,而不是造毫无技术含量的代码生成器.
    这种代码哪有简洁性可言? "任何以代码生成器生成的侵入业务的外部代码都是垃圾".如果不明白我这句话,可以看看隔壁的 beego 的注解路由.注解只是开发,运行的时候使用自动生成的代码.不入侵业务,因为 controller 里只有一个注解.
    onanying
        11
    onanying  
    OP
       2021-04-16 13:19:53 +08:00
    @CheatEngine 你会用一个别人造的轮子,然后你哪里来的优越感?谁给你的勇气?
    onanying
        12
    onanying  
    OP
       2021-04-16 13:27:27 +08:00
    生成的代码里使用了这些轮子,反正你也是看不到的,只看到了代码生成器。

    https://github.com/mix-go/xcli
    https://github.com/mix-go/xdi
    https://github.com/mix-go/xwp
    https://github.com/mix-go/xfmt
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     4755 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 28ms UTC 04:00 PVG 12:00 LAX 20:00 JFK 23:00
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86