gRPC 全栈开发:Proto3 + 四种 RPC + 拦截器 + 生产部署 + 前端集成

前言

💡 痛点: gRPC 和 REST 怎么选?Proto3 怎么写?四种 RPC 类型怎么用?服务间通信怎么保障安全和性能?前端怎么调用 gRPC?

🎯 解决方案: 本文系统覆盖 gRPC 全栈开发:Proto3 语法与代码生成、四种 RPC 类型(Unary/ServerStreaming/ClientStreaming/BidirectionalStreaming)、元数据与拦截器、认证与 TLS、负载均衡、gRPC-Web 前端集成、NestJS + gRPC 实战、Docker Compose 生产部署。
#mermaid-svg-V9kmfUqjb8qMvQKS{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-V9kmfUqjb8qMvQKS .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-V9kmfUqjb8qMvQKS .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-V9kmfUqjb8qMvQKS .error-icon{fill:#552222;}#mermaid-svg-V9kmfUqjb8qMvQKS .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-V9kmfUqjb8qMvQKS .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-V9kmfUqjb8qMvQKS .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-V9kmfUqjb8qMvQKS .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-V9kmfUqjb8qMvQKS .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-V9kmfUqjb8qMvQKS .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-V9kmfUqjb8qMvQKS .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-V9kmfUqjb8qMvQKS .marker{fill:#333333;stroke:#333333;}#mermaid-svg-V9kmfUqjb8qMvQKS .marker.cross{stroke:#333333;}#mermaid-svg-V9kmfUqjb8qMvQKS svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-V9kmfUqjb8qMvQKS p{margin:0;}#mermaid-svg-V9kmfUqjb8qMvQKS .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-V9kmfUqjb8qMvQKS .cluster-label text{fill:#333;}#mermaid-svg-V9kmfUqjb8qMvQKS .cluster-label span{color:#333;}#mermaid-svg-V9kmfUqjb8qMvQKS .cluster-label span p{background-color:transparent;}#mermaid-svg-V9kmfUqjb8qMvQKS .label text,#mermaid-svg-V9kmfUqjb8qMvQKS span{fill:#333;color:#333;}#mermaid-svg-V9kmfUqjb8qMvQKS .node rect,#mermaid-svg-V9kmfUqjb8qMvQKS .node circle,#mermaid-svg-V9kmfUqjb8qMvQKS .node ellipse,#mermaid-svg-V9kmfUqjb8qMvQKS .node polygon,#mermaid-svg-V9kmfUqjb8qMvQKS .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-V9kmfUqjb8qMvQKS .rough-node .label text,#mermaid-svg-V9kmfUqjb8qMvQKS .node .label text,#mermaid-svg-V9kmfUqjb8qMvQKS .image-shape .label,#mermaid-svg-V9kmfUqjb8qMvQKS .icon-shape .label{text-anchor:middle;}#mermaid-svg-V9kmfUqjb8qMvQKS .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-V9kmfUqjb8qMvQKS .rough-node .label,#mermaid-svg-V9kmfUqjb8qMvQKS .node .label,#mermaid-svg-V9kmfUqjb8qMvQKS .image-shape .label,#mermaid-svg-V9kmfUqjb8qMvQKS .icon-shape .label{text-align:center;}#mermaid-svg-V9kmfUqjb8qMvQKS .node.clickable{cursor:pointer;}#mermaid-svg-V9kmfUqjb8qMvQKS .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-V9kmfUqjb8qMvQKS .arrowheadPath{fill:#333333;}#mermaid-svg-V9kmfUqjb8qMvQKS .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-V9kmfUqjb8qMvQKS .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-V9kmfUqjb8qMvQKS .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-V9kmfUqjb8qMvQKS .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-V9kmfUqjb8qMvQKS .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-V9kmfUqjb8qMvQKS .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-V9kmfUqjb8qMvQKS .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-V9kmfUqjb8qMvQKS .cluster text{fill:#333;}#mermaid-svg-V9kmfUqjb8qMvQKS .cluster span{color:#333;}#mermaid-svg-V9kmfUqjb8qMvQKS div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-V9kmfUqjb8qMvQKS .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-V9kmfUqjb8qMvQKS rect.text{fill:none;stroke-width:0;}#mermaid-svg-V9kmfUqjb8qMvQKS .icon-shape,#mermaid-svg-V9kmfUqjb8qMvQKS .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-V9kmfUqjb8qMvQKS .icon-shape p,#mermaid-svg-V9kmfUqjb8qMvQKS .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-V9kmfUqjb8qMvQKS .icon-shape .label rect,#mermaid-svg-V9kmfUqjb8qMvQKS .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-V9kmfUqjb8qMvQKS .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-V9kmfUqjb8qMvQKS .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-V9kmfUqjb8qMvQKS :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 基础设施
四种 RPC 类型
Proto3 核心
Message

消息定义
Service

服务定义
Option

自定义选项
Import

导入复用
Unary

请求-响应
Server Streaming

服务端流
Client Streaming

客户端流
Bidirectional

双向流
拦截器

Auth/Logging/Retry
mTLS

双向认证
负载均衡

客户端/服务端
gRPC-Gateway

HTTP/JSON 桥接


一、Proto3 深度语法

1.1 消息定义进阶

protobuf 复制代码
syntax = "proto3";

package api.v1;

option go_package = "github.com/example/api/v1;apiv1";
option java_multiple_files = true;
option java_package = "com.example.api.v1";
option java_outer_classname = "UserProtos";

// ======== 导入(可导入 google 类型)=======
import "google/protobuf/timestamp.proto";
import "google/protobuf/wrappers.proto";
import "google/protobuf/empty.proto";
import "google/api/field_behavior.proto";
import "validate/validate.proto";

