You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

通过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)
	}
}

代码说明

  1. 类型转换:自定义请求/响应类型和Google API的类型做双向转换,确保格式兼容。
  2. API调用:使用HTTP客户端调用Google Routes API的REST端点,避免导入官方gRPC客户端(解决你担心的不稳定问题)。
  3. 流式响应:按照gRPC流式响应的要求,逐个返回结果元素。
  4. 错误处理:用gRPC标准错误码返回各类异常,方便客户端处理。

三、运行说明

  1. 替换代码中的your-module-path为你的Go模块路径,YOUR_GOOGLE_API_KEY为从Google Cloud控制台获取的API密钥(需要启用Routes API)。
  2. 运行服务器:go run main.go。
  3. 客户端可以通过gRPC调用localhost:50051上的ComputeRouteMatrix方法,实现对Google Routes API的间接调用。

内容的提问来源于stack exchange,提问作者Sahildeep Kaur

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 03:23:14