master
xiaochanghai 2 weeks ago
parent 6caeda7651
commit 75351ca06c
  1. 79
      Ocelot.Provider.Nacos/Nacos.cs
  2. 39
      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. 269
      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. 7
      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 Ocelot.Values;
using Microsoft.Extensions.Options;
using Nacos.V2;
using Microsoft.Extensions.Options;
using Ocelot.Provider.Nacos.NacosClient.V2;
using Ocelot.ServiceDiscovery.Providers;
using Ocelot.Values;
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 string _groupName;
private readonly List<string> _clusters;
private readonly INacosNamingService _client;
private readonly string _serviceName;
private readonly string _groupName;
private readonly List<string> _clusters;
public Nacos(string serviceName, INacosNamingService client, IOptions<NacosAspNetOptions> options)
{
_serviceName = serviceName;
_client = client;
_groupName = string.IsNullOrWhiteSpace(options.Value.GroupName) ?
NacosConstants.DEFAULT_GROUP : options.Value.GroupName;
_clusters = (string.IsNullOrWhiteSpace(options.Value.ClusterName) ? NacosConstants.DEFAULT_CLUSTER_NAME : options.Value.ClusterName).Split(",").ToList();
}
public Nacos(string serviceName, INacosNamingService client, IOptions<NacosAspNetOptions> options)
{
_serviceName = serviceName;
_client = client;
_groupName = string.IsNullOrWhiteSpace(options.Value.GroupName) ?
NacosConstants.DEFAULT_GROUP : options.Value.GroupName;
_clusters = (string.IsNullOrWhiteSpace(options.Value.ClusterName) ? NacosConstants.DEFAULT_CLUSTER_NAME : options.Value.ClusterName).Split(",").ToList();
}
public async Task<List<Service>> Get()
{
var services = new List<Service>();
public async Task<List<Service>> Get()
{
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;
int sport = Sitem.Port;
if (Sitem.Metadata.ContainsKey("endpoint"))
{
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>()));
string[] ipport = Sitem.Metadata["endpoint"].Split(':');
sip = ipport[0];
sport =int.Parse( ipport[1]);
}
// 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>()));
}
return await Task.FromResult(services);
// services.AddRange(instances.Select(i => new Service(i.InstanceId, new ServiceHostAndPort(i.Ip, i.Port), "", "", new List<string>())));
}
public Task<List<Service>> GetAsync()
{
throw new NotImplementedException();
}
return await Task.FromResult(services);
}
public Task<List<Service>> GetAsync()
{
throw new NotImplementedException();
}
}

@ -2,25 +2,24 @@
using Nacos.V2.Naming.Dtos;
using System.Collections.Generic;
namespace Ocelot.Provider.Nacos.NacosClient
namespace Ocelot.Provider.Nacos.NacosClient;
public interface ILBStrategy
{
public interface ILBStrategy
{
/// <summary>
/// Strategy Name
/// </summary>
LBStrategyName Name { get; }
/// <summary>
/// Get host
/// </summary>
/// <param name="list">host list</param>
/// <returns>The Host</returns>
Instance GetHost(List<Instance> list);
}
/// <summary>
/// Strategy Name
/// </summary>
LBStrategyName Name { get; }
/// <summary>
/// Get host
/// </summary>
/// <param name="list">host list</param>
/// <returns>The Host</returns>
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>
WeightRoundRobin,
/// <summary>
/// Weight Round Robin
/// </summary>
WeightRoundRobin,
/// <summary>
/// Weight Random
/// </summary>
WeightRandom,
/// <summary>
/// Weight Random
/// </summary>
WeightRandom,
/// <summary>
/// Ext1
/// </summary>
Ext1
}
/// <summary>
/// Ext1
/// </summary>
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 System;
using System.Collections.Generic;
using System.Linq;
using Nacos.V2.Naming.Dtos;
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)
{
var dict = BuildScore(list);
public Instance GetHost(List<Instance> 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)
{
var arr = item.Key.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));
}
if (instance == null)
{
var arr = item.Key.Split("#");
var ip = arr[0];
int.TryParse(arr[1], out var port);
var cluster = arr[2];
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)
{
var dict = new Dictionary<string, double>();
return instance;
}
// 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; });
private Dictionary<string, double> BuildScore(List<Instance> list)
{
var dict = new Dictionary<string, double>();
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();
// 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 total = tmp.Sum(x => x.Weight);
var cur = 0d;
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();
foreach (var item in tmp)
{
cur += item.Weight;
dict.TryAdd(item.InstanceId, cur / total);
}
var total = tmp.Sum(x => x.Weight);
var cur = 0d;
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 System.Collections.Generic;
using System.Linq;
using Nacos.V2.Naming.Dtos;
namespace Ocelot.Provider.Nacos.NacosClient
using Nacos.V2.Naming.Dtos;
namespace Ocelot.Provider.Nacos.NacosClient;
public class WeightRoundRobinLBStrategy : ILBStrategy
{
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
// 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();
// <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;
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;
}
}

