← 返回
后端开发 2026.03.06

Go工程師體系課 003

后端开发
  • grpc
  • grpc-go

grpc 無縫集成了 protobuf

protobuf

  • 習慣用 Json、XML 數據存儲格式的你們,相信大多都沒聽過 Protocol Buffer。
  • Protocol Buffer 其實是 Google 出品的一種輕量 & 高效的結構化數據存儲格式,性能比 Json、XML 真的強!太!多!
  • protobuf 經歷了 protobuf2 和 protobuf3,pb3 比 pb2 簡化了很多,目前主流的版本是 pb3。

protoc 的下載

下載 protoc 和go get github.com/golang/protobuf/protoc-gen-go

syntax = "proto3";

option go_package = "."; //這名不寫現在編譯不了

message HelloRequest {
  string name = 1; //1 是編號不是值
}
protoc --proto_path=. \
  --go_out=. \
  --go-grpc_out=. \
  ./helloworld.proto

會在目錄中生成一個helloworld.pb.go的文件

壓縮比的對比

package main

import (
 "RpcLearn/protobuf"
 "encoding/json"
 "fmt"
 "github.com/golang/protobuf/proto"
)

// TIP <p>To run your code, right-click the code and select <b>Run</b>.</p> <p>Alternatively, click
// the <icon src="AllIcons.Actions.Execute"/> icon in the gutter and select the <b>Run</b> menu item from here.</p>
type Hello struct {
 Name string `json:"name"`
}

func main() {
 //TIP <p>Press <shortcut actionId="ShowIntentionActions"/> when your caret is at the underlined text
 // to see how GoLand suggests fixing the warning.</p><p>Alternatively, if available, click the lightbulb to view possible fixes.</p>
 //s := "gopher"
 //fmt.Printf("Hello and welcome, %s!\n", s)
 //
 //for i := 1; i <= 5; i++ {
 // //TIP <p>To start your debugging session, right-click your code in the editor and select the Debug option.</p> <p>We have set one <icon src="AllIcons.Debugger.Db_set_breakpoint"/> breakpoint
 // // for you, but you can always add more by pressing <shortcut actionId="ToggleLineBreakpoint"/>.</p>
 // fmt.Println("i =", 100/i)
 //}
 jsonStruct := Hello{
  Name: "gopher",
 }
 jsonReq, _ := json.Marshal(jsonStruct)
 fmt.Println(string(jsonReq))
 fmt.Println(len(jsonReq))
 req := __.HelloRequest{
  Name: "gopher",
 }
 rsp, _ := proto.Marshal(&req)
 fmt.Println(rsp)
 fmt.Println(len(rsp))

}

差不多兩倍的差異

Go 語言中使用 Protobuf 的變化說明

教程內容當前實際
以前只有一個 .pb.go 文件現在生成兩個文件:一個 .pb.go(數據結構),一個 _grpc.pb.go(gRPC 接口)
插件不分離插件已拆分爲 protoc-gen-goprotoc-gen-go-grpc,需要分別安裝和調用

📌 當前行爲說明

從 Go 的 Protobuf 插件 v1.20+ 開始,gRPC 支持被拆分爲單獨的插件 protoc-gen-go-grpc,用於生成服務接口相關代碼。這種方式更模塊化,職責更清晰。

因此,執行如下命令:

protoc --proto_path=. \
  --go_out=. \
  --go-grpc_out=. \
  ./your_file.proto

將會生成:

  • your_file.pb.go: 包含消息結構(如 Request、Response)
  • your_file_grpc.pb.go: 包含服務接口定義(如 YourServiceServer)

建議保留並同步這兩個文件。

簡單梳理一下 grpc 開的流程

  1. 創建存根文件及方法定義,然後編譯
// proto/helloworld.proto
syntax = "proto3";
option go_package = ".;proto";

service Greeter{
    rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
    string name = 1;
}
message HelloReply {
    string message = 1;
}
  1. server 端實現定義方法的調用,注意 server 實現(鴨子類型)
package main

import (
 "RpcLearn/grpc_test/proto"
 "context"
 "google.golang.org/grpc"
 "log"
 "net"
)

type Server struct {
 proto.UnimplementedGreeterServer // 👈 重點:嵌入這個類型
}

func (s *Server) SayHello(ctx context.Context, req *proto.HelloRequest) (*proto.HelloReply, error) {
 reply := &proto.HelloReply{
  Message: "Hello " + req.Name,
 }
 return reply, nil
}