// ======== 消息定义(完整示例)=======
message User {
  // 标量类型:double, float, int32, int64, uint32, uint64, sint32, sint64,
  //           fixed32, fixed64, sfixed32, sfixed64, bool, string, bytes

  string user_id = 1 [json_name = "userId", (validate.rules).string.min_len = 1];
  string username = 2 [(validate.rules).string.min_len = 3, (validate.rules).string.max_len = 50];
  string email = 3 [(validate.rules).string.email = true];

  // oneof(互斥字段)
  oneof contact {
    string phone = 4;
    string wechat = 5;
  }

  // 枚举
  UserStatus status = 6;
  map<string, string> metadata = 7;           // Map 类型
  repeated Address addresses = 8;            // repeated = 数组

  // Google 标准类型
  google.protobuf.Timestamp created_at = 9;
  google.protobuf.Timestamp updated_at = 10;

  // Optional(Proto3.15+,显式可选字段)
  optional string nickname = 11;

  // 保留字段(防止字段号被误用)
  reserved 100, 101 to 105;
  reserved "deprecated_field";
}

// ======== 枚举(带默认值处理)=======
enum UserStatus {
  // 第一个枚举值必须是 0(默认值)
  USER_STATUS_UNSPECIFIED = 0;
  USER_STATUS_ACTIVE = 1;
  USER_STATUS_INACTIVE = 2;
  USER_STATUS_SUSPENDED = 3;
  USER_STATUS_DELETED = 4;

  // 枚举别名
  option allow_alias = true;
  USER_STATUS_BANNED = 3;  // 别名,与 USER_STATUS_SUSPENDED 相同
}

// ======== Map 类型 ========
message Config {
  map<string, string> settings = 1;
  map<string, int64> counters = 2;
  map<string, UserRole> permissions = 3;
}

// ======== 嵌套消息 ========
message Address {
  string street = 1;
  string city = 2;
  string country = 3;
  string postal_code = 4 [(validate.rules).string.pattern = "^\\d{5,6}$"];
  Location location = 5;

  message Location {
    double latitude = 1 [(validate.rules).double.gte = -90, (validate.rules).double.lte = 90];
    double longitude = 2 [(validate.rules).double.gte = -180, (validate.rules).double.lte = 180];
  }
}

// ======== Oneof(互斥字段)=======
message LoginRequest {
  oneof login_method {
    string email = 1 [(validate.rules).string.email = true];
    string phone = 2 [(validate.rules).string.pattern = "^\\d{10,11}$"];
    string wechat_code = 3;
  }
  string password = 4;
}

// ======== 验证规则(buf 插件)=======
message CreateUserRequest {
  string username = 1 [
    (validate.rules).string.min_len = 3,
    (validate.rules).string.max_len = 50,
    (validate.rules).string.pattern = "^[a-zA-Z][a-zA-Z0-9_]*$"
  ];

  string email = 2 [
    (validate.rules).string.email = true
  ];

  int32 age = 3 [
    (validate.rules).int32.gte = 0,
    (validate.rules).int32.lte = 150
  ];

  repeated string roles = 4 [
    (validate.rules).repeated.min_items = 1,
    (validate.rules).repeated.max_items = 10,
    (validate.rules).repeated.items.string.in = {"values": ["admin", "user", "guest"]}
  ];

  map<string, string> tags = 5 [
    (validate.rules).map.min_pairs = 1,
    (validate.rules).map.max_pairs = 20
  ];
}

// ======== 自定义选项 ========
extend google.protobuf.MessageOptions {
  string message_doc_url = 50001;
  int32 message_version = 50002;
}

message SensitiveData {
  option (message_doc_url) = "https://docs.example.com/api/sensitive-data";
  option (message_version) = 2;
  string ssn = 1;
  string credit_card = 2;
}

// ======== gRPC Service 定义 ========
service UserService {
  // Unary RPC:标准请求-响应
  rpc GetUser(GetUserRequest) returns (GetUserResponse);
  rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);
  rpc UpdateUser(UpdateUserRequest) returns (UpdateUserResponse);
  rpc DeleteUser(DeleteUserRequest) returns (google.protobuf.Empty);

  // Server Streaming RPC:服务端流
  rpc ListUsers(ListUsersRequest) returns (stream User);
  rpc WatchUser(GetUserRequest) returns (stream User);  // 实时更新

  // Client Streaming RPC:客户端流
  rpc BatchCreateUsers(stream CreateUserRequest) returns (BatchCreateResponse);
  rpc UploadAvatar(stream AvatarChunk) returns (UploadResult);

  // Bidirectional Streaming RPC:双向流
  rpc Chat(stream ChatMessage) returns (stream ChatMessage);
  rpc QueryData(stream QueryRequest) returns (stream QueryResult);
}

// ======== 请求/响应消息 ========
message GetUserRequest {
  string user_id = 1 [(validate.rules).string.min_len = 1];
}

message GetUserResponse {
  User user = 1;
}

message ListUsersRequest {
  int32 page_size = 1 [(validate.rules).int32.gte = 1, (validate.rules).int32.lte = 100];
  string page_token = 2;
  string filter = 3;
}

message ListUsersResponse {
  repeated User users = 1;
  string next_page_token = 2;
  int32 total_count = 3;
}

// ======== 流式消息 ========
message AvatarChunk {
  string upload_id = 1;
  bytes data = 2;
  int32 offset = 3;
  string mime_type = 4;
}

message UploadResult {
  string file_id = 1;
  string url = 2;
  int64 size = 3;
}

message ChatMessage {
  string room_id = 1;
  string content = 2;
  google.protobuf.Timestamp timestamp = 3;
}

message QueryRequest {
  string query = 1;
  repeated string dimensions = 2;
}

message QueryResult {
  string dimension = 1;
  repeated string labels = 2;
  repeated double values = 3;
}

1.2 Buf 管理 Proto

