master
xiaochanghai 2 weeks ago
parent 6caeda7651
commit 75351ca06c
  1. 79
      Ocelot.Provider.Nacos/Nacos.cs
  2. 29
      Ocelot.Provider.Nacos/NacosClient/LoadBalance/ILBStrategy.cs
  3. 31
      Ocelot.Provider.Nacos/NacosClient/LoadBalance/LBStrategyName.cs
  4. 11
      Ocelot.Provider.Nacos/NacosClient/LoadBalance/LbKv.cs
  5. 101
      Ocelot.Provider.Nacos/NacosClient/LoadBalance/WeightRandomLBStrategy.cs
  6. 120
      Ocelot.Provider.Nacos/NacosClient/LoadBalance/WeightRoundRobinLBStrategy.cs
  7. 198
      Ocelot.Provider.Nacos/NacosClient/UriTool.cs
  8. 150
      Ocelot.Provider.Nacos/NacosClient/V2/NacosAspNetOptions.cs
  9. 164
      Ocelot.Provider.Nacos/NacosClient/V2/RegSvcBgTask.cs
  10. 95
      Ocelot.Provider.Nacos/NacosClient/V2/ServiceCollectionExtensions.cs
  11. 39
      Ocelot.Provider.Nacos/NacosMiddlewareConfigurationProvider.cs
  12. 30
      Ocelot.Provider.Nacos/NacosProviderFactory.cs
  13. 20
      Ocelot.Provider.Nacos/OcelotBuilderExtensions.cs
  14. 8
      Tiobon.Core.Common/Helper/ExtensionHelper.cs
  15. 177
      Tiobon.Core.EventBus/EventBusKafka/EventBusKafka.cs
  16. 32
      Tiobon.Core.EventBus/EventBusKafka/IKafkaConnectionPool.cs
  17. 113
      Tiobon.Core.EventBus/EventBusKafka/KafkaConnectionPool.cs
  18. 243
      Tiobon.Core.EventBus/EventBusKafka/KafkaConsumerHostService.cs
  19. 43
      Tiobon.Core.EventBus/EventBusKafka/KafkaOptions.cs
  20. 45
      Tiobon.Core.EventBus/EventBusKafka/ProtobufTransfer.cs
  21. 274
      Tiobon.Core.EventBus/EventBusSubscriptions/InMemoryEventBusSubscriptionsManager.cs
  22. 42
      Tiobon.Core.EventBus/EventBusSubscriptions/SubscriptionInfo.cs
  23. 17
      Tiobon.Core.EventBus/Eventbus/IDynamicIntegrationEventHandler.cs
  24. 81
      Tiobon.Core.EventBus/Eventbus/IEventBus.cs
  25. 55
      Tiobon.Core.EventBus/Eventbus/IEventBusSubscriptionsManager.cs
  26. 35
      Tiobon.Core.EventBus/Eventbus/IIntegrationEventHandler.cs
  27. 44
      Tiobon.Core.EventBus/Eventbus/IntegrationEvent.cs
  28. 523
      Tiobon.Core.EventBus/RabbitMQPersistent/EventBusRabbitMQ.cs
  29. 66
      Tiobon.Core.EventBus/RabbitMQPersistent/IRabbitMQPersistentConnection.cs
  30. 331
      Tiobon.Core.EventBus/RabbitMQPersistent/RabbitMQPersistentConnection.cs
  31. 57
      Tiobon.Core.Gateway/Controllers/UserController.cs
  32. 69
      Tiobon.Core.Gateway/Extensions/CustomAuthenticationHandler.cs
  33. 43
      Tiobon.Core.Gateway/Extensions/CustomOcelotSetup.cs
  34. 81
      Tiobon.Core.Gateway/Extensions/CustomResultHandler.cs
  35. 113
      Tiobon.Core.Gateway/Extensions/CustomSwaggerSetup.cs
  36. 259
      Tiobon.Core.Gateway/Helper/CustomJwtTokenAuthMiddleware.cs
  37. 41
      Tiobon.Core.Gateway/Program.cs
  38. 7
      Tiobon.Core.Model/HttpEnum.cs
  39. 16
      Tiobon.Core.Model/ViewModels/EnumDemoDto.cs
  40. 22
      Tiobon.Core.Model/ViewModels/TopgbViewModels.cs
  41. 18
      Tiobon.Core.Model/ViewModels/UploadFileDto.cs
  42. 5
      Tiobon.Core.Tasks/GlobalUsings.cs
  43. 7
      Tiobon.Core.Tasks/QuartzNet/ISchedulerCenter.cs
  44. 5
      Tiobon.Core.Tasks/QuartzNet/Jobs/JobBase.cs
  45. 4
      Tiobon.Core.Tasks/QuartzNet/Jobs/Job_AutoClearLog_Quartz.cs
  46. 4
      Tiobon.Core.Tasks/QuartzNet/Jobs/Job_AutoIssueCertificate_Quartz.cs
  47. 3
      Tiobon.Core.Tasks/QuartzNet/Jobs/Job_AutoIssueCredit_Quartz.cs
  48. 4
      Tiobon.Core.Tasks/QuartzNet/Jobs/Job_AutoMarkCompleteStatus_Quartz.cs
  49. 1
      Tiobon.Core.Tasks/QuartzNet/Jobs/Job_OperateLog_Quartz.cs
  50. 4
      Tiobon.Core.Tasks/QuartzNet/Jobs/Job_URL_Quartz.cs
  51. 3
      Tiobon.Core.Tasks/QuartzNet/SchedulerCenterServer.cs
  52. 28
      Tiobon.Core.Tests/Common_Test/DbAccess_Should.cs
  53. 31
      Tiobon.Core.Tests/Common_Test/HttpHelper_Should.cs
  54. 55
      Tiobon.Core.Tests/Common_Test/SM4Helper_Should.cs
  55. 133
      Tiobon.Core.Tests/Controller_Test/LoginController_Should.cs
  56. 181
      Tiobon.Core.Tests/DependencyInjection/DI_Test.cs
  57. 31
      Tiobon.Core.Tests/Redis_Test/Redis_Should.cs
  58. 79
      Tiobon.Core.Tests/Repository_Test/MongoRepository_Base_Should.cs
  59. 113
      Tiobon.Core.Tests/Repository_Test/Repository_Base_Should.cs
  60. 9
      Tiobon.Core.sln
  61. 25
      Tiobon.Core/Tiobon.Core.Model.xml

@ -1,57 +1,56 @@
using Ocelot.ServiceDiscovery.Providers; using Microsoft.Extensions.Options;
using Ocelot.Values;
using Nacos.V2; using Nacos.V2;
using Microsoft.Extensions.Options;
using Ocelot.Provider.Nacos.NacosClient.V2; using Ocelot.Provider.Nacos.NacosClient.V2;
using Ocelot.ServiceDiscovery.Providers;
using Ocelot.Values;
using NacosConstants = Nacos.V2.Common.Constants; using NacosConstants = Nacos.V2.Common.Constants;
namespace Ocelot.Provider.Nacos namespace Ocelot.Provider.Nacos;
public class Nacos : IServiceDiscoveryProvider
{ {
public class Nacos : IServiceDiscoveryProvider private readonly INacosNamingService _client;
{ private readonly string _serviceName;
private readonly INacosNamingService _client; private readonly string _groupName;
private readonly string _serviceName; private readonly List<string> _clusters;
private readonly string _groupName;
private readonly List<string> _clusters;
public Nacos(string serviceName, INacosNamingService client, IOptions<NacosAspNetOptions> options) public Nacos(string serviceName, INacosNamingService client, IOptions<NacosAspNetOptions> options)
{ {
_serviceName = serviceName; _serviceName = serviceName;
_client = client; _client = client;
_groupName = string.IsNullOrWhiteSpace(options.Value.GroupName) ? _groupName = string.IsNullOrWhiteSpace(options.Value.GroupName) ?
NacosConstants.DEFAULT_GROUP : options.Value.GroupName; NacosConstants.DEFAULT_GROUP : options.Value.GroupName;
_clusters = (string.IsNullOrWhiteSpace(options.Value.ClusterName) ? NacosConstants.DEFAULT_CLUSTER_NAME : options.Value.ClusterName).Split(",").ToList(); _clusters = (string.IsNullOrWhiteSpace(options.Value.ClusterName) ? NacosConstants.DEFAULT_CLUSTER_NAME : options.Value.ClusterName).Split(",").ToList();
} }
public async Task<List<Service>> Get() public async Task<List<Service>> Get()
{ {
var services = new List<Service>(); var services = new List<Service>();
var instances = await _client.GetAllInstances(_serviceName, _groupName, _clusters); var instances = await _client.GetAllInstances(_serviceName, _groupName, _clusters);
if (instances != null && instances.Any()) if (instances != null && instances.Any())
{
foreach (var Sitem in instances)
{ {
foreach (var Sitem in instances) string sip = Sitem.Ip;
int sport = Sitem.Port;
if (Sitem.Metadata.ContainsKey("endpoint"))
{ {
string sip = Sitem.Ip; string[] ipport = Sitem.Metadata["endpoint"].Split(':');
int sport = Sitem.Port; sip = ipport[0];
if (Sitem.Metadata.ContainsKey("endpoint")) sport =int.Parse( ipport[1]);
{
string[] ipport = Sitem.Metadata["endpoint"].Split(':');
sip = ipport[0];
sport =int.Parse( ipport[1]);
}
services.Add(new Service(Sitem.InstanceId, new ServiceHostAndPort(sip, sport), "", "", new List<string>()));
} }
// services.AddRange(instances.Select(i => new Service(i.InstanceId, new ServiceHostAndPort(i.Ip, i.Port), "", "", new List<string>()))); services.Add(new Service(Sitem.InstanceId, new ServiceHostAndPort(sip, sport), "", "", new List<string>()));
} }
// services.AddRange(instances.Select(i => new Service(i.InstanceId, new ServiceHostAndPort(i.Ip, i.Port), "", "", new List<string>())));
return await Task.FromResult(services);
} }
public Task<List<Service>> GetAsync() return await Task.FromResult(services);
{ }
throw new NotImplementedException();
} public Task<List<Service>> GetAsync()
{
throw new NotImplementedException();
} }
} }

@ -2,25 +2,24 @@
using Nacos.V2.Naming.Dtos; using Nacos.V2.Naming.Dtos;
using System.Collections.Generic; using System.Collections.Generic;
namespace Ocelot.Provider.Nacos.NacosClient namespace Ocelot.Provider.Nacos.NacosClient;
public interface ILBStrategy
{ {
public interface ILBStrategy /// <summary>
{ /// Strategy Name
/// <summary> /// </summary>
/// Strategy Name LBStrategyName Name { get; }
/// </summary>
LBStrategyName Name { get; }
/// <summary> /// <summary>
/// Get host /// Get host
/// </summary> /// </summary>
/// <param name="list">host list</param> /// <param name="list">host list</param>
/// <returns>The Host</returns> /// <returns>The Host</returns>
Instance GetHost(List<Instance> list); Instance GetHost(List<Instance> list);
} }
}

@ -1,20 +1,19 @@
namespace Ocelot.Provider.Nacos.NacosClient namespace Ocelot.Provider.Nacos.NacosClient;
public enum LBStrategyName
{ {
public enum LBStrategyName /// <summary>
{ /// Weight Round Robin
/// <summary> /// </summary>
/// Weight Round Robin WeightRoundRobin,
/// </summary>
WeightRoundRobin,
/// <summary> /// <summary>
/// Weight Random /// Weight Random
/// </summary> /// </summary>
WeightRandom, WeightRandom,
/// <summary> /// <summary>
/// Ext1 /// Ext1
/// </summary> /// </summary>
Ext1 Ext1
}
} }

@ -1,9 +1,8 @@
namespace Ocelot.Provider.Nacos.NacosClient namespace Ocelot.Provider.Nacos.NacosClient;
public class LbKv
{ {
public class LbKv public string InstanceId { get; set; }
{
public string InstanceId { get; set; }
public double Weight { get; set; } public double Weight { get; set; }
}
} }

@ -1,74 +1,69 @@
using Nacos; using Nacos.V2.Naming.Dtos;
using Nacos.V2.Naming.Dtos;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Ocelot.Provider.Nacos.NacosClient namespace Ocelot.Provider.Nacos.NacosClient;
public class WeightRandomLBStrategy : ILBStrategy
{ {
public class WeightRandomLBStrategy : ILBStrategy public LBStrategyName Name => LBStrategyName.WeightRandom;
{
public LBStrategyName Name => LBStrategyName.WeightRandom;
public Instance GetHost(List<Instance> list) public Instance GetHost(List<Instance> list)
{ {
var dict = BuildScore(list); var dict = BuildScore(list);
Instance instance = null; Instance instance = null;
var rd = new Random().NextDouble(); var rd = new Random().NextDouble();
foreach (var item in dict) foreach (var item in dict)
{
if (item.Value >= rd)
{ {
if (item.Value >= rd) instance = list.FirstOrDefault(x => x.InstanceId.Equals(item.Key));
{
instance = list.FirstOrDefault(x => x.InstanceId.Equals(item.Key));
if (instance == null) if (instance == null)
{ {
var arr = item.Key.Split("#"); var arr = item.Key.Split("#");
var ip = arr[0]; var ip = arr[0];
int.TryParse(arr[1], out var port); int.TryParse(arr[1], out var port);
var cluster = arr[2]; var cluster = arr[2];
instance = list.First(x => x.Ip.Equals(ip) && x.Port == port && x.ClusterName.Equals(cluster));
}
break; instance = list.First(x => x.Ip.Equals(ip) && x.Port == port && x.ClusterName.Equals(cluster));
} }
}
return instance; break;
}
} }
private Dictionary<string, double> BuildScore(List<Instance> list) return instance;
{ }
var dict = new Dictionary<string, double>();
// aliyun sae, the instanceid returns empty string private Dictionary<string, double> BuildScore(List<Instance> list)
// when the instanceid is empty, create a new one, but the group was missed. {
list.ForEach(x => { x.InstanceId = string.IsNullOrWhiteSpace(x.InstanceId) ? $"{x.Ip}#{x.Port}#{x.ClusterName}#{x.ServiceName}" : x.InstanceId; }); var dict = new Dictionary<string, double>();
var tmp = list.Select(x => new LbKv // aliyun sae, the instanceid returns empty string
{ // when the instanceid is empty, create a new one, but the group was missed.
InstanceId = x.InstanceId, list.ForEach(x => { x.InstanceId = string.IsNullOrWhiteSpace(x.InstanceId) ? $"{x.Ip}#{x.Port}#{x.ClusterName}#{x.ServiceName}" : x.InstanceId; });
Weight = x.Weight
}).GroupBy(x => x.InstanceId).Select(x => new LbKv
{
InstanceId = x.Key,
Weight = x.Max(y => y.Weight)
}).ToList();
var total = tmp.Sum(x => x.Weight); var tmp = list.Select(x => new LbKv
var cur = 0d; {
InstanceId = x.InstanceId,
Weight = x.Weight
}).GroupBy(x => x.InstanceId).Select(x => new LbKv
{
InstanceId = x.Key,
Weight = x.Max(y => y.Weight)
}).ToList();
foreach (var item in tmp) var total = tmp.Sum(x => x.Weight);
{ var cur = 0d;
cur += item.Weight;
dict.TryAdd(item.InstanceId, cur / total);
}
return dict; foreach (var item in tmp)
{
cur += item.Weight;
dict.TryAdd(item.InstanceId, cur / total);
} }
return dict;
} }
} }

@ -1,71 +1,67 @@
using Nacos; using Nacos.V2.Naming.Dtos;
using System.Collections.Generic; namespace Ocelot.Provider.Nacos.NacosClient;
using System.Linq;
using Nacos.V2.Naming.Dtos; public class WeightRoundRobinLBStrategy : ILBStrategy
namespace Ocelot.Provider.Nacos.NacosClient
{ {
public class WeightRoundRobinLBStrategy : ILBStrategy public LBStrategyName Name => LBStrategyName.WeightRoundRobin;
private int _pos;
private static object obj = new object();
public Instance GetHost(List<Instance> list)
{ {
public LBStrategyName Name => LBStrategyName.WeightRoundRobin; // aliyun sae, the instanceid returns empty string
// when the instanceid is empty, create a new one, but the group was missed.
list.ForEach(x => { x.InstanceId = string.IsNullOrWhiteSpace(x.InstanceId) ? $"{x.Ip}#{x.Port}#{x.ClusterName}#{x.ServiceName}" : x.InstanceId; });
var tmp = list.Select(x => new LbKv
{
InstanceId = x.InstanceId,
Weight = x.Weight
}).GroupBy(x => x.InstanceId).Select(x => new LbKv
{
InstanceId = x.Key,
Weight = x.Max(y => y.Weight)
}).ToList();
private int _pos; // <instanceid, weight>
var dic = tmp.ToDictionary(k => k.InstanceId, v => (int)v.Weight);
private static object obj = new object(); var srcInstanceIdList = dic.Keys.ToList();
var tagInstanceIdList = new List<string>();
public Instance GetHost(List<Instance> list) foreach (var item in srcInstanceIdList)
{ {
// aliyun sae, the instanceid returns empty string dic.TryGetValue(item, out var weight);
// when the instanceid is empty, create a new one, but the group was missed.
list.ForEach(x => { x.InstanceId = string.IsNullOrWhiteSpace(x.InstanceId) ? $"{x.Ip}#{x.Port}#{x.ClusterName}#{x.ServiceName}" : x.InstanceId; }); for (int i = 0; i < weight; i++)
tagInstanceIdList.Add(item);
var tmp = list.Select(x => new LbKv
{
InstanceId = x.InstanceId,
Weight = x.Weight
}).GroupBy(x => x.InstanceId).Select(x => new LbKv
{
InstanceId = x.Key,
Weight = x.Max(y => y.Weight)
}).ToList();
// <instanceid, weight>
var dic = tmp.ToDictionary(k => k.InstanceId, v => (int)v.Weight);
var srcInstanceIdList = dic.Keys.ToList();
var tagInstanceIdList = new List<string>();
foreach (var item in srcInstanceIdList)
{
dic.TryGetValue(item, out var weight);
for (int i = 0; i < weight; i++)
tagInstanceIdList.Add(item);
}
var instanceId = string.Empty;
lock (obj)
{
if (_pos >= tagInstanceIdList.Count)
_pos = 0;
instanceId = tagInstanceIdList[_pos];
_pos++;
}
var instance = list.FirstOrDefault(x => x.InstanceId.Equals(instanceId));
if (instance == null)
{
var arr = instanceId.Split("#");
var ip = arr[0];
int.TryParse(arr[1], out var port);
var cluster = arr[2];
instance = list.First(x => x.Ip.Equals(ip) && x.Port == port && x.ClusterName.Equals(cluster));
}
return instance;
} }
var instanceId = string.Empty;
lock (obj)
{
if (_pos >= tagInstanceIdList.Count)
_pos = 0;
instanceId = tagInstanceIdList[_pos];
_pos++;
}
var instance = list.FirstOrDefault(x => x.InstanceId.Equals(instanceId));
if (instance == null)
{
var arr = instanceId.Split("#");
var ip = arr[0];
int.TryParse(arr[1], out var port);
var cluster = arr[2];
instance = list.First(x => x.Ip.Equals(ip) && x.Port == port && x.ClusterName.Equals(cluster));
}
return instance;
} }
} }

