开发gRPC在程序之间实现远程服务调用

2025 年 9 月 7 日 星期日(已编辑)

开发gRPC在程序之间实现远程服务调用

本教程将指导您从零开始搭建一个完整的 gRPC 通信系统,使用 TypeScript 作为客户端,Go 作为服务端。

目录

  1. 环境准备
  2. 项目结构
  3. 步骤一:创建 Proto 文件
  4. 步骤二:搭建 Go 服务端
  5. 步骤三:搭建 TypeScript 客户端
  6. 步骤四:运行测试
  7. 故障排查

环境准备

必需软件

在开始之前,请确保已安装以下软件:

1. Node.js 和 npm

# 检查是否已安装
node --version  # 需要 v18 或更高版本
npm --version

如未安装,请访问 Node.js 官网 下载安装。

2. Go

# 检查是否已安装
go version  # 需要 1.20 或更高版本

如未安装,请访问 Go 官网 下载安装。

3. Protocol Buffers 编译器 (protoc)

# 检查是否已安装
protoc --version  # 需要 3.0 或更高版本

macOS 安装:

brew install protobuf

Ubuntu/Debian 安装:

sudo apt-get update
sudo apt-get install -y protobuf-compiler

Windows 安装:GitHub Releases 下载并配置环境变量。

4. Go 的 protoc 插件

go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

# 将 Go bin 目录添加到 PATH
export PATH="$PATH:$(go env GOPATH)/bin"

# 建议将上述命令添加到 ~/.bashrc 或 ~/.zshrc
echo 'export PATH="$PATH:$(go env GOPATH)/bin"' >> ~/.zshrc
source ~/.zshrc

5. TypeScript 的 grpc-tools

npm install -g grpc-tools grpc_tools_node_protoc_ts

验证安装

运行以下命令验证所有工具已正确安装:

node --version
npm --version
go version
protoc --version
protoc-gen-go --version
grpc_tools_node_protoc --version

项目结构

我们将创建以下目录结构:

grpc-demo/
├── proto/
│   └── demo.proto              # Protocol Buffers 定义文件
├── go-server/                  # Go 服务端
│   ├── main.go                 # 服务端主程序
│   ├── go.mod                  # Go 模块定义
│   └── proto/                  # 生成的 Go proto 文件
│       ├── demo.pb.go
│       └── demo_grpc.pb.go
└── ts-client/                  # TypeScript 客户端
    ├── package.json            # npm 配置文件
    ├── tsconfig.json           # TypeScript 配置
    ├── src/
    │   ├── client.ts           # 客户端主程序
    │   └── test.ts             # 测试程序
    └── generated/              # 生成的 TypeScript proto 文件
        ├── demo_pb.js
        ├── demo_pb.d.ts
        ├── demo_grpc_pb.js
        └── demo_grpc_pb.d.ts

步骤一:创建 Proto 文件

1. 创建项目根目录

mkdir grpc-demo
cd grpc-demo

2. 创建 proto 目录和文件

mkdir proto

创建文件 proto/demo.proto

syntax = "proto3";

package demo;

option go_package = "./proto;demo";

// CatService 定义了管理猫咪的服务
service CatService {
  // GetCat 根据 ID 获取猫咪信息
  rpc GetCat(CatRequest) returns (CatResponse);
  
  // ListCats 返回所有猫咪列表
  rpc ListCats(ListCatsRequest) returns (ListCatsResponse);
  
  // CreateCat 创建新的猫咪记录
  rpc CreateCat(CreateCatRequest) returns (CatResponse);
}

// Cat 表示猫咪实体
message Cat {
  int64 id = 1;
  string name = 2;
  string breed = 3;
  int32 age = 4;
  string color = 5;
}

// CatRequest 用于请求特定 ID 的猫咪
message CatRequest {
  int64 id = 1;
}

// CatResponse 返回猫咪信息
message CatResponse {
  Cat cat = 1;
  string message = 2;
}

// ListCatsRequest 用于请求猫咪列表
message ListCatsRequest {
  int32 limit = 1;
  int32 offset = 2;
}

// ListCatsResponse 返回猫咪列表
message ListCatsResponse {
  repeated Cat cats = 1;
  int32 total = 2;
}

// CreateCatRequest 用于创建新猫咪
message CreateCatRequest {
  string name = 1;
  string breed = 2;
  int32 age = 3;
  string color = 4;
}

步骤二:搭建 Go 服务端

1. 创建 Go 服务端目录

mkdir go-server
cd go-server