yaml 复制代码
# ======== buf.yaml(项目根目录)=======
version: v1
name: buf.build/example/api
lint:
  use:
    - DEFAULT
    - COMMENTS
    - FILE_LOWER_SNAKE_CASE
  except:
    - PACKAGE_VERSION_SUFFIX
  disallow_comment_ignores: true
breaking:
  use:
    - FILE
  ignore:
    - api/v1/user.proto

---
# buf.gen.yaml(生成配置)
version: v1
managed:
  enabled: true
  override:
    - file_option: go_package_prefix
      value: github.com/example/api
plugins:
  - remote: buf.build/protocolbuffers/go
    out: gen/go
    opt:
      - paths=source_relative
  - remote: buf.build/grpc/go
    out: gen/go
    opt:
      - paths=source_relative
  - remote: buf.build/grpc-ecosystem/gateway
    out: gen/go
    opt:
      - paths=source_relative
  - remote: buf.build/bufcommunity/protoc-gen-validate-go
    out: gen/go
    opt:
      - paths=source_relative
  - remote: buf.build/openapitools/openapiv2
    out: gen/openapi
  - remote: buf.build/community/ts-proto
    out: gen/ts
    opt:
      - useDate=string
      - esModuleInterop=true
bash 复制代码
# ======== 构建命令 ========
# 安装 buf
# brew install buf (macOS)
# 或: scoop install buf (Windows)

# 初始化模块
buf mod init

# lint 检查
buf lint

# breaking change 检查
buf breaking --against '.git#branch=main'

# 生成代码
buf generate

# 完整 CI/CD
# .github/workflows/grpc.yml
# on: [push, pull_request]
# jobs:
#   build:
#     steps:
#       - uses: actions/checkout@v4
#       - uses: buf-build/buf-setup@v1
#       - run: buf lint
#       - run: buf breaking --against '.git#branch=main'
#       - run: buf generate

二、四种 RPC 类型实现

2.1 Go gRPC 服务端

go 复制代码
// ======== gRPC 服务端实现(Go)=======
package server

import (
	"context"
	"io"
	"log"
	"sync"
	"time"

	apiv1 "github.com/example/api/v1/gen/go"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/peer"
	"google.golang.org/grpc/status"
)

// UserServiceServer gRPC 服务实现
type UserServiceServer struct {
	apiv1.UnimplementedUserServiceServer
	users map[string]*apiv1.User
	mu    sync.RWMutex
}

func NewUserServiceServer() *UserServiceServer {
	return &UserServiceServer{
		users: make(map[string]*apiv1.User),
	}
}

// ======== Unary RPC:获取用户 ========
func (s *UserServiceServer) GetUser(
	ctx context.Context,
	req *apiv1.GetUserRequest,
) (*apiv1.GetUserResponse, error) {
	// 从 metadata 获取请求追踪 ID
	md, _ := metadata.FromIncomingContext(ctx)
	traceID := md.Get("x-trace-id")
	log.Printf("[GetUser] user_id=%s trace_id=%v", req.UserId, traceID)

	s.mu.RLock()
	user, ok := s.users[req.UserId]
	s.mu.RUnlock()

	if !ok {
		return nil, status.Errorf(codes.NotFound, "user %s not found", req.UserId)
	}

	return &apiv1.GetUserResponse{User: user}, nil
}

// ======== Unary RPC:创建用户 ========
func (s *UserServiceServer) CreateUser(
	ctx context.Context,
	req *apiv1.CreateUserRequest,
) (*apiv1.CreateUserResponse, error) {
	// 获取客户端信息(用于审计)
	if p, ok := peer.FromContext(ctx); ok {
		log.Printf("[CreateUser] client=%s user=%s", p.Addr, req.Username)
	}

	user := &apiv1.User{
		UserId:    generateUUID(),
		Username:  req.Username,
		Email:     req.Email,
		Status:    apiv1.UserStatus_USER_STATUS_ACTIVE,
		CreatedAt: timestampNow(),
	}

	s.mu.Lock()
	s.users[user.UserId] = user
	s.mu.Unlock()

	log.Printf("[CreateUser] created user_id=%s", user.UserId)

	return &apiv1.CreateUserResponse{User: user}, nil
}

// ======== Server Streaming RPC:列表用户 ========
func (s *UserServiceServer) ListUsers(
	req *apiv1.ListUsersRequest,
	stream apiv1.UserService_ListUsersServer,
) error {
	s.mu.RLock()
	allUsers := make([]*apiv1.User, 0, len(s.users))
	for _, u := range s.users {
		allUsers = append(allUsers, u)
	}
	s.mu.RUnlock()

	// 分页
	pageSize := int(req.PageSize)
	if pageSize <= 0 {
		pageSize = 10
	}

	start := 0
	if req.PageToken != "" {
		start = parsePageToken(req.PageToken)
	}

	for i := start; i < len(allUsers) && i < start+pageSize; i++ {
		if err := stream.Send(allUsers[i]); err != nil {
			return status.Errorf(codes.Internal, "send error: %v", err)
		}
		time.Sleep(100 * time.Millisecond) // 模拟延迟
	}

	return nil
}

// ======== Server Streaming RPC:实时监控用户变化 ========
func (s *UserServiceServer) WatchUser(
	req *apiv1.GetUserRequest,
	stream apiv1.UserService_WatchUserServer,
) error {
	ctx := stream.Context()

	s.mu.RLock()
	user, ok := s.users[req.UserId]
	s.mu.RUnlock()

	if !ok {
		return status.Errorf(codes.NotFound, "user %s not found", req.UserId)
	}

	// 立即发送当前状态
	if err := stream.Send(user); err != nil {
		return err
	}

	// 模拟实时更新(每 5 秒)
	ticker := time.NewTicker(5 * time.Second)
	defer ticker.Stop()

	for {
		select {
		case <-ctx.Done():
			return ctx.Err()
		case <-ticker.C:
			s.mu.RLock()
			user = s.users[req.UserId]
			s.mu.RUnlock()
			if err := stream.Send(user); err != nil {
				return err
			}
		}
	}
}