@ -1,145 +1,141 @@
using System; using Microsoft.AspNetCore.Hosting.Server.Features;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using System.Net; using System.Net;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Net.Sockets; using System.Net.Sockets;
namespace Ocelot.Provider.Nacos.NacosClient namespace Ocelot.Provider.Nacos.NacosClient;
internal static class UriTool
{ {
internal static class UriTool public static IEnumerable<Uri> GetUri(IFeatureCollection features, string ip, int port, string preferredNetworks)
{ {
public static IEnumerable<Uri> GetUri(IFeatureCollection features, string ip, int port, string preferredNetworks) var splitChars = new char[] { ',', ';' };
var appPort = port <= 0 ? 80 : port;
// 1. config
if (!string.IsNullOrWhiteSpace(ip))
{ {
var splitChars = new char[] { ',', ';' }; // it seems that nacos don't return the scheme
var appPort = port <= 0 ? 80 : port; // so here use http only.
return new List<Uri> { new Uri($"http://{ip}:{appPort}") };
}
// 1. config // 1.1. Ip is null && Port has value
if (!string.IsNullOrWhiteSpace(ip)) if (string.IsNullOrWhiteSpace(ip) && appPort != 80)
{ {
// it seems that nacos don't return the scheme return new List<Uri> { new Uri($"http://{GetCurrentIp(preferredNetworks)}:{appPort}") };
// so here use http only. }
return new List<Uri> { new Uri($"http://{ip}:{appPort}") };
}
// 1.1. Ip is null && Port has value var address = string.Empty;
if (string.IsNullOrWhiteSpace(ip) && appPort != 80)
{
return new List<Uri> { new Uri($"http://{GetCurrentIp(preferredNetworks)}:{appPort}") };
}
var address = string.Empty; // 2. IServerAddressesFeature
if (features != null)
{
var addresses = features.Get<IServerAddressesFeature>();
var addressCollection = addresses?.Addresses;
// 2. IServerAddressesFeature if (addressCollection != null && addressCollection.Any())
if (features != null)
{ {
var addresses = features.Get<IServerAddressesFeature>(); var uris = new List<Uri>();
var addressCollection = addresses?.Addresses; foreach (var item in addressCollection)
if (addressCollection != null && addressCollection.Any())
{ {
var uris = new List<Uri>(); var url = ReplaceAddress(item, preferredNetworks);
foreach (var item in addressCollection) uris.Add(new Uri(url));
{
var url = ReplaceAddress(item, preferredNetworks);
uris.Add(new Uri(url));
}
return uris;
} }
return uris;
} }
}
// 3. ASPNETCORE_URLS // 3. ASPNETCORE_URLS
address = Environment.GetEnvironmentVariable("ASPNETCORE_URLS"); address = Environment.GetEnvironmentVariable("ASPNETCORE_URLS");
if (!string.IsNullOrWhiteSpace(address)) if (!string.IsNullOrWhiteSpace(address))
{
var url = ReplaceAddress(address, preferredNetworks);
return url.Split(splitChars).Select(x => new Uri(x));
}
// 4. --urls
var cmdArgs = Environment.GetCommandLineArgs();
if (cmdArgs != null && cmdArgs.Any())
{
var cmd = cmdArgs.FirstOrDefault(x => x.StartsWith("--urls", StringComparison.OrdinalIgnoreCase));
if (!string.IsNullOrWhiteSpace(cmd))
{ {
address = cmd.Split('=')[1];
var url = ReplaceAddress(address, preferredNetworks); var url = ReplaceAddress(address, preferredNetworks);
return url.Split(splitChars).Select(x => new Uri(x)); return url.Split(splitChars).Select(x => new Uri(x));
} }
}
// 4. --urls // 5. current ip address third
var cmdArgs = Environment.GetCommandLineArgs(); address = $"http://{GetCurrentIp(preferredNetworks)}:{appPort}";
if (cmdArgs != null && cmdArgs.Any())
{
var cmd = cmdArgs.FirstOrDefault(x => x.StartsWith("--urls", StringComparison.OrdinalIgnoreCase));
if (!string.IsNullOrWhiteSpace(cmd))
{
address = cmd.Split('=')[1];
var url = ReplaceAddress(address, preferredNetworks);
return url.Split(splitChars).Select(x => new Uri(x)); return new List<Uri> { new Uri(address) };
} }
}
// 5. current ip address third private static string ReplaceAddress(string address, string preferredNetworks)
address = $"http://{GetCurrentIp(preferredNetworks)}:{appPort}"; {
var ip = GetCurrentIp(preferredNetworks);
return new List<Uri> { new Uri(address) }; if (address.Contains("*"))
{
address = address.Replace("*", ip);
} }
else if (address.Contains("+"))
private static string ReplaceAddress(string address, string preferredNetworks) {
address = address.Replace("+", ip);
}
else if (address.Contains("localhost", StringComparison.OrdinalIgnoreCase))
{
address = address.Replace("localhost", ip, StringComparison.OrdinalIgnoreCase);
}
else if (address.Contains("0.0.0.0", StringComparison.OrdinalIgnoreCase))
{ {
var ip = GetCurrentIp(preferredNetworks); address = address.Replace("0.0.0.0", ip, StringComparison.OrdinalIgnoreCase);
}
if (address.Contains("*")) return address;
{ }
address = address.Replace("*", ip);
}
else if (address.Contains("+"))
{
address = address.Replace("+", ip);
}
else if (address.Contains("localhost", StringComparison.OrdinalIgnoreCase))
{
address = address.Replace("localhost", ip, StringComparison.OrdinalIgnoreCase);
}
else if (address.Contains("0.0.0.0", StringComparison.OrdinalIgnoreCase))
{
address = address.Replace("0.0.0.0", ip, StringComparison.OrdinalIgnoreCase);
}
return address; private static string GetCurrentIp(string preferredNetworks)
} {
var instanceIp = "127.0.0.1";
private static string GetCurrentIp(string preferredNetworks) try
{ {
var instanceIp = "127.0.0.1"; // 获取可用网卡
var nics = NetworkInterface.GetAllNetworkInterfaces()?.Where(network => network.OperationalStatus == OperationalStatus.Up);
try // 获取所有可用网卡IP信息
{ var ipCollection = nics?.Select(x => x.GetIPProperties())?.SelectMany(x => x.UnicastAddresses);
// 获取可用网卡
var nics = NetworkInterface.GetAllNetworkInterfaces()?.Where(network => network.OperationalStatus == OperationalStatus.Up);
// 获取所有可用网卡IP信息
var ipCollection = nics?.Select(x => x.GetIPProperties())?.SelectMany(x => x.UnicastAddresses);
foreach (var ipadd in ipCollection) foreach (var ipadd in ipCollection)
{
if (!IPAddress.IsLoopback(ipadd.Address) && ipadd.Address.AddressFamily == AddressFamily.InterNetwork)
{ {
if (!IPAddress.IsLoopback(ipadd.Address) && ipadd.Address.AddressFamily == AddressFamily.InterNetwork) if (string.IsNullOrEmpty(preferredNetworks))
{ {
if (string.IsNullOrEmpty(preferredNetworks))
{
instanceIp = ipadd.Address.ToString();
break;
}
if (!ipadd.Address.ToString().StartsWith(preferredNetworks)) continue;
instanceIp = ipadd.Address.ToString(); instanceIp = ipadd.Address.ToString();
break; break;
} }
if (!ipadd.Address.ToString().StartsWith(preferredNetworks)) continue;
instanceIp = ipadd.Address.ToString();
break;
} }
} }
catch
{
// ignored
}
return instanceIp;
} }
catch
{
// ignored
}
return instanceIp;
} }
} }

@ -1,96 +1,94 @@
using Nacos.V2; using Nacos.V2;
using Nacos.V2.Common; using Nacos.V2.Common;
using System.Collections.Generic;
namespace Ocelot.Provider.Nacos.NacosClient.V2 namespace Ocelot.Provider.Nacos.NacosClient.V2;
public class NacosAspNetOptions : NacosSdkOptions
{ {
public class NacosAspNetOptions : NacosSdkOptions /// <summary>
{ /// the name of the service.
/// <summary> /// </summary>
/// the name of the service. public string ServiceName { get; set; }
/// </summary>
public string ServiceName { get; set; }
/// <summary> /// <summary>
/// the name of the group. /// the name of the group.
/// </summary> /// </summary>
public string GroupName { get; set; } = Constants.DEFAULT_GROUP; public string GroupName { get; set; } = Constants.DEFAULT_GROUP;
/// <summary> /// <summary>
/// the name of the cluster. /// the name of the cluster.
/// </summary> /// </summary>
/// <value>The name of the cluster.</value> /// <value>The name of the cluster.</value>
public string ClusterName { get; set; } = Constants.DEFAULT_CLUSTER_NAME; public string ClusterName { get; set; } = Constants.DEFAULT_CLUSTER_NAME;
/// <summary> /// <summary>
/// the ip of this instance /// the ip of this instance
/// </summary> /// </summary>
public string Ip { get; set; } public string Ip { get; set; }
/// <summary> /// <summary>
/// Select an IP that matches the prefix as the service registration IP /// Select an IP that matches the prefix as the service registration IP
/// like the config of spring.cloud.inetutils.preferred-networks /// like the config of spring.cloud.inetutils.preferred-networks
/// </summary> /// </summary>
public string PreferredNetworks { get; set; } public string PreferredNetworks { get; set; }
/// <summary> /// <summary>
/// the port of this instance /// the port of this instance
/// </summary> /// </summary>
public int Port { get; set; } public int Port { get; set; }
/// <summary> /// <summary>
/// the weight of this instance. /// the weight of this instance.
/// </summary> /// </summary>
public double Weight { get; set; } = 100; public double Weight { get; set; } = 100;
/// <summary> /// <summary>
/// if you just want to subscribe, but don't want to register your service, set it to false. /// if you just want to subscribe, but don't want to register your service, set it to false.
/// </summary> /// </summary>
public bool RegisterEnabled { get; set; } = true; public bool RegisterEnabled { get; set; } = true;
/// <summary> /// <summary>
/// the metadata of this instance /// the metadata of this instance
/// </summary> /// </summary>
public Dictionary<string, string> Metadata { get; set; } = new Dictionary<string, string>(); public Dictionary<string, string> Metadata { get; set; } = new Dictionary<string, string>();
/// <summary> /// <summary>
/// If instance is enabled to accept request. The default value is true. /// If instance is enabled to accept request. The default value is true.
/// </summary> /// </summary>
public bool InstanceEnabled { get; set; } = true; public bool InstanceEnabled { get; set; } = true;
/// <summary> /// <summary>
/// If instance is ephemeral.The default value is true. /// If instance is ephemeral.The default value is true.
/// </summary> /// </summary>
public bool Ephemeral { get; set; } = true; public bool Ephemeral { get; set; } = true;
/// <summary> /// <summary>
/// whether your service is a https service. /// whether your service is a https service.
/// </summary> /// </summary>
public bool Secure { get; set; } = false; public bool Secure { get; set; } = false;
/// <summary> /// <summary>
/// Load Balance Strategy /// Load Balance Strategy
/// </summary> /// </summary>
public string LBStrategy { get; set; } = LBStrategyName.WeightRandom.ToString(); public string LBStrategy { get; set; } = LBStrategyName.WeightRandom.ToString();
public NacosSdkOptions BuildSdkOptions() public NacosSdkOptions BuildSdkOptions()
{
return new NacosSdkOptions
{ {
return new NacosSdkOptions AccessKey = this.AccessKey,
{ ConfigUseRpc = this.ConfigUseRpc,
AccessKey = this.AccessKey, ContextPath = this.ContextPath,
ConfigUseRpc = this.ConfigUseRpc, DefaultTimeOut = this.DefaultTimeOut,
ContextPath = this.ContextPath, EndPoint = this.EndPoint,
DefaultTimeOut = this.DefaultTimeOut, ListenInterval = this.ListenInterval,
EndPoint = this.EndPoint, Namespace = this.Namespace,
ListenInterval = this.ListenInterval, NamingLoadCacheAtStart = this.NamingLoadCacheAtStart,
Namespace = this.Namespace, NamingUseRpc = this.NamingUseRpc,
NamingLoadCacheAtStart = this.NamingLoadCacheAtStart, Password = this.Password,
NamingUseRpc = this.NamingUseRpc, SecretKey = this.SecretKey,
Password = this.Password, ServerAddresses = this.ServerAddresses,
SecretKey = this.SecretKey, UserName = this.UserName,
ServerAddresses = this.ServerAddresses, };
UserName = this.UserName,
};
}
} }
} }

@ -1,69 +1,103 @@
using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Nacos.V2; using Nacos.V2;
using Nacos.V2.Naming.Core; using Nacos.V2.Naming.Core;
using Nacos.V2.Naming.Dtos; using Nacos.V2.Naming.Dtos;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace Ocelot.Provider.Nacos.NacosClient.V2 namespace Ocelot.Provider.Nacos.NacosClient.V2;
public class RegSvcBgTask
{ {
public class RegSvcBgTask private static readonly string MetadataNetVersion = "DOTNET_VERSION";
{ private static readonly string MetadataHostOs = "HOST_OS";
private static readonly string MetadataNetVersion = "DOTNET_VERSION"; private static readonly string MetadataSecure = "secure";
private static readonly string MetadataHostOs = "HOST_OS";
private static readonly string MetadataSecure = "secure"; private readonly ILogger _logger;
private readonly INacosNamingService _svc;
private readonly IFeatureCollection _features;
private NacosAspNetOptions _options;
private readonly ILogger _logger; private IEnumerable<Uri> uris = null;
private readonly INacosNamingService _svc;
private readonly IFeatureCollection _features;
private NacosAspNetOptions _options;
private IEnumerable<Uri> uris = null; public RegSvcBgTask(
ILoggerFactory loggerFactory,
INacosNamingService svc,
IServer server,
IOptionsMonitor<NacosAspNetOptions> optionsAccs)
{
_logger = loggerFactory.CreateLogger<RegSvcBgTask>();
_svc = svc;
_options = optionsAccs.CurrentValue;
_features = server.Features;
}
public RegSvcBgTask( public async Task StartAsync()
ILoggerFactory loggerFactory, {
INacosNamingService svc, if (!_options.RegisterEnabled)
IServer server,
IOptionsMonitor<NacosAspNetOptions> optionsAccs)
{ {
_logger = loggerFactory.CreateLogger<RegSvcBgTask>(); _logger.LogInformation("setting RegisterEnabled to false, will not register to nacos");
_svc = svc; return;
_options = optionsAccs.CurrentValue;
_features = server.Features;
} }
public async Task StartAsync() uris = UriTool.GetUri(_features, _options.Ip, _options.Port, _options.PreferredNetworks);
var metadata = new Dictionary<string, string>()
{ {
if (!_options.RegisterEnabled) { PreservedMetadataKeys.REGISTER_SOURCE, $"ASPNET_CORE" },
{ MetadataNetVersion, Environment.Version.ToString() },
{ MetadataHostOs, Environment.OSVersion.ToString() },
};
if (_options.Secure) metadata[MetadataSecure] = "true";
foreach (var item in _options.Metadata)
{
if (!metadata.ContainsKey(item.Key))
{ {
_logger.LogInformation("setting RegisterEnabled to false, will not register to nacos"); metadata.TryAdd(item.Key, item.Value);
return;
} }
}
uris = UriTool.GetUri(_features, _options.Ip, _options.Port, _options.PreferredNetworks); foreach (var uri in uris)
{
var metadata = new Dictionary<string, string>() for (int i = 0; i < 3; i++)
{ {
{ PreservedMetadataKeys.REGISTER_SOURCE, $"ASPNET_CORE" }, try
{ MetadataNetVersion, Environment.Version.ToString() }, {
{ MetadataHostOs, Environment.OSVersion.ToString() }, var instance = new Instance
}; {
Ephemeral = _options.Ephemeral,
ServiceName = _options.ServiceName,
ClusterName = _options.ClusterName,
Enabled = _options.InstanceEnabled,
Healthy = true,
Ip = uri.Host,
Port = uri.Port,
Weight = _options.Weight,
Metadata = metadata,
InstanceId = ""
};
if (_options.Secure) metadata[MetadataSecure] = "true"; _logger.LogInformation("register instance to nacos server, 【{0}】", instance);
foreach (var item in _options.Metadata) await _svc.RegisterInstance(_options.ServiceName, _options.GroupName, instance);
{ break;
if (!metadata.ContainsKey(item.Key)) }
catch (Exception ex)
{ {
metadata.TryAdd(item.Key, item.Value); _logger.LogError(ex, "register instance error, count = {0}", i + 1);
} }
} }
}
}
public async Task StopAsync()
{
if (_options.RegisterEnabled)
{
_logger.LogWarning("deregister instance from nacos server, serviceName={0}", _options.ServiceName);
foreach (var uri in uris) foreach (var uri in uris)
{ {
@ -71,54 +105,14 @@ namespace Ocelot.Provider.Nacos.NacosClient.V2
{ {
try try
{ {
var instance = new Instance _logger.LogWarning("begin to remove instance");
{ await _svc.DeregisterInstance(_options.ServiceName, _options.GroupName, uri.Host, uri.Port, _options.ClusterName);
Ephemeral = _options.Ephemeral, _logger.LogWarning("removed instance");
ServiceName = _options.ServiceName,
ClusterName = _options.ClusterName,
Enabled = _options.InstanceEnabled,
Healthy = true,
Ip = uri.Host,
Port = uri.Port,
Weight = _options.Weight,
Metadata = metadata,
InstanceId = ""
};
_logger.LogInformation("register instance to nacos server, 【{0}】", instance);
await _svc.RegisterInstance(_options.ServiceName, _options.GroupName, instance);
break; break;
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "register instance error, count = {0}", i + 1); _logger.LogError(ex, "deregister instance error, count = {0}", i + 1);
}
}
}
}
public async Task StopAsync()
{
if (_options.RegisterEnabled)
{
_logger.LogWarning("deregister instance from nacos server, serviceName={0}", _options.ServiceName);
foreach (var uri in uris)
{
for (int i = 0; i < 3; i++)
{
try
{
_logger.LogWarning("begin to remove instance");
await _svc.DeregisterInstance(_options.ServiceName, _options.GroupName, uri.Host, uri.Port, _options.ClusterName);
_logger.LogWarning("removed instance");
break;
}
catch (Exception ex)
{
_logger.LogError(ex, "deregister instance error, count = {0}", i + 1);
}
} }
} }
} }

@ -3,57 +3,54 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Nacos.V2.DependencyInjection; using Nacos.V2.DependencyInjection;
using System;
using System.Threading.Tasks;
namespace Ocelot.Provider.Nacos.NacosClient.V2 namespace Ocelot.Provider.Nacos.NacosClient.V2;
public static class ServiceCollectionExtensions
{ {
public static class ServiceCollectionExtensions /// <summary>
/// Add Nacos AspNet. This will register and de-register instance automatically.
/// Mainly for nacos server 2.x
/// </summary>
/// <param name="services">services.</param>
/// <param name="configuration">configuration</param>
/// <returns>IServiceCollection</returns>
public static IServiceCollection AddNacosAspNet(this IServiceCollection services, IConfiguration configuration)
{
services.Configure<NacosAspNetOptions>(configuration.GetSection("nacos"));
services.AddNacosV2Naming(configuration);
services.AddSingleton<RegSvcBgTask>();
return services;
}
/// <summary>
/// Add Nacos AspNet. This will register and de-register instance automatically.
/// Mainly for nacos server 2.x
/// </summary>
/// <param name="services">services</param>
/// <param name="optionsAccs">optionsAccs</param>
/// <returns>IServiceCollection</returns>
public static IServiceCollection AddNacosAspNet(this IServiceCollection services, Action<NacosAspNetOptions> optionsAccs)
{
services.Configure(optionsAccs);
var options = new NacosAspNetOptions();
optionsAccs.Invoke(options);
services.AddNacosV2Naming(x => options.BuildSdkOptions());
services.AddSingleton<RegSvcBgTask>();
return services;
}
public static async Task<IApplicationBuilder> UseNacosAspNet(this IApplicationBuilder app, IHostApplicationLifetime lifetime)
{ {
/// <summary> RegSvcBgTask regSvcBgTask = app.ApplicationServices.GetRequiredService<RegSvcBgTask>();
/// Add Nacos AspNet. This will register and de-register instance automatically. await regSvcBgTask.StartAsync();
/// Mainly for nacos server 2.x lifetime.ApplicationStopping.Register(async () => {
/// </summary> await regSvcBgTask.StopAsync();
/// <param name="services">services.</param> });
/// <param name="configuration">configuration</param> return app;
/// <returns>IServiceCollection</returns>
public static IServiceCollection AddNacosAspNet(this IServiceCollection services, IConfiguration configuration)
{
services.Configure<NacosAspNetOptions>(configuration.GetSection("nacos"));
services.AddNacosV2Naming(configuration);
services.AddSingleton<RegSvcBgTask>();
return services;
}
/// <summary>
/// Add Nacos AspNet. This will register and de-register instance automatically.
/// Mainly for nacos server 2.x
/// </summary>
/// <param name="services">services</param>
/// <param name="optionsAccs">optionsAccs</param>
/// <returns>IServiceCollection</returns>
public static IServiceCollection AddNacosAspNet(this IServiceCollection services, Action<NacosAspNetOptions> optionsAccs)
{
services.Configure(optionsAccs);
var options = new NacosAspNetOptions();
optionsAccs.Invoke(options);
services.AddNacosV2Naming(x => options.BuildSdkOptions());
services.AddSingleton<RegSvcBgTask>();
return services;
}
public static async Task<IApplicationBuilder> UseNacosAspNet(this IApplicationBuilder app, IHostApplicationLifetime lifetime)
{
RegSvcBgTask regSvcBgTask = app.ApplicationServices.GetRequiredService<RegSvcBgTask>();
await regSvcBgTask.StartAsync();
lifetime.ApplicationStopping.Register(async () => {
await regSvcBgTask.StopAsync();
});
return app;
}
} }
} }

@ -1,34 +1,31 @@
using System; using Microsoft.Extensions.DependencyInjection;
using System.Threading.Tasks; using Microsoft.Extensions.Hosting;
using Ocelot.Configuration; using Ocelot.Configuration;
using Ocelot.Configuration.Repository; using Ocelot.Configuration.Repository;
using Ocelot.Middleware; using Ocelot.Middleware;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Ocelot.Provider.Nacos.NacosClient.V2; using Ocelot.Provider.Nacos.NacosClient.V2;
namespace Ocelot.Provider.Nacos namespace Ocelot.Provider.Nacos;
public class NacosMiddlewareConfigurationProvider
{ {
public class NacosMiddlewareConfigurationProvider public static OcelotMiddlewareConfigurationDelegate Get = builder =>
{ {
public static OcelotMiddlewareConfigurationDelegate Get = builder => var internalConfigRepo = builder.ApplicationServices.GetService<IInternalConfigurationRepository>();
{ var config = internalConfigRepo.Get();
var internalConfigRepo = builder.ApplicationServices.GetService<IInternalConfigurationRepository>();
var config = internalConfigRepo.Get();
var hostLifetime = builder.ApplicationServices.GetService<IHostApplicationLifetime>();
if (UsingNacosServiceDiscoveryProvider(config.Data)) var hostLifetime = builder.ApplicationServices.GetService<IHostApplicationLifetime>();
{
builder.UseNacosAspNet(hostLifetime).GetAwaiter().GetResult();
}
return Task.CompletedTask; if (UsingNacosServiceDiscoveryProvider(config.Data))
};
private static bool UsingNacosServiceDiscoveryProvider(IInternalConfiguration configuration)
{ {
return configuration?.ServiceProviderConfiguration != null && configuration.ServiceProviderConfiguration.Type?.ToLower() == "nacos"; builder.UseNacosAspNet(hostLifetime).GetAwaiter().GetResult();
} }
return Task.CompletedTask;
};
private static bool UsingNacosServiceDiscoveryProvider(IInternalConfiguration configuration)
{
return configuration?.ServiceProviderConfiguration != null && configuration.ServiceProviderConfiguration.Type?.ToLower() == "nacos";
} }
} }

@ -1,23 +1,21 @@
using System; using Microsoft.Extensions.DependencyInjection;
using Ocelot.ServiceDiscovery; using Microsoft.Extensions.Options;
using Microsoft.Extensions.DependencyInjection;
using Nacos.V2; using Nacos.V2;
using Ocelot.Provider.Nacos.NacosClient.V2; using Ocelot.Provider.Nacos.NacosClient.V2;
using Microsoft.Extensions.Options; using Ocelot.ServiceDiscovery;
namespace Ocelot.Provider.Nacos;
namespace Ocelot.Provider.Nacos public static class NacosProviderFactory
{ {
public static class NacosProviderFactory public static ServiceDiscoveryFinderDelegate Get = (provider, config, route) =>
{ {
public static ServiceDiscoveryFinderDelegate Get = (provider, config, route) => var client = provider.GetService<INacosNamingService>();
if (config.Type?.ToLower() == "nacos" && client != null)
{ {
var client = provider.GetService<INacosNamingService>(); var option = provider.GetService<IOptions<NacosAspNetOptions>>();
if (config.Type?.ToLower() == "nacos" && client != null) return new Nacos(route.ServiceName, client, option);
{ }
var option = provider.GetService<IOptions<NacosAspNetOptions>>(); return null;
return new Nacos(route.ServiceName, client, option); };
}
return null;
};
}
} }

@ -1,20 +1,18 @@
using System.Linq; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using Ocelot.DependencyInjection; using Ocelot.DependencyInjection;
using Ocelot.Middleware; using Ocelot.Middleware;
using Ocelot.Provider.Nacos.NacosClient.V2; using Ocelot.Provider.Nacos.NacosClient.V2;
using Ocelot.ServiceDiscovery; using Ocelot.ServiceDiscovery;
namespace Ocelot.Provider.Nacos namespace Ocelot.Provider.Nacos;
public static class OcelotBuilderExtensions
{ {
public static class OcelotBuilderExtensions public static IOcelotBuilder AddNacosDiscovery(this IOcelotBuilder builder)
{ {
public static IOcelotBuilder AddNacosDiscovery(this IOcelotBuilder builder) builder.Services.AddNacosAspNet(builder.Configuration);
{ builder.Services.AddSingleton<ServiceDiscoveryFinderDelegate>(NacosProviderFactory.Get);
builder.Services.AddNacosAspNet(builder.Configuration); builder.Services.AddSingleton<OcelotMiddlewareConfigurationDelegate>(NacosMiddlewareConfigurationProvider.Get);
builder.Services.AddSingleton<ServiceDiscoveryFinderDelegate>(NacosProviderFactory.Get); return builder;
builder.Services.AddSingleton<OcelotMiddlewareConfigurationDelegate>(NacosMiddlewareConfigurationProvider.Get);
return builder;
}
} }
} }

@ -1,4 +1,10 @@
namespace Tiobon.Core.Common.Helper; using SqlSugar;
using System.Data;
using Tiobon.Core.Helper;
using Tiobon.Core.Model.Entity;
using Tiobon.Core.Model.ViewModels.Extend;
namespace Tiobon.Core.Common.Helper;
public class ExtensionHelper public class ExtensionHelper
{ {

@ -4,114 +4,113 @@ using Microsoft.Extensions.Options;
using Newtonsoft.Json; using Newtonsoft.Json;
using Tiobon.Core.Extensions; using Tiobon.Core.Extensions;
namespace Tiobon.Core.EventBus namespace Tiobon.Core.EventBus;
/// <summary>
/// 基于Kafka的事件总线
/// </summary>
public class EventBusKafka : IEventBus
{ {
private readonly ILogger<EventBusKafka> _logger;
private readonly IEventBusSubscriptionsManager _subsManager;
private readonly IKafkaConnectionPool _connectionPool;
private readonly KafkaOptions _options;
public EventBusKafka(ILogger<EventBusKafka> logger,
IEventBusSubscriptionsManager subsManager,
IKafkaConnectionPool connectionPool,
IOptions<KafkaOptions> options)
{
_logger = logger;
_subsManager = subsManager;
_connectionPool = connectionPool;
_options = options.Value;
}
/// <summary> /// <summary>
/// 基于Kafka的事件总线 /// 发布
/// </summary> /// </summary>
public class EventBusKafka : IEventBus public void Publish(IntegrationEvent @event)
{ {
private readonly ILogger<EventBusKafka> _logger; var producer = _connectionPool.Producer();
private readonly IEventBusSubscriptionsManager _subsManager; try
private readonly IKafkaConnectionPool _connectionPool;
private readonly KafkaOptions _options;
public EventBusKafka(ILogger<EventBusKafka> logger,
IEventBusSubscriptionsManager subsManager,
IKafkaConnectionPool connectionPool,
IOptions<KafkaOptions> options)
{ {
_logger = logger; var eventName = @event.GetType().Name;
_subsManager = subsManager; var body = Protobuf.Serialize(JsonConvert.SerializeObject(@event));
_connectionPool = connectionPool; DeliveryResult<string, byte[]> result = producer.ProduceAsync(_options.Topic, new Message<string, byte[]>
_options = options.Value; {
Key = eventName,
Value = body
}).ConfigureAwait(false).GetAwaiter().GetResult();
} }
/// <summary> catch (Exception ex)
/// 发布
/// </summary>
public void Publish(IntegrationEvent @event)
{ {
var producer = _connectionPool.Producer(); _logger.LogWarning($"Could not publish event: {@event.Id.ToString("N")} ({ex.Message}); Message:{ JsonConvert.SerializeObject(@event)}");
try
{
var eventName = @event.GetType().Name;
var body = Protobuf.Serialize(JsonConvert.SerializeObject(@event));
DeliveryResult<string, byte[]> result = producer.ProduceAsync(_options.Topic, new Message<string, byte[]>
{
Key = eventName,
Value = body
}).ConfigureAwait(false).GetAwaiter().GetResult();
}
catch (Exception ex)
{
_logger.LogWarning($"Could not publish event: {@event.Id.ToString("N")} ({ex.Message}); Message:{ JsonConvert.SerializeObject(@event)}");
}
finally
{
//放入连接池中
_connectionPool.Return(producer);
}
} }
finally
/// <summary>
/// 订阅
/// 动态
/// </summary>
/// <typeparam name="TH">事件处理器</typeparam>
/// <param name="eventName">事件名</param>
public void SubscribeDynamic<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler
{ {
_logger.LogInformation("Subscribing to dynamic event {EventName} with {EventHandler}", eventName, typeof(TH).GetGenericTypeName()); //放入连接池中
_connectionPool.Return(producer);
_subsManager.AddDynamicSubscription<TH>(eventName);
} }
}
/// <summary> /// <summary>
/// 订阅 /// 订阅
/// </summary> /// 动态
/// <typeparam name="T">约束:事件模型</typeparam> /// </summary>
/// <typeparam name="TH">约束:事件处理器<事件模型></typeparam> /// <typeparam name="TH">事件处理器</typeparam>
public void Subscribe<T, TH>() /// <param name="eventName">事件名</param>
where T : IntegrationEvent public void SubscribeDynamic<TH>(string eventName)
where TH : IIntegrationEventHandler<T> where TH : IDynamicIntegrationEventHandler
{ {
var eventName = _subsManager.GetEventKey<T>(); _logger.LogInformation("Subscribing to dynamic event {EventName} with {EventHandler}", eventName, typeof(TH).GetGenericTypeName());
_logger.LogInformation("Subscribing to event {EventName} with {EventHandler}", eventName, typeof(TH).GetGenericTypeName()); _subsManager.AddDynamicSubscription<TH>(eventName);
}
_subsManager.AddSubscription<T, TH>(); /// <summary>
} /// 订阅
/// </summary>
/// <typeparam name="T">约束:事件模型</typeparam>
/// <typeparam name="TH">约束:事件处理器<事件模型></typeparam>
public void Subscribe<T, TH>()
where T : IntegrationEvent
where TH : IIntegrationEventHandler<T>
{
var eventName = _subsManager.GetEventKey<T>();
/// <summary> _logger.LogInformation("Subscribing to event {EventName} with {EventHandler}", eventName, typeof(TH).GetGenericTypeName());
/// 取消订阅
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TH"></typeparam>
public void Unsubscribe<T, TH>()
where T : IntegrationEvent
where TH : IIntegrationEventHandler<T>
{
var eventName = _subsManager.GetEventKey<T>();
_logger.LogInformation("Unsubscribing from event {EventName}", eventName); _subsManager.AddSubscription<T, TH>();
}
_subsManager.RemoveSubscription<T, TH>(); /// <summary>
} /// 取消订阅
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TH"></typeparam>
public void Unsubscribe<T, TH>()
where T : IntegrationEvent
where TH : IIntegrationEventHandler<T>
{
var eventName = _subsManager.GetEventKey<T>();
public void UnsubscribeDynamic<TH>(string eventName) _logger.LogInformation("Unsubscribing from event {EventName}", eventName);
where TH : IDynamicIntegrationEventHandler
{
_subsManager.RemoveDynamicSubscription<TH>(eventName);
}
public void Dispose() _subsManager.RemoveSubscription<T, TH>();
}
public void UnsubscribeDynamic<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler
{
_subsManager.RemoveDynamicSubscription<TH>(eventName);
}
public void Dispose()
{
if (_connectionPool != null)
{ {
if (_connectionPool != null) _connectionPool.Dispose();
{
_connectionPool.Dispose();
}
_subsManager.Clear();
} }
_subsManager.Clear();
} }
} }

@ -1,25 +1,23 @@
using Confluent.Kafka; using Confluent.Kafka;
using System;
namespace Tiobon.Core.EventBus namespace Tiobon.Core.EventBus;
/// <summary>
/// Kafka连接池
/// </summary>
public interface IKafkaConnectionPool:IDisposable
{ {
/// <summary> /// <summary>
/// Kafka连接池 /// 取对象
/// </summary> /// </summary>
public interface IKafkaConnectionPool:IDisposable /// <returns></returns>
{ IProducer<string, byte[]> Producer();
/// <summary>
/// 取对象
/// </summary>
/// <returns></returns>
IProducer<string, byte[]> Producer();
/// <summary> /// <summary>
/// 将对象放入连接池 /// 将对象放入连接池
/// </summary> /// </summary>
/// <param name="producer"></param> /// <param name="producer"></param>
/// <returns></returns> /// <returns></returns>
bool Return(IProducer<string, byte[]> producer); bool Return(IProducer<string, byte[]> producer);
}
} }

@ -1,79 +1,76 @@
using Confluent.Kafka; using Confluent.Kafka;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Threading;
namespace Tiobon.Core.EventBus namespace Tiobon.Core.EventBus;
/// <summary>
/// Kafka producer 连接池管理
/// 可以使用微软官方的对象池进行构造ObjectPool
/// </summary>
public class KafkaConnectionPool : IKafkaConnectionPool
{ {
private readonly KafkaOptions _options;
private ConcurrentQueue<IProducer<string, byte[]>> _producerPool = new();
private int _currentCount;
private int _maxSize;
public KafkaConnectionPool(IOptions<KafkaOptions> options)
{
_options = options.Value;
_maxSize = _options.ConnectionPoolSize;
}
/// <summary> /// <summary>
/// Kafka producer 连接池管理 /// 取对象
/// 可以使用微软官方的对象池进行构造ObjectPool
/// </summary> /// </summary>
public class KafkaConnectionPool : IKafkaConnectionPool /// <returns></returns>
public IProducer<string,byte[]> Producer()
{ {
private readonly KafkaOptions _options; if (_producerPool.TryDequeue(out var producer))
private ConcurrentQueue<IProducer<string, byte[]>> _producerPool = new();
private int _currentCount;
private int _maxSize;
public KafkaConnectionPool(IOptions<KafkaOptions> options)
{ {
_options = options.Value; Interlocked.Decrement(ref _currentCount);
_maxSize = _options.ConnectionPoolSize; return producer;
} }
/// <summary> var config = new ProducerConfig()
/// 取对象
/// </summary>
/// <returns></returns>
public IProducer<string,byte[]> Producer()
{ {
if (_producerPool.TryDequeue(out var producer)) BootstrapServers = _options.Servers,
{ QueueBufferingMaxMessages = 10,
Interlocked.Decrement(ref _currentCount); MessageTimeoutMs = 5000,
return producer; RequestTimeoutMs = 3000
} };
var config = new ProducerConfig()
{
BootstrapServers = _options.Servers,
QueueBufferingMaxMessages = 10,
MessageTimeoutMs = 5000,
RequestTimeoutMs = 3000
};
producer = new ProducerBuilder<string, byte[]>(config) producer = new ProducerBuilder<string, byte[]>(config)
.Build(); .Build();
return producer; return producer;
} }
/// <summary> /// <summary>
/// 将对象放入连接池 /// 将对象放入连接池
/// </summary> /// </summary>
/// <param name="producer"></param> /// <param name="producer"></param>
/// <returns></returns> /// <returns></returns>
public bool Return(IProducer<string, byte[]> producer) public bool Return(IProducer<string, byte[]> producer)
{
if (Interlocked.Increment(ref _currentCount) <= _maxSize)
{ {
if (Interlocked.Increment(ref _currentCount) <= _maxSize) _producerPool.Enqueue(producer);
{ return true;
_producerPool.Enqueue(producer); }
return true;
}
producer.Dispose(); producer.Dispose();
Interlocked.Decrement(ref _currentCount); Interlocked.Decrement(ref _currentCount);
return false; return false;
} }
public void Dispose() public void Dispose()
{
_maxSize = 0;
_currentCount = 0;
while (_producerPool.TryDequeue(out var context))
{ {
_maxSize = 0; context?.Dispose();
_currentCount = 0;
while (_producerPool.TryDequeue(out var context))
{
context?.Dispose();
}
} }
} }
} }

@ -6,157 +6,152 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Tiobon.Core.EventBus namespace Tiobon.Core.EventBus;
/// <summary>
/// Kafka consumer 监听服务
/// </summary>
public class KafkaConsumerHostService : BackgroundService
{ {
/// <summary> private readonly string AUTOFAC_SCOPE_NAME = "Tioboncore_event_bus";
/// Kafka consumer 监听服务 private readonly ILogger<KafkaConsumerHostService> _logger;
/// </summary> private readonly IConsumer<string, byte[]> _consumer;
public class KafkaConsumerHostService : BackgroundService private readonly KafkaOptions _options;
private readonly IEventBusSubscriptionsManager _subsManager;
private readonly ILifetimeScope _autofac;
private CancellationTokenSource cts = new();
public KafkaConsumerHostService(ILogger<KafkaConsumerHostService> logger,
IOptions<KafkaOptions> options,
IEventBusSubscriptionsManager eventBusSubscriptionsManager,
ILifetimeScope autofac)
{ {
private readonly string AUTOFAC_SCOPE_NAME = "Tioboncore_event_bus"; _autofac = autofac;
private readonly ILogger<KafkaConsumerHostService> _logger; _subsManager = eventBusSubscriptionsManager;
private readonly IConsumer<string, byte[]> _consumer; _logger = logger;
private readonly KafkaOptions _options; _options = options.Value;
private readonly IEventBusSubscriptionsManager _subsManager; _consumer = new ConsumerBuilder<string, byte[]>(new ConsumerConfig
private readonly ILifetimeScope _autofac;
private CancellationTokenSource cts = new();
public KafkaConsumerHostService(ILogger<KafkaConsumerHostService> logger,
IOptions<KafkaOptions> options,
IEventBusSubscriptionsManager eventBusSubscriptionsManager,
ILifetimeScope autofac)
{ {
_autofac = autofac; BootstrapServers = _options.Servers,
_subsManager = eventBusSubscriptionsManager; GroupId = _options.GroupId,
_logger = logger; AutoOffsetReset = AutoOffsetReset.Earliest,
_options = options.Value; AllowAutoCreateTopics = true,
_consumer = new ConsumerBuilder<string, byte[]>(new ConsumerConfig EnableAutoCommit = false,
{ LogConnectionClose = false
BootstrapServers = _options.Servers, }).SetErrorHandler(ConsumerClient_OnConsumeError)
GroupId = _options.GroupId, .Build();
AutoOffsetReset = AutoOffsetReset.Earliest, }
AllowAutoCreateTopics = true, protected override async Task ExecuteAsync(CancellationToken stoppingToken)
EnableAutoCommit = false, {
LogConnectionClose = false var result = await FetchTopicAsync();
}).SetErrorHandler(ConsumerClient_OnConsumeError) if (result)
.Build();
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{ {
var result = await FetchTopicAsync(); _consumer.Subscribe(_options.Topic);
if (result) while (!cts.Token.IsCancellationRequested)
{ {
_consumer.Subscribe(_options.Topic); var consumerResult = _consumer.Consume(cts.Token);
while (!cts.Token.IsCancellationRequested) try
{ {
var consumerResult = _consumer.Consume(cts.Token); if (consumerResult.IsPartitionEOF || consumerResult.Message.Value == null) continue;
try
{
if (consumerResult.IsPartitionEOF || consumerResult.Message.Value == null) continue;
var @event = Protobuf.Deserialize<string>(consumerResult.Message.Value); var @event = Protobuf.Deserialize<string>(consumerResult.Message.Value);
await ProcessEvent(consumerResult.Message.Key, @event); await ProcessEvent(consumerResult.Message.Key, @event);
} }
catch (ConsumeException e) catch (ConsumeException e)
{ {
_logger.LogError($"Error occured: {e.Error.Reason}"); _logger.LogError($"Error occured: {e.Error.Reason}");
} }
finally finally
{ {
_consumer.Commit(consumerResult); _consumer.Commit(consumerResult);
}
} }
} }
} }
public override Task StopAsync(CancellationToken cancellationToken) }
public override Task StopAsync(CancellationToken cancellationToken)
{
cts.Cancel();
_logger.LogInformation("kafka consumer stop and disposable");
_consumer.Dispose();
return base.StopAsync(cancellationToken);
}
/// <summary>
/// 检测当前Topic是否存在
/// </summary>
/// <returns></returns>
private async Task<bool> FetchTopicAsync()
{
if (string.IsNullOrEmpty(_options.Topic))
throw new ArgumentNullException(nameof(_options.Topic));
try
{ {
cts.Cancel(); var config = new AdminClientConfig { BootstrapServers = _options.Servers };
_logger.LogInformation("kafka consumer stop and disposable"); using var adminClient = new AdminClientBuilder(config).Build();
_consumer.Dispose(); await adminClient.CreateTopicsAsync(Enumerable.Range(0,1).Select(u=> new TopicSpecification
return base.StopAsync(cancellationToken); {
Name = _options.Topic,
NumPartitions = _options.NumPartitions
}));
} }
/// <summary> catch (CreateTopicsException ex) when (ex.Message.Contains("already exists"))
/// 检测当前Topic是否存在
/// </summary>
/// <returns></returns>
private async Task<bool> FetchTopicAsync()
{ {
if (string.IsNullOrEmpty(_options.Topic))
throw new ArgumentNullException(nameof(_options.Topic));
try
{
var config = new AdminClientConfig { BootstrapServers = _options.Servers };
using var adminClient = new AdminClientBuilder(config).Build();
await adminClient.CreateTopicsAsync(Enumerable.Range(0,1).Select(u=> new TopicSpecification
{
Name = _options.Topic,
NumPartitions = _options.NumPartitions
}));
}
catch (CreateTopicsException ex) when (ex.Message.Contains("already exists"))
{
}
catch (Exception ex)
{
_logger.LogError("An error was encountered when automatically creating topic! -->" + ex.Message);
return false;
}
return true;
} }
/// <summary> catch (Exception ex)
/// 接收到消息进行处理
/// </summary>
/// <param name="eventName">事件名称</param>
/// <param name="message">消息内容</param>
/// <returns></returns>
private async Task ProcessEvent(string eventName, string message)
{ {
_logger.LogTrace("Processing Kafka event: {EventName}", eventName); _logger.LogError("An error was encountered when automatically creating topic! -->" + ex.Message);
return false;
}
return true;
}
/// <summary>
/// 接收到消息进行处理
/// </summary>
/// <param name="eventName">事件名称</param>
/// <param name="message">消息内容</param>
/// <returns></returns>
private async Task ProcessEvent(string eventName, string message)
{
_logger.LogTrace("Processing Kafka event: {EventName}", eventName);
if (_subsManager.HasSubscriptionsForEvent(eventName)) if (_subsManager.HasSubscriptionsForEvent(eventName))
{
using (var scope = _autofac.BeginLifetimeScope(AUTOFAC_SCOPE_NAME))
{ {
using (var scope = _autofac.BeginLifetimeScope(AUTOFAC_SCOPE_NAME)) var subscriptions = _subsManager.GetHandlersForEvent(eventName);
foreach (var subscription in subscriptions)
{ {
var subscriptions = _subsManager.GetHandlersForEvent(eventName); if (subscription.IsDynamic)
foreach (var subscription in subscriptions)
{ {
if (subscription.IsDynamic) var handler = scope.ResolveOptional(subscription.HandlerType) as IDynamicIntegrationEventHandler;
{ if (handler == null) continue;
var handler = scope.ResolveOptional(subscription.HandlerType) as IDynamicIntegrationEventHandler; dynamic eventData = JObject.Parse(message);
if (handler == null) continue;
dynamic eventData = JObject.Parse(message);
await Task.Yield(); await Task.Yield();
await handler.Handle(eventData); await handler.Handle(eventData);
} }
else else
{ {
var handler = scope.ResolveOptional(subscription.HandlerType); var handler = scope.ResolveOptional(subscription.HandlerType);
if (handler == null) continue; if (handler == null) continue;
var eventType = _subsManager.GetEventTypeByName(eventName); var eventType = _subsManager.GetEventTypeByName(eventName);
var integrationEvent = JsonConvert.DeserializeObject(message, eventType); var integrationEvent = JsonConvert.DeserializeObject(message, eventType);
var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType); var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType);
await Task.Yield(); await Task.Yield();
await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent }); await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent });
}
} }
} }
} }
else
{
_logger.LogWarning("No subscription for Kafka event: {EventName}", eventName);
}
} }
else
private void ConsumerClient_OnConsumeError(IConsumer<string, byte[]> consumer, Error e)
{ {
_logger.LogError("An error occurred during connect kafka:" + e.Reason); _logger.LogWarning("No subscription for Kafka event: {EventName}", eventName);
} }
} }
private void ConsumerClient_OnConsumeError(IConsumer<string, byte[]> consumer, Error e)
{
_logger.LogError("An error occurred during connect kafka:" + e.Reason);
}
} }

@ -1,28 +1,27 @@
 
namespace Tiobon.Core.EventBus namespace Tiobon.Core.EventBus;
/// <summary>
/// Kafka 配置项
/// </summary>
public class KafkaOptions
{ {
public int ConnectionPoolSize { get; set; } = 10;
/// <summary>
/// 地址
/// </summary>
public string Servers { get; set; }
/// <summary>
/// 主题
/// </summary>
public string Topic { get; set; }
/// <summary>
/// 消费者组Id
/// </summary>
public string GroupId { get; set; }
/// <summary> /// <summary>
/// Kafka 配置项 /// 主题分区
/// </summary> /// </summary>
public class KafkaOptions public int NumPartitions { get; set; }
{
public int ConnectionPoolSize { get; set; } = 10;
/// <summary>
/// 地址
/// </summary>
public string Servers { get; set; }
/// <summary>
/// 主题
/// </summary>
public string Topic { get; set; }
/// <summary>
/// 消费者组Id
/// </summary>
public string GroupId { get; set; }
/// <summary>
/// 主题分区
/// </summary>
public int NumPartitions { get; set; }
}
} }

@ -1,32 +1,29 @@
using System; namespace Tiobon.Core.EventBus;
using System.IO;
namespace Tiobon.Core.EventBus public class Protobuf
{ {
public class Protobuf /// <summary>
/// Protobuf 反序列化
/// </summary>
public static T Deserialize<T>(ReadOnlySpan<byte> data)
{
Stream stream = new MemoryStream(data.ToArray());
var info = ProtoBuf.Serializer.Deserialize<T>(stream);
return info;
}
/// <summary>
/// 通过Protobuf 转字节
/// </summary>
public static byte[] Serialize<T>(T data)
{ {
/// <summary> byte[] datas;
/// Protobuf 反序列化 using (var stream = new MemoryStream())
/// </summary>
public static T Deserialize<T>(ReadOnlySpan<byte> data)
{ {
Stream stream = new MemoryStream(data.ToArray()); ProtoBuf.Serializer.Serialize(stream, data);
var info = ProtoBuf.Serializer.Deserialize<T>(stream); datas = stream.ToArray();
return info;
} }
/// <summary> return datas;
/// 通过Protobuf 转字节
/// </summary>
public static byte[] Serialize<T>(T data)
{
byte[] datas;
using (var stream = new MemoryStream())
{
ProtoBuf.Serializer.Serialize(stream, data);
datas = stream.ToArray();
}
return datas;
}
} }
} }

@ -1,183 +1,177 @@
using System; namespace Tiobon.Core.EventBus;
using System.Collections.Generic;
using System.Linq; /// <summary>
/// 基于内存
namespace Tiobon.Core.EventBus /// 事件总线订阅管理器
/// 单例模式
/// </summary>
public partial class InMemoryEventBusSubscriptionsManager : IEventBusSubscriptionsManager
{ {
private readonly Dictionary<string, List<SubscriptionInfo>> _handlers;
private readonly List<Type> _eventTypes;
public event EventHandler<string> OnEventRemoved;
public InMemoryEventBusSubscriptionsManager()
{
_handlers = new Dictionary<string, List<SubscriptionInfo>>();
_eventTypes = new List<Type>();
}
public bool IsEmpty => !_handlers.Keys.Any();
public void Clear() => _handlers.Clear();
/// <summary> /// <summary>
/// 基于内存 /// 添加动态订阅
/// 事件总线订阅管理器
/// 单例模式
/// </summary> /// </summary>
public partial class InMemoryEventBusSubscriptionsManager : IEventBusSubscriptionsManager /// <typeparam name="TH">约束:动态事件处理器接口</typeparam>
/// <param name="eventName"></param>
public void AddDynamicSubscription<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler
{ {
private readonly Dictionary<string, List<SubscriptionInfo>> _handlers; DoAddSubscription(typeof(TH), eventName, isDynamic: true);
private readonly List<Type> _eventTypes; }
public event EventHandler<string> OnEventRemoved; /// <summary>
/// 添加订阅
/// </summary>
/// <typeparam name="T">约束:事件</typeparam>
/// <typeparam name="TH">约束:事件处理器接口<事件></typeparam>
public void AddSubscription<T, TH>()
where T : IntegrationEvent
where TH : IIntegrationEventHandler<T>
{
var eventName = GetEventKey<T>();
DoAddSubscription(typeof(TH), eventName, isDynamic: false);
public InMemoryEventBusSubscriptionsManager() if (!_eventTypes.Contains(typeof(T)))
{ {
_handlers = new Dictionary<string, List<SubscriptionInfo>>(); _eventTypes.Add(typeof(T));
_eventTypes = new List<Type>();
} }
}
public bool IsEmpty => !_handlers.Keys.Any(); private void DoAddSubscription(Type handlerType, string eventName, bool isDynamic)
public void Clear() => _handlers.Clear(); {
if (!HasSubscriptionsForEvent(eventName))
/// <summary>
/// 添加动态订阅
/// </summary>
/// <typeparam name="TH">约束:动态事件处理器接口</typeparam>
/// <param name="eventName"></param>
public void AddDynamicSubscription<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler
{ {
DoAddSubscription(typeof(TH), eventName, isDynamic: true); _handlers.Add(eventName, new List<SubscriptionInfo>());
} }
/// <summary> if (_handlers[eventName].Any(s => s.HandlerType == handlerType))
/// 添加订阅
/// </summary>
/// <typeparam name="T">约束:事件</typeparam>
/// <typeparam name="TH">约束:事件处理器接口<事件></typeparam>
public void AddSubscription<T, TH>()
where T : IntegrationEvent
where TH : IIntegrationEventHandler<T>
{ {
var eventName = GetEventKey<T>(); throw new ArgumentException(
$"Handler Type {handlerType.Name} already registered for '{eventName}'", nameof(handlerType));
DoAddSubscription(typeof(TH), eventName, isDynamic: false);
if (!_eventTypes.Contains(typeof(T)))
{
_eventTypes.Add(typeof(T));
}
} }
private void DoAddSubscription(Type handlerType, string eventName, bool isDynamic) if (isDynamic)
{ {
if (!HasSubscriptionsForEvent(eventName)) _handlers[eventName].Add(SubscriptionInfo.Dynamic(handlerType));
{
_handlers.Add(eventName, new List<SubscriptionInfo>());
}
if (_handlers[eventName].Any(s => s.HandlerType == handlerType))
{
throw new ArgumentException(
$"Handler Type {handlerType.Name} already registered for '{eventName}'", nameof(handlerType));
}
if (isDynamic)
{
_handlers[eventName].Add(SubscriptionInfo.Dynamic(handlerType));
}
else
{
_handlers[eventName].Add(SubscriptionInfo.Typed(handlerType));
}
} }
else
/// <summary>
/// 移除动态订阅
/// </summary>
/// <typeparam name="TH"></typeparam>
/// <param name="eventName"></param>
public void RemoveDynamicSubscription<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler
{ {
var handlerToRemove = FindDynamicSubscriptionToRemove<TH>(eventName); _handlers[eventName].Add(SubscriptionInfo.Typed(handlerType));
DoRemoveHandler(eventName, handlerToRemove);
} }
}
/// <summary>
/// 移除动态订阅
/// </summary>
/// <typeparam name="TH"></typeparam>
/// <param name="eventName"></param>
public void RemoveDynamicSubscription<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler
{
var handlerToRemove = FindDynamicSubscriptionToRemove<TH>(eventName);
DoRemoveHandler(eventName, handlerToRemove);
}
public void RemoveSubscription<T, TH>()
where TH : IIntegrationEventHandler<T> public void RemoveSubscription<T, TH>()
where T : IntegrationEvent where TH : IIntegrationEventHandler<T>
{ where T : IntegrationEvent
var handlerToRemove = FindSubscriptionToRemove<T, TH>(); {
var eventName = GetEventKey<T>(); var handlerToRemove = FindSubscriptionToRemove<T, TH>();
DoRemoveHandler(eventName, handlerToRemove); var eventName = GetEventKey<T>();
} DoRemoveHandler(eventName, handlerToRemove);
}
private void DoRemoveHandler(string eventName, SubscriptionInfo subsToRemove) private void DoRemoveHandler(string eventName, SubscriptionInfo subsToRemove)
{
if (subsToRemove != null)
{ {
if (subsToRemove != null) _handlers[eventName].Remove(subsToRemove);
if (!_handlers[eventName].Any())
{ {
_handlers[eventName].Remove(subsToRemove); _handlers.Remove(eventName);
if (!_handlers[eventName].Any()) var eventType = _eventTypes.SingleOrDefault(e => e.Name == eventName);
if (eventType != null)
{ {
_handlers.Remove(eventName); _eventTypes.Remove(eventType);
var eventType = _eventTypes.SingleOrDefault(e => e.Name == eventName);
if (eventType != null)
{
_eventTypes.Remove(eventType);
}
RaiseOnEventRemoved(eventName);
} }
RaiseOnEventRemoved(eventName);
} }
}
public IEnumerable<SubscriptionInfo> GetHandlersForEvent<T>() where T : IntegrationEvent
{
var key = GetEventKey<T>();
return GetHandlersForEvent(key);
} }
public IEnumerable<SubscriptionInfo> GetHandlersForEvent(string eventName) => _handlers[eventName]; }
private void RaiseOnEventRemoved(string eventName)
{
var handler = OnEventRemoved;
handler?.Invoke(this, eventName);
}
private SubscriptionInfo FindDynamicSubscriptionToRemove<TH>(string eventName) public IEnumerable<SubscriptionInfo> GetHandlersForEvent<T>() where T : IntegrationEvent
where TH : IDynamicIntegrationEventHandler {
{ var key = GetEventKey<T>();
return DoFindSubscriptionToRemove(eventName, typeof(TH)); return GetHandlersForEvent(key);
} }
public IEnumerable<SubscriptionInfo> GetHandlersForEvent(string eventName) => _handlers[eventName];
/// <summary> private void RaiseOnEventRemoved(string eventName)
/// 查询订阅并移除 {
/// </summary> var handler = OnEventRemoved;
/// <typeparam name="T"></typeparam> handler?.Invoke(this, eventName);
/// <typeparam name="TH"></typeparam> }
/// <returns></returns>
private SubscriptionInfo FindSubscriptionToRemove<T, TH>()
where T : IntegrationEvent
where TH : IIntegrationEventHandler<T>
{
var eventName = GetEventKey<T>();
return DoFindSubscriptionToRemove(eventName, typeof(TH));
}
private SubscriptionInfo DoFindSubscriptionToRemove(string eventName, Type handlerType)
{
if (!HasSubscriptionsForEvent(eventName))
{
return null;
}
return _handlers[eventName].SingleOrDefault(s => s.HandlerType == handlerType); private SubscriptionInfo FindDynamicSubscriptionToRemove<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler
{
return DoFindSubscriptionToRemove(eventName, typeof(TH));
}
} /// <summary>
/// 查询订阅并移除
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TH"></typeparam>
/// <returns></returns>
private SubscriptionInfo FindSubscriptionToRemove<T, TH>()
where T : IntegrationEvent
where TH : IIntegrationEventHandler<T>
{
var eventName = GetEventKey<T>();
return DoFindSubscriptionToRemove(eventName, typeof(TH));
}
public bool HasSubscriptionsForEvent<T>() where T : IntegrationEvent private SubscriptionInfo DoFindSubscriptionToRemove(string eventName, Type handlerType)
{
if (!HasSubscriptionsForEvent(eventName))
{ {
var key = GetEventKey<T>(); return null;
return HasSubscriptionsForEvent(key);
} }
public bool HasSubscriptionsForEvent(string eventName) => _handlers.ContainsKey(eventName);
public Type GetEventTypeByName(string eventName) => _eventTypes.SingleOrDefault(t => t.Name == eventName); return _handlers[eventName].SingleOrDefault(s => s.HandlerType == handlerType);
public string GetEventKey<T>()
{
return typeof(T).Name;
}
} }
public bool HasSubscriptionsForEvent<T>() where T : IntegrationEvent
{
var key = GetEventKey<T>();
return HasSubscriptionsForEvent(key);
}
public bool HasSubscriptionsForEvent(string eventName) => _handlers.ContainsKey(eventName);
public Type GetEventTypeByName(string eventName) => _eventTypes.SingleOrDefault(t => t.Name == eventName);
public string GetEventKey<T>()
{
return typeof(T).Name;
}
} }

@ -1,29 +1,25 @@
using System; namespace Tiobon.Core.EventBus;
namespace Tiobon.Core.EventBus /// <summary>
/// 订阅信息模型
/// </summary>
public class SubscriptionInfo
{ {
/// <summary> public bool IsDynamic { get; }
/// 订阅信息模型 public Type HandlerType { get; }
/// </summary>
public class SubscriptionInfo
{
public bool IsDynamic { get; }
public Type HandlerType { get; }
private SubscriptionInfo(bool isDynamic, Type handlerType)
{
IsDynamic = isDynamic;
HandlerType = handlerType;
}
public static SubscriptionInfo Dynamic(Type handlerType) private SubscriptionInfo(bool isDynamic, Type handlerType)
{ {
return new SubscriptionInfo(true, handlerType); IsDynamic = isDynamic;
} HandlerType = handlerType;
public static SubscriptionInfo Typed(Type handlerType)
{
return new SubscriptionInfo(false, handlerType);
}
} }
public static SubscriptionInfo Dynamic(Type handlerType)
{
return new SubscriptionInfo(true, handlerType);
}
public static SubscriptionInfo Typed(Type handlerType)
{
return new SubscriptionInfo(false, handlerType);
}
} }

@ -1,13 +1,10 @@
using System.Threading.Tasks; namespace Tiobon.Core.EventBus;
namespace Tiobon.Core.EventBus /// <summary>
/// 动态集成事件处理程序
/// 接口
/// </summary>
public interface IDynamicIntegrationEventHandler
{ {
/// <summary> Task Handle(dynamic eventData);
/// 动态集成事件处理程序
/// 接口
/// </summary>
public interface IDynamicIntegrationEventHandler
{
Task Handle(dynamic eventData);
}
} }

@ -1,49 +1,48 @@
namespace Tiobon.Core.EventBus namespace Tiobon.Core.EventBus;
/// <summary>
/// 事件总线
/// 接口
/// </summary>
public interface IEventBus
{ {
/// <summary> /// <summary>
/// 事件总线 /// 发布
/// 接口
/// </summary> /// </summary>
public interface IEventBus /// <param name="event">事件模型</param>
{ void Publish(IntegrationEvent @event);
/// <summary>
/// 发布
/// </summary>
/// <param name="event">事件模型</param>
void Publish(IntegrationEvent @event);
/// <summary> /// <summary>
/// 订阅 /// 订阅
/// </summary> /// </summary>
/// <typeparam name="T">约束:事件模型</typeparam> /// <typeparam name="T">约束:事件模型</typeparam>
/// <typeparam name="TH">约束:事件处理器<事件模型></typeparam> /// <typeparam name="TH">约束:事件处理器<事件模型></typeparam>
void Subscribe<T, TH>() void Subscribe<T, TH>()
where T : IntegrationEvent where T : IntegrationEvent
where TH : IIntegrationEventHandler<T>; where TH : IIntegrationEventHandler<T>;
/// <summary> /// <summary>
/// 取消订阅 /// 取消订阅
/// </summary> /// </summary>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
/// <typeparam name="TH"></typeparam> /// <typeparam name="TH"></typeparam>
void Unsubscribe<T, TH>() void Unsubscribe<T, TH>()
where TH : IIntegrationEventHandler<T> where TH : IIntegrationEventHandler<T>
where T : IntegrationEvent; where T : IntegrationEvent;
/// <summary> /// <summary>
/// 动态订阅 /// 动态订阅
/// </summary> /// </summary>
/// <typeparam name="TH">约束:事件处理器</typeparam> /// <typeparam name="TH">约束:事件处理器</typeparam>
/// <param name="eventName"></param> /// <param name="eventName"></param>
void SubscribeDynamic<TH>(string eventName) void SubscribeDynamic<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler; where TH : IDynamicIntegrationEventHandler;
/// <summary> /// <summary>
/// 动态取消订阅 /// 动态取消订阅
/// </summary> /// </summary>
/// <typeparam name="TH"></typeparam> /// <typeparam name="TH"></typeparam>
/// <param name="eventName"></param> /// <param name="eventName"></param>
void UnsubscribeDynamic<TH>(string eventName) void UnsubscribeDynamic<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler; where TH : IDynamicIntegrationEventHandler;
}
} }

@ -1,36 +1,31 @@
using System; namespace Tiobon.Core.EventBus;
using System.Collections.Generic;
namespace Tiobon.Core.EventBus /// <summary>
/// 事件总线订阅管理器
/// 接口
/// </summary>
public interface IEventBusSubscriptionsManager
{ {
/// <summary> bool IsEmpty { get; }
/// 事件总线订阅管理器 event EventHandler<string> OnEventRemoved;
/// 接口 void AddDynamicSubscription<TH>(string eventName)
/// </summary> where TH : IDynamicIntegrationEventHandler;
public interface IEventBusSubscriptionsManager
{
bool IsEmpty { get; }
event EventHandler<string> OnEventRemoved;
void AddDynamicSubscription<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler;
void AddSubscription<T, TH>() void AddSubscription<T, TH>()
where T : IntegrationEvent where T : IntegrationEvent
where TH : IIntegrationEventHandler<T>; where TH : IIntegrationEventHandler<T>;
void RemoveSubscription<T, TH>() void RemoveSubscription<T, TH>()
where TH : IIntegrationEventHandler<T> where TH : IIntegrationEventHandler<T>
where T : IntegrationEvent; where T : IntegrationEvent;
void RemoveDynamicSubscription<TH>(string eventName) void RemoveDynamicSubscription<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler; where TH : IDynamicIntegrationEventHandler;
bool HasSubscriptionsForEvent<T>() where T : IntegrationEvent;
bool HasSubscriptionsForEvent(string eventName);
Type GetEventTypeByName(string eventName);
void Clear();
IEnumerable<SubscriptionInfo> GetHandlersForEvent<T>() where T : IntegrationEvent;
IEnumerable<SubscriptionInfo> GetHandlersForEvent(string eventName);
string GetEventKey<T>();
}
bool HasSubscriptionsForEvent<T>() where T : IntegrationEvent;
bool HasSubscriptionsForEvent(string eventName);
Type GetEventTypeByName(string eventName);
void Clear();
IEnumerable<SubscriptionInfo> GetHandlersForEvent<T>() where T : IntegrationEvent;
IEnumerable<SubscriptionInfo> GetHandlersForEvent(string eventName);
string GetEventKey<T>();
} }

@ -1,23 +1,20 @@
using System.Threading.Tasks; namespace Tiobon.Core.EventBus;
namespace Tiobon.Core.EventBus /// <summary>
/// 集成事件处理程序
/// 泛型接口
/// </summary>
/// <typeparam name="TIntegrationEvent"></typeparam>
public interface IIntegrationEventHandler<in TIntegrationEvent> : IIntegrationEventHandler
where TIntegrationEvent : IntegrationEvent
{ {
/// <summary> Task Handle(TIntegrationEvent @event);
/// 集成事件处理程序 }
/// 泛型接口
/// </summary>
/// <typeparam name="TIntegrationEvent"></typeparam>
public interface IIntegrationEventHandler<in TIntegrationEvent> : IIntegrationEventHandler
where TIntegrationEvent : IntegrationEvent
{
Task Handle(TIntegrationEvent @event);
}
/// <summary> /// <summary>
/// 集成事件处理程序 /// 集成事件处理程序
/// 基 接口 /// 基 接口
/// </summary> /// </summary>
public interface IIntegrationEventHandler public interface IIntegrationEventHandler
{ {
}
} }

@ -1,31 +1,29 @@
using Newtonsoft.Json; using Newtonsoft.Json;
using System;
namespace Tiobon.Core.EventBus namespace Tiobon.Core.EventBus;
/// <summary>
/// 事件模型
/// 基类
/// </summary>
public class IntegrationEvent
{ {
/// <summary> public IntegrationEvent()
/// 事件模型
/// 基类
/// </summary>
public class IntegrationEvent
{ {
public IntegrationEvent() Id = Guid.NewGuid();
{ CreationDate = DateTime.UtcNow;
Id = Guid.NewGuid(); }
CreationDate = DateTime.UtcNow;
}
[JsonConstructor] [JsonConstructor]
public IntegrationEvent(Guid id, DateTime createDate) public IntegrationEvent(Guid id, DateTime createDate)
{ {
Id = id; Id = id;
CreationDate = createDate; CreationDate = createDate;
} }
[JsonProperty] [JsonProperty]
public Guid Id { get; private set; } public Guid Id { get; private set; }
[JsonProperty] [JsonProperty]
public DateTime CreationDate { get; private set; } public DateTime CreationDate { get; private set; }
}
} }

@ -11,341 +11,340 @@ using System.Net.Sockets;
using System.Text; using System.Text;
using Tiobon.Core.Extensions; using Tiobon.Core.Extensions;
namespace Tiobon.Core.EventBus namespace Tiobon.Core.EventBus;
/// <summary>
/// 基于RabbitMQ的事件总线
/// </summary>
public class EventBusRabbitMQ : IEventBus, IDisposable
{ {
const string BROKER_NAME = "Tioboncore_event_bus";
private readonly IRabbitMQPersistentConnection _persistentConnection;
private readonly ILogger<EventBusRabbitMQ> _logger;
private readonly IEventBusSubscriptionsManager _subsManager;
private readonly ILifetimeScope _autofac;
private readonly string AUTOFAC_SCOPE_NAME = "Tioboncore_event_bus";
private readonly int _retryCount;
private IModel _consumerChannel;
private string _queueName;
/// <summary>
/// RabbitMQ事件总线
/// </summary>
/// <param name="persistentConnection">RabbitMQ持久连接</param>
/// <param name="logger">日志</param>
/// <param name="autofac">autofac容器</param>
/// <param name="subsManager">事件总线订阅管理器</param>
/// <param name="queueName">队列名称</param>
/// <param name="retryCount">重试次数</param>
public EventBusRabbitMQ(IRabbitMQPersistentConnection persistentConnection, ILogger<EventBusRabbitMQ> logger,
ILifetimeScope autofac,
IEventBusSubscriptionsManager subsManager,
string queueName = null,
int retryCount = 5)
{
_persistentConnection = persistentConnection ?? throw new ArgumentNullException(nameof(persistentConnection));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_subsManager = subsManager ?? new InMemoryEventBusSubscriptionsManager();
_queueName = queueName;
_consumerChannel = CreateConsumerChannel();
_autofac = autofac;
_retryCount = retryCount;
_subsManager.OnEventRemoved += SubsManager_OnEventRemoved;
}
/// <summary> /// <summary>
/// 基于RabbitMQ的事件总线 /// 订阅管理器事件
/// </summary> /// </summary>
public class EventBusRabbitMQ : IEventBus, IDisposable /// <param name="sender"></param>
/// <param name="eventName"></param>
private void SubsManager_OnEventRemoved(object sender, string eventName)
{ {
const string BROKER_NAME = "Tioboncore_event_bus"; if (!_persistentConnection.IsConnected)
private readonly IRabbitMQPersistentConnection _persistentConnection;
private readonly ILogger<EventBusRabbitMQ> _logger;
private readonly IEventBusSubscriptionsManager _subsManager;
private readonly ILifetimeScope _autofac;
private readonly string AUTOFAC_SCOPE_NAME = "Tioboncore_event_bus";
private readonly int _retryCount;
private IModel _consumerChannel;
private string _queueName;
/// <summary>
/// RabbitMQ事件总线
/// </summary>
/// <param name="persistentConnection">RabbitMQ持久连接</param>
/// <param name="logger">日志</param>
/// <param name="autofac">autofac容器</param>
/// <param name="subsManager">事件总线订阅管理器</param>
/// <param name="queueName">队列名称</param>
/// <param name="retryCount">重试次数</param>
public EventBusRabbitMQ(IRabbitMQPersistentConnection persistentConnection, ILogger<EventBusRabbitMQ> logger,
ILifetimeScope autofac,
IEventBusSubscriptionsManager subsManager,
string queueName = null,
int retryCount = 5)
{ {
_persistentConnection = persistentConnection ?? throw new ArgumentNullException(nameof(persistentConnection)); _persistentConnection.TryConnect();
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_subsManager = subsManager ?? new InMemoryEventBusSubscriptionsManager();
_queueName = queueName;
_consumerChannel = CreateConsumerChannel();
_autofac = autofac;
_retryCount = retryCount;
_subsManager.OnEventRemoved += SubsManager_OnEventRemoved;
} }
/// <summary> using (var channel = _persistentConnection.CreateModel())
/// 订阅管理器事件
/// </summary>
/// <param name="sender"></param>
/// <param name="eventName"></param>
private void SubsManager_OnEventRemoved(object sender, string eventName)
{ {
if (!_persistentConnection.IsConnected) channel.QueueUnbind(queue: _queueName,
{ exchange: BROKER_NAME,
_persistentConnection.TryConnect(); routingKey: eventName);
}
using (var channel = _persistentConnection.CreateModel()) if (_subsManager.IsEmpty)
{ {
channel.QueueUnbind(queue: _queueName, _queueName = string.Empty;
exchange: BROKER_NAME, _consumerChannel.Close();
routingKey: eventName);
if (_subsManager.IsEmpty)
{
_queueName = string.Empty;
_consumerChannel.Close();
}
} }
} }
}
/// <summary> /// <summary>
/// 发布 /// 发布
/// </summary> /// </summary>
/// <param name="event">事件模型</param> /// <param name="event">事件模型</param>
public void Publish(IntegrationEvent @event) public void Publish(IntegrationEvent @event)
{
if (!_persistentConnection.IsConnected)
{ {
if (!_persistentConnection.IsConnected) _persistentConnection.TryConnect();
}
var policy = RetryPolicy.Handle<BrokerUnreachableException>()
.Or<SocketException>()
.WaitAndRetry(_retryCount, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (ex, time) =>
{ {
_persistentConnection.TryConnect(); _logger.LogWarning(ex, "Could not publish event: {EventId} after {Timeout}s ({ExceptionMessage})", @event.Id, $"{time.TotalSeconds:n1}", ex.Message);
} });
var policy = RetryPolicy.Handle<BrokerUnreachableException>() var eventName = @event.GetType().Name;
.Or<SocketException>()
.WaitAndRetry(_retryCount, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (ex, time) =>
{
_logger.LogWarning(ex, "Could not publish event: {EventId} after {Timeout}s ({ExceptionMessage})", @event.Id, $"{time.TotalSeconds:n1}", ex.Message);
});
var eventName = @event.GetType().Name; _logger.LogTrace("Creating RabbitMQ channel to publish event: {EventId} ({EventName})", @event.Id, eventName);
_logger.LogTrace("Creating RabbitMQ channel to publish event: {EventId} ({EventName})", @event.Id, eventName); using (var channel = _persistentConnection.CreateModel())
{
using (var channel = _persistentConnection.CreateModel()) _logger.LogTrace("Declaring RabbitMQ exchange to publish event: {EventId}", @event.Id);
{
_logger.LogTrace("Declaring RabbitMQ exchange to publish event: {EventId}", @event.Id); channel.ExchangeDeclare(exchange: BROKER_NAME, type: "direct");
channel.ExchangeDeclare(exchange: BROKER_NAME, type: "direct"); var message = JsonConvert.SerializeObject(@event);
var body = Encoding.UTF8.GetBytes(message);
var message = JsonConvert.SerializeObject(@event); policy.Execute(() =>
var body = Encoding.UTF8.GetBytes(message); {
var properties = channel.CreateBasicProperties();
properties.DeliveryMode = 2; // persistent
policy.Execute(() => _logger.LogTrace("Publishing event to RabbitMQ: {EventId}", @event.Id);
{
var properties = channel.CreateBasicProperties(); channel.BasicPublish(
properties.DeliveryMode = 2; // persistent exchange: BROKER_NAME,
routingKey: eventName,
_logger.LogTrace("Publishing event to RabbitMQ: {EventId}", @event.Id); mandatory: true,
basicProperties: properties,
channel.BasicPublish( body: body);
exchange: BROKER_NAME, });
routingKey: eventName,
mandatory: true,
basicProperties: properties,
body: body);
});
}
} }
}
/// <summary> /// <summary>
/// 订阅 /// 订阅
/// 动态 /// 动态
/// </summary> /// </summary>
/// <typeparam name="TH">事件处理器</typeparam> /// <typeparam name="TH">事件处理器</typeparam>
/// <param name="eventName">事件名</param> /// <param name="eventName">事件名</param>
public void SubscribeDynamic<TH>(string eventName) public void SubscribeDynamic<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler where TH : IDynamicIntegrationEventHandler
{ {
_logger.LogInformation("Subscribing to dynamic event {EventName} with {EventHandler}", eventName, typeof(TH).GetGenericTypeName()); _logger.LogInformation("Subscribing to dynamic event {EventName} with {EventHandler}", eventName, typeof(TH).GetGenericTypeName());
DoInternalSubscription(eventName); DoInternalSubscription(eventName);
_subsManager.AddDynamicSubscription<TH>(eventName); _subsManager.AddDynamicSubscription<TH>(eventName);
StartBasicConsume(); StartBasicConsume();
} }
/// <summary> /// <summary>
/// 订阅 /// 订阅
/// </summary> /// </summary>
/// <typeparam name="T">约束:事件模型</typeparam> /// <typeparam name="T">约束:事件模型</typeparam>
/// <typeparam name="TH">约束:事件处理器<事件模型></typeparam> /// <typeparam name="TH">约束:事件处理器<事件模型></typeparam>
public void Subscribe<T, TH>() public void Subscribe<T, TH>()
where T : IntegrationEvent where T : IntegrationEvent
where TH : IIntegrationEventHandler<T> where TH : IIntegrationEventHandler<T>
{ {
var eventName = _subsManager.GetEventKey<T>(); var eventName = _subsManager.GetEventKey<T>();
DoInternalSubscription(eventName); DoInternalSubscription(eventName);
_logger.LogInformation("Subscribing to event {EventName} with {EventHandler}", eventName, typeof(TH).GetGenericTypeName()); _logger.LogInformation("Subscribing to event {EventName} with {EventHandler}", eventName, typeof(TH).GetGenericTypeName());
ConsoleHelper.WriteSuccessLine($"Subscribing to event {eventName} with {typeof(TH).GetGenericTypeName()}"); ConsoleHelper.WriteSuccessLine($"Subscribing to event {eventName} with {typeof(TH).GetGenericTypeName()}");
_subsManager.AddSubscription<T, TH>(); _subsManager.AddSubscription<T, TH>();
StartBasicConsume(); StartBasicConsume();
} }
private void DoInternalSubscription(string eventName) private void DoInternalSubscription(string eventName)
{
var containsKey = _subsManager.HasSubscriptionsForEvent(eventName);
if (!containsKey)
{ {
var containsKey = _subsManager.HasSubscriptionsForEvent(eventName); if (!_persistentConnection.IsConnected)
if (!containsKey)
{ {
if (!_persistentConnection.IsConnected) _persistentConnection.TryConnect();
{ }
_persistentConnection.TryConnect();
}
using (var channel = _persistentConnection.CreateModel()) using (var channel = _persistentConnection.CreateModel())
{ {
channel.QueueBind(queue: _queueName, channel.QueueBind(queue: _queueName,
exchange: BROKER_NAME, exchange: BROKER_NAME,
routingKey: eventName); routingKey: eventName);
}
} }
} }
}
/// <summary> /// <summary>
/// 取消订阅 /// 取消订阅
/// </summary> /// </summary>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
/// <typeparam name="TH"></typeparam> /// <typeparam name="TH"></typeparam>
public void Unsubscribe<T, TH>() public void Unsubscribe<T, TH>()
where T : IntegrationEvent where T : IntegrationEvent
where TH : IIntegrationEventHandler<T> where TH : IIntegrationEventHandler<T>
{ {
var eventName = _subsManager.GetEventKey<T>(); var eventName = _subsManager.GetEventKey<T>();
_logger.LogInformation("Unsubscribing from event {EventName}", eventName); _logger.LogInformation("Unsubscribing from event {EventName}", eventName);
_subsManager.RemoveSubscription<T, TH>(); _subsManager.RemoveSubscription<T, TH>();
} }
public void UnsubscribeDynamic<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler
{
_subsManager.RemoveDynamicSubscription<TH>(eventName);
}
public void UnsubscribeDynamic<TH>(string eventName) public void Dispose()
where TH : IDynamicIntegrationEventHandler {
if (_consumerChannel != null)
{ {
_subsManager.RemoveDynamicSubscription<TH>(eventName); _consumerChannel.Dispose();
} }
public void Dispose() _subsManager.Clear();
{ }
if (_consumerChannel != null)
{
_consumerChannel.Dispose();
}
_subsManager.Clear(); /// <summary>
} /// 开始基本消费
/// </summary>
private void StartBasicConsume()
{
_logger.LogTrace("Starting RabbitMQ basic consume");
/// <summary> if (_consumerChannel != null)
/// 开始基本消费
/// </summary>
private void StartBasicConsume()
{ {
_logger.LogTrace("Starting RabbitMQ basic consume"); var consumer = new AsyncEventingBasicConsumer(_consumerChannel);
if (_consumerChannel != null)
{
var consumer = new AsyncEventingBasicConsumer(_consumerChannel);
consumer.Received += Consumer_Received; consumer.Received += Consumer_Received;
_consumerChannel.BasicConsume( _consumerChannel.BasicConsume(
queue: _queueName, queue: _queueName,
autoAck: false, autoAck: false,
consumer: consumer); consumer: consumer);
}
else
{
_logger.LogError("StartBasicConsume can't call on _consumerChannel == null");
}
} }
else
/// <summary>
/// 消费者接受到
/// </summary>
/// <param name="sender"></param>
/// <param name="eventArgs"></param>
/// <returns></returns>
private async Task Consumer_Received(object sender, BasicDeliverEventArgs eventArgs)
{ {
var eventName = eventArgs.RoutingKey; _logger.LogError("StartBasicConsume can't call on _consumerChannel == null");
var message = Encoding.UTF8.GetString(eventArgs.Body.Span); }
}
try /// <summary>
{ /// 消费者接受到
if (message.ToLowerInvariant().Contains("throw-fake-exception")) /// </summary>
{ /// <param name="sender"></param>
throw new InvalidOperationException($"Fake exception requested: \"{message}\""); /// <param name="eventArgs"></param>
} /// <returns></returns>
private async Task Consumer_Received(object sender, BasicDeliverEventArgs eventArgs)
{
var eventName = eventArgs.RoutingKey;
var message = Encoding.UTF8.GetString(eventArgs.Body.Span);
await ProcessEvent(eventName, message); try
} {
catch (Exception ex) if (message.ToLowerInvariant().Contains("throw-fake-exception"))
{ {
_logger.LogWarning(ex, "----- ERROR Processing message \"{Message}\"", message); throw new InvalidOperationException($"Fake exception requested: \"{message}\"");
} }
// Even on exception we take the message off the queue. await ProcessEvent(eventName, message);
// in a REAL WORLD app this should be handled with a Dead Letter Exchange (DLX). }
// For more information see: https://www.rabbitmq.com/dlx.html catch (Exception ex)
_consumerChannel.BasicAck(eventArgs.DeliveryTag, multiple: false); {
_logger.LogWarning(ex, "----- ERROR Processing message \"{Message}\"", message);
} }
/// <summary> // Even on exception we take the message off the queue.
/// 创造消费通道 // in a REAL WORLD app this should be handled with a Dead Letter Exchange (DLX).
/// </summary> // For more information see: https://www.rabbitmq.com/dlx.html
/// <returns></returns> _consumerChannel.BasicAck(eventArgs.DeliveryTag, multiple: false);
private IModel CreateConsumerChannel() }
/// <summary>
/// 创造消费通道
/// </summary>
/// <returns></returns>
private IModel CreateConsumerChannel()
{
if (!_persistentConnection.IsConnected)
{ {
if (!_persistentConnection.IsConnected) _persistentConnection.TryConnect();
{ }
_persistentConnection.TryConnect();
}
_logger.LogTrace("Creating RabbitMQ consumer channel"); _logger.LogTrace("Creating RabbitMQ consumer channel");
var channel = _persistentConnection.CreateModel(); var channel = _persistentConnection.CreateModel();
channel.ExchangeDeclare(exchange: BROKER_NAME, channel.ExchangeDeclare(exchange: BROKER_NAME,
type: "direct"); type: "direct");
channel.QueueDeclare(queue: _queueName, channel.QueueDeclare(queue: _queueName,
durable: true, durable: true,
exclusive: false, exclusive: false,
autoDelete: false, autoDelete: false,
arguments: null); arguments: null);
channel.CallbackException += (sender, ea) => channel.CallbackException += (sender, ea) =>
{ {
_logger.LogWarning(ea.Exception, "Recreating RabbitMQ consumer channel"); _logger.LogWarning(ea.Exception, "Recreating RabbitMQ consumer channel");
_consumerChannel.Dispose(); _consumerChannel.Dispose();
_consumerChannel = CreateConsumerChannel(); _consumerChannel = CreateConsumerChannel();
StartBasicConsume(); StartBasicConsume();
}; };
return channel; return channel;
} }
private async Task ProcessEvent(string eventName, string message) private async Task ProcessEvent(string eventName, string message)
{ {
_logger.LogTrace("Processing RabbitMQ event: {EventName}", eventName); _logger.LogTrace("Processing RabbitMQ event: {EventName}", eventName);
if (_subsManager.HasSubscriptionsForEvent(eventName)) if (_subsManager.HasSubscriptionsForEvent(eventName))
{
using (var scope = _autofac.BeginLifetimeScope(AUTOFAC_SCOPE_NAME))
{ {
using (var scope = _autofac.BeginLifetimeScope(AUTOFAC_SCOPE_NAME)) var subscriptions = _subsManager.GetHandlersForEvent(eventName);
foreach (var subscription in subscriptions)
{ {
var subscriptions = _subsManager.GetHandlersForEvent(eventName); if (subscription.IsDynamic)
foreach (var subscription in subscriptions)
{ {
if (subscription.IsDynamic) var handler = scope.ResolveOptional(subscription.HandlerType) as IDynamicIntegrationEventHandler;
{ if (handler == null) continue;
var handler = scope.ResolveOptional(subscription.HandlerType) as IDynamicIntegrationEventHandler; dynamic eventData = JObject.Parse(message);
if (handler == null) continue;
dynamic eventData = JObject.Parse(message); await Task.Yield();
await handler.Handle(eventData);
await Task.Yield(); }
await handler.Handle(eventData); else
} {
else var handler = scope.ResolveOptional(subscription.HandlerType);
{ if (handler == null) continue;
var handler = scope.ResolveOptional(subscription.HandlerType); var eventType = _subsManager.GetEventTypeByName(eventName);
if (handler == null) continue; var integrationEvent = JsonConvert.DeserializeObject(message, eventType);
var eventType = _subsManager.GetEventTypeByName(eventName); var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType);
var integrationEvent = JsonConvert.DeserializeObject(message, eventType);
var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType); await Task.Yield();
await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent });
await Task.Yield();
await (Task)concreteType.GetMethod("Handle").Invoke(handler, new object[] { integrationEvent });
}
} }
} }
} }
else }
{ else
_logger.LogWarning("No subscription for RabbitMQ event: {EventName}", eventName); {
} _logger.LogWarning("No subscription for RabbitMQ event: {EventName}", eventName);
} }
} }
} }

@ -1,44 +1,42 @@
using RabbitMQ.Client; using RabbitMQ.Client;
using System;
namespace Tiobon.Core.EventBus namespace Tiobon.Core.EventBus;
/// <summary>
/// RabbitMQ持久连接
/// 接口
/// </summary>
public interface IRabbitMQPersistentConnection
: IDisposable
{ {
/// <summary> /// <summary>
/// RabbitMQ持久连接 /// 是否已经连接
/// 接口
/// </summary> /// </summary>
public interface IRabbitMQPersistentConnection bool IsConnected { get; }
: IDisposable
{
/// <summary>
/// 是否已经连接
/// </summary>
bool IsConnected { get; }
/// <summary> /// <summary>
/// 尝试重连 /// 尝试重连
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
bool TryConnect(); bool TryConnect();
/// <summary> /// <summary>
/// 创建Model /// 创建Model
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
IModel CreateModel(); IModel CreateModel();
/// <summary> /// <summary>
/// 发布消息 /// 发布消息
/// </summary> /// </summary>
/// <param name="message"></param> /// <param name="message"></param>
/// <param name="exchangeName"></param> /// <param name="exchangeName"></param>
/// <param name="routingKey"></param> /// <param name="routingKey"></param>
void PublishMessage(string message, string exchangeName, string routingKey); void PublishMessage(string message, string exchangeName, string routingKey);
/// <summary> /// <summary>
/// 订阅消息 /// 订阅消息
/// </summary> /// </summary>
/// <param name="queueName"></param> /// <param name="queueName"></param>
void StartConsuming(string queueName); void StartConsuming(string queueName);
}
} }

@ -4,208 +4,205 @@ using Polly.Retry;
using RabbitMQ.Client; using RabbitMQ.Client;
using RabbitMQ.Client.Events; using RabbitMQ.Client.Events;
using RabbitMQ.Client.Exceptions; using RabbitMQ.Client.Exceptions;
using System;
using System.IO;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text; using System.Text;
namespace Tiobon.Core.EventBus namespace Tiobon.Core.EventBus;
/// <summary>
/// RabbitMQ持久连接
/// </summary>
public class RabbitMQPersistentConnection
: IRabbitMQPersistentConnection
{ {
private readonly IConnectionFactory _connectionFactory;
private readonly ILogger<RabbitMQPersistentConnection> _logger;
private readonly int _retryCount;
IConnection _connection;
bool _disposed;
object sync_root = new object();
public RabbitMQPersistentConnection(IConnectionFactory connectionFactory, ILogger<RabbitMQPersistentConnection> logger,
int retryCount = 5)
{
_connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_retryCount = retryCount;
}
/// <summary> /// <summary>
/// RabbitMQ持久连接 /// 是否已连接
/// </summary> /// </summary>
public class RabbitMQPersistentConnection public bool IsConnected
: IRabbitMQPersistentConnection
{ {
private readonly IConnectionFactory _connectionFactory; get
private readonly ILogger<RabbitMQPersistentConnection> _logger;
private readonly int _retryCount;
IConnection _connection;
bool _disposed;
object sync_root = new object();
public RabbitMQPersistentConnection(IConnectionFactory connectionFactory, ILogger<RabbitMQPersistentConnection> logger,
int retryCount = 5)
{ {
_connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory)); return _connection != null && _connection.IsOpen && !_disposed;
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_retryCount = retryCount;
} }
}
/// <summary> /// <summary>
/// 是否已连接 /// 创建Model
/// </summary> /// </summary>
public bool IsConnected /// <returns></returns>
public IModel CreateModel()
{
if (!IsConnected)
{ {
get throw new InvalidOperationException("No RabbitMQ connections are available to perform this action");
{
return _connection != null && _connection.IsOpen && !_disposed;
}
} }
/// <summary> return _connection.CreateModel();
/// 创建Model }
/// </summary>
/// <returns></returns>
public IModel CreateModel()
{
if (!IsConnected)
{
throw new InvalidOperationException("No RabbitMQ connections are available to perform this action");
}
return _connection.CreateModel(); /// <summary>
} /// 释放
/// </summary>
public void Dispose()
{
if (_disposed) return;
/// <summary> _disposed = true;
/// 释放
/// </summary> try
public void Dispose()
{ {
if (_disposed) return; _connection.Dispose();
}
catch (IOException ex)
{
_logger.LogCritical(ex.ToString());
}
}
_disposed = true; /// <summary>
/// 连接
/// </summary>
/// <returns></returns>
public bool TryConnect()
{
_logger.LogInformation("RabbitMQ Client is trying to connect");
try lock (sync_root)
{
var policy = RetryPolicy.Handle<SocketException>()
.Or<BrokerUnreachableException>()
.WaitAndRetry(_retryCount,
retryAttempt =>
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (ex, time) =>
{
_logger.LogWarning(ex, "RabbitMQ Client could not connect after {TimeOut}s ({ExceptionMessage})", $"{time.TotalSeconds:n1}", ex.Message);
}
);
policy.Execute(() =>
{ {
_connection.Dispose(); _connection = _connectionFactory
} .CreateConnection();
catch (IOException ex) });
if (IsConnected)
{ {
_logger.LogCritical(ex.ToString()); _connection.ConnectionShutdown += OnConnectionShutdown;
} _connection.CallbackException += OnCallbackException;
} _connection.ConnectionBlocked += OnConnectionBlocked;
/// <summary> _logger.LogInformation("RabbitMQ Client acquired a persistent connection to '{HostName}' and is subscribed to failure events", _connection.Endpoint.HostName);
/// 连接
/// </summary>
/// <returns></returns>
public bool TryConnect()
{
_logger.LogInformation("RabbitMQ Client is trying to connect");
lock (sync_root) return true;
}
else
{ {
var policy = RetryPolicy.Handle<SocketException>() _logger.LogCritical("FATAL ERROR: RabbitMQ connections could not be created and opened");
.Or<BrokerUnreachableException>()
.WaitAndRetry(_retryCount, return false;
retryAttempt =>
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (ex, time) =>
{
_logger.LogWarning(ex, "RabbitMQ Client could not connect after {TimeOut}s ({ExceptionMessage})", $"{time.TotalSeconds:n1}", ex.Message);
}
);
policy.Execute(() =>
{
_connection = _connectionFactory
.CreateConnection();
});
if (IsConnected)
{
_connection.ConnectionShutdown += OnConnectionShutdown;
_connection.CallbackException += OnCallbackException;
_connection.ConnectionBlocked += OnConnectionBlocked;
_logger.LogInformation("RabbitMQ Client acquired a persistent connection to '{HostName}' and is subscribed to failure events", _connection.Endpoint.HostName);
return true;
}
else
{
_logger.LogCritical("FATAL ERROR: RabbitMQ connections could not be created and opened");
return false;
}
} }
} }
}
/// <summary> /// <summary>
/// 连接被阻断 /// 连接被阻断
/// </summary> /// </summary>
/// <param name="sender"></param> /// <param name="sender"></param>
/// <param name="e"></param> /// <param name="e"></param>
private void OnConnectionBlocked(object sender, ConnectionBlockedEventArgs e) private void OnConnectionBlocked(object sender, ConnectionBlockedEventArgs e)
{ {
if (_disposed) return; if (_disposed) return;
_logger.LogWarning("A RabbitMQ connection is shutdown. Trying to re-connect..."); _logger.LogWarning("A RabbitMQ connection is shutdown. Trying to re-connect...");
TryConnect(); TryConnect();
} }
/// <summary> /// <summary>
/// 连接出现异常 /// 连接出现异常
/// </summary> /// </summary>
/// <param name="sender"></param> /// <param name="sender"></param>
/// <param name="e"></param> /// <param name="e"></param>
void OnCallbackException(object sender, CallbackExceptionEventArgs e) void OnCallbackException(object sender, CallbackExceptionEventArgs e)
{ {
if (_disposed) return; if (_disposed) return;
_logger.LogWarning("A RabbitMQ connection throw exception. Trying to re-connect..."); _logger.LogWarning("A RabbitMQ connection throw exception. Trying to re-connect...");
TryConnect(); TryConnect();
} }
/// <summary> /// <summary>
/// 连接被关闭 /// 连接被关闭
/// </summary> /// </summary>
/// <param name="sender"></param> /// <param name="sender"></param>
/// <param name="reason"></param> /// <param name="reason"></param>
void OnConnectionShutdown(object sender, ShutdownEventArgs reason) void OnConnectionShutdown(object sender, ShutdownEventArgs reason)
{ {
if (_disposed) return; if (_disposed) return;
_logger.LogWarning("A RabbitMQ connection is on shutdown. Trying to re-connect..."); _logger.LogWarning("A RabbitMQ connection is on shutdown. Trying to re-connect...");
TryConnect(); TryConnect();
} }
/// <summary> /// <summary>
/// 发布消息 /// 发布消息
/// </summary> /// </summary>
/// <param name="message"></param> /// <param name="message"></param>
/// <param name="exchangeName"></param> /// <param name="exchangeName"></param>
/// <param name="routingKey"></param> /// <param name="routingKey"></param>
public void PublishMessage(string message, string exchangeName, string routingKey) public void PublishMessage(string message, string exchangeName, string routingKey)
{ {
using var channel = CreateModel(); using var channel = CreateModel();
channel.ExchangeDeclare(exchange: exchangeName, type: ExchangeType.Direct, true); channel.ExchangeDeclare(exchange: exchangeName, type: ExchangeType.Direct, true);
var body = Encoding.UTF8.GetBytes(message); var body = Encoding.UTF8.GetBytes(message);
channel.BasicPublish(exchange: exchangeName, routingKey: routingKey, basicProperties: null, body: body); channel.BasicPublish(exchange: exchangeName, routingKey: routingKey, basicProperties: null, body: body);
} }
/// <summary> /// <summary>
/// 订阅消息 /// 订阅消息
/// </summary> /// </summary>
/// <param name="queueName"></param> /// <param name="queueName"></param>
public void StartConsuming(string queueName) public void StartConsuming(string queueName)
{ {
using var channel = CreateModel(); using var channel = CreateModel();
channel.QueueDeclare(queue: queueName, durable: true, exclusive: false, autoDelete: false, arguments: null); channel.QueueDeclare(queue: queueName, durable: true, exclusive: false, autoDelete: false, arguments: null);
var consumer = new AsyncEventingBasicConsumer(channel); var consumer = new AsyncEventingBasicConsumer(channel);
consumer.Received += new AsyncEventHandler<BasicDeliverEventArgs>( consumer.Received += new AsyncEventHandler<BasicDeliverEventArgs>(
async (a, b) => async (a, b) =>
{ {
var Headers = b.BasicProperties.Headers; var Headers = b.BasicProperties.Headers;
var msgBody = b.Body.ToArray(); var msgBody = b.Body.ToArray();
var message = Encoding.UTF8.GetString(msgBody); var message = Encoding.UTF8.GetString(msgBody);
await Task.CompletedTask; await Task.CompletedTask;
Console.WriteLine("Received message: {0}", message); Console.WriteLine("Received message: {0}", message);
//bool Dealresult = await Dealer(b.Exchange, b.RoutingKey, msgBody, Headers); //bool Dealresult = await Dealer(b.Exchange, b.RoutingKey, msgBody, Headers);
//if (Dealresult) channel.BasicAck(b.DeliveryTag, false); //if (Dealresult) channel.BasicAck(b.DeliveryTag, false);
//else channel.BasicNack(b.DeliveryTag, false, true); //else channel.BasicNack(b.DeliveryTag, false, true);
} }
); );
channel.BasicConsume(queue: queueName, autoAck: true, consumer: consumer); channel.BasicConsume(queue: queueName, autoAck: true, consumer: consumer);
Console.WriteLine("Consuming messages..."); Console.WriteLine("Consuming messages...");
}
} }
} }