@ -1,145 +1,141 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Http.Features;
using System.Net;
using System.Net.NetworkInformation;
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[] { ',', ';' };
var appPort = port <= 0 ? 80 : port;
// it seems that nacos don't return the scheme
// so here use http only.
return new List<Uri> { new Uri($"http://{ip}:{appPort}") };
}
// 1. config
if (!string.IsNullOrWhiteSpace(ip))
{
// it seems that nacos don't return the scheme
// so here use http only.
return new List<Uri> { new Uri($"http://{ip}:{appPort}") };
}
// 1.1. Ip is null && Port has value
if (string.IsNullOrWhiteSpace(ip) && appPort != 80)
{
return new List<Uri> { new Uri($"http://{GetCurrentIp(preferredNetworks)}:{appPort}") };
}
// 1.1. Ip is null && Port has value
if (string.IsNullOrWhiteSpace(ip) && appPort != 80)
{
return new List<Uri> { new Uri($"http://{GetCurrentIp(preferredNetworks)}:{appPort}") };
}
var address = string.Empty;
var address = string.Empty;
// 2. IServerAddressesFeature
if (features != null)
{
var addresses = features.Get<IServerAddressesFeature>();
var addressCollection = addresses?.Addresses;
// 2. IServerAddressesFeature
if (features != null)
if (addressCollection != null && addressCollection.Any())
{
var addresses = features.Get<IServerAddressesFeature>();
var addressCollection = addresses?.Addresses;
if (addressCollection != null && addressCollection.Any())
var uris = new List<Uri>();
foreach (var item in addressCollection)
{
var uris = new List<Uri>();
foreach (var item in addressCollection)
{
var url = ReplaceAddress(item, preferredNetworks);
uris.Add(new Uri(url));
}
return uris;
var url = ReplaceAddress(item, preferredNetworks);
uris.Add(new Uri(url));
}
return uris;
}
}
// 3. ASPNETCORE_URLS
address = Environment.GetEnvironmentVariable("ASPNETCORE_URLS");
if (!string.IsNullOrWhiteSpace(address))
// 3. ASPNETCORE_URLS
address = Environment.GetEnvironmentVariable("ASPNETCORE_URLS");
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);
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);
// 5. current ip address third
address = $"http://{GetCurrentIp(preferredNetworks)}:{appPort}";
return url.Split(splitChars).Select(x => new Uri(x));
}
}
return new List<Uri> { new Uri(address) };
}
// 5. current ip address third
address = $"http://{GetCurrentIp(preferredNetworks)}:{appPort}";
private static string ReplaceAddress(string address, string preferredNetworks)
{
var ip = GetCurrentIp(preferredNetworks);
return new List<Uri> { new Uri(address) };
if (address.Contains("*"))
{
address = address.Replace("*", ip);
}
private static string ReplaceAddress(string address, string preferredNetworks)
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))
{
var ip = GetCurrentIp(preferredNetworks);
address = address.Replace("0.0.0.0", ip, StringComparison.OrdinalIgnoreCase);
}
if (address.Contains("*"))
{
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;
}
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
{
// 获取可用网卡
var nics = NetworkInterface.GetAllNetworkInterfaces()?.Where(network => network.OperationalStatus == OperationalStatus.Up);
// 获取所有可用网卡IP信息
var ipCollection = nics?.Select(x => x.GetIPProperties())?.SelectMany(x => x.UnicastAddresses);
// 获取所有可用网卡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();
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.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>
public string ServiceName { get; set; }
/// <summary>
/// the name of the service.
/// </summary>
public string ServiceName { get; set; }
/// <summary>
/// the name of the group.
/// </summary>
public string GroupName { get; set; } = Constants.DEFAULT_GROUP;
/// <summary>
/// the name of the group.
/// </summary>
public string GroupName { get; set; } = Constants.DEFAULT_GROUP;
/// <summary>
/// the name of the cluster.
/// </summary>
/// <value>The name of the cluster.</value>
public string ClusterName { get; set; } = Constants.DEFAULT_CLUSTER_NAME;
/// <summary>
/// the name of the cluster.
/// </summary>
/// <value>The name of the cluster.</value>
public string ClusterName { get; set; } = Constants.DEFAULT_CLUSTER_NAME;
/// <summary>
/// the ip of this instance
/// </summary>
public string Ip { get; set; }
/// <summary>
/// the ip of this instance
/// </summary>
public string Ip { get; set; }
/// <summary>
/// Select an IP that matches the prefix as the service registration IP
/// like the config of spring.cloud.inetutils.preferred-networks
/// </summary>
public string PreferredNetworks { get; set; }
/// <summary>
/// Select an IP that matches the prefix as the service registration IP
/// like the config of spring.cloud.inetutils.preferred-networks
/// </summary>
public string PreferredNetworks { get; set; }
/// <summary>
/// the port of this instance
/// </summary>
public int Port { get; set; }
/// <summary>
/// the port of this instance
/// </summary>
public int Port { get; set; }
/// <summary>
/// the weight of this instance.
/// </summary>
public double Weight { get; set; } = 100;
/// <summary>
/// the weight of this instance.
/// </summary>
public double Weight { get; set; } = 100;
/// <summary>
/// if you just want to subscribe, but don't want to register your service, set it to false.
/// </summary>
public bool RegisterEnabled { get; set; } = true;
/// <summary>
/// if you just want to subscribe, but don't want to register your service, set it to false.
/// </summary>
public bool RegisterEnabled { get; set; } = true;
/// <summary>
/// the metadata of this instance
/// </summary>
public Dictionary<string, string> Metadata { get; set; } = new Dictionary<string, string>();
/// <summary>
/// the metadata of this instance
/// </summary>
public Dictionary<string, string> Metadata { get; set; } = new Dictionary<string, string>();
/// <summary>
/// If instance is enabled to accept request. The default value is true.
/// </summary>
public bool InstanceEnabled { get; set; } = true;
/// <summary>
/// If instance is enabled to accept request. The default value is true.
/// </summary>
public bool InstanceEnabled { get; set; } = true;
/// <summary>
/// If instance is ephemeral.The default value is true.
/// </summary>
public bool Ephemeral { get; set; } = true;
/// <summary>
/// If instance is ephemeral.The default value is true.
/// </summary>
public bool Ephemeral { get; set; } = true;
/// <summary>
/// whether your service is a https service.
/// </summary>
public bool Secure { get; set; } = false;
/// <summary>
/// whether your service is a https service.
/// </summary>
public bool Secure { get; set; } = false;
/// <summary>
/// Load Balance Strategy
/// </summary>
public string LBStrategy { get; set; } = LBStrategyName.WeightRandom.ToString();
/// <summary>
/// Load Balance Strategy
/// </summary>
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,
ContextPath = this.ContextPath,
DefaultTimeOut = this.DefaultTimeOut,
EndPoint = this.EndPoint,
ListenInterval = this.ListenInterval,
Namespace = this.Namespace,
NamingLoadCacheAtStart = this.NamingLoadCacheAtStart,
NamingUseRpc = this.NamingUseRpc,
Password = this.Password,
SecretKey = this.SecretKey,
ServerAddresses = this.ServerAddresses,
UserName = this.UserName,
};
}
AccessKey = this.AccessKey,
ConfigUseRpc = this.ConfigUseRpc,
ContextPath = this.ContextPath,
DefaultTimeOut = this.DefaultTimeOut,
EndPoint = this.EndPoint,
ListenInterval = this.ListenInterval,
Namespace = this.Namespace,
NamingLoadCacheAtStart = this.NamingLoadCacheAtStart,
NamingUseRpc = this.NamingUseRpc,
Password = this.Password,
SecretKey = this.SecretKey,
ServerAddresses = this.ServerAddresses,
UserName = this.UserName,
};
}
}

@ -1,69 +1,103 @@
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Nacos.V2;
using Nacos.V2.Naming.Core;
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 MetadataSecure = "secure";
private static readonly string MetadataNetVersion = "DOTNET_VERSION";
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 readonly INacosNamingService _svc;
private readonly IFeatureCollection _features;
private NacosAspNetOptions _options;
private IEnumerable<Uri> uris = null;
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(
ILoggerFactory loggerFactory,
INacosNamingService svc,
IServer server,
IOptionsMonitor<NacosAspNetOptions> optionsAccs)
public async Task StartAsync()
{
if (!_options.RegisterEnabled)
{
_logger = loggerFactory.CreateLogger<RegSvcBgTask>();
_svc = svc;
_options = optionsAccs.CurrentValue;
_features = server.Features;
_logger.LogInformation("setting RegisterEnabled to false, will not register to nacos");
return;
}
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");
return;
metadata.TryAdd(item.Key, item.Value);
}
}
uris = UriTool.GetUri(_features, _options.Ip, _options.Port, _options.PreferredNetworks);
var metadata = new Dictionary<string, string>()
foreach (var uri in uris)
{
for (int i = 0; i < 3; i++)
{
{ PreservedMetadataKeys.REGISTER_SOURCE, $"ASPNET_CORE" },
{ MetadataNetVersion, Environment.Version.ToString() },
{ MetadataHostOs, Environment.OSVersion.ToString() },
};
try
{
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)
{
if (!metadata.ContainsKey(item.Key))
await _svc.RegisterInstance(_options.ServiceName, _options.GroupName, instance);
break;
}
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)
{
@ -71,54 +105,14 @@ namespace Ocelot.Provider.Nacos.NacosClient.V2
{
try
{
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 = ""
};
_logger.LogInformation("register instance to nacos server, 【{0}】", instance);
await _svc.RegisterInstance(_options.ServiceName, _options.GroupName, instance);
_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, "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)
{
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);
}
_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.Hosting;
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>
/// 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)
{
RegSvcBgTask regSvcBgTask = app.ApplicationServices.GetRequiredService<RegSvcBgTask>();
await regSvcBgTask.StartAsync();
lifetime.ApplicationStopping.Register(async () => {
await regSvcBgTask.StopAsync();
});
return app;
}
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 System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Ocelot.Configuration;
using Ocelot.Configuration.Repository;
using Ocelot.Middleware;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
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 hostLifetime = builder.ApplicationServices.GetService<IHostApplicationLifetime>();
var internalConfigRepo = builder.ApplicationServices.GetService<IInternalConfigurationRepository>();
var config = internalConfigRepo.Get();
if (UsingNacosServiceDiscoveryProvider(config.Data))
{
builder.UseNacosAspNet(hostLifetime).GetAwaiter().GetResult();
}
var hostLifetime = builder.ApplicationServices.GetService<IHostApplicationLifetime>();
return Task.CompletedTask;
};
private static bool UsingNacosServiceDiscoveryProvider(IInternalConfiguration configuration)
if (UsingNacosServiceDiscoveryProvider(config.Data))
{
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 Ocelot.ServiceDiscovery;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Nacos.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>();
if (config.Type?.ToLower() == "nacos" && client != null)
{
var option = provider.GetService<IOptions<NacosAspNetOptions>>();
return new Nacos(route.ServiceName, client, option);
}
return null;
};
}
var option = provider.GetService<IOptions<NacosAspNetOptions>>();
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.Middleware;
using Ocelot.Provider.Nacos.NacosClient.V2;
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.AddSingleton<OcelotMiddlewareConfigurationDelegate>(NacosMiddlewareConfigurationProvider.Get);
return builder;
}
builder.Services.AddNacosAspNet(builder.Configuration);
builder.Services.AddSingleton<ServiceDiscoveryFinderDelegate>(NacosProviderFactory.Get);
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
{

@ -4,114 +4,113 @@ using Microsoft.Extensions.Options;
using Newtonsoft.Json;
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>
/// 基于Kafka的事件总线
/// 发布
/// </summary>
public class EventBusKafka : IEventBus
public void Publish(IntegrationEvent @event)
{
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)
var producer = _connectionPool.Producer();
try
{
_logger = logger;
_subsManager = subsManager;
_connectionPool = connectionPool;
_options = options.Value;
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();
}
/// <summary>
/// 发布
/// </summary>
public void Publish(IntegrationEvent @event)
catch (Exception ex)
{
var producer = _connectionPool.Producer();
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);
}
_logger.LogWarning($"Could not publish event: {@event.Id.ToString("N")} ({ex.Message}); Message:{ JsonConvert.SerializeObject(@event)}");
}
/// <summary>
/// 订阅
/// 动态
/// </summary>
/// <typeparam name="TH">事件处理器</typeparam>
/// <param name="eventName">事件名</param>
public void SubscribeDynamic<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler
finally
{
_logger.LogInformation("Subscribing to dynamic event {EventName} with {EventHandler}", eventName, typeof(TH).GetGenericTypeName());
_subsManager.AddDynamicSubscription<TH>(eventName);
//放入连接池中
_connectionPool.Return(producer);
}
}
/// <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>
/// 订阅
/// 动态
/// </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());
_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>
/// 取消订阅
/// </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("Subscribing to event {EventName} with {EventHandler}", eventName, typeof(TH).GetGenericTypeName());
_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)
where TH : IDynamicIntegrationEventHandler
{
_subsManager.RemoveDynamicSubscription<TH>(eventName);
}
_logger.LogInformation("Unsubscribing from event {EventName}", 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();
}
_subsManager.Clear();
_connectionPool.Dispose();
}
_subsManager.Clear();
}
}

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

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