2. 初始化 Go 模块

go mod init grpc-demo

3. 安装依赖

go get google.golang.org/grpc
go get google.golang.org/protobuf

4. 生成 Go 代码

protoc --go_out=. --go_opt=paths=source_relative \
       --go-grpc_out=. --go-grpc_opt=paths=source_relative \
       ../proto/demo.proto

执行后会在当前目录生成 proto/ 文件夹,包含:

  • demo.pb.go
  • demo_grpc.pb.go

5. 创建服务端主程序

创建文件 go-server/main.go

package main

import (
    "context"
    "fmt"
    "log"
    "net"
    "sync"

    pb "grpc-demo/proto"

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

// server 实现 CatService gRPC 服务
type server struct {
    pb.UnimplementedCatServiceServer
    cats   map[int64]*pb.Cat
    nextID int64
    mu     sync.RWMutex
}

// newServer 创建并初始化一个新的服务器,带有示例数据
func newServer() *server {
    s := &server{
        cats:   make(map[int64]*pb.Cat),
        nextID: 1,
    }
    
    // 添加一些示例猫咪数据
    s.cats[1] = &pb.Cat{
        Id:    1,
        Name:  "Whiskers",
        Breed: "波斯猫",
        Age:   3,
        Color: "白色",
    }
    s.cats[2] = &pb.Cat{
        Id:    2,
        Name:  "Shadow",
        Breed: "暹罗猫",
        Age:   5,
        Color: "黑色",
    }
    s.nextID = 3
    
    return s
}

// GetCat 根据 ID 获取猫咪
func (s *server) GetCat(ctx context.Context, req *pb.CatRequest) (*pb.CatResponse, error) {
    s.mu.RLock()
    defer s.mu.RUnlock()

    log.Printf("收到 GetCat 请求,ID: %d", req.GetId())

    cat, exists := s.cats[req.GetId()]
    if !exists {
        return nil, status.Errorf(codes.NotFound, "未找到 ID 为 %d 的猫咪", req.GetId())
    }

    return &pb.CatResponse{
        Cat:     cat,
        Message: "成功找到猫咪",
    }, nil
}

// ListCats 返回猫咪列表,支持分页
func (s *server) ListCats(ctx context.Context, req *pb.ListCatsRequest) (*pb.ListCatsResponse, error) {
    s.mu.RLock()
    defer s.mu.RUnlock()

    log.Printf("收到 ListCats 请求 - Limit: %d, Offset: %d", req.GetLimit(), req.GetOffset())

    var cats []*pb.Cat
    for _, cat := range s.cats {
        cats = append(cats, cat)
    }

    // 应用分页
    offset := int(req.GetOffset())
    limit := int(req.GetLimit())
    
    if limit == 0 {
        limit = 10 // 默认限制
    }
    
    total := len(cats)
    
    if offset >= total {
        return &pb.ListCatsResponse{
            Cats:  []*pb.Cat{},
            Total: int32(total),
        }, nil
    }
    
    end := offset + limit
    if end > total {
        end = total
    }
    
    paginatedCats := cats[offset:end]

    return &pb.ListCatsResponse{
        Cats:  paginatedCats,
        Total: int32(total),
    }, nil
}

// CreateCat 创建新的猫咪
func (s *server) CreateCat(ctx context.Context, req *pb.CreateCatRequest) (*pb.CatResponse, error) {
    s.mu.Lock()
    defer s.mu.Unlock()

    log.Printf("收到 CreateCat 请求 - Name: %s, Breed: %s", req.GetName(), req.GetBreed())

    // 验证输入
    if req.GetName() == "" {
        return nil, status.Error(codes.InvalidArgument, "猫咪名称不能为空")
    }

    newCat := &pb.Cat{
        Id:    s.nextID,
        Name:  req.GetName(),
        Breed: req.GetBreed(),
        Age:   req.GetAge(),
        Color: req.GetColor(),
    }

    s.cats[s.nextID] = newCat
    s.nextID++

    return &pb.CatResponse{
        Cat:     newCat,
        Message: "成功创建猫咪",
    }, nil
}

func main() {
    // 在 50051 端口创建 TCP 监听器
    lis, err := net.Listen("tcp", ":50051")
    if err != nil {
        log.Fatalf("监听失败: %v", err)
    }

    // 创建新的 gRPC 服务器
    grpcServer := grpc.NewServer()

    // 注册 CatService 实现
    pb.RegisterCatServiceServer(grpcServer, newServer())

    log.Println("🚀 gRPC 服务器正在 50051 端口运行...")
    fmt.Println("服务器准备接受连接")

    // 开始服务
    if err := grpcServer.Serve(lis); err != nil {
        log.Fatalf("服务启动失败: %v", err)
    }
}

6. 整理依赖

go mod tidy

7. 测试运行服务端

go run main.go

如果看到以下输出,说明服务端启动成功:

🚀 gRPC 服务器正在 50051 端口运行...
服务器准备接受连接

保持服务端运行,打开新的终端窗口继续下一步。

步骤三:搭建 TypeScript 客户端

1. 创建 TypeScript 客户端目录

在新的终端窗口中:

cd grpc-demo  # 确保在项目根目录
mkdir ts-client
cd ts-client

2. 初始化 npm 项目

npm init -y

3. 创建 package.json

编辑 package.json 文件,替换为以下内容:

{
  "name": "grpc-typescript-demo",
  "version": "1.0.0",
  "description": "TypeScript gRPC 客户端示例",
  "main": "dist/src/client.js",
  "scripts": {
    "proto:generate": "npm run proto:js && npm run proto:ts",
    "proto:js": "grpc_tools_node_protoc --plugin=protoc-gen-grpc=./node_modules/.bin/grpc_tools_node_protoc_plugin --js_out=import_style=commonjs,binary:./generated --grpc_out=grpc_js:./generated --proto_path=../proto ../proto/*.proto",
    "proto:ts": "grpc_tools_node_protoc --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts --ts_out=grpc_js:./generated --proto_path=../proto ../proto/*.proto",
    "build": "tsc",
    "start": "node dist/src/client.js",
    "test": "node dist/src/test.js",
    "dev": "ts-node src/client.ts",
    "dev:test": "ts-node src/test.ts"
  },
  "keywords": ["grpc", "typescript", "demo"],
  "author": "",
  "license": "MIT",
  "dependencies": {
    "@grpc/grpc-js": "^1.13.4",
    "@grpc/proto-loader": "^0.7.15",
    "@types/node": "^24.0.12",
    "typescript": "^5.8.3"
  },
  "devDependencies": {
    "grpc-tools": "^1.13.0",
    "grpc_tools_node_protoc_ts": "^5.3.3",
    "ts-node": "^10.9.2"
  }
}

4. 安装依赖

npm install

5. 创建 TypeScript 配置

创建文件 tsconfig.json

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "lib": ["ES2020"],
    "outDir": "./dist",
    "rootDir": "./",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "moduleResolution": "node"
  },
  "include": ["src/**/*", "generated/**/*"],
  "exclude": ["node_modules", "dist"]
}