@ -3,38 +3,37 @@ using Microsoft.AspNetCore.Mvc;
using Tiobon.Core.HttpContextUser; using Tiobon.Core.HttpContextUser;
using Tiobon.Core.Model.Entity; using Tiobon.Core.Model.Entity;
namespace Tiobon.Core.Gateway.Controllers namespace Tiobon.Core.Gateway.Controllers;
{
[Authorize(AuthenticationSchemes = Permissions.GWName)]
[Route("/gateway/[controller]/[action]")]
public class UserController : ControllerBase
{
private readonly IUser _user;
public UserController(IUser user) [Authorize(AuthenticationSchemes = Permissions.GWName)]
{ [Route("/gateway/[controller]/[action]")]
_user = user; public class UserController : ControllerBase
} {
private readonly IUser _user;
[HttpGet] public UserController(IUser user)
public ServiceResult<List<ClaimDto>> MyClaims() {
{ _user = user;
return new ServiceResult<List<ClaimDto>>()
{
Success = true,
Data = (_user.GetClaimsIdentity().ToList()).Select(d =>
new ClaimDto
{
Type = d.Type,
Value = d.Value
}
).ToList()
};
}
} }
public class ClaimDto
[HttpGet]
public ServiceResult<List<ClaimDto>> MyClaims()
{ {
public string Type { get; set; } return new ServiceResult<List<ClaimDto>>()
public string Value { get; set; } {
Success = true,
Data = (_user.GetClaimsIdentity().ToList()).Select(d =>
new ClaimDto
{
Type = d.Type,
Value = d.Value
}
).ToList()
};
} }
} }
public class ClaimDto
{
public string Type { get; set; }
public string Value { get; set; }
}

