boboyunz gateway

This commit is contained in:
boboyunz 2022-02-14 23:35:25 +08:00
parent 95f341037f
commit 9161ec914e
29 changed files with 1769 additions and 11 deletions

View File

@ -36,6 +36,7 @@
<ItemGroup>
<ProjectReference Include="..\Blog.Core.Model\Blog.Core.Model.csproj" />
<ProjectReference Include="..\Blog.Core.Serilog.Es\Blog.Core.Serilog.Es.csproj" />
<ProjectReference Include="..\Ocelot.Provider.Nacos\Ocelot.Provider.Nacos.csproj" />
</ItemGroup>
</Project>

View File

@ -11,7 +11,7 @@ namespace Blog.Core.Common
/// </summary>
public class Appsettings
{
static IConfiguration Configuration { get; set; }
public static IConfiguration Configuration { get; set; }
static string contentPath { get; set; }
public Appsettings(string contentPath)
@ -65,5 +65,23 @@ namespace Blog.Core.Common
Configuration.Bind(string.Join(":", sections), list);
return list;
}
/// <summary>
/// 根据路径 configuration["App:Name"];
/// </summary>
/// <param name="sectionsPath"></param>
/// <returns></returns>
public static string GetValue(string sectionsPath)
{
try
{
return Configuration[sectionsPath];
}
catch (Exception) { }
return "";
}
}
}

View File

