using System.Threading.RateLimiting;
using Dpz.Core.Infrastructure.RateLimiting;
using Dpz.Core.MessageQueue.Extensions;
using Dpz.Core.Public.ViewModel.Messages;
using Dpz.Core.Web.EventHandlers;
using Microsoft.AspNetCore.DataProtection.KeyManagement;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.Extensions.Primitives;
using OpenIddict.Client.AspNetCore;
using Serilog.Events;
using StackExchange.Redis;

const string corsScheme = "Open-Dpz-Core";

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;
    LibraryHost =
        configuration["LibraryHost"]
        ?? throw new InvalidConfigurationException(
            "configuration node LibraryHost is null or empty"
        );
    AssetsHost =
        configuration["AssetsHost"]
        ?? throw new InvalidConfigurationException(
            "configuration node AssetsHost is null or empty"
        );

    var logSeq = configuration.GetSection("LogSeq").Get<LogSeq>();

    // ReSharper disable once RedundantAssignment
    Func<LogEvent, bool>? filter = null;
#if DEBUG
    filter = x =>
    {
        if (
            !x.Properties.TryGetValue("RequestPath", out var requestPath)
            || requestPath is not ScalarValue pathValue
        )
        {
            return false;
        }

        var pathPrefixes = new[] { "/js", "/css" };
        var path = pathValue.Value?.ToString();
        return path != null
            && pathPrefixes.Any(p => path.StartsWith(p, StringComparison.OrdinalIgnoreCase));
    };
#endif
    builder.Host.ConfigurationLog(logSeq, filter);

    builder
        .WebHost.UseKestrel(
            (_, option) =>
            {
                option.AddServerHeader = false;
                option.ConfigureHttpsDefaults(x =>
                    x.SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13
                );
            }
        )
        .UseIIS()
        .UseIISIntegration();

    #region services

    var services = builder.Services;

    services.Configure<ForwardedHeadersOptions>(options =>
    {
        options.ForwardedHeaders =
            ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
        options.KnownIPNetworks.Clear();
        options.KnownProxies.Clear();
    });

    services.AddMvc(x =>
    {
        x.Filters.Add(typeof(GlobalFilter));
        x.Filters.Add(new TypeFilterAttribute(typeof(ExceptionHandleAttribute)));
    });
    //压缩
    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",
        ]);
        options.EnableForHttps = true;
    });

    services.Configure<BrotliCompressionProviderOptions>(options =>
    {
        options.Level = CompressionLevel.Optimal;
    });

    services.Configure<GzipCompressionProviderOptions>(options =>
    {
        options.Level = CompressionLevel.Optimal;
    });

#if DEBUG
    services.Configure<KestrelServerOptions>(options =>
    {
        options.Limits.MaxRequestBodySize = int.MaxValue;
    });
