mirror of
https://github.com/zeromicro/go-zero.git
synced 2025-02-03 00:38:40 +08:00
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
This commit is contained in:
parent
0d151c17f8
commit
17e6cfb7a9
@ -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]",
|
||||
|
@ -1,5 +1,10 @@
|
||||
# Change log
|
||||
|
||||
# 2020-08-29
|
||||
* rpc greet服务一键生成
|
||||
* 修复相对路径生成rpc服务package引入错误bug
|
||||
* 移除`--shared`参数
|
||||
|
||||
# 2020-08-29
|
||||
* 新增支持windows生成
|
||||
|
||||
|
@ -8,47 +8,86 @@ Goctl Rpc是`goctl`脚手架下的一个rpc服务代码生成模块,支持prot
|
||||
|
||||
# 快速开始
|
||||
|
||||
### 生成proto模板
|
||||
### 方式一:快速生成greet服务
|
||||
|
||||
```shell script
|
||||
$ goctl rpc template -o=user.proto
|
||||
```
|
||||
通过命令 `goctl rpc new ${servieName}`生成
|
||||
|
||||
```golang
|
||||
syntax = "proto3";
|
||||
如生成greet rpc服务:
|
||||
|
||||
package remote;
|
||||
```shell script
|
||||
$ goctl rpc new greet
|
||||
```
|
||||
|
||||
message Request {
|
||||
执行后代码结构如下:
|
||||
|
||||
```golang
|
||||
└── greet
|
||||
├── etc
|
||||
│ └── greet.yaml
|
||||
├── go.mod
|
||||
├── go.sum
|
||||
├── greet
|
||||
│ ├── greet.go
|
||||
│ ├── greet_mock.go
|
||||
│ └── types.go
|
||||
├── greet.go
|
||||
├── greet.proto
|
||||
├── internal
|
||||
│ ├── config
|
||||
│ │ └── config.go
|
||||
│ ├── logic
|
||||
│ │ └── pinglogic.go
|
||||
│ ├── server
|
||||
│ │ └── greetserver.go
|
||||
│ └── svc
|
||||
│ └── servicecontext.go
|
||||
└── 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 {
|
||||
message Response {
|
||||
// 用户名称
|
||||
string name = 1;
|
||||
// 用户性别
|
||||
string gender = 2;
|
||||
}
|
||||
}
|
||||
|
||||
service User {
|
||||
service User {
|
||||
// 登录
|
||||
rpc Login(Request)returns(Response);
|
||||
}
|
||||
```
|
||||
### 生成rpc服务代码
|
||||
}
|
||||
```
|
||||
* 生成rpc服务代码
|
||||
|
||||
生成user rpc服务
|
||||
```
|
||||
$ goctl rpc proto -src=user.proto
|
||||
```
|
||||
```
|
||||
$ goctl rpc proto -src=user.proto
|
||||
```
|
||||
|
||||
代码tree
|
||||
代码tree
|
||||
|
||||
```
|
||||
user
|
||||
```
|
||||
user
|
||||
├── etc
|
||||
│ └── user.json
|
||||
├── internal
|
||||
@ -69,13 +108,18 @@ user
|
||||
├── user.go
|
||||
└── user.proto
|
||||
|
||||
```
|
||||
```
|
||||
# 准备工作
|
||||
|
||||
* 安装了go环境
|
||||
* 安装了protoc&protoc-gen-go,并且已经设置环境变量
|
||||
* mockgen(可选)
|
||||
* mockgen(可选,将移除)
|
||||
* 更多问题请见 <a href="#注意事项">注意事项</a>
|
||||
|
||||
# 用法
|
||||
|
||||
### rpc服务生成用法
|
||||
|
||||
```shell script
|
||||
$ goctl rpc proto -h
|
||||
```
|
||||
@ -91,15 +135,16 @@ 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所在目录为一下结构:
|
||||
### 参数说明
|
||||
|
||||
* --src 必填,proto数据源,目前暂时支持单个proto文件生成,这里不支持(不建议)外部依赖
|
||||
* --dir 非必填,默认为proto文件所在目录,生成代码的目标目录
|
||||
* --service 服务名称,非必填,默认为proto文件所在目录名称,但是,如果proto所在目录为一下结构:
|
||||
```shell script
|
||||
user
|
||||
├── cmd
|
||||
@ -107,11 +152,11 @@ OPTIONS:
|
||||
│ └── user.proto
|
||||
```
|
||||
则服务名称亦为user,而非proto所在文件夹名称了,这里推荐使用这种结构,可以方便在同一个服务名下建立不同类型的服务(api、rpc、mq等),便于代码管理与维护。
|
||||
* --shared 非必填,默认为$dir(xxx.proto)/shared,rpc client逻辑代码存放目录。
|
||||
* --shared[⚠️已废弃] 非必填,默认为$dir(xxx.proto)/shared,rpc client逻辑代码存放目录。
|
||||
|
||||
> 注意:这里的shared文件夹名称将会是代码中的package名称。
|
||||
|
||||
* --idea 非必填,是否为idea插件中执行,保留字段,终端执行可以忽略
|
||||
* --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
|
||||
```
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
tools/goctl/rpc/gen/gomod.go
Normal file
22
tools/goctl/rpc/gen/gomod.go
Normal file
@ -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
|
||||
|
||||
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)
|
||||
if showState {
|
||||
r.Success("Done.")
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,8 @@ type (
|
||||
Path string // Project path name
|
||||
Name string // Project name
|
||||
Package string // The service related package
|
||||
// true-> project in go path or project init with go mod,or else->false
|
||||
IsInGoEnv bool
|
||||
GoMod GoMod
|
||||
}
|
||||
|
||||
@ -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)
|
||||
@ -119,6 +123,7 @@ func Prepare(projectDir string, checkGrpcEnv bool) (*Project, error) {
|
||||
Name: name,
|
||||
Path: path,
|
||||
Package: pkg,
|
||||
IsInGoEnv: isInGoEnv,
|
||||
GoMod: GoMod{
|
||||
Module: module,
|
||||
Path: goMod,
|
||||
|
Loading…
Reference in New Issue
Block a user