前言:上一篇中实现了consul的服务注册与发现,现在可以把网关系统进行一次升级改造,实现简单的服务发现与治理以及身份认证
一,Consul服务发现的引用与配置
1,使用NuGet安装Ocelot.Provider.Consul:
2,使用Startup.cs进行注册:
public void ConfigureServices(IServiceCollection services)
{
//添加ocelot服务
services.AddOcelot()
//服务发现
.AddConsul();
}
3,修改ocelot.json配置文件:
{
"Routes": [
{
"DownstreamPathTemplate": "/product/{xx}",
"DownstreamScheme": "http",
"ServiceName": "productsevrice",
//"DownstreamHostAndPorts": [
// {
// "Host": "localhost",
// "Port": 4001
// },
// {
// "Host": "localhost",
// "Port": 4002
// }
//],
"UpstreamPathTemplate": "/api/product/{xx}",
"UpstreamHttpMethod": [ "Get", "Post", "Put", "Delete" ],
"LoadBalancerOptions": {
"Type": "LeastConnection"
}
}
],
"GlobalConfiguration": {
"baseUrl": "http://localhost:4000",
"ServiceDiscoveryProvider": {
"Scheme": "http",
"Host": "localhost",
"Port": 8500,
"Type": "Consul"
}
}
}
使用consul服务发现相关配置,在全局GlobalConfiguration增加ServiceDiscoveryProvider, 移除DownstreamHostAndPorts节点。
二,Polly服务治理的引用与配置
服务治理是为了提高服务质量以及可用性,缓存,限流,熔断,负载均衡等等都算,实际使用中按需实现即可
1,超时/熔断:
超时即网关请求服务时的最长响应时间,熔断某个服务的请求异常次数达到一定量时不对其继续请求
(1)添加Ocelot.Provider.Polly
(2)修改Startup配置
public void ConfigureServices(IServiceCollection services)
{
//添加ocelot服务
services.AddOcelot()
//服务发现
.AddConsul()
//添加Polly(防止熔断)
.AddPolly();
}
(3)修改Ocelot.json
"QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 3, //发生错误的次数
"DurationOfBreak": 10000, //熔断时间
"TimeoutValue": 5000 //请求超过 5 秒,它将自动超时。
}
2,缓存/限流
(1) 添加 Ocelot.Cache.CacheManager
(2)修改配置:
public void ConfigureServices(IServiceCollection services)
{
//添加ocelot服务
services.AddOcelot()
//服务发现
.AddConsul()
//添加Polly(防止熔断)
.AddPolly()
//添加缓存,限流
.AddCacheManager(x =>
{
x.WithDictionaryHandle();
});
}
Ocelot.json配置修改:
"FileCacheOptions": {
"TtlSeconds": 5, //过期时间
"Region": "productcache"
},
"RateLimitOptions": {
"ClientWhitelist": [ "SuperClient" ], //白名单中的客户端可以不受限流的影响
"EnableRateLimiting": true, //是否限流
"Period": "5s", //限流的单位时间
"PeriodTimespan": 2, //请求上限多少秒后可以重试
"Limit": 1 //定义的时间内可以发出的最大请求数
}
最后修改完成之后的Ocelot.json:
{
"Routes": [
{
"DownstreamPathTemplate": "/product/{xx}",
"DownstreamScheme": "http",
"ServiceName": "productsevrice",
//"DownstreamHostAndPorts": [
// {
// "Host": "localhost",
// "Port": 4001
// },
// {
// "Host": "localhost",
// "Port": 4002
// }
//],
"UpstreamPathTemplate": "/api/product/{xx}",
"UpstreamHttpMethod": [ "Get", "Post", "Put", "Delete" ],
"LoadBalancerOptions": {
"Type": "LeastConnection"
},
"FileCacheOptions": {
"TtlSeconds": 5, //过期时间
"Region": "productcache"
},
"RateLimitOptions": {
"ClientWhitelist": [ "SuperClient" ], //白名单中的客户端可以不受限流的影响
"EnableRateLimiting": true, //是否限流
"Period": "5s", //限流的单位时间
"PeriodTimespan": 2, //请求上限多少秒后可以重试
"Limit": 1 //定义的时间内可以发出的最大请求数
},
"QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 3, //发生错误的次数
"DurationOfBreak": 10000, //熔断时间
"TimeoutValue": 5000 //请求超过 5 秒,它将自动超时。
}
},
{
"DownstreamPathTemplate": "/order/{xx}",
"DownstreamScheme": "http",
"ServiceName": "orderservice",
"LoadBalancerOptions": {
"Type": "LeastConnection"
},
"UpstreamPathTemplate": "/api/order/{xx}",
"UpstreamHttpMethod": [ "Get", "Post" ]
}
],
"GlobalConfiguration": {
"baseUrl": "http://localhost:4000",
"ServiceDiscoveryProvider": {
"Scheme": "http",
"Host": "localhost",
"Port": 8500,
"Type": "Consul"
},
"RateLimitOptions": {
"DisableRateLimitHeaders": false, //是否禁用X-Rate-Limit和Retry-After标头
"QuotaExceededMessage": "{"res_code":"999","res_msg":"请求中"}", //请求达到上限时返回
"HttpStatusCode": 999, //HTTP状态代码
"ClientIdHeader": "Test"
}
}
}
三,JWT的引用配置与认证
1 ,第一步老规矩Nugget 获取JWT
2,添加了一个帮助类:
public class JWtHelper
{
public static string JwtEncrypt(string phone, string uid, string openid, string encrypt_key)
{
var token = "";
try
{
var claims = new[] {
new Claim("openid", openid),
new Claim("phone", phone),
new Claim("uid", uid),};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(encrypt_key));
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var jwtToken = new JwtSecurityToken("MER", "API", claims, expires: DateTime.Now.AddYears(1), signingCredentials: credentials);
token = new JwtSecurityTokenHandler().WriteToken(jwtToken);
}
catch (Exception)
{
throw;
}
return token;
}
public static string JwtDecrypt(string token, string encrypt_key)
{
var josn = "";
try
{
var param = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(encrypt_key)),
ValidateIssuer = true,
ValidIssuers = new string[] { "ASD123", "MER"},
ValidateAudience = true,
ValidAudience = "API",
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(5)
};
SecurityToken security;
var principal = new JwtSecurityTokenHandler().ValidateToken(token, param, out security);
if (principal != null)
{
var exp = principal.Claims.FirstOrDefault(c => c.Type.Equals("exp"))?.Value;
var uid = principal.Claims.FirstOrDefault(c => c.Type.Equals("uid"))?.Value;
var phone = principal.Claims.FirstOrDefault(c => c.Type.Equals("phone"))?.Value;
var openid = principal.Claims.FirstOrDefault(c => c.Type.Equals("openid"))?.Value;
josn = JsonConvert.SerializeObject(new { openid, phone, uid, exp });
}
}
catch (Exception ex)
{
return null;
}
return josn;
}
}
ps:这个帮助类只是为了看看效果,测试类随便编写能不能再生产环境使用自行考虑。
3,添加Jwt中间件
public class JwtSafeMiddleware
{
private readonly RequestDelegate _next;
public IConfiguration _configuration;
public JwtSafeMiddleware(RequestDelegate next, IConfiguration configuration)
{
_next = next;
_configuration = configuration;
}
public async Task Invoke(HttpContext context)
{
//请求不走jwt校验的一种方式:识别url
//if(!context.Request.Path.Value.StartsWith("/auth"))
context.Response.ContentType = "application/json";
if (context.Request.Method == "GET" || context.Request.Method == "POST")
{
string token = context.Request.Headers["token"].FirstOrDefault();
if (string.IsNullOrEmpty(token))
{
context.Response.StatusCode = 401; //401未授权
var json = JsonConvert.SerializeObject(new { res_code = 401, res_msg = "token为空!" });
await context.Response.WriteAsync(json);
return;
}
//校验auth的正确性
var result = JWtHelper.JwtDecrypt(token, _configuration["SecretKey"]);
if (result == "expired")
{
context.Response.StatusCode = 666;
var json = JsonConvert.SerializeObject(new { res_code = 666, res_msg = "参数已经过期!" });
await context.Response.WriteAsync(json);
return;
}
else
{
//校验通过
}
}
await _next.Invoke(context);
}
}
最后在Startup中的Configure方法注册:app.UseMiddleware
下一篇 微服务中的CAP
--------to be continue --------



