using Autofac; using Autofac.Extensions.DependencyInjection; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.OpenApi.Models; using System; using System.IO; using System.Text; using System.Threading.Tasks; using NPlatform.Infrastructure.Config; using NPlatform.Middleware; using NPlatform.API; using NPlatform.DI; using NPlatform.Repositories; using Newtonsoft.Json; using System.Text.Json.Serialization; using SqlSugar; using IGeekFan.AspNetCore.Knife4jUI; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using IdentityServer4.Configuration; using System.Security.Cryptography.X509Certificates; using BZPT.Repositories; using Microsoft.Extensions.DependencyInjection.Extensions; using NPlatform.Infrastructure.Config.Section; using ServiceStack; using Microsoft.IdentityModel.Tokens; using Hangfire; using Hangfire.Redis.StackExchange; using Hangfire.Dashboard.BasicAuthorization; using BZPT.Domains.Application; using System.Security.Claims; using NPlatform.Infrastructure.IdGenerators; using NPOI.SS.Formula.Functions; using Microsoft.AspNetCore.DataProtection; using StackExchange.Redis; using BZPT.Api.Middleware; using Microsoft.AspNetCore.Server.Kestrel.Core; using Newtonsoft.Json.Linq; using Serilog.Context; using Serilog; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.Extensions.FileProviders; using BZPT.Domains.IRepositories; using BZPT.Domains.IService.Sys; using BZPT.Api; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.HttpOverrides; var builder = WebApplication.CreateBuilder(args); var serviceConfig = builder.Configuration.GetServiceConfig(); builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()); // 替换控制器激活器 builder.Services.Replace(ServiceDescriptor.Scoped()); // 健康检查 builder.Services.AddHealthChecks().AddCheck(serviceConfig.ServiceName); // 内存缓存 builder.Services.AddMemoryCache(); // Autofac 配置 builder.Services.AddAutofac(); var redisConfig = builder.Configuration.GetRedisConfig(); //从配置获取 Redis 连接字符串 var redisConn = $"{redisConfig.Connections?.FirstOrDefault()},password={redisConfig.Password}"; Console.WriteLine("redis 连接:" + redisConn); // 配置 Hangfire builder.Services.AddHangfire(x => { x.SetDataCompatibilityLevel(CompatibilityLevel.Version_170) .UseSimpleAssemblyNameTypeSerializer() .UseRecommendedSerializerSettings() .UseRedisStorage(redisConn, new RedisStorageOptions { Db = 11, Prefix = "Hangfire:" }); }); builder.Services.AddHangfireServer(); // 连接到 Redis var connectionMultiplexer = ConnectionMultiplexer.Connect(redisConn); // 注册 IConnectionMultiplexer builder.Services.AddSingleton(connectionMultiplexer); // 注册 IDatabase builder.Services.AddScoped(provider => { var connection = provider.GetRequiredService(); return connection.GetDatabase(); // 返回 IDatabase 实例 }); // 主机配置 var repositoryOptions = new RepositoryOptions(); builder.Host.Configure(builder.Configuration, repositoryOptions); // 数据库上下文配置 var dbHost = builder.Configuration["DB_HOST"]; var dbPort = builder.Configuration["DB_PORT"]; var dbName = builder.Configuration["DB_NAME"]; var logDbName = builder.Configuration["LOG_DB_NAME"]; var dbUser = builder.Configuration["DB_USER"]; var dbPass = builder.Configuration["DB_PASSWORD"]; builder.UseMySerilog($"Server={dbHost};Port={dbPort};DATABASE={logDbName};User Id={dbUser};PWD={dbPass};", serviceConfig.ServiceName); //// 配置日志 //var log = builder.Logging.AddLog4Net(); //log.AddConsole(); //log.AddDebug(); //log.SetMinimumLevel(LogLevel.Debug); builder.Services.AddScoped(serviceProvider => { return new DBContext($"Server={dbHost};Port={dbPort};DATABASE={dbName};User Id={dbUser};PWD={dbPass};", DbType.Dm, ConfigId: "default"); }); builder.Services.AddScoped(); // 控制器配置 builder.Services.AddControllersWithViews(mvcOptions => { mvcOptions.Filters.Remove(mvcOptions.Filters.OfType().FirstOrDefault()); }).AddJsonOptions(options => { //options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; //options.JsonSerializerOptions.PropertyNameCaseInsensitive = false; // 确保区分大小写 options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; options.JsonSerializerOptions.Converters.Add(new DateTimeConverter("yyyy-MM-dd HH:mm:ss")); }); // 从 appsettings.json 加载表单限制 builder.Services.Configure(options => { options.MultipartBodyLengthLimit = builder.Configuration.GetValue("FormOptions:MultipartBodyLengthLimit"); }); // API 行为配置 builder.Services.Configure(options => { options.InvalidModelStateResponseFactory = actionContext => { var errors = actionContext.ModelState?.Where(e => e.Value.Errors.Count > 0); var strError = new StringBuilder(); foreach (var error in errors) { var msg = error.Value.Errors.FirstOrDefault()?.ErrorMessage; if (!string.IsNullOrWhiteSpace(msg)) strError.Append(msg); } return new FailResult("错误:" + strError.ToString()); }; }); // IdentityServer4 配置 var serviceProvider = builder.Services.BuildServiceProvider(); var dbContext = serviceProvider.GetService(); var keyPath = Path.Combine(Environment.CurrentDirectory, builder.Configuration["CERT_PATH"] ?? ""); Console.WriteLine($"Certificate path: {keyPath}, Exists: {File.Exists(keyPath)}"); // CORS 配置 builder.Services.AddCors(options => options.AddPolicy("CorsMy", policy => { // 允许特定的源访问 policy.WithOrigins("*") .AllowAnyHeader() .AllowAnyMethod(); })); if (!string.IsNullOrEmpty(keyPath) && File.Exists(keyPath)) { builder.WebHost.ConfigureKestrel(serverOptions => { serverOptions.ConfigureHttpsDefaults(httpsOptions => { httpsOptions.ServerCertificate = new X509Certificate2(keyPath, builder.Configuration["CERT_PASSWORD"]); }); }); } else { Console.WriteLine("⚠️ 证书未找到或环境变量未设置,HTTPS 可能无法正常工作!"); } Console.WriteLine($"AuthorityServer 配置: {builder.Configuration["AuthorityServer"]}"); // 配置 Cookie 认证 builder.Services.AddAuthentication(options => { options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultSignOutScheme = CookieAuthenticationDefaults.AuthenticationScheme; }) .AddJwtBearer(options => { options.Authority = builder.Configuration["AuthorityServer"]; options.Audience = serviceConfig.ServiceID; // 受众 #if DEBUG options.RequireHttpsMetadata = false; // 开发环境允许 HTTP #else options.RequireHttpsMetadata = true; // 生产环境强制 HTTPS #endif options.TokenValidationParameters = new TokenValidationParameters { ValidateAudience = true, // 验证 Audience ValidAudiences =new string[] { serviceConfig.ServiceID }, // 或者 "api.BZPT",取决于你的 API 需要验证哪个 Audience ValidateIssuer = true, // 验证 Issuer ValidIssuer = builder.Configuration["AuthorityServer"], // 确保与 IdentityServer4 的 Issuer 配置一致 ValidateLifetime = true, ClockSkew = TimeSpan.Zero // 生产环境建议设为小值或 TimeSpan.Zero,防止 Token 刚签发就被认为过期 }; options.BackchannelHttpHandler = new HttpClientHandler { ServerCertificateCustomValidationCallback = (message, cert, chain, sslPolicyErrors) => true // 忽略 SSL 错误 }; options.Events = new JwtBearerEvents { OnAuthenticationFailed = context => { Console.WriteLine($"{serviceConfig.ServiceID}|{builder.Configuration["AuthorityServer"]} "); // 记录身份验证失败的详细信息 Console.WriteLine($"Authentication failed:{context.Exception.Message}"); return Task.CompletedTask; }, OnTokenValidated = context => { // 记录 token 验证成功的信息 Console.WriteLine("Token validated successfully."); ////打印 Claims 信息以便调试 if (context.Principal != null) { Console.WriteLine($"Authenticated User Name: {context.Principal.Identity?.Name}"); foreach (var claim in context.Principal.Claims) { Console.WriteLine($" Claim Type: {claim.Type}, Value: {claim.Value}"); } } return Task.CompletedTask; }, OnMessageReceived = context => { //Console.WriteLine("--- JwtBearer OnMessageReceived triggered ---"); if (context.Request.Headers.ContainsKey("Authorization")) { Console.WriteLine($"Raw Authorization Header: {context.Request.Headers["Authorization"]}"); } // 这里的 context.Token 应该已经由 JwtBearerHandler 填充,除非有其他中间件干扰 Console.WriteLine($"Received token (JwtBearer): {context.Token ?? "NULL"}"); // 确保打印 NULL Console.WriteLine("--- End JwtBearer OnMessageReceived ---"); return Task.CompletedTask; }, OnChallenge = context => { Console.WriteLine("JwtBearer Challenge triggered."); Console.WriteLine($"Challenge Scheme: {context.Scheme.Name}"); Console.WriteLine($"Challenge Status Code: {context.Response.StatusCode}"); if (!string.IsNullOrEmpty(context.Error)) Console.WriteLine($"Challenge Error: {context.Error}"); if (!string.IsNullOrEmpty(context.ErrorDescription)) Console.WriteLine($"Challenge Error Description: {context.ErrorDescription}"); //全自定义 401 响应,可以在这里处理 // context.HandleResponse(); // 阻止默认的 401 行为 // context.Response.StatusCode = StatusCodes.Status401Unauthorized; // context.Response.ContentType = "application/json"; // return context.Response.WriteAsync(JsonSerializer.Serialize(new { message = "Unauthorized", detail = context.ErrorDescription })); return Task.CompletedTask; } }; }); // Swagger 配置 builder.Services.AddSwaggerGen(c => { c.SwaggerDoc(serviceConfig.ServiceName, new OpenApiInfo { Title = $"{serviceConfig.ServiceName} 接口文档", Version = serviceConfig.ServiceVersion }); c.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, "BZPT.Api.xml"), true); c.AddSecurityRequirement(new OpenApiSecurityRequirement { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" } , Scheme = "oauth2", Name = "Bearer", In = ParameterLocation.Header, }, new List() } }); // 定义 OAuth2 Client Credentials 安全方案 c.AddSecurityDefinition("OAuth2", new OpenApiSecurityScheme { Type = SecuritySchemeType.OAuth2, Flows = new OpenApiOAuthFlows { ClientCredentials = new OpenApiOAuthFlow { TokenUrl = new Uri($"{builder.Configuration["AuthorityServer"]}/connect/token"), // IdentityServer4 令牌端点 Scopes = new Dictionary { { "api_scope", "访问 API 的权限" } } } } }); c.AddServer(new OpenApiServer { Url = "", Description = "vvv" }); c.CustomOperationIds(apiDesc => (apiDesc.ActionDescriptor as ControllerActionDescriptor)?.ControllerName + "-" + (apiDesc.ActionDescriptor as ControllerActionDescriptor)?.ActionName); }); var redis = ConnectionMultiplexer.Connect(redisConn); builder.Services.AddDataProtection() .PersistKeysToStackExchangeRedis(redis, "DataProtection-Keys") .SetApplicationName(serviceConfig.ServiceID); builder.Services.AddAntiforgery(options => { // 可以在这里配置防伪令牌的相关选项 options.HeaderName = "X-CSRF-TOKEN"; }); //将 Serilog 注册到默认日志工厂(非必须,但更兼容) builder.Logging.ClearProviders(); // 可选:移除默认日志提供程序(如不需要控制台重复输出) builder.Logging.AddSerilog(dispose: true); // 桥接 Microsoft.Extensions.Logging 到 Serilog // 应用构建 var app = builder.Build(); // 并且在 UseAuthentication() 和 UseAuthorization() 之前。 app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost // 注意:通常不需要 ForwardedHeaders.XForwardedPort,因为 X-Forwarded-Host 包含主机和端口 // 但如果您的反向代理只设置了 X-Forwarded-Port,则可能需要添加 // !!!重要:如果您的反向代理是内部的,并且其IP是固定的,建议指定 KnownProxies 或 KnownNetworks, // 以防止伪造的 X-Forwarded-* 头。如果这是在容器内部,且代理是 Ingress Controller 或 Docker 自己的网络, // 通常可以安全地跳过 KnownProxies/Networks,但在生产环境中要小心。 // KnownProxies = { IPAddress.Parse("YOUR_PROXY_IP_HERE") } // KnownNetworks = { new IPNetwork(IPAddress.Parse("YOUR_PROXY_NETWORK_START_IP"), YOUR_PROXY_NETWORK_CIDR) } }); // 2. 配置 ASP.NET Core 内置异常页(开发环境) if (app.Environment.IsDevelopment()) { } else { app.UseHsts(); } // 1. 中间件捕获请求管道中的异常 app.Use(async (context, next) => { try { #if !DEBUG if (DateTime.Now.Hour >= 23 || DateTime.Now.Hour < 7) { context.Response.StatusCode = 400; await context.Response.WriteAsync("系统维护中~"); return; } #endif // 生成唯一ID(GUID 或 Snowflake ID) var requestId = StringObjectIdGenerator.Instance.GenerateId().ToString(); // 注入请求头 context.Request.Headers["X-Request-ID"] = requestId; // 存储到 HttpContext.Items 供后续使用 context.Items["X-Request-ID"] = requestId; // 将 ID 添加到日志上下文 using (LogContext.PushProperty("RequestID", requestId)) { await next(); } // 处理响应状态码 if (context.Response.StatusCode == StatusCodes.Status401Unauthorized) { context.Response.ContentType = "application/json"; await context.Response.WriteAsync(JsonConvert.SerializeObject(new { code = 401, message = "未授权,请登录" })); } } catch (Exception ex) { LogContext.PushProperty("ErrorCode", 500); Serilog.Log.Fatal(ex, "全局捕获的未处理异常(中间件)"); throw; // 重新抛出以触发内置错误页面 } }); // 3. 捕获非 HTTP 管道的全局异常 AppDomain.CurrentDomain.UnhandledException += (sender, e) => { var exception = e.ExceptionObject as Exception; LogContext.PushProperty("ErrorCode", 500); Serilog.Log.Fatal(exception, "全局捕获的非 HTTP 未处理异常"); }; TaskScheduler.UnobservedTaskException += (sender, e) => { LogContext.PushProperty("ErrorCode", 500); Serilog.Log.Fatal(e.Exception, "全局捕获的未观察到的任务异常"); e.SetObserved(); // 标记异常已处理 }; app.UseSwagger(); app.UseKnife4UI(c => { c.RoutePrefix = "swagger"; c.SwaggerEndpoint($"/{serviceConfig.ServiceName}/swagger.json", serviceConfig.ServiceName); }); app.UseHealthChecks("/healthChecks"); app.UseDefaultFiles(new DefaultFilesOptions { DefaultFileNames = new List { "index.html" } }); app.UseAntiforgery(); app.UseRouting(); app.UseCors("CorsMy"); app.UseAuthentication(); app.UseAuthorization(); //app.UseMiddleware(); // OPTIONS 请求处理 app.MapMethods("/{**path}", new[] { "OPTIONS" }, () => Results.NoContent()).RequireCors("CorsMy"); try { app.UseIdentityServer(); } catch (Exception ex) { Console.WriteLine($"IdentityServer 中间件加载失败: {ex}"); } app.MapControllers(); app.Run();