@ -1,11 +1,15 @@
using RestSharp.Extensions;
using Microsoft.AspNetCore.Http;
using RestSharp.Extensions;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Linq.Expressions;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace Blog.Core.Common.Helper
{
@ -294,7 +298,192 @@ namespace Blog.Core.Common.Helper
/// </summary>
public static class ExpressionExtensions
{
#region Nacos NamingService
private static readonly HttpClient httpclient = new HttpClient();
private static string GetServiceUrl(Nacos.V2.INacosNamingService serv, string ServiceName, string Group, string apiurl)
{
try
{
var instance = serv.SelectOneHealthyInstance(ServiceName, Group).GetAwaiter().GetResult();
var host = $"{instance.Ip}:{instance.Port}";
if (instance.Metadata.ContainsKey("endpoint")) host = instance.Metadata["endpoint"];
var baseUrl = instance.Metadata.TryGetValue("secure", out _)
? $"https://{host}"
: $"http://{host}";
if (string.IsNullOrWhiteSpace(baseUrl))
{
return "";
}
return $"{baseUrl}{apiurl}";
}
catch (System.Exception ee)
{
}
return "";
}
public static async Task<string> Cof_NaoceGet(this Nacos.V2.INacosNamingService serv, string ServiceName, string Group, string apiurl, Dictionary<string, string> Parameters = null)
{
try
{
var url = GetServiceUrl(serv, ServiceName, Group, apiurl);
if (string.IsNullOrEmpty(url)) return "";
if (Parameters!=null && Parameters.Any())
{
StringBuilder sb = new StringBuilder();
foreach (var pitem in Parameters)
{
sb.Append($"{pitem.Key}={pitem.Value}&");
}
url = $"{url}?{sb.ToString().Trim('&')}";
}
httpclient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var result = await httpclient.GetAsync(url);
return await result.Content.ReadAsStringAsync();
}
catch (System.Exception ee)
{
}
return "";
}
public static async Task<string> Cof_NaocePostForm(this Nacos.V2.INacosNamingService serv, string ServiceName, string Group, string apiurl, Dictionary<string, string> Parameters)
{
try
{
var url = GetServiceUrl(serv, ServiceName, Group, apiurl);
if (string.IsNullOrEmpty(url)) return "";
var content = (Parameters != null && Parameters.Any())? new FormUrlEncodedContent(Parameters) : null;
httpclient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var result = await httpclient.PostAsync(url, content);
return await result.Content.ReadAsStringAsync();//.GetAwaiter().GetResult();
}
catch (System.Exception ee)
{
}
return "";
}
public static async Task<string> Cof_NaocePostJson(this Nacos.V2.INacosNamingService serv, string ServiceName, string Group, string apiurl, string jSonData)
{
try
{
var url = GetServiceUrl(serv, ServiceName, Group, apiurl);
if (string.IsNullOrEmpty(url)) return "";
httpclient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var result = await httpclient.PostAsync(url, new StringContent(jSonData, Encoding.UTF8, "application/json"));
return await result.Content.ReadAsStringAsync();//.GetAwaiter().GetResult();
//httpClient.BaseAddress = new Uri("https://www.testapi.com");
//httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
//httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
}
catch (System.Exception ee)
{
}
return "";
}
public static async Task<string> Cof_NaocePostFile(this Nacos.V2.INacosNamingService serv, string ServiceName, string Group, string apiurl, Dictionary<string, byte[]> Parameters)
{
try
{
var url = GetServiceUrl(serv, ServiceName, Group, apiurl);
if (string.IsNullOrEmpty(url)) return "";
var content = new MultipartFormDataContent();
foreach (var pitem in Parameters)
{
content.Add(new ByteArrayContent(pitem.Value), "files", pitem.Key);
}
httpclient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var result = await httpclient.PostAsync(url, content);
return await result.Content.ReadAsStringAsync();//.GetAwaiter().GetResult();
}
catch (System.Exception ee)
{
//InfluxdbHelper.GetInstance().AddLog("Cof_NaocePostFile.Err", ee);
}
return "";
}
#endregion
#region HttpContext
/// <summary>
/// 返回请求上下文
/// </summary>
/// <param name="context"></param>
/// <param name="code"></param>
/// <param name="message"></param>
/// <param name="ContentType"></param>
/// <returns></returns>
public static async Task Cof_SendResponse(this HttpContext context, System.Net.HttpStatusCode code, string message, string ContentType = "text/html;charset=utf-8")
{
context.Response.StatusCode = (int)code;
context.Response.ContentType = ContentType;
await context.Response.WriteAsync(message);
}
#endregion
#region ICaching
/// <summary>
/// 从缓存里取数据,如果不存在则执行查询方法,
/// </summary>
/// <typeparam name="T">类型</typeparam>
/// <param name="cache">ICaching </param>
/// <param name="key">键值</param>
/// <param name="GetFun">查询方法</param>
/// <param name="timeSpanMin">有效期 单位分钟/param>
/// <returns></returns>
public static T Cof_GetICaching<T>(this ICaching cache, string key, Func<T> GetFun, int timeSpanMin) where T : class
{
var obj = cache.Get(key);
obj = GetFun();
if (obj == null)
{
obj = GetFun();
cache.Set(key, obj, timeSpanMin);
}
return obj as T;
}
/// <summary>
/// 异步从缓存里取数据,如果不存在则执行查询方法
/// </summary>
/// <typeparam name="T">类型</typeparam>
/// <param name="cache">ICaching </param>
/// <param name="key">键值</param>
/// <param name="GetFun">查询方法</param>
/// <param name="timeSpanMin">有效期 单位分钟/param>
/// <returns></returns>
public static async Task<T> Cof_AsyncGetICaching<T>(this ICaching cache, string key, Func<Task<T>> GetFun, int timeSpanMin) where T : class
{
var obj = cache.Get(key);
if (obj == null)
{
obj = await GetFun();
cache.Set(key, obj, timeSpanMin);
}
return obj as T;
}
#endregion
#region
public static bool Cof_CheckAvailable<TSource>(this IEnumerable<TSource> Tlist)
{
return Tlist != null && Tlist.Count() > 0;
}
/// <summary>
/// 调用内部方法

View File

@ -12,7 +12,7 @@
<PackageReference Include="Com.Ctrip.Framework.Apollo.Configuration" Version="2.4.1.1" />
<PackageReference Include="Consul" Version="1.6.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.9" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.9" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="5.0.14" />
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="5.0.0-preview.2.20167.3" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="5.0.0" />

View File

@ -11,6 +11,7 @@
<ItemGroup>
<Compile Remove="Extensions\ApiResponseHandler.cs" />
<Compile Remove="Helper\HeaderDelegatingHandler.cs" />
</ItemGroup>
<ItemGroup>

View File

@ -4,6 +4,57 @@
<name>Blog.Core.Gateway</name>
</assembly>
<members>
<member name="T:Blog.Core.AuthHelper.JwtTokenAuth">
<summary>
中间件
原做为自定义授权中间件
先做检查 header token的使用
</summary>
</member>
<member name="F:Blog.Core.AuthHelper.JwtTokenAuth.Appsettings">
<summary>
配置数据
</summary>
</member>
<member name="P:Blog.Core.AuthHelper.JwtTokenAuth.Schemes">
<summary>
验证方案提供对象
</summary>
</member>
<member name="F:Blog.Core.AuthHelper.JwtTokenAuth._next">
<summary>
请求上下文
</summary>
</member>
<member name="M:Blog.Core.AuthHelper.JwtTokenAuth.#ctor(Nacos.V2.INacosNamingService,Microsoft.AspNetCore.Http.RequestDelegate,Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider,Blog.Core.Common.Appsettings,Blog.Core.Common.ICaching)">
<summary>
</summary>
<param name="next"></param>
</member>
<member name="M:Blog.Core.AuthHelper.JwtTokenAuth.Invoke(Microsoft.AspNetCore.Http.HttpContext)">
<summary>
网关授权
</summary>
<param name="httpContext"></param>
<returns></returns>
</member>
<member name="M:Blog.Core.AuthHelper.JwtTokenAuth.SendResponse(Microsoft.AspNetCore.Http.HttpContext,System.String,System.Net.HttpStatusCode)">
<summary>
返回相应
</summary>
<param name="context"></param>
<param name="message"></param>
<returns></returns>
</member>
<member name="M:Blog.Core.AuthHelper.JwtTokenAuth.CheckWhiteList(System.String)">
<summary>
判断是否在白名单内,支持通配符 ****
</summary>
<param name="url"></param>
<returns></returns>
</member>
<member name="M:Blog.Core.AdminMvc.Startup.#ctor(Microsoft.Extensions.Configuration.IConfiguration,Microsoft.AspNetCore.Hosting.IWebHostEnvironment)">
┌──────────────────────────────────────────────────────────────┐
│ 描 述:模拟一个网关项目
@ -11,5 +62,47 @@
│ 作 者anson zhang
└──────────────────────────────────────────────────────────────┘
</member>
<member name="T:ApiGateway.Helper.OcelotConfigurationTask">
<summary>
Nacos配置文件变更事件
</summary>
</member>
<member name="F:ApiGateway.Helper.OcelotConfigurationTask.nacosConfigListener">
<summary>
Nacos 配置文件监听事件
</summary>
</member>
<member name="M:ApiGateway.Helper.OcelotConfigurationTask.#ctor(Nacos.V2.INacosNamingService,Nacos.V2.INacosConfigService,System.IServiceProvider,Ocelot.Configuration.Repository.IInternalConfigurationRepository,Ocelot.Configuration.Creator.IInternalConfigurationCreator)">
<summary>
重载方法
</summary>
<param name="configClient"></param>
<param name="serviceProvider"></param>
</member>
<member name="M:ApiGateway.Helper.OcelotConfigurationTask.ExecuteAsync(System.Threading.CancellationToken)">
<summary>
执行
</summary>
<param name="stoppingToken"></param>
<returns></returns>
</member>
<member name="M:ApiGateway.Helper.OcelotConfigurationTask.StopAsync(System.Threading.CancellationToken)">
<summary>
停止
</summary>
<param name="cancellationToken"></param>
<returns></returns>
</member>
<member name="T:ApiGateway.Helper.OcelotConfigListener">
<summary>
配置监听事件
</summary>
</member>
<member name="M:ApiGateway.Helper.OcelotConfigListener.ReceiveConfigInfo(System.String)">
<summary>
收到配置文件变更
</summary>
<param name="configInfo"></param>
</member>
</members>
</doc>

View File

@ -1,8 +1,10 @@
using Microsoft.AspNetCore.Builder;
using Blog.Core.Extensions;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Ocelot.DependencyInjection;
using Ocelot.Middleware;
using Ocelot.Provider.Consul;
using Ocelot.Provider.Nacos;
using Ocelot.Provider.Polly;
using System;
using System.Threading.Tasks;
@ -17,7 +19,9 @@ namespace Blog.Core.Gateway.Extensions
var basePath = AppContext.BaseDirectory;
services.AddOcelot().AddDelegatingHandler<CustomResultHandler>().AddConsul().AddPolly();
services.AddAuthentication_JWTSetup();
services.AddOcelot().AddDelegatingHandler<CustomResultHandler>().AddNacosDiscovery().AddPolly();
//.AddConsul().AddPolly();
}
public static async Task<IApplicationBuilder> UseCustomOcelotMildd(this IApplicationBuilder app)

View File

@ -16,6 +16,8 @@ namespace Blog.Core.Gateway.Extensions
var basePath = AppContext.BaseDirectory;
services.AddMvc(option => option.EnableEndpointRouting = false);
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo
@ -55,6 +57,7 @@ namespace Blog.Core.Gateway.Extensions
apis.ForEach(m =>
{
options.SwaggerEndpoint($"/swagger/apiswg/{m}/swagger.json", m);
//options.IndexStream = () => GetType().GetTypeInfo().Assembly.GetManifestResourceStream("Blog.Core.ApiGateway.index.html");
});
options.RoutePrefix = "";

View File

@ -0,0 +1,35 @@
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ApiGateway.Helper
{
public class HeaderDelegatingHandler : DelegatingHandler
{
private readonly IHttpContextAccessor _httpContextAccessor;
public HeaderDelegatingHandler(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
IEnumerable<string> headerValues;
if (request.Headers.TryGetValues("AccessToken", out headerValues))
{
string accessToken = headerValues.First();
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
request.Headers.Remove("AccessToken");
}
return await base.SendAsync(request, cancellationToken);
}
}
}

View File

@ -0,0 +1,259 @@
using Microsoft.AspNetCore.Http;
using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Configuration;
using Blog.Core.Common;
using Blog.Core.Common.Helper;
using Blog.Core.AuthHelper;
using System.Text.RegularExpressions;
using System.Net;
using Ocelot.Configuration.Repository;
using Microsoft.Extensions.DependencyInjection;
using Ocelot.Configuration.Creator;
using Ocelot.Configuration.File;
using Nacos.V2;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using Newtonsoft.Json.Converters;
namespace Blog.Core.AuthHelper
{
/// <summary>
/// 中间件
/// 原做为自定义授权中间件
/// 先做检查 header token的使用
/// </summary>
public class JwtTokenAuth
{
/// <summary>
/// 配置数据
/// </summary>
private readonly Appsettings Appsettings;
private readonly ICaching _cache;
private readonly INacosNamingService NacosServClient;
/// <summary>
/// 验证方案提供对象
/// </summary>
public IAuthenticationSchemeProvider Schemes { get; set; }
/// <summary>
/// 请求上下文
/// </summary>
private readonly RequestDelegate _next;
/// <summary>
///
/// </summary>
/// <param name="next"></param>
///
public JwtTokenAuth(INacosNamingService serv, RequestDelegate next, IAuthenticationSchemeProvider schemes, Appsettings appset,ICaching cache)
{
NacosServClient = serv;
_cache = cache;
_next = next;
Appsettings = appset;
Schemes = schemes;
List<PermissionItem> Permissions = _cache.Cof_AsyncGetICaching<List<PermissionItem>>("Permissions", GetPermitionData, 10).GetAwaiter().GetResult();
}
private void PreProceed(HttpContext next)
{
//Console.WriteLine($"{DateTime.Now} middleware invoke preproceed");
//...
}
private void PostProceed(HttpContext next)
{
//Console.WriteLine($"{DateTime.Now} middleware invoke postproceed");
//....
}
/// <summary>
/// 网关授权
/// </summary>
/// <param name="httpContext"></param>
/// <returns></returns>
public async Task Invoke(HttpContext httpContext)
{
var questUrl = httpContext?.Request.Path.Value.ToLower();
if (string.IsNullOrEmpty(questUrl)) return;
//白名单验证
if (CheckWhiteList(questUrl))
{
await _next.Invoke(httpContext);
return;
}
//黑名单验证
if(CheckBlackList(questUrl))
{
return;
}
List<PermissionItem> Permissions= await _cache.Cof_AsyncGetICaching<List<PermissionItem>>("Permissions", GetPermitionData, 10);
httpContext.Features.Set<IAuthenticationFeature>(new AuthenticationFeature
{
OriginalPath = httpContext.Request.Path,
OriginalPathBase = httpContext.Request.PathBase
});
//判断请求是否拥有凭据,即有没有登录
var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync();
if (defaultAuthenticate != 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)
{
try
{
if (Regex.IsMatch(questUrl, item.Url, RegexOptions.IgnoreCase))
{
isMatchRole = true;
break;
}
}
catch (Exception)
{
// ignored
}
}
//验证权限
if (currentUserRoles.Count <= 0 || !isMatchRole)
{
await httpContext.Cof_SendResponse(HttpStatusCode.ServiceUnavailable, "未授权此资源");
return ;
}
}
else
{
await httpContext.Cof_SendResponse(HttpStatusCode.Unauthorized, "请重新登录");
return ;
}
}
else
{
await httpContext.Cof_SendResponse(HttpStatusCode.Unauthorized, "系统鉴权出错");
return ;
}
await _next.Invoke(httpContext);
}
private async Task<List<PermissionItem>> GetPermitionData()
{
try
{
string PermissionServName = Appsettings.GetValue("ApiGateWay:PermissionServName");
string PermissionServGroup = Appsettings.GetValue("ApiGateWay:PermissionServGroup");
string PermissionServUrl = Appsettings.GetValue("ApiGateWay:PermissionServUrl");
string requestdata = await NacosServClient.Cof_NaoceGet(PermissionServName, PermissionServGroup, PermissionServUrl);
if (string.IsNullOrEmpty(requestdata)) return null;
JToken perJt = JToken.Parse(requestdata);
if(perJt["response"]!=null) return perJt["response"].ToObject<List<PermissionItem>>();
return perJt["data"].ToObject<List<PermissionItem>>();
}
catch (Exception ee)
{
}
return null;
}
/// <summary>
/// 返回相应
/// </summary>
/// <param name="context"></param>
/// <param name="message"></param>
/// <returns></returns>
private async Task SendResponse(HttpContext context, string message, System.Net.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)
{
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;
}
}
return false;
}
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)
{
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;
}
}
return false;
}
}
public class urlobj
{
public string url { get; set; }
}
}

