【Golang】动态路由 WebSocket 消息到 gRPC 服务 - 【续】

实现的效果

1,通过配置过滤Websocket的某些方法
2,服务间grpc调用的时候,不关心是哪个服务,哪个链接,调用的方法是啥,只想统一处理,如:
ack, err := rpc.OnDirectCall(&ServerA.MethodA{Data1:data1})
ack, err := rpc.OnDirectCall(&ServerB.MethodB{Data1:data1})

全局函数映射表

首先,我们定义了一个全局的同步映射表gRpcFuncMap,用于存储所有注册的gRPC方法:

var gRpcFuncMap sync.Map

gRPC方法的元信息结构体

接下来,我们定义了一个结构体stGrpcMapBase,用于存储每个gRPC方法的元信息:

type stGrpcMapBase struct {
	funccall reflect.Value
	inParam  reflect.Type
	nameIn   string
	nameOut  string
}

gRPC服务结构体

然后,我们定义了另一个结构体stGrpcStruct,用于管理gRPC服务的函数映射和消息分发:

type stGrpcStruct struct {
	ServiceName string
	clientConn  *grpc.ClientConn
	fucMapping  sync.Map // map[string]*stGrpcMapBase
	dirMapping  sync.Map // map[string]*stGrpcMapBase
}

直接调用方法

我们实现了一个全局的直接调用方法OnDirectCall,用于通过全局映射表查找并调用相应的gRPC方法:

func OnDirectCall(req protoreflect.ProtoMessage) (interface{}, error) {
	method := string(req.ProtoReflect().Descriptor().FullName())
	if value, ok := gRpcFuncMap.Load(method); ok && value != nil {
		client := value.(*stGrpcStruct)
		return client.OnDirectCall(method, req)
	} else {
		bdlog.Error("OnDirectCall undefined : %v", req)
	}
	return nil, fmt.Errorf("OnDirectCall %v undefined", method)
}

函数映射初始化方法

initFuncMap方法用于初始化函数映射,通过读取配置文件和反射机制,将gRPC方法注册到映射表中:

func (st *stGrpcStruct) initFuncMap(client interface{}) bool {
	codeMap := make(map[string]int)
	filePtr, err := os.Open("swapper/" + st.ServiceName + ".json")
	if err == nil {
		decoder := json.NewDecoder(filePtr)
		if err = decoder.Decode(&codeMap); err != nil {
			bdlog.Error("initFuncMap failed : %s", err.Error())
			return false
		}
		defer filePtr.Close()
	}
	bdlog.Debug("initFuncMac name:%s", st.ServiceName)
	rValue := reflect.ValueOf(client)
	for i := 0; i < rValue.NumMethod(); i++ {
		nameIn := st.ServiceName + "." + rValue.Method(i).Type().In(1).Elem().Name()
		nameOut := st.ServiceName + "." + rValue.Method(i).Type().Out(0).Elem().Name()

		stBase := &stGrpcMapBase{}
		stBase.nameIn = nameIn
		stBase.nameOut = nameOut
		stBase.funccall = rValue.Method(i)
		stBase.inParam = rValue.Method(i).Type().In(1).Elem()
		st.dirMapping.Store(nameIn, stBase)
		if len(codeMap) <= 0 || (codeMap[nameIn] > 0 && codeMap[nameOut] > 0) {
			st.fucMapping.Store(nameIn, stBase)
		}
		gRpcFuncMap.Store(nameIn, st)
	}
	return true
}

消息分发处理方法

OnRecvDispatch方法用于处理接收到的消息并分发给相应的gRPC方法:

func (st *stGrpcStruct) OnRecvDispatch(method string, datas []byte) ([]byte, string, error) {
	var err error
	if value, ok := st.fucMapping.Load(method); ok {
		stbase := value.(*grpcMapBase)

		req := reflect.New(stbase.inParam).Interface()
		if err = proto.Unmarshal(datas, req.(protoreflect.ProtoMessage)); err != nil {
			goto endRecv
		}
		bdlog.Debug("OnRecvDispatch req %s: %v", method, req)
		ctx := authen.WithHeader(context.Background())
		ctx, cancel := context.WithTimeout(ctx, time.Second*30)
		defer cancel()

		args := []reflect.Value{reflect.ValueOf(ctx), reflect.ValueOf(req)}

		values := stbase.funccall.Call(args)

		ack := values[0].Interface()
		ackErr := values[1].Interface()
		err, _ = ackErr.(error)
		if err != nil { //优先判断是否错误
			bdlog.Error("OnRecvDispatch ack %s: %v, err:%v", method, ack, ackErr)
		} else if msg, ok := ack.(protoreflect.ProtoMessage); ok {
			if data, err := proto.Marshal(msg); err == nil {
				bdlog.Debug("OnRecvDispatch ack %s: %v", method, ack)
				return data, stbase.nameOut, nil
			} else {
				bdlog.Error("OnRecvDispatch ack %s: %v, err:%v", method, ack, ackErr)
			}
		}
	}
endRecv:
	if err != nil {
		bdlog.Error("OnRecvDispatch method:%s err:", method, err.Error())
		return []byte(st.ServiceName), "", err
	}
	return nil, "", nil
}

直接调用

我们还在stGrpcStruct结构体内实现了一个实例方法OnDirectCall,用于直接调用已注册的方法:

func (st *stGrpcStruct) OnDirectCall(name string, req interface{}, tickets ...*authen.Ticket) (interface{}, error) {
	if value, ok := st.dirMapping.Load(name); ok {
		stbase := value.(*grpcMapBase)
		bdlog.Debug("OnDirectCall req %s: %v", stbase.nameIn, req)
		ctx := authen.WithHeader(context.Background(), tickets...)
		ctx, cancel := context.WithTimeout(ctx, time.Second*30)
		defer cancel()
		args := []reflect.Value{reflect.ValueOf(ctx), reflect.ValueOf(req)}
		values := stbase.funccall.Call(args)
		ack := values[0].Interface() //rpc服务不应该空返回,有就修改
		ackErr := values[1].Interface()
		err, _ := ackErr.(error)
		bdlog.Debug("OnDirectCall ack %s: %v, err:%v", stbase.nameOut, ack, ackErr)
		return ack, err
	}
	bdlog.Error("OnDirectCall err: none method def:%f", name)
	return nil, fmt.Errorf("none method def:%s", name)
}

总结

希望本文能够为您在实际项目中应用反射机制提供一些有价值的参考。通过这种方式,您可以更加灵活地管理和调用gRPC服务,实现更加优雅的代码设计。

  • 12
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值