func main() {
 g := grpc.NewServer()                     // 創建一個新的gRPC服務器
 proto.RegisterGreeterServer(g, &Server{}) // 註冊服務
 listener, err := net.Listen("tcp", ":9090")
 if err != nil {
  log.Fatalf("failed to listen: %v", err)
 }
 err = g.Serve(listener)
 if err != nil {
  log.Fatalf("failed to serve: %v", err)
 }
}
  1. 客戶端調用實現
package main

import (
 "RpcLearn/grpc_test/proto"
 "context"
 "fmt"
 "google.golang.org/grpc"
 "google.golang.org/grpc/credentials/insecure"
)

func main() {
 // Create a new gRPC server
 //conn, err := grpc.Dial("localhost:9090", grpc.WithInsecure())
 // 使用安全連接
 //creds := credentials.NewTLS(&tls.Config{InsecureSkipVerify: true})
 conn, err := grpc.NewClient("localhost:9090",
  //grpc.WithInsecure()
  grpc.WithTransportCredentials(insecure.NewCredentials()), // ✅ 新方式
 )
 if err != nil {
  panic(err)
 }
 defer conn.Close()
 c := proto.NewGreeterClient(conn)
 r, err := c.SayHello(context.Background(), &proto.HelloRequest{Name: "world"})
 if err != nil {
  panic(err)
 }
 fmt.Println(r.Message)

}

grpc 的 stream 方式(流模式)

  1. 簡單模式(simple RPC)
  2. 服務端的數據流
  3. 客戶端的數據流
  4. 雙向的

集中學習 grpc

Protobuf 類型Go 類型
doublefloat64
floatfloat32
int32int32
int64int64
uint32uint32
uint64uint64
sint32int32
sint64int64
fixed32uint32
fixed64uint64

默認值

默認值說明

在 Protobuf 中,不同字段類型有以下默認值:

  • 字符串類型(string):默認值是一個空字符串。
  • 字節類型(bytes):默認值是一個空的字節序列。
  • 布爾類型(bool):默認值是 false
  • 數值類型(數字類型):默認值爲 0
  • 枚舉類型:默認值是第一個定義的枚舉值,且必須是 0
  • 消息類型(message):默認值是未設置(即 null)。

注意事項

  1. 對於標量消息字段:一旦消息被解析後,無法區分字段是否被設置爲默認值(例如布爾值是否爲 false)還是字段根本未被設置。
  2. 布爾值的使用建議:避免定義布爾值的默認值爲 false,因爲可能會導致觸發無意的行爲。
  3. 標量消息的序列化:如果一個標量消息字段被設置爲默認值,該值不會被序列化傳輸。

可以通過參考 Generated Code Guide 來更詳細地瞭解 Protobuf 默認值的處理方式。


枚舉類型的定義

在需要爲消息類型的某個字段指定一組“預定義值序列”時,可以使用枚舉類型。每個枚舉值都對應一個整數值。

示例

下面是一個枚舉的定義示例:

message SearchRequest {
  enum Corpus {
    UNIVERSAL = 0;
    WEB = 1;
    IMAGES = 2;
    LOCAL = 3;
    NEWS = 4;
    PRODUCTS = 5;
    VIDEO = 6;
  }
  Corpus corpus = 1;
}
  • 服務端數據流(獲取實時數據)
  • 客戶端數據流(上報數據)
  • 雙向數據流
 syntax = "proto3";

option go_package = ".;proto";

service Greeter {
    rpc GetStream (StreamReqData) returns (stream StreamResData); // 服務端流模式
    rpc PutStream (stream StreamReqData) returns ( StreamResData); // 客戶端流模式
    rpc AllStream (stream StreamReqData) returns (stream StreamResData); // 雙向流模式
}

message StreamReqData {
    string data = 1;
}

message StreamResData {
    string message = 1;
}

編譯生成 go 的 protobuf

protoc --proto_path=. \
  --go_out=. \
  --go-grpc_out=. \
  ./helloworld.proto

服務端代碼

package main

import (
 "RpcLearn/stream_grpc_test/proto"
 "fmt"
 "google.golang.org/grpc"
 "log"
 "net"
 "sync"
 "time"
)

const PORT = ":50052"

type server struct {
 proto.UnimplementedGreeterServer // 👈 重點:嵌入這個類型
}

