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.
353 lines
13 KiB
353 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 TaskManager.EntityFramework.Repository;
|
|
using TaskManager.EntityFramework.Repository;
|
|
using Wood.Admin.WebApi.Filter;
|
|
using Wood.Admin.WebApi.Middleware;
|
|
using Wood.Data.Repository;
|
|
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.AddScoped<ITaskUnitOfWork, TaskUnitOfWork>();
|
|
services.AddScoped(typeof(TaskManager.EntityFramework.IRepository<>), typeof(Repository<>));
|
|
|
|
|
|
|
|
|
|
//注册控制器
|
|
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.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);
|
|
}
|
|
}
|
|
}
|