Browse Source

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
anqiansong 2 years ago
parent
commit
ca3c687f1c
47 changed files with 2289 additions and 361 deletions
  1. 1 1
      go.mod
  2. 17 0
      tools/goctl/example/rpc/hello.proto
  3. 37 0
      tools/goctl/example/rpc/hello/client/greet/greet.go
  4. 6 0
      tools/goctl/example/rpc/hello/etc/hello.yaml
  5. 39 0
      tools/goctl/example/rpc/hello/hello.go
  6. 7 0
      tools/goctl/example/rpc/hello/internal/config/config.go
  7. 30 0
      tools/goctl/example/rpc/hello/internal/logic/greet/sayhellologic.go
  8. 28 0
      tools/goctl/example/rpc/hello/internal/server/greet/greetserver.go
  9. 13 0
      tools/goctl/example/rpc/hello/internal/svc/servicecontext.go
  10. 208 0
      tools/goctl/example/rpc/hello/pb/hello/hello.pb.go
  11. 105 0
      tools/goctl/example/rpc/hello/pb/hello/hello_grpc.pb.go
  12. 33 0
      tools/goctl/example/rpc/hi.proto
  13. 41 0
      tools/goctl/example/rpc/hi/client/event/event.go
  14. 47 0
      tools/goctl/example/rpc/hi/client/greet/greet.go
  15. 6 0
      tools/goctl/example/rpc/hi/etc/hi.yaml
  16. 41 0
      tools/goctl/example/rpc/hi/hi.go
  17. 7 0
      tools/goctl/example/rpc/hi/internal/config/config.go
  18. 30 0
      tools/goctl/example/rpc/hi/internal/logic/event/askquestionlogic.go
  19. 30 0
      tools/goctl/example/rpc/hi/internal/logic/greet/sayhellologic.go
  20. 30 0
      tools/goctl/example/rpc/hi/internal/logic/greet/sayhilogic.go
  21. 28 0
      tools/goctl/example/rpc/hi/internal/server/event/eventserver.go
  22. 33 0
      tools/goctl/example/rpc/hi/internal/server/greet/greetserver.go
  23. 13 0
      tools/goctl/example/rpc/hi/internal/svc/servicecontext.go
  24. 443 0
      tools/goctl/example/rpc/hi/pb/hi/hi.pb.go
  25. 227 0
      tools/goctl/example/rpc/hi/pb/hi/hi_grpc.pb.go
  26. 22 0
      tools/goctl/example/rpc/multiple_rpc_service_generate.sh
  27. 22 0
      tools/goctl/example/rpc/single_rpc_service_generate.sh
  28. 3 1
      tools/goctl/go.mod
  29. 32 2
      tools/goctl/go.sum
  30. 134 165
      tools/goctl/rpc/README.md
  31. 2 0
      tools/goctl/rpc/cli/cli.go
  32. 1 0
      tools/goctl/rpc/cli/zrpc.go
  33. 44 23
      tools/goctl/rpc/cmd.go
  34. 23 13
      tools/goctl/rpc/generator/gen.go
  35. 10 34
      tools/goctl/rpc/generator/gen_test.go
  36. 98 12
      tools/goctl/rpc/generator/gencall.go
  37. 78 10
      tools/goctl/rpc/generator/genlogic.go
  38. 40 9
      tools/goctl/rpc/generator/genmain.go
  39. 109 16
      tools/goctl/rpc/generator/genserver.go
  40. 1 1
      tools/goctl/rpc/generator/logic.tpl
  41. 2 3
      tools/goctl/rpc/generator/main.tpl
  42. 85 20
      tools/goctl/rpc/generator/mkdir.go
  43. 10 12
      tools/goctl/rpc/generator/template.go
  44. 8 23
      tools/goctl/rpc/parser/parser.go
  45. 14 9
      tools/goctl/rpc/parser/parser_test.go
  46. 1 1
      tools/goctl/rpc/parser/proto.go
  47. 50 6
      tools/goctl/rpc/parser/service.go

+ 1 - 1
go.mod

@@ -54,6 +54,6 @@ require (
 	go.uber.org/multierr v1.8.0 // indirect
 	go.uber.org/zap v1.21.0 // indirect
 	golang.org/x/net v0.0.0-20220531201128-c960675eff93 // indirect
-	google.golang.org/genproto v0.0.0-20220602131408-e326c6e8e9c8 // indirect
+	google.golang.org/genproto v0.0.0-20220602131408-e326c6e8e9c8
 	k8s.io/klog/v2 v2.40.1 // indirect
 )

+ 17 - 0
tools/goctl/example/rpc/hello.proto

@@ -0,0 +1,17 @@
+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);
+}

+ 37 - 0
tools/goctl/example/rpc/hello/client/greet/greet.go

@@ -0,0 +1,37 @@
+// Code generated by goctl. DO NOT EDIT!
+// Source: hello.proto
+
+package client
+
+import (
+	"context"
+
+	"github.com/zeromicro/go-zero/tools/goctl/example/rpc/hello/pb/hello"
+
+	"github.com/zeromicro/go-zero/zrpc"
+	"google.golang.org/grpc"
+)
+
+type (
+	HelloReq  = hello.HelloReq
+	HelloResp = hello.HelloResp
+
+	Greet interface {
+		SayHello(ctx context.Context, in *HelloReq, opts ...grpc.CallOption) (*HelloResp, error)
+	}
+
+	defaultGreet struct {
+		cli zrpc.Client
+	}
+)
+
+func NewGreet(cli zrpc.Client) Greet {
+	return &defaultGreet{
+		cli: cli,
+	}
+}
+
+func (m *defaultGreet) SayHello(ctx context.Context, in *HelloReq, opts ...grpc.CallOption) (*HelloResp, error) {
+	client := hello.NewGreetClient(m.cli.Conn())
+	return client.SayHello(ctx, in, opts...)
+}

+ 6 - 0
tools/goctl/example/rpc/hello/etc/hello.yaml

@@ -0,0 +1,6 @@
+Name: hello.rpc
+ListenOn: 127.0.0.1:8080
+Etcd:
+  Hosts:
+  - 127.0.0.1:2379
+  Key: hello.rpc

+ 39 - 0
tools/goctl/example/rpc/hello/hello.go

@@ -0,0 +1,39 @@
+package main
+
+import (
+	"flag"
+	"fmt"
+
+	"github.com/zeromicro/go-zero/tools/goctl/example/rpc/hello/internal/config"
+	greetServer "github.com/zeromicro/go-zero/tools/goctl/example/rpc/hello/internal/server/greet"
+	"github.com/zeromicro/go-zero/tools/goctl/example/rpc/hello/internal/svc"
+	"github.com/zeromicro/go-zero/tools/goctl/example/rpc/hello/pb/hello"
+
+	"github.com/zeromicro/go-zero/core/conf"
+	"github.com/zeromicro/go-zero/core/service"
+	"github.com/zeromicro/go-zero/zrpc"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/reflection"
+)
+
+var configFile = flag.String("f", "etc/hello.yaml", "the config file")
+
+func main() {
+	flag.Parse()
+
+	var c config.Config
+	conf.MustLoad(*configFile, &c)
+	ctx := svc.NewServiceContext(c)
+
+	s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
+		hello.RegisterGreetServer(grpcServer, greetServer.NewGreetServer(ctx))
+
+		if c.Mode == service.DevMode || c.Mode == service.TestMode {
+			reflection.Register(grpcServer)
+		}
+	})
+	defer s.Stop()
+
+	fmt.Printf("Starting rpc server at %s...\n", c.ListenOn)
+	s.Start()
+}