@ -1,53 +1,48 @@
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
using System;
using System.Collections.Generic;
using System.Security.Claims; using System.Security.Claims;
using System.Text.Encodings.Web; using System.Text.Encodings.Web;
using System.Threading.Tasks;
namespace Tiobon.Core.Gateway.Extensions namespace Tiobon.Core.Gateway.Extensions;
public class CustomAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{ {
public class CustomAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions> public CustomAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock) : base(options, logger, encoder, clock)
{ {
public CustomAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, }
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock) : base(options, logger, encoder, clock)
{
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync() protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{ {
// 可以查询数据库等操作 // 可以查询数据库等操作
// 获取当前用户不能放到token中的私密信息 // 获取当前用户不能放到token中的私密信息
var userPhone = "15010000000"; var userPhone = "15010000000";
var claims = new List<Claim>()
{
new Claim("user-phone", userPhone),
new Claim("gw-sign", "gw")
};
var principal = new ClaimsPrincipal(new ClaimsIdentity(claims, Scheme.Name));
var ticket = new AuthenticationTicket(principal, Scheme.Name);
await Task.CompletedTask;
return AuthenticateResult.Success(ticket);
}
protected virtual string GetTokenStringFromHeader() var claims = new List<Claim>()
{ {
var token = string.Empty; new Claim("user-phone", userPhone),
string authorization = Request.Headers[HeaderNames.Authorization]; new Claim("gw-sign", "gw")
};
var principal = new ClaimsPrincipal(new ClaimsIdentity(claims, Scheme.Name));
var ticket = new AuthenticationTicket(principal, Scheme.Name);
await Task.CompletedTask;
return AuthenticateResult.Success(ticket);
}
if (!string.IsNullOrEmpty(authorization) && authorization.StartsWith($"Bearer ", StringComparison.OrdinalIgnoreCase)) protected virtual string GetTokenStringFromHeader()
{ {
token = authorization["Bearer ".Length..].Trim(); var token = string.Empty;
} string authorization = Request.Headers[HeaderNames.Authorization];
return token; if (!string.IsNullOrEmpty(authorization) && authorization.StartsWith($"Bearer ", StringComparison.OrdinalIgnoreCase))
{
token = authorization["Bearer ".Length..].Trim();
} }
return token;
} }
} }