View File

@ -0,0 +1,148 @@

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Nacos.V2;
using System;
using System.Threading;
using System.Threading.Tasks;
using Blog.Core.Common.Helper;
using Ocelot.Configuration.Repository;
using Ocelot.Configuration.Creator;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using Newtonsoft.Json.Converters;
using Ocelot.Configuration.File;
using Blog.Core.Common;
namespace ApiGateway.Helper
{
/// <summary>
/// Nacos配置文件变更事件
/// </summary>
public class OcelotConfigurationTask : BackgroundService
{
private readonly INacosConfigService _configClient;
private readonly INacosNamingService _servClient;
/// <summary>
/// Nacos 配置文件监听事件
/// </summary>
private OcelotConfigListener nacosConfigListener = new OcelotConfigListener();
private AppConfigListener AppConfigListener = new AppConfigListener();
private string OcelotConfig = "";
private string OcelotConfigGroup = "";
private string AppConfig = "";
private string AppConfigGroup = "";
/// <summary>
/// 重载方法
/// </summary>
/// <param name="configClient"></param>
/// <param name="serviceProvider"></param>
public OcelotConfigurationTask(INacosNamingService serv, INacosConfigService configClient, IServiceProvider serviceProvider, IInternalConfigurationRepository _internalConfigurationRepo, IInternalConfigurationCreator _internalConfigurationCreator)
{
_configClient = configClient;
_servClient = serv;
nacosConfigListener.internalConfigurationRepo = _internalConfigurationRepo;
nacosConfigListener.internalConfigurationCreator = _internalConfigurationCreator;
OcelotConfig = Appsettings.GetValue("ApiGateWay:OcelotConfig");
OcelotConfigGroup = Appsettings.GetValue("ApiGateWay:OcelotConfigGroup");
AppConfig = Appsettings.GetValue("ApiGateWay:AppConfig");
AppConfigGroup = Appsettings.GetValue("ApiGateWay:AppConfigGroup");
string OcelotCfg = configClient.GetConfig(OcelotConfig, OcelotConfigGroup, 10000).GetAwaiter().GetResult();
nacosConfigListener.ReceiveConfigInfo(OcelotCfg);
string AppCfg= configClient.GetConfig(AppConfig, AppConfigGroup, 10000).GetAwaiter().GetResult();
AppConfigListener.ReceiveConfigInfo(AppCfg);
//string sss = serv.Cof_NaoceGet("fld-cloud-datax", "DEFAULT_GROUP", "/api/base/deviceList?limit=10&page=1").GetAwaiter().GetResult();
}
/// <summary>
/// 执行
/// </summary>
/// <param name="stoppingToken"></param>
/// <returns></returns>
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
try
{
// Add listener OcelotConfig.json"
await _configClient.AddListener(OcelotConfig, OcelotConfigGroup, nacosConfigListener);
await _configClient.AddListener(AppConfig, AppConfigGroup, AppConfigListener);
}
catch (Exception)
{
}
}
/// <summary>
/// 停止
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public override async Task StopAsync(CancellationToken cancellationToken)
{
// Remove listener
await _configClient.RemoveListener(OcelotConfig, OcelotConfigGroup, nacosConfigListener);
await _configClient.RemoveListener(AppConfig, AppConfigGroup, AppConfigListener);
await base.StopAsync(cancellationToken);
}
}
/// <summary>
/// 配置监听事件
/// </summary>
public class OcelotConfigListener : IListener
{
public IInternalConfigurationRepository internalConfigurationRepo { get; set; }
public IInternalConfigurationCreator internalConfigurationCreator { get; set; }
/// <summary>
/// 收到配置文件变更
/// </summary>
/// <param name="configInfo"></param>
public void ReceiveConfigInfo(string configInfo)
{
Task.Run(async () =>
{
FileConfiguration filecfg = JToken.Parse(configInfo).ToObject<FileConfiguration>();
var internalConfiguration = await internalConfigurationCreator.Create(filecfg);
if (!internalConfiguration.IsError)
{
internalConfigurationRepo.AddOrReplace(internalConfiguration.Data);
}
});
}
}
public class AppConfigListener : IListener
{
public void ReceiveConfigInfo(string configInfo)
{
var _configurationBuilder = new ConfigurationBuilder();
_configurationBuilder.Sources.Clear();
var buffer = System.Text.Encoding.Default.GetBytes(configInfo);
System.IO.MemoryStream ms = new System.IO.MemoryStream(buffer);
_configurationBuilder.AddJsonStream(ms);
var configuration = _configurationBuilder.Build();
ms.Dispose();
// 读取配置 将nacos配置中心读取到的配置 替换掉.net core 内存中的 configuration
// 当前监听到配置配置 应该重新断开 重连 刷新等一些中间件操作
// 比如 mq redis 等其他跟配置相关的中间件
JsonConfigSettings.Configuration = configuration;
Appsettings.Configuration = configuration;
}
}
}