+ 7 - 0
tools/goctl/example/rpc/hello/internal/config/config.go

@@ -0,0 +1,7 @@
+package config
+
+import "github.com/zeromicro/go-zero/zrpc"
+
+type Config struct {
+	zrpc.RpcServerConf
+}

+ 30 - 0
tools/goctl/example/rpc/hello/internal/logic/greet/sayhellologic.go

@@ -0,0 +1,30 @@
+package greetlogic
+
+import (
+	"context"
+
+	"github.com/zeromicro/go-zero/tools/goctl/example/rpc/hello/internal/svc"
+	"github.com/zeromicro/go-zero/tools/goctl/example/rpc/hello/pb/hello"
+
+	"github.com/zeromicro/go-zero/core/logx"
+)
+
+type SayHelloLogic struct {
+	ctx    context.Context
+	svcCtx *svc.ServiceContext
+	logx.Logger
+}
+
+func NewSayHelloLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SayHelloLogic {
+	return &SayHelloLogic{
+		ctx:    ctx,
+		svcCtx: svcCtx,
+		Logger: logx.WithContext(ctx),
+	}
+}
+
+func (l *SayHelloLogic) SayHello(in *hello.HelloReq) (*hello.HelloResp, error) {
+	// todo: add your logic here and delete this line
+
+	return &hello.HelloResp{}, nil
+}

+ 28 - 0
tools/goctl/example/rpc/hello/internal/server/greet/greetserver.go

@@ -0,0 +1,28 @@
+// Code generated by goctl. DO NOT EDIT!
+// Source: hello.proto
+
+package server
+
+import (
+	"context"
+
+	"github.com/zeromicro/go-zero/tools/goctl/example/rpc/hello/internal/logic/greet"
+	"github.com/zeromicro/go-zero/tools/goctl/example/rpc/hello/internal/svc"
+	"github.com/zeromicro/go-zero/tools/goctl/example/rpc/hello/pb/hello"
+)
+
+type GreetServer struct {
+	svcCtx *svc.ServiceContext
+	hello.UnimplementedGreetServer
+}
+
+func NewGreetServer(svcCtx *svc.ServiceContext) *GreetServer {
+	return &GreetServer{
+		svcCtx: svcCtx,
+	}
+}
+
+func (s *GreetServer) SayHello(ctx context.Context, in *hello.HelloReq) (*hello.HelloResp, error) {
+	l := greetlogic.NewSayHelloLogic(ctx, s.svcCtx)
+	return l.SayHello(in)
+}

+ 13 - 0
tools/goctl/example/rpc/hello/internal/svc/servicecontext.go

@@ -0,0 +1,13 @@
+package svc
+
+import "github.com/zeromicro/go-zero/tools/goctl/example/rpc/hello/internal/config"
+
+type ServiceContext struct {
+	Config config.Config
+}
+
+func NewServiceContext(c config.Config) *ServiceContext {
+	return &ServiceContext{
+		Config: c,
+	}
+}

+ 208 - 0
tools/goctl/example/rpc/hello/pb/hello/hello.pb.go

@@ -0,0 +1,208 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.28.0
+// 	protoc        v3.19.4
+// source: hello.proto
+
+package hello
+
+import (
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type HelloReq struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	In string `protobuf:"bytes,1,opt,name=in,proto3" json:"in,omitempty"`
+}
+
+func (x *HelloReq) Reset() {
+	*x = HelloReq{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_hello_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *HelloReq) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*HelloReq) ProtoMessage() {}
+
+func (x *HelloReq) ProtoReflect() protoreflect.Message {
+	mi := &file_hello_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use HelloReq.ProtoReflect.Descriptor instead.
+func (*HelloReq) Descriptor() ([]byte, []int) {
+	return file_hello_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *HelloReq) GetIn() string {
+	if x != nil {
+		return x.In
+	}
+	return ""
+}
+
+type HelloResp struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Msg string `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg,omitempty"`
+}
+
+func (x *HelloResp) Reset() {
+	*x = HelloResp{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_hello_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *HelloResp) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*HelloResp) ProtoMessage() {}
+
+func (x *HelloResp) ProtoReflect() protoreflect.Message {
+	mi := &file_hello_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use HelloResp.ProtoReflect.Descriptor instead.
+func (*HelloResp) Descriptor() ([]byte, []int) {
+	return file_hello_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *HelloResp) GetMsg() string {
+	if x != nil {
+		return x.Msg
+	}
+	return ""
+}
+
+var File_hello_proto protoreflect.FileDescriptor
+
+var file_hello_proto_rawDesc = []byte{
+	0x0a, 0x0b, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x68,
+	0x65, 0x6c, 0x6c, 0x6f, 0x22, 0x1a, 0x0a, 0x08, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71,
+	0x12, 0x0e, 0x0a, 0x02, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x6e,
+	0x22, 0x1d, 0x0a, 0x09, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x12, 0x10, 0x0a,
+	0x03, 0x6d, 0x73, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x32,
+	0x36, 0x0a, 0x05, 0x47, 0x72, 0x65, 0x65, 0x74, 0x12, 0x2d, 0x0a, 0x08, 0x53, 0x61, 0x79, 0x48,
+	0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x0f, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2e, 0x48, 0x65, 0x6c,
+	0x6c, 0x6f, 0x52, 0x65, 0x71, 0x1a, 0x10, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2e, 0x48, 0x65,
+	0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x42, 0x09, 0x5a, 0x07, 0x2e, 0x2f, 0x68, 0x65, 0x6c,
+	0x6c, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+	file_hello_proto_rawDescOnce sync.Once
+	file_hello_proto_rawDescData = file_hello_proto_rawDesc
+)
+
+func file_hello_proto_rawDescGZIP() []byte {
+	file_hello_proto_rawDescOnce.Do(func() {
+		file_hello_proto_rawDescData = protoimpl.X.CompressGZIP(file_hello_proto_rawDescData)
+	})
+	return file_hello_proto_rawDescData
+}
+
+var file_hello_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_hello_proto_goTypes = []interface{}{
+	(*HelloReq)(nil),  // 0: hello.HelloReq
+	(*HelloResp)(nil), // 1: hello.HelloResp
+}
+var file_hello_proto_depIdxs = []int32{
+	0, // 0: hello.Greet.SayHello:input_type -> hello.HelloReq
+	1, // 1: hello.Greet.SayHello:output_type -> hello.HelloResp
+	1, // [1:2] is the sub-list for method output_type
+	0, // [0:1] is the sub-list for method input_type
+	0, // [0:0] is the sub-list for extension type_name
+	0, // [0:0] is the sub-list for extension extendee
+	0, // [0:0] is the sub-list for field type_name
+}
+
+func init() { file_hello_proto_init() }
+func file_hello_proto_init() {
+	if File_hello_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_hello_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*HelloReq); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_hello_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*HelloResp); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_hello_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   2,
+			NumExtensions: 0,
+			NumServices:   1,
+		},
+		GoTypes:           file_hello_proto_goTypes,
+		DependencyIndexes: file_hello_proto_depIdxs,
+		MessageInfos:      file_hello_proto_msgTypes,
+	}.Build()
+	File_hello_proto = out.File
+	file_hello_proto_rawDesc = nil
+	file_hello_proto_goTypes = nil
+	file_hello_proto_depIdxs = nil
+}

