feat: finalize core modules and gateway

This commit is contained in:
2025-11-23 18:53:12 +08:00
parent 429d4fb747
commit ae273e510a
115 changed files with 4695 additions and 223 deletions

View File

@@ -1,67 +1,110 @@
using System.Collections.Generic;
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 Yarp.ReverseProxy.Configuration;
using Microsoft.Extensions.Logging;
using System.Diagnostics;
using System.Threading.RateLimiting;
var builder = WebApplication.CreateBuilder(args);
var routes = new[]
{
new RouteConfig
{
RouteId = "admin-route",
ClusterId = "admin",
Match = new() { Path = "/api/admin/{**catch-all}" }
},
new RouteConfig
{
RouteId = "mini-route",
ClusterId = "mini",
Match = new() { Path = "/api/mini/{**catch-all}" }
},
new RouteConfig
{
RouteId = "user-route",
ClusterId = "user",
Match = new() { Path = "/api/user/{**catch-all}" }
}
};
var clusters = new[]
{
new ClusterConfig
{
ClusterId = "admin",
Destinations = new Dictionary<string, DestinationConfig>
{
["d1"] = new() { Address = "http://localhost:5001/" }
}
},
new ClusterConfig
{
ClusterId = "mini",
Destinations = new Dictionary<string, DestinationConfig>
{
["d1"] = new() { Address = "http://localhost:5002/" }
}
},
new ClusterConfig
{
ClusterId = "user",
Destinations = new Dictionary<string, DestinationConfig>
{
["d1"] = new() { Address = "http://localhost:5003/" }
}
}
};
builder.Services.AddReverseProxy()
.LoadFromMemory(routes, clusters);
.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.MapReverseProxy();
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();

View File

@@ -0,0 +1,38 @@
{
"ReverseProxy": {
"Routes": [
{
"RouteId": "admin-route",
"ClusterId": "admin",
"Match": { "Path": "/api/admin/{**catch-all}" }
},
{
"RouteId": "mini-route",
"ClusterId": "mini",
"Match": { "Path": "/api/mini/{**catch-all}" }
},
{
"RouteId": "user-route",
"ClusterId": "user",
"Match": { "Path": "/api/user/{**catch-all}" }
}
],
"Clusters": {
"admin": {
"Destinations": {
"d1": { "Address": "http://localhost:5001/" }
}
},
"mini": {
"Destinations": {
"d1": { "Address": "http://localhost:5002/" }
}
},
"user": {
"Destinations": {
"d1": { "Address": "http://localhost:5003/" }
}
}
}
}
}