View File

@ -1,12 +1,15 @@
using Blog.Core.Common;
using Blog.Core.AuthHelper;
using Blog.Core.Common;
using Blog.Core.Extensions;
using Blog.Core.Gateway.Extensions;
using Blog.Core.Middlewares;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Nacos.V2.DependencyInjection;
namespace Blog.Core.AdminMvc
{
@ -38,6 +41,11 @@ namespace Blog.Core.AdminMvc
.AddScheme<AuthenticationSchemeOptions, CustomAuthenticationHandler>(Permissions.GWName, _ => { });
services.AddNacosV2Config(Configuration, null, "nacosConfig");
services.AddNacosV2Naming(Configuration, null, "nacos");
services.AddHostedService<ApiGateway.Helper.OcelotConfigurationTask>();
services.AddCustomSwaggerSetup();
services.AddControllers();
@ -70,7 +78,8 @@ namespace Blog.Core.AdminMvc
{
endpoints.MapControllers();
});
app.UseMiddleware<JwtTokenAuth>();
app.UseCustomOcelotMildd().Wait();
}
}

View File

@ -17,15 +17,87 @@
"Startup": {
"Cors": {
"PolicyName": "CorsIpAccess",
"EnableAllIPs": false,
"EnableAllIPs": false,
"IPs": "http://127.0.0.1:2364,http://localhost:2364"
}
},
"Audience": {
"Secret": "sdfsdfsrty45634kkhllghtdgdfss345t678fs",
"Secret": "sdfsdfsrty45634kkhllghtdgdfss345t678fs",
"SecretFile": "C:\\my-file\\blog.core.audience.secret.txt",
"Issuer": "Blog.Core",
"Audience": "wr"
},
"WhiteList": [
{ "url": "/" },
{ "url": "/illagal/****" },
{ "url": "/api3/****" },
{ "url": "/baseapi/swagger.json" }
],
"BlackList": [
{ "url": "/favicon.ico" }
],
"ApiGateWay": {
"OcelotConfig": "OcelotConfig.json",
"OcelotConfigGroup": "DEFAULT_GROUP",
"AppConfig": "****.****.Gateway.json",
"AppConfigGroup": "DEFAULT_GROUP",
"PermissionServName": "****.****.Api",
"PermissionServGroup": "DEFAULT_GROUP",
"PermissionServUrl": "/api/Permission/GetPermissionlist"
},
"Influxdb": {
"Endpoint": "http://*******:9328",
"uid": "root",
"pwd": "*****",
"dbname": "mndata"
},
"nacos": {
"ServerAddresses": [ "http://******:8848/" ],
"ServiceName": "*****.****.Gateway",
"DefaultTimeOut": 15000,
"Namespace": "****",
"ListenInterval": 1000,
"GroupName": "DEFAULT_GROUP",
"ClusterName": "DEFAULT",
"Ip": "",
"PreferredNetworks": "",
"Port": 8090,
"Weight": 100,
"RegisterEnabled": true,
"InstanceEnabled": true,
"Ephemeral": true,
"Secure": false,
"AccessKey": "",
"SecretKey": "",
"UserName": "****",
"Password": "*****",
"NamingUseRpc": true,
"NamingLoadCacheAtStart": "",
"LBStrategy": "WeightRandom",
"Metadata": {
"aa": "bb",
"cc": "dd",
"endpoint33": "******:8090"
}
},
"nacosConfig": {
"ServiceName": "*****.*****.Gateway",
"Optional": false,
"DataId": "options1",
"Tenant": "******",
"Group": "DEFAULT_GROUP",
"Namespace": "*****",
"ServerAddresses": [ "http://******:8848/" ],
"UserName": "****",
"Password": "*****",
"AccessKey": "",
"SecretKey": "",
"EndPoint": "",
"ConfigUseRpc": true,
"ConfigFilterAssemblies": [ "apigateway" ],
"ConfigFilterExtInfo": "{\"JsonPaths\":[\"ConnectionStrings.Default\"],\"Other\":\"xxxxxx\"}"
}
}