@ -6,157 +6,152 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
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>
/// Kafka consumer 监听服务
/// </summary>
public class KafkaConsumerHostService : BackgroundService
private readonly string AUTOFAC_SCOPE_NAME = "Tioboncore_event_bus";
private readonly ILogger<KafkaConsumerHostService> _logger;
private readonly IConsumer<string, byte[]> _consumer;
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";
private readonly ILogger<KafkaConsumerHostService> _logger;
private readonly IConsumer<string, byte[]> _consumer;
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)
_autofac = autofac;
_subsManager = eventBusSubscriptionsManager;
_logger = logger;
_options = options.Value;
_consumer = new ConsumerBuilder<string, byte[]>(new ConsumerConfig
{
_autofac = autofac;
_subsManager = eventBusSubscriptionsManager;
_logger = logger;
_options = options.Value;
_consumer = new ConsumerBuilder<string, byte[]>(new ConsumerConfig
{
BootstrapServers = _options.Servers,
GroupId = _options.GroupId,
AutoOffsetReset = AutoOffsetReset.Earliest,
AllowAutoCreateTopics = true,
EnableAutoCommit = false,
LogConnectionClose = false
}).SetErrorHandler(ConsumerClient_OnConsumeError)
.Build();
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
BootstrapServers = _options.Servers,
GroupId = _options.GroupId,
AutoOffsetReset = AutoOffsetReset.Earliest,
AllowAutoCreateTopics = true,
EnableAutoCommit = false,
LogConnectionClose = false
}).SetErrorHandler(ConsumerClient_OnConsumeError)
.Build();
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var result = await FetchTopicAsync();
if (result)
{
var result = await FetchTopicAsync();
if (result)
_consumer.Subscribe(_options.Topic);
while (!cts.Token.IsCancellationRequested)
{
_consumer.Subscribe(_options.Topic);
while (!cts.Token.IsCancellationRequested)
var consumerResult = _consumer.Consume(cts.Token);
try
{
var consumerResult = _consumer.Consume(cts.Token);
try
{
if (consumerResult.IsPartitionEOF || consumerResult.Message.Value == null) continue;
if (consumerResult.IsPartitionEOF || consumerResult.Message.Value == null) continue;
var @event = Protobuf.Deserialize<string>(consumerResult.Message.Value);
await ProcessEvent(consumerResult.Message.Key, @event);
}
catch (ConsumeException e)
{
_logger.LogError($"Error occured: {e.Error.Reason}");
}
finally
{
_consumer.Commit(consumerResult);
}
var @event = Protobuf.Deserialize<string>(consumerResult.Message.Value);
await ProcessEvent(consumerResult.Message.Key, @event);
}
catch (ConsumeException e)
{
_logger.LogError($"Error occured: {e.Error.Reason}");
}
finally
{
_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();
_logger.LogInformation("kafka consumer stop and disposable");
_consumer.Dispose();
return base.StopAsync(cancellationToken);
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
}));
}
/// <summary>
/// 检测当前Topic是否存在
/// </summary>
/// <returns></returns>
private async Task<bool> FetchTopicAsync()
catch (CreateTopicsException ex) when (ex.Message.Contains("already exists"))
{
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>
/// 接收到消息进行处理
/// </summary>
/// <param name="eventName">事件名称</param>
/// <param name="message">消息内容</param>
/// <returns></returns>
private async Task ProcessEvent(string eventName, string message)
catch (Exception ex)
{
_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);
foreach (var subscription in subscriptions)
if (subscription.IsDynamic)
{
if (subscription.IsDynamic)
{
var handler = scope.ResolveOptional(subscription.HandlerType) as IDynamicIntegrationEventHandler;
if (handler == null) continue;
dynamic eventData = JObject.Parse(message);
var handler = scope.ResolveOptional(subscription.HandlerType) as IDynamicIntegrationEventHandler;
if (handler == null) continue;
dynamic eventData = JObject.Parse(message);
await Task.Yield();
await handler.Handle(eventData);
}
else
{
var handler = scope.ResolveOptional(subscription.HandlerType);
if (handler == null) continue;
var eventType = _subsManager.GetEventTypeByName(eventName);
var integrationEvent = JsonConvert.DeserializeObject(message, eventType);
var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType);
await Task.Yield();
await handler.Handle(eventData);
}
else
{
var handler = scope.ResolveOptional(subscription.HandlerType);
if (handler == null) continue;
var eventType = _subsManager.GetEventTypeByName(eventName);
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
{
_logger.LogWarning("No subscription for Kafka event: {EventName}", eventName);
}
}
private void ConsumerClient_OnConsumeError(IConsumer<string, byte[]> consumer, Error e)
else
{
_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>
/// 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>
public int NumPartitions { get; set; }
}
public int NumPartitions { get; set; }
}

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

