From 6b04728516738c4d7d9902203c935c5ef3221631 Mon Sep 17 00:00:00 2001 From: wubin1989 <328454505@qq.com> Date: Thu, 19 Sep 2024 00:02:51 +0800 Subject: [PATCH] add annotatedOnly flag --- cmd/grpc.go | 4 +- cmd/internal/svc/codegen/grpcproto.go | 6 +- cmd/internal/svc/svc_test.go | 11 +- .../svc/testdata/outputanonystruct/dto/dto.go | 107 ++++++++++++++++++ .../svc/testdata/outputanonystruct/svc.go | 11 +- .../transport/grpc/usersvc.proto | 75 ++++++++++++ framework/testdata/vo/vo.go | 8 +- toolkit/protobuf/v3/message.go | 13 ++- toolkit/protobuf/v3/service.go | 19 +++- 9 files changed, 233 insertions(+), 21 deletions(-) create mode 100644 cmd/internal/svc/testdata/outputanonystruct/dto/dto.go create mode 100644 cmd/internal/svc/testdata/outputanonystruct/transport/grpc/usersvc.proto diff --git a/cmd/grpc.go b/cmd/grpc.go index 6414a5a8..95c07efd 100644 --- a/cmd/grpc.go +++ b/cmd/grpc.go @@ -10,6 +10,7 @@ import ( var naming string var http2grpc bool +var annotatedOnly bool var grpcCmd = &cobra.Command{ Use: "grpc", @@ -22,7 +23,7 @@ var grpcCmd = &cobra.Command{ fn = strcase.ToSnake } s := svc.NewSvc("", - svc.WithProtoGenerator(v3.NewProtoGenerator(v3.WithFieldNamingFunc(fn))), + svc.WithProtoGenerator(v3.NewProtoGenerator(v3.WithFieldNamingFunc(fn), v3.WithAnnotatedOnly(annotatedOnly))), svc.WithHttp2Grpc(http2grpc), svc.WithAllowGetWithReqBody(allowGetWithReqBody), svc.WithCaseConverter(fn), @@ -38,4 +39,5 @@ func init() { grpcCmd.Flags().StringVar(&naming, "case", "lowerCamel", `protobuf message field naming strategy, only support "lowerCamel" and "snake"`) grpcCmd.Flags().BoolVar(&http2grpc, "http2grpc", false, `whether need RESTful api for your grpc service`) grpcCmd.Flags().BoolVar(&allowGetWithReqBody, "allowGetWithReqBody", false, "Whether allow get http request with request body.") + grpcCmd.Flags().BoolVar(&annotatedOnly, "annotatedOnly", false, "Whether generate grpc api only for method annotated with @grpc or not") } diff --git a/cmd/internal/svc/codegen/grpcproto.go b/cmd/internal/svc/codegen/grpcproto.go index e55b21b3..b712128a 100644 --- a/cmd/internal/svc/codegen/grpcproto.go +++ b/cmd/internal/svc/codegen/grpcproto.go @@ -106,7 +106,11 @@ func GenGrpcProto(dir string, ic astutils.InterfaceCollector, p protov3.ProtoGen service = p.NewService(svcname, servicePkg+"/transport/grpc") service.Comments = ic.Interfaces[0].Comments for _, method := range ic.Interfaces[0].Methods { - service.Rpcs = append(service.Rpcs, p.NewRpc(method)) + rpc := p.NewRpc(method) + if rpc == nil { + continue + } + service.Rpcs = append(service.Rpcs, *rpc) } for k := range protov3.ImportStore { service.Imports = append(service.Imports, k) diff --git a/cmd/internal/svc/svc_test.go b/cmd/internal/svc/svc_test.go index 6db8503c..6c0d2d5d 100644 --- a/cmd/internal/svc/svc_test.go +++ b/cmd/internal/svc/svc_test.go @@ -2,6 +2,10 @@ package svc_test import ( "fmt" + "github.com/iancoleman/strcase" + "github.com/unionj-cloud/go-doudou/v2/cmd/internal/svc/codegen" + "github.com/unionj-cloud/go-doudou/v2/cmd/internal/svc/parser" + v3 "github.com/unionj-cloud/go-doudou/v2/toolkit/protobuf/v3" "os" "os/exec" "path/filepath" @@ -297,11 +301,12 @@ func Test_checkIc_output_anonystruct(t *testing.T) { }, }, } + dir := filepath.Join(testDir, "outputanonystruct") + pg := v3.NewProtoGenerator(v3.WithFieldNamingFunc(strcase.ToSnake)) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - assert.Panics(t, func() { - validate.RestApi(testDir, ic) - }) + parser.ParseDtoGrpc(dir, pg, "dto") + codegen.GenGrpcProto(dir, ic, pg) }) } } diff --git a/cmd/internal/svc/testdata/outputanonystruct/dto/dto.go b/cmd/internal/svc/testdata/outputanonystruct/dto/dto.go new file mode 100644 index 00000000..d38045e5 --- /dev/null +++ b/cmd/internal/svc/testdata/outputanonystruct/dto/dto.go @@ -0,0 +1,107 @@ +package dto + +import "encoding/json" + +//go:generate go-doudou name --file $GOFILE -o + +// 筛选条件 +type PageFilter struct { + // 真实姓名,前缀匹配 + Name string + // 所属部门ID + Dept int +} + +// 排序条件 +type Order struct { + Col string + Sort string +} + +type Page struct { + // 排序规则 + Orders []Order + // 页码 + PageNo int + // 每页行数 + Size int + User UserVo +} + +// 分页筛选条件 +type PageQuery struct { + Filter PageFilter + Page Page + Options []struct { + Label string `json:"label" form:"label"` + Value string `json:"value" form:"value"` + } `json:"options" form:"options"` +} + +type PageRet struct { + Items interface{} + PageNo int + PageSize int + Total int + HasNext bool +} + +type UserVo struct { + Id int + Name string + Phone string + Dept string +} + +type KeyboardLayout int + +const ( + UNKNOWN KeyboardLayout = iota + QWERTZ + AZERTY + QWERTY +) + +func (k *KeyboardLayout) StringSetter(value string) { + switch value { + case "UNKNOWN": + *k = UNKNOWN + case "QWERTY": + *k = QWERTY + case "QWERTZ": + *k = QWERTZ + case "AZERTY": + *k = AZERTY + default: + *k = UNKNOWN + } +} + +func (k *KeyboardLayout) StringGetter() string { + switch *k { + case UNKNOWN: + return "UNKNOWN" + case QWERTY: + return "QWERTY" + case QWERTZ: + return "QWERTZ" + case AZERTY: + return "AZERTY" + default: + return "UNKNOWN" + } +} + +func (k *KeyboardLayout) UnmarshalJSON(bytes []byte) error { + var _k string + err := json.Unmarshal(bytes, &_k) + if err != nil { + return err + } + k.StringSetter(_k) + return nil +} + +func (k KeyboardLayout) MarshalJSON() ([]byte, error) { + return json.Marshal(k.StringGetter()) +} diff --git a/cmd/internal/svc/testdata/outputanonystruct/svc.go b/cmd/internal/svc/testdata/outputanonystruct/svc.go index 2c894c54..8883c4fc 100644 --- a/cmd/internal/svc/testdata/outputanonystruct/svc.go +++ b/cmd/internal/svc/testdata/outputanonystruct/svc.go @@ -2,19 +2,12 @@ package service import ( "context" - - "github.com/unionj-cloud/go-doudou/v2/framework/testdata/vo" + "github.com/unionj-cloud/go-doudou/v2/cmd/internal/svc/testdata/outputanonystruct/dto" ) // 用户服务接口 // v1版本 type Usersvc interface { // You can define your service methods as your need. Below is an example. - PageUsers(ctx context.Context, query vo.PageQuery) (code int, data struct { - Items interface{} - PageNo int - PageSize int - Total int - HasNext bool - }, msg error) + PageUsers(ctx context.Context, query dto.PageQuery) (page dto.Page, msg error) } diff --git a/cmd/internal/svc/testdata/outputanonystruct/transport/grpc/usersvc.proto b/cmd/internal/svc/testdata/outputanonystruct/transport/grpc/usersvc.proto new file mode 100644 index 00000000..3c3d11f2 --- /dev/null +++ b/cmd/internal/svc/testdata/outputanonystruct/transport/grpc/usersvc.proto @@ -0,0 +1,75 @@ +/** +* Generated by go-doudou v2.4.2. +* Don't edit! +* +* Version No.: v20240918 +*/ +syntax = "proto3"; + +package usersvc; +option go_package = "github.com/unionj-cloud/go-doudou/v2/cmd/internal/svc/testdata/outputanonystruct/transport/grpc"; + +import "google/protobuf/any.proto"; + +enum KeyboardLayout { + UNKNOWN = 0; + QWERTZ = 1; + AZERTY = 2; + QWERTY = 3; +} + +message Anonystructkfvagz8uXPWVpn5z9xyjWS { + optional string label = 1 [json_name="label"]; + optional string value = 2 [json_name="value"]; +} + +// 排序条件 +message Order { + optional string col = 1 [json_name="col"]; + optional string sort = 2 [json_name="sort"]; +} + +message Page { + // 排序规则 + repeated Order orders = 1 [json_name="orders"]; + // 页码 + optional int32 page_no = 2 [json_name="page_no"]; + // 每页行数 + optional int32 size = 3 [json_name="size"]; + optional UserVo user = 4 [json_name="user"]; +} + +// 筛选条件 +message PageFilter { + // 真实姓名,前缀匹配 + optional string name = 1 [json_name="name"]; + // 所属部门ID + optional int32 dept = 2 [json_name="dept"]; +} + +// 分页筛选条件 +message PageQuery { + optional PageFilter filter = 1 [json_name="filter"]; + optional Page page = 2 [json_name="page"]; + repeated Anonystructkfvagz8uXPWVpn5z9xyjWS options = 3 [json_name="options"]; +} + +message PageRet { + optional google.protobuf.Any items = 1 [json_name="items"]; + optional int32 page_no = 2 [json_name="page_no"]; + optional int32 page_size = 3 [json_name="page_size"]; + optional int32 total = 4 [json_name="total"]; + optional bool has_next = 5 [json_name="has_next"]; +} + +message UserVo { + optional int32 id = 1 [json_name="id"]; + optional string name = 2 [json_name="name"]; + optional string phone = 3 [json_name="phone"]; + optional string dept = 4 [json_name="dept"]; +} + +service UsersvcService { + // You can define your service methods as your need. Below is an example. + rpc PageUsersRpc(PageQuery) returns (Page); +} diff --git a/framework/testdata/vo/vo.go b/framework/testdata/vo/vo.go index ad40b8a9..26cdcec7 100644 --- a/framework/testdata/vo/vo.go +++ b/framework/testdata/vo/vo.go @@ -28,8 +28,12 @@ type Page struct { // 分页筛选条件 type PageQuery struct { - Filter PageFilter - Page Page + Filter PageFilter + Page Page + Options []struct { + Label string `json:"label" form:"label"` + Value string `json:"value" form:"value"` + } `json:"options" form:"options"` } type PageRet struct { diff --git a/toolkit/protobuf/v3/message.go b/toolkit/protobuf/v3/message.go index 8f663634..b5e5f910 100644 --- a/toolkit/protobuf/v3/message.go +++ b/toolkit/protobuf/v3/message.go @@ -4,6 +4,8 @@ import ( "encoding/json" "fmt" "github.com/goccy/go-reflect" + "github.com/lithammer/shortuuid/v4" + log "github.com/sirupsen/logrus" "regexp" "strings" "unicode" @@ -242,11 +244,13 @@ func (receiver ProtoGenerator) handleDefaultCase(ft string) ProtobufType { key := ft[4:strings.Index(ft, "]")] keyMessage := receiver.MessageOf(key) if reflect.DeepEqual(keyMessage, Float) || reflect.DeepEqual(keyMessage, Double) || reflect.DeepEqual(keyMessage, Bytes) { - panic("floating point types and bytes cannot be key_type of maps, please refer to https://developers.google.com/protocol-buffers/docs/proto3#maps") + log.Error("floating point types and bytes cannot be key_type of maps, please refer to https://developers.google.com/protocol-buffers/docs/proto3#maps") + return Any } elemMessage := receiver.MessageOf(elem) if strings.HasPrefix(elemMessage.GetName(), "map<") { - panic("the value_type cannot be another map, please refer to https://developers.google.com/protocol-buffers/docs/proto3#maps") + log.Error("the value_type cannot be another map, please refer to https://developers.google.com/protocol-buffers/docs/proto3#maps") + return Any } return Message{ Name: fmt.Sprintf("map<%s, %s>", keyMessage.GetName(), elemMessage.GetName()), @@ -257,7 +261,8 @@ func (receiver ProtoGenerator) handleDefaultCase(ft string) ProtobufType { elem := ft[strings.Index(ft, "]")+1:] elemMessage := receiver.MessageOf(elem) if strings.HasPrefix(elemMessage.GetName(), "map<") { - panic("map fields cannot be repeated, please refer to https://developers.google.com/protocol-buffers/docs/proto3#maps") + log.Error("map fields cannot be repeated, please refer to https://developers.google.com/protocol-buffers/docs/proto3#maps") + return Any } messageName := elemMessage.GetName() if strings.Contains(elemMessage.GetName(), "repeated ") { @@ -289,6 +294,8 @@ func (receiver ProtoGenerator) handleDefaultCase(ft string) ProtobufType { message := receiver.NewMessage(structmeta) message.IsInner = true message.IsTopLevel = false + message.Name = "Anonystruct" + shortuuid.NewWithNamespace(result[1]) + MessageStore[message.Name] = message return message } var title string diff --git a/toolkit/protobuf/v3/service.go b/toolkit/protobuf/v3/service.go index f8db9c98..3531229a 100644 --- a/toolkit/protobuf/v3/service.go +++ b/toolkit/protobuf/v3/service.go @@ -3,6 +3,7 @@ package v3 import ( "fmt" "github.com/goccy/go-reflect" + "github.com/samber/lo" "strings" "time" @@ -15,6 +16,7 @@ import ( type ProtoGenerator struct { fieldNamingFunc func(string) string Structs []astutils.StructMeta + annotatedOnly bool } type ProtoGeneratorOption func(*ProtoGenerator) @@ -25,6 +27,12 @@ func WithFieldNamingFunc(fn func(string) string) ProtoGeneratorOption { } } +func WithAnnotatedOnly(annotatedOnly bool) ProtoGeneratorOption { + return func(p *ProtoGenerator) { + p.annotatedOnly = annotatedOnly + } +} + func NewProtoGenerator(options ...ProtoGeneratorOption) ProtoGenerator { var p ProtoGenerator p.Structs = make([]astutils.StructMeta, 0) @@ -78,7 +86,14 @@ type Rpc struct { StreamType StreamType } -func (receiver ProtoGenerator) NewRpc(method astutils.MethodMeta) Rpc { +func (receiver ProtoGenerator) NewRpc(method astutils.MethodMeta) *Rpc { + if receiver.annotatedOnly { + if lo.CountBy(method.Comments, func(item string) bool { + return strings.Contains(item, "@grpc") + }) == 0 { + return nil + } + } rpcName := strcase.ToCamel(method.Name) + "Rpc" rpcRequest := receiver.newRequest(rpcName, method.Params) if reflect.DeepEqual(rpcRequest, Empty) { @@ -106,7 +121,7 @@ func (receiver ProtoGenerator) NewRpc(method astutils.MethodMeta) Rpc { } else if strings.HasPrefix(rpcResponse.Name, "stream ") { st = serverStream } - return Rpc{ + return &Rpc{ Name: rpcName, Request: rpcRequest, Response: rpcResponse,