// ======== Client Streaming RPC:批量创建用户 ========
func (s *UserServiceServer) BatchCreateUsers(
	stream apiv1.UserService_BatchCreateUsersServer,
) error {
	created := []*apiv1.User{}

	for {
		req, err := stream.Recv()
		if err == io.EOF {
			// 客户端发送完毕
			break
		}
		if err != nil {
			return status.Errorf(codes.Internal, "recv error: %v", err)
		}

		user := &apiv1.User{
			UserId:    generateUUID(),
			Username:  req.Username,
			Email:     req.Email,
			Status:    apiv1.UserStatus_USER_STATUS_ACTIVE,
			CreatedAt: timestampNow(),
		}

		s.mu.Lock()
		s.users[user.UserId] = user
		s.mu.Unlock()

		created = append(created, user)
		log.Printf("[BatchCreate] created user_id=%s", user.UserId)
	}

	// 发送汇总响应
	return stream.SendAndClose(&apiv1.BatchCreateResponse{
		CreatedCount: int32(len(created)),
		Users:        created,
	})
}

// ======== Client Streaming RPC:文件上传 ========
func (s *UserServiceServer) UploadAvatar(
	stream apiv1.UserService_UploadAvatarServer,
) error {
	var uploadID string
	var data []byte
	var mimeType string

	for {
		req, err := stream.Recv()
		if err == io.EOF {
			break
		}
		if err != nil {
			return status.Errorf(codes.Internal, "recv error: %v", err)
		}

		if uploadID == "" {
			uploadID = req.UploadId
			mimeType = req.MimeType
		}

		data = append(data, req.Data...)
		log.Printf("[Upload] upload_id=%s offset=%d bytes=%d",
			req.UploadId, req.Offset, len(req.Data))
	}

	// 保存文件(实际存储逻辑)
	fileID := "avatar_" + uploadID

	// 发送完成结果
	return stream.SendAndClose(&apiv1.UploadResult{
		FileId: fileID,
		Url:    "/uploads/" + fileID,
		Size:   int64(len(data)),
	})
}

// ======== Bidirectional Streaming RPC:聊天 ========
func (s *UserServiceServer) Chat(
	stream apiv1.UserService_ChatServer,
) error {
	ctx := stream.Context()
	userRooms := make(map[string]string) // clientID -> roomID

	for {
		select {
		case <-ctx.Done():
			return ctx.Err()
		default:
		}

		req, err := stream.Recv()
		if err == io.EOF {
			return nil
		}
		if err != nil {
			return status.Errorf(codes.Internal, "recv error: %v", err)
		}

		roomID := req.RoomId
		if _, ok := userRooms["client"]; !ok {
			userRooms["client"] = roomID
		}

		log.Printf("[Chat] room=%s msg=%s", roomID, req.Content)

		// 处理消息
		response := &apiv1.ChatMessage{
			RoomId:    roomID,
			Content:   "Echo: " + req.Content,
			Timestamp: timestampNow(),
		}

		if err := stream.Send(response); err != nil {
			return status.Errorf(codes.Internal, "send error: %v", err)
		}
	}
}

// ======== Bidirectional Streaming RPC:查询引擎 ========
func (s *UserServiceServer) QueryData(
	stream apiv1.UserService_QueryDataServer,
) error {
	ctx := stream.Context()

	for {
		select {
		case <-ctx.Done():
			return ctx.Err()
		default:
		}

		req, err := stream.Recv()
		if err == io.EOF {
			return nil
		}
		if err != nil {
			return status.Errorf(codes.Internal, "recv error: %v", err)
		}

		// 模拟查询(按维度分批返回)
		results := simulateQuery(req.Query, req.Dimensions)

		for _, r := range results {
			if err := stream.Send(r); err != nil {
				return status.Errorf(codes.Internal, "send error: %v", err)
			}
		}
	}
}

2.2 gRPC 客户端

go 复制代码
// ======== gRPC 客户端实现(Go)=======
package client

import (
	"context"
	"io"
	"log"
	"time"

	apiv1 "github.com/example/api/v1/gen/go"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/credentials/insecure"
	"google.golang.org/grpc/encoding/gzip"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/resolver"
	"google.golang.org/grpc/resolver/manual"
)

// ======== 客户端配置 ========
type Config struct {
	Address    string
	TLSEnabled bool
	TLSCert    string
	Token      string  // Bearer token
	DialOpts   []grpc.DialOption
}

// ======== 创建连接 ========
func NewGRPCConnection(ctx context.Context, cfg Config) (*grpc.ClientConn, error) {
	var opts []grpc.DialOption

	// 传输层
	if cfg.TLSEnabled {
		creds, err := credentials.NewClientTLSFromFile(cfg.TLSCert, "")
		if err != nil {
			return nil, err
		}
		opts = append(opts, grpc.WithTransportCredentials(creds))
	} else {
		opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
	}

	// 压缩
	opts = append(opts, grpc.WithDefaultCallOptions(
		grpc.UseCompressor(gzip.Name),
	))

	// 超时
	opts = append(opts, grpc.WithBlock())
	opts = append(opts, grpc.WithTimeout(10*time.Second))

	// 连接
	conn, err := grpc.NewClient(cfg.Address, opts...)
	if err != nil {
		return nil, err
	}

	return conn, nil
}

// ======== 带认证的客户端 ========
type AuthClient struct {
	conn   *grpc.ClientConn
	client apiv1.UserServiceClient
	token  string
}

