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(configuration)
        .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应用程序的启动配置代码。让我为你详细解释各个部分:

主要功能概述

这是一个企业级的Web API项目,集成了多种中间件和服务,包括配置中心、日志记录、身份认证、限流、缓存等功能。

代码结构分析

1. 初始化和配置

const string originsName = "Open";
Log.Logger = new LoggerConfiguration().Enrich.FromLogContext().CreateBootstrapLogger();
  • 定义CORS策略名称
  • 初始化Serilog日志记录器

2. 配置中心集成

builder.Host.UseAgileConfig(new ConfigClient(builder.Configuration));
  • 使用AgileConfig作为配置中心,实现配置的集中管理

3. 开发环境特殊处理

if (builder.Environment.IsDevelopment())
{
    builder.Configuration.AddJsonFile("appsettings.Development.json", optional: true, reloadOnChange: true);
}
  • 开发环境下重新加载本地配置文件,可覆盖配置中心的设置

4. 核心服务配置

反向代理支持

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;
    });
});
  • 配置名为"comment"的固定窗口限流器
  • 每分钟最多3个请求,队列最多2个请求

控制器和序列化

services.AddControllers()
    .AddMessagePackSerializerFormatters()
    .AddJsonOptions(opts =>
    {
        opts.JsonSerializerOptions.Converters.Add(new TimeSpanConverter());
        opts.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
    });
  • 添加MessagePack序列化支持
  • 配置JSON序列化选项,支持TimeSpan和枚举转换

响应压缩

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",
    ]);
});
  • 支持Brotli和Gzip压缩
  • 扩展了默认的MIME类型支持

OpenAPI文档

services.AddOpenApi(options =>
{
    options.AddDocumentTransformer(async (doc, _, cancelToken) =>
    {
        // 读取README.md作为API描述
        // 配置JWT Bearer认证
        // 设置版本信息
    });
});
  • 自动生成API文档
  • 集成JWT Bearer认证方案
  • 从README.md读取API描述

身份认证和授权

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.Authority = issuer;
        options.Audience = "resource_server";
        options.RequireHttpsMetadata = true;
    });
  • 使用JWT Bearer认证
  • 配置OAuth2/OpenID Connect

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();
    });
});
  • 从配置中读取允许的源地址
  • 支持常见的HTTP方法

5. 中间件管道配置

按顺序配置了以下中间件:

  1. 开发环境异常页面
  2. 转发头处理
  3. Serilog请求日志记录
  4. 响应头、请求记录、日期时间格式化
  5. 响应压缩
  6. OpenAPI和Scalar文档
  7. 路由
  8. CORS
  9. 身份认证和授权
  10. 控制器映射

6. 简单的健康检查端点

app.MapGet("/ping", () => "pong");
app.MapGet("/datetime", () => $"{DateTime.Now:yyyy/Mm/dd HH:mm:ss}\r\n{Environment.MachineName}");

7. 常量定义

public partial class Program
{
    public const string AuthorizeCookieName = "Dpz.Web.Api.Server.Authoriza";
    public const string AccountCookieName = "Dpz.Web.Api.Server.Account";
}
  • 定义了认证和账户相关的Cookie名称

总结

这是一个功能完整的企业级Web API项目模板,具备:

  • 配置中心集成
  • 结构化日志记录
  • API文档自动生成
  • 身份认证授权
  • 请求限流
  • 响应压缩
  • CORS支持
  • 缓存机制
  • 消息队列集成(Hangfire)

代码结构清晰,遵循了ASP.NET Core的最佳实践。

评论加载中...