@ -1,183 +1,177 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Tiobon.Core.EventBus
namespace Tiobon.Core.EventBus;
/// <summary>
/// 基于内存
/// 事件总线订阅管理器
/// 单例模式
/// </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>
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;
private readonly List<Type> _eventTypes;
DoAddSubscription(typeof(TH), eventName, isDynamic: true);
}
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 = new List<Type>();
_eventTypes.Add(typeof(T));
}
}
public bool IsEmpty => !_handlers.Keys.Any();
public void Clear() => _handlers.Clear();
/// <summary>
/// 添加动态订阅
/// </summary>
/// <typeparam name="TH">约束:动态事件处理器接口</typeparam>
/// <param name="eventName"></param>
public void AddDynamicSubscription<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler
private void DoAddSubscription(Type handlerType, string eventName, bool isDynamic)
{
if (!HasSubscriptionsForEvent(eventName))
{
DoAddSubscription(typeof(TH), eventName, isDynamic: true);
_handlers.Add(eventName, new List<SubscriptionInfo>());
}
/// <summary>
/// 添加订阅
/// </summary>
/// <typeparam name="T">约束:事件</typeparam>
/// <typeparam name="TH">约束:事件处理器接口<事件></typeparam>
public void AddSubscription<T, TH>()
where T : IntegrationEvent
where TH : IIntegrationEventHandler<T>
if (_handlers[eventName].Any(s => s.HandlerType == handlerType))
{
var eventName = GetEventKey<T>();
DoAddSubscription(typeof(TH), eventName, isDynamic: false);
if (!_eventTypes.Contains(typeof(T)))
{
_eventTypes.Add(typeof(T));
}
throw new ArgumentException(
$"Handler Type {handlerType.Name} already registered for '{eventName}'", nameof(handlerType));
}
private void DoAddSubscription(Type handlerType, string eventName, bool isDynamic)
if (isDynamic)
{
if (!HasSubscriptionsForEvent(eventName))
{
_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));
}
_handlers[eventName].Add(SubscriptionInfo.Dynamic(handlerType));
}
/// <summary>
/// 移除动态订阅
/// </summary>
/// <typeparam name="TH"></typeparam>
/// <param name="eventName"></param>
public void RemoveDynamicSubscription<TH>(string eventName)
where TH : IDynamicIntegrationEventHandler
else
{
var handlerToRemove = FindDynamicSubscriptionToRemove<TH>(eventName);
DoRemoveHandler(eventName, handlerToRemove);
_handlers[eventName].Add(SubscriptionInfo.Typed(handlerType));
}
}
/// <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>
where T : IntegrationEvent
{
var handlerToRemove = FindSubscriptionToRemove<T, TH>();
var eventName = GetEventKey<T>();
DoRemoveHandler(eventName, handlerToRemove);
}
public void RemoveSubscription<T, TH>()
where TH : IIntegrationEventHandler<T>
where T : IntegrationEvent
{
var handlerToRemove = FindSubscriptionToRemove<T, TH>();
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);
if (!_handlers[eventName].Any())
_handlers.Remove(eventName);
var eventType = _eventTypes.SingleOrDefault(e => e.Name == eventName);
if (eventType != null)
{
_handlers.Remove(eventName);
var eventType = _eventTypes.SingleOrDefault(e => e.Name == eventName);
if (eventType != null)
{
_eventTypes.Remove(eventType);
}
RaiseOnEventRemoved(eventName);
_eventTypes.Remove(eventType);
}
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)
where TH : IDynamicIntegrationEventHandler
{
return DoFindSubscriptionToRemove(eventName, typeof(TH));
}
public IEnumerable<SubscriptionInfo> GetHandlersForEvent<T>() where T : IntegrationEvent
{
var key = GetEventKey<T>();
return GetHandlersForEvent(key);
}
public IEnumerable<SubscriptionInfo> GetHandlersForEvent(string eventName) => _handlers[eventName];
/// <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));
}
private void RaiseOnEventRemoved(string eventName)
{
var handler = OnEventRemoved;
handler?.Invoke(this, eventName);
}
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 HasSubscriptionsForEvent(key);
return null;
}
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>
/// 订阅信息模型
/// </summary>
public class SubscriptionInfo
{
public bool IsDynamic { get; }
public Type HandlerType { get; }
private SubscriptionInfo(bool isDynamic, Type handlerType)
{
IsDynamic = isDynamic;
HandlerType = handlerType;
}
public bool IsDynamic { get; }
public Type HandlerType { get; }
public static SubscriptionInfo Dynamic(Type handlerType)
{
return new SubscriptionInfo(true, handlerType);
}
public static SubscriptionInfo Typed(Type handlerType)
{
return new SubscriptionInfo(false, handlerType);
}
private SubscriptionInfo(bool isDynamic, Type handlerType)
{
IsDynamic = isDynamic;
HandlerType = 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>
/// 动态集成事件处理程序
/// 接口
/// </summary>
public interface IDynamicIntegrationEventHandler
{
Task Handle(dynamic eventData);
}
Task Handle(dynamic eventData);
}

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

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

@ -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>
/// 集成事件处理程序
/// 泛型接口
/// </summary>
/// <typeparam name="TIntegrationEvent"></typeparam>
public interface IIntegrationEventHandler<in TIntegrationEvent> : IIntegrationEventHandler
where TIntegrationEvent : IntegrationEvent
{
Task Handle(TIntegrationEvent @event);
}
Task Handle(TIntegrationEvent @event);
}
/// <summary>
/// 集成事件处理程序
/// 基 接口
/// </summary>
public interface IIntegrationEventHandler
{
}
/// <summary>
/// 集成事件处理程序
/// 基 接口
/// </summary>
public interface IIntegrationEventHandler
{
}

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

