使用 Yarp 做網(wǎng)關(guān)+.NET 6.0 + Swagger
資料
GitHub: https://github.com/microsoft/reverse-proxy
YARP 文檔
https://microsoft.github.io/reverse-proxy/articles/getting-started.html
主動(dòng)和被動(dòng)健康檢查
https://microsoft.github.io/reverse-proxy/articles/dests-health-checks.html#active-health-check
gRpc:https://microsoft.github.io/reverse-proxy/articles/grpc.html
實(shí)戰(zhàn)項(xiàng)目概覽
Yarp Gateway 示意圖

共享類庫(kù)
創(chuàng)建一個(gè) .Net6.0 的類庫(kù),項(xiàng)目名稱:Artisan.Shared.Hosting.AspNetCore, 其它項(xiàng)目公用方法放在這個(gè)項(xiàng)目。
Serilog 日志
需要的包:
<PackageReference Include="Serilog.AspNetCore" Version="5.0.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
代碼清單:Artisan.Shared.Hosting.AspNetCore/SerilogConfigurationHelper.cs
using Serilog;
using Serilog.Events;
namespace Artisan.Shared.Hosting.AspNetCore;
public static class SerilogConfigurationHelper
{
public static void Configure(string applicationName)
{
Log.Logger = new LoggerConfiguration()
#if DEBUG
.MinimumLevel.Debug()
#else
.MinimumLevel.Information()
#endif
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
.MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Warning)
.Enrich.FromLogContext()
.Enrich.WithProperty("Application", $"{applicationName}")
.WriteTo.Async(c => c.File($"{AppDomain.CurrentDomain.BaseDirectory}/Logs/logs.txt"))
.WriteTo.Async(c => c.Console())
.CreateLogger();
}
}
創(chuàng)建服務(wù)
IdentityService
創(chuàng)建一個(gè)【AspNetCore Web Api】項(xiàng)目,項(xiàng)目名稱為:IdentityService
Program
代碼清單:IdentityService/Program.cs
using Artisan.Shared.Hosting.AspNetCore;
using Microsoft.OpenApi.Models;
using Serilog;
namespace IdentityService;
public class Program
{
public static int Main(string[] args)
{
var assemblyName = typeof(Program).Assembly.GetName().Name;
SerilogConfigurationHelper.Configure(assemblyName);
try
{
Log.Information($"Starting {assemblyName}.");
var builder = WebApplication.CreateBuilder(args);
builder.Host
.UseSerilog();
builder.Services.AddControllers(); //Web MVC
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo { Title = "Identity Service", Version = "v1" });
options.DocInclusionPredicate((docName, description) => true);
options.CustomSchemaIds(type => type.FullName);
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseSwagger();
app.UseSwaggerUI();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers(); //Web MVC
});
app.Run();
return 0;
}
catch (Exception ex)
{
Log.Fatal(ex, $"{assemblyName} terminated unexpectedly!");
return 1;
}
finally
{
Log.CloseAndFlush();
}
}
}
其中:
SerilogConfigurationHelper.Configure(assemblyName);
是配置 Serilog 日志:引用上面創(chuàng)建的共享項(xiàng)目:【Artisan.Shared.Hosting.AspNetCore】
User 實(shí)體
代碼清單:IdentityService/Models/User.cs
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
UserController
代碼清單:IdentityService/Controlles/UserController.cs
using Microsoft.AspNetCore.Mvc;
using IdentityService.Models;
using System.Threading.Tasks;
namespace IdentityService.Controllers
{
[ApiController]
[Route("/api/identity/users")]
public class UserController : Controller
{
private readonly ILogger<UserController> _logger;
private static List<User> Users = new List<User>()
{
new User(){ Id = 1, Name = "Jack"},
new User(){ Id = 2, Name = "Tom"},
new User(){ Id = 3, Name = "Franck"},
new User(){ Id = 4, Name = "Tony"},
};
public UserController(ILogger<UserController> logger)
{
_logger = logger;
}
[HttpGet]
public async Task<List<User>> GetAllAsync()
{
return await Task.Run(() =>
{
return Users;
});
}
[HttpGet]
[Route("{id}")]
public async Task<User> GetAsync(int id)
{
return await Task.Run(() =>
{
var entity = Users.FirstOrDefault(p => p.Id == id);
if (entity == null)
{
throw new Exception($"未找到用戶:{id}");
}
return entity;
});
}
[HttpPost]
public async Task<User> CreateAsync(User user)
{
return await Task.Run(() =>
{
Users.Add(user);
return user;
});
}
[HttpPut]
[Route("{id}")]
public async Task<User> UpdateAsync(int id, User user)
{
return await Task.Run(() =>
{
var entity = Users.FirstOrDefault(p => p.Id == id);
if(entity == null)
{
throw new Exception($"未找到用戶:{id}");
}
entity.Name = user.Name;
return entity;
});
}
[HttpDelete]
[Route("{id}")]
public async Task<User> DeleteAsync(int id)
{
return await Task.Run(() =>
{
var entity = Users.FirstOrDefault(p => p.Id == id);
if (entity == null)
{
throw new Exception($"未找到用戶:{id}");
}
Users.Remove(entity);
return entity;
});
}
}
}
OrderService
創(chuàng)建一個(gè)【AspNetCore Web Api】項(xiàng)目,項(xiàng)目名稱為:OrderService
Program
代碼清單:OrderService/Program.cs
using Artisan.Shared.Hosting.AspNetCore;
using Microsoft.OpenApi.Models;
using Serilog;
namespace OrderService;
public class Program
{
public static int Main(string[] args)
{
var assemblyName = typeof(Program).Assembly.GetName().Name;
SerilogConfigurationHelper.Configure(assemblyName);
try
{
Log.Information($"Starting {assemblyName}.");
var builder = WebApplication.CreateBuilder(args);
builder.Host
.UseSerilog();
builder.Services.AddControllers(); //Web MVC
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo { Title = "Order Service", Version = "v1" });
options.DocInclusionPredicate((docName, description) => true);
options.CustomSchemaIds(type => type.FullName);
});
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseSwagger();
app.UseSwaggerUI();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers(); //Web MVC
});
app.Run();
return 0;
}
catch (Exception ex)
{
Log.Fatal(ex, $"{assemblyName} terminated unexpectedly!");
return 1;
}
finally
{
Log.CloseAndFlush();
}
}
}
Order 實(shí)體
代碼清單:OrderService/Models/Order.cs
public class Order
{
public string Id { get; set; }
public string Name { get; set; }
}
OrderController
代碼清單:OrderService/Controlles/OrderController.cs
using Microsoft.AspNetCore.Mvc;
using OrderService.Models;
using System.Diagnostics;
namespace OrderService.Controllers
{
[ApiController]
[Route("/api/ordering/orders")]
public class OrderController : Controller
{
private readonly ILogger<OrderController> _logger;
private static List<Order> Orders = new List<Order>()
{
new Order(){ Id = "1", Name = "Order #1"},
new Order(){ Id = "2", Name = "Order #2"},
new Order(){ Id = "3", Name = "Order #3"},
new Order(){ Id = "4", Name = "Order #4"},
};
public OrderController(ILogger<OrderController> logger)
{
_logger = logger;
}
[HttpGet]
public async Task<List<Order>> GetAllAsync()
{
return await Task.Run(() =>
{
return Orders;
});
}
[HttpGet]
[Route("{id}")]
public async Task<Order> GetAsync(string id)
{
return await Task.Run(() =>
{
var entity = Orders.FirstOrDefault(p => p.Id == id);
if (entity == null)
{
throw new Exception($"未找到訂單:{id}");
}
return entity;
});
}
[HttpPost]
public async Task<Order> CreateAsync(Order order)
{
return await Task.Run(() =>
{
Orders.Add(order);
return order;
});
}
[HttpPut]
[Route("{id}")]
public async Task<Order> UpdateAsync(string id, Order Order)
{
return await Task.Run(() =>
{
var entity = Orders.FirstOrDefault(p => p.Id == id);
if (entity == null)
{
throw new Exception($"未找到訂單:{id}");
}
entity.Name = Order.Name;
return entity;
});
}
[HttpDelete]
[Route("{id}")]
public async Task<Order> DeleteAsync(string id)
{
return await Task.Run(() =>
{
var entity = Orders.FirstOrDefault(p => p.Id == id);
if (entity == null)
{
throw new Exception($"未找到訂單:{id}");
}
Orders.Remove(entity);
return entity;
});
}
}
}
創(chuàng)建網(wǎng)關(guān)
創(chuàng)建一個(gè)【AspNetCore 空】項(xiàng)目,項(xiàng)目名稱為:YarpGateway
引用包
<PackageReference Include="Yarp.ReverseProxy" Version="1.1.0" />
添加 Yarp
代碼清單:YarpGateway/Program.cs
using Artisan.Shared.Hosting.AspNetCore;
using Serilog;
using YarpGateway.Extensions;
namespace YarpGateway;
public class Program
{
public static int Main(string[] args)
{
var assemblyName = typeof(Program).Assembly.GetName().Name;
SerilogConfigurationHelper.Configure(assemblyName);
try
{
Log.Information($"Starting {assemblyName}.");
var builder = WebApplication.CreateBuilder(args);
builder.Host
.UseSerilog()
.AddYarpJson(); // 添加Yarp的配置文件
// 添加Yarp反向代理ReverseProxy
builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
var app = builder.Build();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
// 添加Yarp終端Endpoints
endpoints.MapReverseProxy();
});
app.Run();
return 0;
}
catch (Exception ex)
{
Log.Fatal(ex, $"{assemblyName} terminated unexpectedly!");
return 1;
}
finally
{
Log.CloseAndFlush();
}
}
}
其中:
方法AddYarpJson() 是為了把 Yarp 的有關(guān)配置從appsetting.json獨(dú)立處理,避免配置文件很長(zhǎng)很長(zhǎng),其代碼如下:
代碼清單:YarpGateway/Extensions/GatewayHostBuilderExtensions.cs
namespace YarpGateway.Extensions;
public static class GatewayHostBuilderExtensions
{
public const string AppYarpJsonPath = "yarp.json";
public static IHostBuilder AddYarpJson(
this IHostBuilder hostBuilder,
bool optional = true,
bool reloadOnChange = true,
string path = AppYarpJsonPath)
{
return hostBuilder.ConfigureAppConfiguration((_, builder) =>
{
builder.AddJsonFile(
path: AppYarpJsonPath,
optional: optional,
reloadOnChange: reloadOnChange
)
.AddEnvironmentVariables();
});
}
}
其中:
reloadOnChange = true 保證配置文件修改時(shí), Yarp 能重新讀取配置文件。
添加 Yarp配置文件 : yarp.json
記得保證文件的屬性:
復(fù)制到輸出目錄:如果內(nèi)容較新則復(fù)制 生成操作:內(nèi)容
代碼清單:YarpGateway/yarp.json
{
"ReverseProxy": {
"Routes": {
"Identity Service": {
"ClusterId": "identityCluster",
"Match": {
"Path": "/api/identity/{**everything}"
}
},
"Ordering Service": {
"ClusterId": "orderingCluster",
"Match": {
"Path": "/api/ordering/{**everything}"
}
}
},
"Clusters": {
"identityCluster": {
"Destinations": {
"destination1": {
"Address": "http://localhost:7711"
}
}
},
"orderingCluster": {
"Destinations": {
"destination1": {
"Address": "http://localhost:7721"
}
"destination2": {
"Address": "http://localhost:7722"
}
}
}
}
}
}
運(yùn)行
Yarp Gateway 示意圖:

