基于 .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,用来标识服务接口.

远程服务客户端代理

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

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接入段统一入口

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

[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:因为涉及到反射获取具体的方法,暂不支持相同参数个数的方法重载.仅支持不同参数个数的方法重载.

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

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;

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

?

1

2

3

4

5

6

7

8

9

10

[

 {

 "ServiceId": "XZL.Api.IOrderService",

 "Address": "http://localhost:8801/api/svc"

 },

 {

 "ServiceId": "XZL.Api.IUserService",

 "Address": "http://localhost:8802/api/svc"

 }

]

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

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

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类型缓存.

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

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远程服务代理示例

?

1

2

3

4

5

6

7

8

9

10

11

12

13

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依赖形式存在,这个便对调用者透明了.无缝的对接上了大家固有习惯.

猜你喜欢

转载自blog.csdn.net/qqqqqqqqqq198968/article/details/83998487