feat: Support for multiple rpc service generation and rpc grouping (#1972)

* Add group & compatible flag

* Add group & compatible flag

* Support for multiple rpc service generation and rpc grouping

* Support for multiple rpc service generation and rpc grouping

* Format code

* Format code

* Add comments

* Fix unit test

* Refactor function name

* Add example & Update grpc readme

* go mod tidy

* update mod

* update mod
This commit is contained in:
anqiansong
2022-07-21 12:47:46 +08:00
committed by GitHub
parent 1b51d0ce82
commit ca3c687f1c
47 changed files with 2305 additions and 377 deletions

View File

@@ -7,7 +7,7 @@ Goctl Rpc是`goctl`脚手架下的一个rpc服务代码生成模块支持prot
* 简单易用
* 快速提升开发效率
* 出错率低
* 贴近protoc
* 贴近 protoc
## 快速开始
@@ -24,168 +24,41 @@ Goctl Rpc是`goctl`脚手架下的一个rpc服务代码生成模块支持prot
执行后代码结构如下:
```golang
```text
.
── etc // yaml配置文件
── greet.yaml
├── go.mod
├── greet // pb.go文件夹①
│ └── greet.pb.go
├── greet.go // main函数
├── greet.proto // proto 文件
├── greetclient // call logic ②
── greet.go
└── internal
├── config // yaml配置对应的实体
└── config.go
├── logic // 业务代码
└── pinglogic.go
├── server // rpc server
└── greetserver.go
└── svc // 依赖资源
└── servicecontext.go
```
> ① pb文件夹名老版本文件夹固定为pb称取自于proto文件中option go_package的值最后一层级按照一定格式进行转换若无此声明则取自于package的值大致代码如下
```go
if option.Name == "go_package" {
ret.GoPackage = option.Constant.Source
}
...
if len(ret.GoPackage) == 0 {
ret.GoPackage = ret.Package.Name
}
ret.PbPackage = GoSanitized(filepath.Base(ret.GoPackage))
...
── greet
── etc
│   └── greet.yaml
├── greet
│   ├── greet.go
│   ├── greet.pb.go
│   └── greet_grpc.pb.go
├── greet.go
── greet.proto
└── internal
├── config
│   └── config.go
├── logic
│   └── pinglogic.go
├── server
│   └── greetserver.go
└── svc
└── servicecontext.go
```
> GoSanitized方法请参考google.golang.org/protobuf@v1.25.0/internal/strs/strings.go:71
> ② call 层文件夹名称取自于proto中service的名称如该sercice的名称和pb文件夹名称相等则会在srervice后面补充client进行区分使pb和call分隔。
```go
if strings.ToLower(proto.Service.Name) == strings.ToLower(proto.GoPackage) {
callDir = filepath.Join(ctx.WorkDir, strings.ToLower(stringx.From(proto.Service.Name+"_client").ToCamel()))
}
```
rpc一键生成常见问题解决见 <a href="#常见问题解决">常见问题解决</a>
### 方式二通过指定proto生成rpc服务
* 生成proto模板
```Bash
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服务代码
```Bash
goctl rpc proto -src=user.proto
```
## 准备工作
* 安装了go环境
* 安装了protoc&protoc-gen-go并且已经设置环境变量
* 更多问题请见 <a href="#注意事项">注意事项</a>
## 用法
### rpc服务生成用法
```Bash
goctl rpc proto -h
$ goctl rpc template -o=user.proto
```
```Bash
NAME:
goctl rpc proto - generate rpc from proto
USAGE:
goctl rpc proto [command options] [arguments...]
OPTIONS:
--src value, -s value the file path of the proto source file
--proto_path value, -I value native command of protoc, specify the directory in which to search for imports. [optional]
--go_opt value native command of protoc-gen-go, specify the mapping from proto to go, eg --go_opt=proto_import=go_package_import. [optional]
--dir value, -d value the target path of the code
--style value the file naming format, see [https://github.com/zeromicro/go-zero/tree/master/tools/goctl/config/readme.md]
--idea whether the command execution environment is from idea plugin. [optional]
```
### 参数说明
* --src 必填proto数据源目前暂时支持单个proto文件生成
* --proto_path 可选protoc原生子命令用于指定proto import从何处查找可指定多个路径,如`goctl rpc -I={path1} -I={path2} ...`
,在没有import时可不填。当前proto路径不用指定已经内置`-I`的详细用法请参考`protoc -h`
* --go_opt 可选protoc-gen-go插件原生flag用于指定go_package
* --dir 可选默认为proto文件所在目录生成代码的目标目录
* --style 可选,指定生成文件名的命名风格
* --idea 可选是否为idea插件中执行终端执行可以忽略
### 开发人员需要做什么
关注业务代码编写将重复性、与业务无关的工作交给goctl生成好rpc服务代码后开发人员仅需要修改
* 服务中的配置文件编写(etc/xx.json、internal/config/config.go)
* 服务中业务逻辑编写(internal/logic/xxlogic.go)
* 服务中资源上下文的编写(internal/svc/servicecontext.go)
### 注意事项
* proto不支持暂多文件同时生成
* proto不支持外部依赖包引入message不支持inline
* 目前main文件、shared文件、handler文件会被强制覆盖而和开发人员手动需要编写的则不会覆盖生成这一类在代码头部均有
```shell script
// Code generated by goctl. DO NOT EDIT!
// Source: xxx.proto
```
的标识,请注意不要将也写业务性代码写在里面。
## proto import
* 对于rpc中的requestType和returnType必须在main proto文件定义对于proto中的message可以像protoc一样import其他proto文件。
proto示例:
### 错误import
```proto
syntax = "proto3";
package greet;
import "base/common.proto"
package user;
option go_package="./user";
message Request {
string ping = 1;
@@ -195,40 +68,136 @@ message Response {
string pong = 1;
}
service Greet {
rpc Ping(base.In) returns(base.Out);// request和return 不支持import
}
```
### 正确import
```proto
syntax = "proto3";
package greet;
import "base/common.proto"
message Request {
base.In in = 1;// 支持import
}
message Response {
base.Out out = 2;// 支持import
}
service Greet {
service User {
rpc Ping(Request) returns(Response);
}
```
## 常见问题解决(go mod工程)
* 生成rpc服务代码
* 错误一:
```text
A required privilege is not held by the client.
```bash
$ goctl rpc protoc user.proto --go_out=. --go-grpc_out=. --zrpc_out=.
```
这个问题只有goctl 版本在`goctl.exe version 1.2.1` 以后的 Windows操作系统出现主要原因是goctl需要以管理员身份运行这样才能将`goctl.exe` 创建一个 `ptocot-gen-gcotl`
的符号链接。
## 用法
### rpc 服务生成用法
```Bash
$ goctl rpc protoc -h
Generate grpc code
Usage:
goctl rpc protoc [flags]
Examples:
goctl rpc protoc xx.proto --go_out=./pb --go-grpc_out=./pb --zrpc_out=.
Flags:
--branch string The branch of the remote repo, it does work with --remote
-h, --help help for protoc
--home string The goctl home path of the template, --home and --remote cannot be set at the same time, if they are, --remote has higher priority
-m, --multiple Generated in multiple rpc service mode
--remote string The remote git repo of the template, --home and --remote cannot be set at the same time, if they are, --remote has higher priority
The git repo directory must be consistent with the https://github.com/zeromicro/go-zero-template directory structure
--style string The file naming format, see [https://github.com/zeromicro/go-zero/tree/master/tools/goctl/config/readme.md] (default "gozero")
-v, --verbose Enable log output
--zrpc_out string The zrpc output directory
```
### 参数说明
* --branch 指定远程仓库模板分支
* --home 指定goctl模板根目录
* -m, --multiple 指定生成多个rpc服务模式, 默认为 false, 如果为 false, 则只支持生成一个rpc service, 如果为 true, 则支持生成多个 rpc service且多个 rpc service 会分组。
* --style 指定文件输出格式
* -v, --verbose 显示日志
* --zrpc_out 指定zrpc输出目录
> ## --multiple
> 是否开启多个 rpc service 生成,如果开启,则满足一下新特性
> 1. 支持 1 到多个 rpc service
> 2. 生成 rpc 服务会按照服务名称分组(尽管只有一个 rpc service
> 3. rpc client 的文件目录变更为固定名称 `client`
>
> 如果不开启,则和旧版本 rpc 生成逻辑一样(兼容)
> 1. 有且只能有一个 rpc service
## rpc 服务生成 example
详情见 [example/rpc](https://github.com/zeromicro/go-zero/tree/master/tools/goctl/example)
## --multiple 为 true 和 false 的目录区别
源 proto 文件
```protobuf
syntax = "proto3";
package hello;
option go_package = "./hello";
message HelloReq {
string in = 1;
}
message HelloResp {
string msg = 1;
}
service Greet {
rpc SayHello(HelloReq) returns (HelloResp);
}
```
### --multiple=true
```text
hello
├── client // 区别1rpc client 目录固定为 client 名称
│   └── greet // 区别2会按照 rpc service 名称分组
│   └── greet.go
├── etc
│   └── hello.yaml
├── hello.go
├── internal
│   ├── config
│   │   └── config.go
│   ├── logic
│   │   └── greet // 区别2会按照 rpc service 名称分组
│   │   └── sayhellologic.go
│   ├── server
│   │   └── greet // 区别2会按照 rpc service 名称分组
│   │   └── greetserver.go
│   └── svc
│   └── servicecontext.go
└── pb
└── hello
├── hello.pb.go
└── hello_grpc.pb.go
```
### --multiple=false (旧版本目录,向后兼容)
```text
hello
├── etc
│   └── hello.yaml
├── greet
│   └── greet.go
├── hello.go
├── internal
│   ├── config
│   │   └── config.go
│   ├── logic
│   │   └── sayhellologic.go
│   ├── server
│   │   └── greetserver.go
│   └── svc
│   └── servicecontext.go
└── pb
└── hello
├── hello.pb.go
└── hello_grpc.pb.go
```

View File

@@ -42,6 +42,8 @@ var (
VarBoolIdea bool
// VarBoolVerbose describes whether verbose.
VarBoolVerbose bool
// VarBoolMultiple describes whether support generating multiple rpc services or not.
VarBoolMultiple bool
)
// RPCNew is to generate rpc greet service, this greet service can speed

View File

@@ -95,6 +95,7 @@ func ZRPC(_ *cobra.Command, args []string) error {
}
var ctx generator.ZRpcContext
ctx.Multiple = VarBoolMultiple
ctx.Src = source
ctx.GoOutput = goOut
ctx.GrpcOutput = grpcOut

View File

@@ -37,25 +37,37 @@ var (
func init() {
Cmd.Flags().StringVar(&cli.VarStringOutput, "o", "", "Output a sample proto file")
Cmd.Flags().StringVar(&cli.VarStringHome, "home", "", "The goctl home path of the template, --home and --remote cannot be set at the same time, if they are, --remote has higher priority")
Cmd.Flags().StringVar(&cli.VarStringRemote, "remote", "", "The remote git repo of the template, --home and --remote cannot be set at the same time, if they are, --remote has higher priority\nThe git repo directory must be consistent with the https://github.com/zeromicro/go-zero-template directory structure")
Cmd.Flags().StringVar(&cli.VarStringBranch, "branch", "", "The branch of the remote repo, it does work with --remote")
Cmd.Flags().StringVar(&cli.VarStringHome, "home", "", "The goctl home path of "+
"the template, --home and --remote cannot be set at the same time, if they are, --remote has"+
" higher priority")
Cmd.Flags().StringVar(&cli.VarStringRemote, "remote", "", "The remote git repo"+
" of the template, --home and --remote cannot be set at the same time, if they are, --remote"+
" has higher priority\n\tThe git repo directory must be consistent with the "+
"https://github.com/zeromicro/go-zero-template directory structure")
Cmd.Flags().StringVar(&cli.VarStringBranch, "branch", "", "The branch of the "+
"remote repo, it does work with --remote")
newCmd.Flags().StringSliceVar(&cli.VarStringSliceGoOpt, "go_opt", nil, "")
newCmd.Flags().StringSliceVar(&cli.VarStringSliceGoGRPCOpt, "go-grpc_opt", nil, "")
newCmd.Flags().StringVar(&cli.VarStringStyle, "style", "gozero", "The file naming format, see [https://github.com/zeromicro/go-zero/tree/master/tools/goctl/config/readme.md]")
newCmd.Flags().BoolVar(&cli.VarBoolIdea, "idea", false, "Whether the command execution environment is from idea plugin.")
newCmd.Flags().StringVar(&cli.VarStringHome, "home", "", "The goctl home path of the template, "+
"--home and --remote cannot be set at the same time, if they are, --remote has higher priority")
newCmd.Flags().StringVar(&cli.VarStringRemote, "remote", "", "The remote git repo of the template, "+
"--home and --remote cannot be set at the same time, if they are, --remote has higher priority\nThe git repo "+
"directory must be consistent with the https://github.com/zeromicro/go-zero-template directory structure")
newCmd.Flags().StringVar(&cli.VarStringBranch, "branch", "", "The branch of the remote repo, it "+
"does work with --remote")
newCmd.Flags().StringVar(&cli.VarStringStyle, "style", "gozero", "The file "+
"naming format, see [https://github.com/zeromicro/go-zero/tree/master/tools/goctl/config/readme.md]")
newCmd.Flags().BoolVar(&cli.VarBoolIdea, "idea", false, "Whether the command "+
"execution environment is from idea plugin.")
newCmd.Flags().StringVar(&cli.VarStringHome, "home", "", "The goctl home path "+
"of the template, --home and --remote cannot be set at the same time, if they are, --remote "+
"has higher priority")
newCmd.Flags().StringVar(&cli.VarStringRemote, "remote", "", "The remote git "+
"repo of the template, --home and --remote cannot be set at the same time, if they are, "+
"--remote has higher priority\n\tThe git repo directory must be consistent with the "+
"https://github.com/zeromicro/go-zero-template directory structure")
newCmd.Flags().StringVar(&cli.VarStringBranch, "branch", "",
"The branch of the remote repo, it does work with --remote")
newCmd.Flags().BoolVarP(&cli.VarBoolVerbose, "verbose", "v", false, "Enable log output")
newCmd.Flags().MarkHidden("go_opt")
newCmd.Flags().MarkHidden("go-grpc_opt")
protocCmd.Flags().BoolVarP(&cli.VarBoolMultiple, "multiple", "m", false,
"Generated in multiple rpc service mode")
protocCmd.Flags().StringSliceVar(&cli.VarStringSliceGoOut, "go_out", nil, "")
protocCmd.Flags().StringSliceVar(&cli.VarStringSliceGoGRPCOut, "go-grpc_out", nil, "")
protocCmd.Flags().StringSliceVar(&cli.VarStringSliceGoOpt, "go_opt", nil, "")
@@ -63,14 +75,17 @@ func init() {
protocCmd.Flags().StringSliceVar(&cli.VarStringSlicePlugin, "plugin", nil, "")
protocCmd.Flags().StringSliceVarP(&cli.VarStringSliceProtoPath, "proto_path", "I", nil, "")
protocCmd.Flags().StringVar(&cli.VarStringZRPCOut, "zrpc_out", "", "The zrpc output directory")
protocCmd.Flags().StringVar(&cli.VarStringStyle, "style", "gozero", "The file naming format, see [https://github.com/zeromicro/go-zero/tree/master/tools/goctl/config/readme.md]")
protocCmd.Flags().StringVar(&cli.VarStringHome, "home", "", "The goctl home path of the template, "+
"--home and --remote cannot be set at the same time, if they are, --remote has higher priority")
protocCmd.Flags().StringVar(&cli.VarStringRemote, "remote", "", "The remote git repo of the template, "+
"--home and --remote cannot be set at the same time, if they are, --remote has higher priority\nThe git repo "+
"directory must be consistent with the https://github.com/zeromicro/go-zero-template directory structure")
protocCmd.Flags().StringVar(&cli.VarStringBranch, "branch", "", "The branch of the remote repo, it "+
"does work with --remote")
protocCmd.Flags().StringVar(&cli.VarStringStyle, "style", "gozero", "The file "+
"naming format, see [https://github.com/zeromicro/go-zero/tree/master/tools/goctl/config/readme.md]")
protocCmd.Flags().StringVar(&cli.VarStringHome, "home", "", "The goctl home "+
"path of the template, --home and --remote cannot be set at the same time, if they are, "+
"--remote has higher priority")
protocCmd.Flags().StringVar(&cli.VarStringRemote, "remote", "", "The remote "+
"git repo of the template, --home and --remote cannot be set at the same time, if they are, "+
"--remote has higher priority\n\tThe git repo directory must be consistent with the "+
"https://github.com/zeromicro/go-zero-template directory structure")
protocCmd.Flags().StringVar(&cli.VarStringBranch, "branch", "",
"The branch of the remote repo, it does work with --remote")
protocCmd.Flags().BoolVarP(&cli.VarBoolVerbose, "verbose", "v", false, "Enable log output")
protocCmd.Flags().MarkHidden("go_out")
protocCmd.Flags().MarkHidden("go-grpc_out")
@@ -80,9 +95,15 @@ func init() {
protocCmd.Flags().MarkHidden("proto_path")
templateCmd.Flags().StringVar(&cli.VarStringOutput, "o", "", "Output a sample proto file")
templateCmd.Flags().StringVar(&cli.VarStringHome, "home", "", "The goctl home path of the template, --home and --remote cannot be set at the same time, if they are, --remote has higher priority")
templateCmd.Flags().StringVar(&cli.VarStringRemote, "remote", "", "The remote git repo of the template, --home and --remote cannot be set at the same time, if they are, --remote has higher priority\nThe git repo directory must be consistent with the https://github.com/zeromicro/go-zero-template directory structure")
templateCmd.Flags().StringVar(&cli.VarStringBranch, "branch", "", "The branch of the remote repo, it does work with --remote")
templateCmd.Flags().StringVar(&cli.VarStringHome, "home", "", "The goctl home"+
" path of the template, --home and --remote cannot be set at the same time, if they are, "+
"--remote has higher priority")
templateCmd.Flags().StringVar(&cli.VarStringRemote, "remote", "", "The remote "+
"git repo of the template, --home and --remote cannot be set at the same time, if they are, "+
"--remote has higher priority\n\tThe git repo directory must be consistent with the "+
"https://github.com/zeromicro/go-zero-template directory structure")
templateCmd.Flags().StringVar(&cli.VarStringBranch, "branch", "", "The branch"+
" of the remote repo, it does work with --remote")
Cmd.AddCommand(newCmd)
Cmd.AddCommand(protocCmd)

View File

@@ -10,17 +10,27 @@ import (
)
type ZRpcContext struct {
Src string
ProtocCmd string
// Sre is the source file of the proto.
Src string
// ProtoCmd is the command to generate proto files.
ProtocCmd string
// ProtoGenGrpcDir is the directory to store the generated proto files.
ProtoGenGrpcDir string
ProtoGenGoDir string
IsGooglePlugin bool
GoOutput string
GrpcOutput string
Output string
// ProtoGenGoDir is the directory to store the generated go files.
ProtoGenGoDir string
// IsGooglePlugin is the flag to indicate whether the proto file is generated by google plugin.
IsGooglePlugin bool
// GoOutput is the output directory of the generated go files.
GoOutput string
// GrpcOutput is the output directory of the generated grpc files.
GrpcOutput string
// Output is the output directory of the generated files.
Output string
// Multiple is the flag to indicate whether the proto file is generated in multiple mode.
Multiple bool
}
// Generate generates an rpc service, through the proto file,
// Generate generates a rpc service, through the proto file,
// code storage directory, and proto import parameters to control
// the source file and target location of the rpc service that needs to be generated
func (g *Generator) Generate(zctx *ZRpcContext) error {
@@ -45,7 +55,7 @@ func (g *Generator) Generate(zctx *ZRpcContext) error {
}
p := parser.NewDefaultProtoParser()
proto, err := p.Parse(zctx.Src)
proto, err := p.Parse(zctx.Src, zctx.Multiple)
if err != nil {
return err
}
@@ -75,22 +85,22 @@ func (g *Generator) Generate(zctx *ZRpcContext) error {
return err
}
err = g.GenLogic(dirCtx, proto, g.cfg)
err = g.GenLogic(dirCtx, proto, g.cfg, zctx)
if err != nil {
return err
}
err = g.GenServer(dirCtx, proto, g.cfg)
err = g.GenServer(dirCtx, proto, g.cfg, zctx)
if err != nil {
return err
}
err = g.GenMain(dirCtx, proto, g.cfg)
err = g.GenMain(dirCtx, proto, g.cfg, zctx)
if err != nil {
return err
}
err = g.GenCall(dirCtx, proto, g.cfg)
err = g.GenCall(dirCtx, proto, g.cfg, zctx)
console.NewColorConsole().MarkDone()

View File

@@ -12,7 +12,6 @@ import (
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/core/stringx"
"github.com/zeromicro/go-zero/tools/goctl/rpc/execx"
"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
)
func TestRpcGenerate(t *testing.T) {
@@ -41,8 +40,9 @@ func TestRpcGenerate(t *testing.T) {
// case go path
t.Run("GOPATH", func(t *testing.T) {
ctx := &ZRpcContext{
Src: "./test.proto",
ProtocCmd: fmt.Sprintf("protoc -I=%s test.proto --go_out=%s --go_opt=Mbase/common.proto=./base --go-grpc_out=%s", common, projectDir, projectDir),
Src: "./test.proto",
ProtocCmd: fmt.Sprintf("protoc -I=%s test.proto --go_out=%s --go_opt=Mbase/common.proto=./base --go-grpc_out=%s",
common, projectDir, projectDir),
IsGooglePlugin: true,
GoOutput: projectDir,
GrpcOutput: projectDir,
@@ -53,15 +53,16 @@ func TestRpcGenerate(t *testing.T) {
_, err = execx.Run("go test "+projectName, projectDir)
if err != nil {
assert.True(t, func() bool {
return strings.Contains(err.Error(), "not in GOROOT") || strings.Contains(err.Error(), "cannot find package")
return strings.Contains(err.Error(),
"not in GOROOT") || strings.Contains(err.Error(), "cannot find package")
}())
}
})
// case go mod
t.Run("GOMOD", func(t *testing.T) {
workDir := pathx.MustTempDir()
name := filepath.Base(workDir)
workDir := projectDir
name := filepath.Base(projectDir)
_, err = execx.Run("go mod init "+name, workDir)
if err != nil {
logx.Error(err)
@@ -70,8 +71,9 @@ func TestRpcGenerate(t *testing.T) {
projectDir = filepath.Join(workDir, projectName)
ctx := &ZRpcContext{
Src: "./test.proto",
ProtocCmd: fmt.Sprintf("protoc -I=%s test.proto --go_out=%s --go_opt=Mbase/common.proto=./base --go-grpc_out=%s", common, projectDir, projectDir),
Src: "./test.proto",
ProtocCmd: fmt.Sprintf("protoc -I=%s test.proto --go_out=%s --go_opt=Mbase/common.proto=./base --go-grpc_out=%s",
common, projectDir, projectDir),
IsGooglePlugin: true,
GoOutput: projectDir,
GrpcOutput: projectDir,
@@ -79,31 +81,5 @@ func TestRpcGenerate(t *testing.T) {
}
err = g.Generate(ctx)
assert.Nil(t, err)
_, err = execx.Run("go test "+projectName, projectDir)
if err != nil {
assert.True(t, func() bool {
return strings.Contains(err.Error(), "not in GOROOT") || strings.Contains(err.Error(), "cannot find package")
}())
}
})
// case not in go mod and go path
t.Run("OTHER", func(t *testing.T) {
ctx := &ZRpcContext{
Src: "./test.proto",
ProtocCmd: fmt.Sprintf("protoc -I=%s test.proto --go_out=%s --go_opt=Mbase/common.proto=./base --go-grpc_out=%s", common, projectDir, projectDir),
IsGooglePlugin: true,
GoOutput: projectDir,
GrpcOutput: projectDir,
Output: projectDir,
}
err = g.Generate(ctx)
assert.Nil(t, err)
_, err = execx.Run("go test "+projectName, projectDir)
if err != nil {
assert.True(t, func() bool {
return strings.Contains(err.Error(), "not in GOROOT") || strings.Contains(err.Error(), "cannot find package")
}())
}
})
}

View File

@@ -35,9 +35,88 @@ var callTemplateText string
// GenCall generates the rpc client code, which is the entry point for the rpc service call.
// It is a layer of encapsulation for the rpc client and shields the details in the pb.
func (g *Generator) GenCall(ctx DirContext, proto parser.Proto, cfg *conf.Config) error {
func (g *Generator) GenCall(ctx DirContext, proto parser.Proto, cfg *conf.Config,
c *ZRpcContext) error {
if !c.Multiple {
return g.genCallInCompatibility(ctx, proto, cfg)
}
return g.genCallGroup(ctx, proto, cfg)
}
func (g *Generator) genCallGroup(ctx DirContext, proto parser.Proto, cfg *conf.Config) error {
dir := ctx.GetCall()
service := proto.Service
head := util.GetHead(proto.Name)
for _, service := range proto.Service {
childPkg, err := dir.GetChildPackage(service.Name)
if err != nil {
return err
}
callFilename, err := format.FileNamingFormat(cfg.NamingFormat, service.Name)
if err != nil {
return err
}
childDir := filepath.Base(childPkg)
filename := filepath.Join(dir.Filename, childDir, fmt.Sprintf("%s.go", callFilename))
isCallPkgSameToPbPkg := childDir == ctx.GetProtoGo().Filename
isCallPkgSameToGrpcPkg := childDir == ctx.GetProtoGo().Filename
functions, err := g.genFunction(proto.PbPackage, service, isCallPkgSameToGrpcPkg)
if err != nil {
return err
}
iFunctions, err := g.getInterfaceFuncs(proto.PbPackage, service, isCallPkgSameToGrpcPkg)
if err != nil {
return err
}
text, err := pathx.LoadTemplate(category, callTemplateFile, callTemplateText)
if err != nil {
return err
}
alias := collection.NewSet()
if !isCallPkgSameToPbPkg {
for _, item := range proto.Message {
msgName := getMessageName(*item.Message)
alias.AddStr(fmt.Sprintf("%s = %s", parser.CamelCase(msgName),
fmt.Sprintf("%s.%s", proto.PbPackage, parser.CamelCase(msgName))))
}
}
pbPackage := fmt.Sprintf(`"%s"`, ctx.GetPb().Package)
protoGoPackage := fmt.Sprintf(`"%s"`, ctx.GetProtoGo().Package)
if isCallPkgSameToGrpcPkg {
pbPackage = ""
protoGoPackage = ""
}
aliasKeys := alias.KeysStr()
sort.Strings(aliasKeys)
if err = util.With("shared").GoFmt(true).Parse(text).SaveTo(map[string]interface{}{
"name": callFilename,
"alias": strings.Join(aliasKeys, pathx.NL),
"head": head,
"filePackage": dir.Base,
"pbPackage": pbPackage,
"protoGoPackage": protoGoPackage,
"serviceName": stringx.From(service.Name).ToCamel(),
"functions": strings.Join(functions, pathx.NL),
"interface": strings.Join(iFunctions, pathx.NL),
}, filename, true); err != nil {
return err
}
}
return nil
}
func (g *Generator) genCallInCompatibility(ctx DirContext, proto parser.Proto,
cfg *conf.Config) error {
dir := ctx.GetCall()
service := proto.Service[0]
head := util.GetHead(proto.Name)
isCallPkgSameToPbPkg := ctx.GetCall().Filename == ctx.GetPb().Filename
isCallPkgSameToGrpcPkg := ctx.GetCall().Filename == ctx.GetProtoGo().Filename
@@ -67,7 +146,8 @@ func (g *Generator) GenCall(ctx DirContext, proto parser.Proto, cfg *conf.Config
if !isCallPkgSameToPbPkg {
for _, item := range proto.Message {
msgName := getMessageName(*item.Message)
alias.AddStr(fmt.Sprintf("%s = %s", parser.CamelCase(msgName), fmt.Sprintf("%s.%s", proto.PbPackage, parser.CamelCase(msgName))))
alias.AddStr(fmt.Sprintf("%s = %s", parser.CamelCase(msgName),
fmt.Sprintf("%s.%s", proto.PbPackage, parser.CamelCase(msgName))))
}
}
@@ -79,7 +159,7 @@ func (g *Generator) GenCall(ctx DirContext, proto parser.Proto, cfg *conf.Config
}
aliasKeys := alias.KeysStr()
sort.Strings(aliasKeys)
err = util.With("shared").GoFmt(true).Parse(text).SaveTo(map[string]interface{}{
return util.With("shared").GoFmt(true).Parse(text).SaveTo(map[string]interface{}{
"name": callFilename,
"alias": strings.Join(aliasKeys, pathx.NL),
"head": head,
@@ -90,7 +170,6 @@ func (g *Generator) GenCall(ctx DirContext, proto parser.Proto, cfg *conf.Config
"functions": strings.Join(functions, pathx.NL),
"interface": strings.Join(iFunctions, pathx.NL),
}, filename, true)
return err
}
func getMessageName(msg proto.Message) string {
@@ -115,7 +194,8 @@ func getMessageName(msg proto.Message) string {
return strings.Join(list, "_")
}
func (g *Generator) genFunction(goPackage string, service parser.Service, isCallPkgSameToGrpcPkg bool) ([]string, error) {
func (g *Generator) genFunction(goPackage string, service parser.Service,
isCallPkgSameToGrpcPkg bool) ([]string, error) {
functions := make([]string, 0)
for _, rpc := range service.RPC {
@@ -125,9 +205,11 @@ func (g *Generator) genFunction(goPackage string, service parser.Service, isCall
}
comment := parser.GetComment(rpc.Doc())
streamServer := fmt.Sprintf("%s.%s_%s%s", goPackage, parser.CamelCase(service.Name), parser.CamelCase(rpc.Name), "Client")
streamServer := fmt.Sprintf("%s.%s_%s%s", goPackage, parser.CamelCase(service.Name),
parser.CamelCase(rpc.Name), "Client")
if isCallPkgSameToGrpcPkg {
streamServer = fmt.Sprintf("%s_%s%s", parser.CamelCase(service.Name), parser.CamelCase(rpc.Name), "Client")
streamServer = fmt.Sprintf("%s_%s%s", parser.CamelCase(service.Name),
parser.CamelCase(rpc.Name), "Client")
}
buffer, err := util.With("sharedFn").Parse(text).Execute(map[string]interface{}{
"serviceName": stringx.From(service.Name).ToCamel(),
@@ -153,19 +235,23 @@ func (g *Generator) genFunction(goPackage string, service parser.Service, isCall
return functions, nil
}
func (g *Generator) getInterfaceFuncs(goPackage string, service parser.Service, isCallPkgSameToGrpcPkg bool) ([]string, error) {
func (g *Generator) getInterfaceFuncs(goPackage string, service parser.Service,
isCallPkgSameToGrpcPkg bool) ([]string, error) {
functions := make([]string, 0)
for _, rpc := range service.RPC {
text, err := pathx.LoadTemplate(category, callInterfaceFunctionTemplateFile, callInterfaceFunctionTemplate)
text, err := pathx.LoadTemplate(category, callInterfaceFunctionTemplateFile,
callInterfaceFunctionTemplate)
if err != nil {
return nil, err
}
comment := parser.GetComment(rpc.Doc())
streamServer := fmt.Sprintf("%s.%s_%s%s", goPackage, parser.CamelCase(service.Name), parser.CamelCase(rpc.Name), "Client")
streamServer := fmt.Sprintf("%s.%s_%s%s", goPackage, parser.CamelCase(service.Name),
parser.CamelCase(rpc.Name), "Client")
if isCallPkgSameToGrpcPkg {
streamServer = fmt.Sprintf("%s_%s%s", parser.CamelCase(service.Name), parser.CamelCase(rpc.Name), "Client")
streamServer = fmt.Sprintf("%s_%s%s", parser.CamelCase(service.Name),
parser.CamelCase(rpc.Name), "Client")
}
buffer, err := util.With("interfaceFn").Parse(text).Execute(
map[string]interface{}{

View File

@@ -27,17 +27,28 @@ func (l *{{.logicName}}) {{.method}} ({{if .hasReq}}in {{.request}}{{if .stream}
var logicTemplate string
// GenLogic generates the logic file of the rpc service, which corresponds to the RPC definition items in proto.
func (g *Generator) GenLogic(ctx DirContext, proto parser.Proto, cfg *conf.Config) error {
func (g *Generator) GenLogic(ctx DirContext, proto parser.Proto, cfg *conf.Config,
c *ZRpcContext) error {
if !c.Multiple {
return g.genLogicInCompatibility(ctx, proto, cfg)
}
return g.genLogicGroup(ctx, proto, cfg)
}
func (g *Generator) genLogicInCompatibility(ctx DirContext, proto parser.Proto,
cfg *conf.Config) error {
dir := ctx.GetLogic()
service := proto.Service.Service.Name
for _, rpc := range proto.Service.RPC {
service := proto.Service[0].Service.Name
for _, rpc := range proto.Service[0].RPC {
logicName := fmt.Sprintf("%sLogic", stringx.From(rpc.Name).ToCamel())
logicFilename, err := format.FileNamingFormat(cfg.NamingFormat, rpc.Name+"_logic")
if err != nil {
return err
}
filename := filepath.Join(dir.Filename, logicFilename+".go")
functions, err := g.genLogicFunction(service, proto.PbPackage, rpc)
functions, err := g.genLogicFunction(service, proto.PbPackage, logicName, rpc)
if err != nil {
return err
}
@@ -50,9 +61,10 @@ func (g *Generator) GenLogic(ctx DirContext, proto parser.Proto, cfg *conf.Confi
return err
}
err = util.With("logic").GoFmt(true).Parse(text).SaveTo(map[string]interface{}{
"logicName": fmt.Sprintf("%sLogic", stringx.From(rpc.Name).ToCamel()),
"functions": functions,
"imports": strings.Join(imports.KeysStr(), pathx.NL),
"logicName": fmt.Sprintf("%sLogic", stringx.From(rpc.Name).ToCamel()),
"functions": functions,
"packageName": "logic",
"imports": strings.Join(imports.KeysStr(), pathx.NL),
}, filename, false)
if err != nil {
return err
@@ -61,16 +73,72 @@ func (g *Generator) GenLogic(ctx DirContext, proto parser.Proto, cfg *conf.Confi
return nil
}
func (g *Generator) genLogicFunction(serviceName, goPackage string, rpc *parser.RPC) (string, error) {
func (g *Generator) genLogicGroup(ctx DirContext, proto parser.Proto, cfg *conf.Config) error {
dir := ctx.GetLogic()
for _, item := range proto.Service {
serviceName := item.Name
for _, rpc := range item.RPC {
var (
err error
filename string
logicName string
logicFilename string
packageName string
)
logicName = fmt.Sprintf("%sLogic", stringx.From(rpc.Name).ToCamel())
childPkg, err := dir.GetChildPackage(serviceName)
if err != nil {
return err
}
serviceDir := filepath.Base(childPkg)
nameJoin := fmt.Sprintf("%s_logic", serviceName)
packageName = strings.ToLower(stringx.From(nameJoin).ToCamel())
logicFilename, err = format.FileNamingFormat(cfg.NamingFormat, rpc.Name+"_logic")
if err != nil {
return err
}
filename = filepath.Join(dir.Filename, serviceDir, logicFilename+".go")
functions, err := g.genLogicFunction(serviceName, proto.PbPackage, logicName, rpc)
if err != nil {
return err
}
imports := collection.NewSet()
imports.AddStr(fmt.Sprintf(`"%v"`, ctx.GetSvc().Package))
imports.AddStr(fmt.Sprintf(`"%v"`, ctx.GetPb().Package))
text, err := pathx.LoadTemplate(category, logicTemplateFileFile, logicTemplate)
if err != nil {
return err
}
if err = util.With("logic").GoFmt(true).Parse(text).SaveTo(map[string]interface{}{
"logicName": logicName,
"functions": functions,
"packageName": packageName,
"imports": strings.Join(imports.KeysStr(), pathx.NL),
}, filename, false); err != nil {
return err
}
}
}
return nil
}
func (g *Generator) genLogicFunction(serviceName, goPackage, logicName string,
rpc *parser.RPC) (string,
error) {
functions := make([]string, 0)
text, err := pathx.LoadTemplate(category, logicFuncTemplateFileFile, logicFunctionTemplate)
if err != nil {
return "", err
}
logicName := stringx.From(rpc.Name + "_logic").ToCamel()
comment := parser.GetComment(rpc.Doc())
streamServer := fmt.Sprintf("%s.%s_%s%s", goPackage, parser.CamelCase(serviceName), parser.CamelCase(rpc.Name), "Server")
streamServer := fmt.Sprintf("%s.%s_%s%s", goPackage, parser.CamelCase(serviceName),
parser.CamelCase(rpc.Name), "Server")
buffer, err := util.With("fun").Parse(text).Execute(map[string]interface{}{
"logicName": logicName,
"method": parser.CamelCase(rpc.Name),

View File

@@ -11,14 +11,20 @@ import (
"github.com/zeromicro/go-zero/tools/goctl/util"
"github.com/zeromicro/go-zero/tools/goctl/util/format"
"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
"github.com/zeromicro/go-zero/tools/goctl/util/stringx"
)
//go:embed main.tpl
var mainTemplate string
type MainServiceTemplateData struct {
Service string
ServerPkg string
Pkg string
}
// GenMain generates the main file of the rpc service, which is an rpc service program call entry
func (g *Generator) GenMain(ctx DirContext, proto parser.Proto, cfg *conf.Config) error {
func (g *Generator) GenMain(ctx DirContext, proto parser.Proto, cfg *conf.Config,
c *ZRpcContext) error {
mainFilename, err := format.FileNamingFormat(cfg.NamingFormat, ctx.GetServiceName().Source())
if err != nil {
return err
@@ -28,9 +34,35 @@ func (g *Generator) GenMain(ctx DirContext, proto parser.Proto, cfg *conf.Config
imports := make([]string, 0)
pbImport := fmt.Sprintf(`"%v"`, ctx.GetPb().Package)
svcImport := fmt.Sprintf(`"%v"`, ctx.GetSvc().Package)
remoteImport := fmt.Sprintf(`"%v"`, ctx.GetServer().Package)
configImport := fmt.Sprintf(`"%v"`, ctx.GetConfig().Package)
imports = append(imports, configImport, pbImport, remoteImport, svcImport)
imports = append(imports, configImport, pbImport, svcImport)
var serviceNames []MainServiceTemplateData
for _, e := range proto.Service {
var (
remoteImport string
serverPkg string
)
if !c.Multiple {
serverPkg = "server"
remoteImport = fmt.Sprintf(`"%v"`, ctx.GetServer().Package)
} else {
childPkg, err := ctx.GetServer().GetChildPackage(e.Name)
if err != nil {
return err
}
serverPkg = filepath.Base(childPkg + "Server")
remoteImport = fmt.Sprintf(`%s "%v"`, serverPkg, childPkg)
}
imports = append(imports, remoteImport)
serviceNames = append(serviceNames, MainServiceTemplateData{
Service: parser.CamelCase(e.Name),
ServerPkg: serverPkg,
Pkg: proto.PbPackage,
})
}
text, err := pathx.LoadTemplate(category, mainTemplateFile, mainTemplate)
if err != nil {
return err
@@ -42,10 +74,9 @@ func (g *Generator) GenMain(ctx DirContext, proto parser.Proto, cfg *conf.Config
}
return util.With("main").GoFmt(true).Parse(text).SaveTo(map[string]interface{}{
"serviceName": etcFileName,
"imports": strings.Join(imports, pathx.NL),
"pkg": proto.PbPackage,
"serviceNew": stringx.From(proto.Service.Name).ToCamel(),
"service": parser.CamelCase(proto.Service.Name),
"serviceName": etcFileName,
"imports": strings.Join(imports, pathx.NL),
"pkg": proto.PbPackage,
"serviceNames": serviceNames,
}, fileName, false)
}

View File

@@ -18,7 +18,7 @@ import (
const functionTemplate = `
{{if .hasComment}}{{.comment}}{{end}}
func (s *{{.server}}Server) {{.method}} ({{if .notStream}}ctx context.Context,{{if .hasReq}} in {{.request}}{{end}}{{else}}{{if .hasReq}} in {{.request}},{{end}}stream {{.streamBody}}{{end}}) ({{if .notStream}}{{.response}},{{end}}error) {
l := logic.New{{.logicName}}({{if .notStream}}ctx,{{else}}stream.Context(),{{end}}s.svcCtx)
l := {{.logicPkg}}.New{{.logicName}}({{if .notStream}}ctx,{{else}}stream.Context(),{{end}}s.svcCtx)
return l.{{.method}}({{if .hasReq}}in{{if .stream}} ,stream{{end}}{{else}}{{if .stream}}stream{{end}}{{end}})
}
`
@@ -27,7 +27,85 @@ func (s *{{.server}}Server) {{.method}} ({{if .notStream}}ctx context.Context,{{
var serverTemplate string
// GenServer generates rpc server file, which is an implementation of rpc server
func (g *Generator) GenServer(ctx DirContext, proto parser.Proto, cfg *conf.Config) error {
func (g *Generator) GenServer(ctx DirContext, proto parser.Proto, cfg *conf.Config,
c *ZRpcContext) error {
if !c.Multiple {
return g.genServerInCompatibility(ctx, proto, cfg, c)
}
return g.genServerGroup(ctx, proto, cfg)
}
func (g *Generator) genServerGroup(ctx DirContext, proto parser.Proto, cfg *conf.Config) error {
dir := ctx.GetServer()
for _, service := range proto.Service {
var (
serverFile string
logicImport string
)
serverFilename, err := format.FileNamingFormat(cfg.NamingFormat, service.Name+"_server")
if err != nil {
return err
}
serverChildPkg, err := dir.GetChildPackage(service.Name)
if err != nil {
return err
}
logicChildPkg, err := ctx.GetLogic().GetChildPackage(service.Name)
if err != nil {
return err
}
serverDir := filepath.Base(serverChildPkg)
logicImport = fmt.Sprintf(`"%v"`, logicChildPkg)
serverFile = filepath.Join(dir.Filename, serverDir, serverFilename+".go")
svcImport := fmt.Sprintf(`"%v"`, ctx.GetSvc().Package)
pbImport := fmt.Sprintf(`"%v"`, ctx.GetPb().Package)
imports := collection.NewSet()
imports.AddStr(logicImport, svcImport, pbImport)
head := util.GetHead(proto.Name)
funcList, err := g.genFunctions(proto.PbPackage, service, true)
if err != nil {
return err
}
text, err := pathx.LoadTemplate(category, serverTemplateFile, serverTemplate)
if err != nil {
return err
}
notStream := false
for _, rpc := range service.RPC {
if !rpc.StreamsRequest && !rpc.StreamsReturns {
notStream = true
break
}
}
if err = util.With("server").GoFmt(true).Parse(text).SaveTo(map[string]interface{}{
"head": head,
"unimplementedServer": fmt.Sprintf("%s.Unimplemented%sServer", proto.PbPackage,
stringx.From(service.Name).ToCamel()),
"server": stringx.From(service.Name).ToCamel(),
"imports": strings.Join(imports.KeysStr(), pathx.NL),
"funcs": strings.Join(funcList, pathx.NL),
"notStream": notStream,
}, serverFile, true); err != nil {
return err
}
}
return nil
}
func (g *Generator) genServerInCompatibility(ctx DirContext, proto parser.Proto,
cfg *conf.Config, c *ZRpcContext) error {
dir := ctx.GetServer()
logicImport := fmt.Sprintf(`"%v"`, ctx.GetLogic().Package)
svcImport := fmt.Sprintf(`"%v"`, ctx.GetSvc().Package)
@@ -37,14 +115,14 @@ func (g *Generator) GenServer(ctx DirContext, proto parser.Proto, cfg *conf.Conf
imports.AddStr(logicImport, svcImport, pbImport)
head := util.GetHead(proto.Name)
service := proto.Service
service := proto.Service[0]
serverFilename, err := format.FileNamingFormat(cfg.NamingFormat, service.Name+"_server")
if err != nil {
return err
}
serverFile := filepath.Join(dir.Filename, serverFilename+".go")
funcList, err := g.genFunctions(proto.PbPackage, service)
funcList, err := g.genFunctions(proto.PbPackage, service, false)
if err != nil {
return err
}
@@ -62,30 +140,44 @@ func (g *Generator) GenServer(ctx DirContext, proto parser.Proto, cfg *conf.Conf
}
}
err = util.With("server").GoFmt(true).Parse(text).SaveTo(map[string]interface{}{
"head": head,
"unimplementedServer": fmt.Sprintf("%s.Unimplemented%sServer", proto.PbPackage, stringx.From(service.Name).ToCamel()),
"server": stringx.From(service.Name).ToCamel(),
"imports": strings.Join(imports.KeysStr(), pathx.NL),
"funcs": strings.Join(funcList, pathx.NL),
"notStream": notStream,
return util.With("server").GoFmt(true).Parse(text).SaveTo(map[string]interface{}{
"head": head,
"unimplementedServer": fmt.Sprintf("%s.Unimplemented%sServer", proto.PbPackage,
stringx.From(service.Name).ToCamel()),
"server": stringx.From(service.Name).ToCamel(),
"imports": strings.Join(imports.KeysStr(), pathx.NL),
"funcs": strings.Join(funcList, pathx.NL),
"notStream": notStream,
}, serverFile, true)
return err
}
func (g *Generator) genFunctions(goPackage string, service parser.Service) ([]string, error) {
var functionList []string
func (g *Generator) genFunctions(goPackage string, service parser.Service, multiple bool) ([]string, error) {
var (
functionList []string
logicPkg string
)
for _, rpc := range service.RPC {
text, err := pathx.LoadTemplate(category, serverFuncTemplateFile, functionTemplate)
if err != nil {
return nil, err
}
var logicName string
if !multiple {
logicPkg = "logic"
logicName = fmt.Sprintf("%sLogic", stringx.From(rpc.Name).ToCamel())
} else {
nameJoin := fmt.Sprintf("%s_logic", service.Name)
logicPkg = strings.ToLower(stringx.From(nameJoin).ToCamel())
logicName = fmt.Sprintf("%sLogic", stringx.From(rpc.Name).ToCamel())
}
comment := parser.GetComment(rpc.Doc())
streamServer := fmt.Sprintf("%s.%s_%s%s", goPackage, parser.CamelCase(service.Name), parser.CamelCase(rpc.Name), "Server")
streamServer := fmt.Sprintf("%s.%s_%s%s", goPackage, parser.CamelCase(service.Name),
parser.CamelCase(rpc.Name), "Server")
buffer, err := util.With("func").Parse(text).Execute(map[string]interface{}{
"server": stringx.From(service.Name).ToCamel(),
"logicName": fmt.Sprintf("%sLogic", stringx.From(rpc.Name).ToCamel()),
"logicName": logicName,
"method": parser.CamelCase(rpc.Name),
"request": fmt.Sprintf("*%s.%s", goPackage, parser.CamelCase(rpc.RequestType)),
"response": fmt.Sprintf("*%s.%s", goPackage, parser.CamelCase(rpc.ReturnsType)),
@@ -95,6 +187,7 @@ func (g *Generator) genFunctions(goPackage string, service parser.Service) ([]st
"stream": rpc.StreamsRequest || rpc.StreamsReturns,
"notStream": !rpc.StreamsRequest && !rpc.StreamsReturns,
"streamBody": streamServer,
"logicPkg": logicPkg,
})
if err != nil {
return nil, err

View File

@@ -1,4 +1,4 @@
package logic
package {{.packageName}}
import (
"context"

View File

@@ -21,11 +21,10 @@ func main() {
var c config.Config
conf.MustLoad(*configFile, &c)
ctx := svc.NewServiceContext(c)
svr := server.New{{.serviceNew}}Server(ctx)
s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
{{.pkg}}.Register{{.service}}Server(grpcServer, svr)
{{range .serviceNames}} {{.Pkg}}.Register{{.Service}}Server(grpcServer, {{.ServerPkg}}.New{{.Service}}Server(ctx))
{{end}}
if c.Mode == service.DevMode || c.Mode == service.TestMode {
reflection.Register(grpcServer)
}

View File

@@ -43,9 +43,10 @@ type (
// Dir defines a directory
Dir struct {
Base string
Filename string
Package string
Base string
Filename string
Package string
GetChildPackage func(childPath string) (string, error)
}
defaultDirContext struct {
@@ -55,9 +56,11 @@ type (
}
)
func mkdir(ctx *ctx.ProjectContext, proto parser.Proto, _ *conf.Config, c *ZRpcContext) (DirContext, error) {
func mkdir(ctx *ctx.ProjectContext, proto parser.Proto, _ *conf.Config, c *ZRpcContext) (DirContext,
error) {
inner := make(map[string]Dir)
etcDir := filepath.Join(ctx.WorkDir, "etc")
clientDir := filepath.Join(ctx.WorkDir, "client")
internalDir := filepath.Join(ctx.WorkDir, "internal")
configDir := filepath.Join(internalDir, "config")
logicDir := filepath.Join(internalDir, "logic")
@@ -70,64 +73,125 @@ func mkdir(ctx *ctx.ProjectContext, proto parser.Proto, _ *conf.Config, c *ZRpcC
protoGoDir = c.ProtoGenGoDir
}
callDir := filepath.Join(ctx.WorkDir, strings.ToLower(stringx.From(proto.Service.Name).ToCamel()))
if strings.EqualFold(proto.Service.Name, filepath.Base(proto.GoPackage)) {
callDir = filepath.Join(ctx.WorkDir, strings.ToLower(stringx.From(proto.Service.Name+"_client").ToCamel()))
getChildPackage := func(parent, childPath string) (string, error) {
child := strings.TrimPrefix(childPath, parent)
abs := filepath.Join(parent, strings.ToLower(child))
if c.Multiple {
if err := pathx.MkdirIfNotExist(abs); err != nil {
return "", err
}
}
childPath = strings.TrimPrefix(abs, ctx.Dir)
pkg := filepath.Join(ctx.Path, childPath)
return filepath.ToSlash(pkg), nil
}
if !c.Multiple {
callDir := filepath.Join(ctx.WorkDir,
strings.ToLower(stringx.From(proto.Service[0].Name).ToCamel()))
if strings.EqualFold(proto.Service[0].Name, filepath.Base(proto.GoPackage)) {
callDir = filepath.Join(ctx.WorkDir,
strings.ToLower(stringx.From(proto.Service[0].Name+"_client").ToCamel()))
}
inner[call] = Dir{
Filename: callDir,
Package: filepath.ToSlash(filepath.Join(ctx.Path,
strings.TrimPrefix(callDir, ctx.Dir))),
Base: filepath.Base(callDir),
GetChildPackage: func(childPath string) (string, error) {
return getChildPackage(callDir, childPath)
},
}
} else {
inner[call] = Dir{
Filename: clientDir,
Package: filepath.ToSlash(filepath.Join(ctx.Path,
strings.TrimPrefix(clientDir, ctx.Dir))),
Base: filepath.Base(clientDir),
GetChildPackage: func(childPath string) (string, error) {
return getChildPackage(clientDir, childPath)
},
}
}
inner[wd] = Dir{
Filename: ctx.WorkDir,
Package: filepath.ToSlash(filepath.Join(ctx.Path, strings.TrimPrefix(ctx.WorkDir, ctx.Dir))),
Base: filepath.Base(ctx.WorkDir),
Package: filepath.ToSlash(filepath.Join(ctx.Path,
strings.TrimPrefix(ctx.WorkDir, ctx.Dir))),
Base: filepath.Base(ctx.WorkDir),
GetChildPackage: func(childPath string) (string, error) {
return getChildPackage(ctx.WorkDir, childPath)
},
}
inner[etc] = Dir{
Filename: etcDir,
Package: filepath.ToSlash(filepath.Join(ctx.Path, strings.TrimPrefix(etcDir, ctx.Dir))),
Base: filepath.Base(etcDir),
GetChildPackage: func(childPath string) (string, error) {
return getChildPackage(etcDir, childPath)
},
}
inner[internal] = Dir{
Filename: internalDir,
Package: filepath.ToSlash(filepath.Join(ctx.Path, strings.TrimPrefix(internalDir, ctx.Dir))),
Base: filepath.Base(internalDir),
Package: filepath.ToSlash(filepath.Join(ctx.Path,
strings.TrimPrefix(internalDir, ctx.Dir))),
Base: filepath.Base(internalDir),
GetChildPackage: func(childPath string) (string, error) {
return getChildPackage(internalDir, childPath)
},
}
inner[config] = Dir{
Filename: configDir,
Package: filepath.ToSlash(filepath.Join(ctx.Path, strings.TrimPrefix(configDir, ctx.Dir))),
Base: filepath.Base(configDir),
GetChildPackage: func(childPath string) (string, error) {
return getChildPackage(configDir, childPath)
},
}
inner[logic] = Dir{
Filename: logicDir,
Package: filepath.ToSlash(filepath.Join(ctx.Path, strings.TrimPrefix(logicDir, ctx.Dir))),
Base: filepath.Base(logicDir),
GetChildPackage: func(childPath string) (string, error) {
return getChildPackage(logicDir, childPath)
},
}
inner[server] = Dir{
Filename: serverDir,
Package: filepath.ToSlash(filepath.Join(ctx.Path, strings.TrimPrefix(serverDir, ctx.Dir))),
Base: filepath.Base(serverDir),
GetChildPackage: func(childPath string) (string, error) {
return getChildPackage(serverDir, childPath)
},
}
inner[svc] = Dir{
Filename: svcDir,
Package: filepath.ToSlash(filepath.Join(ctx.Path, strings.TrimPrefix(svcDir, ctx.Dir))),
Base: filepath.Base(svcDir),
GetChildPackage: func(childPath string) (string, error) {
return getChildPackage(svcDir, childPath)
},
}
inner[pb] = Dir{
Filename: pbDir,
Package: filepath.ToSlash(filepath.Join(ctx.Path, strings.TrimPrefix(pbDir, ctx.Dir))),
Base: filepath.Base(pbDir),
GetChildPackage: func(childPath string) (string, error) {
return getChildPackage(pbDir, childPath)
},
}
inner[protoGo] = Dir{
Filename: protoGoDir,
Package: filepath.ToSlash(filepath.Join(ctx.Path, strings.TrimPrefix(protoGoDir, ctx.Dir))),
Base: filepath.Base(protoGoDir),
Package: filepath.ToSlash(filepath.Join(ctx.Path,
strings.TrimPrefix(protoGoDir, ctx.Dir))),
Base: filepath.Base(protoGoDir),
GetChildPackage: func(childPath string) (string, error) {
return getChildPackage(protoGoDir, childPath)
},
}
inner[call] = Dir{
Filename: callDir,
Package: filepath.ToSlash(filepath.Join(ctx.Path, strings.TrimPrefix(callDir, ctx.Dir))),
Base: filepath.Base(callDir),
}
for _, v := range inner {
err := pathx.MkdirIfNotExist(v.Filename)
if err != nil {
@@ -151,8 +215,9 @@ func (d *defaultDirContext) SetPbDir(pbDir, grpcDir string) {
d.inner[protoGo] = Dir{
Filename: grpcDir,
Package: filepath.ToSlash(filepath.Join(d.ctx.Path, strings.TrimPrefix(grpcDir, d.ctx.Dir))),
Base: filepath.Base(grpcDir),
Package: filepath.ToSlash(filepath.Join(d.ctx.Path,
strings.TrimPrefix(grpcDir, d.ctx.Dir))),
Base: filepath.Base(grpcDir),
}
}

View File

@@ -23,18 +23,16 @@ const (
)
var templates = map[string]string{
callTemplateFile: callTemplateText,
callInterfaceFunctionTemplateFile: callInterfaceFunctionTemplate,
callFunctionTemplateFile: callFunctionTemplate,
configTemplateFileFile: configTemplate,
etcTemplateFileFile: etcTemplate,
logicTemplateFileFile: logicTemplate,
logicFuncTemplateFileFile: logicFunctionTemplate,
mainTemplateFile: mainTemplate,
serverTemplateFile: serverTemplate,
serverFuncTemplateFile: functionTemplate,
svcTemplateFile: svcTemplate,
rpcTemplateFile: rpcTemplateText,
callTemplateFile: callTemplateText,
configTemplateFileFile: configTemplate,
etcTemplateFileFile: etcTemplate,
logicTemplateFileFile: logicTemplate,
logicFuncTemplateFileFile: logicFunctionTemplate,
mainTemplateFile: mainTemplate,
serverTemplateFile: serverTemplate,
serverFuncTemplateFile: functionTemplate,
svcTemplateFile: svcTemplate,
rpcTemplateFile: rpcTemplateText,
}
// GenTemplates is the entry for command goctl template,

View File

@@ -1,8 +1,6 @@
package parser
import (
"errors"
"fmt"
"go/token"
"os"
"path/filepath"
@@ -14,7 +12,7 @@ import (
)
type (
// DefaultProtoParser types a empty struct
// DefaultProtoParser types an empty struct
DefaultProtoParser struct{}
)
@@ -25,7 +23,7 @@ func NewDefaultProtoParser() *DefaultProtoParser {
// Parse provides to parse the proto file into a golang structure,
// which is convenient for subsequent rpc generation and use
func (p *DefaultProtoParser) Parse(src string) (Proto, error) {
func (p *DefaultProtoParser) Parse(src string, multiple ...bool) (Proto, error) {
var ret Proto
abs, err := filepath.Abs(src)
@@ -45,7 +43,7 @@ func (p *DefaultProtoParser) Parse(src string) (Proto, error) {
return ret, err
}
var serviceList []Service
var serviceList Services
proto.Walk(
set,
proto.WithImport(func(i *proto.Import) {
@@ -76,31 +74,18 @@ func (p *DefaultProtoParser) Parse(src string) (Proto, error) {
}
}),
)
if len(serviceList) == 0 {
return ret, errors.New("rpc service not found")
if err = serviceList.validate(abs, multiple...); err != nil {
return ret, err
}
if len(serviceList) > 1 {
return ret, errors.New("only one service expected")
}
service := serviceList[0]
name := filepath.Base(abs)
for _, rpc := range service.RPC {
if strings.Contains(rpc.RequestType, ".") {
return ret, fmt.Errorf("line %v:%v, request type must defined in %s", rpc.Position.Line, rpc.Position.Column, name)
}
if strings.Contains(rpc.ReturnsType, ".") {
return ret, fmt.Errorf("line %v:%v, returns type must defined in %s", rpc.Position.Line, rpc.Position.Column, name)
}
}
if len(ret.GoPackage) == 0 {
ret.GoPackage = ret.Package.Name
}
ret.PbPackage = GoSanitized(filepath.Base(ret.GoPackage))
ret.Src = abs
ret.Name = name
ret.Service = service
ret.Name = filepath.Base(abs)
ret.Service = serviceList
return ret, nil
}

View File

@@ -19,17 +19,22 @@ func TestDefaultProtoParse(t *testing.T) {
assert.Equal(t, "test", data.Package.Name)
assert.Equal(t, true, data.GoPackage == "go")
assert.Equal(t, true, data.PbPackage == "_go")
assert.Equal(t, []string{"Inline", "Inner", "TestMessage", "TestReply", "TestReq"}, func() []string {
var list []string
for _, item := range data.Message {
list = append(list, item.Name)
}
sort.Strings(list)
return list
}())
assert.Equal(t, []string{"Inline", "Inner", "TestMessage", "TestReply", "TestReq"},
func() []string {
var list []string
for _, item := range data.Message {
list = append(list, item.Name)
}
sort.Strings(list)
return list
}())
assert.Equal(t, true, func() bool {
s := data.Service
if len(data.Service) != 1 {
return false
}
s := data.Service[0]
if s.Name != "TestService" {
return false
}

View File

@@ -9,5 +9,5 @@ type Proto struct {
GoPackage string
Import []Import
Message []Message
Service Service
Service Services
}

View File

@@ -1,10 +1,54 @@
package parser
import "github.com/emicklei/proto"
import (
"errors"
"fmt"
"path/filepath"
"strings"
// Service describes the rpc service, which is the relevant
// content after the translation of the proto file
type Service struct {
*proto.Service
RPC []*RPC
"github.com/emicklei/proto"
)
type (
// Services is a slice of Service.
Services []Service
// Service describes the rpc service, which is the relevant
// content after the translation of the proto file
Service struct {
*proto.Service
RPC []*RPC
}
)
func (s Services) validate(filename string, multipleOpt ...bool) error {
if len(s) == 0 {
return errors.New("rpc service not found")
}
var multiple bool
for _, c := range multipleOpt {
multiple = c
}
if !multiple && len(s) > 1 {
return errors.New("only one service expected")
}
name := filepath.Base(filename)
for _, service := range s {
for _, rpc := range service.RPC {
if strings.Contains(rpc.RequestType, ".") {
return fmt.Errorf("line %v:%v, request type must defined in %s",
rpc.Position.Line,
rpc.Position.Column, name)
}
if strings.Contains(rpc.ReturnsType, ".") {
return fmt.Errorf("line %v:%v, returns type must defined in %s",
rpc.Position.Line,
rpc.Position.Column, name)
}
}
}
return nil
}