func NewAuthClient(ctx context.Context, address, token string) (*AuthClient, error) {
	conn, err := grpc.NewClient(address,
		grpc.WithTransportCredentials(insecure.NewCredentials()),
	)
	if err != nil {
		return nil, err
	}

	return &AuthClient{
		conn:   conn,
		client: apiv1.NewUserServiceClient(conn),
		token:  token,
	}, nil
}

// ======== 带 metadata 的调用 ========
func (c *AuthClient) GetUserWithAuth(ctx context.Context, userID string) (*apiv1.User, error) {
	// 注入认证 token
	ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs(
		"authorization", "Bearer "+c.token,
		"x-client-version", "1.0.0",
		"x-request-id", generateUUID(),
	))

	return c.client.GetUser(ctx, &apiv1.GetUserRequest{UserId: userID})
}

// ======== Server Streaming 客户端 ========
func (c *AuthClient) ListUsersStream(ctx context.Context, pageSize int32) error {
	stream, err := c.client.ListUsers(ctx, &apiv1.ListUsersRequest{
		PageSize: pageSize,
	})
	if err != nil {
		return err
	}

	for {
		user, err := stream.Recv()
		if err == io.EOF {
			break
		}
		if err != nil {
			return err
		}
		log.Printf("[ListUsers] user_id=%s username=%s", user.UserId, user.Username)
	}

	return nil
}

// ======== Client Streaming 客户端 ========
func (c *AuthClient) BatchCreate(ctx context.Context, users []*apiv1.CreateUserRequest) error {
	stream, err := c.client.BatchCreateUsers(ctx)
	if err != nil {
		return err
	}

	// 发送所有用户
	for _, req := range users {
		if err := stream.Send(req); err != nil {
			return status.Errorf(codes.Internal, "send error: %v", err)
		}
	}

	// 关闭流并获取响应
	resp, err := stream.CloseAndRecv()
	if err != nil {
		return err
	}

	log.Printf("[BatchCreate] created %d users", resp.CreatedCount)
	return nil
}

// ======== Bidirectional Streaming 客户端 ========
func (c *AuthClient) Chat(ctx context.Context, roomID string, messages []string) error {
	stream, err := c.client.Chat(ctx)
	if err != nil {
		return err
	}

	// Goroutine 用于接收响应
	go func() {
		for {
			msg, err := stream.Recv()
			if err == io.EOF {
				return
			}
			if err != nil {
				log.Printf("[Chat] recv error: %v", err)
				return
			}
			log.Printf("[Chat] received: %s", msg.Content)
		}
	}()

	// 发送消息
	for _, msg := range messages {
		if err := stream.Send(&apiv1.ChatMessage{
			RoomId:  roomID,
			Content: msg,
		}); err != nil {
			return err
		}
		time.Sleep(500 * time.Millisecond)
	}

	return stream.CloseSend()
}

// ======== 负载均衡(客户端负载均衡)=======
func (c *AuthClient) WithLoadBalancing(ctx context.Context) error {
	// 使用 DNS Resolver + Round Robin
	// 或使用 grpclb(gRPC Load Balancer)

	// grpclb 方案:
	// conn, _ := grpc.Dial("passthrough:///grpclb:///backend-service",
	//     grpc.WithBalancerName("round_robin"))

	// xds 方案(推荐,替代 grpclb):
	conn, err := grpc.NewClient(
		"xds:///user-service",  // xds:///服务名,自动从控制面获取地址
		grpc.WithResolvers(),    // 启用 xDS
	)
	if err != nil {
		return err
	}
	defer conn.Close()

	client := apiv1.NewUserServiceClient(conn)
	// 每次调用自动负载均衡

	return nil
}

三、拦截器

3.1 服务端拦截器

go 复制代码
// ======== 服务端拦截器 ========
package interceptor