@ -11,341 +11,340 @@ using System.Net.Sockets;
using System.Text;
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>
/// 基于RabbitMQ的事件总线
/// 订阅管理器事件
/// </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";
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)
if (!_persistentConnection.IsConnected)
{
_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;
_persistentConnection.TryConnect();
}
/// <summary>
/// 订阅管理器事件
/// </summary>
/// <param name="sender"></param>
/// <param name="eventName"></param>
private void SubsManager_OnEventRemoved(object sender, string eventName)
using (var channel = _persistentConnection.CreateModel())
{
if (!_persistentConnection.IsConnected)
{
_persistentConnection.TryConnect();
}
channel.QueueUnbind(queue: _queueName,
exchange: BROKER_NAME,
routingKey: eventName);
using (var channel = _persistentConnection.CreateModel())
if (_subsManager.IsEmpty)
{
channel.QueueUnbind(queue: _queueName,
exchange: BROKER_NAME,
routingKey: eventName);
if (_subsManager.IsEmpty)
{
_queueName = string.Empty;
_consumerChannel.Close();
}
_queueName = string.Empty;
_consumerChannel.Close();
}
}
}
/// <summary>
/// 发布
/// </summary>
/// <param name="event">事件模型</param>
public void Publish(IntegrationEvent @event)
/// <summary>
/// 发布
/// </summary>
/// <param name="event">事件模型</param>
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>()
.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;
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);
var body = Encoding.UTF8.GetBytes(message);
policy.Execute(() =>
{
var properties = channel.CreateBasicProperties();
properties.DeliveryMode = 2; // persistent
policy.Execute(() =>
{
var properties = channel.CreateBasicProperties();
properties.DeliveryMode = 2; // persistent
_logger.LogTrace("Publishing event to RabbitMQ: {EventId}", @event.Id);
channel.BasicPublish(
exchange: BROKER_NAME,
routingKey: eventName,
mandatory: true,
basicProperties: properties,
body: body);
});
}
_logger.LogTrace("Publishing event to RabbitMQ: {EventId}", @event.Id);
channel.BasicPublish(
exchange: BROKER_NAME,
routingKey: eventName,
mandatory: true,
basicProperties: properties,
body: body);
});
}
}
/// <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());
/// <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());
DoInternalSubscription(eventName);
_subsManager.AddDynamicSubscription<TH>(eventName);
StartBasicConsume();
}
DoInternalSubscription(eventName);
_subsManager.AddDynamicSubscription<TH>(eventName);
StartBasicConsume();
}
/// <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>();
DoInternalSubscription(eventName);
/// <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>();
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>();
StartBasicConsume();
}
_subsManager.AddSubscription<T, TH>();
StartBasicConsume();
}
private void DoInternalSubscription(string eventName)
private void DoInternalSubscription(string eventName)
{
var containsKey = _subsManager.HasSubscriptionsForEvent(eventName);
if (!containsKey)
{
var containsKey = _subsManager.HasSubscriptionsForEvent(eventName);
if (!containsKey)
if (!_persistentConnection.IsConnected)
{
if (!_persistentConnection.IsConnected)
{
_persistentConnection.TryConnect();
}
_persistentConnection.TryConnect();
}
using (var channel = _persistentConnection.CreateModel())
{
channel.QueueBind(queue: _queueName,
exchange: BROKER_NAME,
routingKey: eventName);
}
using (var channel = _persistentConnection.CreateModel())
{
channel.QueueBind(queue: _queueName,
exchange: BROKER_NAME,
routingKey: eventName);
}
}
}
/// <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>();
/// <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>();
_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)
where TH : IDynamicIntegrationEventHandler
public void Dispose()
{
if (_consumerChannel != null)
{
_subsManager.RemoveDynamicSubscription<TH>(eventName);
_consumerChannel.Dispose();
}
public void Dispose()
{
if (_consumerChannel != null)
{
_consumerChannel.Dispose();
}
_subsManager.Clear();
}
_subsManager.Clear();
}
/// <summary>
/// 开始基本消费
/// </summary>
private void StartBasicConsume()
{
_logger.LogTrace("Starting RabbitMQ basic consume");
/// <summary>
/// 开始基本消费
/// </summary>
private void StartBasicConsume()
if (_consumerChannel != null)
{
_logger.LogTrace("Starting RabbitMQ basic consume");
if (_consumerChannel != null)
{
var consumer = new AsyncEventingBasicConsumer(_consumerChannel);
var consumer = new AsyncEventingBasicConsumer(_consumerChannel);
consumer.Received += Consumer_Received;
consumer.Received += Consumer_Received;
_consumerChannel.BasicConsume(
queue: _queueName,
autoAck: false,
consumer: consumer);
}
else
{
_logger.LogError("StartBasicConsume can't call on _consumerChannel == null");
}
_consumerChannel.BasicConsume(
queue: _queueName,
autoAck: false,
consumer: consumer);
}
/// <summary>
/// 消费者接受到
/// </summary>
/// <param name="sender"></param>
/// <param name="eventArgs"></param>
/// <returns></returns>
private async Task Consumer_Received(object sender, BasicDeliverEventArgs eventArgs)
else
{
var eventName = eventArgs.RoutingKey;
var message = Encoding.UTF8.GetString(eventArgs.Body.Span);
_logger.LogError("StartBasicConsume can't call on _consumerChannel == null");
}
}
try
{
if (message.ToLowerInvariant().Contains("throw-fake-exception"))
{
throw new InvalidOperationException($"Fake exception requested: \"{message}\"");
}
/// <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;
var message = Encoding.UTF8.GetString(eventArgs.Body.Span);
await ProcessEvent(eventName, message);
}
catch (Exception ex)
try
{
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.
// 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
_consumerChannel.BasicAck(eventArgs.DeliveryTag, multiple: false);
await ProcessEvent(eventName, message);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "----- ERROR Processing message \"{Message}\"", message);
}
/// <summary>
/// 创造消费通道
/// </summary>
/// <returns></returns>
private IModel CreateConsumerChannel()
// 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).
// For more information see: https://www.rabbitmq.com/dlx.html
_consumerChannel.BasicAck(eventArgs.DeliveryTag, multiple: false);
}
/// <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,
type: "direct");
channel.ExchangeDeclare(exchange: BROKER_NAME,
type: "direct");
channel.QueueDeclare(queue: _queueName,
durable: true,
exclusive: false,
autoDelete: false,
arguments: null);
channel.QueueDeclare(queue: _queueName,
durable: true,
exclusive: false,
autoDelete: false,
arguments: null);
channel.CallbackException += (sender, ea) =>
{
_logger.LogWarning(ea.Exception, "Recreating RabbitMQ consumer channel");
channel.CallbackException += (sender, ea) =>
{
_logger.LogWarning(ea.Exception, "Recreating RabbitMQ consumer channel");
_consumerChannel.Dispose();
_consumerChannel = CreateConsumerChannel();
StartBasicConsume();
};
_consumerChannel.Dispose();
_consumerChannel = CreateConsumerChannel();
StartBasicConsume();
};
return channel;
}
return channel;
}
private async Task ProcessEvent(string eventName, string message)
{
_logger.LogTrace("Processing RabbitMQ event: {EventName}", eventName);
private async Task ProcessEvent(string eventName, string message)
{
_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);
foreach (var subscription in subscriptions)
if (subscription.IsDynamic)
{
if (subscription.IsDynamic)
{
var handler = scope.ResolveOptional(subscription.HandlerType) as IDynamicIntegrationEventHandler;
if (handler == null) continue;
dynamic eventData = JObject.Parse(message);
await Task.Yield();
await handler.Handle(eventData);
}
else
{
var handler = scope.ResolveOptional(subscription.HandlerType);
if (handler == null) continue;
var eventType = _subsManager.GetEventTypeByName(eventName);
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 });
}
var handler = scope.ResolveOptional(subscription.HandlerType) as IDynamicIntegrationEventHandler;
if (handler == null) continue;
dynamic eventData = JObject.Parse(message);
await Task.Yield();
await handler.Handle(eventData);
}
else
{
var handler = scope.ResolveOptional(subscription.HandlerType);
if (handler == null) continue;
var eventType = _subsManager.GetEventTypeByName(eventName);
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 });
}
}
}
else
{
_logger.LogWarning("No subscription for RabbitMQ event: {EventName}", eventName);
}
}
else
{
_logger.LogWarning("No subscription for RabbitMQ event: {EventName}", eventName);
}
}
}

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

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

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

