实现的效果
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服务,实现更加优雅的代码设计。