最新文章专题视频专题问答1问答10问答100问答1000问答2000关键字专题1关键字专题50关键字专题500关键字专题1500TAG最新视频文章推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37视频文章20视频文章30视频文章40视频文章50视频文章60 视频文章70视频文章80视频文章90视频文章100视频文章120视频文章140 视频2关键字专题关键字专题tag2tag3文章专题文章专题2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章专题3
当前位置: 首页 - 科技 - 知识百科 - 正文

基于.net core微服务的另一种实现方法

来源:动视网 责编:小采 时间:2020-11-27 22:34:57
文档

基于.net core微服务的另一种实现方法

基于.net core微服务的另一种实现方法:前言 基于.net core 的微服务,网上很多介绍都是千篇一律基于类似webapi,通过http请求形式进行访问,但这并不符合大家使用习惯.如何像形如[ GetService<IOrderService>().SaveOrder(orderInfo)]的方式, 调用远程的服务,如果你正在为此苦
推荐度:
导读基于.net core微服务的另一种实现方法:前言 基于.net core 的微服务,网上很多介绍都是千篇一律基于类似webapi,通过http请求形式进行访问,但这并不符合大家使用习惯.如何像形如[ GetService<IOrderService>().SaveOrder(orderInfo)]的方式, 调用远程的服务,如果你正在为此苦


前言

基于.net core 的微服务,网上很多介绍都是千篇一律基于类似webapi,通过http请求形式进行访问,但这并不符合大家使用习惯.如何像形如[ GetService<IOrderService>().SaveOrder(orderInfo)]的方式, 调用远程的服务,如果你正在为此苦恼, 本文或许是一种参考.

背景

原项目基于传统三层模式组织代码逻辑,随着时间的推移,项目内各模块逻辑互相交织,互相依赖,维护起来较为困难.为此我们需要引入一种新的机制来尝试改变这个现状,在考察了 Java spring cloud/doubbo, c# wcf/webapi/asp.net core 等一些微服务框架后,我们最终选择了基于 .net core + Ocelot 微服务方式. 经过讨论大家最终期望的项目结果大致如下所示.

但原项目团队成员已经习惯了基于接口服务的这种编码形式, 让大家将需要定义的接口全部以http 接口形式重写定义一遍, 同时客户端调用的时候, 需要将原来熟悉的形如 XXService.YYMethod(args1, args2) 直接使用通过 "."出内部成员,替换为让其直接写 HttpClient.Post("url/XX/YY",”args1=11&args2=22”)的形式访问远程接口,确实是一件十分痛苦的事情.

问题提出

基于以上, 如何通过一种模式来简化这种调用形式, 继而使大家在调用的时候不需要关心该服务是在本地(本地类库依赖)还是远程, 只需要按照常规方式使用即可, 至于是直接使用本地服务还是通过http发送远程请求,这个都交给框架处理.为了方便叙述, 本文假定以销售订单和用户服务为例. 销售订单服务对外提供一个创建订单的接口.订单创建成功后, 调用用户服务更新用户积分.UML参考如下.