import (
	"context"
	"log"
	"time"

	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

// ======== 日志拦截器 ========
func LoggingInterceptor() grpc.UnaryServerInterceptor {
	return func(
		ctx context.Context,
		req interface{},
		info *grpc.UnaryServerInfo,
		handler grpc.UnaryHandler,
	) (interface{}, error) {
		start := time.Now()

		// 提取 metadata
		md, _ := metadata.FromIncomingContext(ctx)
		traceID := md.Get("x-trace-id")

		log.Printf("[GRPC] method=%s trace_id=%v req=%T started_at=%s",
			info.FullMethod, traceID, req, start.Format(time.RFC3339))

		resp, err := handler(ctx, req)

		duration := time.Since(start)
		code := status.Code(err)

		log.Printf("[GRPC] method=%s code=%s duration=%v err=%v",
			info.FullMethod, code, duration, err)

		return resp, err
	}
}

// ======== 认证拦截器 ========
func AuthInterceptor(validators ...TokenValidator) grpc.UnaryServerInterceptor {
	return func(
		ctx context.Context,
		req interface{},
		info *grpc.UnaryServerInfo,
		handler grpc.UnaryHandler,
	) (interface{}, error) {
		// 跳过健康检查和认证豁免的方法
		if isExemptMethod(info.FullMethod) {
			return handler(ctx, req)
		}

		md, ok := metadata.FromIncomingContext(ctx)
		if !ok {
			return nil, status.Errorf(codes.Unauthenticated, "missing metadata")
		}

		tokens := md.Get("authorization")
		if len(tokens) == 0 {
			return nil, status.Errorf(codes.Unauthenticated, "missing token")
		}

		token := tokens[0]
		if len(token) > 7 && token[:7] == "Bearer " {
			token = token[7:]
		}

		// 验证 token
		for _, v := range validators {
			userID, err := v.Validate(token)
			if err != nil {
				return nil, status.Errorf(codes.Unauthenticated, "invalid token: %v", err)
			}
			ctx = context.WithValue(ctx, "user_id", userID)
		}

		return handler(ctx, req)
	}
}

// ======== 限流拦截器(Token Bucket)=======
func RateLimitInterceptor(rate int, burst int) grpc.UnaryServerInterceptor {
	limiter := NewTokenBucketLimiter(rate, burst)

	return func(
		ctx context.Context,
		req interface{},
		info *grpc.UnaryServerInfo,
		handler grpc.UnaryHandler,
	) (interface{}, error) {
		if !limiter.Allow() {
			return nil, status.Errorf(codes.ResourceExhausted,
				"rate limit exceeded: %d req/s, burst %d", rate, burst)
		}
		return handler(ctx, req)
	}
}

// ======== 链式拦截器(多个拦截器组合)=======
func ChainUnaryInterceptors(
	interceptors ...grpc.UnaryServerInterceptor,
) grpc.ServerOption {
	return grpc.ChainUnaryInterceptor(interceptors...)
}

// ======== 服务端注册(带拦截器)=======
func RegisterServer(grpcServer *grpc.Server) {
	server := NewUserServiceServer()

	grpcServer.RegisterService(&apiv1.UserService_ServiceDesc, server)

	// 应用链式拦截器
	// 注意:拦截器在 NewServer 时传入,而不是 RegisterService
}

3.2 客户端拦截器

go 复制代码
// ======== 客户端拦截器 ========
package interceptor

import (
	"context"
	"time"

	"google.golang.org/grpc"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/status"
)

// ======== 重试拦截器 ========
func RetryUnaryInterceptor(maxRetries int, backoff BackoffStrategy) grpc.UnaryClientInterceptor {
	return func(
		ctx context.Context,
		method string,
		req, reply interface{},
		cc *grpc.ClientConn,
		invoker grpc.UnaryInvoker,
		opts ...grpc.CallOption,
	) error {
		var lastErr error

		for attempt := 0; attempt <= maxRetries; attempt++ {
			if attempt > 0 {
				wait := backoff.Duration(attempt)
				select {
				case <-time.After(wait):
				case <-ctx.Done():
					return ctx.Err()
				}
			}

			lastErr = invoker(ctx, method, req, reply, cc, opts...)

			// 只对可重试的错误重试
			if lastErr == nil {
				return nil
			}

			s := status.Convert(lastErr)
			if !isRetryableCode(s.Code()) {
				return lastErr
			}

			// 指数退避
		}

		return lastErr
	}
}

func isRetryableCode(code codes.Code) bool {
	return code == codes.Unavailable ||
		code == codes.ResourceExhausted ||
		code == codes.Internal
}

// ======== 超时拦截器 ========
func TimeoutUnaryInterceptor(timeout time.Duration) grpc.UnaryClientInterceptor {
	return func(
		ctx context.Context,
		method string,
		req, reply interface{},
		cc *grpc.ClientConn,
		invoker grpc.UnaryInvoker,
		opts ...grpc.CallOption,
	) error {
		ctx, cancel := context.WithTimeout(ctx, timeout)
		defer cancel()
		return invoker(ctx, method, req, reply, cc, opts...)
	}
}

// ======== 客户端链式拦截器 ========
func ChainUnaryClientInterceptors(
	interceptors ...grpc.UnaryClientInterceptor,
) grpc.DialOption {
	return grpc.WithUnaryInterceptor(
		grpc.ChainUnaryInterceptor(interceptors...),
	)
}

四、gRPC-Web 前端集成

4.1 Envoy 网关配置

yaml 复制代码
# ======== Envoy gRPC-Web 配置 ========
# envoy-grpc-web.yaml
static_resources:
  listeners:
  - name: grpc_web_listener
    address:
      socket_address:
        address: 0.0.0.0
        port_value: 8080
    filter_chains:
    - filters:
      - name: envoy.filters.network.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
          codec_type: AUTO
          stat_prefix: grpc_web
          route_config:
            name: grpc_web_route
            virtual_hosts:
            - name: backend
              domains: ["*"]
              routes:
              # gRPC-Web 路由
              - match: { prefix: "/api.v1.UserService" }
                route:
                  cluster: grpc_backend
                  timeout: 300s
                  # gRPC-Web 必须设置
                  grpc: {}
              # gRPC-JSON 转码路由(可选)
              - match: { prefix: "/v1/users" }
                route:
                  cluster: grpc_backend
                  timeout: 60s
                  grpc: {}
          http_filters:
          - name: envoy.filters.http.grpc_web
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_web.v3.GrpcWeb
          - name: envoy.filters.http.cors
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.cors.v3.Cors
              allow_origin_string_match:
              - safe_regex:
                  regex: ".*"
              allow_methods: GET, POST, OPTIONS
              allow_headers: keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,x-grpc-web,x-user-agent,authorization
              exposed_headers: grpc-status,grpc-message
              max_age: "86400"
          - name: envoy.filters.http.router
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router

  clusters:
  - name: grpc_backend
    type: STRICT_DNS
    lb_policy: ROUND_ROBIN
    typed_dns_resolution_config:
      dns_resolver_name: envoy.network.dns_resolver.cares
    dns_lookup_family: V4_ONLY
    targets:
    - socket_address:
        address: grpc-backend
        port_value: 50051
    transport_socket:
      name: envoy.transport_sockets.tls
      typed_config:
        "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
        sni: grpc-backend.internal.com

4.2 TypeScript 客户端

typescript 复制代码
// ======== TypeScript gRPC-Web 客户端 ========
// 安装:npm install @improbable-eng/grpc-web google-protobuf @types/google-protobuf

import { grpc } from '@improbable-eng/grpc-web';
import { UserServiceClient } from './generated/api/v1/UserServiceClientPb';
import {
  GetUserRequest,
  GetUserResponse,
  ListUsersRequest,
  User,
} from './generated/api/v1/user_pb';
import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb';

// 创建 gRPC-Web 客户端
const client = new UserServiceClient('http://localhost:8080', {
  transport: grpc.CrossBrowserHttpTransport({ withCredentials: true }),
});

// ======== Unary 调用 ========
async function getUser(userId: string): Promise<User | null> {
  const req = new GetUserRequest();
  req.setUserId(userId);

  return new Promise((resolve, reject) => {
    client.getUser(
      req,
      grpc.metadataConstructor({
        Authorization: `Bearer ${getAuthToken()}`,
        'X-Request-ID': crypto.randomUUID(),
      }),
      (err: grpc.RpcError | null, response: GetUserResponse | null) => {
        if (err) {
          console.error('gRPC Error:', err.code, err.message);
          reject(err);
          return;
        }
        resolve(response?.getUser() || null);
      }
    );
  });
}

// ======== Server Streaming 调用 ========
function listUsersStream(pageSize: number): void {
  const req = new ListUsersRequest();
  req.setPageSize(pageSize);

  const stream = client.listUsers(req, grpc.metadataConstructor({
    Authorization: `Bearer ${getAuthToken()}`,
  }));

  stream.on('data', (response: User) => {
    console.log('User:', {
      userId: response.getUserId(),
      username: response.getUsername(),
      email: response.getEmail(),
    });
  });

  stream.on('status', (status: grpc.Status) => {
    console.log('Stream status:', status.code, status.details);
  });

  stream.on('error', (err: grpc.RpcError) => {
    console.error('Stream error:', err.code, err.message);
  });

  stream.on('end', () => {
    console.log('Stream ended');
  });
}

// ======== Client Streaming 调用(批量上传)=======
async function batchCreateUsers(users: Array<{username: string; email: string}>): Promise<void> {
  const stream = client.batchCreateUsers(
    grpc.metadataConstructor({ Authorization: `Bearer ${getAuthToken()}` })
  );

  // 发送所有请求
  for (const user of users) {
    const req = new CreateUserRequest();
    req.setUsername(user.username);
    req.setEmail(user.email);
    stream.write(req);
  }

  return new Promise((resolve, reject) => {
    stream.on('data', (response: BatchCreateResponse) => {
      console.log('Batch created:', response.getCreatedCount());
    });

    stream.on('status', (status: grpc.Status) => {
      if (status.code !== grpc.Code.OK) {
        reject(new Error(status.details));
      }
    });

    stream.on('error', reject);
    stream.on('end', resolve);

    // 关闭流
    stream.end();
  });
}

// ======== Bidirectional Streaming 调用(聊天)=======
function chatStream(roomId: string): {
  send: (content: string) => void;
  onMessage: (handler: (msg: string) => void) => void;
  close: () => void;
} {
  const stream = client.chat(grpc.metadataConstructor({
    Authorization: `Bearer ${getAuthToken()}`,
  }));

  const handlers: Array<(msg: string) => void> = [];

  stream.on('data', (response: ChatMessage) => {
    const content = response.getContent();
    handlers.forEach(h => h(content));
  });

  return {
    send: (content: string) => {
      const req = new ChatMessage();
      req.setRoomId(roomId);
      req.setContent(content);
      stream.write(req);
    },
    onMessage: (handler: (msg: string) => void) => {
      handlers.push(handler);
    },
    close: () => stream.end(),
  };
}

// ======== React Hook 封装 ========
import { useState, useEffect, useCallback } from 'react';

function useGRPCUser(userId: string | null) {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<Error | null>(null);

  const fetchUser = useCallback(async () => {
    if (!userId) return;

    setLoading(true);
    setError(null);

    try {
      const result = await getUser(userId);
      setUser(result);
    } catch (e) {
      setError(e as Error);
    } finally {
      setLoading(false);
    }
  }, [userId]);

  useEffect(() => {
    fetchUser();
  }, [fetchUser]);

  return { user, loading, error, refetch: fetchUser };
}

// ======== Vue 3 Composition API 封装 ========
import { ref, onMounted } from 'vue';

function useGRPCUserVue(userId: Ref<string | null>) {
  const user = ref<User | null>(null);
  const loading = ref(false);
  const error = ref<Error | null>(null);

  const fetchUser = async () => {
    if (!userId.value) return;
    loading.value = true;
    error.value = null;

    try {
      user.value = await getUser(userId.value);
    } catch (e) {
      error.value = e as Error;
    } finally {
      loading.value = false;
    }
  };

  onMounted(fetchUser);

  return { user, loading, error, refetch: fetchUser };
}

4.3 gRPC-Gateway(HTTP/JSON 转码)

yaml 复制代码
# ======== gRPC-Gateway 生成配置 ========
# buf.gen.yaml 中添加:
# - remote: buf.build/grpc-ecosystem/gateway
#   out: gen/go
#   opt:
#     - paths=source_relative

# ======== 生成的 HTTP 端点 ========
/*
gRPC-Gateway 根据 proto 注解自动生成 HTTP 路由:

// HTTP GET /api/v1/users/{user_id}
rpc GetUser(GetUserRequest) returns (GetUserResponse) {
  option (google.api.http) = {
    get: "/api/v1/users/{user_id}"
  };
}

// HTTP POST /api/v1/users
rpc CreateUser(CreateUserRequest) returns (CreateUserResponse) {
  option (google.api.http) = {
    post: "/api/v1/users"
    body: "*"
  };
}

// HTTP LIST /api/v1/users
rpc ListUsers(ListUsersRequest) returns (ListUsersResponse) {
  option (google.api.http) = {
    get: "/api/v1/users"
  };
}
*/

# ======== HTTP 请求示例 ========
# curl http://localhost:8080/api/v1/users/user-123
curl -X GET http://localhost:8080/api/v1/users/user-123 \
  -H "Authorization: Bearer $TOKEN"

# curl -X POST http://localhost:8080/api/v1/users
curl -X POST http://localhost:8080/api/v1/users \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "username": "testuser",
    "email": "test@example.com"
  }'