6. 生成 TypeScript 代码

npm run proto:generate

执行后会在 generated/ 目录生成四个文件:

  • demo_pb.js
  • demo_pb.d.ts
  • demo_grpc_pb.js
  • demo_grpc_pb.d.ts

7. 创建客户端源代码目录

mkdir src

8. 创建客户端主程序

创建文件 src/client.ts

import * as grpc from '@grpc/grpc-js';
import { CatServiceClient } from '../generated/demo_grpc_pb';
import {
  CatRequest,
  CatResponse,
  ListCatsRequest,
  ListCatsResponse,
  CreateCatRequest,
  Cat
} from '../generated/demo_pb';

/**
 * CatClient 封装 gRPC 客户端以便更容易使用
 */
export class CatClient {
  private client: CatServiceClient;

  constructor(serverAddress: string, credentials?: grpc.ChannelCredentials) {
    this.client = new CatServiceClient(
      serverAddress,
      credentials ?? grpc.ChannelCredentials.createInsecure()
    );
  }

  /**
   * 根据 ID 获取猫咪
   */
  public async getCat(id: number): Promise<Cat.AsObject> {
    return new Promise((resolve, reject) => {
      const request = new CatRequest();
      request.setId(id);

      this.client.getCat(request, (err, response) => {
        if (err) {
          reject(err);
          return;
        }

        const cat = response.getCat();
        if (!cat) {
          reject(new Error('响应中没有找到猫咪'));
          return;
        }

        console.log(`✅ ${response.getMessage()}`);
        resolve(cat.toObject());
      });
    });
  }

  /**
   * 获取所有猫咪列表,支持分页
   */
  public async listCats(limit: number = 10, offset: number = 0): Promise<ListCatsResponse.AsObject> {
    return new Promise((resolve, reject) => {
      const request = new ListCatsRequest();
      request.setLimit(limit);
      request.setOffset(offset);

      this.client.listCats(request, (err, response) => {
        if (err) {
          reject(err);
          return;
        }

        resolve(response.toObject());
      });
    });
  }