+ 105 - 0
tools/goctl/example/rpc/hello/pb/hello/hello_grpc.pb.go

@@ -0,0 +1,105 @@
+// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
+// versions:
+// - protoc-gen-go-grpc v1.2.0
+// - protoc             v3.19.4
+// source: hello.proto
+
+package hello
+
+import (
+	context "context"
+	grpc "google.golang.org/grpc"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
+)
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the grpc package it is being compiled against.
+// Requires gRPC-Go v1.32.0 or later.
+const _ = grpc.SupportPackageIsVersion7
+
+// GreetClient is the client API for Greet service.
+//
+// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
+type GreetClient interface {
+	SayHello(ctx context.Context, in *HelloReq, opts ...grpc.CallOption) (*HelloResp, error)
+}
+
+type greetClient struct {
+	cc grpc.ClientConnInterface
+}
+
+func NewGreetClient(cc grpc.ClientConnInterface) GreetClient {
+	return &greetClient{cc}
+}
+
+func (c *greetClient) SayHello(ctx context.Context, in *HelloReq, opts ...grpc.CallOption) (*HelloResp, error) {
+	out := new(HelloResp)
+	err := c.cc.Invoke(ctx, "/hello.Greet/SayHello", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+// GreetServer is the server API for Greet service.
+// All implementations must embed UnimplementedGreetServer
+// for forward compatibility
+type GreetServer interface {
+	SayHello(context.Context, *HelloReq) (*HelloResp, error)
+	mustEmbedUnimplementedGreetServer()
+}
+
+// UnimplementedGreetServer must be embedded to have forward compatible implementations.
+type UnimplementedGreetServer struct {
+}
+
+func (UnimplementedGreetServer) SayHello(context.Context, *HelloReq) (*HelloResp, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented")
+}
+func (UnimplementedGreetServer) mustEmbedUnimplementedGreetServer() {}
+
+// UnsafeGreetServer may be embedded to opt out of forward compatibility for this service.
+// Use of this interface is not recommended, as added methods to GreetServer will
+// result in compilation errors.
+type UnsafeGreetServer interface {
+	mustEmbedUnimplementedGreetServer()
+}
+
+func RegisterGreetServer(s grpc.ServiceRegistrar, srv GreetServer) {
+	s.RegisterService(&Greet_ServiceDesc, srv)
+}
+
+func _Greet_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(HelloReq)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(GreetServer).SayHello(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/hello.Greet/SayHello",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(GreetServer).SayHello(ctx, req.(*HelloReq))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+// Greet_ServiceDesc is the grpc.ServiceDesc for Greet service.
+// It's only intended for direct use with grpc.RegisterService,
+// and not to be introspected or modified (even as a copy)
+var Greet_ServiceDesc = grpc.ServiceDesc{
+	ServiceName: "hello.Greet",
+	HandlerType: (*GreetServer)(nil),
+	Methods: []grpc.MethodDesc{
+		{
+			MethodName: "SayHello",
+			Handler:    _Greet_SayHello_Handler,
+		},
+	},
+	Streams:  []grpc.StreamDesc{},
+	Metadata: "hello.proto",
+}

+ 33 - 0
tools/goctl/example/rpc/hi.proto

@@ -0,0 +1,33 @@
+syntax = "proto3";
+
+package hi;
+
+option go_package = "./hi";
+
+message HiReq {
+  string in = 1;
+}
+
+message HelloReq {
+  string in = 1;
+}
+
+message HiResp {
+  string msg = 1;
+}
+
+message HelloResp {
+  string msg = 1;
+}
+
+service Greet {
+  rpc SayHi(HiReq) returns (HiResp);
+  rpc SayHello(HelloReq) returns (HelloResp);
+}
+
+message EventReq{}
+message EventResp{}
+
+service Event {
+  rpc AskQuestion(EventReq) returns (EventResp);
+}

+ 41 - 0
tools/goctl/example/rpc/hi/client/event/event.go

@@ -0,0 +1,41 @@
+// Code generated by goctl. DO NOT EDIT!
+// Source: hi.proto
+
+package client
+
+import (
+	"context"
+
+	"github.com/zeromicro/go-zero/tools/goctl/example/rpc/hi/pb/hi"
+
+	"github.com/zeromicro/go-zero/zrpc"
+	"google.golang.org/grpc"
+)
+
+type (
+	EventReq  = hi.EventReq
+	EventResp = hi.EventResp
+	HelloReq  = hi.HelloReq
+	HelloResp = hi.HelloResp
+	HiReq     = hi.HiReq
+	HiResp    = hi.HiResp
+
+	Event interface {
+		AskQuestion(ctx context.Context, in *EventReq, opts ...grpc.CallOption) (*EventResp, error)
+	}
+
+	defaultEvent struct {
+		cli zrpc.Client
+	}
+)
+
+func NewEvent(cli zrpc.Client) Event {
+	return &defaultEvent{
+		cli: cli,
+	}
+}
+
+func (m *defaultEvent) AskQuestion(ctx context.Context, in *EventReq, opts ...grpc.CallOption) (*EventResp, error) {
+	client := hi.NewEventClient(m.cli.Conn())
+	return client.AskQuestion(ctx, in, opts...)
+}

+ 47 - 0
tools/goctl/example/rpc/hi/client/greet/greet.go

@@ -0,0 +1,47 @@
+// Code generated by goctl. DO NOT EDIT!
+// Source: hi.proto
+
+package client
+
+import (
+	"context"
+
+	"github.com/zeromicro/go-zero/tools/goctl/example/rpc/hi/pb/hi"
+
+	"github.com/zeromicro/go-zero/zrpc"
+	"google.golang.org/grpc"
+)
+
+type (
+	EventReq  = hi.EventReq
+	EventResp = hi.EventResp
+	HelloReq  = hi.HelloReq
+	HelloResp = hi.HelloResp
+	HiReq     = hi.HiReq
+	HiResp    = hi.HiResp
+
+	Greet interface {
+		SayHi(ctx context.Context, in *HiReq, opts ...grpc.CallOption) (*HiResp, error)
+		SayHello(ctx context.Context, in *HelloReq, opts ...grpc.CallOption) (*HelloResp, error)
+	}
+
+	defaultGreet struct {
+		cli zrpc.Client
+	}
+)
+
+func NewGreet(cli zrpc.Client) Greet {
+	return &defaultGreet{
+		cli: cli,
+	}
+}
+
+func (m *defaultGreet) SayHi(ctx context.Context, in *HiReq, opts ...grpc.CallOption) (*HiResp, error) {
+	client := hi.NewGreetClient(m.cli.Conn())
+	return client.SayHi(ctx, in, opts...)
+}
+
+func (m *defaultGreet) SayHello(ctx context.Context, in *HelloReq, opts ...grpc.CallOption) (*HelloResp, error) {
+	client := hi.NewGreetClient(m.cli.Conn())
+	return client.SayHello(ctx, in, opts...)
+}

+ 6 - 0
tools/goctl/example/rpc/hi/etc/hi.yaml

@@ -0,0 +1,6 @@
+Name: hi.rpc
+ListenOn: 127.0.0.1:8080
+Etcd:
+  Hosts:
+  - 127.0.0.1:2379
+  Key: hi.rpc

+ 41 - 0
tools/goctl/example/rpc/hi/hi.go

@@ -0,0 +1,41 @@
+package main
+
+import (
+	"flag"
+	"fmt"
+
+	"github.com/zeromicro/go-zero/tools/goctl/example/rpc/hi/internal/config"
+	eventServer "github.com/zeromicro/go-zero/tools/goctl/example/rpc/hi/internal/server/event"
+	greetServer "github.com/zeromicro/go-zero/tools/goctl/example/rpc/hi/internal/server/greet"
+	"github.com/zeromicro/go-zero/tools/goctl/example/rpc/hi/internal/svc"
+	"github.com/zeromicro/go-zero/tools/goctl/example/rpc/hi/pb/hi"
+
+	"github.com/zeromicro/go-zero/core/conf"
+	"github.com/zeromicro/go-zero/core/service"
+	"github.com/zeromicro/go-zero/zrpc"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/reflection"
+)
+
+var configFile = flag.String("f", "etc/hi.yaml", "the config file")
+
+func main() {
+	flag.Parse()
+
+	var c config.Config
+	conf.MustLoad(*configFile, &c)
+	ctx := svc.NewServiceContext(c)
+
+	s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
+		hi.RegisterGreetServer(grpcServer, greetServer.NewGreetServer(ctx))
+		hi.RegisterEventServer(grpcServer, eventServer.NewEventServer(ctx))
+
+		if c.Mode == service.DevMode || c.Mode == service.TestMode {
+			reflection.Register(grpcServer)
+		}
+	})
+	defer s.Stop()
+
+	fmt.Printf("Starting rpc server at %s...\n", c.ListenOn)
+	s.Start()
+}

+ 7 - 0
tools/goctl/example/rpc/hi/internal/config/config.go

@@ -0,0 +1,7 @@
+package config
+
+import "github.com/zeromicro/go-zero/zrpc"
+
+type Config struct {
+	zrpc.RpcServerConf
+}

+ 30 - 0
tools/goctl/example/rpc/hi/internal/logic/event/askquestionlogic.go

@@ -0,0 +1,30 @@
+package eventlogic
+
+import (
+	"context"
+
+	"github.com/zeromicro/go-zero/tools/goctl/example/rpc/hi/internal/svc"
+	"github.com/zeromicro/go-zero/tools/goctl/example/rpc/hi/pb/hi"
+
+	"github.com/zeromicro/go-zero/core/logx"
+)
+
+type AskQuestionLogic struct {
+	ctx    context.Context
+	svcCtx *svc.ServiceContext
+	logx.Logger
+}
+
+func NewAskQuestionLogic(ctx context.Context, svcCtx *svc.ServiceContext) *AskQuestionLogic {
+	return &AskQuestionLogic{
+		ctx:    ctx,
+		svcCtx: svcCtx,
+		Logger: logx.WithContext(ctx),
+	}
+}
+
+func (l *AskQuestionLogic) AskQuestion(in *hi.EventReq) (*hi.EventResp, error) {
+	// todo: add your logic here and delete this line
+
+	return &hi.EventResp{}, nil
+}

+ 30 - 0
tools/goctl/example/rpc/hi/internal/logic/greet/sayhellologic.go

@@ -0,0 +1,30 @@
+package greetlogic
+
+import (
+	"context"
+
+	"github.com/zeromicro/go-zero/tools/goctl/example/rpc/hi/internal/svc"
+	"github.com/zeromicro/go-zero/tools/goctl/example/rpc/hi/pb/hi"
+
+	"github.com/zeromicro/go-zero/core/logx"
+)
+
+type SayHelloLogic struct {
+	ctx    context.Context
+	svcCtx *svc.ServiceContext
+	logx.Logger
+}
+
+func NewSayHelloLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SayHelloLogic {
+	return &SayHelloLogic{
+		ctx:    ctx,
+		svcCtx: svcCtx,
+		Logger: logx.WithContext(ctx),
+	}
+}
+
+func (l *SayHelloLogic) SayHello(in *hi.HelloReq) (*hi.HelloResp, error) {
+	// todo: add your logic here and delete this line
+
+	return &hi.HelloResp{}, nil
+}

+ 30 - 0
tools/goctl/example/rpc/hi/internal/logic/greet/sayhilogic.go

@@ -0,0 +1,30 @@
+package greetlogic
+
+import (
+	"context"
+
+	"github.com/zeromicro/go-zero/tools/goctl/example/rpc/hi/internal/svc"
+	"github.com/zeromicro/go-zero/tools/goctl/example/rpc/hi/pb/hi"
+
+	"github.com/zeromicro/go-zero/core/logx"
+)
+
+type SayHiLogic struct {
+	ctx    context.Context
+	svcCtx *svc.ServiceContext
+	logx.Logger
+}
+
+func NewSayHiLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SayHiLogic {
+	return &SayHiLogic{
+		ctx:    ctx,
+		svcCtx: svcCtx,
+		Logger: logx.WithContext(ctx),
+	}
+}
+
+func (l *SayHiLogic) SayHi(in *hi.HiReq) (*hi.HiResp, error) {
+	// todo: add your logic here and delete this line
+
+	return &hi.HiResp{}, nil
+}

+ 28 - 0
tools/goctl/example/rpc/hi/internal/server/event/eventserver.go

@@ -0,0 +1,28 @@
+// Code generated by goctl. DO NOT EDIT!
+// Source: hi.proto
+
+package server
+
+import (
+	"context"
+
+	"github.com/zeromicro/go-zero/tools/goctl/example/rpc/hi/internal/logic/event"
+	"github.com/zeromicro/go-zero/tools/goctl/example/rpc/hi/internal/svc"
+	"github.com/zeromicro/go-zero/tools/goctl/example/rpc/hi/pb/hi"
+)
+
+type EventServer struct {
+	svcCtx *svc.ServiceContext
+	hi.UnimplementedEventServer
+}
+
+func NewEventServer(svcCtx *svc.ServiceContext) *EventServer {
+	return &EventServer{
+		svcCtx: svcCtx,
+	}
+}
+
+func (s *EventServer) AskQuestion(ctx context.Context, in *hi.EventReq) (*hi.EventResp, error) {
+	l := eventlogic.NewAskQuestionLogic(ctx, s.svcCtx)
+	return l.AskQuestion(in)
+}

+ 33 - 0
tools/goctl/example/rpc/hi/internal/server/greet/greetserver.go

@@ -0,0 +1,33 @@
+// Code generated by goctl. DO NOT EDIT!
+// Source: hi.proto
+
+package server
+
+import (
+	"context"
+
+	"github.com/zeromicro/go-zero/tools/goctl/example/rpc/hi/internal/logic/greet"
+	"github.com/zeromicro/go-zero/tools/goctl/example/rpc/hi/internal/svc"
+	"github.com/zeromicro/go-zero/tools/goctl/example/rpc/hi/pb/hi"
+)
+
+type GreetServer struct {
+	svcCtx *svc.ServiceContext
+	hi.UnimplementedGreetServer
+}
+
+func NewGreetServer(svcCtx *svc.ServiceContext) *GreetServer {
+	return &GreetServer{
+		svcCtx: svcCtx,
+	}
+}
+
+func (s *GreetServer) SayHi(ctx context.Context, in *hi.HiReq) (*hi.HiResp, error) {
+	l := greetlogic.NewSayHiLogic(ctx, s.svcCtx)
+	return l.SayHi(in)
+}
+
+func (s *GreetServer) SayHello(ctx context.Context, in *hi.HelloReq) (*hi.HelloResp, error) {
+	l := greetlogic.NewSayHelloLogic(ctx, s.svcCtx)
+	return l.SayHello(in)
+}

+ 13 - 0
tools/goctl/example/rpc/hi/internal/svc/servicecontext.go

@@ -0,0 +1,13 @@
+package svc
+
+import "github.com/zeromicro/go-zero/tools/goctl/example/rpc/hi/internal/config"
+
+type ServiceContext struct {
+	Config config.Config
+}
+
+func NewServiceContext(c config.Config) *ServiceContext {
+	return &ServiceContext{
+		Config: c,
+	}
+}

+ 443 - 0
tools/goctl/example/rpc/hi/pb/hi/hi.pb.go

@@ -0,0 +1,443 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.28.0
+// 	protoc        v3.19.4
+// source: hi.proto
+
+package hi
+
+import (
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type HiReq struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	In string `protobuf:"bytes,1,opt,name=in,proto3" json:"in,omitempty"`
+}
+
+func (x *HiReq) Reset() {
+	*x = HiReq{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_hi_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *HiReq) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*HiReq) ProtoMessage() {}
+
+func (x *HiReq) ProtoReflect() protoreflect.Message {
+	mi := &file_hi_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use HiReq.ProtoReflect.Descriptor instead.
+func (*HiReq) Descriptor() ([]byte, []int) {
+	return file_hi_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *HiReq) GetIn() string {
+	if x != nil {
+		return x.In
+	}
+	return ""
+}
+
+type HelloReq struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	In string `protobuf:"bytes,1,opt,name=in,proto3" json:"in,omitempty"`
+}
+
+func (x *HelloReq) Reset() {
+	*x = HelloReq{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_hi_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *HelloReq) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*HelloReq) ProtoMessage() {}
+
+func (x *HelloReq) ProtoReflect() protoreflect.Message {
+	mi := &file_hi_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use HelloReq.ProtoReflect.Descriptor instead.
+func (*HelloReq) Descriptor() ([]byte, []int) {
+	return file_hi_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *HelloReq) GetIn() string {
+	if x != nil {
+		return x.In
+	}
+	return ""
+}
+
+type HiResp struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Msg string `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg,omitempty"`
+}
+
+func (x *HiResp) Reset() {
+	*x = HiResp{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_hi_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *HiResp) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*HiResp) ProtoMessage() {}
+
+func (x *HiResp) ProtoReflect() protoreflect.Message {
+	mi := &file_hi_proto_msgTypes[2]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use HiResp.ProtoReflect.Descriptor instead.
+func (*HiResp) Descriptor() ([]byte, []int) {
+	return file_hi_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *HiResp) GetMsg() string {
+	if x != nil {
+		return x.Msg
+	}
+	return ""
+}
+
+type HelloResp struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Msg string `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg,omitempty"`
+}
+
+func (x *HelloResp) Reset() {
+	*x = HelloResp{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_hi_proto_msgTypes[3]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *HelloResp) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*HelloResp) ProtoMessage() {}
+
+func (x *HelloResp) ProtoReflect() protoreflect.Message {
+	mi := &file_hi_proto_msgTypes[3]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use HelloResp.ProtoReflect.Descriptor instead.
+func (*HelloResp) Descriptor() ([]byte, []int) {
+	return file_hi_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *HelloResp) GetMsg() string {
+	if x != nil {
+		return x.Msg
+	}
+	return ""
+}
+
+type EventReq struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+}
+
+func (x *EventReq) Reset() {
+	*x = EventReq{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_hi_proto_msgTypes[4]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *EventReq) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*EventReq) ProtoMessage() {}
+
+func (x *EventReq) ProtoReflect() protoreflect.Message {
+	mi := &file_hi_proto_msgTypes[4]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use EventReq.ProtoReflect.Descriptor instead.
+func (*EventReq) Descriptor() ([]byte, []int) {
+	return file_hi_proto_rawDescGZIP(), []int{4}
+}
+
+type EventResp struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+}
+
+func (x *EventResp) Reset() {
+	*x = EventResp{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_hi_proto_msgTypes[5]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *EventResp) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*EventResp) ProtoMessage() {}
+
+func (x *EventResp) ProtoReflect() protoreflect.Message {
+	mi := &file_hi_proto_msgTypes[5]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use EventResp.ProtoReflect.Descriptor instead.
+func (*EventResp) Descriptor() ([]byte, []int) {
+	return file_hi_proto_rawDescGZIP(), []int{5}
+}
+
+var File_hi_proto protoreflect.FileDescriptor
+
+var file_hi_proto_rawDesc = []byte{
+	0x0a, 0x08, 0x68, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x02, 0x68, 0x69, 0x22, 0x17,
+	0x0a, 0x05, 0x48, 0x69, 0x52, 0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x6e, 0x18, 0x01, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x6e, 0x22, 0x1a, 0x0a, 0x08, 0x48, 0x65, 0x6c, 0x6c, 0x6f,
+	0x52, 0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x02, 0x69, 0x6e, 0x22, 0x1a, 0x0a, 0x06, 0x48, 0x69, 0x52, 0x65, 0x73, 0x70, 0x12, 0x10, 0x0a,
+	0x03, 0x6d, 0x73, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x22,
+	0x1d, 0x0a, 0x09, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x12, 0x10, 0x0a, 0x03,
+	0x6d, 0x73, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x22, 0x0a,
+	0x0a, 0x08, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x22, 0x0b, 0x0a, 0x09, 0x45, 0x76,
+	0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x32, 0x50, 0x0a, 0x05, 0x47, 0x72, 0x65, 0x65, 0x74,
+	0x12, 0x1e, 0x0a, 0x05, 0x53, 0x61, 0x79, 0x48, 0x69, 0x12, 0x09, 0x2e, 0x68, 0x69, 0x2e, 0x48,
+	0x69, 0x52, 0x65, 0x71, 0x1a, 0x0a, 0x2e, 0x68, 0x69, 0x2e, 0x48, 0x69, 0x52, 0x65, 0x73, 0x70,
+	0x12, 0x27, 0x0a, 0x08, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x0c, 0x2e, 0x68,
+	0x69, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x1a, 0x0d, 0x2e, 0x68, 0x69, 0x2e,
+	0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x73, 0x70, 0x32, 0x33, 0x0a, 0x05, 0x45, 0x76, 0x65,
+	0x6e, 0x74, 0x12, 0x2a, 0x0a, 0x0b, 0x41, 0x73, 0x6b, 0x51, 0x75, 0x65, 0x73, 0x74, 0x69, 0x6f,
+	0x6e, 0x12, 0x0c, 0x2e, 0x68, 0x69, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x1a,
+	0x0d, 0x2e, 0x68, 0x69, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x42, 0x06,
+	0x5a, 0x04, 0x2e, 0x2f, 0x68, 0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+	file_hi_proto_rawDescOnce sync.Once
+	file_hi_proto_rawDescData = file_hi_proto_rawDesc
+)
+
+func file_hi_proto_rawDescGZIP() []byte {
+	file_hi_proto_rawDescOnce.Do(func() {
+		file_hi_proto_rawDescData = protoimpl.X.CompressGZIP(file_hi_proto_rawDescData)
+	})
+	return file_hi_proto_rawDescData
+}
+
+var file_hi_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
+var file_hi_proto_goTypes = []interface{}{
+	(*HiReq)(nil),     // 0: hi.HiReq
+	(*HelloReq)(nil),  // 1: hi.HelloReq
+	(*HiResp)(nil),    // 2: hi.HiResp
+	(*HelloResp)(nil), // 3: hi.HelloResp
+	(*EventReq)(nil),  // 4: hi.EventReq
+	(*EventResp)(nil), // 5: hi.EventResp
+}
+var file_hi_proto_depIdxs = []int32{
+	0, // 0: hi.Greet.SayHi:input_type -> hi.HiReq
+	1, // 1: hi.Greet.SayHello:input_type -> hi.HelloReq
+	4, // 2: hi.Event.AskQuestion:input_type -> hi.EventReq
+	2, // 3: hi.Greet.SayHi:output_type -> hi.HiResp
+	3, // 4: hi.Greet.SayHello:output_type -> hi.HelloResp
+	5, // 5: hi.Event.AskQuestion:output_type -> hi.EventResp
+	3, // [3:6] is the sub-list for method output_type
+	0, // [0:3] is the sub-list for method input_type
+	0, // [0:0] is the sub-list for extension type_name
+	0, // [0:0] is the sub-list for extension extendee
+	0, // [0:0] is the sub-list for field type_name
+}
+
+func init() { file_hi_proto_init() }
+func file_hi_proto_init() {
+	if File_hi_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_hi_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*HiReq); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_hi_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*HelloReq); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_hi_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*HiResp); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_hi_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*HelloResp); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_hi_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*EventReq); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_hi_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*EventResp); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_hi_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   6,
+			NumExtensions: 0,
+			NumServices:   2,
+		},
+		GoTypes:           file_hi_proto_goTypes,
+		DependencyIndexes: file_hi_proto_depIdxs,
+		MessageInfos:      file_hi_proto_msgTypes,
+	}.Build()
+	File_hi_proto = out.File
+	file_hi_proto_rawDesc = nil
+	file_hi_proto_goTypes = nil
+	file_hi_proto_depIdxs = nil
+}