@ -1,34 +1,29 @@
using Tiobon.Core.Extensions; using Ocelot.DependencyInjection;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Ocelot.DependencyInjection;
using Ocelot.Middleware; using Ocelot.Middleware;
//using Ocelot.Provider.Nacos; //using Ocelot.Provider.Nacos;
using Ocelot.Provider.Polly; using Ocelot.Provider.Polly;
using System; using Tiobon.Core.Extensions;
using System.Threading.Tasks;
namespace Tiobon.Core.Gateway.Extensions namespace Tiobon.Core.Gateway.Extensions;
public static class CustomOcelotSetup
{ {
public static class CustomOcelotSetup public static void AddCustomOcelotSetup(this IServiceCollection services)
{ {
public static void AddCustomOcelotSetup(this IServiceCollection services) if (services == null) throw new ArgumentNullException(nameof(services));
{
if (services == null) throw new ArgumentNullException(nameof(services));
services.AddAuthentication_JWTSetup();
services.AddOcelot()
.AddDelegatingHandler<CustomResultHandler>()
//.AddNacosDiscovery()
//.AddConsul()
.AddPolly();
}
public static async Task<IApplicationBuilder> UseCustomOcelotMildd(this IApplicationBuilder app) services.AddAuthentication_JWTSetup();
{ services.AddOcelot()
await app.UseOcelot(); .AddDelegatingHandler<CustomResultHandler>()
return app; //.AddNacosDiscovery()
} //.AddConsul()
.AddPolly();
}
public static async Task<IApplicationBuilder> UseCustomOcelotMildd(this IApplicationBuilder app)
{
await app.UseOcelot();
return app;
} }
} }