View File

@ -0,0 +1,138 @@

<!--1、版本号要与nuget包一致2、id不能为空-->
<script async id="mini-profiler" src="/mini-profiler-resources/includes.min.js?v=4.2.1+b27bea37e9" data-version="4.2.1+b27bea37e9" data-path="/mini-profiler-resources/" data-current-id="144b1192-acd3-4fe2-bbc5-6f1e1c6d53df" data-ids="87a1341b-995d-4d1d-aaba-8f2bfcfc9ca9,144b1192-acd3-4fe2-bbc5-6f1e1c6d53df" data-position="Left" data-scheme="Light" data-authorized="true" data-max-traces="15" data-toggle-shortcut="Alt+P" data-trivial-milliseconds="2.0" data-ignored-duplicate-execute-types="Open,OpenAsync,Close,CloseAsync">
</script>
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!--极速模式-->
<meta name="renderer" content="webkit" />
<meta name="force-rendering" content="webkit" />
<title>%(DocumentTitle)</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" type="text/css" href="./swagger-ui.css">
<link rel="icon" type="image/png" href="./logo/favicon-32x32.png" sizes="32x32" />
<script src="/js/jquery-3.3.1.min.js"></script>
<style>
html {
box-sizing: border-box;
overflow-y: scroll;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
body {
margin: 0;
background: #fafafa;
}
.qqgroup {
position: absolute;
top: 67px;
right: 10px;
}
.info {
float: left;
}
.download-contents {
display: none;
}
</style>
%(HeadContent)
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position:absolute;width:0;height:0">
<defs>
<symbol viewBox="0 0 20 20" id="unlocked">
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V6h2v-.801C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8z"></path>
</symbol>
<symbol viewBox="0 0 20 20" id="locked">
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8zM12 8H8V5.199C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8z" />
</symbol>
<symbol viewBox="0 0 20 20" id="close">
<path d="M14.348 14.849c-.469.469-1.229.469-1.697 0L10 11.819l-2.651 3.029c-.469.469-1.229.469-1.697 0-.469-.469-.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-.469-.469-.469-1.228 0-1.697.469-.469 1.228-.469 1.697 0L10 8.183l2.651-3.031c.469-.469 1.228-.469 1.697 0 .469.469.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c.469.469.469 1.229 0 1.698z" />
</symbol>
<symbol viewBox="0 0 20 20" id="large-arrow">
<path d="M13.25 10L6.109 2.58c-.268-.27-.268-.707 0-.979.268-.27.701-.27.969 0l7.83 7.908c.268.271.268.709 0 .979l-7.83 7.908c-.268.271-.701.27-.969 0-.268-.269-.268-.707 0-.979L13.25 10z" />
</symbol>
<symbol viewBox="0 0 20 20" id="large-arrow-down">
<path d="M17.418 6.109c.272-.268.709-.268.979 0s.271.701 0 .969l-7.908 7.83c-.27.268-.707.268-.979 0l-7.908-7.83c-.27-.268-.27-.701 0-.969.271-.268.709-.268.979 0L10 13.25l7.418-7.141z" />
</symbol>
<symbol viewBox="0 0 24 24" id="jump-to">
<path d="M19 7v4H5.83l3.58-3.59L8 6l-6 6 6 6 1.41-1.41L5.83 13H21V7z" />
</symbol>
<symbol viewBox="0 0 24 24" id="expand">
<path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z" />
</symbol>
</defs>
</svg>
<div id="swagger-ui"></div>
<div id="footer" style="text-align: center;margin-bottom: 10px;">
Copyright © 2018-2020 交科院(北京)科技发展有限公司
<br><span id="poweredby">Powered by .NET 5.0.0 on Docker & CentOS 7</span>
</div>
<!-- Workaround for https://github.com/swagger-api/swagger-editor/issues/1371 -->
<script>
if (window.navigator.userAgent.indexOf("Edge") > -1) {
console.log("Removing native Edge fetch in favor of swagger-ui's polyfill")
window.fetch = undefined;
}
</script>
<script src="./swagger-ui-bundle.js"></script>
<script src="./swagger-ui-standalone-preset.js"></script>
<script>
var int = null;
if (window.location.href.indexOf("docExpansion") < 0)
window.location = window.location.href.replace("index.html", "index.html?docExpansion=none");
window.onload = function () {
var configObject = JSON.parse('%(ConfigObject)');
var oauthConfigObject = JSON.parse('%(OAuthConfigObject)');
// Apply mandatory parameters
configObject.dom_id = "#swagger-ui";
configObject.presets = [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset];
configObject.layout = "StandaloneLayout";
// If oauth2RedirectUrl isn't specified, use the built-in default
if (!configObject.hasOwnProperty("oauth2RedirectUrl"))
configObject.oauth2RedirectUrl = window.location.href.replace("index.html", "oauth2-redirect.html");
// Build a system
const ui = SwaggerUIBundle(configObject);
// Apply OAuth config
ui.initOAuth(oauthConfigObject);
//setTimeout(function () {
// // $(".link img").attr("src", "./logo/favicon-32x32.png");
// $('#swagger-ui').after("<div class='qqgroup'><div style=\"color: #4990e2;\"><a href=\"../allservices\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"link\">查看所有依赖注册的服务</a></div></div><div style='clear: both;'></div>");
// //docExpansion = none;
// //debugger
// //$(".opblock-tag-section").removeClass("is-open")
// //$(".opblock-tag-section .opblock-tag").attr("data-is-open", "false");
// //$(".opblock-tag-section .no-margin").hide();
//}, 10000);
}
</script>
</body>
</html>

View File

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30114.105
# Visual Studio Version 17
VisualStudioVersion = 17.0.32014.148
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blog.Core.Api", "Blog.Core.Api\Blog.Core.Api.csproj", "{6F47A41A-085E-4422-BB73-5A2CBAA07D9F}"
EndProject
@ -57,6 +57,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blog.Core.Gateway", "Blog.C
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blog.Core.Serilog.Es", "Blog.Core.Serilog.Es\Blog.Core.Serilog.Es.csproj", "{52AFAB53-D1CA-4014-8B63-3550FDCDA6E1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Provider.Nacos", "Ocelot.Provider.Nacos\Ocelot.Provider.Nacos.csproj", "{6463FB13-5F01-4A1D-8B62-A454FB3812EB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -119,6 +121,10 @@ Global
{52AFAB53-D1CA-4014-8B63-3550FDCDA6E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{52AFAB53-D1CA-4014-8B63-3550FDCDA6E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{52AFAB53-D1CA-4014-8B63-3550FDCDA6E1}.Release|Any CPU.Build.0 = Release|Any CPU
{6463FB13-5F01-4A1D-8B62-A454FB3812EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6463FB13-5F01-4A1D-8B62-A454FB3812EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6463FB13-5F01-4A1D-8B62-A454FB3812EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6463FB13-5F01-4A1D-8B62-A454FB3812EB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -129,6 +135,7 @@ Global
{17C9E9DC-E926-4C90-9025-3DAC55D7EDA3} = {A592C96A-4E44-4F2A-AC21-30683AF6C493}
{0B3265A9-6716-4D28-8648-C64D5E692ACA} = {047A9723-9AAC-42E3-8C69-B3835F15FF96}
{A11C0DF2-1E13-4EED-BA49-44A57136B189} = {E2BD7D4D-9ED5-41CD-8401-C3FB26F203BB}
{6463FB13-5F01-4A1D-8B62-A454FB3812EB} = {E2BD7D4D-9ED5-41CD-8401-C3FB26F203BB}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {AB40D0C5-E3EA-4A9B-86C2-38F0BB33FC04}

View File

@ -0,0 +1,56 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Threading.Tasks;
using Ocelot.ServiceDiscovery.Providers;
using Ocelot.Values;
using Nacos.V2;
using Microsoft.Extensions.Options;
using Ocelot.Provider.Nacos.NacosClient.V2;
using NacosConstants = Nacos.V2.Common.Constants;
namespace Ocelot.Provider.Nacos
{
public class Nacos : IServiceDiscoveryProvider
{
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 async Task<List<Service>> Get()
{
var services = new List<Service>();
var instances = await _client.GetAllInstances(_serviceName, _groupName, _clusters);
if (instances != null && instances.Any())
{
foreach (var Sitem in instances)
{
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>()));
}
// services.AddRange(instances.Select(i => new Service(i.InstanceId, new ServiceHostAndPort(i.Ip, i.Port), "", "", new List<string>())));
}
return await Task.FromResult(services);
}
}
}

View File

@ -0,0 +1,26 @@
using Nacos;
using Nacos.V2.Naming.Dtos;
using System.Collections.Generic;
namespace Ocelot.Provider.Nacos.NacosClient
{
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);
}
}

View File

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

View File

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

View File

@ -0,0 +1,74 @@
using Nacos;
using Nacos.V2.Naming.Dtos;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Ocelot.Provider.Nacos.NacosClient
{
public class WeightRandomLBStrategy : ILBStrategy
{
public LBStrategyName Name => LBStrategyName.WeightRandom;
public Instance GetHost(List<Instance> list)
{
var dict = BuildScore(list);
Instance instance = null;
var rd = new Random().NextDouble();
foreach (var item in dict)
{
if (item.Value >= rd)
{
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));
}
break;
}
}
return instance;
}
private Dictionary<string, double> BuildScore(List<Instance> list)
{
var dict = new Dictionary<string, double>();
// 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();
var total = tmp.Sum(x => x.Weight);
var cur = 0d;
foreach (var item in tmp)
{
cur += item.Weight;
dict.TryAdd(item.InstanceId, cur / total);
}
return dict;
}
}
}

