111 lines
3.3 KiB
C#
111 lines
3.3 KiB
C#
using Microsoft.AspNetCore.Builder;
|
||
using Microsoft.AspNetCore.Diagnostics;
|
||
using Microsoft.AspNetCore.Http;
|
||
using Microsoft.AspNetCore.RateLimiting;
|
||
using Microsoft.Extensions.DependencyInjection;
|
||
using Microsoft.Extensions.Hosting;
|
||
using Microsoft.Extensions.Logging;
|
||
using System.Diagnostics;
|
||
using System.Threading.RateLimiting;
|
||
|
||
var builder = WebApplication.CreateBuilder(args);
|
||
|
||
builder.Services.AddReverseProxy()
|
||
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
|
||
|
||
builder.Services.AddRateLimiter(options =>
|
||
{
|
||
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
|
||
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
|
||
{
|
||
const string partitionKey = "proxy-default";
|
||
return RateLimitPartition.GetFixedWindowLimiter(partitionKey, _ => new FixedWindowRateLimiterOptions
|
||
{
|
||
PermitLimit = 100,
|
||
Window = TimeSpan.FromSeconds(1),
|
||
QueueLimit = 50,
|
||
QueueProcessingOrder = QueueProcessingOrder.OldestFirst
|
||
});
|
||
});
|
||
});
|
||
|
||
var app = builder.Build();
|
||
|
||
app.UseExceptionHandler(errorApp =>
|
||
{
|
||
errorApp.Run(async context =>
|
||
{
|
||
var feature = context.Features.Get<IExceptionHandlerFeature>();
|
||
var traceId = Activity.Current?.Id ?? context.TraceIdentifier;
|
||
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
|
||
context.Response.ContentType = "application/json";
|
||
|
||
var payload = new
|
||
{
|
||
success = false,
|
||
code = 500,
|
||
message = "Gateway internal error",
|
||
traceId
|
||
};
|
||
|
||
var logger = context.RequestServices.GetRequiredService<ILoggerFactory>().CreateLogger("Gateway");
|
||
logger.LogError(feature?.Error, "网关异常 {TraceId}", traceId);
|
||
await context.Response.WriteAsJsonAsync(payload, cancellationToken: context.RequestAborted);
|
||
});
|
||
});
|
||
|
||
app.Use(async (context, next) =>
|
||
{
|
||
var logger = context.RequestServices.GetRequiredService<ILoggerFactory>().CreateLogger("Gateway");
|
||
var start = DateTime.UtcNow;
|
||
await next(context);
|
||
var elapsed = DateTime.UtcNow - start;
|
||
logger.LogInformation("Gateway {Method} {Path} => {Status} ({Elapsed} ms)",
|
||
context.Request.Method,
|
||
context.Request.Path,
|
||
context.Response.StatusCode,
|
||
(int)elapsed.TotalMilliseconds);
|
||
});
|
||
|
||
app.UseRateLimiter();
|
||
|
||
app.Use(async (context, next) =>
|
||
{
|
||
// 确保存在请求 ID,便于上下游链路追踪。
|
||
if (!context.Request.Headers.ContainsKey("X-Request-Id"))
|
||
{
|
||
context.Request.Headers["X-Request-Id"] = Guid.NewGuid().ToString("N");
|
||
}
|
||
|
||
// 透传租户与认证头。
|
||
var tenantId = context.Request.Headers["X-Tenant-Id"];
|
||
var tenantCode = context.Request.Headers["X-Tenant-Code"];
|
||
if (!string.IsNullOrWhiteSpace(tenantId))
|
||
{
|
||
context.Request.Headers["X-Tenant-Id"] = tenantId;
|
||
}
|
||
if (!string.IsNullOrWhiteSpace(tenantCode))
|
||
{
|
||
context.Request.Headers["X-Tenant-Code"] = tenantCode;
|
||
}
|
||
|
||
await next(context);
|
||
});
|
||
|
||
app.MapReverseProxy(proxyPipeline =>
|
||
{
|
||
proxyPipeline.Use(async (context, next) =>
|
||
{
|
||
await next().ConfigureAwait(false);
|
||
});
|
||
});
|
||
|
||
app.MapGet("/", () => Results.Json(new
|
||
{
|
||
Service = "TakeoutSaaS.ApiGateway",
|
||
Status = "OK",
|
||
Timestamp = DateTimeOffset.UtcNow
|
||
}));
|
||
|
||
app.Run();
|