using System.IdentityModel.Tokens.Jwt; using System.IO.Compression; using System.Reflection; using System.Text; using Hotline.Api.Filter; using Hotline.Application; using Hotline.Application.Jobs; using Hotline.Caching.Interfaces; using Hotline.CallCenter.Configs; using Hotline.Configurations; using Hotline.DI; using Hotline.EventBus; using Hotline.Identity; using Hotline.Repository.SqlSugar; using Hotline.Repository.SqlSugar.Ts; using Hotline.Settings; using Mapster; using MapsterMapper; using MediatR.Pipeline; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.ResponseCompression; using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Models; using Quartz; using Quartz.AspNetCore; using XF.Domain.Cache; using XF.Domain.Entities; using XF.Domain.Exceptions; using XF.Domain.Options; using XF.Domain.Repository; using XF.EasyCaching; namespace Hotline.Api { public static class StartupHelper { /// /// Authentication /// /// /// /// public static IServiceCollection RegisterAuthentication(this IServiceCollection services, ConfigurationManager configuration) { var jwtOptions = configuration.GetSection("IdentityConfiguration").Get().Jwt; #region remote ids //JwtSecurityTokenHandler.DefaultMapInboundClaims = false; //services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) // .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, d => // { // d.Authority = identityConfigs.IdentityUrl; // d.RequireHttpsMetadata = false; // d.TokenValidationParameters = new TokenValidationParameters // { // ValidateAudience = false // }; // //d.Audience = "hotline_api"; // d.Events = new JwtBearerEvents // { // OnMessageReceived = context => // { // var accessToken = context.Request.Query["access_token"]; // // If the request is for our hub... // var path = context.HttpContext.Request.Path; // if (!string.IsNullOrEmpty(accessToken) && // (path.StartsWithSegments("/hubs/callcenter"))) // { // // Read the token out of the query string // context.Token = accessToken; // } // return Task.CompletedTask; // } // }; // }); #endregion services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(d => { byte[] bytes = Encoding.UTF8.GetBytes(jwtOptions.SecretKey); var secKey = new SymmetricSecurityKey(bytes); d.TokenValidationParameters = new() { ValidateIssuer = false, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, IssuerSigningKey = secKey, AudienceValidator = (audiences, token, parameters) => { if (token is /*JwtSecurityToken*/ JsonWebToken jwtToken) { using var serviceProvider = services.BuildServiceProvider(); var cacheAudience = serviceProvider.GetService>(); var audience = cacheAudience.Get(jwtToken.Subject); return audiences != null && audiences.Any(a => a == audience?.Ticket); } return false; } }; //d.Audience = "hotline_api"; d.Events = new JwtBearerEvents { OnMessageReceived = context => { var accessToken = context.Request.Query["access_token"]; // If the request is for our hub... var path = context.HttpContext.Request.Path; if (!string.IsNullOrEmpty(accessToken) && (path.StartsWithSegments("/hubs/hotline"))) { // Read the token out of the query string context.Token = accessToken; } return Task.CompletedTask; } }; }) ; return services; } public static IServiceCollection RegisterAuthorization(this IServiceCollection services, ConfigurationManager configuration) { services.AddAuthorization(options => { options.AddPolicy(AppDefaults.AuthPolicy.Hotline, d => d.RequireClaim("scope", AppDefaults.AuthPolicy.Hotline)); }); return services; } /// /// Swagger /// /// /// public static IServiceCollection RegisterSwagger(this IServiceCollection services) { services.AddSwaggerGen(c => { //添加文档 c.SwaggerDoc("v1", new OpenApiInfo() { Title = "Hotline Api", Version = "v1.0", Description = "智慧热线api" }); var files = Directory.GetFiles(AppContext.BaseDirectory).Where(d => d.EndsWith(".xml")); foreach (var file in files) { c.IncludeXmlComments(file, true); } var scheme = new OpenApiSecurityScheme() { Description = "Authorization header. \r\nExample: 'Bearer ***'", Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Authorization" }, Scheme = "oauth2", Name = "Authorization", In = ParameterLocation.Header, Type = SecuritySchemeType.ApiKey, }; c.AddSecurityDefinition("Authorization", scheme); var requirement = new OpenApiSecurityRequirement(); requirement[scheme] = new List(); c.AddSecurityRequirement(requirement); }); return services; } /// /// Cors /// /// /// /// /// public static IServiceCollection RegisterCors(this IServiceCollection services, ConfigurationManager configuration, string corsOrigins) { services.AddCors(options => { options.AddPolicy(name: corsOrigins, builder => { var origins = configuration.GetSection("Cors:Origins").Get(); builder.SetIsOriginAllowed(a => { return origins.Any(origin => origin.StartsWith("*.", StringComparison.Ordinal) ? a.EndsWith(origin[1..], StringComparison.Ordinal) : a.Equals(origin, StringComparison.Ordinal)); }) .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials(); }); }); return services; } /// /// Mapper /// /// /// public static IServiceCollection RegisterMapper(this IServiceCollection services) { var config = TypeAdapterConfig.GlobalSettings; config.ForDestinationType() .Ignore(d => d.CreatorId) .Ignore(d => d.CreatorOrgId) //.Ignore(d => d.CreatorOrgCode) .Ignore(d => d.AreaId); config.ForDestinationType() .Ignore(d => d.ExpiredTimeConfigId); config.ForDestinationType() .Ignore(d => d.CreationTime); config.ForDestinationType().Ignore(d => d.DeletionTime); config.ForDestinationType().Ignore(d => d.IsDeleted); config.ForDestinationType().Ignore(d => d.LastModificationTime); config.ForDestinationType().Ignore(d => d.Id); services.AddSingleton(config); services.AddScoped(); return services; } /// /// SignalR /// /// /// /// public static IServiceCollection RegisterSignalR(this IServiceCollection services, ConfigurationManager configuration) { //var connectionString = configuration.GetConnectionString("Redis"); //if (string.IsNullOrEmpty(connectionString)) // throw new UserFriendlyException("未配置signalR的redis连接"); //services.AddSignalR().AddStackExchangeRedis(connectionString, options => //{ // options.Configuration.ChannelPrefix = "Hotline:signalr:"; //}); var cacheConfig = configuration.GetRequiredSection("Cache").Get(); services.AddSignalR().AddStackExchangeRedis($"{cacheConfig.Host}:{cacheConfig.Port}", options => { options.Configuration.ChannelPrefix = "Hotline:signalr:"; options.Configuration.Password = cacheConfig.Password; options.Configuration.DefaultDatabase = cacheConfig.Database; }); return services; } public static IServiceCollection RegisterJob(this IServiceCollection services,AppConfiguration appConfiguration ) { services.AddQuartz(d => { d.SchedulerId = "default_scheduler"; d.InterruptJobsOnShutdown = true; d.InterruptJobsOnShutdownWithWait = true; d.MaxBatchSize = 3; //load send order job //即将超期和超期短信 //var autoSendOverTimeSmsKey = new JobKey(nameof(SendOverTimeSmsJob), "send overtime order task"); //d.AddJob(autoSendOverTimeSmsKey); //d.AddTrigger(t => t // .WithIdentity("task-send-overtime-order-trigger") // .ForJob(autoSendOverTimeSmsKey) // .StartNow() // .WithCronSchedule("0 30 09,14 * * ?")); var autoSendOrderKey = new JobKey(nameof(SendOrderJob), "send order task"); switch (appConfiguration.AppScope) { //智能化任务 case AppDefaults.AppScope.YiBin: var aiVisitStatusKey = new JobKey(nameof(CheckAiVisitStateJob), "check aivisit state task"); d.AddJob(aiVisitStatusKey); d.AddTrigger(t => t .WithIdentity("task-check-aivisit-state-trigger") .ForJob(aiVisitStatusKey) .StartNow() .WithCronSchedule("0 0/5 * * * ? *")); //d.AddJob(autoSendOrderKey); //d.AddTrigger(t => t // .WithIdentity("task-send-order-trigger") // .ForJob(autoSendOrderKey) // .StartNow() // .WithCronSchedule("0 10 9 * * ?") //); break; case AppDefaults.AppScope.ZiGong: //d.AddJob(autoSendOrderKey); //d.AddTrigger(t => t // .WithIdentity("task-send-order-trigger") // .ForJob(autoSendOrderKey) // .StartNow() // .WithCronSchedule("0 10 9 * * ?") //); break; default: break; } switch (appConfiguration.GetDefaultAppScopeConfiguration().CallCenterType) { case AppDefaults.CallCenterType.XingTang: var getCallsJobKey = new JobKey(nameof(XingTangCallsSyncJob)); d.AddJob(getCallsJobKey); d.AddTrigger(t => t .WithIdentity("get-callsxt-trigger") .ForJob(getCallsJobKey) .StartNow() .WithCronSchedule("0/5 * * * * ?") ); //var getOperationsJobKey = new JobKey(nameof(XingTangTelOperationSyncJob)); //d.AddJob(getOperationsJobKey); //d.AddTrigger(t => t // .WithIdentity("get-operationsxt-trigger") // .ForJob(getOperationsJobKey) // .StartNow() // .WithCronSchedule("0/10 * * * * ?") //); var getCallSatisfactionJobKey = new JobKey(nameof(XingTangCallSatisfactionSyncJob)); d.AddJob(getCallSatisfactionJobKey); d.AddTrigger(t => t .WithIdentity("get-callsatisfaction-trigger") .ForJob(getCallSatisfactionJobKey) .StartNow() .WithCronSchedule("0/30 * * * * ?") ); break; } }); services.AddQuartzServer(d => { d.WaitForJobsToComplete = true; d.StartDelay = TimeSpan.FromSeconds(5); }); return services; } public static IServiceCollection RegisterRepository(this IServiceCollection services) { services.AddScoped(typeof(IRepository<>), typeof(BaseRepository<>)); services.AddScoped(typeof(IRepositoryWorkflow<>), typeof(BaseRepositoryWorkflow<>)); services.AddScoped(typeof(IRepositoryTextSearch<>), typeof(BaseRepositoryTextSearch<>)); return services; } public static IServiceCollection RegisterMediatR(this IServiceCollection services, AppConfiguration appConfiguration) { services.AddScoped(); services.AddMediatR(cfg => { cfg.TypeEvaluator = d => { var attr = d.GetCustomAttribute(typeof(InjectionAttribute)) as InjectionAttribute; if (attr is null) return true; return attr.IsEnable(appConfiguration.AppScope, appConfiguration.GetDefaultAppScopeConfiguration().CallCenterType); //if (!attr.Enable) return false; //switch (attr.EventHandlerType) //{ // case EEventHandlerType.CallCenter: // return attr.Label == appConfiguration.GetDefaultAppScopeConfiguration().CallCenterType; // default: // return attr.AppScopes.ToString() // .Split(',', StringSplitOptions.RemoveEmptyEntries) // .Any(x => string.Compare(x, appConfiguration.AppScope, StringComparison.Ordinal) == 0); //} }; //cfg.RegisterServicesFromAssemblyContaining(); cfg.RegisterServicesFromAssembly(typeof(ApplicationStartupExtensions).Assembly); }); return services; } public static IServiceCollection RegisterCompression(this IServiceCollection services) { services.Configure(options => { options.Level = CompressionLevel.Optimal; }).Configure(options => { options.Level = CompressionLevel.Optimal; }).AddResponseCompression(options => { options.EnableForHttps = true; options.Providers.Add(); options.Providers.Add(); options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat([ "application/json; charset=utf-8", "text/html; charset=utf-8", "application/xhtml+xml", "application/atom+xml", "image/svg+xml" ]); }); return services; } } }