@ -1,62 +1,57 @@
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Serialization; using Newtonsoft.Json.Serialization;
using System;
using System.Net; using System.Net;
using System.Net.Http;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Tiobon.Core.Gateway.Extensions namespace Tiobon.Core.Gateway.Extensions;
public class CustomResultHandler : DelegatingHandler
{ {
public class CustomResultHandler : DelegatingHandler JsonSerializerSettings _camelSettings = new JsonSerializerSettings() { ContractResolver = new CamelCasePropertyNamesContractResolver() };
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{ {
JsonSerializerSettings _camelSettings = new JsonSerializerSettings() { ContractResolver = new CamelCasePropertyNamesContractResolver() }; var response = await base.SendAsync(request, cancellationToken);
var contentType = response.Content.Headers.ContentType?.MediaType ?? "";
if (!contentType.Equals("application/json")) return response;
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) dynamic result = null;
var resultStr = await response.Content.ReadAsStringAsync();
try
{ {
var response = await base.SendAsync(request, cancellationToken); Console.WriteLine(resultStr);
var contentType = response.Content.Headers.ContentType?.MediaType ?? ""; result = JsonConvert.DeserializeObject<dynamic>(resultStr);
if (!contentType.Equals("application/json")) return response; }
catch (Exception)
dynamic result = null; {
var resultStr = await response.Content.ReadAsStringAsync(); return response;
try }
{
Console.WriteLine(resultStr);
result = JsonConvert.DeserializeObject<dynamic>(resultStr);
}
catch (Exception)
{
return response;
}
if (result != null && result.errorCode == 500) resultStr = result.message.ToString(); if (result != null && result.errorCode == 500) resultStr = result.message.ToString();
var exception = new Exception(resultStr); var exception = new Exception(resultStr);
if (response.StatusCode == HttpStatusCode.InternalServerError || result.errorCode == (int)HttpStatusCode.InternalServerError) if (response.StatusCode == HttpStatusCode.InternalServerError || result.errorCode == (int)HttpStatusCode.InternalServerError)
{
var apiResult = new
{ {
var apiResult = new Result = false,
Message = "服务器内部错误",
ErrorCode = (int)HttpStatusCode.InternalServerError,
Data = new
{ {
Result = false, exception.Message,
Message = "服务器内部错误", exception.StackTrace
ErrorCode = (int)HttpStatusCode.InternalServerError, }
Data = new };
{ response.Content = new StringContent(JsonConvert.SerializeObject(apiResult, _camelSettings), Encoding.UTF8, "application/json");
exception.Message, }
exception.StackTrace else
} {
};
response.Content = new StringContent(JsonConvert.SerializeObject(apiResult, _camelSettings), Encoding.UTF8, "application/json");
}
else
{
} }
return response; return response;
}
} }
} }

@ -1,81 +1,70 @@
using Tiobon.Core.Common; using Microsoft.OpenApi.Models;
using Tiobon.Core.Extensions.Middlewares;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.Filters; using Swashbuckle.AspNetCore.Filters;
using Swashbuckle.AspNetCore.SwaggerUI;
using System; namespace Tiobon.Core.Gateway.Extensions;
using System.Collections.Generic;
using System.IO; public static class CustomSwaggerSetup
using System.Reflection;
using static Tiobon.Core.Extensions.CustomApiVersion;
namespace Tiobon.Core.Gateway.Extensions
{ {
public static class CustomSwaggerSetup public static void AddCustomSwaggerSetup(this IServiceCollection services)
{ {
public static void AddCustomSwaggerSetup(this IServiceCollection services) if (services == null) throw new ArgumentNullException(nameof(services));
{
if (services == null) throw new ArgumentNullException(nameof(services));
var basePath = AppContext.BaseDirectory; var basePath = AppContext.BaseDirectory;
services.AddMvc(option => option.EnableEndpointRouting = false); services.AddMvc(option => option.EnableEndpointRouting = false);
services.AddSwaggerGen(c => services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo
{ {
c.SwaggerDoc("v1", new OpenApiInfo Version = "v1",
{ Title = "自定义网关 接口文档",
Version = "v1",
Title = "自定义网关 接口文档",
});
var xmlPath = Path.Combine(basePath, "Tiobon.Core.Gateway.xml");
c.IncludeXmlComments(xmlPath, true);
c.OperationFilter<AddResponseHeadersFilter>();
c.OperationFilter<AppendAuthorizeToSummaryOperationFilter>();
c.OperationFilter<SecurityRequirementsOperationFilter>();
c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
{
Description = "JWT授权(数据将在请求头中进行传输) 直接在下框中输入Bearer {token}(注意两者之间是一个空格)\"",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey
});
}); });
}
public static void UseCustomSwaggerMildd(this IApplicationBuilder app, Func<Stream> streamHtml) var xmlPath = Path.Combine(basePath, "Tiobon.Core.Gateway.xml");
{ c.IncludeXmlComments(xmlPath, true);
if (app == null) throw new ArgumentNullException(nameof(app));
var apis = new List<string> { "Tiobon-svc" }; c.OperationFilter<AddResponseHeadersFilter>();
app.UseSwagger(); c.OperationFilter<AppendAuthorizeToSummaryOperationFilter>();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint($"/swagger/v1/swagger.json", "gateway");
apis.ForEach(m =>
{
c.SwaggerEndpoint($"/swagger/apiswg/{m}/swagger.json", m);
});
c.OperationFilter<SecurityRequirementsOperationFilter>();
if (streamHtml.Invoke() == null) c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
{ {
var msg = "index.html的属性,必须设置为嵌入的资源"; Description = "JWT授权(数据将在请求头中进行传输) 直接在下框中输入Bearer {token}(注意两者之间是一个空格)\"",
throw new Exception(msg); Name = "Authorization",
} In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey
});
});
}
c.IndexStream = streamHtml; public static void UseCustomSwaggerMildd(this IApplicationBuilder app, Func<Stream> streamHtml)
{
if (app == null) throw new ArgumentNullException(nameof(app));
c.RoutePrefix = ""; var apis = new List<string> { "Tiobon-svc" };
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint($"/swagger/v1/swagger.json", "gateway");
apis.ForEach(m =>
{
c.SwaggerEndpoint($"/swagger/apiswg/{m}/swagger.json", m);
}); });
}
if (streamHtml.Invoke() == null)
{
var msg = "index.html的属性,必须设置为嵌入的资源";
throw new Exception(msg);
}
c.IndexStream = streamHtml;
c.RoutePrefix = "";
});
} }
} }

