mirror of
https://github.com/anjoy8/Blog.Core.git
synced 2024-09-20 23:48:27 +08:00
boboyunz gateway
This commit is contained in:
parent
95f341037f
commit
9161ec914e
|
@ -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>
|
||||
|
|
|
@ -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 "";
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
/// 调用内部方法
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Extensions\ApiResponseHandler.cs" />
|
||||
<Compile Remove="Helper\HeaderDelegatingHandler.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 = "";
|
||||
|
|
35
Blog.Core.Gateway/Helper/HeaderDelegatingHandler.cs
Normal file
35
Blog.Core.Gateway/Helper/HeaderDelegatingHandler.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
259
Blog.Core.Gateway/Helper/JwtTokenAuth.cs
Normal file
259
Blog.Core.Gateway/Helper/JwtTokenAuth.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
|
148
Blog.Core.Gateway/Helper/OcelotConfigurationTask.cs
Normal file
148
Blog.Core.Gateway/Helper/OcelotConfigurationTask.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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\"}"
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
138
Blog.Core.Gateway/index.html
Normal file
138
Blog.Core.Gateway/index.html
Normal 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>
|
|
@ -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}
|
||||
|
|
56
Ocelot.Provider.Nacos/Nacos.cs
Normal file
56
Ocelot.Provider.Nacos/Nacos.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
26
Ocelot.Provider.Nacos/NacosClient/LoadBalance/ILBStrategy.cs
Normal file
26
Ocelot.Provider.Nacos/NacosClient/LoadBalance/ILBStrategy.cs
Normal 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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
9
Ocelot.Provider.Nacos/NacosClient/LoadBalance/LbKv.cs
Normal file
9
Ocelot.Provider.Nacos/NacosClient/LoadBalance/LbKv.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace Ocelot.Provider.Nacos.NacosClient
|
||||
{
|
||||
public class LbKv
|
||||
{
|
||||
public string InstanceId { get; set; }
|
||||
|
||||
public double Weight { get; set; }
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
145
Ocelot.Provider.Nacos/NacosClient/UriTool.cs
Normal file
145
Ocelot.Provider.Nacos/NacosClient/UriTool.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
96
Ocelot.Provider.Nacos/NacosClient/V2/NacosAspNetOptions.cs
Normal file
96
Ocelot.Provider.Nacos/NacosClient/V2/NacosAspNetOptions.cs
Normal 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
127
Ocelot.Provider.Nacos/NacosClient/V2/RegSvcBgTask.cs
Normal file
127
Ocelot.Provider.Nacos/NacosClient/V2/RegSvcBgTask.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
23
Ocelot.Provider.Nacos/NacosProviderFactory.cs
Normal file
23
Ocelot.Provider.Nacos/NacosProviderFactory.cs
Normal 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;
|
||||
};
|
||||
}
|
||||
}
|
21
Ocelot.Provider.Nacos/Ocelot.Provider.Nacos.csproj
Normal file
21
Ocelot.Provider.Nacos/Ocelot.Provider.Nacos.csproj
Normal 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>
|
20
Ocelot.Provider.Nacos/OcelotBuilderExtensions.cs
Normal file
20
Ocelot.Provider.Nacos/OcelotBuilderExtensions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user