func (s server) GetStream(data *proto.StreamReqData, res proto.Greeter_GetStreamServer) error {
 //TODO implement me
 //panic("implement me")
 i := 0
 for {
  i++
  _ = res.Send(&proto.StreamResData{
   Message: fmt.Sprintf("%v", time.Now().Unix()),
  })
  time.Sleep(time.Second)
  if i >= 10 {
   break
  }
 }
 return nil
}

func (s server) PutStream(cliStr proto.Greeter_PutStreamServer) error {
 //TODO implement me
 //panic("implement me")//
 for {
  if a, err := cliStr.Recv(); err != nil {
   fmt.Println(err)
   break
  } else {
   fmt.Println(a)
  }

 }
 return nil
}

func (s server) AllStream(allStr proto.Greeter_AllStreamServer) error {
 //TODO implement me
 //panic("implement me")
 wg := sync.WaitGroup{}
 wg.Add(2)
 go func() {
  defer wg.Done()
  for {
   if a, err := allStr.Recv(); err != nil {
    fmt.Println(err)
    break
   } else {
    fmt.Println("客戶端發送的消息:", a)
   }
  }

 }()
 go func() {
  defer wg.Done()
  for {
   allStr.Send(&proto.StreamResData{
    Message: fmt.Sprintf("我是服務端數據:%v\n", time.Now().Unix()),
   })
   time.Sleep(time.Second)
  }

 }()
 wg.Wait()
 return nil
}

func (s *server) SomeRPC(ctx context.Context, in *pb.SomeRequest) (*pb.SomeResponse, error) {
    md, ok := metadata.FromIncomingContext(ctx)
    if (!ok) {
        // handle missing metadata
    }
    // do something with metadata
    return &pb.SomeResponse{}, nil
}

func main() {
 // 創建一個新的gRPC服務器
 g := grpc.NewServer()
 proto.RegisterGreeterServer(g, &server{}) // 註冊服務
 listener, err := net.Listen("tcp", PORT)
 if err != nil {
  log.Fatalf("failed to listen: %v", err)
 }
 err = g.Serve(listener)
 if err != nil {
  log.Fatalf("failed to serve: %v", err)
 }
}

客戶端代碼

package main

import (
 "RpcLearn/stream_grpc_test/proto"
 "context"
 "fmt"
 "google.golang.org/grpc"
 "google.golang.org/grpc/credentials/insecure"
 "sync"
 "time"
)

func main() {
 // Create a new gRPC server
 conn, err := grpc.("localhost:50052",
  //grpc.WithInsecure()
  grpc.WithTransportCredentials(insecure.NewCredentials()), // ✅ 新方式
 )
 if err != nil {
  panic(err)
 }
 defer conn.Close()
 c := proto.NewGreeterClient(conn)
 //res, _ := c.GetStream(context.Background(), &proto.StreamReqData{Data: "慕課網"})
 //for {
 // r, err := res.Recv()
 // if err != nil {
 //  break
 // }
 // fmt.Println(r)
 //}
 //// 客戶端流模式
 //putS, _ := c.PutStream(context.Background())
 //i := 0
 //for {
 // i++
 // putS.Send(&proto.StreamReqData{Data: fmt.Sprintf("慕課網%v", i)})
 // time.Sleep(time.Second)
 // if i >= 10 {
 //  break
 // }
 //}

 //雙向流模式
 allStr, _ := c.AllStream(context.Background())
 wg := sync.WaitGroup{}
 wg.Add(2)
 go func() {
  defer wg.Done()
  for {
   if a, err := allStr.Recv(); err != nil {
    fmt.Println(err)
    break
   } else {
    fmt.Println("我是客戶端:%v", a)
   }
  }
 }()
 go func() {
  defer wg.Done()
  i := 0
  for {
   i++
   allStr.Send(&proto.StreamReqData{Data: fmt.Sprintf("雙向的慕課網:%v", i)})
   time.Sleep(time.Second)
   if i >= 10 {
    break
   }
  }
 }()
 wg.Wait()
}

Protobuf 類型與 Go 類型對應關係(含編碼建議)