@ -1,53 +1,48 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
using System;
using System.Collections.Generic;
using System.Security.Claims;
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()
{
// 可以查询数据库等操作
// 获取当前用户不能放到token中的私密信息
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 override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
// 可以查询数据库等操作
// 获取当前用户不能放到token中的私密信息
var userPhone = "15010000000";
protected virtual string GetTokenStringFromHeader()
var claims = new List<Claim>()
{
var token = string.Empty;
string authorization = Request.Headers[HeaderNames.Authorization];
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);
}
if (!string.IsNullOrEmpty(authorization) && authorization.StartsWith($"Bearer ", StringComparison.OrdinalIgnoreCase))
{
token = authorization["Bearer ".Length..].Trim();
}
protected virtual string GetTokenStringFromHeader()
{
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 Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Ocelot.DependencyInjection;
using Ocelot.DependencyInjection;
using Ocelot.Middleware;
//using Ocelot.Provider.Nacos;
using Ocelot.Provider.Polly;
using System;
using System.Threading.Tasks;
using Tiobon.Core.Extensions;
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));
services.AddAuthentication_JWTSetup();
services.AddOcelot()
.AddDelegatingHandler<CustomResultHandler>()
//.AddNacosDiscovery()
//.AddConsul()
.AddPolly();
}
if (services == null) throw new ArgumentNullException(nameof(services));
public static async Task<IApplicationBuilder> UseCustomOcelotMildd(this IApplicationBuilder app)
{
await app.UseOcelot();
return app;
}
services.AddAuthentication_JWTSetup();
services.AddOcelot()
.AddDelegatingHandler<CustomResultHandler>()
//.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.Serialization;
using System;
using System.Net;
using System.Net.Http;
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);
var contentType = response.Content.Headers.ContentType?.MediaType ?? "";
if (!contentType.Equals("application/json")) return response;
dynamic result = null;
var resultStr = await response.Content.ReadAsStringAsync();
try
{
Console.WriteLine(resultStr);
result = JsonConvert.DeserializeObject<dynamic>(resultStr);
}
catch (Exception)
{
return response;
}
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,
Message = "服务器内部错误",
ErrorCode = (int)HttpStatusCode.InternalServerError,
Data = new
{
exception.Message,
exception.StackTrace
}
};
response.Content = new StringContent(JsonConvert.SerializeObject(apiResult, _camelSettings), Encoding.UTF8, "application/json");
}
else
{
exception.Message,
exception.StackTrace
}
};
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 Tiobon.Core.Extensions.Middlewares;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.Filters;
using Swashbuckle.AspNetCore.SwaggerUI;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using static Tiobon.Core.Extensions.CustomApiVersion;
namespace Tiobon.Core.Gateway.Extensions
namespace Tiobon.Core.Gateway.Extensions;
public static class CustomSwaggerSetup
{
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 = "自定义网关 接口文档",
});
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
});
Version = "v1",
Title = "自定义网关 接口文档",
});
}
public static void UseCustomSwaggerMildd(this IApplicationBuilder app, Func<Stream> streamHtml)
{
if (app == null) throw new ArgumentNullException(nameof(app));
var xmlPath = Path.Combine(basePath, "Tiobon.Core.Gateway.xml");
c.IncludeXmlComments(xmlPath, true);
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);
});
c.OperationFilter<AddResponseHeadersFilter>();
c.OperationFilter<AppendAuthorizeToSummaryOperationFilter>();
c.OperationFilter<SecurityRequirementsOperationFilter>();
if (streamHtml.Invoke() == null)
{
var msg = "index.html的属性,必须设置为嵌入的资源";
throw new Exception(msg);
}
c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
{
Description = "JWT授权(数据将在请求头中进行传输) 直接在下框中输入Bearer {token}(注意两者之间是一个空格)\"",
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.Helper;
namespace Tiobon.Core.AuthHelper
namespace Tiobon.Core.AuthHelper;
/// <summary>
/// 中间件
/// 原做为自定义授权中间件
/// 先做检查 header token的使用
/// </summary>
public class CustomJwtTokenAuthMiddleware
{
private readonly ICaching _cache;
/// <summary>
/// 验证方案提供对象
/// </summary>
public IAuthenticationSchemeProvider Schemes { get; set; }
/// <summary>
/// 请求上下文
/// </summary>
private readonly RequestDelegate _next;
public CustomJwtTokenAuthMiddleware(RequestDelegate next, IAuthenticationSchemeProvider schemes, AppSettings appset, ICaching cache)
{
_cache = cache;
_next = next;
Schemes = schemes;
}
/// <summary>
/// 中间件
/// 原做为自定义授权中间件
/// 先做检查 header token的使用
/// 网关授权
/// </summary>
public class CustomJwtTokenAuthMiddleware
/// <param name="httpContext"></param>
/// <returns></returns>
public async Task Invoke(HttpContext httpContext)
{
private readonly ICaching _cache;
/// <summary>
/// 验证方案提供对象
/// </summary>
public IAuthenticationSchemeProvider Schemes { get; set; }
/// <summary>
/// 请求上下文
/// </summary>
private readonly RequestDelegate _next;
public CustomJwtTokenAuthMiddleware(RequestDelegate next, IAuthenticationSchemeProvider schemes, AppSettings appset,ICaching cache)
var questUrl = httpContext?.Request.Path.Value.ToLower();
if (string.IsNullOrEmpty(questUrl)) return;
//白名单验证
if (CheckWhiteList(questUrl))
{
_cache = cache;
_next = next;
Schemes = schemes;
await _next.Invoke(httpContext);
return;
}
/// <summary>
/// 网关授权
/// </summary>
/// <param name="httpContext"></param>
/// <returns></returns>
public async Task Invoke(HttpContext httpContext)
//黑名单验证
if (CheckBlackList(questUrl))
{
var questUrl = httpContext?.Request.Path.Value.ToLower();
if (string.IsNullOrEmpty(questUrl)) return;
//白名单验证
if (CheckWhiteList(questUrl))
{
await _next.Invoke(httpContext);
return;
}
//黑名单验证
if(CheckBlackList(questUrl))
{
return;
}
return;
}
List<PermissionItem> Permissions= new();
List<PermissionItem> Permissions = new();
httpContext.Features.Set<IAuthenticationFeature>(new AuthenticationFeature
{
OriginalPath = httpContext.Request.Path,
OriginalPathBase = httpContext.Request.PathBase
});
httpContext.Features.Set<IAuthenticationFeature>(new AuthenticationFeature
{
OriginalPath = httpContext.Request.Path,
OriginalPathBase = httpContext.Request.PathBase
});
//判断请求是否拥有凭据,即有没有登录
var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();
if (defaultAuthenticate != null)
//判断请求是否拥有凭据,即有没有登录
var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();
if (defaultAuthenticate != null)
{
var Authresult = await httpContext.AuthenticateAsync(defaultAuthenticate.Name);
if (Authresult?.Principal != null)
{
var Authresult = await httpContext.AuthenticateAsync(defaultAuthenticate.Name);
if (Authresult?.Principal != null)
httpContext.User = Authresult.Principal;
// 获取当前用户的角色信息
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;
// 获取当前用户的角色信息
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
{
try
if (Regex.IsMatch(questUrl, item.Url, RegexOptions.IgnoreCase))
{
if (Regex.IsMatch(questUrl, item.Url, RegexOptions.IgnoreCase))
{
isMatchRole = true;
break;
}
}
catch (Exception)
{
// ignored
isMatchRole = true;
break;
}
}
//验证权限
if (currentUserRoles.Count <= 0 || !isMatchRole)
catch (Exception)
{
await httpContext.Cof_SendResponse(HttpStatusCode.ServiceUnavailable, "未授权此资源");
return ;
// ignored
}
}
else
//验证权限
if (currentUserRoles.Count <= 0 || !isMatchRole)
{
await httpContext.Cof_SendResponse(HttpStatusCode.Unauthorized, "请重新登录");
return ;
await httpContext.Cof_SendResponse(HttpStatusCode.ServiceUnavailable, "未授权此资源");
return;
}
}
else
{
await httpContext.Cof_SendResponse(HttpStatusCode.Unauthorized, "系统鉴权出错");
return ;
await httpContext.Cof_SendResponse(HttpStatusCode.Unauthorized, "请重新登录");
return;
}
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)
}
else
{
context.Response.StatusCode = (int)code;
context.Response.ContentType = "text/plain";
await context.Response.WriteAsync(message);
await httpContext.Cof_SendResponse(HttpStatusCode.Unauthorized, "系统鉴权出错");
return;
}
/// <summary>
/// 判断是否在白名单内,支持通配符 ****
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
public bool CheckWhiteList(string url)
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>
/// <param name="url"></param>
/// <returns></returns>
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;
foreach (var Urlitem in WhiteList)
if (Urlitem.url.IndexOf("****") > 0)
{
if (Urlitem.url.Equals(url, StringComparison.OrdinalIgnoreCase)) 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;
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 (!BlackList.Cof_CheckAvailable()) return false;
foreach (var Urlitem in BlackList)
{
if (Urlitem.url.Equals(url, StringComparison.OrdinalIgnoreCase)) return true;
if (Urlitem.url.Equals(url, StringComparison.OrdinalIgnoreCase)) 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;
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 class Urlobj
{
public string url { get; set; }
}
}
public class Urlobj
{
public string url { get; set; }
}

@ -1,28 +1,23 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
namespace Tiobon.Core.AdminMvc;
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();
}
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");
});
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");
});
}