@ -4,182 +4,181 @@ using System.Text.RegularExpressions;
using Tiobon.Core.Caches; using Tiobon.Core.Caches;
using Tiobon.Core.Helper; using Tiobon.Core.Helper;
namespace Tiobon.Core.AuthHelper namespace Tiobon.Core.AuthHelper;
/// <summary>
/// 中间件
/// 原做为自定义授权中间件
/// 先做检查 header token的使用
/// </summary>
public class CustomJwtTokenAuthMiddleware
{ {
private readonly ICaching _cache;
/// <summary> /// <summary>
/// 中间件 /// 验证方案提供对象
/// 原做为自定义授权中间件
/// 先做检查 header token的使用
/// </summary> /// </summary>
public class CustomJwtTokenAuthMiddleware public IAuthenticationSchemeProvider Schemes { get; set; }
{
private readonly ICaching _cache;
/// <summary> /// <summary>
/// 验证方案提供对象 /// 请求上下文
/// </summary> /// </summary>
public IAuthenticationSchemeProvider Schemes { get; set; } private readonly RequestDelegate _next;
/// <summary>
/// 请求上下文
/// </summary>
private readonly RequestDelegate _next;
public CustomJwtTokenAuthMiddleware(RequestDelegate next, IAuthenticationSchemeProvider schemes, AppSettings appset, ICaching cache)
{
_cache = cache;
_next = next;
Schemes = schemes;
}
public CustomJwtTokenAuthMiddleware(RequestDelegate next, IAuthenticationSchemeProvider schemes, AppSettings appset,ICaching cache) /// <summary>
/// 网关授权
/// </summary>
/// <param name="httpContext"></param>
/// <returns></returns>
public async Task Invoke(HttpContext httpContext)
{
var questUrl = httpContext?.Request.Path.Value.ToLower();
if (string.IsNullOrEmpty(questUrl)) return;
//白名单验证
if (CheckWhiteList(questUrl))
{ {
_cache = cache; await _next.Invoke(httpContext);
_next = next; return;
Schemes = schemes;
} }
//黑名单验证
/// <summary> if (CheckBlackList(questUrl))
/// 网关授权
/// </summary>
/// <param name="httpContext"></param>
/// <returns></returns>
public async Task Invoke(HttpContext httpContext)
{ {
var questUrl = httpContext?.Request.Path.Value.ToLower(); return;
if (string.IsNullOrEmpty(questUrl)) return; }
//白名单验证
if (CheckWhiteList(questUrl))
{
await _next.Invoke(httpContext);
return;
}
//黑名单验证
if(CheckBlackList(questUrl))
{
return;
}
List<PermissionItem> Permissions= new(); List<PermissionItem> Permissions = new();
httpContext.Features.Set<IAuthenticationFeature>(new AuthenticationFeature httpContext.Features.Set<IAuthenticationFeature>(new AuthenticationFeature
{ {
OriginalPath = httpContext.Request.Path, OriginalPath = httpContext.Request.Path,
OriginalPathBase = httpContext.Request.PathBase OriginalPathBase = httpContext.Request.PathBase
}); });
//判断请求是否拥有凭据,即有没有登录 //判断请求是否拥有凭据,即有没有登录
var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync(); var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();
if (defaultAuthenticate != null) if (defaultAuthenticate != null)
{
var Authresult = await httpContext.AuthenticateAsync(defaultAuthenticate.Name);
if (Authresult?.Principal != null)
{ {
var Authresult = await httpContext.AuthenticateAsync(defaultAuthenticate.Name); httpContext.User = Authresult.Principal;
if (Authresult?.Principal != null) // 获取当前用户的角色信息
var currentUserRoles = (from item in httpContext.User.Claims
where item.Type == "CofRole"
select item.Value).ToList();
var isMatchRole = false;
var permisssionRoles = Permissions.Where(w => currentUserRoles.Contains(w.Role));
foreach (var item in permisssionRoles)
{ {
httpContext.User = Authresult.Principal; try
// 获取当前用户的角色信息
var currentUserRoles = (from item in httpContext.User.Claims
where item.Type == "CofRole"
select item.Value).ToList();
var isMatchRole = false;
var permisssionRoles = Permissions.Where(w => currentUserRoles.Contains(w.Role));
foreach (var item in permisssionRoles)
{ {
try if (Regex.IsMatch(questUrl, item.Url, RegexOptions.IgnoreCase))
{ {
if (Regex.IsMatch(questUrl, item.Url, RegexOptions.IgnoreCase)) isMatchRole = true;
{ break;
isMatchRole = true;
break;
}
}
catch (Exception)
{
// ignored
} }
} }
catch (Exception)
//验证权限
if (currentUserRoles.Count <= 0 || !isMatchRole)
{ {
await httpContext.Cof_SendResponse(HttpStatusCode.ServiceUnavailable, "未授权此资源"); // ignored
return ;
} }
} }
else
//验证权限
if (currentUserRoles.Count <= 0 || !isMatchRole)
{ {
await httpContext.Cof_SendResponse(HttpStatusCode.Unauthorized, "请重新登录"); await httpContext.Cof_SendResponse(HttpStatusCode.ServiceUnavailable, "未授权此资源");
return ; return;
} }
} }
else else
{ {
await httpContext.Cof_SendResponse(HttpStatusCode.Unauthorized, "系统鉴权出错"); await httpContext.Cof_SendResponse(HttpStatusCode.Unauthorized, "请重新登录");
return ; return;
} }
await _next.Invoke(httpContext);
}
/// <summary> }
/// 返回相应 else
/// </summary>
/// <param name="context"></param>
/// <param name="message"></param>
/// <param name="code"></param>
/// <returns></returns>
private async Task SendResponse(HttpContext context, string message, HttpStatusCode code)
{ {
context.Response.StatusCode = (int)code; await httpContext.Cof_SendResponse(HttpStatusCode.Unauthorized, "系统鉴权出错");
context.Response.ContentType = "text/plain"; return;
await context.Response.WriteAsync(message);
} }
await _next.Invoke(httpContext);
}
/// <summary>
/// 返回相应
/// </summary>
/// <param name="context"></param>
/// <param name="message"></param>
/// <param name="code"></param>
/// <returns></returns>
private async Task SendResponse(HttpContext context, string message, HttpStatusCode code)
{
context.Response.StatusCode = (int)code;
context.Response.ContentType = "text/plain";
await context.Response.WriteAsync(message);
}
/// <summary> /// <summary>
/// 判断是否在白名单内,支持通配符 **** /// 判断是否在白名单内,支持通配符 ****
/// </summary> /// </summary>
/// <param name="url"></param> /// <param name="url"></param>
/// <returns></returns> /// <returns></returns>
public bool CheckWhiteList(string url) public bool CheckWhiteList(string url)
{
List<Urlobj> WhiteList = _cache.Cof_GetICaching<List<Urlobj>>("WhiteList", () => AppSettings.app<Urlobj>("WhiteList"), 10);
if (!WhiteList.Cof_CheckAvailable()) return false;
foreach (var Urlitem in WhiteList)
{ {
List<Urlobj> WhiteList = _cache.Cof_GetICaching<List<Urlobj>>("WhiteList", () => AppSettings.app<Urlobj>("WhiteList"), 10); if (Urlitem.url.Equals(url, StringComparison.OrdinalIgnoreCase)) return true;
if (!WhiteList.Cof_CheckAvailable()) return false; if (Urlitem.url.IndexOf("****") > 0)
foreach (var Urlitem in WhiteList)
{ {
if (Urlitem.url.Equals(url, StringComparison.OrdinalIgnoreCase)) return true; string UrlitemP = Urlitem.url.Replace("****", "");
if (Regex.IsMatch(url, UrlitemP, RegexOptions.IgnoreCase)) return true;
if (url.Length >= UrlitemP.Length && UrlitemP.ToLower() == url.Substring(0, UrlitemP.Length).ToLower()) return true;
if (Urlitem.url.IndexOf("****") > 0)
{
string UrlitemP = Urlitem.url.Replace("****", "");
if (Regex.IsMatch(url, UrlitemP, RegexOptions.IgnoreCase)) return true;
if (url.Length >= UrlitemP.Length && UrlitemP.ToLower() == url.Substring(0, UrlitemP.Length).ToLower()) return true;
}
} }
return false;
} }
return false;
}
public bool CheckBlackList(string url) public bool CheckBlackList(string url)
{
List<Urlobj> BlackList = _cache.Cof_GetICaching<List<Urlobj>>("BlackList", () => AppSettings.app<Urlobj>("BlackList"), 10);
if (!BlackList.Cof_CheckAvailable()) return false;
foreach (var Urlitem in BlackList)
{ {
List<Urlobj> BlackList = _cache.Cof_GetICaching<List<Urlobj>>("BlackList", () => AppSettings.app<Urlobj>("BlackList"), 10); if (Urlitem.url.Equals(url, StringComparison.OrdinalIgnoreCase)) return true;
if (!BlackList.Cof_CheckAvailable()) return false; if (Urlitem.url.IndexOf("****") > 0)
foreach (var Urlitem in BlackList)
{ {
if (Urlitem.url.Equals(url, StringComparison.OrdinalIgnoreCase)) return true; string UrlitemP = Urlitem.url.Replace("****", "");
if (Regex.IsMatch(url, UrlitemP, RegexOptions.IgnoreCase)) return true;
if (Urlitem.url.IndexOf("****") > 0) if (url.Length >= UrlitemP.Length && UrlitemP.ToLower() == url.Substring(0, UrlitemP.Length).ToLower()) return true;
{
string UrlitemP = Urlitem.url.Replace("****", "");
if (Regex.IsMatch(url, UrlitemP, RegexOptions.IgnoreCase)) return true;
if (url.Length >= UrlitemP.Length && UrlitemP.ToLower() == url.Substring(0, UrlitemP.Length).ToLower()) return true;
}
} }
return false;
} }
} return false;
public class Urlobj
{
public string url { get; set; }
} }
} }
public class Urlobj
{
public string url { get; set; }
}

@ -1,28 +1,23 @@
using Microsoft.AspNetCore.Hosting; namespace Tiobon.Core.AdminMvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
namespace Tiobon.Core.AdminMvc public class Program
{ {
public class Program public static void Main(string[] args)
{ {
public static void Main(string[] args) CreateHostBuilder(args).Build().Run();
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddJsonFile("appsettings.gw.json", optional: true, reloadOnChange: false)
.AddJsonFile($"appsettings.gw.{hostingContext.HostingEnvironment.EnvironmentName}.json", optional: true, reloadOnChange: false)
.AddJsonFile("ocelot.json", optional: true, reloadOnChange: true)
.AddJsonFile($"ocelot.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true);
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>().UseUrls("http://*:9000");
});
} }
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddJsonFile("appsettings.gw.json", optional: true, reloadOnChange: false)
.AddJsonFile($"appsettings.gw.{hostingContext.HostingEnvironment.EnvironmentName}.json", optional: true, reloadOnChange: false)
.AddJsonFile("ocelot.json", optional: true, reloadOnChange: true)
.AddJsonFile($"ocelot.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true);
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>().UseUrls("http://*:9000");
});
} }

@ -1,7 +0,0 @@
namespace Tiobon.Core.Model;
public enum HttpEnum
{
Common,
LocalHost
}

@ -1,16 +0,0 @@
namespace Tiobon.Core.Model.ViewModels;
public class EnumDemoDto
{
public int Id { get; set; }
/// <summary>
/// Type Description balabala
/// </summary>
public EnumType Type { get; set; }
}
public enum EnumType
{
foo, bar, baz
}

@ -1,22 +0,0 @@
namespace Tiobon.Core.Model.ViewModels;
/// <summary>
/// 留言排名展示类
/// </summary>
public class TopgbViewModels
{
/// <summary>博客ID
///
/// </summary>
public int? TiobonId { get; set; }
/// <summary>
/// 评论数量
/// </summary>
public int counts { get; set; }
/// <summary>博客标题
///
/// </summary>
public string btitle { get; set; }
}

@ -1,18 +0,0 @@
using Microsoft.AspNetCore.Http;
namespace Tiobon.Core.Model.ViewModels;
public class UploadFileDto
{
//多文件
[Required]
public IFormFileCollection file { get; set; }
//单文件
//public IFormFile File { get; set; }
//其他数据
public string Foo { get; set; }
}

@ -1,2 +1,7 @@
global using Tiobon.Core.IServices; global using Tiobon.Core.IServices;
global using Quartz; global using Quartz;
global using Microsoft.Extensions.Logging;
global using Tiobon.Core.Model.Entity;
global using Tiobon.Core.Model.Models;
global using Tiobon.Core.Model.ViewModels;
global using Tiobon.Core.Helper;

@ -1,9 +1,4 @@
using Tiobon.Core.Model; namespace Tiobon.Core.Tasks;
using Tiobon.Core.Model.Entity;
using Tiobon.Core.Model.Models;
using Tiobon.Core.Model.ViewModels;
namespace Tiobon.Core.Tasks;
/// <summary> /// <summary>
/// 服务调度接口 /// 服务调度接口