五、生产部署

5.1 Docker Compose 生产部署

yaml 复制代码
# ======== docker-compose.grpc.yml ========
version: "3.9"

services:
  # ======== gRPC 服务 ========
  user-service:
    image: example/user-service:v1.0.0
    container_name: user-service
    ports:
      - "50051:50051"
      - "9090:9090"  # Prometheus metrics
    environment:
      - GRPC_PORT=50051
      - DATABASE_URL=postgres://postgres:password@postgres:5432/users
      - REDIS_URL=redis://redis:6379/0
      - JAEGER_ENDPOINT=jaeger:4317
      - LOG_LEVEL=info
    healthcheck:
      test: ["CMD-SHELL", "grpc_health_probe -addr=:50051"]
      interval: 10s
      timeout: 5s
      retries: 3
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_started
    networks:
      - grpc-network
    restart: unless-stopped

  # ======== Envoy 反向代理 ========
  envoy:
    image: envoyproxy/envoy:v1.29.0
    container_name: envoy
    ports:
      - "8080:8080"  # HTTP/gRPC-Web
      - "9901:9901"  # Admin
    volumes:
      - ./envoy.yaml:/etc/envoy/envoy.yaml:ro
    depends_on:
      - user-service
    networks:
      - grpc-network
    restart: unless-stopped

  # ======== 数据库 ========
  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
      POSTGRES_DB: users
    volumes:
      - postgres-data:/var/lib/postgresql/data
    command: >
      -c max_connections=200
      -c shared_buffers=256MB
      -c effective_cache_size=768MB
      -c work_mem=16MB
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 3
    networks:
      - grpc-network
    restart: unless-stopped

  # ======== Redis ========
  redis:
    image: redis:7.2-alpine
    command: redis-server --maxmemory 256mb --maxmemory-policy allkeys-lru
    volumes:
      - redis-data:/data
    networks:
      - grpc-network
    restart: unless-stopped

  # ======== Prometheus ========
  prometheus:
    image: prom/prometheus:v2.48
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
    ports:
      - "9091:9090"
    networks:
      - grpc-network
    restart: unless-stopped

  # ======== Grafana ========
  grafana:
    image: grafana/grafana:10.2
    ports:
      - "3000:3000"
    volumes:
      - grafana-data:/var/lib/grafana
    depends_on:
      - prometheus
    networks:
      - grpc-network
    restart: unless-stopped