@ -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 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;
using Tiobon.Core.Model.Entity;
using Tiobon.Core.Model.Models;
using Tiobon.Core.Model.ViewModels;
namespace Tiobon.Core.Tasks;
namespace Tiobon.Core.Tasks;
/// <summary>
/// 服务调度接口

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

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

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

@ -1,5 +1,4 @@
using Microsoft.Extensions.Logging;

/// <summary>
/// 这里要注意下,命名空间和程序集是一样的,不然反射不到(任务类要去JobSetup添加注入)
/// </summary>

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

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

@ -1,6 +1,4 @@
using Microsoft.Extensions.Logging;
using Tiobon.Core.Helper;

/// <summary>
/// 这里要注意下,命名空间和程序集是一样的,不然反射不到(任务类要去JobSetup添加注入)
/// </summary>

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

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

@ -1,26 +1,25 @@
using Tiobon.Core.Helper;
using Xunit;
namespace Tiobon.Core.Tests.Common_Test
{
public class HttpHelper_Should
{
namespace Tiobon.Core.Tests.Common_Test;
[Fact]
public async void Get_Async_Test()
{
var responseString = (await HttpHelper.GetAsync("http://apk.neters.club/api/Tiobon"));
public class HttpHelper_Should
{
Assert.NotNull(responseString);
}
[Fact]
public async void Get_Async_Test()
{
var responseString = (await HttpHelper.GetAsync("http://apk.neters.club/api/Tiobon"));
[Fact]
public void Post_Async_Test()
{
var responseString = HttpHelper.PostAsync("http://apk.neters.club/api/Login/swgLogin", "{\"name\":\"admin\",\"pwd\":\"admin\"}");
Assert.NotNull(responseString);
}
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 System;
using Tiobon.Core.Common.Helper.SM;
using Xunit;
namespace Tiobon.Core.Tests.Common_Test
{
public class SM4Helper_Should
{
namespace Tiobon.Core.Tests.Common_Test;
[Fact]
public void Encrypt_ECB_Test()
{
var plainText = "暗号";
public class SM4Helper_Should
{
var sm4 = new SM4Helper();
[Fact]
public void Encrypt_ECB_Test()
{
var plainText = "暗号";
Console.Out.WriteLine("ECB模式");
var cipherText = sm4.Encrypt_ECB(plainText);
Console.Out.WriteLine("密文: " + cipherText);
var sm4 = new SM4Helper();
Assert.NotNull(cipherText);
Assert.Equal("VhVDC0KzyZjAVMpwz0GyQA==", cipherText);
}
Console.Out.WriteLine("ECB模式");
var cipherText = sm4.Encrypt_ECB(plainText);
Console.Out.WriteLine("密文: " + cipherText);
[Fact]
public void Decrypt_ECB_Test()
{
var cipherText = "Y9ygWexdpuLQjW/qsnZNQw==";
Assert.NotNull(cipherText);
Assert.Equal("VhVDC0KzyZjAVMpwz0GyQA==", cipherText);
}
var sm4 = new SM4Helper();
[Fact]
public void Decrypt_ECB_Test()
{
var cipherText = "Y9ygWexdpuLQjW/qsnZNQw==";
Console.Out.WriteLine("ECB模式");
var plainText = sm4.Decrypt_ECB(cipherText);
Console.Out.WriteLine("明文: " + plainText);
var sm4 = new SM4Helper();
Assert.NotNull(plainText);
Assert.Equal("老张的哲学", plainText);
}
Console.Out.WriteLine("ECB模式");
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 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;
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()
{
var container = dI_Test.DICollections();
_ghrs_UserServices = container.Resolve<IGhrs_UserServices>();
_userRoleServices = container.Resolve<IUserRoleServices>();
_roleServices = container.Resolve<IRoleServices>();
_requirement = container.Resolve<PermissionRequirement>();
_roleModulePermissionServices = container.Resolve<IRoleModulePermissionServices>();
_logger = container.Resolve<ILogger<LoginController>>();
loginController = new LoginController(_ghrs_UserServices, _userRoleServices, _roleServices, _requirement,
_roleModulePermissionServices, _logger);
}
[Fact]
public void GetJwtStrTest()
{
var data = loginController.GetJwtStr("test", "test");
Assert.NotNull(data);
}
[Fact]
public void GetJwtStrForNuxtTest()
{
object Tiobons = loginController.GetJwtStrForNuxt("test", "test");
Assert.NotNull(Tiobons);
}
[Fact]
public async void GetJwtToken3Test()
{
var res = await loginController.GetJwtToken3("test", "test");
Assert.NotNull(res);
}
[Fact]
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);
}
var container = dI_Test.DICollections();
_ghrs_UserServices = container.Resolve<IGhrs_UserServices>();
_userRoleServices = container.Resolve<IUserRoleServices>();
_roleServices = container.Resolve<IRoleServices>();
_requirement = container.Resolve<PermissionRequirement>();
_roleModulePermissionServices = container.Resolve<IRoleModulePermissionServices>();
_logger = container.Resolve<ILogger<LoginController>>();
loginController = new LoginController(_ghrs_UserServices, _userRoleServices, _roleServices, _requirement,
_roleModulePermissionServices, _logger);
}
[Fact]
public void GetJwtStrTest()
{
var data = loginController.GetJwtStr("test", "test");
Assert.NotNull(data);
}
[Fact]
public void GetJwtStrForNuxtTest()
{
object Tiobons = loginController.GetJwtStrForNuxt("test", "test");
Assert.NotNull(Tiobons);
}
[Fact]
public async void GetJwtToken3Test()
{
var res = await loginController.GetJwtToken3("test", "test");
Assert.NotNull(res);
}
[Fact]
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.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>
/// 连接字符串
/// Tiobon.Core
/// </summary>
public static MutiDBOperate GetMainConnectionDb()
var mainConnetctDb = BaseDBConfig.MutiConnectionString.allDbs.Find(x => x.ConnId == MainDb.CurrentDbConnId);
if (BaseDBConfig.MutiConnectionString.allDbs.Count > 0)
{
var mainConnetctDb = BaseDBConfig.MutiConnectionString.allDbs.Find(x => x.ConnId == MainDb.CurrentDbConnId);
if (BaseDBConfig.MutiConnectionString.allDbs.Count > 0)
{
if (mainConnetctDb == null)
{
mainConnetctDb = BaseDBConfig.MutiConnectionString.allDbs[0];
}
}
else
if (mainConnetctDb == null)
{
throw new Exception("请确保appsettigns.json中配置连接字符串,并设置Enabled为true;");
mainConnetctDb = BaseDBConfig.MutiConnectionString.allDbs[0];
}
return mainConnetctDb;
}
public IContainer DICollections()
else
{
var basePath = AppContext.BaseDirectory;
throw new Exception("请确保appsettigns.json中配置连接字符串,并设置Enabled为true;");
}
IServiceCollection services = new ServiceCollection();
services.AddAutoMapperSetup();
return mainConnetctDb;
}
services.AddSingleton(new AppSettings(basePath));
services.AddScoped<DBSeed>();
services.AddScoped<MyContext>();
public IContainer DICollections()
{
var basePath = AppContext.BaseDirectory;
//读取配置文件
var symmetricKeyAsBase64 = AppSecretConfig.Audience_Secret_String;
var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64);
var signingKey = new SymmetricSecurityKey(keyByteArray);
IServiceCollection services = new ServiceCollection();
services.AddAutoMapperSetup();
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(
"/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 signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256);
//【授权】
services.AddAuthorization(options =>
{
options.AddPolicy(Permissions.Name,
policy => policy.Requirements.Add(permissionRequirement));
});
var permission = new List<PermissionItem>();
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, //必填, 数据库类型
IsAutoCloseConnection = true, //默认false, 时候知道关闭数据库连接, 设置为true无需使用using或者Close操作
});
ConnectionString = GetMainConnectionDb().Connection, //必填, 数据库连接字符串
DbType = (SqlSugar.DbType)GetMainConnectionDb().DbType, //必填, 数据库类型
IsAutoCloseConnection = true, //默认false, 时候知道关闭数据库连接, 设置为true无需使用using或者Close操作
});
});
//实例化 AutoFac 容器
var builder = new ContainerBuilder();
//builder.RegisterType<AdvertisementServices>().As<IAdvertisementServices>();
builder.RegisterInstance(new LoggerFactory())
.As<ILoggerFactory>();
//实例化 AutoFac 容器
var builder = new ContainerBuilder();
//builder.RegisterType<AdvertisementServices>().As<IAdvertisementServices>();
builder.RegisterInstance(new LoggerFactory())
.As<ILoggerFactory>();
builder.RegisterGeneric(typeof(Logger<>))
.As(typeof(ILogger<>))
.SingleInstance();
//指定已扫描程序集中的类型注册为提供所有其实现的接口。
builder.RegisterGeneric(typeof(Logger<>))
.As(typeof(ILogger<>))
.SingleInstance();
//指定已扫描程序集中的类型注册为提供所有其实现的接口。
builder.RegisterGeneric(typeof(BaseRepository<>)).As(typeof(IBaseRepository<>)).InstancePerDependency(); //注册仓储
builder.RegisterGeneric(typeof(MongoBaseRepository<>)).As(typeof(IMongoBaseRepository<>)).InstancePerDependency(); //注册仓储
builder.RegisterGeneric(typeof(BaseRepository<>)).As(typeof(IBaseRepository<>)).InstancePerDependency(); //注册仓储
builder.RegisterGeneric(typeof(MongoBaseRepository<>)).As(typeof(IMongoBaseRepository<>)).InstancePerDependency(); //注册仓储
// 属性注入
var controllerBaseType = typeof(ControllerBase);
//builder.RegisterAssemblyTypes(typeof(Program).Assembly)
// .Where(t => controllerBaseType.IsAssignableFrom(t) && t != controllerBaseType)
// .PropertiesAutowired();
// 属性注入
var controllerBaseType = typeof(ControllerBase);
//builder.RegisterAssemblyTypes(typeof(Program).Assembly)
// .Where(t => controllerBaseType.IsAssignableFrom(t) && t != controllerBaseType)
// .PropertiesAutowired();
var servicesDllFile = Path.Combine(basePath, "Tiobon.Core.Services.dll");
var assemblysServices = Assembly.LoadFrom(servicesDllFile);
builder.RegisterAssemblyTypes(assemblysServices)
.AsImplementedInterfaces()
.InstancePerLifetimeScope()
.PropertiesAutowired()
.EnableInterfaceInterceptors();
var servicesDllFile = Path.Combine(basePath, "Tiobon.Core.Services.dll");
var assemblysServices = Assembly.LoadFrom(servicesDllFile);
builder.RegisterAssemblyTypes(assemblysServices)
.AsImplementedInterfaces()
.InstancePerLifetimeScope()
.PropertiesAutowired()
.EnableInterfaceInterceptors();
var repositoryDllFile = Path.Combine(basePath, "Tiobon.Core.Repository.dll");
var assemblysRepository = Assembly.LoadFrom(repositoryDllFile);
builder.RegisterAssemblyTypes(assemblysRepository)
.PropertiesAutowired().AsImplementedInterfaces();
var repositoryDllFile = Path.Combine(basePath, "Tiobon.Core.Repository.dll");
var assemblysRepository = Assembly.LoadFrom(repositoryDllFile);
builder.RegisterAssemblyTypes(assemblysRepository)
.PropertiesAutowired().AsImplementedInterfaces();
services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>());
services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>());
services.AddAutoMapperSetup();
services.AddAutoMapperSetup();
//将services填充到Autofac容器生成器中
builder.Populate(services);
//将services填充到Autofac容器生成器中
builder.Populate(services);
//使用已进行的组件登记创建新容器
var ApplicationContainer = builder.Build();
//使用已进行的组件登记创建新容器
var ApplicationContainer = builder.Build();
return ApplicationContainer;
}
return ApplicationContainer;
}
}

