diff --git a/tools/goctl/goctl.go b/tools/goctl/goctl.go index 969199f2..351d7b31 100644 --- a/tools/goctl/goctl.go +++ b/tools/goctl/goctl.go @@ -4,6 +4,8 @@ import ( "fmt" "os" + "github.com/urfave/cli" + "github.com/tal-tech/go-zero/core/logx" "github.com/tal-tech/go-zero/tools/goctl/api/apigen" "github.com/tal-tech/go-zero/tools/goctl/api/dartgen" @@ -19,7 +21,6 @@ import ( "github.com/tal-tech/go-zero/tools/goctl/feature" model "github.com/tal-tech/go-zero/tools/goctl/model/sql/command" rpc "github.com/tal-tech/go-zero/tools/goctl/rpc/command" - "github.com/urfave/cli" ) var ( @@ -193,6 +194,17 @@ var ( Name: "rpc", Usage: "generate rpc code", Subcommands: []cli.Command{ + { + Name: "new", + Usage: `generate rpc demo service`, + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "idea", + Usage: "whether the command execution environment is from idea plugin. [option]", + }, + }, + Action: rpc.RpcNew, + }, { Name: "template", Usage: `generate proto template`, @@ -224,10 +236,6 @@ var ( Name: "service, srv", Usage: `the name of rpc service. [option]`, }, - cli.StringFlag{ - Name: "shared", - Usage: `the dir of the shared file,default path is "${pwd}/shared. [option]`, - }, cli.BoolFlag{ Name: "idea", Usage: "whether the command execution environment is from idea plugin. [option]", diff --git a/tools/goctl/rpc/CHANGELOG.md b/tools/goctl/rpc/CHANGELOG.md index 37be49b9..3518f698 100644 --- a/tools/goctl/rpc/CHANGELOG.md +++ b/tools/goctl/rpc/CHANGELOG.md @@ -1,5 +1,10 @@ # Change log +# 2020-08-29 +* rpc greet服务一键生成 +* 修复相对路径生成rpc服务package引入错误bug +* 移除`--shared`参数 + # 2020-08-29 * 新增支持windows生成 diff --git a/tools/goctl/rpc/README.md b/tools/goctl/rpc/README.md index 8db5d969..9ac26164 100644 --- a/tools/goctl/rpc/README.md +++ b/tools/goctl/rpc/README.md @@ -8,74 +8,118 @@ Goctl Rpc是`goctl`脚手架下的一个rpc服务代码生成模块,支持prot # 快速开始 -### 生成proto模板 +### 方式一:快速生成greet服务 -```shell script -$ goctl rpc template -o=user.proto -``` + 通过命令 `goctl rpc new ${servieName}`生成 -```golang -syntax = "proto3"; - -package remote; - -message Request { - // 用户名 - string username = 1; - // 用户密码 - string password = 2; -} - -message Response { - // 用户名称 - string name = 1; - // 用户性别 - string gender = 2; -} - -service User { - // 登录 - rpc Login(Request)returns(Response); -} -``` -### 生成rpc服务代码 + 如生成greet rpc服务: -生成user rpc服务 -``` -$ goctl rpc proto -src=user.proto -``` + ```shell script + $ goctl rpc new greet + ``` -代码tree + 执行后代码结构如下: -``` -user + ```golang + └── greet ├── etc - │   └── user.json + │   └── greet.yaml + ├── go.mod + ├── go.sum + ├── greet + │   ├── greet.go + │   ├── greet_mock.go + │   └── types.go + ├── greet.go + ├── greet.proto ├── internal │   ├── config │   │   └── config.go - │   ├── handler - │   │   ├── loginhandler.go │   ├── logic - │   │   └── loginlogic.go + │   │   └── pinglogic.go + │   ├── server + │   │   └── greetserver.go │   └── svc │   └── servicecontext.go - ├── pb - │   └── user.pb.go - ├── shared - │   ├── mockusermodel.go - │   ├── types.go - │   └── usermodel.go - ├── user.go - └── user.proto - -``` + └── pb + └── greet.pb.go + ``` + +rpc一键生成常见问题解决见 常见问题解决 +### 方式二:通过指定proto生成rpc服务 + +* 生成proto模板 + + ```shell script + $ goctl rpc template -o=user.proto + ``` + + ```golang + syntax = "proto3"; + + package remote; + + message Request { + // 用户名 + string username = 1; + // 用户密码 + string password = 2; + } + + message Response { + // 用户名称 + string name = 1; + // 用户性别 + string gender = 2; + } + + service User { + // 登录 + rpc Login(Request)returns(Response); + } + ``` +* 生成rpc服务代码 + + ``` + $ goctl rpc proto -src=user.proto + ``` + + 代码tree + + ``` + user + ├── etc + │   └── user.json + ├── internal + │   ├── config + │   │   └── config.go + │   ├── handler + │   │   ├── loginhandler.go + │   ├── logic + │   │   └── loginlogic.go + │   └── svc + │   └── servicecontext.go + ├── pb + │   └── user.pb.go + ├── shared + │   ├── mockusermodel.go + │   ├── types.go + │   └── usermodel.go + ├── user.go + └── user.proto + + ``` # 准备工作 + * 安装了go环境 * 安装了protoc&protoc-gen-go,并且已经设置环境变量 -* mockgen(可选) +* mockgen(可选,将移除) +* 更多问题请见 注意事项 # 用法 + +### rpc服务生成用法 + ```shell script $ goctl rpc proto -h ``` @@ -91,27 +135,28 @@ OPTIONS: --src value, -s value the file path of the proto source file --dir value, -d value the target path of the code,default path is "${pwd}". [option] --service value, --srv value the name of rpc service. [option] - --shared value the dir of the shared file,default path is "${pwd}/shared. [option]" + --shared[已废弃] value the dir of the shared file,default path is "${pwd}/shared. [option]" --idea whether the command execution environment is from idea plugin. [option] ``` -* 参数说明 - * --src 必填,proto数据源,目前暂时支持单个proto文件生成,这里不支持(不建议)外部依赖 - * --dir 非必填,默认为proto文件所在目录,生成代码的目标目录 - * --service 服务名称,非必填,默认为proto文件所在目录名称,但是,如果proto所在目录为一下结构: - ```shell script - user - ├── cmd - │   └── rpc - │   └── user.proto - ``` - 则服务名称亦为user,而非proto所在文件夹名称了,这里推荐使用这种结构,可以方便在同一个服务名下建立不同类型的服务(api、rpc、mq等),便于代码管理与维护。 - * --shared 非必填,默认为$dir(xxx.proto)/shared,rpc client逻辑代码存放目录。 - - > 注意:这里的shared文件夹名称将会是代码中的package名称。 - - * --idea 非必填,是否为idea插件中执行,保留字段,终端执行可以忽略 +### 参数说明 + +* --src 必填,proto数据源,目前暂时支持单个proto文件生成,这里不支持(不建议)外部依赖 +* --dir 非必填,默认为proto文件所在目录,生成代码的目标目录 +* --service 服务名称,非必填,默认为proto文件所在目录名称,但是,如果proto所在目录为一下结构: + ```shell script + user + ├── cmd + │   └── rpc + │   └── user.proto + ``` + 则服务名称亦为user,而非proto所在文件夹名称了,这里推荐使用这种结构,可以方便在同一个服务名下建立不同类型的服务(api、rpc、mq等),便于代码管理与维护。 +* --shared[⚠️已废弃] 非必填,默认为$dir(xxx.proto)/shared,rpc client逻辑代码存放目录。 + + > 注意:这里的shared文件夹名称将会是代码中的package名称。 + +* --idea 非必填,是否为idea插件中执行,保留字段,终端执行可以忽略 # 开发人员需要做什么 @@ -124,6 +169,10 @@ OPTIONS: 对于需要进行rpc mock的开发人员,在安装了`mockgen`工具的前提下可以在rpc的shared文件中生成好对应的mock文件。 # 注意事项 +* `google.golang.org/grpc`需要降级到v1.26.0,且protoc-gen-go版本不能高于v1.3.2(see [https://github.com/grpc/grpc-go/issues/3347](https://github.com/grpc/grpc-go/issues/3347))即 + ``` + replace google.golang.org/grpc => google.golang.org/grpc v1.26.0 + ``` * proto不支持暂多文件同时生成 * proto不支持外部依赖包引入,message不支持inline * 目前main文件、shared文件、handler文件会被强制覆盖,而和开发人员手动需要编写的则不会覆盖生成,这一类在代码头部均有 @@ -133,8 +182,42 @@ OPTIONS: ``` 的标识,请注意不要将也写业务性代码写在里面。 - - - - - +# 常见问题解决(go mod工程) + +* 错误一: + + ```golang + pb/xx.pb.go:220:7: undefined: grpc.ClientConnInterface + pb/xx.pb.go:224:11: undefined: grpc.SupportPackageIsVersion6 + pb/xx.pb.go:234:5: undefined: grpc.ClientConnInterface + pb/xx.pb.go:237:24: undefined: grpc.ClientConnInterface + ``` + 解决方法:请将`protoc-gen-go`版本降至v1.3.2及一下 + +* 错误二: + + ```golang + + # go.etcd.io/etcd/clientv3/balancer/picker + ../../../go/pkg/mod/go.etcd.io/etcd@v0.0.0-20200402134248-51bdeb39e698/clientv3/balancer/picker/err.go:25:9: cannot use &errPicker literal (type *errPicker) as type Picker in return argument:*errPicker does not implement Picker (wrong type for Pick method) + have Pick(context.Context, balancer.PickInfo) (balancer.SubConn, func(balancer.DoneInfo), error) + want Pick(balancer.PickInfo) (balancer.PickResult, error) + ../../../go/pkg/mod/go.etcd.io/etcd@v0.0.0-20200402134248-51bdeb39e698/clientv3/balancer/picker/roundrobin_balanced.go:33:9: cannot use &rrBalanced literal (type *rrBalanced) as type Picker in return argument: + *rrBalanced does not implement Picker (wrong type for Pick method) + have Pick(context.Context, balancer.PickInfo) (balancer.SubConn, func(balancer.DoneInfo), error) + want Pick(balancer.PickInfo) (balancer.PickResult, error) + #github.com/tal-tech/go-zero/rpcx/internal/balancer/p2c + ../../../go/pkg/mod/github.com/tal-tech/go-zero@v1.0.12/rpcx/internal/balancer/p2c/p2c.go:41:32: not enough arguments in call to base.NewBalancerBuilder + have (string, *p2cPickerBuilder) + want (string, base.PickerBuilder, base.Config) + ../../../go/pkg/mod/github.com/tal-tech/go-zero@v1.0.12/rpcx/internal/balancer/p2c/p2c.go:58:9: cannot use &p2cPicker literal (type *p2cPicker) as type balancer.Picker in return argument: + *p2cPicker does not implement balancer.Picker (wrong type for Pick method) + have Pick(context.Context, balancer.PickInfo) (balancer.SubConn, func(balancer.DoneInfo), error) + want Pick(balancer.PickInfo) (balancer.PickResult, error) + ``` + + 解决方法: + + ```golang + replace google.golang.org/grpc => google.golang.org/grpc v1.26.0 + ``` diff --git a/tools/goctl/rpc/command/command.go b/tools/goctl/rpc/command/command.go index be0946d4..eba54062 100644 --- a/tools/goctl/rpc/command/command.go +++ b/tools/goctl/rpc/command/command.go @@ -1,9 +1,15 @@ package command import ( + "fmt" + "os" + "path/filepath" + + "github.com/urfave/cli" + "github.com/tal-tech/go-zero/tools/goctl/rpc/ctx" "github.com/tal-tech/go-zero/tools/goctl/rpc/gen" - "github.com/urfave/cli" + "github.com/tal-tech/go-zero/tools/goctl/util" ) func Rpc(c *cli.Context) error { @@ -17,6 +23,39 @@ func RpcTemplate(c *cli.Context) error { out := c.String("out") idea := c.Bool("idea") generator := gen.NewRpcTemplate(out, idea) - generator.MustGenerate() + generator.MustGenerate(true) + return nil +} + +func RpcNew(c *cli.Context) error { + idea := c.Bool("idea") + arg := c.Args().First() + if len(arg) == 0 { + arg = "greet" + } + abs, err := filepath.Abs(arg) + if err != nil { + return err + } + _, err = os.Stat(abs) + if err != nil { + if !os.IsNotExist(err) { + return err + } + err = util.MkdirIfNotExist(abs) + if err != nil { + return err + } + } + + dir := filepath.Base(filepath.Clean(abs)) + + protoSrc := filepath.Join(abs, fmt.Sprintf("%v.proto", dir)) + templateGenerator := gen.NewRpcTemplate(protoSrc, idea) + templateGenerator.MustGenerate(false) + + rpcCtx := ctx.MustCreateRpcContext(protoSrc, "", "", idea) + generator := gen.NewDefaultRpcGenerator(rpcCtx) + rpcCtx.Must(generator.Generate()) return nil } diff --git a/tools/goctl/rpc/ctx/ctx.go b/tools/goctl/rpc/ctx/ctx.go index f48f35b4..e06ca8c9 100644 --- a/tools/goctl/rpc/ctx/ctx.go +++ b/tools/goctl/rpc/ctx/ctx.go @@ -6,12 +6,13 @@ import ( "runtime" "strings" + "github.com/urfave/cli" + "github.com/tal-tech/go-zero/core/logx" "github.com/tal-tech/go-zero/tools/goctl/util" "github.com/tal-tech/go-zero/tools/goctl/util/console" "github.com/tal-tech/go-zero/tools/goctl/util/project" "github.com/tal-tech/go-zero/tools/goctl/util/stringx" - "github.com/urfave/cli" ) const ( @@ -30,13 +31,12 @@ type RpcContext struct { ProtoFileSrc string ProtoSource string TargetDir string + IsInGoEnv bool console.Console } func MustCreateRpcContext(protoSrc, targetDir, serviceName string, idea bool) *RpcContext { log := console.NewConsole(idea) - info, err := project.Prepare(targetDir, true) - log.Must(err) if stringx.From(protoSrc).IsEmptyOrSpace() { log.Fatalln("expected proto source, but nothing found") @@ -62,6 +62,9 @@ func MustCreateRpcContext(protoSrc, targetDir, serviceName string, idea bool) *R log.Fatalln("service name is not found") } + info, err := project.Prepare(targetDir, true) + log.Must(err) + return &RpcContext{ ProjectPath: info.Path, ProjectName: stringx.From(info.Name), @@ -71,6 +74,7 @@ func MustCreateRpcContext(protoSrc, targetDir, serviceName string, idea bool) *R ProtoFileSrc: srcFp, ProtoSource: filepath.Base(srcFp), TargetDir: targetDirFp, + IsInGoEnv: info.IsInGoEnv, Console: log, } } diff --git a/tools/goctl/rpc/gen/gen.go b/tools/goctl/rpc/gen/gen.go index ebd82e88..12c32d69 100644 --- a/tools/goctl/rpc/gen/gen.go +++ b/tools/goctl/rpc/gen/gen.go @@ -42,6 +42,11 @@ func (g *defaultRpcGenerator) Generate() (err error) { return } + err = g.initGoMod() + if err != nil { + return + } + err = g.genEtc() if err != nil { return @@ -82,5 +87,5 @@ func (g *defaultRpcGenerator) Generate() (err error) { return } - return nil + return } diff --git a/tools/goctl/rpc/gen/gencall.go b/tools/goctl/rpc/gen/gencall.go index 3e47be88..61e71e72 100644 --- a/tools/goctl/rpc/gen/gencall.go +++ b/tools/goctl/rpc/gen/gencall.go @@ -113,10 +113,7 @@ func (g *defaultRpcGenerator) genCall() error { } service := file.Service[0] - callPath, err := filepath.Abs(service.Name.Lower()) - if err != nil { - return err - } + callPath := filepath.Join(g.dirM[dirTarget], service.Name.Lower()) if err = util.MkdirIfNotExist(callPath); err != nil { return err @@ -152,7 +149,7 @@ func (g *defaultRpcGenerator) genCall() error { } mockFile := filepath.Join(callPath, fmt.Sprintf("%s_mock.go", service.Name.Lower())) - os.Remove(mockFile) + _ = os.Remove(mockFile) err = util.With("shared").GoFmt(true).Parse(callTemplateText).SaveTo(map[string]interface{}{ "name": service.Name.Lower(), "head": head, @@ -167,9 +164,9 @@ func (g *defaultRpcGenerator) genCall() error { return err } // if mockgen is already installed, it will generate code of gomock for shared files - _, err = exec.LookPath("mockgen") - if mockGenInstalled { - execx.Run(fmt.Sprintf("go generate %s", filename), "") + // Deprecated: it will be removed + if mockGenInstalled && g.Ctx.IsInGoEnv { + _, _ = execx.Run(fmt.Sprintf("go generate %s", filename), "") } return nil diff --git a/tools/goctl/rpc/gen/gomod.go b/tools/goctl/rpc/gen/gomod.go new file mode 100644 index 00000000..070b4d9e --- /dev/null +++ b/tools/goctl/rpc/gen/gomod.go @@ -0,0 +1,22 @@ +package gen + +import ( + "fmt" + + "github.com/tal-tech/go-zero/core/logx" + "github.com/tal-tech/go-zero/tools/goctl/rpc/execx" +) + +func (g *defaultRpcGenerator) initGoMod() error { + if !g.Ctx.IsInGoEnv { + projectDir := g.dirM[dirTarget] + cmd := fmt.Sprintf("go mod init %s", g.Ctx.ProjectName.Source()) + output, err := execx.Run(fmt.Sprintf(cmd), projectDir) + if err != nil { + logx.Error(err) + return err + } + g.Ctx.Info(output) + } + return nil +} diff --git a/tools/goctl/rpc/gen/template.go b/tools/goctl/rpc/gen/template.go index cb5051d5..eff5b1b7 100644 --- a/tools/goctl/rpc/gen/template.go +++ b/tools/goctl/rpc/gen/template.go @@ -1,26 +1,28 @@ package gen import ( + "path/filepath" + "strings" + "github.com/tal-tech/go-zero/tools/goctl/util" "github.com/tal-tech/go-zero/tools/goctl/util/console" + "github.com/tal-tech/go-zero/tools/goctl/util/stringx" ) const rpcTemplateText = `syntax = "proto3"; -package remote; +package {{.package}}; message Request { - string username = 1; - string password = 2; + string ping = 1; } message Response { - string name = 1; - string gender = 2; + string pong = 1; } -service User { - rpc Login(Request) returns(Response); +service {{.serviceName}} { + rpc Ping(Request) returns(Response); } ` @@ -36,8 +38,15 @@ func NewRpcTemplate(out string, idea bool) *rpcTemplate { } } -func (r *rpcTemplate) MustGenerate() { - err := util.With("t").Parse(rpcTemplateText).SaveTo(nil, r.out, false) +func (r *rpcTemplate) MustGenerate(showState bool) { + protoFilename := filepath.Base(r.out) + serviceName := stringx.From(strings.TrimSuffix(protoFilename, filepath.Ext(protoFilename))) + err := util.With("t").Parse(rpcTemplateText).SaveTo(map[string]string{ + "package": serviceName.UnTitle(), + "serviceName": serviceName.Title(), + }, r.out, false) r.Must(err) - r.Success("Done.") + if showState { + r.Success("Done.") + } } diff --git a/tools/goctl/util/project/project.go b/tools/goctl/util/project/project.go index 8c470b65..587938bb 100644 --- a/tools/goctl/util/project/project.go +++ b/tools/goctl/util/project/project.go @@ -24,7 +24,9 @@ type ( Path string // Project path name Name string // Project name Package string // The service related package - GoMod GoMod + // true-> project in go path or project init with go mod,or else->false + IsInGoEnv bool + GoMod GoMod } GoMod struct { @@ -75,6 +77,7 @@ func Prepare(projectDir string, checkGrpcEnv bool) (*Project, error) { goPath = strings.TrimSpace(ret) src := filepath.Join(goPath, "src") + var isInGoEnv = true if len(goMod) > 0 { path = filepath.Dir(goMod) name = filepath.Base(path) @@ -103,6 +106,7 @@ func Prepare(projectDir string, checkGrpcEnv bool) (*Project, error) { name = filepath.Clean(filepath.Base(absPath)) path = projectDir pkg = name + isInGoEnv = false } else { r := strings.TrimPrefix(pwd, src+string(filepath.Separator)) name = filepath.Dir(r) @@ -116,9 +120,10 @@ func Prepare(projectDir string, checkGrpcEnv bool) (*Project, error) { } return &Project{ - Name: name, - Path: path, - Package: pkg, + Name: name, + Path: path, + Package: pkg, + IsInGoEnv: isInGoEnv, GoMod: GoMod{ Module: module, Path: goMod,