Selaa lähdekoodia

quickly generating rpc demo service (#60)

* add execute files

* add protoc-osx

* add rpc generation

* add rpc generation

* add: rpc template generation

* add README.md

* format error

* reactor templatex.go

* update project.go & README.md

* add: quickly generate rpc service
Keson 4 vuotta sitten
vanhempi
sitoutus
17e6cfb7a9

+ 13 - 5
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]",

+ 5 - 0
tools/goctl/rpc/CHANGELOG.md

@@ -1,5 +1,10 @@
 # Change log
 
+# 2020-08-29
+* rpc greet服务一键生成
+* 修复相对路径生成rpc服务package引入错误bug
+* 移除`--shared`参数
+
 # 2020-08-29
 * 新增支持windows生成
 

+ 156 - 73
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一键生成常见问题解决见 <a href="#常见问题解决">常见问题解决</a>
+### 方式二:通过指定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(可选,将移除)
+* 更多问题请见 <a href="#注意事项">注意事项</a>
 
 # 用法
+
+### 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
+    ```

+ 41 - 2
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
 }

+ 7 - 3
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,
 	}
 }

+ 6 - 1
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
 }

+ 5 - 8
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

+ 22 - 0
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
+}

+ 19 - 10
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.")
+	}
 }

+ 9 - 4
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,