@ -1,26 +1,25 @@
using Xunit;
namespace Tiobon.Core.Tests
{
public class Redis_Should
{
DI_Test dI_Test = new DI_Test();
namespace Tiobon.Core.Tests;
public Redis_Should()
{
//var container = dI_Test.DICollections();
//_redisCacheManager = container.Resolve<IRedisCacheManager>();
public class Redis_Should
{
DI_Test dI_Test = new DI_Test();
}
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 Xunit;
using System;
using System.Linq;
using Autofac;
using Tiobon.Core.IRepository.Base;
using Tiobon.Core.Repository.MongoRepository;
using MongoDB.Bson.Serialization.Attributes;
using Autofac;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
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; }
public string name { get; set; }
public bool isDel { get; set; }
public DateTime time { get; set; }
}
[BsonId]
public ObjectId id { get; set; }
public string name { get; set; }
public bool isDel { get; set; }
public DateTime time { get; set; }
}
private IMongoBaseRepository<MongoTest> baseRepository;
DI_Test dI_Test = new DI_Test();
private IMongoBaseRepository<MongoTest> baseRepository;
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]
public async void Add_Test()
{
await baseRepository.AddAsync(new MongoTest { isDel = false, name = "test", time = DateTime.UtcNow });
}
[Fact]
public async void Add_Test()
{
await baseRepository.AddAsync(new MongoTest { isDel = false, name = "test", time = DateTime.UtcNow });
}
[Fact]
public async void GetObjectId_Test()
{
var data = await baseRepository.GetByObjectIdAsync("612b9b0be677976fa0f0cfa2");
[Fact]
public async void GetObjectId_Test()
{
var data = await baseRepository.GetByObjectIdAsync("612b9b0be677976fa0f0cfa2");
Assert.NotNull(data);
}
Assert.NotNull(data);
}
[Fact]
public async void GetListFilter_Test()
{
var data = await baseRepository.GetListFilterAsync(new FilterDefinitionBuilder<MongoTest>().Gte("time", DateTime.Parse("2022-06-01")));
[Fact]
public async void GetListFilter_Test()
{
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 Xunit;
using System;
using System.Linq;
using Autofac;
using Autofac;
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]
public async void Get_Tiobons_Test()
[Fact]
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]
public async void Add_Tiobon_Test()
{
TiobonArticle TiobonArticle = new TiobonArticle()
{
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();
[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.bCreateTime = DateTime.Now;
updateModel.bUpdateTime = DateTime.Now;
updateModel.bcontent = "xuint: test repositoryBase content update";
updateModel.bCreateTime = DateTime.Now;
updateModel.bUpdateTime = DateTime.Now;
IsUpd = await baseRepository.Update(updateModel);
IsUpd = await baseRepository.Update(updateModel);
Assert.True(IsUpd);
}
Assert.True(IsUpd);
}
[Fact]
public async void Delete_Tiobon_Test()
{
var IsDel = false;
var deleteModel = (await baseRepository.Query(d => d.btitle == "xuint test title")).FirstOrDefault();
[Fact]
public async void Delete_Tiobon_Test()
{
var IsDel = false;
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
.editorconfig = .editorconfig
.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
CreateYourProject.bat = CreateYourProject.bat
DockerBuild.bat = DockerBuild.bat
Dockerfile = Dockerfile
nuget.config = nuget.config
README.md = README.md
EndProjectSection

@ -42792,11 +42792,6 @@
</summary>
</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">
<summary>
bgColor
@ -43662,26 +43657,6 @@
用来测试 RestSharp Post 请求
</summary>
</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">
<summary>
分组

Loading…
Cancel
Save