View File

@ -0,0 +1,71 @@
using Nacos;
using System.Collections.Generic;
using System.Linq;
using Nacos.V2.Naming.Dtos;
namespace Ocelot.Provider.Nacos.NacosClient
{
public class WeightRoundRobinLBStrategy : ILBStrategy
{
public LBStrategyName Name => LBStrategyName.WeightRoundRobin;
private int _pos;
private static object obj = new object();
public Instance GetHost(List<Instance> list)
{
// 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;
}
}
}

View File

@ -0,0 +1,145 @@
using System;
using System.Collections.Generic;
using System.Linq;
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
{
internal static class UriTool
{
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))
{
// 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}") };
}
var address = string.Empty;
// 2. IServerAddressesFeature
if (features != null)
{
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 url = ReplaceAddress(item, preferredNetworks);
uris.Add(new Uri(url));
}
return uris;
}
}
// 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));
}
}
// 5. current ip address third
address = $"http://{GetCurrentIp(preferredNetworks)}:{appPort}";
return new List<Uri> { new Uri(address) };
}
private static string ReplaceAddress(string address, string preferredNetworks)
{
var ip = GetCurrentIp(preferredNetworks);
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;
}
private static string GetCurrentIp(string preferredNetworks)
{
var instanceIp = "127.0.0.1";
try
{
// 获取可用网卡
var nics = NetworkInterface.GetAllNetworkInterfaces()?.Where(network => network.OperationalStatus == OperationalStatus.Up);
// 获取所有可用网卡IP信息
var ipCollection = nics?.Select(x => x.GetIPProperties())?.SelectMany(x => x.UnicastAddresses);
foreach (var ipadd in ipCollection)
{
if (!IPAddress.IsLoopback(ipadd.Address) && ipadd.Address.AddressFamily == AddressFamily.InterNetwork)
{
if (string.IsNullOrEmpty(preferredNetworks))
{
instanceIp = ipadd.Address.ToString();
break;
}
if (!ipadd.Address.ToString().StartsWith(preferredNetworks)) continue;
instanceIp = ipadd.Address.ToString();
break;
}
}
}
catch
{
// ignored
}
return instanceIp;
}
}
}