  /**
   * 创建新的猫咪
   */
  public async createCat(
    name: string,
    breed: string,
    age: number,
    color: string
  ): Promise<Cat.AsObject> {
    return new Promise((resolve, reject) => {
      const request = new CreateCatRequest();
      request.setName(name);
      request.setBreed(breed);
      request.setAge(age);
      request.setColor(color);

      this.client.createCat(request, (err, response) => {
        if (err) {
          reject(err);
          return;
        }

        const cat = response.getCat();
        if (!cat) {
          reject(new Error('响应中没有找到猫咪'));
          return;
        }

        console.log(`✅ ${response.getMessage()}`);
        resolve(cat.toObject());
      });
    });
  }

  /**
   * 关闭 gRPC 连接
   */
  public close(): void {
    this.client.close();
  }
}

// 示例用法
async function main() {
  const client = new CatClient('localhost:50051');

  try {
    console.log('🐱 === 猫咪服务演示 ===\n');

    // 测试 1: 根据 ID 获取猫咪
    console.log('📋 测试 1: 根据 ID 获取猫咪');
    const cat = await client.getCat(1);
    console.log('猫咪详情:', cat);
    console.log('');

    // 测试 2: 列出所有猫咪
    console.log('📋 测试 2: 列出所有猫咪');
    const catsList = await client.listCats(10, 0);
    console.log(`猫咪总数: ${catsList.total}`);
    catsList.catsList.forEach((c) => {
      console.log(`  - ${c.name} (${c.breed}), 年龄: ${c.age}, 颜色: ${c.color}`);
    });
    console.log('');

    // 测试 3: 创建新猫咪
    console.log('📋 测试 3: 创建新猫咪');
    const newCat = await client.createCat('Fluffy', '缅因猫', 2, '橙色');
    console.log('新创建的猫咪:', newCat);
    console.log('');

    // 测试 4: 验证新猫咪已创建
    console.log('📋 测试 4: 验证新猫咪在列表中');
    const updatedList = await client.listCats(10, 0);
    console.log(`创建后的猫咪总数: ${updatedList.total}`);
    console.log('');

    console.log('✅ 所有测试完成!');
  } catch (error: any) {
    console.error('❌ 错误:', error.message);
    if (error.code) {
      console.error('错误代码:', error.code);
    }
  } finally {
    client.close();
  }
}

// 运行示例
if (require.main === module) {
  main();
}

9. 创建测试程序

创建文件 src/test.ts

import { CatClient } from './client';

/**
 * CatClient 综合测试套件
 */
class CatServiceTester {
  private client: CatClient;

  constructor(serverAddress: string) {
    this.client = new CatClient(serverAddress);
  }

  async runAllTests(): Promise<void> {
    console.log('🧪 开始猫咪服务测试套件\n');
    console.log('=' .repeat(50));

    try {
      await this.testGetCat();
      await this.testListCats();
      await this.testCreateCat();
      await this.testErrorHandling();
      await this.testPagination();

      console.log('\n' + '='.repeat(50));
      console.log('✅ 所有测试通过!');
    } catch (error: any) {
      console.error('\n❌ 测试套件失败:', error.message);
      throw error;
    } finally {
      this.client.close();
    }
  }

  private async testGetCat(): Promise<void> {
    console.log('\n🧪 测试: GetCat');
    console.log('-'.repeat(50));

    try {
      const cat = await this.client.getCat(1);
      console.log(`✓ 成功获取猫咪: ${cat.name}`);
      console.log(`  品种: ${cat.breed}, 年龄: ${cat.age}, 颜色: ${cat.color}`);

      if (!cat.id || !cat.name) {
        throw new Error('接收到的猫咪数据无效');
      }
    } catch (error: any) {
      console.error('✗ GetCat 测试失败:', error.message);
      throw error;
    }
  }

  private async testListCats(): Promise<void> {
    console.log('\n🧪 测试: ListCats');
    console.log('-'.repeat(50));

    try {
      const result = await this.client.listCats(10, 0);
      console.log(`✓ 获取到 ${result.catsList.length} 只猫咪 (总数: ${result.total})`);
      
      result.catsList.forEach((cat, index) => {
        console.log(`  ${index + 1}. ${cat.name} - ${cat.breed}`);
      });

      if (result.total < 1) {
        throw new Error('期望列表中至少有一只猫咪');
      }
    } catch (error: any) {
      console.error('✗ ListCats 测试失败:', error.message);
      throw error;
    }
  }