问题转化

  • 在客户端,通过微服务对外公开的接口,生成接口代理, 即将接口需要的信息[接口名/方法名及该方法需要的参数]包装成http请求向远程服务发起请求.
  • 在微服务http接入段, 我们可以定义一个统一的入口,当服务端收到请求后,解析出接口名/方法名及参数信息,并创建对应的实现类,从而执行接口请求,并将返回值通过http返回给客户端.
  • 最后,客户端通过类似 AppRuntims.Instance.GetService<IOrderService>().SaveOrder(orderInfo) 形式访问远程服务创建订单.
  • 数据以json格式传输.
  • 解决方案及实现

    为了便于处理,我们定义了一个空接口IApiService,用来标识服务接口.

    远程服务客户端代理

    public class RemoteServiceProxy : IApiService
    {
     public string Address { get; set; } //服务地址private ApiActionResult PostHttpRequest(string interfaceId, string methodId, params object[] p)
     {
     ApiActionResult apiRetult = null;
     using (var httpClient = new HttpClient())
     {
     var param = new ArrayList(); //包装参数
    
     foreach (var t in p)
     {
     if (t == null)
     {
     param.Add(null);
     }
     else
     {
     var ns = t.GetType().Namespace;
     param.Add(ns != null && ns.Equals("System") ? t : JsonConvert.SerializeObject(t));
     }
     }
     var postContentStr = JsonConvert.SerializeObject(param);
     HttpContent httpContent = new StringContent(postContentStr);
     if (CurrentUserId != Guid.Empty)
     {
     httpContent.Headers.Add("UserId", CurrentUserId.ToString());
     }
     httpContent.Headers.Add("EnterpriseId", EnterpriseId.ToString());
     httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
    
     var url = Address.TrimEnd('/') + $"/{interfaceId}/{methodId}";
     AppRuntimes.Instance.Loger.Debug($"httpRequest:{url},data:{postContentStr}");
    
     var response = httpClient.PostAsync(url, httpContent).Result; //提交请求
    
     if (!response.IsSuccessStatusCode)
     {
     AppRuntimes.Instance.Loger.Error($"httpRequest error:{url},statuscode:{response.StatusCode}");
     throw new ICVIPException("网络异常或服务响应失败");
     }
     var responseStr = response.Content.ReadAsStringAsync().Result;
     AppRuntimes.Instance.Loger.Debug($"httpRequest response:{responseStr}");
    
     apiRetult = JsonConvert.DeserializeObject<ApiActionResult>(responseStr);
     }
     if (!apiRetult.IsSuccess)
     {
     throw new BusinessException(apiRetult.Message ?? "服务请求失败");
     }
     return apiRetult;
     }
    
     //有返回值的方法代理
     public T Invoke<T>(string interfaceId, string methodId, params object[] param)
     {
     T rs = default(T);
    
     var apiRetult = PostHttpRequest(interfaceId, methodId, param);
    
     try
     {
     if (typeof(T).Namespace == "System")
     {
     rs = (T)TypeConvertUtil.BasicTypeConvert(typeof(T), apiRetult.Data);
     }
     else
     {
     rs = JsonConvert.DeserializeObject<T>(Convert.ToString(apiRetult.Data));
     }
     }
     catch (Exception ex)
     {
     AppRuntimes.Instance.Loger.Error("数据转化失败", ex);
     throw;
     }
     return rs;
     }
    
     //没有返回值的代理
     public void InvokeWithoutReturn(string interfaceId, string methodId, params object[] param)
     {
     PostHttpRequest(interfaceId, methodId, param);
     }
    }

    远程服务端http接入段统一入口

    [Route("api/svc/{interfaceId}/{methodId}"), Produces("application/json")]
    public async Task<ApiActionResult> Process(string interfaceId, string methodId)
    {
     Stopwatch stopwatch = new Stopwatch();
     stopwatch.Start();
     ApiActionResult result = null;
     string reqParam = string.Empty;
     try
     {
     using (var reader = new StreamReader(Request.Body, Encoding.UTF8))
     {
     reqParam = await reader.ReadToEndAsync();
     }
     AppRuntimes.Instance.Loger.Debug($"recive client request:api/svc/{interfaceId}/{methodId},data:{reqParam}");
    
     ArrayList param = null;
     if (!string.IsNullOrWhiteSpace(reqParam))
     {
     //解析参数
     param = JsonConvert.DeserializeObject<ArrayList>(reqParam);
     } 
     //转交本地服务处理中心处理
     var data = LocalServiceExector.Exec(interfaceId, methodId, param);
     result = ApiActionResult.Success(data);
     }
     catch BusinessException ex) //业务异常
     {
     result = ApiActionResult.Error(ex.Message);
     }
     catch (Exception ex)
     {
     //业务异常
     if (ex.InnerException is BusinessException)
     {
     result = ApiActionResult.Error(ex.InnerException.Message);
     }
     else
     {
     AppRuntimes.Instance.Loger.Error($"调用服务发生异常{interfaceId}-{methodId},data:{reqParam}", ex);
     result = ApiActionResult.Fail("服务发生异常");
     }
     }
     finally
     {
     stopwatch.Stop();
     AppRuntimes.Instance.Loger.Debug($"process client request end:api/svc/{interfaceId}/{methodId},耗时[ {stopwatch.ElapsedMilliseconds} ]毫秒");
     }
     //result.Message = AppRuntimes.Instance.GetCfgVal("ServerName") + " " + result.Message;
     result.Message = result.Message;
     return result;
    }

    本地服务中心通过接口名和方法名,找出具体的实现类的方法,并使用传递的参数执行,ps:因为涉及到反射获取具体的方法,暂不支持相同参数个数的方法重载.仅支持不同参数个数的方法重载.

    public static object Exec(string interfaceId, string methodId, ArrayList param)
    {
     var svcMethodInfo = GetInstanceAndMethod(interfaceId, methodId, param.Count);
     var currentMethodParameters = new ArrayList();
    
     for (var i = 0; i < svcMethodInfo.Paramters.Length; i++)
     {
     var tempParamter = svcMethodInfo.Paramters[i];
    
     if (param[i] == null)
     {
     currentMethodParameters.Add(null);
     }
     else
     {
     if (!tempParamter.ParameterType.Namespace.Equals("System") || tempParamter.ParameterType.Name == "Byte[]")
     {
     currentMethodParameters.Add(JsonConvert.DeserializeObject(Convert.ToString(param[i]), tempParamter.ParameterType)
     }
     else
     {
     currentMethodParameters.Add(TypeConvertUtil.BasicTypeConvert(tempParamter.ParameterType, param[i]));
     }
     }
     }
    
     return svcMethodInfo.Invoke(currentMethodParameters.ToArray());
    }
    
    private static InstanceMethodInfo GetInstanceAndMethod(string interfaceId, string methodId, int paramCount)
    {
     var methodKey = $"{interfaceId}_{methodId}_{paramCount}";
     if (methodCache.ContainsKey(methodKey))
     {
     return methodCache[methodKey];
     }
     InstanceMethodInfo temp = null;
    
     var svcType = ServiceFactory.GetSvcType(interfaceId, true);
     if (svcType == null)
     {
     throw new ICVIPException($"找不到API接口的服务实现:{interfaceId}");
     }
     var methods = svcType.GetMethods().Where(t => t.Name == methodId).ToList();
     if (methods.IsNullEmpty())
     {
     throw new BusinessException($"在API接口[{interfaceId}]的服务实现中[{svcType.FullName}]找不到指定的方法:{methodId}");
     }
     var method = methods.FirstOrDefault(t => t.GetParameters().Length == paramCount);
     if (method == null)
     {
     throw new ICVIPException($"在API接口中[{interfaceId}]的服务实现[{svcType.FullName}]中,方法[{methodId}]参数个数不匹配");
     }
     var paramtersTypes = method.GetParameters();
    
     object instance = null;
     try
     {
     instance = Activator.CreateInstance(svcType);
     }
     catch (Exception ex)
     {
     throw new BusinessException($"在实例化服务[{svcType}]发生异常,请确认其是否包含一个无参的构造函数", ex);
     }
     temp = new InstanceMethodInfo()
     {
     Instance = instance,
     InstanceType = svcType,
     Key = methodKey,
     Method = method,
     Paramters = paramtersTypes
     };
     if (!methodCache.ContainsKey(methodKey))
     {
     lock (_syncAddMethodCacheLocker)
     {
     if (!methodCache.ContainsKey(methodKey))
     {
     methodCache.Add(methodKey, temp);
     }
     }
     }
     return temp;

    服务配置,指示具体的服务的远程地址,当未配置的服务默认为本地服务.

    [
     {
     "ServiceId": "XZL.Api.IOrderService",
     "Address": "http://localhost:8801/api/svc"
     },
     {
     "ServiceId": "XZL.Api.IUserService",
     "Address": "http://localhost:8802/api/svc"
     } 
    ]

    AppRuntime.Instance.GetService<TService>()的实现.

    private static List<(string typeName, Type svcType)> svcTypeDic;
    private static ConcurrentDictionary<string, Object> svcInstance = new ConcurrentDictionary<string, object>();
     
    public static TService GetService<TService>()
     {
     var serviceId = typeof(TService).FullName;
    
     //读取服务配置
     var serviceInfo = ServiceConfonfig.Instance.GetServiceInfo(serviceId);
     if (serviceInfo == null)
     {
     return (TService)Activator.CreateInstance(GetSvcType(serviceId));
     }
     else
     { 
     var rs = GetService<TService>(serviceId + (serviceInfo.IsRemote ? "|Remote" : ""), serviceInfo.IsSingle);
     if (rs != null && rs is RemoteServiceProxy)
     {
     var temp = rs as RemoteServiceProxy;
     temp.Address = serviceInfo.Address; //指定服务地址
     }
     return rs;
     }
     }
    public static TService GetService<TService>(string interfaceId, bool isSingle)
    {
     //服务非单例模式
     if (!isSingle)
     {
     return (TService)Activator.CreateInstance(GetSvcType(interfaceId));
     }
    
     object obj = null;
     if (svcInstance.TryGetValue(interfaceId, out obj) && obj != null)
     {
     return (TService)obj;
     }
     var svcType = GetSvcType(interfaceId);
    
     if (svcType == null)
     {
     throw new ICVIPException($"系统中未找到[{interfaceId}]的代理类");
     }
     obj = Activator.CreateInstance(svcType);
    
     svcInstance.TryAdd(interfaceId, obj);
     return (TService)obj;
    }
    
    //获取服务的实现类
    public static Type GetSvcType(string interfaceId, bool? isLocal = null)
    {
     if (!_loaded)
     {
     LoadServiceType();
     }
     Type rs = null;
     var tempKey = interfaceId;
    
     var temp = svcTypeDic.Where(x => x.typeName == tempKey).ToList();
    
     if (temp == null || temp.Count == 0)
     {
     return rs;
     }
    
     if (isLocal.HasValue)
     {
     if (isLocal.Value)
     {
     rs = temp.FirstOrDefault(t => !typeof(RemoteServiceProxy).IsAssignableFrom(t.svcType)).svcType;
     }
     else
     {
     rs = temp.FirstOrDefault(t => typeof(RemoteServiceProxy).IsAssignableFrom(t.svcType)).svcType;
     }
     }
     else
     {
     rs = temp[0].svcType;
     }
     return rs;
    }

    为了性能影响,我们在程序启动的时候可以将当前所有的ApiService类型缓存.

    public static void LoadServiceType()
     {
     if (_loaded)
     {
     return;
     }
     lock (_sync)
     {
     if (_loaded)
     {
     return;
     } 
     try
     {
     svcTypeDic = new List<(string typeName, Type svcType)>();
     var path = AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory;
     var dir = new DirectoryInfo(path);
     var files = dir.GetFiles("XZL*.dll");
     foreach (var file in files)
     { 
     var types = LoadAssemblyFromFile(file);
     svcTypeDic.AddRange(types);
     } 
     _loaded = true;
     }
     catch
     {
     _loaded = false;
     }
     }
     }
    
    //加载指定文件中的ApiService实现
    private static List<(string typeName, Type svcType)> LoadAssemblyFromFile(FileInfo file)
    {
     var lst = new List<(string typeName, Type svcType)>();
     if (file.Extension != ".dll" && file.Extension != ".exe")
     {
     return lst;
     }
     try
     {
     var types = Assembly.Load(file.Name.Substring(0, file.Name.Length - 4))
     .GetTypes()
     .Where(c => c.IsClass && !c.IsAbstract && c.IsPublic);
     foreach (Type type in types)
     {
     //客户端代理基类
     if (type == typeof(RemoteServiceProxy))
     {
     continue;
     }
    
     if (!typeof(IApiService).IsAssignableFrom(type))
     {
     continue;
     }
    
     //绑定现类
     lst.Add((type.FullName, type));
    
     foreach (var interfaceType in type.GetInterfaces())
     {
     if (!typeof(IApiService).IsAssignableFrom(interfaceType))
     {
     continue;
     } 
     //绑定接口与实际实现类
     lst.Add((interfaceType.FullName, type)); 
     }
     }
     }
     catch
     {
     }
    
     return lst;
    }

    具体api远程服务代理示例

    public class UserServiceProxy : RemoteServiceProxy, IUserService
     {
     private string serviceId = typeof(IUserService).FullName;
    
     public void IncreaseScore(int userId,int score)
     {
     return InvokeWithoutReturn(serviceId, nameof(IncreaseScore), userId,score);
     }
     public UserInfo GetUserById(int userId)
     {
     return Invoke<UserInfo >(serviceId, nameof(GetUserById), userId);
     }
    }

    结语

    经过以上改造后, 我们便可很方便的通过形如 AppRuntime.Instance.GetService<TService>().MethodXX()无感的访问远程服务, 服务是部署在远程还是在本地以dll依赖形式存在,这个便对调用者透明了.无缝的对接上了大家固有习惯.

    PS: 但是此番改造后, 遗留下来了另外一个问题: 客户端调用远程服务,需要手动创建一个服务代理( 从 RemoteServiceProxy 继承),虽然每个代理很方便写,只是文中提到的简单两句话,但终究显得繁琐, 是否有一种方式能够根据远程api接口动态的生成这个客户端代理呢? 答案是肯定的,因本文较长了,留在下篇再续

    附上动态编译文章链接:https://www.gxlcms.com/article/144101.htm

    好了,

    文档

    基于.net core微服务的另一种实现方法

    基于.net core微服务的另一种实现方法:前言 基于.net core 的微服务,网上很多介绍都是千篇一律基于类似webapi,通过http请求形式进行访问,但这并不符合大家使用习惯.如何像形如[ GetService<IOrderService>().SaveOrder(orderInfo)]的方式, 调用远程的服务,如果你正在为此苦
    推荐度:
    标签: 实现 net 基于
    • 热门焦点

    最新推荐

    猜你喜欢

    热门推荐

    专题
    Top