View File

@ -0,0 +1,96 @@
using Nacos.V2;
using Nacos.V2.Common;
using System.Collections.Generic;
namespace Ocelot.Provider.Nacos.NacosClient.V2
{
public class NacosAspNetOptions : NacosSdkOptions
{
/// <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 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>
/// 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 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>
/// 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 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>
/// Load Balance Strategy
/// </summary>
public string LBStrategy { get; set; } = LBStrategyName.WeightRandom.ToString();
public NacosSdkOptions BuildSdkOptions()
{
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,
};
}
}
}

View File

@ -0,0 +1,127 @@
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
{
public class RegSvcBgTask
{
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 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 async Task StartAsync()
{
if (!_options.RegisterEnabled)
{
_logger.LogInformation("setting RegisterEnabled to false, will not register to nacos");
return;
}
uris = UriTool.GetUri(_features, _options.Ip, _options.Port, _options.PreferredNetworks);
var metadata = new Dictionary<string, string>()
{
{ 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))
{
metadata.TryAdd(item.Key, item.Value);
}
}
foreach (var uri in uris)
{
for (int i = 0; i < 3; i++)
{
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);
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);
}
}
}
}
}
}
}

View File

@ -0,0 +1,59 @@
using Microsoft.AspNetCore.Builder;
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
{
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)
{
RegSvcBgTask regSvcBgTask = app.ApplicationServices.GetRequiredService<RegSvcBgTask>();
await regSvcBgTask.StartAsync();
lifetime.ApplicationStopping.Register(async () => {
await regSvcBgTask.StopAsync();
});
return app;
}
}
}

