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
master
Keson 4 years ago committed by GitHub
parent 0d151c17f8
commit 17e6cfb7a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -4,6 +4,8 @@ import (
"fmt" "fmt"
"os" "os"
"github.com/urfave/cli"
"github.com/tal-tech/go-zero/core/logx" "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/apigen"
"github.com/tal-tech/go-zero/tools/goctl/api/dartgen" "github.com/tal-tech/go-zero/tools/goctl/api/dartgen"
@ -19,7 +21,6 @@ import (
"github.com/tal-tech/go-zero/tools/goctl/feature" "github.com/tal-tech/go-zero/tools/goctl/feature"
model "github.com/tal-tech/go-zero/tools/goctl/model/sql/command" model "github.com/tal-tech/go-zero/tools/goctl/model/sql/command"
rpc "github.com/tal-tech/go-zero/tools/goctl/rpc/command" rpc "github.com/tal-tech/go-zero/tools/goctl/rpc/command"
"github.com/urfave/cli"
) )
var ( var (
@ -193,6 +194,17 @@ var (
Name: "rpc", Name: "rpc",
Usage: "generate rpc code", Usage: "generate rpc code",
Subcommands: []cli.Command{ 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", Name: "template",
Usage: `generate proto template`, Usage: `generate proto template`,
@ -224,10 +236,6 @@ var (
Name: "service, srv", Name: "service, srv",
Usage: `the name of rpc service. [option]`, 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{ cli.BoolFlag{
Name: "idea", Name: "idea",
Usage: "whether the command execution environment is from idea plugin. [option]", Usage: "whether the command execution environment is from idea plugin. [option]",

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

@ -8,74 +8,118 @@ Goctl Rpc是`goctl`脚手架下的一个rpc服务代码生成模块支持prot
# 快速开始 # 快速开始
### 生成proto模板 ### 方式一快速生成greet服务
```shell script 通过命令 `goctl rpc new ${servieName}`生成
$ goctl rpc template -o=user.proto
```
```golang 如生成greet rpc服务
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服务代码
生成user rpc服务 ```shell script
``` $ goctl rpc new greet
$ goctl rpc proto -src=user.proto ```
```
代码tree 执行后代码结构如下:
``` ```golang
user └── greet
├── etc ├── etc
│   └── user.json │   └── greet.yaml
├── go.mod
├── go.sum
├── greet
│   ├── greet.go
│   ├── greet_mock.go
│   └── types.go
├── greet.go
├── greet.proto
├── internal ├── internal
│   ├── config │   ├── config
│   │   └── config.go │   │   └── config.go
│   ├── handler
│   │   ├── loginhandler.go
│   ├── logic │   ├── logic
│   │   └── loginlogic.go │   │   └── pinglogic.go
│   ├── server
│   │   └── greetserver.go
│   └── svc │   └── svc
│   └── servicecontext.go │   └── servicecontext.go
├── pb └── pb
│   └── user.pb.go └── greet.pb.go
├── shared ```
│   ├── mockusermodel.go
│   ├── types.go rpc一键生成常见问题解决见 <a href="#常见问题解决">常见问题解决</a>
│   └── usermodel.go ### 方式二通过指定proto生成rpc服务
├── user.go
└── user.proto * 生成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环境 * 安装了go环境
* 安装了protoc&protoc-gen-go并且已经设置环境变量 * 安装了protoc&protoc-gen-go并且已经设置环境变量
* mockgen(可选) * mockgen(可选,将移除)
* 更多问题请见 <a href="#注意事项">注意事项</a>
# 用法 # 用法
### rpc服务生成用法
```shell script ```shell script
$ goctl rpc proto -h $ goctl rpc proto -h
``` ```
@ -91,27 +135,28 @@ OPTIONS:
--src value, -s value the file path of the proto source file --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] --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] --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] --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)/sharedrpc client逻辑代码存放目录。
> 注意这里的shared文件夹名称将会是代码中的package名称。 * --src 必填proto数据源目前暂时支持单个proto文件生成这里不支持不建议外部依赖
* --dir 非必填默认为proto文件所在目录生成代码的目标目录
* --service 服务名称非必填默认为proto文件所在目录名称但是如果proto所在目录为一下结构
```shell script
user
├── cmd
│   └── rpc
│   └── user.proto
```
则服务名称亦为user而非proto所在文件夹名称了这里推荐使用这种结构可以方便在同一个服务名下建立不同类型的服务(api、rpc、mq等),便于代码管理与维护。
* --shared[⚠️已废弃] 非必填,默认为$dir(xxx.proto)/sharedrpc client逻辑代码存放目录。
* --idea 非必填是否为idea插件中执行保留字段终端执行可以忽略 > 注意这里的shared文件夹名称将会是代码中的package名称。
* --idea 非必填是否为idea插件中执行保留字段终端执行可以忽略
# 开发人员需要做什么 # 开发人员需要做什么
@ -124,6 +169,10 @@ OPTIONS:
对于需要进行rpc mock的开发人员在安装了`mockgen`工具的前提下可以在rpc的shared文件中生成好对应的mock文件。 对于需要进行rpc mock的开发人员在安装了`mockgen`工具的前提下可以在rpc的shared文件中生成好对应的mock文件。
# 注意事项 # 注意事项
* `google.golang.org/grpc`需要降级到v1.26.0,且protoc-gen-go版本不能高于v1.3.2see [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不支持暂多文件同时生成
* proto不支持外部依赖包引入message不支持inline * proto不支持外部依赖包引入message不支持inline
* 目前main文件、shared文件、handler文件会被强制覆盖而和开发人员手动需要编写的则不会覆盖生成这一类在代码头部均有 * 目前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
```

@ -1,9 +1,15 @@
package command package command
import ( 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/ctx"
"github.com/tal-tech/go-zero/tools/goctl/rpc/gen" "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 { func Rpc(c *cli.Context) error {
@ -17,6 +23,39 @@ func RpcTemplate(c *cli.Context) error {
out := c.String("out") out := c.String("out")
idea := c.Bool("idea") idea := c.Bool("idea")
generator := gen.NewRpcTemplate(out, 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 return nil
} }

@ -6,12 +6,13 @@ import (
"runtime" "runtime"
"strings" "strings"
"github.com/urfave/cli"
"github.com/tal-tech/go-zero/core/logx" "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"
"github.com/tal-tech/go-zero/tools/goctl/util/console" "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/project"
"github.com/tal-tech/go-zero/tools/goctl/util/stringx" "github.com/tal-tech/go-zero/tools/goctl/util/stringx"
"github.com/urfave/cli"
) )
const ( const (
@ -30,13 +31,12 @@ type RpcContext struct {
ProtoFileSrc string ProtoFileSrc string
ProtoSource string ProtoSource string
TargetDir string TargetDir string
IsInGoEnv bool
console.Console console.Console
} }
func MustCreateRpcContext(protoSrc, targetDir, serviceName string, idea bool) *RpcContext { func MustCreateRpcContext(protoSrc, targetDir, serviceName string, idea bool) *RpcContext {
log := console.NewConsole(idea) log := console.NewConsole(idea)
info, err := project.Prepare(targetDir, true)
log.Must(err)
if stringx.From(protoSrc).IsEmptyOrSpace() { if stringx.From(protoSrc).IsEmptyOrSpace() {
log.Fatalln("expected proto source, but nothing found") 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") log.Fatalln("service name is not found")
} }
info, err := project.Prepare(targetDir, true)
log.Must(err)
return &RpcContext{ return &RpcContext{
ProjectPath: info.Path, ProjectPath: info.Path,
ProjectName: stringx.From(info.Name), ProjectName: stringx.From(info.Name),
@ -71,6 +74,7 @@ func MustCreateRpcContext(protoSrc, targetDir, serviceName string, idea bool) *R
ProtoFileSrc: srcFp, ProtoFileSrc: srcFp,
ProtoSource: filepath.Base(srcFp), ProtoSource: filepath.Base(srcFp),
TargetDir: targetDirFp, TargetDir: targetDirFp,
IsInGoEnv: info.IsInGoEnv,
Console: log, Console: log,
} }
} }

@ -42,6 +42,11 @@ func (g *defaultRpcGenerator) Generate() (err error) {
return return
} }
err = g.initGoMod()
if err != nil {
return
}
err = g.genEtc() err = g.genEtc()
if err != nil { if err != nil {
return return
@ -82,5 +87,5 @@ func (g *defaultRpcGenerator) Generate() (err error) {
return return
} }
return nil return
} }

@ -113,10 +113,7 @@ func (g *defaultRpcGenerator) genCall() error {
} }
service := file.Service[0] service := file.Service[0]
callPath, err := filepath.Abs(service.Name.Lower()) callPath := filepath.Join(g.dirM[dirTarget], service.Name.Lower())
if err != nil {
return err
}
if err = util.MkdirIfNotExist(callPath); err != nil { if err = util.MkdirIfNotExist(callPath); err != nil {
return err return err
@ -152,7 +149,7 @@ func (g *defaultRpcGenerator) genCall() error {
} }
mockFile := filepath.Join(callPath, fmt.Sprintf("%s_mock.go", service.Name.Lower())) 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{}{ err = util.With("shared").GoFmt(true).Parse(callTemplateText).SaveTo(map[string]interface{}{
"name": service.Name.Lower(), "name": service.Name.Lower(),
"head": head, "head": head,
@ -167,9 +164,9 @@ func (g *defaultRpcGenerator) genCall() error {
return err return err
} }
// if mockgen is already installed, it will generate code of gomock for shared files // if mockgen is already installed, it will generate code of gomock for shared files
_, err = exec.LookPath("mockgen") // Deprecated: it will be removed
if mockGenInstalled { if mockGenInstalled && g.Ctx.IsInGoEnv {
execx.Run(fmt.Sprintf("go generate %s", filename), "") _, _ = execx.Run(fmt.Sprintf("go generate %s", filename), "")
} }
return nil return nil

@ -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
}

@ -1,26 +1,28 @@
package gen package gen
import ( import (
"path/filepath"
"strings"
"github.com/tal-tech/go-zero/tools/goctl/util" "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/console"
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
) )
const rpcTemplateText = `syntax = "proto3"; const rpcTemplateText = `syntax = "proto3";
package remote; package {{.package}};
message Request { message Request {
string username = 1; string ping = 1;
string password = 2;
} }
message Response { message Response {
string name = 1; string pong = 1;
string gender = 2;
} }
service User { service {{.serviceName}} {
rpc Login(Request) returns(Response); rpc Ping(Request) returns(Response);
} }
` `
@ -36,8 +38,15 @@ func NewRpcTemplate(out string, idea bool) *rpcTemplate {
} }
} }
func (r *rpcTemplate) MustGenerate() { func (r *rpcTemplate) MustGenerate(showState bool) {
err := util.With("t").Parse(rpcTemplateText).SaveTo(nil, r.out, false) 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.Must(err)
r.Success("Done.") if showState {
r.Success("Done.")
}
} }

@ -24,7 +24,9 @@ type (
Path string // Project path name Path string // Project path name
Name string // Project name Name string // Project name
Package string // The service related package 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 { GoMod struct {
@ -75,6 +77,7 @@ func Prepare(projectDir string, checkGrpcEnv bool) (*Project, error) {
goPath = strings.TrimSpace(ret) goPath = strings.TrimSpace(ret)
src := filepath.Join(goPath, "src") src := filepath.Join(goPath, "src")
var isInGoEnv = true
if len(goMod) > 0 { if len(goMod) > 0 {
path = filepath.Dir(goMod) path = filepath.Dir(goMod)
name = filepath.Base(path) name = filepath.Base(path)
@ -103,6 +106,7 @@ func Prepare(projectDir string, checkGrpcEnv bool) (*Project, error) {
name = filepath.Clean(filepath.Base(absPath)) name = filepath.Clean(filepath.Base(absPath))
path = projectDir path = projectDir
pkg = name pkg = name
isInGoEnv = false
} else { } else {
r := strings.TrimPrefix(pwd, src+string(filepath.Separator)) r := strings.TrimPrefix(pwd, src+string(filepath.Separator))
name = filepath.Dir(r) name = filepath.Dir(r)
@ -116,9 +120,10 @@ func Prepare(projectDir string, checkGrpcEnv bool) (*Project, error) {
} }
return &Project{ return &Project{
Name: name, Name: name,
Path: path, Path: path,
Package: pkg, Package: pkg,
IsInGoEnv: isInGoEnv,
GoMod: GoMod{ GoMod: GoMod{
Module: module, Module: module,
Path: goMod, Path: goMod,

Loading…
Cancel
Save