vue3+Ts中grpc-web的代码封装思路

接上一篇文章:添加链接描述基于Vue3+Ts+Vite项目中grpc-Web的应用以及其中的坑

继续更新一下grpc-web在前端vue项目中的封装思路,仅是个人封装思路,有更好的封装代码可以@我学习一下谢谢。
首先建议先看上一篇文章:添加链接描述基于Vue3+Ts+Vite项目中grpc-Web的应用以及其中的坑

正文开始

在使用 gRPC-Web 时,为了更好地管理和封装 gRPC 客户端调用,可以借助 TypeScript 提供的类型系统和面向对象的封装思想,编写一个通用的 gRPC 客户端管理模块。通过封装,将 gRPC 客户端的创建、请求数据的动态处理、响应的映射等逻辑提取出来,便于多次调用和维护。

代码结构

  1. GrpcClientManager.ts:gRPC 客户端管理器,负责创建和缓存 gRPC 客户端,避免重复实例化。
  2. GrpcService.ts:通用的 gRPC 调用服务,封装了对不同 gRPC 方法的动态调用逻辑,简化请求和响应的处理。
  3. CalculationService.ts:具体业务服务(如计算服务),通过 GrpcService 实现对 gRPC 接口的调用,业务逻辑集中在这里。

GrpcClientManager.ts

// services/GrpcClientManager.ts

class GrpcClientManager {
  // 静态变量,用于存储 gRPC 客户端,使用 Map 数据结构以支持多个不同服务的客户端
  private static clients: Map<string, any> = new Map()

  // 静态方法,用于获取或注册 gRPC 客户端
  public static getClient<T>(serviceName: string, clientFactory: () => T): T {
    // 如果客户端已存在,直接返回缓存的客户端
    if (this.clients.has(serviceName)) {
      return this.clients.get(serviceName) as T
    }

    // 如果客户端不存在,创建新客户端并注册到 clients Map 中
    const client = clientFactory()
    this.clients.set(serviceName, client)
    return client
  }
}

// 导出 GrpcClientManager 供其他模块使用
export default GrpcClientManager

作用

  • GrpcClientManager 管理所有的 gRPC 客户端实例,确保每个 gRPC 服务只会创建一个客户端,避免重复实例化,提高性能。
  • 使用 Map 数据结构来存储不同服务对应的客户端,支持按服务名称动态注册和获取客户端。

GrpcService.ts

// services/GrpcService.ts
import GrpcClientManager from './GrpcClientManager'

// 定义通用的 gRPC 响应接口
interface GrpcResponse<T = any> {
  data: T
  error?: any
}

class GrpcService {
  private client: any // 保存 gRPC 客户端实例

  constructor(private serviceName: string, clientFactory: () => any) {
    // 从 GrpcClientManager 获取 gRPC 客户端
    this.client = GrpcClientManager.getClient<any>(serviceName, clientFactory)
  }

  // 通用的 gRPC 调用方法封装
  public async grpcCall<TRequest, TResponse>(
    methodName: string, // gRPC 方法名称
    requestData: Record<string, any>, // 请求数据,键值对形式
    createRequest: () => TRequest, // 请求对象的创建函数
    responseMapper: (response: TResponse) => any, // 响应映射函数
  ): Promise<GrpcResponse<any>> {
    return new Promise((resolve, reject) => {
      const request = createRequest() // 创建请求对象
      // 动态设置请求数据,调用 gRPC 请求对象的 setter 方法
      Object.keys(requestData).forEach((key) => {
        const setter = `set${key.charAt(0).toUpperCase() + key.slice(1)}` // 生成 setter 方法名
        if (typeof (request as any)[setter] === 'function') {
          (request as any)[setter](requestData[key]) // 调用 setter 设置值
        } else {
          console.warn(`No setter found for key: ${key}`)
        }
      })

      // 获取 gRPC 客户端方法
      const clientMethod = this.client[methodName]
      if (!clientMethod) {
        return reject(new Error(`Method ${methodName} not found on client.`))
      }

      // 调用 gRPC 服务方法
      clientMethod.call(this.client, request, {}, (err: any, response: TResponse) => {
        if (err) {
          console.error('gRPC Error:', err)
          return reject(err)
        }

        // 通过映射器处理响应
        const mappedResponse = responseMapper(response)
        resolve({ data: mappedResponse })
      })
    })
  }
}

export default GrpcService

作用

  • GrpcService 封装了通用的 gRPC 调用逻辑,支持传入请求数据、动态生成 gRPC 请求对象、调用 gRPC 服务并处理响应。
  • grpcCall 方法通过动态构建 setter 方法来设置请求参数,并灵活处理服务响应。

CalculationService.ts

// services/CalculationService.ts
import * as api from '@generated/api_pb.js'
import { CalculationServiceClient } from '@generated/ApiServiceClientPb'
import GrpcService from '../GrpcService'

class CalculationService {
  private grpcService: GrpcService

  constructor() {
    // 初始化 gRPC 服务,创建计算服务的客户端
    this.grpcService = new GrpcService('CalculationService', () => new CalculationServiceClient('/api'))
  }

  // 处理加法操作
  public async add(data: { num1: number, num2: number }): Promise<number> {
    return this.grpcService.grpcCall<api.AddRequest, api.AddResponse>(
      'add', // gRPC 方法名
      data, // 动态输入数据
      () => new api.AddRequest(), // 请求对象生成函数
      response => response.getResult(), // 响应映射函数
    ).then(res => res.data).catch((err) => {
      console.error('Add error:', err)
      throw new Error('Failed to perform addition')
    })
  }

  // 处理减法操作
  public async subtract(data: { num1: number, num2: number }): Promise<number> {
    return this.grpcService.grpcCall<api.SubtractRequest, api.SubtractResponse>(
      'subtract', // gRPC 方法名
      data, // 请求数据
      () => new api.SubtractRequest(), // 创建请求对象
      response => response.getResult(), // 映射响应结果
    ).then(res => res.data).catch((err) => {
      console.error('Subtract error:', err)
      throw new Error('Failed to perform subtraction')
    })
  }
}

export default new CalculationService()

作用

  • CalculationService 封装了具体的加法和减法操作,通过 GrpcService 实现了 gRPC 服务的调用。
  • 加法和减法操作分别调用 GrpcService 提供的 grpcCall 方法,并对请求和响应进行处理。

调用示例

try {
  const response = await CalculationService.add({ num1: num1.value, num2: num2.value })
  result.value = response
  console.log('计算结果:', result.value)
} catch (err) {
  error.value = '计算出错!'
  console.error(err)
}

作用

  • 在 Vue 组件中,通过 CalculationServiceadd 方法调用 gRPC 服务进行加法计算,捕获异常并处理。

总结

通过这种封装方式,可以在 Vue3 + TypeScript 项目中高效地管理和调用 gRPC 服务:

  1. 客户端管理:通过 GrpcClientManager 集中管理 gRPC 客户端,避免重复创建。
  2. 通用服务GrpcService 封装了通用的 gRPC 调用逻辑,支持动态处理请求数据和响应映射。
  3. 具体业务实现:在 CalculationService 中封装业务逻辑,通过 GrpcService 实现具体的 gRPC 调用。
  4. 如果我们有多个proto文件生成的代码,就可以在创建一个CalculationService,新建一个modules包来管理它们 。
  5. 代码第一版是这样考虑的,还有许多需要优化的地方,交给时间。

番外

其实我是想创建一个流式调用的service,代码写好了之后调用发现没有proto自动生成的代码中无法生成stream的方法。这个还没研究出是咋回事。也没搜到相关解决方案。暂且搁置。

完结

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值