You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

346 lines
13 KiB

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
{
/// <summary>
/// 入口
/// </summary>
public class Startup
{
/// <summary>
/// 配置
/// </summary>
public IConfiguration Configuration { get; }
/// <summary>
/// 构造函数
/// </summary>
/// <param name="configuration"></param>
/// <param name="env"></param>
public Startup(IConfiguration configuration, IWebHostEnvironment env)
{
Configuration = configuration;
GlobalContext.LogWhenStart(env);
GlobalContext.HostingEnvironment = env;
}
/// <summary>
/// This method gets called by the runtime. Use this method to add services to the container.
/// </summary>
/// <param name="services"></param>
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
//初始化配置
GlobalContext.SystemConfig = Configuration.GetSection("SystemConfig").Get<SystemConfig>()!;
GlobalContext.JwtConfig = Configuration.GetSection("JwtConfig").Get<JwtConfig>()!;
GlobalContext.Services = services;
GlobalContext.Configuration = Configuration;
//初始化 eventbus
services.AddEventBus();
//初始化数据库
services.AddSqlSugar(Configuration);
services.AddHttpClient();
services.AddTransient<LogController>();
services.AddTransient<SupplierProPlaningService>();
services.AddTransient<TaskConifgureController>();
// 配置 DbContext 使用 SQL Server 连接字符串
services.AddDbContext<JobDbContext>(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<ILogger<LogJobFilter>>())
); // 添加日志过滤器(可选)
services.AddHangfireServer(options =>
{
options.WorkerCount = 10;
// 可选:配置队列优先级
//options.Queues = builder.Configuration.GetSection("Hangfire:ServerOptions:Queues").Get<string[]>() ?? 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<string>()
}
});
options.OperationFilter<AddResponseHeadersFilter>();
options.OperationFilter<AppendAuthorizeToSummaryOperationFilter>();
options.OperationFilter<SecurityRequirementsOperationFilter>();
#endregion
});
}
/// <summary>
/// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
/// </summary>
/// <param name="app"></param>
/// <param name="env"></param>
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<FileBridgeMiddleware>();
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);
}
}
}