using System.Text.Json.Serialization;
using System.Threading.RateLimiting;
using AgileConfig.Client;
using Dpz.Core.Hangfire;
using Dpz.Core.Infrastructure.Configuration;
using Dpz.Core.WebApi.Security;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.OpenApi;
using Scalar.AspNetCore;
const string originsName = "Open";
Log.Logger = new LoggerConfiguration().Enrich.FromLogContext().CreateBootstrapLogger();
try
{
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseAgileConfig(new ConfigClient(builder.Configuration));
// 开发环境下重新加载本地配置,覆盖配置中心的特定节点
if (builder.Environment.IsDevelopment())
{
builder.Configuration.AddJsonFile(
"appsettings.Development.json",
optional: true,
reloadOnChange: true
);
}
var configuration = builder.Configuration;
var seq = configuration.GetSection("LogSeq").Get<LogSeq>();
builder.Host.ConfigurationLog(seq);
#region services
var services = builder.Services;
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
options.KnownIPNetworks.Clear();
options.KnownProxies.Clear();
});
services.AddRateLimiter(options =>
{
options.AddFixedWindowLimiter(
"comment",
opt =>
{
opt.AutoReplenishment = true;
opt.PermitLimit = 3;
opt.Window = TimeSpan.FromMinutes(1);
opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
opt.QueueLimit = 2;
}
);
});
services
.AddControllers()
// .AddXmlDataContractSerializerFormatters()
//.AddXmlSerializerFormatters()
.AddMessagePackSerializerFormatters()
.AddJsonOptions(opts =>
{
opts.JsonSerializerOptions.Converters.Add(new TimeSpanConverter());
opts.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
});
services.AddResponseCompression(options =>
{
options.Providers.Add<BrotliCompressionProvider>();
options.Providers.Add<GzipCompressionProvider>();
options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat([
"application/font-woff2",
"image/svg+xml",
"text/plain",
"application/lrc",
]);
});
services.Configure<BrotliCompressionProviderOptions>(options =>
{
options.Level = CompressionLevel.Optimal;
});
services.Configure<GzipCompressionProviderOptions>(options =>
{
options.Level = CompressionLevel.Optimal;
});
services.Configure<KestrelServerOptions>(options =>
{
options.Limits.MaxRequestBodySize = int.MaxValue;
});
services.AddEndpointsApiExplorer();
services.AddOutputCache(options =>
{
options.AddBasePolicy(policy => policy.Expire(TimeSpan.FromMinutes(10)));
});
services.AddOpenApi(options =>
{
options.AddDocumentTransformer(
async (doc, _, cancelToken) =>
{
var basePath = Path.GetDirectoryName(typeof(Program).Assembly.Location);
var description = "";
if (!string.IsNullOrEmpty(basePath))
{
var readmePath = Path.Combine(basePath, "README.md");
if (File.Exists(readmePath))
{
description = await File.ReadAllTextAsync(readmePath, cancelToken);
}
}
doc.Info.Version = Assembly.GetExecutingAssembly().GetName().Version?.ToString();
doc.Info.Title = "Dpz.Core.WebApi | v" + doc.Info.Version;
doc.Info.Description = description;
doc.Components ??= new OpenApiComponents();
doc.Components.SecuritySchemes ??= new Dictionary<string, IOpenApiSecurityScheme>();
doc.Components.SecuritySchemes.TryAdd(
"Bearer",
new OpenApiSecurityScheme
{
Description = "使用Bearer方案的JWT授权标头(示例:“ Bearer JWT_Token”)",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.OAuth2,
Scheme = "Bearer",
BearerFormat = "JWT",
}
);
doc.Security ??= new List<OpenApiSecurityRequirement>();
doc.Security.Add(
new OpenApiSecurityRequirement
{
{
new OpenApiSecuritySchemeReference("Bearer")
{
Reference = new OpenApiReferenceWithDescription
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer",
},
},
[]
},
}
);
}
);
});
// IoC
services.AddBusinessServices(configuration);
services.AddDefaultServices().AddProjectServices().AddMessageQueueServices(configuration);
// Cors
services.AddCors(options =>
{
options.AddPolicy(
originsName,
cfg =>
{
var origins = configuration.GetSection("Origins").Get<string[]>() ?? [];
cfg.WithOrigins(origins)
.WithMethods("GET", "PUT", "POST", "DELETE", "PATCH")
.AllowAnyHeader();
}
);
});
var issuer = configuration["Server:Issuer"] ?? throw new InvalidConfigurationException();
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = issuer;
options.Audience = "resource_server";
options.RequireHttpsMetadata = true;
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
Log.Error(context.Exception, "Failed to authenticate");
return Task.CompletedTask;
},
};
});
services.AddPermissionAuthorization();
services.AddRazorPages();
var webApiHangfireCollectionPrefix =
configuration["WebApiHangfireCollectionPrefix"]
?? throw new InvalidConfigurationException();
services.AddHangfireService(configuration, webApiHangfireCollectionPrefix);
#endregion
var app = builder.Build();
#region configuration
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseForwardedHeaders();
app.UseSerilogRequestLogging(options =>
{
options.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
{
diagnosticContext.Set("RequestHost", httpContext.Request.Host.Value);
diagnosticContext.Set("RequestScheme", httpContext.Request.Scheme);
diagnosticContext.Set(
"UserAgent",
httpContext.Request.Headers["User-Agent"].ToString()
);
diagnosticContext.Set("IpAddress", httpContext.Request.GetIpAddress());
};
});
app.UseResponseHeaders().UseRequestRecord().UseDateTimeLocalFormat();
app.UseResponseCompression();
app.MapOpenApi().CacheOutput();
app.MapScalarApiReference(options =>
{
options.Title = "Dpz.Core.WebApi";
options.Favicon = "https://dpangzi.com/favicon.ico";
options.Theme = ScalarTheme.Saturn;
options.HideDarkModeToggle = false;
options.HideClientButton = true;
});
app.GetHostingEnvironment();
// //强制HTTPS
// app.UseHttpsRedirection();
app.UseRouting();
app.MapRazorPages();
app.UseCors(originsName);
//身份认证
app.UseAuthentication();
//身份授权
app.UseAuthorization();
app.MapControllers();
//app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
#if DEBUG
app.HangfireSetting();
#endif
#endregion
app.MapGet("/ping", () => "pong");
app.MapGet(
"/datetime",
() => $"{DateTime.Now:yyyy/Mm/dd HH:mm:ss}\r\n{Environment.MachineName}"
);
Log.Information("Starting web api host,current process id:{ID}", Environment.ProcessId);
app.Run();
}
catch (Exception e)
{
Console.Error.WriteLine(e);
Log.Fatal(e, "Host terminated unexpectedly");
}
/// <summary>
///
/// </summary>
public partial class Program
{
/// <summary>
/// 身份认证Cookie名称
/// </summary>
public const string AuthorizeCookieName = "Dpz.Web.Api.Server.Authoriza";
/// <summary>
/// 账号Cookie名称
/// </summary>
public const string AccountCookieName = "Dpz.Web.Api.Server.Account";
}
⚠⚠ 以下内容为AI分析的结果,请根据实际情况进行判断。
代码解释
这是一个基于 ASP.NET Core 的 Web API 项目的启动配置文件(Program.cs),采用了 .NET 6+ 的最小托管模型。以下是详细解释:
主要功能模块
1. 日志初始化
Log.Logger = new LoggerConfiguration().Enrich.FromLogContext().CreateBootstrapLogger();
使用 Serilog 创建启动日志记录器,用于记录应用程序启动阶段的日志。
2. 配置加载
builder.Host.UseAgileConfig(new ConfigClient(builder.Configuration));
- 集成 AgileConfig 配置中心,动态加载远程配置
- 开发环境下会重新加载本地
appsettings.Development.json,覆盖远程配置的特定节点
3. 核心服务注册
请求头转发配置
services.Configure<ForwardedHeadersOptions>(...)
配置反向代理头信息转发(用于获取真实客户端 IP 和协议)。
限流器
services.AddRateLimiter(...)
为 "comment" 端点添加固定窗口限流:
- 每分钟最多 3 次请求
- 队列最多容纳 2 个请求
控制器配置
services.AddControllers()
.AddMessagePackSerializerFormatters() // 支持 MessagePack 序列化
.AddJsonOptions(...) // 配置 JSON 序列化(枚举转字符串、TimeSpan 转换器)
响应压缩
services.AddResponseCompression(...)
启用 Brotli 和 Gzip 压缩,优化网络传输。
4. API 文档配置
services.AddOpenApi(...)
- 配置 OpenAPI(原 Swagger)文档
- 从
README.md读取 API 描述 - 添加 JWT Bearer 认证配置
- 使用 Scalar 作为 API 文档 UI(替代 Swagger UI)
5. 依赖注入
services.AddBusinessServices(configuration);
services.AddDefaultServices().AddProjectServices().AddMessageQueueServices(configuration);
注册业务服务、默认服务、项目服务和消息队列服务(具体实现在扩展方法中)。
6. CORS 配置
services.AddCors(...)
从配置文件读取允许的跨域来源,支持指定的 HTTP 方法。
7. JWT 身份认证
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(...)
- 使用 JWT Bearer 认证
- 从配置中读取授权服务器地址(Issuer)
- 配置受众为
resource_server - 添加认证失败的日志记录
8. Hangfire 后台任务
services.AddHangfireService(configuration, webApiHangfireCollectionPrefix);
集成 Hangfire 作为后台任务调度框架。
中间件管道配置
请求处理流程
app.UseForwardedHeaders(); // 处理代理头
app.UseSerilogRequestLogging(...); // 请求日志记录
app.UseResponseHeaders() // 自定义响应头
.UseRequestRecord() // 请求记录
.UseDateTimeLocalFormat(); // 时间格式本地化
app.UseResponseCompression(); // 响应压缩
app.UseCors(originsName); // 跨域
app.UseAuthentication(); // 身份认证
app.UseAuthorization(); // 身份授权
API 文档端点
app.MapOpenApi().CacheOutput(); // OpenAPI JSON 端点(带缓存)
app.MapScalarApiReference(...); // Scalar UI 界面
健康检查端点
app.MapGet("/ping", () => "pong");
app.MapGet("/datetime", () => $"{DateTime.Now:yyyy/Mm/dd HH:mm:ss}\r\n{Environment.MachineName}");
简单的服务可用性检查和时间查询接口。
特殊功能
调试模式下的 Hangfire 配置
#if DEBUG
app.HangfireSetting();
#endif
仅在 DEBUG 模式下启用 Hangfire 仪表板。
Program 类定义
public partial class Program
{
public const string AuthorizeCookieName = "Dpz.Web.Api.Server.Authoriza";
public const string AccountCookieName = "Dpz.Web.Api.Server.Account";
}
定义部分类,用于测试或扩展(常见于集成测试中使用 WebApplicationFactory<Program>)。
异常处理
catch (Exception e)
{
Console.Error.WriteLine(e);
Log.Fatal(e, "Host terminated unexpectedly");
}
捕获启动阶段的致命异常并记录。
技术栈总结
- 配置中心:AgileConfig
- 日志:Serilog
- 认证:JWT Bearer
- API 文档:OpenAPI + Scalar
- 后台任务:Hangfire
- 序列化:MessagePack + JSON
- 限流:ASP.NET Core Rate Limiting
- 压缩:Brotli + Gzip
这是一个生产级的现代 Web API 架构,具有完善的监控、限流、认证和文档功能。
评论加载中...