  private async testCreateCat(): Promise<void> {
    console.log('\n🧪 测试: CreateCat');
    console.log('-'.repeat(50));

    try {
      const newCat = await this.client.createCat(
        'TestCat',
        '英国短毛猫',
        4,
        '灰色'
      );
      
      console.log(`✓ 创建新猫咪: ${newCat.name} (ID: ${newCat.id})`);
      console.log(`  品种: ${newCat.breed}, 年龄: ${newCat.age}, 颜色: ${newCat.color}`);

      if (!newCat.id || !newCat.name) {
        throw new Error('创建后的猫咪数据无效');
      }
    } catch (error: any) {
      console.error('✗ CreateCat 测试失败:', error.message);
      throw error;
    }
  }

  private async testErrorHandling(): Promise<void> {
    console.log('\n🧪 测试: 错误处理');
    console.log('-'.repeat(50));

    try {
      // 尝试获取不存在的猫咪 ID
      await this.client.getCat(99999);
      throw new Error('期望对无效猫咪 ID 抛出错误');
    } catch (error: any) {
      if (error.message.includes('未找到') || error.message.includes('not found')) {
        console.log('✓ 错误处理对无效 ID 正常工作');
      } else {
        console.error('✗ 未预期的错误:', error.message);
        throw error;
      }
    }
  }

  private async testPagination(): Promise<void> {
    console.log('\n🧪 测试: 分页');
    console.log('-'.repeat(50));

    try {
      // 第一页
      const page1 = await this.client.listCats(2, 0);
      console.log(`✓ 第 1 页: 获取到 ${page1.catsList.length} 只猫咪`);

      // 第二页
      const page2 = await this.client.listCats(2, 2);
      console.log(`✓ 第 2 页: 获取到 ${page2.catsList.length} 只猫咪`);

      if (page1.catsList.length > 2 || page2.catsList.length > 2) {
        throw new Error('分页限制未生效');
      }

      console.log('✓ 分页功能正常工作');
    } catch (error: any) {
      console.error('✗ 分页测试失败:', error.message);
      throw error;
    }
  }
}

// 运行测试
async function main() {
  const tester = new CatServiceTester('localhost:50051');
  await tester.runAllTests();
}

if (require.main === module) {
  main().catch((error) => {
    console.error('致命错误:', error);
    process.exit(1);
  });
}

10. 编译 TypeScript 代码

npm run build

编译成功后会在 dist/ 目录生成 JavaScript 文件。

步骤四:运行测试

1. 确保 Go 服务端正在运行

在第一个终端窗口(go-server 目录):

go run main.go

应该看到:

🚀 gRPC 服务器正在 50051 端口运行...
服务器准备接受连接

2. 运行 TypeScript 客户端

在第二个终端窗口(ts-client 目录):

npm start

预期输出:

🐱 === 猫咪服务演示 ===

📋 测试 1: 根据 ID 获取猫咪
✅ 成功找到猫咪
猫咪详情: { id: 1, name: 'Whiskers', breed: '波斯猫', age: 3, color: '白色' }

📋 测试 2: 列出所有猫咪
猫咪总数: 2
  - Whiskers (波斯猫), 年龄: 3, 颜色: 白色
  - Shadow (暹罗猫), 年龄: 5, 颜色: 黑色

📋 测试 3: 创建新猫咪
✅ 成功创建猫咪
新创建的猫咪: { id: 3, name: 'Fluffy', breed: '缅因猫', age: 2, color: '橙色' }

📋 测试 4: 验证新猫咪在列表中
创建后的猫咪总数: 3

✅ 所有测试完成!

3. 运行完整测试套件

npm run test

预期输出:

🧪 开始猫咪服务测试套件

==================================================

🧪 测试: GetCat
--------------------------------------------------
✅ 成功找到猫咪
✓ 成功获取猫咪: Whiskers
  品种: 波斯猫, 年龄: 3, 颜色: 白色

🧪 测试: ListCats
--------------------------------------------------
✓ 获取到 2 只猫咪 (总数: 2)
  1. Whiskers - 波斯猫
  2. Shadow - 暹罗猫

🧪 测试: CreateCat
--------------------------------------------------
✅ 成功创建猫咪
✓ 创建新猫咪: TestCat (ID: 3)
  品种: 英国短毛猫, 年龄: 4, 颜色: 灰色