啟動(dòng)網(wǎng)關(guān)
在項(xiàng)目的bin/net6.0目錄下打開(kāi) CMD,執(zhí)行如下命令啟動(dòng)網(wǎng)關(guān):
dotnet YarpGateway.dll --urls "http://localhost:7700"監(jiān)聽(tīng)端口:7700
IdentityService
在項(xiàng)目的bin/net6.0目錄下打開(kāi) CMD,執(zhí)行如下命令啟動(dòng) Web API 服務(wù):
dotnet IdentityService.dll --urls "http://localhost:7711"監(jiān)聽(tīng)端口:7711
OrderService
開(kāi)啟兩個(gè) OrderServcie 的進(jìn)程,
在 bin/net6.0目錄下打開(kāi) CMD,執(zhí)行如下命令啟動(dòng) Web API 服務(wù):
第一個(gè)監(jiān)聽(tīng)端口:7721
dotnet OrderService.dll --urls "http://localhost:7721"第二個(gè)監(jiān)聽(tīng)端口:7722
dotnet OrderService.dll --urls "http://localhost:7722"測(cè)試
路由功能
打開(kāi) PostMan,創(chuàng)建調(diào)用服務(wù)的各種請(qǐng)求
IdentityService
創(chuàng)建 GET 請(qǐng)求調(diào)用網(wǎng)關(guān):http://localhost:7700/api/identity/users
請(qǐng)求會(huì)被轉(zhuǎn)發(fā)到 IdentityService的集群節(jié)點(diǎn):http://localhost:7711/api/identity/users
OrderService
創(chuàng)建 GET 請(qǐng)求調(diào)用網(wǎng)關(guān):http://localhost:7700/api/ordering/orders
請(qǐng)求會(huì)被轉(zhuǎn)發(fā)到 OrderService 的集群中如下某個(gè)節(jié)點(diǎn)中的一個(gè):
http://localhost:7721/api/ordering/orders http://localhost:7722/api/ordering/orders
支持請(qǐng)求類型
Tips:
由于是兩個(gè)服務(wù),每個(gè)服務(wù)的進(jìn)程都是獨(dú)立的,數(shù)據(jù)也是獨(dú)立,數(shù)據(jù)并沒(méi)有共享,故測(cè)試結(jié)果可能不是你所預(yù)期的,比如:
第一步:增加數(shù)據(jù),這次是由第一個(gè)服務(wù)處理的;
第二步:查詢數(shù)據(jù),如果這次查詢是由第二個(gè)服務(wù)器處理的話,就會(huì)找不到剛才新增的數(shù)據(jù)。
當(dāng)然在實(shí)際開(kāi)發(fā)中,我們的數(shù)據(jù)都是從同一個(gè)數(shù)據(jù)庫(kù)中讀取,不會(huì)出現(xiàn)數(shù)據(jù)不一致的情況。
HTTP 1.0 / 2.0
創(chuàng)建 GET 請(qǐng)求:http://localhost:7700/api/ordering/orders/1
創(chuàng)建 POST 請(qǐng)求:http://localhost:7700/api/ordering/orders
參數(shù):
{
"id":"10",
"name":"Order #100"
}
創(chuàng)建 PUT 請(qǐng)求:http://localhost:7700/api/ordering/orders/10
參數(shù):
{
"id":"10",
"name":"Order #100-1"
}創(chuàng)建 DELETE 請(qǐng)求:http://localhost:7700/api/ordering/orders/10
結(jié)論
上述4種 HTTP 請(qǐng)求都支持。
gRpc
待測(cè)試...
結(jié)論
支持 gRpc
新增集群服務(wù)節(jié)點(diǎn)
Yarp 支持動(dòng)態(tài)添加服務(wù)集群服務(wù)節(jié)點(diǎn),只要在配置文件 yarp.json, 添加新的服務(wù)配置,Yarp會(huì)自動(dòng)加載新的服務(wù)節(jié)點(diǎn):
代碼清單:yarp.json
{
"ReverseProxy": {
"Routes": {
"Identity Service": {
"ClusterId": "identityCluster",
"Match": {
"Path": "/api/identity/{**everything}"
}
},
...
},
"Clusters": {
"orderingCluster": {
"Destinations": {
"destination1": {
"Address": "http://localhost:7721"
},
+ "destination2": {
+ "Address": "http://localhost:7722"
+ }
}
}
}
}
}
添加上述配置后,會(huì)看到如下日志信息:
14:51:11 DBG] Destination 'destination2' has been added.
[14:51:11 DBG] Existing client reused for cluster 'orderingCluster'.結(jié)論
Yarp 會(huì)重新加載配置,使得新增的集群新服務(wù)節(jié)點(diǎn)生效。
刪除集群服務(wù)節(jié)點(diǎn)
刪除集群下的某個(gè)服務(wù)節(jié)點(diǎn)
- "destination2": {
- "Address": "http://localhost:7722"
- }Yarp 會(huì)重新加載配置,該集群服務(wù)節(jié)點(diǎn)被刪除。
[14:41:26 DBG] Destination 'destination2' has been removed.
[14:41:26 DBG] Existing client reused for cluster 'orderingCluster'.結(jié)論
Yarp 會(huì)重新加載配置,使得被刪除的集群服務(wù)節(jié)點(diǎn)配置失效。
某集群節(jié)點(diǎn)因故障離線
把監(jiān)聽(tīng)7722端口的服務(wù)終止,請(qǐng)求還是會(huì)發(fā)送到這個(gè)端口程序上!?。?/p>
結(jié)論
Yarp 默認(rèn)不會(huì)做健康檢查
如何通過(guò) YarpGateway 訪問(wèn)內(nèi)部服務(wù)的Swagger呢?
問(wèn)題:無(wú)法訪問(wèn)內(nèi)部服務(wù) Swagger
外部訪問(wèn) IdentityService 和 OrderService 是通過(guò) 網(wǎng)關(guān):YarpGateway 訪問(wèn)的,使用者這個(gè)并不知道這個(gè)兩個(gè)服務(wù)的具體地址,也就是不知道如何訪問(wèn)它們的 Swagger,那么:
如何通過(guò) YarpGateway 訪問(wèn)這兩個(gè)服務(wù)的Swagger呢?
實(shí)現(xiàn)原理
使用網(wǎng)關(guān)內(nèi)部服務(wù)的 Swagger 信息,其地址為:
http://ip:port/swagger/v1/swagger.json例如,OrderService 服務(wù)的 Swagger 信息為:
http://localhost:7721/swagger/v1/swagger.json
在網(wǎng)關(guān)中使用內(nèi)部服務(wù)的 Swagger 終點(diǎn),再注冊(cè) Swagger 終點(diǎn)。
訪問(wèn) OrderService 服務(wù)的 Swagger 信息地址:http://localhost:7711/swagger/v1/swagger.json
返回如下信息:(只列舉部分?jǐn)?shù)據(jù))
{
"openapi": "3.0.1",
"info": {
"title": "Identity Service",
"version": "v1"
},
"paths": {
"/api/identity/users": {
"get": {
"tags": [
"User"
],
"responses": {
"200": {
"description": "Success",
"content": {
"text/plain": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/IdentityService.Models.User"
}
}
},
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/IdentityService.Models.User"
}
}
},
"text/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/IdentityService.Models.User"
}
}
}
}
}
}
},
.....
內(nèi)部服務(wù)支持跨域
網(wǎng)關(guān)要請(qǐng)求內(nèi)部服務(wù)的Swagger 信息,這是跨域請(qǐng)求,所以要求兩個(gè)服務(wù)支持對(duì)網(wǎng)關(guān)的跨域請(qǐng)求。
在IdentityService 和 OrderService 項(xiàng)目中都做如下修改:
添加跨域配置
在 appsettins.json 文件中添加跨域配置:
{
"App": {
"CorsOrigins": "http://localhost:7700" // 網(wǎng)關(guān)地址,支持網(wǎng)關(guān)的Yarp gatewary跨域請(qǐng)求
}
}
其中,這個(gè)地址**http://localhost:7700** 就是網(wǎng)關(guān)的地址。
支持跨域
修改 Program.cs文件:
......
IConfiguration configuration = builder.Configuration;
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(builder =>
{
builder
.WithOrigins(
configuration["App:CorsOrigins"]
.Split(",", StringSplitOptions.RemoveEmptyEntries)
.ToArray()
)
.SetIsOriginAllowedToAllowWildcardSubdomains()
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
......
app.UseRouting();
+ app.UseCors(); // 添加跨域支持
app.UseSwagger();
app.UseSwaggerUI();
.....
網(wǎng)關(guān)添加 Swagger
在網(wǎng)關(guān)項(xiàng)目【YarpGateway】中做如下修改:
代碼清單:YarpGateway/Program.cs
builder.Services.AddControllers(); //Web MVC
......
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo
{
Title = "Gateway", Version = "v1"
});
options.DocInclusionPredicate((docName, description) => true);
options.CustomSchemaIds(type => type.FullName);
});
......
// 添加內(nèi)部服務(wù)的Swagger終點(diǎn)
app.UseSwaggerUIWithYarp();
//訪問(wèn)網(wǎng)關(guān)地址,自動(dòng)跳轉(zhuǎn)到 /swagger 的首頁(yè)
app.UseRewriter(new RewriteOptions()
// Regex for "", "/" and "" (whitespace)
.AddRedirect("^(|\\|\\s+)$", "/swagger"));
app.UseRouting();
其中,調(diào)用方法 app.UseSwaggerUIWithYarp(); 的目的是:
添加內(nèi)部服務(wù)的Swagger終點(diǎn),其代碼如下:
代碼清單:YarpGateway/Extensions/YarpSwaggerUIBuilderExtensions.cs
using Yarp.ReverseProxy.Configuration;
namespace YarpGateway.Extensions;
public static class YarpSwaggerUIBuilderExtensions
{
public static IApplicationBuilder UseSwaggerUIWithYarp(this IApplicationBuilder app)
{
var serviceProvider = app.ApplicationServices;
app.UseSwagger();
app.UseSwaggerUI(options =>
{
var configuration = serviceProvider.GetRequiredService<IConfiguration>();
var logger = serviceProvider.GetRequiredService<ILogger<Program>>();
var proxyConfigProvider = serviceProvider.GetRequiredService<IProxyConfigProvider>();
var yarpConfig = proxyConfigProvider.GetConfig();
var routedClusters = yarpConfig.Clusters
.SelectMany(t => t.Destinations,
(clusterId, destination) => new { clusterId.ClusterId, destination.Value });
var groupedClusters = routedClusters
.GroupBy(q => q.Value.Address)
.Select(t => t.First())
.Distinct()
.ToList();
foreach (var clusterGroup in groupedClusters)
{
var routeConfig = yarpConfig.Routes.FirstOrDefault(q =>
q.ClusterId == clusterGroup.ClusterId);
if (routeConfig == null)
{
logger.LogWarning($"Swagger UI: Couldn't find route configuration for {clusterGroup.ClusterId}...");
continue;
}
options.SwaggerEndpoint($"{clusterGroup.Value.Address}/swagger/v1/swagger.json", $"{routeConfig.RouteId} API");
options.OAuthClientId(configuration["AuthServer:SwaggerClientId"]);
options.OAuthClientSecret(configuration["AuthServer:SwaggerClientSecret"]);
}
});
return app;
}
}
關(guān)鍵代碼:
options.SwaggerEndpoint($"{clusterGroup.Value.Address}/swagger/v1/swagger.json", $"{routeConfig.RouteId} API");
通過(guò) IProxyConfigProvider 得到內(nèi)部服務(wù)的信息,如下圖所示:

然后,拼接出內(nèi)部服務(wù)的 Swagger 信息地址,
$"{clusterGroup.Value.Address}/swagger/v1/swagger.json"
最終得到兩個(gè)服務(wù)的Swagger信息地址:
IdentityServer 的 Swagger 信息地址:
http://localhost:7711/swagger/v1/swagger.jsonOrderService 的 Swagger 信息地址:
http://localhost:7721/swagger/v1/swagger.json最后,根據(jù)信息添加Swagger終點(diǎn):
options.SwaggerEndpoint(
$"{clusterGroup.Value.Address}/swagger/v1/swagger.json",
$"{routeConfig.RouteId} API"
);
其中,
routeConfig.RouteId: Identity Service 或 Ordering Service
訪問(wèn)網(wǎng)關(guān) Swagger
訪問(wèn)網(wǎng)關(guān)地址:http://localhost:7700
自動(dòng)跳轉(zhuǎn)到其 Swagger首頁(yè):http://localhost:7700/swagger/index.html

右上角有個(gè)下拉框,可以選擇不同的服務(wù)的Swagger,這里切換到 OrderService 的Swagger,如下圖所示:

在網(wǎng)關(guān) Swagger 調(diào)用服務(wù)接口
可以在網(wǎng)關(guān) Swagger 調(diào)用內(nèi)部服務(wù)接口,如下圖所示:

返回

轉(zhuǎn)自:easy5
鏈接:cnblogs.com/easy5weikai/p/16314830.html