.proto Type描述說明Go Type
double浮點類型float64
float浮點類型float32
int32使用變長編碼,對負值效率低;如有可能爲負數,建議用 sint32 替代int32
int64使用變長編碼int64
uint32使用變長編碼uint32
uint64使用變長編碼uint64
sint32使用變長編碼,對負值更高效(比 uint32 更好)int32
sint64使用變長編碼,對負值更高效int64
fixed32始終佔 4 字節,適用於總是比 2²²⁸ 大的值,比 uint32 更高效uint32
fixed64始終佔 8 字節,適用於總是比 2²⁵⁶ 大的值,比 uint64 更高效uint64
sfixed32始終佔 4 字節int32
sfixed64始終佔 8 字節int64
bool布爾值bool
string必須是 UTF-8 或 ASCII 編碼的文本string
bytes可包含任意順序的字節數據[]byte

option go_package

編譯生成目錄的指定

如何在一個 proto 中引入其它 proto

import "base.proto"

import "google/protobuf/empty.proto"

嵌套的 message

枚舉類型

enum Gender{
  MALE = 0;
  FEMALE = 1;
}

map 類型 map<string,string> mp = 4

如何使用時間戳 google/protobuf/timestamp.proto

message HelloRequest {
  string name = 1; // 姓名 相當於文檔
  string url = 2;
  Gender g = 3;
  map<string, string> mp = 4;
  google.protobuf.Timestamp addTime = 5;
}

grpc 的更多功能

gRPC 讓我們可以像本地調用一樣實現遠程調用,對於每一次的 RPC 調用中,都可能會有一些有用的數據,而這些數據就可以通過 metadata 來傳遞。metadata 是以 key-value 的形式存儲數據的,其中 key 是 string 類型,而 value 是[]string,即一個字符串數組類型。metadata 使得 client 和 server 能夠爲對方提供關於本次調用的一些信息,就像一次 http 請求的 RequestHeader 和 ResponseHeader 一樣。http 中 header 的生命週期是一 http 請求,那麼 metadata 的生命週期就是一次 RPC 調用。

metadata

不是直接侵入到業務代碼中的

// 新建
type MD mp[string][]string
// 創建
// 第一種方式
md := metadata.New(map[string]string{"key1": "val1", "key2": "val2"})

// 第二種方式 key 不區分大小寫,會被統一轉成小寫。
md := metadata.Pairs(
    "key1", "val1",
    "key1", "val1-2", // "key1" will have map value []string{"val1", "val1-2"}
    "key2", "val2",
)

發送 metadata

md := metadata.Pairs("key", "val")

// 新建一個有 metadata 的 context
ctx := metadata.NewOutgoingContext(context.Background(), md)

// 單向 RPC
response, err := client.SomeRPC(ctx, someRequest)

接收 metadata

func (s *server) SomeRPC(ctx context.Context, in *pb.SomeRequest) (*pb.SomeResponse, error) {
    md, ok := metadata.FromIncomingContext(ctx)
    if !ok {
        // handle missing metadata
    }
    // do something with metadata
    return &pb.SomeResponse{}, nil
}

grpc 的攔截器

服務端和客戶端的攔截器

package main

import (
 "RpcLearn/grpc_test/proto"
 "context"
 "google.golang.org/grpc"
 "log"
 "net"
)

type Server struct {
 proto.UnimplementedGreeterServer // 👈 重點:嵌入這個類型
}

func (s *Server) SayHello(ctx context.Context, req *proto.HelloRequest) (*proto.HelloReply, error) {
 reply := &proto.HelloReply{
  Message: "Hello " + req.Name,
 }
 return reply, nil
}

func main() {
 interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
  log.Println("Request:接收到一個新的請求", req)
  resp, err = handler(ctx, req)
  if err != nil {
   log.Println("Error:", err)
  }
  log.Println("Response:處理這個新請求完成", resp)
  return resp, err
 }
 opt := grpc.UnaryInterceptor(interceptor)
 g := grpc.NewServer(opt)                  // 創建一個新的gRPC服務器
 proto.RegisterGreeterServer(g, &Server{}) // 註冊服務
 listener, err := net.Listen("tcp", ":9090")
 if err != nil {
  log.Fatalf("failed to listen: %v", err)
 }
 err = g.Serve(listener)
 if err != nil {
  log.Fatalf("failed to serve: %v", err)
 }
}
package main

import (
 "RpcLearn/grpc_test/proto"
 "context"
 "fmt"
 "google.golang.org/grpc"
 "google.golang.org/grpc/credentials/insecure"
 "time"
)