networks:
  grpc-network:
    driver: bridge

volumes:
  postgres-data:
  redis-data:
  grafana-data:
yaml 复制代码
# ======== Prometheus 抓取 gRPC 指标 ========
# prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: 'grpc-services'
    static_configs:
      - targets:
          - 'user-service:9090'
    metrics_path: /metrics
    scheme: http
    # gRPC 指标需要配置
    params:
      service: ['user-service']

六、Checklist 总结

复制代码
□ Proto3 语法
  □ 消息定义(标量/枚举/Oneof/Map/嵌套)
  □ 保留字段(reserved)防止误用
  □ 验证规则(validate.proto/buf)
  □ Buf 管理(lint/breaking/gen)
  □ Buf CI/CD 配置

□ 四种 RPC 类型
  □ Unary:请求-响应(同步)
  □ Server Streaming:服务端流(实时推送)
  □ Client Streaming:客户端流(批量上传)
  □ Bidirectional Streaming:双向流(实时交互)

□ 服务端实现
  □ Handler 实现(Go/Java/TS)
  □ Context 传递(metadata/peer)
  □ 流式处理(Send/Recv/CloseAndRecv)
  □ 错误处理(gRPC status codes)

□ 客户端实现
  □ 连接创建(TLS/insecure)
  □ Unary/Streaming 调用
  □ 重试与超时
  □ 客户端负载均衡(xDS/RoundRobin)

□ 拦截器
  □ 服务端:Logging/Auth/RateLimit/Recovery
  □ 客户端:Retry/Timeout/Auth/Metrics
  □ 链式拦截器组合

□ gRPC-Web 前端集成
  □ Envoy gRPC-Web 网关
  □ CORS 配置
  □ TypeScript/JS 客户端
  □ React Hook / Vue Composition API 封装
  □ gRPC-Gateway HTTP/JSON 转码

□ 生产部署
  □ TLS/mTLS 配置
  □ 健康检查(grpc_health_probe)
  □ Prometheus + Grafana 监控
  □ Docker Compose 编排
  □ 限流与熔断

总结

gRPC vs REST 全方位对比:

维度 gRPC REST (HTTP/JSON)
协议 HTTP/2 + Protobuf HTTP/1.1 or HTTP/2
序列化 Protobuf(二进制,高效) JSON(文本,易读)
传输效率 高(压缩率高,序列化快) 低(JSON 体积大)
类型安全 强(Proto 定义,代码生成) 弱(无 schema)
流式支持 原生(双向流) 需 WebSocket/SSE
浏览器支持 需 gRPC-Web + Envoy 原生支持
调试 需工具(grpcurl/evans) curl 即可
适用场景 微服务内部、服务间通信 浏览器/移动端 API

gRPC 适用场景:

  • 微服务间高性能通信
  • 双向流式实时交互(聊天、监控推送)
  • 跨语言服务调用(强类型代码生成)
  • 低延迟系统(金融、游戏后端)

REST 适用场景:

  • 公开 API(外部开发者)
  • 需要浏览器直接调用
  • 简单 CRUD 操作
  • 需要人类可读的调试信息

下一步推荐:

  • gRPC 负载均衡深度实战(xDS/LRS/Lua 扩展)
  • gRPC 安全实战(mTLS + JWT + Certificate Policy)
  • NestJS + gRPC + Microservices 全家桶