前言
微服务基于。net core,互联网上很多介绍都是基于类似的webapi并通过http request访问,但这并不符合人们的使用习惯。如果您担心如何以[getserviceorderservice()。saveorder (orderinfo)],本文可能是参考。
背景
最初的项目是基于传统的三层模型来组织代码逻辑。随着时间的推移,项目中各个模块的逻辑相互交织、相互依赖,难以维护。因此,我们需要引入一种新的机制来试图改变这种情况。在调研了一些微服务框架如Java Spring Cloud/DOUBBO、C # WCF/WebAPI/ASP之后。最后,我们选择了基于.网络核心。经讨论,最终预期项目成果如下。
但是原来的项目组成员已经习惯了这种基于接口服务的编码形式,所以让大家把所有要定义的接口都以http接口的形式重写定义。同时,客户端调用时,需要直接使用原来熟悉的表单,比如XXService。YYMethod(args1,args2)并通过“.”发送内部成员。让它以http client.post ('URL/xx/YY '," args1=11args2=22 ")的形式访问远程界面真的很痛苦。
提出的问题
基于以上,如何通过一种模式简化这种调用形式,使人们在调用时不需要关心服务是本地的(取决于本地类库)还是远程的,只需要用常规的方式使用即可。是直接使用本地服务,还是通过http发送远程请求,由框架决定。为了描述方便,本文假设以销售订单和用户服务为例。销售订单服务提供了创建订单的界面。成功创建订单后,将调用用户服务来更新用户。
问题转化
在客户端,通过微服务公开的接口生成一个接口代理,即将接口所需的信息[接口名称/方法名称和该方法所需的参数]打包成http请求发送给远程服务。在微服务的http访问部分,我们可以定义一个统一的门户。服务器收到请求后,解析出接口名称/方法名称和参数信息,并创建相应的实现类来执行接口请求。并通过http将返回值返回给客户端。最后,客户端访问远程服务,以类似于appruntimes . instance . getserviceorderservice()的形式创建订单。保存订单(orderinfo)。数据以json格式传输。解决方案和实施
为了方便处理,我们定义了一个空接口IApiService来标识服务接口。
远程服务客户端代理
公共类remote service proxy : iapiServiCe {公共字符串地址{ get设置;} //服务地址private APIactionresult post request(字符串接口id,字符串methodId,params object[]p){ APIactionresult APi retult=null;使用(var httpClient=new httpClient()){ var param=new ArrayList();//包装参数foreach(p中的var t){ if(t==null){ param .add(null);} else { var ns=t.GetType().命名空间;帕拉姆。添加(ns!=空ns .等于('系统')?t : JsonConvert .SerializeObject(t));} } var postContentStr=JsonConvert .SerializeObject(param);HttpContent HttpContent=新字符串内容(帖子内容字符串);if(CurrentRid!=Guid .空){ httpContent .标题。添加('用户名,当前用户名. ToString());} httpContent .标题。添加('企业标识,企业标识. ToString());httpContent .标题。内容类型=new MediaTypeHeaderValue(' application/JSON ');网址=地址TrimEnd('/')$ '/{接口id }/{方法id } ';大约例如。廖杰。调试($ ' Httprequest : { URL },数据: { POStentstr } ');定义变量响应=httpClient .postsync(网址,httpContent).结果;//提交请求if(!回应issuessstatuscode){ apprentimes .实例廖杰。错误($ ' Httprequest错误: { URL },statuscode: {响应.状态代码} ');引发新的ICVIPException(')网络异常或服务响应失败');} var responseStr=response .内容。ReadAsStringAsync().结果;大约例如。廖杰。调试($ ' Httprequest响应: { response str } ');apiRetult=JsonConvert .反序列化对象操作结果(responseStr);} if(!结果.issue cess){ 0抛出新的业务异常(apirettult .留言?'服务请求失败');}返回apiRetult} //有返回值的方法代理公共T invoke(字符串接口id,字符串methodId,params object[]param){ T RS=default(T);var apirettult=post ttprequest(接口id,methodId,param);尝试{ if(类型为(T)).命名空间==' System '){ RS=(T)TypeConvertutil .基本类型转换(类型为(T),默认。数据);} else { rs=JsonConvert .反序列化对象(转换ToString(ApireToult .数据));} } catch(Exception ex){ apprentimes .实例。日志。错误('数据转化失败,ex);扔;}返回RS;} //没有返回值的代理public void invoke withoutreturn(字符串接口id,字符串methodId,params object[]param){ post tprequest(接口id,methodId,param);}}远程服务端超文本传送协议(超文本传输协议的缩写)接入段统一入口
[路由(' API/SVC/{ interface id }/{ method id } ')、产品('应用程序/JSON ')]公共异步TaskApiActionResult Process(字符串接口id,字符串方法){ 0秒表秒表=新秒表();秒表start();ApiActionResult结果=null字符串reqParam=字符串。空的;尝试使用(变量阅读器=新的流阅读器(请求.正文,编码UTF8)) { reqParam=等待阅读器ReadToEndAsync();}评估时间例如。廖杰。debug($ ' recive client request : API/SVC/{ interface id }/{ method id },data : { req param } ');数组列表参数=nullif(!字符串isnullorhitespace(ReqParam)){//解析参数param=JsonConvert .反序列化对象数组列表(req param);} //转交本地服务处理中心处理var数据=LocalServiceExector .Exec(interfaceId,methodId,param);结果=ApiActionResult .成功(数据);} catch BusinessException ex) //业务异常{结果=ApiActionResult .错误(例如。消息);} catch(异常ex) { //业务异常如果(例如内部异常是业务异常){ result=ApiationResult .错误(例如内部异常。消息);} else { AppRuntimes .实例。日志。错误($ '调用服务发生异常{interfaceId}-{methodId},data:{reqParam} ',ex);结果=ApiActionResult .失败('服务发生异常');} }最后{秒表stop();大约。实例廖杰。调试($)处理客户端请求end : API/SVC/{接口id }/{方法id },耗时[{秒表elapsedminilisseconds }]毫秒');}//结果。消息=评估时间。实例GetCfgVal('服务器名')结果。消息;结果。消息=结果。消息;返回结果;}本地服务中心通过接口名和方法名,找出具体的实现类的方法,并使用传递的参数执行,ps:因为涉及到反射获取具体的方法,暂不支持相同参数个数的方法重载。仅支持不同参数个数的方法重载。
公共静态对象Exec(字符串接口Id,字符串methodId,ArrayList param){ var svcMethodInfo=getinstance和方法(接口id,methodId,param .计数);var currentmethod参数=new ArrayList();for(var I=0;i svcMethodInfo .参数。长度;I){ var tempparameter=svcMethodInfo .参数[一];if(param[I]==null){ current method参数.add(null);} else { if(!温度参数.参数类型。命名空间。等于('系统')||临时参数。参数类型。名称=='字节[]') { currentMethodParameters .添加(JsonConvert .反序列化对象(转换ToString(参数[i]),tempParamter .参数类型)} else { currentmethod参数.添加(TypeConvertUtil .base ctypecovertible(temp parameter .ParameterType,param[I]);} } }返回svcMethodInfo .调用(当前方法参数.ToArray());}私有静态实例方法dinfo getinstance和方法(字符串interfaceId,字符串methodId,int param count){ var method key=$ ' { interface id } _ { method id } _ { param count } ';if (methodCache .contains KeY(method KeY)){ 0返回方法缓存[方法键];} instance methodinfo temp=nullvar svcType=service factory .GetSvcType(interfaceId,true);if(SVctype==null){ 0引发新的ICViperception(美元)找不到应用程序接口接口的服务实现: {接口id } ');} var methods=svcType .GetMethods().其中(t=t.Name==methodId).to list();如果(方法IsNullEmpty()){ 0引发新的业务异常($ '在应用程序接口接口[{interfaceId}]的服务实现中[{svcType .全名}]找不到指定的方法: {方法id } ');} var方法=方法.first or default(t=t . GetParameters().长度==param count);if(方法==null){ 0引发新的ICViperception(美元)在应用程序接口接口中[{interfaceId}]的服务实现[{svcType .全名}]中,方法[{methodId}]参数个数不匹配');} var paramtersTypes=方法GetParameters();对象实例=null尝试{实例=激活器.创建实例(svcType);}捕获(例外情况){ 0抛出新的BusinessException($ '在实例化服务[{svcType}]发生异常,请确认其是否包含一个无参的构造函数,ex);} temp=new Instance Method info(){ Instance=Instance,InstanceType=svcType,Key=methodKey,Method=method,parameters=parameters type };if(!methodCache .包含密钥(方法密钥)){ lock(_ syncAddMethodCacheLocker){ if(!methodCache .包含键(方法键)){方法缓存.添加(methodKey,temp);} } }返回温度;服务配置,指示具体的服务的远程地址,当未配置的服务默认为本地服务。
[ { 'ServiceId': 'XZL .Api。IOrderService ',' Address ' : ' http://localhost :8801/API/SVC ' },{ 'ServiceId': 'XZL .Api。IUserService ',' Address ' : ' http://localhost :8802/API/SVC ' }]apprentime .实例GetServiceTService()的实现。
私有静态列表(字符串类型名,类型svcType)svcType;私有静态ConcurrentDictionarystring,Object svcininstance=new concurrentdictionary string,Object();公共静态t服务getservice t service(){ var service id=服务类型(t ).全名;//读取服务配置var服务信息=ServiceConFonfig .实例。GetServiceInfo(ServiCeid);if(ServiCeInfo==null){ return(t)ServiCe Activator .创建实例(GetSvcType(ServiCeid));} else { var RS=GetServiceTService(ServiCeid(ServiCeinfo .以色列远程?|远程' : ' '),服务信息.是单身);if (rs!=null rs为RemoteServiceProxy) { var temp=rs为RemoteServiceProxy在…之时地址=服务信息.地址;//指定服务地址}返回RS;} }公共静态t服务获取服务t服务(字符串interfaceId,bool isSingle){ //服务非单例模式if(!I单){退货(t服务)激活器.创建实例(GetSvcType(接口id));}对象obj=nullif (svcInstance .TryGetValue(interfaceId,out obj) obj!=null){ return(tServiCe)obj;} var svcType=GetSvcType(接口id);if(SVctype==null){ 0引发新的ICViperception(美元)系统中未找到[{interfaceId}]的代理类');} obj=激活器.创建实例(svcType);svcInstance .TryAdd(interfaceId,obj);返回(t服务)对象;}//获取服务的实现类公共静态类型GetSvcType(字符串接口波尔。isLocal=null){ if(!_ loaded){ LoadServiceType();}类型rs=空;var tempKey=interfaceIdvar temp=svctyptic .其中(x=x.typeName==tempKey).to list();if (temp==null || temp .count==0){ return RS;} if (isLocal .HasValue) { if (isLocal .值){ rs=温度FirstOrDefault(t=!类型(远程服务代理).IsAssignableFrom(t.svcType)).svcType} else { rs=temp .first ordefault(t=类型(RemoteServiceProxy)).IsAssignableFrom(t.svcType)).svcType} } else { rs=temp[0].svcType}返回RS;}为了性能影响,我们在程序启动的时候可以将当前所有的ApiService类型缓存。
public static void LoadServiceType(){ if(_ loaded){ return;} lock(_ sync){ if(_ loaded){ return;}请尝试{ svcTypeDic=新列表(字符串类型名,类型svcType();定义变量路径=AppDomain .当前域名。相对搜索路径?AppDomain .CurrentDomain。basedorityvar dir=new DirectoryInfo(路径);var文件=目录.GetFiles('XZL*).dll ');foreach(文件中的定义变量文件){ var types=LoadAssemblyFromFile(文件);svcTypeDic .AddRange(类型);} _ loaded=true } catch { _ loaded=false } } }//加载指定文件中的ApiService实现私有静态列表(字符串类型名,类型svcType) LoadAssemblyFromFile(文件信息文件){ var lst=新列表(字符串类型名,类型svcType();如果(文件。分机!='.“dll”文件。分机!='.exe '){ return lst;}请尝试{变量类型=程序集.加载(文件。名称。子字符串(0,文件。名称。长度- 4))。GetTypes().其中(c=c.IsClass!c . IsAbstract c . IsPublic);foreach(类型中的类型){ //客户端代理基类if(type==类型为(RemoteServiceProxy)){ continue;} if(!类型(IApiService).IsAssignableFrom(类型)){继续} //绑定现类一号.添加((类型。全名,类型));类型中的foreach (var interfaceType).GetInterfaces()) { if(!类型(IApiService).IsAssignableFrom(接口类型)){ 0继续;} //绑定接口与实际实现类一号.添加(((接口类型.全名,类型));} } } catch { } return lst}具体美国石油学会(美国石油协会)远程服务代理示例
公共类UserServiceProxy : remote serviceproxy,iuser服务{ private string ServiCeid=类型(iuser服务).全名;public void incrementescore(int userId,int score){ return invoke with trend(服务id,名称为(incremente score),userId,score);}公共用户信息GetUserById(int userId){返回InvokeUserInfo(服务Id,nameof(GetUserById),userId);}}结语
经过上述转换,我们可以通过AppRuntime的形式轻松访问远程服务。实例。GetServiceService()。Methodxx()。服务是远程部署还是以dll依赖关系的形式存在于本地对调用方来说是透明的。它与每个人的固有习惯无缝连接。
而PS:在这次改造之后,又落下了另一个问题。客户端调用远程服务,需要手动创建服务代理(从RemoteServicePro继承)。虽然每个代理都很容易写,只是本文提到的两个简单的句子,但毕竟繁琐。有没有办法根据远程api接口动态生成这个客户端代理?答案是肯定的,因为这篇文章很长,留在下一篇继续
附上动态编译文章的链接:https://www.jb51.net/article/144101.htm
好了,这就是本文的全部内容。希望本文的内容对你的学习或工作有一定的参考价值。有问题可以留言交流。谢谢你的支持。