func main() {
 // Create a new gRPC server
 //conn, err := grpc.Dial("localhost:9090", grpc.WithInsecure())
 // 使用安全連接
 //creds := credentials.NewTLS(&tls.Config{InsecureSkipVerify: true})
 // 添加攔截器
 clientInterceptor := func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
  start := time.Now()
  err := invoker(ctx, method, req, reply, cc, opts...)
  fmt.Println("耗時", time.Since(start), err)
  return err
 }
 opt := grpc.WithUnaryInterceptor(clientInterceptor)
 conn, err := grpc.NewClient("localhost:9090",
  //grpc.WithInsecure()
  grpc.WithTransportCredentials(insecure.NewCredentials()), // ✅ 新方式
  opt,
 )
 if err != nil {
  panic(err)
 }
 defer conn.Close()
 c := proto.NewGreeterClient(conn)
 r, err := c.SayHello(context.Background(), &proto.HelloRequest{Name: "world"})
 if err != nil {
  panic(err)
 }
 fmt.Println(r.Message)

}

grpc 通過 metadata 來驗證登錄

// server
package main

import (
 "RpcLearn/grpc_test/proto"
 "context"
 "google.golang.org/grpc"
 "google.golang.org/grpc/codes"
 "google.golang.org/grpc/metadata"
 "google.golang.org/grpc/status"
 "log"
 "net"
)

type Server struct {
 proto.UnimplementedGreeterServer // 👈 重點:嵌入這個類型
}

func (s *Server) SayHello(ctx context.Context, req *proto.HelloRequest) (*proto.HelloReply, error) {
 reply := &proto.HelloReply{
  Message: "Hello " + req.Name,
 }
 return reply, nil
}

func main() {
 interceptor := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
  log.Println("Request:接收到一個新的請求", req)
  md, ok := metadata.FromIncomingContext(ctx)
  if !ok {
   log.Println("沒有metadata")
   return resp, status.Error(codes.Unauthenticated, "沒有metadata")
  }

  var (
   appid  string
   appkey string
  )
  if val1, ok := md["appid"]; ok {
   appid = val1[0]
  }
  if val2, ok := md["appkey"]; ok {
   appkey = val2[0]
  }
  if appid != "1010101" || appkey != "123456" {
   return resp, status.Error(codes.Unauthenticated, "無token認證信息")
  }

  resp, err = handler(ctx, req)
  if err != nil {
   log.Println("Error:", err)
  }
  log.Println("Response:處理這個新請求完成", resp)
  return resp, err
 }
 opt := grpc.UnaryInterceptor(interceptor)
 g := grpc.NewServer(opt)                  // 創建一個新的gRPC服務器
 proto.RegisterGreeterServer(g, &Server{}) // 註冊服務
 listener, err := net.Listen("tcp", ":9090")
 if err != nil {
  log.Fatalf("failed to listen: %v", err)
 }
 err = g.Serve(listener)
 if err != nil {
  log.Fatalf("failed to serve: %v", err)
 }
}
package main

import (
 "RpcLearn/grpc_test/proto"
 "context"
 "fmt"
 "google.golang.org/grpc"
 "google.golang.org/grpc/credentials/insecure"
)

type customCredentials struct {
}

func (c *customCredentials) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
 return map[string]string{
  "appid":  "1010101",
  "appkey": "123456",
 }, nil
}
func (c *customCredentials) RequireTransportSecurity() bool {
 return false
}

func main() {

 // 添加攔截器
 //clientInterceptor := func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
 // start := time.Now()
 // //生成metadata
 // md := metadata.New(map[string]string{
 //  "appid":  "1010101",
 //  "appkey": "123456",
 // })
 // ctx = metadata.NewOutgoingContext(ctx, md)
 // err := invoker(ctx, method, req, reply, cc, opts...)
 // fmt.Println("耗時", time.Since(start), err)
 // return err
 //}
 //opt := grpc.WithUnaryInterceptor(clientInterceptor
 // 另一種方式
 opt := grpc.WithPerRPCCredentials(&customCredentials{}) // 有一個專門的metadata的攔截器
 conn, err := grpc.NewClient("localhost:9090",
  //grpc.WithInsecure()
  grpc.WithTransportCredentials(insecure.NewCredentials()), // ✅ 新方式
  opt,
 )
 if err != nil {
  panic(err)
 }
 defer conn.Close()
 c := proto.NewGreeterClient(conn)
 r, err := c.SayHello(context.Background(), &proto.HelloRequest{Name: "world"})
 if err != nil {
  panic(err)
 }
 fmt.Println(r.Message)

}

grpc(protoc-gen-validate)