#endif
    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.AddDefaultServices(configuration).AddInject();

    // 注册消息消费者
    services.AddMessageConsumer<ClearCacheMessage, ClearCacheMessageHandler>();

    services
        .AddWebMarkupMin(x =>
        {
            x.AllowMinificationInDevelopmentEnvironment = true;
            x.AllowCompressionInDevelopmentEnvironment = true;
        })
        .AddHtmlMinification(x =>
        {
            var settings = x.MinificationSettings;
            settings.RemoveOptionalEndTags = false;
        })
        .AddXmlMinification()
        .AddHttpCompression(option =>
        {
            option.CompressorFactories = new List<ICompressorFactory>
            {
                new BuiltInBrotliCompressorFactory(
                    new BuiltInBrotliCompressionSettings { Level = CompressionLevel.Optimal }
                ),
                new DeflateCompressorFactory(
                    new DeflateCompressionSettings { Level = CompressionLevel.Optimal }
                ),
                new GZipCompressorFactory(
                    new GZipCompressionSettings { Level = CompressionLevel.Optimal }
                ),
            };
        });

    // 响应式服务
    services.AddResponsive();
    // 设备识别
    services.AddDetection();
    // .AddDevice().AddBrowser()
    // .AddPlatform()
    // .AddEngine()
    // .AddCrawler();

    //注册 响应服务
    services.AddResponsive();

    //services.AddHostedService<TimedHostedService>();
    services.AddControllersWithViews();

    //注册 缓存中间件
    services.AddResponseCaching();

    var redisConnectionString =
        configuration.GetConnectionString("redis")
        ?? throw new Exception("configuration node redis is null or empty");
    services.AddBusinessServices(configuration);

    // 注册限流服务
    services.AddIpRateLimit();

    // Cors
    services.AddCors(options =>
    {
        options.AddPolicy(
            corsScheme,
            cfg =>
            {
                cfg.WithOrigins(configuration.GetSection("Origins").Get<string[]>() ?? [])
                    .WithMethods("GET", "PUT", "POST", "DELETE", "PATCH", "OPTIONS")
                    .AllowAnyHeader();
            }
        );
    });

    //注册 身份认证服务
    services
        .AddAuthentication(x =>
        {
            x.DefaultAuthenticateScheme = AuthorizeCookieName;
            x.DefaultSignInScheme = AuthorizeCookieName;
            x.DefaultChallengeScheme = OpenIddictClientAspNetCoreDefaults.AuthenticationScheme;
        })
        .AddCookie(
            AuthorizeCookieName,
            options =>
            {
                options.LoginPath = "/connect/oidc";
                options.AccessDeniedPath = "/connect/oidc";
                options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
                options.Cookie.HttpOnly = true;
                options.Cookie.SameSite = SameSiteMode.Lax;
            }
        );

    //services.AddAuthorization(x => )

    services
        .AddIdentityCore<VmUserInfo>(x =>
            x.ClaimsIdentity.SecurityStampClaimType = AuthorizeCookieName
        )
        .AddUserStore<UserStore>()
        .AddSignInManager<ApplicationSignInManager>();
    services.AddDataProtection().SetApplicationName(AuthorizeCookieName);
    services
        .AddOptions<KeyManagementOptions>()
        .Configure<IServiceScopeFactory>(
            (options, factory) =>
            {
                options.XmlRepository = new XmlRepositoryService(
                    factory,
                    AuthorizeCookieName + ".Key"
                );
            }
        );

    services.AddOpenIddictClient(configuration);

    //register SignalR
    services
        .AddSignalR(x =>
        {
            x.HandshakeTimeout = TimeSpan.FromMinutes(2);
            x.MaximumReceiveMessageSize = 32768;
        })
        .AddStackExchangeRedis(
            redisConnectionString,
            options =>
            {
                options.Configuration.ChannelPrefix = RedisChannel.Literal("Dpz.Core.signalR");
            }
        );

    // 添加Hangfire 服务
    services.HangfireService(configuration);

    #endregion


    var app = builder.Build();

    #region configuration

    if (app.Environment.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseRouteDebugger();
    }
    app.UseForwardedHeaders();

    var supportedCultures = new[] { new CultureInfo("zh-CN") };
    app.UseRequestLocalization(
        new RequestLocalizationOptions
        {
            DefaultRequestCulture = new RequestCulture("zh-CN"),
            // Formatting numbers, dates, etc.
            SupportedCultures = supportedCultures,
            // UI strings that we have localized.
            SupportedUICultures = supportedCultures,
        }
    );

    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.UserAgent.ToString());
            diagnosticContext.Set("IpAddress", httpContext.Request.GetIpAddress());

            var referer = httpContext.Request.Headers.Referer;
            if (!StringValues.IsNullOrEmpty(referer))
            {
                diagnosticContext.Set("Referer", referer);
            }
        };
    });

    //启用压缩
    app.UseResponseCompression();

    // 添加IP限流中间件
    app.UseIpRateLimit();

    // 拒绝爬虫
    app.UseRejectBots();

    // app.UseHsts();
    // app.UseHttpsRedirection();

    //启用静态文件
    app.UseStaticFiles(
        new StaticFileOptions()
        {
            OnPrepareResponse = ctx =>
            {
                // 如果访问静态资源的图片,那么设置缓存
                if (
                    ctx.Context.Response.ContentType?.StartsWith(
                        "image/",
                        StringComparison.CurrentCultureIgnoreCase
                    ) == true
                )
                {
                    ctx.Context.Response.Headers.Append("Cache-Control", "public,max-age=2592000");
                    ctx.Context.Response.Headers.Append(
                        "Expires",
                        DateTime.UtcNow.AddDays(30).ToString("R", CultureInfo.InvariantCulture)
                    );
                }
            },
        }
    );

    app.UseWebMarkupMin();

    //启用缓存中间件
    app.UseResponseCaching();

    //启用响应服务
    app.UseResponsive();

    app.UseCors(corsScheme);

    app.UseDetection();

    app.UseRouting().UseMiddleware<HttpResponseHeaderHandel>().UseRecordRequest();

    //启用身份认证
    app.UseAuthentication();
    //启用身份授权
    app.UseAuthorization();

    // 路由
    app.ConfigurationRoute();

    //系统初始化
    app.Init();

    // 使用 hangfire UI,及周期性任务调度
    app.HangfireSetting();

    app.MapGet(
        "/ping",
        requestDelegate: async context =>
        {
            await context.Response.WriteAsJsonAsync(context.Request.Headers);
        }
    );

    #endregion

    Log.Information("Starting web core host");

    app.Run();
}
catch (Exception ex)
{
    Console.Error.WriteLine(ex);
    Log.Fatal(ex, "Host terminated unexpectedly");
}

