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,