robocanic commented on code in PR #1429: URL: https://github.com/apache/dubbo-admin/pull/1429#discussion_r2901847914
########## pkg/console/service/service_generic_invoke.go: ########## @@ -0,0 +1,292 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package service + +import ( + "context" + "fmt" + "sort" + "time" + + hessian "github.com/apache/dubbo-go-hessian2" + + dubbo "dubbo.apache.org/dubbo-go/v3" + "dubbo.apache.org/dubbo-go/v3/client" + dubboconstant "dubbo.apache.org/dubbo-go/v3/common/constant" + _ "dubbo.apache.org/dubbo-go/v3/imports" + + "github.com/apache/dubbo-admin/pkg/common/bizerror" + consolectx "github.com/apache/dubbo-admin/pkg/console/context" + "github.com/apache/dubbo-admin/pkg/console/model" + "github.com/apache/dubbo-admin/pkg/core/logger" + "github.com/apache/dubbo-admin/pkg/core/manager" + meshresource "github.com/apache/dubbo-admin/pkg/core/resource/apis/mesh/v1alpha1" + "github.com/apache/dubbo-admin/pkg/core/store/index" +) + +const genericInvokeInstanceName = "dubbo-admin-generic-invoke" + +type genericInvocation struct { + URL string + ServiceName string + Group string + Version string + MethodName string + ParameterTypes []string + Args []hessian.Object +} + +type tripleInvokeTarget struct { + instance *meshresource.RPCInstanceResource + port int64 +} + +var invokeGenericServiceRPC = func(callCtx context.Context, invocation genericInvocation) (any, error) { + ins, err := dubbo.NewInstance(dubbo.WithName(genericInvokeInstanceName)) + if err != nil { + return nil, err + } + + cli, err := ins.NewClient( Review Comment: Question:这里是不是应该根据目标service的协议和序列化来new client? ########## pkg/console/model/service.go: ########## @@ -120,3 +122,121 @@ func (s *BaseServiceReq) Query(c *gin.Context) error { func (s *BaseServiceReq) ServiceKey() string { return s.ServiceName + constants.ColonSeparator + s.Version + constants.ColonSeparator + s.Group } + +type ServiceMethodsReq struct { + ServiceName string `form:"serviceName" json:"serviceName"` + Group string `form:"group" json:"group"` + Version string `form:"version" json:"version"` + Mesh string `form:"mesh" json:"mesh"` + ProviderAppName string `form:"providerAppName" json:"providerAppName"` +} + +func (s *ServiceMethodsReq) Query(c *gin.Context) error { + s.ServiceName = strings.TrimSpace(c.Query("serviceName")) + if s.ServiceName == "" { + return fmt.Errorf("service name is empty") + } + s.Mesh = strings.TrimSpace(c.Query("mesh")) + if s.Mesh == "" { + return fmt.Errorf("mesh is empty") + } + s.Group = strings.TrimSpace(c.Query("group")) + s.Version = strings.TrimSpace(c.Query("version")) + s.ProviderAppName = strings.TrimSpace(c.Query("providerAppName")) + return nil +} + +type ServiceMethodDetailReq struct { + ServiceMethodsReq + + MethodName string `form:"methodName" json:"methodName"` + Signature string `form:"signature" json:"signature"` +} + +func (s *ServiceMethodDetailReq) Query(c *gin.Context) error { + if err := s.ServiceMethodsReq.Query(c); err != nil { + return err + } + s.MethodName = strings.TrimSpace(c.Query("methodName")) + if s.MethodName == "" { + return fmt.Errorf("method name is empty") + } + s.Signature = strings.TrimSpace(c.Query("signature")) + return nil +} + +type ServiceMethodSummaryResp struct { + MethodName string `json:"methodName"` + ParameterTypes []string `json:"parameterTypes"` + Signature string `json:"signature,omitempty"` +} + +type ServiceMethodParameter struct { + Name string `json:"name"` + Type string `json:"type"` +} + +type ServiceMethodDetailResp struct { + MethodName string `json:"methodName"` + Signature string `json:"signature,omitempty"` + ParameterTypes []string `json:"parameterTypes"` + Parameters []ServiceMethodParameter `json:"parameters"` + ReturnType string `json:"returnType"` + Types []ServiceMethodTypeResp `json:"types"` +} + +type ServiceMethodTypeResp struct { + Type string `json:"type"` + Properties map[string]string `json:"properties"` + Items []string `json:"items"` + Enums []string `json:"enums"` +} + +const DefaultServiceGenericInvokeTimeoutMs int64 = 3000 + +type ServiceGenericInvokeReq struct { + Mesh string `json:"mesh"` + ServiceName string `json:"serviceName"` + MethodName string `json:"methodName"` + Signature string `json:"signature"` + Args []json.RawMessage `json:"args"` + Group string `json:"group"` + Version string `json:"version"` + ProviderAppName string `json:"providerAppName"` + TimeoutMs int64 `json:"timeoutMs"` + Attachments map[string]string `json:"attachments"` +} + +func (s *ServiceGenericInvokeReq) Validate() error { + s.Mesh = strings.TrimSpace(s.Mesh) Review Comment: suggestrion: 这里都可以简化用lancet的工具类strutil.isBlank来判断 ########## pkg/console/service/service_generic_invoke.go: ########## @@ -0,0 +1,292 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package service + +import ( + "context" + "fmt" + "sort" + "time" + + hessian "github.com/apache/dubbo-go-hessian2" + + dubbo "dubbo.apache.org/dubbo-go/v3" + "dubbo.apache.org/dubbo-go/v3/client" + dubboconstant "dubbo.apache.org/dubbo-go/v3/common/constant" + _ "dubbo.apache.org/dubbo-go/v3/imports" + + "github.com/apache/dubbo-admin/pkg/common/bizerror" + consolectx "github.com/apache/dubbo-admin/pkg/console/context" + "github.com/apache/dubbo-admin/pkg/console/model" + "github.com/apache/dubbo-admin/pkg/core/logger" + "github.com/apache/dubbo-admin/pkg/core/manager" + meshresource "github.com/apache/dubbo-admin/pkg/core/resource/apis/mesh/v1alpha1" + "github.com/apache/dubbo-admin/pkg/core/store/index" +) + +const genericInvokeInstanceName = "dubbo-admin-generic-invoke" + +type genericInvocation struct { + URL string + ServiceName string + Group string + Version string + MethodName string + ParameterTypes []string + Args []hessian.Object +} + +type tripleInvokeTarget struct { + instance *meshresource.RPCInstanceResource + port int64 +} + +var invokeGenericServiceRPC = func(callCtx context.Context, invocation genericInvocation) (any, error) { + ins, err := dubbo.NewInstance(dubbo.WithName(genericInvokeInstanceName)) + if err != nil { + return nil, err + } + + cli, err := ins.NewClient( + client.WithClientProtocolTriple(), + client.WithClientSerialization(dubboconstant.Hessian2Serialization), + ) + if err != nil { + return nil, err + } + + svc, err := cli.NewGenericService( + invocation.ServiceName, + client.WithURL(invocation.URL), + client.WithVersion(invocation.Version), + client.WithGroup(invocation.Group), + ) + if err != nil { + return nil, err + } + + return svc.Invoke(callCtx, invocation.MethodName, invocation.ParameterTypes, invocation.Args) +} Review Comment: copilot这里说的有道理,这里可以在代码里记一个todo,加上client的缓存 ########## pkg/console/service/service.go: ########## @@ -160,6 +162,421 @@ func ToServiceSearchRespByConsumer(res *meshresource.ServiceConsumerMetadataReso } } +type serviceMethodCandidate struct { + detail *model.ServiceMethodDetailResp + signature string + method *meshproto.Method +} + +func GetServiceMethodNames(ctx consolectx.Context, req model.ServiceMethodsReq) ([]model.ServiceMethodSummaryResp, error) { + metadataList, err := listServiceProviderMetadata(ctx, req) + if err != nil { + return nil, err + } + + return buildServiceMethodSummaries(metadataList), nil +} + +func GetServiceMethodDetail(ctx consolectx.Context, req model.ServiceMethodDetailReq) (*model.ServiceMethodDetailResp, error) { + metadataList, err := listServiceProviderMetadata(ctx, req.ServiceMethodsReq) + if err != nil { + return nil, err + } + + candidate, err := resolveStructuredServiceMethodCandidate(buildServiceMethodCandidates(metadataList), req) + if err != nil { + return nil, err + } + + detail := cloneServiceMethodDetailResp(candidate.detail) + detail.Types = buildServiceMethodRelatedTypes(metadataList, candidate.method) + return detail, nil +} + +func listServiceProviderMetadata(ctx consolectx.Context, req model.ServiceMethodsReq) ([]*meshresource.ServiceProviderMetadataResource, error) { + indexes := map[string]string{ + index.ByMeshIndex: req.Mesh, + index.ByServiceProviderServiceName: req.ServiceName, + } + if req.ProviderAppName != "" { + indexes[index.ByServiceProviderAppName] = req.ProviderAppName + } + + metadataList, err := manager.ListByIndexes[*meshresource.ServiceProviderMetadataResource]( + ctx.ResourceManager(), + meshresource.ServiceProviderMetadataKind, + indexes, + ) + if err != nil { + return nil, err + } + + filtered := make([]*meshresource.ServiceProviderMetadataResource, 0, len(metadataList)) + for _, metadata := range metadataList { + if !matchesServiceMethodsReq(metadata, req) { + continue + } + filtered = append(filtered, metadata) + } + + return filtered, nil +} + +func matchesServiceMethodsReq(metadata *meshresource.ServiceProviderMetadataResource, req model.ServiceMethodsReq) bool { Review Comment: Suggestion:这里的过滤其实可以在查询DB的时候做掉。可以在ServiceProviderMetadata的index(pkg/core/store/index/service_provider_metadata.go)里面新增对于serviceKey(serviceName:version:group)的一个索引,这样就不用在上层过滤了。 ########## pkg/console/service/service.go: ########## @@ -160,6 +162,421 @@ func ToServiceSearchRespByConsumer(res *meshresource.ServiceConsumerMetadataReso } } +type serviceMethodCandidate struct { + detail *model.ServiceMethodDetailResp + signature string + method *meshproto.Method +} + +func GetServiceMethodNames(ctx consolectx.Context, req model.ServiceMethodsReq) ([]model.ServiceMethodSummaryResp, error) { + metadataList, err := listServiceProviderMetadata(ctx, req) + if err != nil { + return nil, err + } + + return buildServiceMethodSummaries(metadataList), nil +} + +func GetServiceMethodDetail(ctx consolectx.Context, req model.ServiceMethodDetailReq) (*model.ServiceMethodDetailResp, error) { + metadataList, err := listServiceProviderMetadata(ctx, req.ServiceMethodsReq) + if err != nil { + return nil, err + } + + candidate, err := resolveStructuredServiceMethodCandidate(buildServiceMethodCandidates(metadataList), req) + if err != nil { + return nil, err + } + + detail := cloneServiceMethodDetailResp(candidate.detail) + detail.Types = buildServiceMethodRelatedTypes(metadataList, candidate.method) + return detail, nil +} + +func listServiceProviderMetadata(ctx consolectx.Context, req model.ServiceMethodsReq) ([]*meshresource.ServiceProviderMetadataResource, error) { + indexes := map[string]string{ + index.ByMeshIndex: req.Mesh, + index.ByServiceProviderServiceName: req.ServiceName, + } + if req.ProviderAppName != "" { + indexes[index.ByServiceProviderAppName] = req.ProviderAppName + } + + metadataList, err := manager.ListByIndexes[*meshresource.ServiceProviderMetadataResource]( + ctx.ResourceManager(), + meshresource.ServiceProviderMetadataKind, + indexes, + ) + if err != nil { + return nil, err + } + + filtered := make([]*meshresource.ServiceProviderMetadataResource, 0, len(metadataList)) + for _, metadata := range metadataList { + if !matchesServiceMethodsReq(metadata, req) { + continue + } + filtered = append(filtered, metadata) + } + + return filtered, nil +} + +func matchesServiceMethodsReq(metadata *meshresource.ServiceProviderMetadataResource, req model.ServiceMethodsReq) bool { + if metadata == nil || metadata.Spec == nil { + return false + } + if req.Group != "" && metadata.Spec.Group != req.Group { + return false + } + if req.Version != "" && metadata.Spec.Version != req.Version { + return false + } + if req.ProviderAppName != "" && metadata.Spec.ProviderAppName != req.ProviderAppName { + return false + } + return true +} + +func buildServiceMethodSummaries(metadataList []*meshresource.ServiceProviderMetadataResource) []model.ServiceMethodSummaryResp { + candidates := buildServiceMethodCandidates(metadataList) + summaries := make([]model.ServiceMethodSummaryResp, 0, len(candidates)) + for _, candidate := range candidates { + summaries = append(summaries, model.ServiceMethodSummaryResp{ + MethodName: candidate.detail.MethodName, + ParameterTypes: append([]string{}, candidate.detail.ParameterTypes...), + Signature: candidate.signature, + }) + } + return summaries +} + +func buildServiceMethodCandidates(metadataList []*meshresource.ServiceProviderMetadataResource) []*serviceMethodCandidate { + candidateByKey := make(map[string]*serviceMethodCandidate) + structuredMethodNames := make(map[string]struct{}) + fallbackMethodNames := make(map[string]struct{}) + + for _, metadata := range metadataList { + for _, method := range metadata.Spec.Methods { + candidate, ok := newStructuredServiceMethodCandidate(method) + if !ok { + continue + } + candidateByKey[serviceMethodKey(candidate.detail.MethodName, candidate.signature)] = candidate + structuredMethodNames[candidate.detail.MethodName] = struct{}{} + } + + if len(metadata.Spec.Methods) > 0 { + continue + } + for _, methodName := range methodNamesFromMetadataParameters(metadata.Spec.Parameters) { + fallbackMethodNames[methodName] = struct{}{} + } + } + + for methodName := range fallbackMethodNames { Review Comment: Question:这段逻辑有点没看懂,可以写写注释 -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
