通过gRPC调用Google Routes API遇命名冲突,求服务器端实现帮助
解决Protobuf命名冲突并实现gRPC服务器端代码
一、先解决命名冲突问题
你的panic是因为自定义proto使用了和官方Google Routes API完全相同的包名google.maps.routing.v2,导致和已导入的官方客户端包cloud.google.com/go/maps/routing/apiv2/routingpb的Protobuf类型冲突。解决方法很简单:修改自定义proto的包名为非官方命名空间,比如my.maps.routing.v2。
修改后的完整proto文件
syntax = "proto3"; package my.maps.routing.v2; // 改成自定义包名,避免冲突 import "google/api/annotations.proto"; // 自定义Routes服务,和官方服务方法保持一致但包名独立 service Routes { rpc ComputeRouteMatrix(ComputeRouteMatrixRequest) returns (stream RouteMatrixElement) { option (google.api.http) = { post: "/v1/computeRouteMatrix" // 自定义服务的HTTP路径,避免和官方混淆 body: "*" }; } } /* Messages */ message ComputeRouteMatrixRequest{ repeated RouteMatrixOrigin origins=1; repeated RouteMatrixDestinations destinations=2; } message RouteMatrixOrigin{ Waypoint waypoint=1; } message Waypoint{ Location location=1; } message Location{ Latlng latLng=1; } message Latlng{ float latitude=1; float longitude=2; } message RouteMatrixDestinations{ Waypoint waypoint=1; } message RouteMatrixElement{ int32 distanceMeters=1; string duration=2; int32 originIndex=3; int32 destinationIndex=4; } message Status{ int32 code =1; string message=2; }
重新生成Go代码
执行protoc命令生成gRPC和Protobuf的Go代码:
protoc --go_out=. --go_opt=paths=source_relative \ --go-grpc_out=. --go-grpc_opt=paths=source_relative \ routing.proto
二、实现gRPC服务器端代码
服务器端的核心逻辑是:接收客户端的gRPC请求,转换为符合Google Routes API要求的请求格式,调用官方API获取结果,再转换为自定义的响应格式返回给客户端。
完整服务器端代码
package main import ( "context" "encoding/json" "fmt" "log" "net" "net/http" "strings" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" pb "your-module-path/my/maps/routing/v2" // 替换成你的实际模块路径 ) // 定义Google Routes API的请求/响应结构(和官方保持一致) type GoogleComputeRouteMatrixRequest struct { Origins []GoogleRouteMatrixOrigin `json:"origins"` Destinations []GoogleRouteMatrixDest `json:"destinations"` } type GoogleRouteMatrixOrigin struct { Waypoint GoogleWaypoint `json:"waypoint"` } type GoogleRouteMatrixDest struct { Waypoint GoogleWaypoint `json:"waypoint"` } type GoogleWaypoint struct { Location GoogleLocation `json:"location"` } type GoogleLocation struct { LatLng GoogleLatLng `json:"latLng"` } type GoogleLatLng struct { Latitude float32 `json:"latitude"` Longitude float32 `json:"longitude"` } type GoogleRouteMatrixElement struct { DistanceMeters int32 `json:"distanceMeters"` Duration string `json:"duration"` OriginIndex int32 `json:"originIndex"` DestinationIndex int32 `json:"destinationIndex"` } type GoogleRouteMatrixResponse struct { Elements []GoogleRouteMatrixElement `json:"elements"` } // routesServer 实现自定义的Routes服务 type routesServer struct { pb.UnimplementedRoutesServer googleAPIKey string // 你的Google Cloud API密钥 } // ComputeRouteMatrix 实现服务方法 func (s *routesServer) ComputeRouteMatrix(req *pb.ComputeRouteMatrixRequest, stream pb.Routes_ComputeRouteMatrixServer) error { // 1. 转换自定义请求为Google API要求的格式 googleReq := &GoogleComputeRouteMatrixRequest{} for _, orig := range req.Origins { googleOrig := GoogleRouteMatrixOrigin{ Waypoint: GoogleWaypoint{ Location: GoogleLocation{ LatLng: GoogleLatLng{ Latitude: orig.Waypoint.Location.LatLng.Latitude, Longitude: orig.Waypoint.Location.LatLng.Longitude, }, }, }, } googleReq.Origins = append(googleReq.Origins, googleOrig) } for _, dest := range req.Destinations { googleDest := GoogleRouteMatrixDest{ Waypoint: GoogleWaypoint{ Location: GoogleLocation{ LatLng: GoogleLatLng{ Latitude: dest.Waypoint.Location.LatLng.Latitude, Longitude: dest.Waypoint.Location.LatLng.Longitude, }, }, }, } googleReq.Destinations = append(googleReq.Destinations, googleDest) } // 2. 序列化请求为JSON reqBody, err := json.Marshal(googleReq) if err != nil { return status.Error(codes.Internal, fmt.Sprintf("failed to marshal request: %v", err)) } // 3. 调用Google Routes API apiURL := fmt.Sprintf("https://routes.googleapis.com/distanceMatrix/v2:computeRouteMatrix?key=%s", s.googleAPIKey) httpReq, err := http.NewRequestWithContext(stream.Context(), http.MethodPost, apiURL, strings.NewReader(string(reqBody))) if err != nil { return status.Error(codes.Internal, fmt.Sprintf("failed to create HTTP request: %v", err)) } httpReq.Header.Set("Content-Type", "application/json") client := &http.Client{} httpResp, err := client.Do(httpReq) if err != nil { return status.Error(codes.Unavailable, fmt.Sprintf("failed to call Google API: %v", err)) } defer httpResp.Body.Close() if httpResp.StatusCode != http.StatusOK { return status.Error(codes.Internal, fmt.Sprintf("Google API returned non-OK status: %s", httpResp.Status)) } // 4. 解析Google API响应 var googleResp GoogleRouteMatrixResponse if err := json.NewDecoder(httpResp.Body).Decode(&googleResp); err != nil { return status.Error(codes.Internal, fmt.Sprintf("failed to decode Google API response: %v", err)) } // 5. 转换响应格式并流式返回给客户端 for _, elem := range googleResp.Elements { customElem := &pb.RouteMatrixElement{ DistanceMeters: elem.DistanceMeters, Duration: elem.Duration, OriginIndex: elem.OriginIndex, DestinationIndex: elem.DestinationIndex, } if err := stream.Send(customElem); err != nil { return status.Error(codes.Internal, fmt.Sprintf("failed to send response element: %v", err)) } } return nil } func main() { // 替换成你的Google Cloud API密钥(从Google Cloud控制台获取) apiKey := "YOUR_GOOGLE_API_KEY" if apiKey == "" { log.Fatal("Google API key is required") } // 启动gRPC服务器 lis, err := net.Listen("tcp", ":50051") if err != nil { log.Fatalf("failed to listen: %v", err) } s := grpc.NewServer() pb.RegisterRoutesServer(s, &routesServer{googleAPIKey: apiKey}) log.Printf("gRPC server listening at %v", lis.Addr()) if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } }
代码说明
- 类型转换:自定义请求/响应类型和Google API的类型做双向转换,确保格式兼容。
- API调用:使用HTTP客户端调用Google Routes API的REST端点,避免导入官方gRPC客户端(解决你担心的不稳定问题)。
- 流式响应:按照gRPC流式响应的要求,逐个返回结果元素。
- 错误处理:用gRPC标准错误码返回各类异常,方便客户端处理。
三、运行说明
- 替换代码中的
your-module-path为你的Go模块路径,YOUR_GOOGLE_API_KEY为从Google Cloud控制台获取的API密钥(需要启用Routes API)。 - 运行服务器:
go run main.go。 - 客户端可以通过gRPC调用
localhost:50051上的ComputeRouteMatrix方法,实现对Google Routes API的间接调用。
内容的提问来源于stack exchange,提问作者Sahildeep Kaur
相关产品推荐
相关产品推荐

