
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 容器。安装
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
首先我们使用 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) } 接下来我们编译上面的程序:
go build -o bin/go_build_main_go main.go 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 队列消费是高并发系统中最常用的异步处理模型,通常我们是编写一个 CLI 命令行程序在后台执行 Redis 、RabbitMQ 等 MQ 的队列消费,并将处理结果落地到 mysql 等数据库中,由于这类需求的标准化比较容易,因此我们开发了 mix-go/xwp 库来处理这类需求,基本上大部分异步处理类需求都可使用。
新建 commands/workerpool.go 文件:
workerpool.NewDispatcher(jobQueue, 15, NewWorker) 创建了一个调度器NewWorker 负责初始化执行任务的工作协程worker.Do 方法中触发,我们只需要将我们的业务逻辑写到该方法中即可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 中即可。
首先我们使用 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) }) } 接下来我们编译上面的程序:
go build -o bin/go_build_main_go main.go 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 内容放不下,省略...
首先我们使用 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 代码,因为双方需要使用该数据结构通讯
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)) } 接下来我们编译上面的程序:
go build -o bin/go_build_main_go main.go 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 目录,框架默认生成了一些常用的组件,用户也可以定义自己的组件,查看更多
可以在代码的任意位置使用,但是为了可以使用到环境变量和自定义配置,通常我们在 xcli.Command 结构体定义的 Run、RunI 中使用。
logrus、zaplogger := di.Logrus() logger.Info("test") gorm、xormdb := di.Gorm() user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()} result := db.Create(&user) fmt.Println(result) go-redisrdb := di.GoRedis() val, err := rdb.Get(context.Background(), "key").Result() if err != nil { panic(err) } fmt.Println("key", val) 官方库
第三方库
Apache License Version 2.0, http://www.apache.org/licenses/
1 cexll 2021-04-13 18:38:55 +08:00 就这? |
2 RyanTaro 2021-04-13 18:43:05 +08:00 这框架也好意思发出来开源? |
4 SWYou 2021-04-13 19:00:03 +08:00 via iPhone 卧槽,这楼上不懂得给点鼓励吗? |
6 TangMonk 2021-04-13 19:04:12 +08:00 via iPhone 挺好的,赞一个 |
7 keer 2021-04-13 19:07:40 +08:00 楼主的 MixPHP 也很牛逼呀。 |
8 airplayxcom 2021-04-13 21:48:42 +08:00 都用 gin 、gorm 了, 为啥还要做胶水。 |
9 zh5e 2021-04-14 09:45:17 +08:00 挺好的,赞一个 |
10 CheatEngine 2021-04-15 06:37:49 +08:00 生成出来的搅屎棍代码,真的,有这个时间不如拿 go 造个轮子,而不是造毫无技术含量的代码生成器. 这种代码哪有简洁性可言? "任何以代码生成器生成的侵入业务的外部代码都是垃圾".如果不明白我这句话,可以看看隔壁的 beego 的注解路由.注解只是开发,运行的时候使用自动生成的代码.不入侵业务,因为 controller 里只有一个注解. |
11 onanying OP @CheatEngine 你会用一个别人造的轮子,然后你哪里来的优越感?谁给你的勇气? |
12 onanying OP 生成的代码里使用了这些轮子,反正你也是看不到的,只看到了代码生成器。 https://github.com/mix-go/xcli https://github.com/mix-go/xdi https://github.com/mix-go/xwp https://github.com/mix-go/xfmt |