🧪 测试: 错误处理
--------------------------------------------------
✓ 错误处理对无效 ID 正常工作

🧪 测试: 分页
--------------------------------------------------
✓ 第 1 页: 获取到 2 只猫咪
✓ 第 2 页: 获取到 1 只猫咪
✓ 分页功能正常工作

==================================================
✅ 所有测试通过!

故障排查

问题 1: "protoc: command not found"

解决方案:

# macOS
brew install protobuf

# Ubuntu/Debian
sudo apt-get install -y protobuf-compiler

# 验证安装
protoc --version

问题 2: "protoc-gen-go: program not found or is not executable"

解决方案:

# 安装 Go 插件
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

# 添加到 PATH
export PATH="$PATH:$(go env GOPATH)/bin"

# 永久添加到 shell 配置
echo 'export PATH="$PATH:$(go env GOPATH)/bin"' >> ~/.zshrc
source ~/.zshrc

问题 3: "Cannot find module '../generated/demogrpcpb'"

解决方案:

cd ts-client
npm run proto:generate
npm run build

问题 4: "Connection refused" 或连接失败

解决方案:

  1. 确保 Go 服务端正在运行
  2. 检查端口 50051 是否被占用:
# macOS/Linux
lsof -i :50051

# 如果端口被占用,杀死进程
lsof -ti:50051 | xargs kill -9

问题 5: Go 模块相关问题

解决方案:

cd go-server
rm -rf go.mod go.sum
go mod init grpc-demo
go get google.golang.org/grpc
go get google.golang.org/protobuf
go mod tidy

问题 6: npm 依赖安装失败

解决方案:

cd ts-client
rm -rf node_modules package-lock.json
npm cache clean --force
npm install

问题 7: TypeScript 编译错误

解决方案:

tsconfig.json 中添加:

{
  "compilerOptions": {
    "skipLibCheck": true
  }
}

然后重新编译:

npm run build

快速命令参考

Go 服务端命令

cd go-server

# 生成 proto 文件
protoc --go_out=. --go_opt=paths=source_relative \
       --go-grpc_out=. --go-grpc_opt=paths=source_relative \
       ../proto/demo.proto

# 下载依赖
go mod download
go mod tidy

# 运行服务端
go run main.go

# 编译二进制文件
go build -o server main.go
./server

TypeScript 客户端命令

cd ts-client

# 安装依赖
npm install

# 生成 proto 文件
npm run proto:generate

# 编译 TypeScript
npm run build

# 运行客户端
npm start

# 运行测试
npm run test

# 开发模式(不需要编译)
npm run dev
npm run dev:test

# 清理生成的文件
rm -rf dist generated

项目扩展建议

1. 添加新的 RPC 方法

  • 编辑 proto/demo.proto 添加新的 service 方法
  • 重新生成代码(Go 和 TypeScript)
  • go-server/main.go 中实现方法
  • ts-client/src/client.ts 中添加客户端方法

2. 实现流式 RPC

gRPC 支持四种类型的 RPC:

  • 简单 RPC(本教程使用的)
  • 服务端流式 RPC
  • 客户端流式 RPC
  • 双向流式 RPC

3. 添加认证和授权

在生产环境中,建议使用 SSL/TLS 和认证机制。

4. 添加中间件

  • 日志记录
  • 指标收集
  • 错误处理
  • 请求追踪

5. 健康检查

实现 gRPC 健康检查协议,方便服务发现和负载均衡。

性能优化建议

  1. 连接池:复用 gRPC 连接
  2. 超时设置:为每个 RPC 调用设置合理的超时
  3. 重试机制:实现智能重试策略
  4. 负载均衡:使用 gRPC 的负载均衡功能
  5. 压缩:启用 gRPC 消息压缩

相关资源

总结

恭喜!🎉 您已经成功创建了一个完整的 gRPC 通信系统:

  • ✅ 使用 Protocol Buffers 定义服务接口
  • ✅ 实现了 Go gRPC 服务端
  • ✅ 实现了 TypeScript gRPC 客户端
  • ✅ 完成了端到端测试

这个示例展示了 gRPC 的基本用法,您可以在此基础上:

  1. 添加更多的服务方法
  2. 实现流式 RPC
  3. 添加认证和安全机制
  4. 集成到实际项目中

如果遇到任何问题,请参考故障排查部分,或查阅官方文档。

使用社交账号登录

  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • Loading...