using Hangfire; using Hangfire.SqlServer; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Models; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using Quartz; using Swashbuckle.AspNetCore.Filters; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text; using TaskManager.Controllers; using TaskManager.EntityFramework; using Wood.Admin.WebApi.Filter; using Wood.Admin.WebApi.Middleware; using Wood.Util; namespace Wood.Admin.WebApi { /// /// 入口 /// public class Startup { /// /// 配置 /// public IConfiguration Configuration { get; } /// /// 构造函数 /// /// /// public Startup(IConfiguration configuration, IWebHostEnvironment env) { Configuration = configuration; GlobalContext.LogWhenStart(env); GlobalContext.HostingEnvironment = env; } /// /// This method gets called by the runtime. Use this method to add services to the container. /// /// public void ConfigureServices(IServiceCollection services) { services.AddHttpContextAccessor(); //初始化配置 GlobalContext.SystemConfig = Configuration.GetSection("SystemConfig").Get()!; GlobalContext.JwtConfig = Configuration.GetSection("JwtConfig").Get()!; GlobalContext.Services = services; GlobalContext.Configuration = Configuration; //初始化 eventbus services.AddEventBus(); //初始化数据库 services.AddSqlSugar(Configuration); services.AddHttpClient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); // 配置 DbContext 使用 SQL Server 连接字符串 services.AddDbContext(options => options.UseSqlServer(GlobalContext.SystemConfig.CustomerDb)); // 配置 Hangfire 使用 SQL Server 存储 services.AddHangfire( configuration => configuration .SetDataCompatibilityLevel(CompatibilityLevel.Version_170) // 建议显式设置兼容性版本 .UseSimpleAssemblyNameTypeSerializer() // 简化类型序列化(可选) .UseRecommendedSerializerSettings() // 使用推荐的序列化设置(可选) .UseSqlServerStorage(GlobalContext.SystemConfig.CustomerDb, new SqlServerStorageOptions { // 可从配置中读取 Hangfire 存储选项(如队列、重试策略等) CommandBatchMaxTimeout = TimeSpan.FromMinutes(5), SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5), QueuePollInterval = TimeSpan.Zero, UseRecommendedIsolationLevel = true, DisableGlobalLocks = true }) //.UseFilter(services.BuildServiceProvider().GetRequiredService>()) ); // 添加日志过滤器(可选) services.AddHangfireServer(options => { options.WorkerCount = 10; // 可选:配置队列优先级 //options.Queues = builder.Configuration.GetSection("Hangfire:ServerOptions:Queues").Get() ?? new[] { "default" }; }); //注册控制器 services.AddControllers(options => { //自动扫描注册控制器 options.Conventions.Add(new AutoRouteConvention()); //options.ModelMetadataDetailsProviders.Add(new ModelBindingMetadataProvider()); // 添加全局授权过滤器 options.Filters.Add(typeof(ApiAuthorizeFilter)); //options.Filters.Add(typeof(AuthorizeFilter)); //异常处理 options.Filters.Add(typeof(ApiExceptionFilter)); //性能记录,日志记录 options.Filters.Add(typeof(ApiPerformanceLoggingFilter)); //结果包装 options.Filters.Add(typeof(ApiResponseWrapperFilter)); }) .AddNewtonsoftJson(options => { // Json序列化设置 options.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Local; options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss"; // 时间格式化 options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; // 忽略循环引用 //options.SerializerSettings.ContractResolver = new DefaultContractResolver(); //对象属性名大写 options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); // 对象属性名小写 //options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore; // 忽略空值 options.SerializerSettings.Converters.Add(new LongToStringJsonConverter()); // long转string(防止js精度溢出) 超过16位开启 options.SerializerSettings.Converters.Add(new NullableLongToStringJsonConverter()); // long转string(防止js精度溢出) 超过16位开启 //options.SerializerSettings.MetadataPropertyHandling = MetadataPropertyHandling.Ignore; // 解决DateTimeOffset异常 //options.SerializerSettings.DateParseHandling = DateParseHandling.None; // 解决DateTimeOffset异常 //options.SerializerSettings.Converters.Add(new IsoDateTimeConverter { DateTimeStyles = System.Globalization.DateTimeStyles.AssumeUniversal }); // 解决DateTimeOffset异常 }); //添加跨域 services.AddCors(); //添加缓存支持 services.AddMemoryCache(); //初始化缓存 services.AddCache(); //添加 Magicodes.IE 导入导出支持 services.AddMagicodesIE(); //services.AddOptions(); //数据保护服务用于加密敏感数据,如身份验证 cookie 和抗请求伪造令牌。 //默认情况下,数据保护密钥存储在临时目录中,这可能导致在重启后丢失密钥,从而导致用户会话失效等问题 //通过 PersistKeysToFileSystem 方法,可以将密钥存储在一个指定的目录中,确保密钥在应用程序重启后仍然可用 //services.AddDataProtection().PersistKeysToFileSystem(new DirectoryInfo(GlobalContext.HostingEnvironment.ContentRootPath + Path.DirectorySeparatorChar + "DataProtection")); //默认情况下,.NET Core 不包含所有旧版 Windows 编码(如 GBK、GB2312 等),这些编码通常在处理某些旧系统或特定文件格式时需要。 //通过注册 CodePagesEncodingProvider,可以启用这些额外的编码支持 //Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); // 注册Encoding //注册jwt验证服务 services.AddAuthentication(options => { options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters() { ValidateIssuer = true, //是否验证Issuer ValidIssuer = Configuration["JwtConfig:Issuer"], //发行人Issuer ValidateAudience = true, //是否验证Audience ValidAudience = Configuration["JwtConfig:Audience"], //订阅人Audience ValidateIssuerSigningKey = true, //是否验证SecurityKey IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JwtConfig:SecretKey"]!)), //SecurityKey ValidateLifetime = true, //是否验证失效时间 ClockSkew = TimeSpan.FromSeconds(30), //过期时间容错值,解决服务器端时间不同步问题(秒) RequireExpirationTime = true, }; }); //身份验证 services.AddAuthorization(); // 添加 Quartz 服务 services.AddQuartz(q => { q.UseSimpleTypeLoader(); }); //获取所有Service业务层进行扫描注入 //业务层命名格式:Wood.xxx.Service //需要注释的情况下在 Service 业务层开启 xml注释 生成 Assembly[] assembly = AppDomain.CurrentDomain.GetAssemblies().Where(it => !string.IsNullOrEmpty(it.FullName) && it.FullName.StartsWith("Wood") && it.FullName.Contains(".Service,")).ToArray(); //扫描依赖注入 services.ScanAndRegisterLifeTimes(assembly); //swagger services.AddSwaggerGen(options => { foreach (var item in assembly) { var xmlPath = Path.Combine(AppContext.BaseDirectory, $"{item.GetName().Name}.xml"); if (File.Exists(xmlPath)) options.IncludeXmlComments(xmlPath, true); options.SwaggerDoc(item.GetName().Name, new OpenApiInfo { Title = "Wood Api", Version = "v1" }); } //根据不同程序集生成不同分组 options.DocInclusionPredicate((docName, apiDesc) => { return apiDesc.ActionDescriptor.DisplayName?.Contains(docName) ?? false; }); #region swagger 身份验证 // 创建一个新的安全方案对象,用于描述如何进行身份验证。 var securityScheme = new OpenApiSecurityScheme { // 设置安全方案的名称,通常与 HTTP 请求头字段匹配。 Name = "Authorization", // 指定安全方案的类型为 API 密钥(在这里特指 JWT Bearer Token)。 Type = SecuritySchemeType.ApiKey, // 指定 API 密钥的位置,这里是 HTTP 请求头。 In = ParameterLocation.Header, // 提供关于如何使用该安全方案的说明,例如在 Swagger UI 中显示给用户。 Description = "请在头信息中使用JWT Token进行身份验证 (例如: 'Bearer YOUR_TOKEN_HERE')", // 设置安全方案的模式,这里是 Bearer 类型的身份验证。 Scheme = "Bearer", // 设置 Bearer Token 的格式,这里指定为 JWT。 //BearerFormat = "JWT" }; // 将创建的安全方案添加到 Swagger 文档中,以便 Swagger UI 可以识别并应用它。 options.AddSecurityDefinition("Bearer", securityScheme); // 添加安全要求 // 添加Jwt验证设置,添加请求头信息 options.AddSecurityRequirement(new OpenApiSecurityRequirement { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Id = "Bearer", Type = ReferenceType.SecurityScheme } }, new List() } }); options.OperationFilter(); options.OperationFilter(); options.OperationFilter(); #endregion }); } /// /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. /// /// /// public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseDeveloperExceptionPage(); } //必须放在最前边 把 根 ServiceProvider 放入到全局 GlobalContext.ServiceProvider = app.ApplicationServices; //初始化sqlsugar数据库结构 app.UseSqlSugar(); app.UseHangfireDashboard(); //自定义静态文件路径 //string resource = Path.Combine(env.ContentRootPath, "Resource"); //FileHelper.CreateDirectory(resource); //app.UseStaticFiles(new StaticFileOptions //{ // RequestPath = "/Resource", // FileProvider = new PhysicalFileProvider(resource), // OnPrepareResponse = GlobalContext.SetCacheControl //}); app.UseStaticFiles(); //全局异常 app.UseMiddleware(typeof(GlobalExceptionMiddleware)); //文件拦截中间件 app.UseMiddleware(); app.UseCors(builder => { builder.WithOrigins(GlobalContext.SystemConfig!.AllowCorsSite.Split(',')).AllowAnyHeader().AllowAnyMethod().AllowCredentials(); }); app.UseRouting(); // 使用身份验证中间件 app.UseAuthentication(); // 使用授权中间件 app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute("default", "{controller=ApiHome}/{action=Index}/{id?}"); }); //启用 自动job app.UseAutoJob(); //获取所有Service业务层 扫描事件 //业务层命名格式:Wood.xxx.Service Assembly[] assembly = AppDomain.CurrentDomain.GetAssemblies().Where(it => !string.IsNullOrEmpty(it.FullName) && it.FullName.StartsWith("Wood") && it.FullName.Contains(".Service,")).ToArray(); app.UseSwagger(c => { c.RouteTemplate = "api-doc/{documentName}/swagger.json"; }); //根据程序集注册选项 app.UseSwaggerUI(c => { c.RoutePrefix = "api-doc"; foreach (var assembly in assembly) c.SwaggerEndpoint(assembly.GetName().Name + "/swagger.json", "Wood Api " + assembly.GetName().Name); }); //自动 注册所有事件 app.UseEventBus(assembly); } } }