protoc-gen-validate(簡稱 PGV)是一個 Protocol Buffers 插件,用於在生成的 Go 代碼中添加結構體字段的驗證邏輯。

它通過在 .proto 文件中添加 validate 規則,自動爲每個字段生成驗證代碼,避免你手動寫驗證邏輯。

syntax = "proto3";

import "validate/validate.proto";

message User {
  string name = 1 [(validate.rules).string.min_len = 3];
  int32 age = 2 [(validate.rules).int32.gte = 18];
}

查看protoc-gen-validate的簡介

grpc_proto_validate

syntax = "proto3";

import "validate.proto";

option go_package = ".;proto";

service Greeter {
  rpc SayHello (Person) returns (Person);
}

message Person {
  uint64 id = 1 [(validate.rules).uint64.gt = 999];

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

  string mobile = 3 [
    (validate.rules).string = {
      pattern: "^1[3-9]\\d{9}$"
    }
  ];
}

編譯這個 proto 添加一個--validate_out="lang=go" ,先要安裝go install github.com/bufbuild/protoc-gen-validate@latest

protoc \
  --proto_path=. \
  --go_out=. \
  --go-grpc_out=. \
  --validate_out=lang=go:. \
  ./helloworld.proto
package main

import (
 "RpcLearn/grpc_validate_test/proto"
 "context"
 "google.golang.org/grpc"
 "google.golang.org/grpc/codes"
 "google.golang.org/grpc/status"
 "log"
 "net"
)

type Server struct {
 proto.UnimplementedGreeterServer // 👈 重點:嵌入這個類型
}

func (s *Server) SayHello(ctx context.Context, request *proto.Person) (*proto.Person, error) {
 // Simulate some processing
 return &proto.Person{
  Id:     request.Id,
  Mobile: request.Mobile,
  Email:  request.Email,
 }, nil
}

type Validator interface {
 Validate() error
}

func main() {
 //p := new(proto.Person)
 //err := p.Validate()
 //if err != nil {
 // panic(err)
 //}
 var interceptor grpc.UnaryServerInterceptor
 interceptor = func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
  if v, ok := req.(Validator); ok {
   if err := v.Validate(); err != nil {
    return nil, status.Error(codes.InvalidArgument, err.Error())
   }
  }
  return handler(ctx, req)
 }
 opt := grpc.UnaryInterceptor(interceptor)
 g := grpc.NewServer(opt)                  // 創建一個新的gRPC服務器
 proto.RegisterGreeterServer(g, &Server{}) // 註冊服務
 listener, err := net.Listen("tcp", ":50051")
 if err != nil {
  log.Fatalf("failed to listen: %v", err)
 }
 err = g.Serve(listener)
 if err != nil {
  log.Fatalf("failed to serve: %v", err)
 }
}
package main

import (
 "RpcLearn/grpc_validate_test/proto"
 "context"
 "fmt"
 "google.golang.org/grpc"
 "google.golang.org/grpc/credentials/insecure"
 "time"
)

type customCredential struct{}

func main() {
 // 攔截器(可選)
 clientInterceptor := func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
  start := time.Now()
  err := invoker(ctx, method, req, reply, cc, opts...)
  fmt.Println("調用耗時:", time.Since(start), "錯誤:", err)
  return err
 }

 // 使用新方式連接(推薦)
 conn, err := grpc.NewClient("localhost:50051",
  grpc.WithTransportCredentials(insecure.NewCredentials()), // 替代 grpc.WithInsecure()
  grpc.WithUnaryInterceptor(clientInterceptor),
 )
 if err != nil {
  panic(err)
 }
 defer conn.Close()

 // 構建 gRPC 客戶端
 c := proto.NewGreeterClient(conn)

 // 調用 RPC
 rsp, err := c.SayHello(context.Background(), &proto.Person{
  Id:     1000,
  Email:  "bobby@test.com",
  Mobile: "13222223333",
 })
 if err != nil {
  panic(err)
 }

 fmt.Println("返回 ID:", rsp.Id)
}

grpc 中的異常處理

1. grpc 的狀態碼

grpc 狀態碼文檔


go 的異常處理

1. 服務端

st := status.New(codes.InvalidArgument, "invalid username")

2. 客戶端

st, ok := status.FromError(err)
if !ok {
    // Error was not a status error
}
st.Message()
st.Code()

超時機制

ctx,_ = content.WithTimeout(context.Background(),time.Second*3)