View File

@ -0,0 +1,34 @@
using System;
using System.Threading.Tasks;
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
{
public class NacosMiddlewareConfigurationProvider
{
public static OcelotMiddlewareConfigurationDelegate Get = builder =>
{
var internalConfigRepo = builder.ApplicationServices.GetService<IInternalConfigurationRepository>();
var config = internalConfigRepo.Get();
var hostLifetime = builder.ApplicationServices.GetService<IHostApplicationLifetime>();
if (UsingNacosServiceDiscoveryProvider(config.Data))
{
builder.UseNacosAspNet(hostLifetime).GetAwaiter().GetResult();
}
return Task.CompletedTask;
};
private static bool UsingNacosServiceDiscoveryProvider(IInternalConfiguration configuration)
{
return configuration?.ServiceProviderConfiguration != null && configuration.ServiceProviderConfiguration.Type?.ToLower() == "nacos";
}
}
}

View File

@ -0,0 +1,23 @@
using System;
using Ocelot.ServiceDiscovery;
using Microsoft.Extensions.DependencyInjection;
using Nacos.V2;
using Ocelot.Provider.Nacos.NacosClient.V2;
using Microsoft.Extensions.Options;
namespace Ocelot.Provider.Nacos
{
public static class NacosProviderFactory
{
public static ServiceDiscoveryFinderDelegate Get = (provider, config, route) =>
{
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;
};
}
}

View File

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<Authors>softlgl</Authors>
<Copyright>softlgl</Copyright>
<Owners>softlgl</Owners>
<PackageProjectUrl>https://github.com/softlgl/Ocelot.Provider.Nacos</PackageProjectUrl>
<Title>Ocelot.Provider.Nacos</Title>
<Description>Repo for Nacos integration with Ocelot</Description>
<Version>1.2.1</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="nacos-sdk-csharp" Version="1.2.1" />
<PackageReference Include="Ocelot" Version="17.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
<PackageReference Include="EasyCaching.InMemory" Version="1.4.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.2" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,20 @@
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using Ocelot.DependencyInjection;
using Ocelot.Middleware;
using Ocelot.Provider.Nacos.NacosClient.V2;
using Ocelot.ServiceDiscovery;
namespace Ocelot.Provider.Nacos
{
public static class OcelotBuilderExtensions
{
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;
}
}
}