+ 227 - 0
tools/goctl/example/rpc/hi/pb/hi/hi_grpc.pb.go

@@ -0,0 +1,227 @@
+// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
+// versions:
+// - protoc-gen-go-grpc v1.2.0
+// - protoc             v3.19.4
+// source: hi.proto
+
+package hi
+
+import (
+	context "context"
+	grpc "google.golang.org/grpc"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
+)
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the grpc package it is being compiled against.
+// Requires gRPC-Go v1.32.0 or later.
+const _ = grpc.SupportPackageIsVersion7
+
+// GreetClient is the client API for Greet service.
+//
+// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
+type GreetClient interface {
+	SayHi(ctx context.Context, in *HiReq, opts ...grpc.CallOption) (*HiResp, error)
+	SayHello(ctx context.Context, in *HelloReq, opts ...grpc.CallOption) (*HelloResp, error)
+}
+
+type greetClient struct {
+	cc grpc.ClientConnInterface
+}
+
+func NewGreetClient(cc grpc.ClientConnInterface) GreetClient {
+	return &greetClient{cc}
+}
+
+func (c *greetClient) SayHi(ctx context.Context, in *HiReq, opts ...grpc.CallOption) (*HiResp, error) {
+	out := new(HiResp)
+	err := c.cc.Invoke(ctx, "/hi.Greet/SayHi", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *greetClient) SayHello(ctx context.Context, in *HelloReq, opts ...grpc.CallOption) (*HelloResp, error) {
+	out := new(HelloResp)
+	err := c.cc.Invoke(ctx, "/hi.Greet/SayHello", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+// GreetServer is the server API for Greet service.
+// All implementations must embed UnimplementedGreetServer
+// for forward compatibility
+type GreetServer interface {
+	SayHi(context.Context, *HiReq) (*HiResp, error)
+	SayHello(context.Context, *HelloReq) (*HelloResp, error)
+	mustEmbedUnimplementedGreetServer()
+}
+
+// UnimplementedGreetServer must be embedded to have forward compatible implementations.
+type UnimplementedGreetServer struct {
+}
+
+func (UnimplementedGreetServer) SayHi(context.Context, *HiReq) (*HiResp, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method SayHi not implemented")
+}
+func (UnimplementedGreetServer) SayHello(context.Context, *HelloReq) (*HelloResp, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented")
+}
+func (UnimplementedGreetServer) mustEmbedUnimplementedGreetServer() {}
+
+// UnsafeGreetServer may be embedded to opt out of forward compatibility for this service.
+// Use of this interface is not recommended, as added methods to GreetServer will
+// result in compilation errors.
+type UnsafeGreetServer interface {
+	mustEmbedUnimplementedGreetServer()
+}
+
+func RegisterGreetServer(s grpc.ServiceRegistrar, srv GreetServer) {
+	s.RegisterService(&Greet_ServiceDesc, srv)
+}
+
+func _Greet_SayHi_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(HiReq)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(GreetServer).SayHi(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/hi.Greet/SayHi",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(GreetServer).SayHi(ctx, req.(*HiReq))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _Greet_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(HelloReq)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(GreetServer).SayHello(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/hi.Greet/SayHello",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(GreetServer).SayHello(ctx, req.(*HelloReq))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+// Greet_ServiceDesc is the grpc.ServiceDesc for Greet service.
+// It's only intended for direct use with grpc.RegisterService,
+// and not to be introspected or modified (even as a copy)
+var Greet_ServiceDesc = grpc.ServiceDesc{
+	ServiceName: "hi.Greet",
+	HandlerType: (*GreetServer)(nil),
+	Methods: []grpc.MethodDesc{
+		{
+			MethodName: "SayHi",
+			Handler:    _Greet_SayHi_Handler,
+		},
+		{
+			MethodName: "SayHello",
+			Handler:    _Greet_SayHello_Handler,
+		},
+	},
+	Streams:  []grpc.StreamDesc{},
+	Metadata: "hi.proto",
+}
+
+// EventClient is the client API for Event service.
+//
+// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
+type EventClient interface {
+	AskQuestion(ctx context.Context, in *EventReq, opts ...grpc.CallOption) (*EventResp, error)
+}
+
+type eventClient struct {
+	cc grpc.ClientConnInterface
+}
+
+func NewEventClient(cc grpc.ClientConnInterface) EventClient {
+	return &eventClient{cc}
+}
+
+func (c *eventClient) AskQuestion(ctx context.Context, in *EventReq, opts ...grpc.CallOption) (*EventResp, error) {
+	out := new(EventResp)
+	err := c.cc.Invoke(ctx, "/hi.Event/AskQuestion", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+// EventServer is the server API for Event service.
+// All implementations must embed UnimplementedEventServer
+// for forward compatibility
+type EventServer interface {
+	AskQuestion(context.Context, *EventReq) (*EventResp, error)
+	mustEmbedUnimplementedEventServer()
+}
+
+// UnimplementedEventServer must be embedded to have forward compatible implementations.
+type UnimplementedEventServer struct {
+}
+
+func (UnimplementedEventServer) AskQuestion(context.Context, *EventReq) (*EventResp, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method AskQuestion not implemented")
+}
+func (UnimplementedEventServer) mustEmbedUnimplementedEventServer() {}
+
+// UnsafeEventServer may be embedded to opt out of forward compatibility for this service.
+// Use of this interface is not recommended, as added methods to EventServer will
+// result in compilation errors.
+type UnsafeEventServer interface {
+	mustEmbedUnimplementedEventServer()
+}
+
+func RegisterEventServer(s grpc.ServiceRegistrar, srv EventServer) {
+	s.RegisterService(&Event_ServiceDesc, srv)
+}
+
+func _Event_AskQuestion_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(EventReq)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(EventServer).AskQuestion(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/hi.Event/AskQuestion",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(EventServer).AskQuestion(ctx, req.(*EventReq))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+// Event_ServiceDesc is the grpc.ServiceDesc for Event service.
+// It's only intended for direct use with grpc.RegisterService,
+// and not to be introspected or modified (even as a copy)
+var Event_ServiceDesc = grpc.ServiceDesc{
+	ServiceName: "hi.Event",
+	HandlerType: (*EventServer)(nil),
+	Methods: []grpc.MethodDesc{
+		{
+			MethodName: "AskQuestion",
+			Handler:    _Event_AskQuestion_Handler,
+		},
+	},
+	Streams:  []grpc.StreamDesc{},
+	Metadata: "hi.proto",
+}

+ 22 - 0
tools/goctl/example/rpc/multiple_rpc_service_generate.sh

@@ -0,0 +1,22 @@
+#!/bin/bash
+
+wd=$(pwd)
+output="$wd/hi"
+
+rm -rf $output
+
+goctl rpc protoc -I $wd "$wd/hi.proto" --go_out="$output/pb" --go-grpc_out="$output/pb" --zrpc_out="$output" --multiple
+
+if [ $? -ne 0 ]; then
+    echo "Generate failed"
+    exit 1
+fi
+
+GOPROXY="https://goproxy.cn,direct" && go mod tidy
+
+if [ $? -ne 0 ]; then
+    echo "Tidy failed"
+    exit 1
+fi
+
+go test ./...

+ 22 - 0
tools/goctl/example/rpc/single_rpc_service_generate.sh

@@ -0,0 +1,22 @@
+#!/bin/bash
+
+wd=$(pwd)
+output="$wd/hello"
+
+rm -rf $output
+
+goctl rpc protoc -I $wd "$wd/hello.proto" --go_out="$output/pb" --go-grpc_out="$output/pb" --zrpc_out="$output" --multiple
+
+if [ $? -ne 0 ]; then
+    echo "Generate failed"
+    exit 1
+fi
+
+GOPROXY="https://goproxy.cn,direct" && go mod tidy
+
+if [ $? -ne 0 ]; then
+    echo "Tidy failed"
+    exit 1
+fi
+
+go test ./...

+ 3 - 1
tools/goctl/go.mod

@@ -11,8 +11,10 @@ require (
 	github.com/logrusorgru/aurora v2.0.3+incompatible
 	github.com/spf13/cobra v1.4.0
 	github.com/stretchr/testify v1.7.1
-	github.com/withfig/autocomplete-tools/integrations/cobra v0.0.0-20220612192618-e5d7d8e71f63
+	github.com/withfig/autocomplete-tools/integrations/cobra v0.0.0-20220705165518-2761d7f4b8bc
 	github.com/zeromicro/antlr v0.0.1
 	github.com/zeromicro/ddl-parser v1.0.4
 	github.com/zeromicro/go-zero v1.3.4
+	google.golang.org/grpc v1.46.2
+	google.golang.org/protobuf v1.28.0
 )

+ 32 - 2
tools/goctl/go.sum

@@ -85,6 +85,7 @@ github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb
 github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
 github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
 github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
+github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
 github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
@@ -116,7 +117,9 @@ github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWH
 github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
 github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI=
 github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
@@ -194,6 +197,7 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me
 github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
 github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@@ -209,6 +213,7 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
 github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
 github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
 github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
+github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
 github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -250,6 +255,7 @@ github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
 github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
 github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
 github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@@ -273,12 +279,14 @@ github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLe
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
 github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
 github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
 github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU=
+github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw=
 github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA=
 github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
 github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
@@ -342,6 +350,7 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV
 github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
 github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
@@ -403,9 +412,11 @@ github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR
 github.com/mkevac/debugcharts v0.0.0-20191222103121-ae1c48aa8615/go.mod h1:Ad7oeElCZqA1Ufj0U9/liOF4BtVepxRcTvr2ey7zTvM=
 github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
 github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
 github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
@@ -449,6 +460,7 @@ github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi
 github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -535,8 +547,8 @@ github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYa
 github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
 github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
 github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
-github.com/withfig/autocomplete-tools/integrations/cobra v0.0.0-20220612192618-e5d7d8e71f63 h1:fh1HoAQAIFTWjlmOQ/vjHHe6H22n2bnzYHX6R0JQE3c=
-github.com/withfig/autocomplete-tools/integrations/cobra v0.0.0-20220612192618-e5d7d8e71f63/go.mod h1:nmuySobZb4kFgFy6BptpXp/BBw+xFSyvVPP6auoJB4k=
+github.com/withfig/autocomplete-tools/integrations/cobra v0.0.0-20220705165518-2761d7f4b8bc h1:2pGkMttK5jQ8+6YhdyeQIHyVa84HMdJhILozImSWX6c=
+github.com/withfig/autocomplete-tools/integrations/cobra v0.0.0-20220705165518-2761d7f4b8bc/go.mod h1:nmuySobZb4kFgFy6BptpXp/BBw+xFSyvVPP6auoJB4k=
 github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
 github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
 github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
@@ -555,10 +567,13 @@ github.com/zeromicro/ddl-parser v1.0.4/go.mod h1:ISU/8NuPyEpl9pa17Py9TBPetMjtsiH
 github.com/zeromicro/go-zero v1.3.4 h1:XeNdwcrOmnvHj891AmeCA9RrRj1PeN49//KKCK4WAXk=
 github.com/zeromicro/go-zero v1.3.4/go.mod h1:nEU/ITZSmxRxvr/JmSoJ48MNV62UpY6bqJz9Voba7Yw=
 go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
+go.etcd.io/etcd/api/v3 v3.5.4 h1:OHVyt3TopwtUQ2GKdd5wu3PmmipR4FTwCqoEjSyRdIc=
 go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
 go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
+go.etcd.io/etcd/client/pkg/v3 v3.5.4 h1:lrneYvz923dvC14R54XcA7FXoZ3mlGZAgmwhfm7HqOg=
 go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
 go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs=
+go.etcd.io/etcd/client/v3 v3.5.4 h1:p83BUL3tAYS0OT/r0qglgc3M1JjhM0diV8DSWAhVXv4=
 go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY=
 go.mongodb.org/mongo-driver v1.9.1/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
@@ -580,14 +595,18 @@ go.opentelemetry.io/otel/trace v1.7.0 h1:O37Iogk1lEkMRXewVtZ1BBTVn5JEp8GrJvP92bJ
 go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU=
 go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
 go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
 go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
 go.uber.org/automaxprocs v1.5.1 h1:e1YG66Lrk73dn4qhg8WFSvhF0JuFQF0ERIp4rpuV8Qk=
 go.uber.org/automaxprocs v1.5.1/go.mod h1:BF4eumQw0P9GtnuxxovUd06vwm1o18oMzFtK66vU6XU=
 go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
+go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
 go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
 go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
 go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
 go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
+go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
 go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@@ -706,6 +725,7 @@ golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ
 golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg=
 golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -805,6 +825,7 @@ golang.org/x/sys v0.0.0-20220429233432-b5fbb4746d32/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -820,6 +841,7 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20220411224347-583f2d630306 h1:+gHMid33q6pen7kv9xvT+JRinntgeXO2AeZVd0AWD3w=
 golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -920,6 +942,7 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
 google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
 google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@@ -1046,6 +1069,7 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
 gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY=
 gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0=
+gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
 gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
 gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
@@ -1070,12 +1094,16 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+k8s.io/api v0.22.9 h1:PidjRtgd0zDa6SvyooBLH/SP62uOhEBY0kx0UYRGr1o=
 k8s.io/api v0.22.9/go.mod h1:rcjO/FPOuvc3x7nQWx29UcDrFJMx82RxDob71ntNH4A=
+k8s.io/apimachinery v0.22.9 h1:5qjnpBk6eC9me0SAzokCUMI0KVF2PENK1PnykF8/Gjo=
 k8s.io/apimachinery v0.22.9/go.mod h1:ZvVLP5iLhwVFg2Yx9Gh5W0um0DUauExbRhe+2Z8I1EU=
+k8s.io/client-go v0.22.9 h1:5p2R2LsoBfaE6QnXfWFmyyvxrFXtfegUGRMZSpTI+Q8=
 k8s.io/client-go v0.22.9/go.mod h1:IoH7exYnoH/zgvHOuVxh2c4yJepcCBt72FzCTisOc4k=
 k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
 k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
 k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
+k8s.io/klog/v2 v2.40.1 h1:P4RRucWk/lFOlDdkAr3mc7iWFkgKrZY9qZMAgek06S4=
 k8s.io/klog/v2 v2.40.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
 k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw=
 k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
@@ -1085,5 +1113,7 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8
 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
 sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
+sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y=
 sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=
+sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
 sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=

+ 134 - 165
tools/goctl/rpc/README.md

@@ -7,7 +7,7 @@ Goctl Rpc是`goctl`脚手架下的一个rpc服务代码生成模块,支持prot
 * 简单易用
 * 快速提升开发效率
 * 出错率低
-* 贴近protoc
+* 贴近 protoc
 
 
 ## 快速开始
@@ -24,211 +24,180 @@ 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
 ```
+  
+```proto
+syntax = "proto3";
 
-```Bash
-NAME:
-   goctl rpc proto - generate rpc from proto
+package user;
+option go_package="./user";
 
-USAGE:
-   goctl rpc proto [command options] [arguments...]
+message Request {
+  string ping = 1;
+}
 
-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]
+message Response {
+  string pong = 1;
+}
 
+service User {
+  rpc Ping(Request) returns(Response);
+}
 ```
+  
 
-### 参数说明
-
-* --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文件会被强制覆盖,而和开发人员手动需要编写的则不会覆盖生成,这一类在代码头部均有
+* 生成rpc服务代码
 
-```shell script
-    // Code generated by goctl. DO NOT EDIT!
-    // Source: xxx.proto
+```bash
+$ goctl rpc protoc  user.proto --go_out=. --go-grpc_out=. --zrpc_out=.
 ```
 
-的标识,请注意不要将也写业务性代码写在里面。
-
-## proto import
-* 对于rpc中的requestType和returnType必须在main proto文件定义,对于proto中的message可以像protoc一样import其他proto文件。
 
-proto示例:
+## 用法
 
-### 错误import
-```proto
-syntax = "proto3";
+### rpc 服务生成用法
 
-package greet;
+```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
+```
 
-import "base/common.proto"
+### 参数说明
 
-message Request {
-  string ping = 1;
-}
+* --branch 指定远程仓库模板分支
+* --home 指定goctl模板根目录
+* -m, --multiple 指定生成多个rpc服务模式, 默认为 false, 如果为  false, 则只支持生成一个rpc service, 如果为 true, 则支持生成多个 rpc service,且多个 rpc service 会分组。
+* --style 指定文件输出格式
+* -v, --verbose 显示日志
+* --zrpc_out 指定zrpc输出目录
 
-message Response {
-  string pong = 1;
-}
+> ## --multiple
+> 是否开启多个 rpc service 生成,如果开启,则满足一下新特性
+> 1. 支持 1 到多个 rpc service 
+> 2. 生成 rpc 服务会按照服务名称分组(尽管只有一个 rpc service)
+> 3. rpc client 的文件目录变更为固定名称 `client`
+> 
+> 如果不开启,则和旧版本 rpc 生成逻辑一样(兼容)
+> 1. 有且只能有一个 rpc service
 
-service Greet {
-  rpc Ping(base.In) returns(base.Out);// request和return 不支持import
-}
 
-```
+## rpc 服务生成 example
+详情见 [example/rpc](https://github.com/zeromicro/go-zero/tree/master/tools/goctl/example)
 
+## --multiple 为 true 和 false 的目录区别
+源 proto 文件
 
-### 正确import
-```proto
+```protobuf
 syntax = "proto3";
 
-package greet;
+package hello;
 
-import "base/common.proto"
+option go_package = "./hello";
 
-message Request {
-  base.In in = 1;// 支持import
+message HelloReq {
+  string in = 1;
 }
 
-message Response {
- base.Out out = 2;// 支持import
+message HelloResp {
+  string msg = 1;
 }
 
 service Greet {
-  rpc Ping(Request) returns(Response);
+  rpc SayHello(HelloReq) returns (HelloResp);
 }
 ```
 
-## 常见问题解决(go mod工程)
+### --multiple=true
 
-* 错误一:
 ```text
-A required privilege is not held by the client.
+hello
+├── client // 区别1:rpc 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
 ```
-这个问题只有goctl 版本在`goctl.exe version 1.2.1` 以后的 Windows操作系统出现,主要原因是goctl需要以管理员身份运行,这样才能将`goctl.exe` 创建一个 `ptocot-gen-gcotl`
-的符号链接。
 
+### --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
+```

+ 2 - 0
tools/goctl/rpc/cli/cli.go

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

+ 1 - 0
tools/goctl/rpc/cli/zrpc.go

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

+ 44 - 23
tools/goctl/rpc/cmd.go

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

+ 23 - 13
tools/goctl/rpc/generator/gen.go

@@ -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()
 

+ 10 - 34
tools/goctl/rpc/generator/gen_test.go

@@ -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")
-			}())
-		}
 	})
 }

+ 98 - 12
tools/goctl/rpc/generator/gencall.go

@@ -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()
+	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
+	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{}{

+ 78 - 10
tools/goctl/rpc/generator/genlogic.go

@@ -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),

+ 40 - 9
tools/goctl/rpc/generator/genmain.go

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

+ 109 - 16
tools/goctl/rpc/generator/genserver.go

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

+ 1 - 1
tools/goctl/rpc/generator/logic.tpl

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

+ 2 - 3
tools/goctl/rpc/generator/main.tpl

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

+ 85 - 20
tools/goctl/rpc/generator/mkdir.go

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

+ 10 - 12
tools/goctl/rpc/generator/template.go

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

+ 8 - 23
tools/goctl/rpc/parser/parser.go

@@ -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 len(serviceList) > 1 {
-		return ret, errors.New("only one service expected")
+	if err = serviceList.validate(abs, multiple...); err != nil {
+		return ret, err
 	}
-	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
 }

+ 14 - 9
tools/goctl/rpc/parser/parser_test.go

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

+ 1 - 1
tools/goctl/rpc/parser/proto.go

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

+ 50 - 6
tools/goctl/rpc/parser/service.go

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