@ -1,7 +1,4 @@
using Tiobon.Core.Helper; namespace Tiobon.Core.Tasks;
using Tiobon.Core.Model.Models;
namespace Tiobon.Core.Tasks;
public class JobBase public class JobBase
{ {

@ -1,6 +1,4 @@
using Microsoft.Extensions.Logging; /// <summary>
/// <summary>
/// 这里要注意下,命名空间和程序集是一样的,不然反射不到(任务类要去JobSetup添加注入) /// 这里要注意下,命名空间和程序集是一样的,不然反射不到(任务类要去JobSetup添加注入)
/// </summary> /// </summary>
namespace Tiobon.Core.Tasks; namespace Tiobon.Core.Tasks;

@ -1,6 +1,4 @@
using Microsoft.Extensions.Logging; /// <summary>
/// <summary>
/// 这里要注意下,命名空间和程序集是一样的,不然反射不到(任务类要去JobSetup添加注入) /// 这里要注意下,命名空间和程序集是一样的,不然反射不到(任务类要去JobSetup添加注入)
/// </summary> /// </summary>
namespace Tiobon.Core.Tasks; namespace Tiobon.Core.Tasks;

@ -1,5 +1,4 @@
using Microsoft.Extensions.Logging; 
/// <summary> /// <summary>
/// 这里要注意下,命名空间和程序集是一样的,不然反射不到(任务类要去JobSetup添加注入) /// 这里要注意下,命名空间和程序集是一样的,不然反射不到(任务类要去JobSetup添加注入)
/// </summary> /// </summary>

@ -1,6 +1,4 @@
using Microsoft.Extensions.Logging; /// <summary>
/// <summary>
/// 这里要注意下,命名空间和程序集是一样的,不然反射不到(任务类要去JobSetup添加注入) /// 这里要注意下,命名空间和程序集是一样的,不然反射不到(任务类要去JobSetup添加注入)
/// </summary> /// </summary>
namespace Tiobon.Core.Tasks; namespace Tiobon.Core.Tasks;

@ -1,7 +1,6 @@
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using System.Text; using System.Text;
using Tiobon.Core.LogHelper; using Tiobon.Core.LogHelper;
using Tiobon.Core.Model.Models;
/// <summary> /// <summary>
/// 这里要注意下,命名空间和程序集是一样的,不然反射不到 /// 这里要注意下,命名空间和程序集是一样的,不然反射不到

@ -1,6 +1,4 @@
using Microsoft.Extensions.Logging; 
using Tiobon.Core.Helper;
/// <summary> /// <summary>
/// 这里要注意下,命名空间和程序集是一样的,不然反射不到(任务类要去JobSetup添加注入) /// 这里要注意下,命名空间和程序集是一样的,不然反射不到(任务类要去JobSetup添加注入)
/// </summary> /// </summary>

@ -4,9 +4,6 @@ using Quartz.Spi;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Reflection; using System.Reflection;
using Tiobon.Core.DB.Dapper; using Tiobon.Core.DB.Dapper;
using Tiobon.Core.Model.Entity;
using Tiobon.Core.Model.Models;
using Tiobon.Core.Model.ViewModels;
namespace Tiobon.Core.Tasks; namespace Tiobon.Core.Tasks;

@ -1,27 +1,25 @@
using System.Data; using System.Data;
using Tiobon.Core.DB.Dapper; using Tiobon.Core.DB.Dapper;
using Tiobon.Core.Model.Models;
using Xunit; using Xunit;
namespace Tiobon.Core.Tests.Common_Test namespace Tiobon.Core.Tests.Common_Test;
{
public class DbAccess_Should
{
[Fact] public class DbAccess_Should
public async void Test() {
{
AppSetting.Init();
string sql = "SELECT * FROM Ghra_Grade"; [Fact]
DataTable dt = await DbAccess.GetDataTableAsync(sql); public async void Test()
{
AppSetting.Init();
//var list = await DbAccess.QueryListAsync<Ghra_Grade>(sql); string sql = "SELECT * FROM Ghra_Grade";
DataTable dt = await DbAccess.GetDataTableAsync(sql);
//var entity = new Ghra_Grade(); //var list = await DbAccess.QueryListAsync<Ghra_Grade>(sql);
//DbAccess.Add(entity);
} //var entity = new Ghra_Grade();
//DbAccess.Add(entity);
} }
} }

@ -1,26 +1,25 @@
using Tiobon.Core.Helper; using Tiobon.Core.Helper;
using Xunit; using Xunit;
namespace Tiobon.Core.Tests.Common_Test namespace Tiobon.Core.Tests.Common_Test;
{
public class HttpHelper_Should
{
[Fact] public class HttpHelper_Should
public async void Get_Async_Test() {
{
var responseString = (await HttpHelper.GetAsync("http://apk.neters.club/api/Tiobon"));
Assert.NotNull(responseString); [Fact]
} public async void Get_Async_Test()
{
var responseString = (await HttpHelper.GetAsync("http://apk.neters.club/api/Tiobon"));
[Fact] Assert.NotNull(responseString);
public void Post_Async_Test() }
{
var responseString = HttpHelper.PostAsync("http://apk.neters.club/api/Login/swgLogin", "{\"name\":\"admin\",\"pwd\":\"admin\"}");
Assert.NotNull(responseString); [Fact]
} public void Post_Async_Test()
{
var responseString = HttpHelper.PostAsync("http://apk.neters.club/api/Login/swgLogin", "{\"name\":\"admin\",\"pwd\":\"admin\"}");
Assert.NotNull(responseString);
} }
} }

@ -1,42 +1,39 @@
using Tiobon.Core.Common.Helper; using Tiobon.Core.Common.Helper.SM;
using Tiobon.Core.Common.Helper.SM;
using System;
using Xunit; using Xunit;
namespace Tiobon.Core.Tests.Common_Test namespace Tiobon.Core.Tests.Common_Test;
{
public class SM4Helper_Should
{
[Fact] public class SM4Helper_Should
public void Encrypt_ECB_Test() {
{
var plainText = "暗号";
var sm4 = new SM4Helper(); [Fact]
public void Encrypt_ECB_Test()
{
var plainText = "暗号";
Console.Out.WriteLine("ECB模式"); var sm4 = new SM4Helper();
var cipherText = sm4.Encrypt_ECB(plainText);
Console.Out.WriteLine("密文: " + cipherText);
Assert.NotNull(cipherText); Console.Out.WriteLine("ECB模式");
Assert.Equal("VhVDC0KzyZjAVMpwz0GyQA==", cipherText); var cipherText = sm4.Encrypt_ECB(plainText);
} Console.Out.WriteLine("密文: " + cipherText);
[Fact] Assert.NotNull(cipherText);
public void Decrypt_ECB_Test() Assert.Equal("VhVDC0KzyZjAVMpwz0GyQA==", cipherText);
{ }
var cipherText = "Y9ygWexdpuLQjW/qsnZNQw==";
var sm4 = new SM4Helper(); [Fact]
public void Decrypt_ECB_Test()
{
var cipherText = "Y9ygWexdpuLQjW/qsnZNQw==";
Console.Out.WriteLine("ECB模式"); var sm4 = new SM4Helper();
var plainText = sm4.Decrypt_ECB(cipherText);
Console.Out.WriteLine("明文: " + plainText);
Assert.NotNull(plainText); Console.Out.WriteLine("ECB模式");
Assert.Equal("老张的哲学", plainText); var plainText = sm4.Decrypt_ECB(cipherText);
} Console.Out.WriteLine("明文: " + plainText);
Assert.NotNull(plainText);
Assert.Equal("老张的哲学", plainText);
} }
} }

@ -5,74 +5,73 @@ using Autofac;
using Tiobon.Core.AuthHelper; using Tiobon.Core.AuthHelper;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace Tiobon.Core.Tests namespace Tiobon.Core.Tests;
public class LoginController_Should
{ {
public class LoginController_Should LoginController loginController;
private readonly IGhrs_UserServices _ghrs_UserServices;
private readonly IUserRoleServices _userRoleServices;
private readonly IRoleServices _roleServices;
private readonly PermissionRequirement _requirement;
private readonly IRoleModulePermissionServices _roleModulePermissionServices;
private readonly ILogger<LoginController> _logger;
DI_Test dI_Test = new DI_Test();
public LoginController_Should()
{ {
LoginController loginController; var container = dI_Test.DICollections();
_ghrs_UserServices = container.Resolve<IGhrs_UserServices>();
private readonly IGhrs_UserServices _ghrs_UserServices; _userRoleServices = container.Resolve<IUserRoleServices>();
private readonly IUserRoleServices _userRoleServices; _roleServices = container.Resolve<IRoleServices>();
private readonly IRoleServices _roleServices; _requirement = container.Resolve<PermissionRequirement>();
private readonly PermissionRequirement _requirement; _roleModulePermissionServices = container.Resolve<IRoleModulePermissionServices>();
private readonly IRoleModulePermissionServices _roleModulePermissionServices; _logger = container.Resolve<ILogger<LoginController>>();
private readonly ILogger<LoginController> _logger; loginController = new LoginController(_ghrs_UserServices, _userRoleServices, _roleServices, _requirement,
_roleModulePermissionServices, _logger);
DI_Test dI_Test = new DI_Test(); }
[Fact]
public LoginController_Should() public void GetJwtStrTest()
{ {
var container = dI_Test.DICollections(); var data = loginController.GetJwtStr("test", "test");
_ghrs_UserServices = container.Resolve<IGhrs_UserServices>();
_userRoleServices = container.Resolve<IUserRoleServices>(); Assert.NotNull(data);
_roleServices = container.Resolve<IRoleServices>(); }
_requirement = container.Resolve<PermissionRequirement>();
_roleModulePermissionServices = container.Resolve<IRoleModulePermissionServices>(); [Fact]
_logger = container.Resolve<ILogger<LoginController>>(); public void GetJwtStrForNuxtTest()
loginController = new LoginController(_ghrs_UserServices, _userRoleServices, _roleServices, _requirement, {
_roleModulePermissionServices, _logger); object Tiobons = loginController.GetJwtStrForNuxt("test", "test");
}
Assert.NotNull(Tiobons);
[Fact] }
public void GetJwtStrTest()
{ [Fact]
var data = loginController.GetJwtStr("test", "test"); public async void GetJwtToken3Test()
{
Assert.NotNull(data); var res = await loginController.GetJwtToken3("test", "test");
}
Assert.NotNull(res);
[Fact] }
public void GetJwtStrForNuxtTest()
{ [Fact]
object Tiobons = loginController.GetJwtStrForNuxt("test", "test"); public async void RefreshTokenTest()
{
Assert.NotNull(Tiobons); var res = await loginController.RefreshToken(
} "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoidGVzdCIsImp0aSI6IjgiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL2V4cGlyYXRpb24iOiIyMDE5LzEwLzE4IDIzOjI2OjQ5IiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9yb2xlIjoiQWRtaW5UZXN0IiwibmJmIjoxNTcxNDA4ODA5LCJleHAiOjE1NzE0MTI0MDksImlzcyI6IkJsb2cuQ29yZSIsImF1ZCI6IndyIn0.oz-SPz6UCL78fM09bUecw5rmjcNYEY9dWGtuPs2gdBg");
[Fact] Assert.NotNull(res);
public async void GetJwtToken3Test() }
{
var res = await loginController.GetJwtToken3("test", "test"); [Fact]
public void Md5PasswordTest()
Assert.NotNull(res); {
} var res = loginController.Md5Password("test");
[Fact] Assert.NotNull(res);
public async void RefreshTokenTest()
{
var res = await loginController.RefreshToken(
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoidGVzdCIsImp0aSI6IjgiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL2V4cGlyYXRpb24iOiIyMDE5LzEwLzE4IDIzOjI2OjQ5IiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9yb2xlIjoiQWRtaW5UZXN0IiwibmJmIjoxNTcxNDA4ODA5LCJleHAiOjE1NzE0MTI0MDksImlzcyI6IkJsb2cuQ29yZSIsImF1ZCI6IndyIn0.oz-SPz6UCL78fM09bUecw5rmjcNYEY9dWGtuPs2gdBg");
Assert.NotNull(res);
}
[Fact]
public void Md5PasswordTest()
{
var res = loginController.Md5Password("test");
Assert.NotNull(res);
}
} }
} }

@ -19,125 +19,124 @@ using Tiobon.Core.IRepository.Base;
using Tiobon.Core.Repository.Base; using Tiobon.Core.Repository.Base;
using Tiobon.Core.Repository.MongoRepository; using Tiobon.Core.Repository.MongoRepository;
namespace Tiobon.Core.Tests namespace Tiobon.Core.Tests;
public class DI_Test
{ {
public class DI_Test /// <summary>
/// 连接字符串
/// Tiobon.Core
/// </summary>
public static MutiDBOperate GetMainConnectionDb()
{ {
/// <summary> var mainConnetctDb = BaseDBConfig.MutiConnectionString.allDbs.Find(x => x.ConnId == MainDb.CurrentDbConnId);
/// 连接字符串 if (BaseDBConfig.MutiConnectionString.allDbs.Count > 0)
/// Tiobon.Core
/// </summary>
public static MutiDBOperate GetMainConnectionDb()
{ {
var mainConnetctDb = BaseDBConfig.MutiConnectionString.allDbs.Find(x => x.ConnId == MainDb.CurrentDbConnId); if (mainConnetctDb == null)
if (BaseDBConfig.MutiConnectionString.allDbs.Count > 0)
{
if (mainConnetctDb == null)
{
mainConnetctDb = BaseDBConfig.MutiConnectionString.allDbs[0];
}
}
else
{ {
throw new Exception("请确保appsettigns.json中配置连接字符串,并设置Enabled为true;"); mainConnetctDb = BaseDBConfig.MutiConnectionString.allDbs[0];
} }
return mainConnetctDb;
} }
else
public IContainer DICollections()
{ {
var basePath = AppContext.BaseDirectory; throw new Exception("请确保appsettigns.json中配置连接字符串,并设置Enabled为true;");
}
IServiceCollection services = new ServiceCollection(); return mainConnetctDb;
services.AddAutoMapperSetup(); }
services.AddSingleton(new AppSettings(basePath)); public IContainer DICollections()
services.AddScoped<DBSeed>(); {
services.AddScoped<MyContext>(); var basePath = AppContext.BaseDirectory;
//读取配置文件 IServiceCollection services = new ServiceCollection();
var symmetricKeyAsBase64 = AppSecretConfig.Audience_Secret_String; services.AddAutoMapperSetup();
var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64);
var signingKey = new SymmetricSecurityKey(keyByteArray);
services.AddSingleton(new AppSettings(basePath));
services.AddScoped<DBSeed>();
services.AddScoped<MyContext>();
var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256); //读取配置文件
var symmetricKeyAsBase64 = AppSecretConfig.Audience_Secret_String;
var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64);
var signingKey = new SymmetricSecurityKey(keyByteArray);
var permission = new List<PermissionItem>();
var permissionRequirement = new PermissionRequirement( var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256);
"/api/denied",
permission,
ClaimTypes.Role,
AppSettings.app(new string[] { "Audience", "Issuer" }),
AppSettings.app(new string[] { "Audience", "Audience" }),
signingCredentials, //签名凭据
expiration: TimeSpan.FromSeconds(60 * 60) //接口的过期时间
);
services.AddSingleton(permissionRequirement);
//【授权】 var permission = new List<PermissionItem>();
services.AddAuthorization(options =>
{
options.AddPolicy(Permissions.Name,
policy => policy.Requirements.Add(permissionRequirement));
});
services.AddScoped<SqlSugar.ISqlSugarClient>(o => var permissionRequirement = new PermissionRequirement(
"/api/denied",
permission,
ClaimTypes.Role,
AppSettings.app(new string[] { "Audience", "Issuer" }),
AppSettings.app(new string[] { "Audience", "Audience" }),
signingCredentials, //签名凭据
expiration: TimeSpan.FromSeconds(60 * 60) //接口的过期时间
);
services.AddSingleton(permissionRequirement);
//【授权】
services.AddAuthorization(options =>
{
options.AddPolicy(Permissions.Name,
policy => policy.Requirements.Add(permissionRequirement));
});
services.AddScoped<SqlSugar.ISqlSugarClient>(o =>
{
return new SqlSugar.SqlSugarScope(new SqlSugar.ConnectionConfig()
{ {
return new SqlSugar.SqlSugarScope(new SqlSugar.ConnectionConfig() ConnectionString = GetMainConnectionDb().Connection, //必填, 数据库连接字符串
{ DbType = (SqlSugar.DbType)GetMainConnectionDb().DbType, //必填, 数据库类型
ConnectionString = GetMainConnectionDb().Connection, //必填, 数据库连接字符串 IsAutoCloseConnection = true, //默认false, 时候知道关闭数据库连接, 设置为true无需使用using或者Close操作
DbType = (SqlSugar.DbType)GetMainConnectionDb().DbType, //必填, 数据库类型
IsAutoCloseConnection = true, //默认false, 时候知道关闭数据库连接, 设置为true无需使用using或者Close操作
});
}); });
});
//实例化 AutoFac 容器 //实例化 AutoFac 容器
var builder = new ContainerBuilder(); var builder = new ContainerBuilder();
//builder.RegisterType<AdvertisementServices>().As<IAdvertisementServices>(); //builder.RegisterType<AdvertisementServices>().As<IAdvertisementServices>();
builder.RegisterInstance(new LoggerFactory()) builder.RegisterInstance(new LoggerFactory())
.As<ILoggerFactory>(); .As<ILoggerFactory>();
builder.RegisterGeneric(typeof(Logger<>)) builder.RegisterGeneric(typeof(Logger<>))
.As(typeof(ILogger<>)) .As(typeof(ILogger<>))
.SingleInstance(); .SingleInstance();
//指定已扫描程序集中的类型注册为提供所有其实现的接口。 //指定已扫描程序集中的类型注册为提供所有其实现的接口。
builder.RegisterGeneric(typeof(BaseRepository<>)).As(typeof(IBaseRepository<>)).InstancePerDependency(); //注册仓储 builder.RegisterGeneric(typeof(BaseRepository<>)).As(typeof(IBaseRepository<>)).InstancePerDependency(); //注册仓储
builder.RegisterGeneric(typeof(MongoBaseRepository<>)).As(typeof(IMongoBaseRepository<>)).InstancePerDependency(); //注册仓储 builder.RegisterGeneric(typeof(MongoBaseRepository<>)).As(typeof(IMongoBaseRepository<>)).InstancePerDependency(); //注册仓储
// 属性注入 // 属性注入
var controllerBaseType = typeof(ControllerBase); var controllerBaseType = typeof(ControllerBase);
//builder.RegisterAssemblyTypes(typeof(Program).Assembly) //builder.RegisterAssemblyTypes(typeof(Program).Assembly)
// .Where(t => controllerBaseType.IsAssignableFrom(t) && t != controllerBaseType) // .Where(t => controllerBaseType.IsAssignableFrom(t) && t != controllerBaseType)
// .PropertiesAutowired(); // .PropertiesAutowired();
var servicesDllFile = Path.Combine(basePath, "Tiobon.Core.Services.dll"); var servicesDllFile = Path.Combine(basePath, "Tiobon.Core.Services.dll");
var assemblysServices = Assembly.LoadFrom(servicesDllFile); var assemblysServices = Assembly.LoadFrom(servicesDllFile);
builder.RegisterAssemblyTypes(assemblysServices) builder.RegisterAssemblyTypes(assemblysServices)
.AsImplementedInterfaces() .AsImplementedInterfaces()
.InstancePerLifetimeScope() .InstancePerLifetimeScope()
.PropertiesAutowired() .PropertiesAutowired()
.EnableInterfaceInterceptors(); .EnableInterfaceInterceptors();
var repositoryDllFile = Path.Combine(basePath, "Tiobon.Core.Repository.dll"); var repositoryDllFile = Path.Combine(basePath, "Tiobon.Core.Repository.dll");
var assemblysRepository = Assembly.LoadFrom(repositoryDllFile); var assemblysRepository = Assembly.LoadFrom(repositoryDllFile);
builder.RegisterAssemblyTypes(assemblysRepository) builder.RegisterAssemblyTypes(assemblysRepository)
.PropertiesAutowired().AsImplementedInterfaces(); .PropertiesAutowired().AsImplementedInterfaces();
services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>()); services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>());
services.AddAutoMapperSetup(); services.AddAutoMapperSetup();
//将services填充到Autofac容器生成器中 //将services填充到Autofac容器生成器中
builder.Populate(services); builder.Populate(services);
//使用已进行的组件登记创建新容器 //使用已进行的组件登记创建新容器
var ApplicationContainer = builder.Build(); var ApplicationContainer = builder.Build();
return ApplicationContainer; return ApplicationContainer;
}
} }
} }

@ -1,26 +1,25 @@
using Xunit; using Xunit;
namespace Tiobon.Core.Tests namespace Tiobon.Core.Tests;
{
public class Redis_Should
{
DI_Test dI_Test = new DI_Test();
public Redis_Should() public class Redis_Should
{ {
//var container = dI_Test.DICollections(); DI_Test dI_Test = new DI_Test();
//_redisCacheManager = container.Resolve<IRedisCacheManager>();
} public Redis_Should()
{
//var container = dI_Test.DICollections();
//_redisCacheManager = container.Resolve<IRedisCacheManager>();
[Fact] }
public void Connect_Redis_Test()
{
//var redisTiobonCache = _redisCacheManager.Get<object>("Redis.Tiobon"); [Fact]
public void Connect_Redis_Test()
{
//Assert.Null(redisTiobonCache); //var redisTiobonCache = _redisCacheManager.Get<object>("Redis.Tiobon");
}
//Assert.Null(redisTiobonCache);
} }
} }

@ -1,60 +1,55 @@
using Tiobon.Core.Model.Models; using Autofac;
using Xunit;
using System;
using System.Linq;
using Autofac;
using Tiobon.Core.IRepository.Base;
using Tiobon.Core.Repository.MongoRepository;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Bson; using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver; using MongoDB.Driver;
using Tiobon.Core.Repository.MongoRepository;
using Xunit;
namespace Tiobon.Core.Tests namespace Tiobon.Core.Tests;
public class MongoRepository_Base_Should
{ {
public class MongoRepository_Base_Should public class MongoTest
{ {
public class MongoTest [BsonId]
{ public ObjectId id { get; set; }
[BsonId] public string name { get; set; }
public ObjectId id { get; set; } public bool isDel { get; set; }
public string name { get; set; } public DateTime time { get; set; }
public bool isDel { get; set; } }
public DateTime time { get; set; }
}
private IMongoBaseRepository<MongoTest> baseRepository; private IMongoBaseRepository<MongoTest> baseRepository;
DI_Test dI_Test = new DI_Test(); DI_Test dI_Test = new DI_Test();
public MongoRepository_Base_Should() public MongoRepository_Base_Should()
{ {
var container = dI_Test.DICollections(); var container = dI_Test.DICollections();
baseRepository = container.Resolve<IMongoBaseRepository<MongoTest>>(); baseRepository = container.Resolve<IMongoBaseRepository<MongoTest>>();
} }
[Fact] [Fact]
public async void Add_Test() public async void Add_Test()
{ {
await baseRepository.AddAsync(new MongoTest { isDel = false, name = "test", time = DateTime.UtcNow }); await baseRepository.AddAsync(new MongoTest { isDel = false, name = "test", time = DateTime.UtcNow });
} }
[Fact] [Fact]
public async void GetObjectId_Test() public async void GetObjectId_Test()
{ {
var data = await baseRepository.GetByObjectIdAsync("612b9b0be677976fa0f0cfa2"); var data = await baseRepository.GetByObjectIdAsync("612b9b0be677976fa0f0cfa2");
Assert.NotNull(data); Assert.NotNull(data);
} }
[Fact] [Fact]
public async void GetListFilter_Test() public async void GetListFilter_Test()
{ {
var data = await baseRepository.GetListFilterAsync(new FilterDefinitionBuilder<MongoTest>().Gte("time", DateTime.Parse("2022-06-01"))); var data = await baseRepository.GetListFilterAsync(new FilterDefinitionBuilder<MongoTest>().Gte("time", DateTime.Parse("2022-06-01")));
Assert.NotNull(data); Assert.NotNull(data);
}
} }
} }

@ -1,81 +1,78 @@
using Tiobon.Core.Model.Models; using Autofac;
using Xunit;
using System;
using System.Linq;
using Autofac;
using Tiobon.Core.IRepository.Base; using Tiobon.Core.IRepository.Base;
using Tiobon.Core.Model.Models;
using Xunit;
namespace Tiobon.Core.Tests;
namespace Tiobon.Core.Tests public class Repository_Base_Should
{ {
public class Repository_Base_Should private IBaseRepository<TiobonArticle> baseRepository;
DI_Test dI_Test = new DI_Test();
public Repository_Base_Should()
{ {
private IBaseRepository<TiobonArticle> baseRepository;
DI_Test dI_Test = new DI_Test();
public Repository_Base_Should() var container = dI_Test.DICollections();
{
var container = dI_Test.DICollections(); baseRepository = container.Resolve<IBaseRepository<TiobonArticle>>();
//DbContext.Init(BaseDBConfig.ConnectionString,(DbType)BaseDBConfig.DbType);
}
baseRepository = container.Resolve<IBaseRepository<TiobonArticle>>();
//DbContext.Init(BaseDBConfig.ConnectionString,(DbType)BaseDBConfig.DbType); [Fact]
} public async void Get_Tiobons_Test()
{
var data = await baseRepository.Query();
Assert.NotNull(data);
}
[Fact] [Fact]
public async void Get_Tiobons_Test() public async void Add_Tiobon_Test()
{
TiobonArticle TiobonArticle = new TiobonArticle()
{ {
var data = await baseRepository.Query(); bCreateTime = DateTime.Now,
bUpdateTime = DateTime.Now,
btitle = "xuint test title",
bcontent = "xuint test content",
bsubmitter = "xuint: test repositoryBase add Tiobon",
};
var BId = await baseRepository.Add(TiobonArticle);
Assert.True(BId > 0);
}
Assert.NotNull(data);
}
[Fact] [Fact]
public async void Add_Tiobon_Test() public async void Update_Tiobon_Test()
{ {
TiobonArticle TiobonArticle = new TiobonArticle() var IsUpd = false;
{ var updateModel = (await baseRepository.Query(d => d.btitle == "xuint test title")).FirstOrDefault();
bCreateTime = DateTime.Now,
bUpdateTime = DateTime.Now,
btitle = "xuint test title",
bcontent = "xuint test content",
bsubmitter = "xuint: test repositoryBase add Tiobon",
};
var BId = await baseRepository.Add(TiobonArticle);
Assert.True(BId > 0);
}
[Fact]
public async void Update_Tiobon_Test()
{
var IsUpd = false;
var updateModel = (await baseRepository.Query(d => d.btitle == "xuint test title")).FirstOrDefault();
Assert.NotNull(updateModel); Assert.NotNull(updateModel);
updateModel.bcontent = "xuint: test repositoryBase content update"; updateModel.bcontent = "xuint: test repositoryBase content update";
updateModel.bCreateTime = DateTime.Now; updateModel.bCreateTime = DateTime.Now;
updateModel.bUpdateTime = DateTime.Now; updateModel.bUpdateTime = DateTime.Now;
IsUpd = await baseRepository.Update(updateModel); IsUpd = await baseRepository.Update(updateModel);
Assert.True(IsUpd); Assert.True(IsUpd);
} }
[Fact] [Fact]
public async void Delete_Tiobon_Test() public async void Delete_Tiobon_Test()
{ {
var IsDel = false; var IsDel = false;
var deleteModel = (await baseRepository.Query(d => d.btitle == "xuint test title")).FirstOrDefault(); var deleteModel = (await baseRepository.Query(d => d.btitle == "xuint test title")).FirstOrDefault();
Assert.NotNull(deleteModel); Assert.NotNull(deleteModel);
IsDel = await baseRepository.Delete(deleteModel); IsDel = await baseRepository.Delete(deleteModel);
Assert.True(IsDel); Assert.True(IsDel);
}
} }
} }

@ -8,16 +8,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionIt
.dockerignore = .dockerignore .dockerignore = .dockerignore
.editorconfig = .editorconfig .editorconfig = .editorconfig
.gitignore = .gitignore .gitignore = .gitignore
Blog.Core.Build.bat = Blog.Core.Build.bat
Blog.Core.Publish.bat = Blog.Core.Publish.bat
Blog.Core.Publish.Docker.Jenkins.sh = Blog.Core.Publish.Docker.Jenkins.sh
Blog.Core.Publish.Docker.sh = Blog.Core.Publish.Docker.sh
Blog.Core.Publish.Linux.sh = Blog.Core.Publish.Linux.sh
codecov.yml = codecov.yml
build\common.targets = build\common.targets build\common.targets = build\common.targets
CreateYourProject.bat = CreateYourProject.bat
DockerBuild.bat = DockerBuild.bat
Dockerfile = Dockerfile
nuget.config = nuget.config nuget.config = nuget.config
README.md = README.md README.md = README.md
EndProjectSection EndProjectSection

@ -42792,11 +42792,6 @@
</summary> </summary>
</member> </member>
<member name="P:Tiobon.Core.Model.ViewModels.EnumDemoDto.Type">
<summary>
Type Description balabala
</summary>
</member>
<member name="P:Tiobon.Core.Model.ViewModels.Extend.CertificateDesignerData.bgColor"> <member name="P:Tiobon.Core.Model.ViewModels.Extend.CertificateDesignerData.bgColor">
<summary> <summary>
bgColor bgColor
@ -43662,26 +43657,6 @@
用来测试 RestSharp Post 请求 用来测试 RestSharp Post 请求
</summary> </summary>
</member> </member>
<member name="T:Tiobon.Core.Model.ViewModels.TopgbViewModels">
<summary>
留言排名展示类
</summary>
</member>
<member name="P:Tiobon.Core.Model.ViewModels.TopgbViewModels.TiobonId">
<summary>博客ID
</summary>
</member>
<member name="P:Tiobon.Core.Model.ViewModels.TopgbViewModels.counts">
<summary>
评论数量
</summary>
</member>
<member name="P:Tiobon.Core.Model.ViewModels.TopgbViewModels.btitle">
<summary>博客标题
</summary>
</member>
<member name="P:Tiobon.Core.Model.CustomFieldView.GroupName"> <member name="P:Tiobon.Core.Model.CustomFieldView.GroupName">
<summary> <summary>
分组 分组

Loading…
Cancel
Save