public partial class Program
{
    /// <summary>
    /// 身份认证Cookie名称
    /// </summary>
    public const string AuthorizeCookieName = "Dpz.Web.Core.Authoriza";

    /// <summary>
    /// library host
    /// </summary>
    public static string LibraryHost { get; private set; } = "";

    /// <summary>
    /// assets host
    /// </summary>
    public static string AssetsHost { get; private set; } = "";

    /// <summary>
    /// 版本号
    /// </summary>
    public static string Version =>
        Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "_version";

    /// <summary>
    /// 任务调度面板地址
    /// </summary>
    public const string HangfireDashboardPath = "/runtask";
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

这是一个使用 ASP.NET Core 构建的 Web 应用程序的启动配置文件(Program.cs)。让我详细解释一下代码的各个部分:

整体结构

这是一个现代化的 ASP.NET Core 应用程序,使用了最小 API 托管模型,包含了丰富的中间件和服务配置。

主要功能模块

1. 日志配置

Log.Logger = new LoggerConfiguration().Enrich.FromLogContext().CreateBootstrapLogger();

使用 Serilog 进行日志记录,支持结构化日志和 LogSeq 集成。

2. 配置管理

  • 使用 AgileConfig 作为配置中心
  • 支持开发环境本地配置覆盖
  • 验证必要的配置节点(LibraryHost、AssetsHost)

3. 服务注册(Services)

核心服务:

  • MVC 服务配置,包含全局过滤器
  • 响应压缩(Brotli 和 Gzip)
  • 限流服务(固定窗口限流器)
  • 依赖注入容器

Web 优化:

  • HTML/XML 压缩和最小化
  • 响应式设计支持
  • 设备检测功能
  • 响应缓存

安全与认证:

  • CORS 跨域配置
  • Cookie 身份认证
  • OpenIddict 客户端集成
  • 数据保护和密钥管理

实时通信:

  • SignalR 配置,使用 Redis 作为后端存储

后台任务:

  • Hangfire 任务调度服务

4. 中间件管道(Pipeline)

按执行顺序配置的中间件:

  1. 开发环境异常页面
  2. 转发头处理
  3. 本地化支持(中文)
  4. Serilog 请求日志记录
  5. 响应压缩
  6. IP 限流
  7. 反爬虫保护
  8. 静态文件服务(带缓存策略)
  9. 标记压缩
  10. 响应缓存
  11. CORS 处理
  12. 路由和自定义中间件
  13. 身份认证和授权

5. 特殊功能

缓存策略:

  • 图片静态资源设置 30 天缓存
  • 响应缓存中间件

安全措施:

  • IP 限流(评论功能每分钟 3 次)
  • 反爬虫中间件
  • HTTPS 强制和安全 Cookie

监控和调试:

  • 详细的请求日志记录(IP、User-Agent、Referer 等)
  • /ping 端点用于健康检查
  • Hangfire 仪表板

配置常量

程序定义了几个重要常量:

  • AuthorizeCookieName: 身份认证 Cookie 名称
  • LibraryHostAssetsHost: 外部资源主机地址
  • HangfireDashboardPath: 任务调度面板路径

总结

这是一个功能完整的企业级 Web 应用程序配置,包含了:

  • 完整的身份认证授权体系
  • 性能优化(压缩、缓存、CDN)
  • 安全防护(限流、反爬虫、CORS)
  • 可观测性(日志、监控)
  • 实时通信能力
  • 后台任务调度

代码结构清晰,遵循了 ASP.NET Core 的最佳实践,适合作为现代 Web 应用程序的起始模板。

评论加载中...