StartupHelper.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. using System.IdentityModel.Tokens.Jwt;
  2. using System.IO.Compression;
  3. using System.Reflection;
  4. using System.Text;
  5. using Hotline.Application;
  6. using Hotline.Application.Jobs;
  7. using Hotline.Application.Orders;
  8. using Hotline.CallCenter.Configs;
  9. using Hotline.Configurations;
  10. using Hotline.DI;
  11. using Hotline.EventBus;
  12. using Hotline.Identity;
  13. using Hotline.Repository.SqlSugar;
  14. using Hotline.Repository.SqlSugar.Ts;
  15. using Mapster;
  16. using MapsterMapper;
  17. using MediatR.Pipeline;
  18. using Microsoft.AspNetCore.Authentication.JwtBearer;
  19. using Microsoft.AspNetCore.ResponseCompression;
  20. using Microsoft.IdentityModel.JsonWebTokens;
  21. using Microsoft.IdentityModel.Tokens;
  22. using Microsoft.OpenApi.Models;
  23. using Quartz;
  24. using Quartz.AspNetCore;
  25. using XF.Domain.Cache;
  26. using XF.Domain.Entities;
  27. using XF.Domain.Exceptions;
  28. using XF.Domain.Options;
  29. using XF.Domain.Repository;
  30. using XF.EasyCaching;
  31. namespace Hotline.Api
  32. {
  33. public static class StartupHelper
  34. {
  35. /// <summary>
  36. /// Authentication
  37. /// </summary>
  38. /// <param name="services"></param>
  39. /// <param name="configuration"></param>
  40. /// <returns></returns>
  41. public static IServiceCollection RegisterAuthentication(this IServiceCollection services, ConfigurationManager configuration)
  42. {
  43. var jwtOptions = configuration.GetSection("IdentityConfiguration").Get<IdentityConfiguration>().Jwt;
  44. #region remote ids
  45. //JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
  46. //services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
  47. // .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, d =>
  48. // {
  49. // d.Authority = identityConfigs.IdentityUrl;
  50. // d.RequireHttpsMetadata = false;
  51. // d.TokenValidationParameters = new TokenValidationParameters
  52. // {
  53. // ValidateAudience = false
  54. // };
  55. // //d.Audience = "hotline_api";
  56. // d.Events = new JwtBearerEvents
  57. // {
  58. // OnMessageReceived = context =>
  59. // {
  60. // var accessToken = context.Request.Query["access_token"];
  61. // // If the request is for our hub...
  62. // var path = context.HttpContext.Request.Path;
  63. // if (!string.IsNullOrEmpty(accessToken) &&
  64. // (path.StartsWithSegments("/hubs/callcenter")))
  65. // {
  66. // // Read the token out of the query string
  67. // context.Token = accessToken;
  68. // }
  69. // return Task.CompletedTask;
  70. // }
  71. // };
  72. // });
  73. #endregion
  74. services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
  75. .AddJwtBearer(d =>
  76. {
  77. byte[] bytes = Encoding.UTF8.GetBytes(jwtOptions.SecretKey);
  78. var secKey = new SymmetricSecurityKey(bytes);
  79. d.TokenValidationParameters = new()
  80. {
  81. ValidateIssuer = false,
  82. ValidateAudience = true,
  83. ValidateLifetime = true,
  84. ValidateIssuerSigningKey = true,
  85. IssuerSigningKey = secKey,
  86. AudienceValidator = (audiences, token, parameters) =>
  87. {
  88. if (token is /*JwtSecurityToken*/ JsonWebToken jwtToken)
  89. {
  90. using var serviceProvider = services.BuildServiceProvider();
  91. var cacheAudience = serviceProvider.GetService<ITypedCache<AudienceTicket>>();
  92. var audience = cacheAudience.Get(jwtToken.Subject);
  93. return audiences != null && audiences.Any(a => a == audience?.Ticket);
  94. }
  95. return false;
  96. }
  97. };
  98. //d.Audience = "hotline_api";
  99. d.Events = new JwtBearerEvents
  100. {
  101. OnMessageReceived = context =>
  102. {
  103. var accessToken = context.Request.Query["access_token"];
  104. // If the request is for our hub...
  105. var path = context.HttpContext.Request.Path;
  106. if (!string.IsNullOrEmpty(accessToken) &&
  107. (path.StartsWithSegments("/hubs/hotline")))
  108. {
  109. // Read the token out of the query string
  110. context.Token = accessToken;
  111. }
  112. return Task.CompletedTask;
  113. }
  114. };
  115. })
  116. ;
  117. return services;
  118. }
  119. /// <summary>
  120. /// Swagger
  121. /// </summary>
  122. /// <param name="services"></param>
  123. /// <returns></returns>
  124. public static IServiceCollection RegisterSwagger(this IServiceCollection services)
  125. {
  126. services.AddSwaggerGen(c =>
  127. {
  128. //添加文档
  129. c.SwaggerDoc("v1", new OpenApiInfo() { Title = "Hotline Api", Version = "v1.0", Description = "智慧热线api" });
  130. var files = Directory.GetFiles(AppContext.BaseDirectory).Where(d => d.EndsWith(".xml"));
  131. foreach (var file in files)
  132. {
  133. c.IncludeXmlComments(file, true);
  134. }
  135. var scheme = new OpenApiSecurityScheme()
  136. {
  137. Description = "Authorization header. \r\nExample: 'Bearer ***'",
  138. Reference = new OpenApiReference
  139. {
  140. Type = ReferenceType.SecurityScheme,
  141. Id = "Authorization"
  142. },
  143. Scheme = "oauth2",
  144. Name = "Authorization",
  145. In = ParameterLocation.Header,
  146. Type = SecuritySchemeType.ApiKey,
  147. };
  148. c.AddSecurityDefinition("Authorization", scheme);
  149. var requirement = new OpenApiSecurityRequirement();
  150. requirement[scheme] = new List<string>();
  151. c.AddSecurityRequirement(requirement);
  152. });
  153. return services;
  154. }
  155. /// <summary>
  156. /// Cors
  157. /// </summary>
  158. /// <param name="services"></param>
  159. /// <param name="configuration"></param>
  160. /// <param name="corsOrigins"></param>
  161. /// <returns></returns>
  162. public static IServiceCollection RegisterCors(this IServiceCollection services, ConfigurationManager configuration, string corsOrigins)
  163. {
  164. services.AddCors(options =>
  165. {
  166. options.AddPolicy(name: corsOrigins,
  167. builder =>
  168. {
  169. var origins = configuration.GetSection("Cors:Origins").Get<string[]>();
  170. builder.SetIsOriginAllowed(a =>
  171. {
  172. return origins.Any(origin => origin.StartsWith("*.", StringComparison.Ordinal)
  173. ? a.EndsWith(origin[1..], StringComparison.Ordinal)
  174. : a.Equals(origin, StringComparison.Ordinal));
  175. })
  176. .AllowAnyHeader()
  177. .AllowAnyMethod()
  178. .AllowCredentials();
  179. });
  180. });
  181. return services;
  182. }
  183. /// <summary>
  184. /// Mapper
  185. /// </summary>
  186. /// <param name="services"></param>
  187. /// <returns></returns>
  188. public static IServiceCollection RegisterMapper(this IServiceCollection services)
  189. {
  190. var config = TypeAdapterConfig.GlobalSettings;
  191. config.ForDestinationType<IDataPermission>()
  192. .Ignore(d => d.CreatorId)
  193. .Ignore(d => d.CreatorOrgId)
  194. //.Ignore(d => d.CreatorOrgCode)
  195. .Ignore(d => d.AreaId);
  196. config.ForDestinationType<IWorkflow>()
  197. .Ignore(d => d.ExpiredTimeConfigId);
  198. config.ForDestinationType<IHasCreationTime>()
  199. .Ignore(d => d.CreationTime);
  200. config.ForDestinationType<IHasDeletionTime>().Ignore(d => d.DeletionTime);
  201. config.ForDestinationType<ISoftDelete>().Ignore(d => d.IsDeleted);
  202. config.ForDestinationType<IHasModificationTime>().Ignore(d => d.LastModificationTime);
  203. config.ForDestinationType<Entity>().Ignore(d => d.Id);
  204. services.AddSingleton(config);
  205. services.AddScoped<IMapper, ServiceMapper>();
  206. return services;
  207. }
  208. /// <summary>
  209. /// SignalR
  210. /// </summary>
  211. /// <param name="services"></param>
  212. /// <param name="configuration"></param>
  213. /// <returns></returns>
  214. public static IServiceCollection RegisterSignalR(this IServiceCollection services, ConfigurationManager configuration)
  215. {
  216. //var connectionString = configuration.GetConnectionString("Redis");
  217. //if (string.IsNullOrEmpty(connectionString))
  218. // throw new UserFriendlyException("未配置signalR的redis连接");
  219. //services.AddSignalR().AddStackExchangeRedis(connectionString, options =>
  220. //{
  221. // options.Configuration.ChannelPrefix = "Hotline:signalr:";
  222. //});
  223. var cacheConfig = configuration.GetRequiredSection("Cache").Get<CacheOptions>();
  224. services.AddSignalR().AddStackExchangeRedis($"{cacheConfig.Host}:{cacheConfig.Port}", options =>
  225. {
  226. options.Configuration.ChannelPrefix = "Hotline:signalr:";
  227. options.Configuration.Password = cacheConfig.Password;
  228. options.Configuration.DefaultDatabase = cacheConfig.Database;
  229. });
  230. return services;
  231. }
  232. public static IServiceCollection RegisterJob(this IServiceCollection services,AppConfiguration appConfiguration )
  233. {
  234. services.AddQuartz(d =>
  235. {
  236. d.SchedulerId = "default_scheduler";
  237. d.InterruptJobsOnShutdown = true;
  238. d.InterruptJobsOnShutdownWithWait = true;
  239. d.MaxBatchSize = 3;
  240. //load send order job
  241. //即将超期和超期短信
  242. var autoSendOverTimeSmsKey = new JobKey(nameof(SendOverTimeSmsJob), "send overtime order task");
  243. d.AddJob<SendOverTimeSmsJob>(autoSendOverTimeSmsKey);
  244. d.AddTrigger(t => t
  245. .WithIdentity("task-send-overtime-order-trigger")
  246. .ForJob(autoSendOverTimeSmsKey)
  247. .StartNow()
  248. .WithCronSchedule("0 30 09,14 * * ?"));
  249. var autoSendOrderKey = new JobKey(nameof(SendOrderJob), "send order task");
  250. switch (appConfiguration.AppScope)
  251. {
  252. //智能化任务
  253. case AppDefaults.AppScope.YiBin:
  254. var aiVisitStatusKey = new JobKey(nameof(CheckAiVisitStateJob), "check aivisit state task");
  255. d.AddJob<CheckAiVisitStateJob>(aiVisitStatusKey);
  256. d.AddTrigger(t => t
  257. .WithIdentity("task-check-aivisit-state-trigger")
  258. .ForJob(aiVisitStatusKey)
  259. .StartNow()
  260. .WithCronSchedule("0 0/5 * * * ? *"));
  261. d.AddJob<SendOrderJob>(autoSendOrderKey);
  262. d.AddTrigger(t => t
  263. .WithIdentity("task-send-order-trigger")
  264. .ForJob(autoSendOrderKey)
  265. .StartNow()
  266. .WithCronSchedule("0 0 9 * * ?")
  267. );
  268. break;
  269. case AppDefaults.AppScope.ZiGong:
  270. d.AddJob<SendOrderJob>(autoSendOrderKey);
  271. d.AddTrigger(t => t
  272. .WithIdentity("task-send-order-trigger")
  273. .ForJob(autoSendOrderKey)
  274. .StartNow()
  275. .WithCronSchedule("0 10 9 * * ?")
  276. );
  277. break;
  278. default:
  279. break;
  280. }
  281. switch (appConfiguration.GetDefaultAppScopeConfiguration().CallCenterType)
  282. {
  283. case AppDefaults.CallCenterType.XingTang:
  284. var getCallsJobKey = new JobKey(nameof(XingTangCallsSyncJob));
  285. d.AddJob<XingTangCallsSyncJob>(getCallsJobKey);
  286. d.AddTrigger(t => t
  287. .WithIdentity("get-callsxt-trigger")
  288. .ForJob(getCallsJobKey)
  289. .StartNow()
  290. .WithCronSchedule("0/5 * * * * ?")
  291. );
  292. var getOperationsJobKey = new JobKey(nameof(XingTangTelOperationSyncJob));
  293. d.AddJob<XingTangTelOperationSyncJob>(getOperationsJobKey);
  294. d.AddTrigger(t => t
  295. .WithIdentity("get-operationsxt-trigger")
  296. .ForJob(getOperationsJobKey)
  297. .StartNow()
  298. .WithCronSchedule("0/10 * * * * ?")
  299. );
  300. //var getCallSatisfactionJobKey = new JobKey(nameof(XingTangCallSatisfactionSyncJob));
  301. //d.AddJob<XingTangCallSatisfactionSyncJob>(getCallSatisfactionJobKey);
  302. //d.AddTrigger(t => t
  303. // .WithIdentity("get-callsatisfaction-trigger")
  304. // .ForJob(getCallSatisfactionJobKey)
  305. // .StartNow()
  306. // .WithCronSchedule("0/30 * * * * ?")
  307. //);
  308. break;
  309. }
  310. });
  311. services.AddQuartzServer(d =>
  312. {
  313. d.WaitForJobsToComplete = true;
  314. d.StartDelay = TimeSpan.FromSeconds(5);
  315. });
  316. return services;
  317. }
  318. public static IServiceCollection RegisterRepository(this IServiceCollection services)
  319. {
  320. services.AddScoped(typeof(IRepository<>), typeof(BaseRepository<>));
  321. services.AddScoped(typeof(IRepositoryWorkflow<>), typeof(BaseRepositoryWorkflow<>));
  322. services.AddScoped(typeof(IRepositoryTextSearch<>), typeof(BaseRepositoryTextSearch<>));
  323. return services;
  324. }
  325. public static IServiceCollection RegisterMediatR(this IServiceCollection services, AppConfiguration appConfiguration)
  326. {
  327. services.AddScoped<Publisher>();
  328. services.AddMediatR(cfg =>
  329. {
  330. cfg.TypeEvaluator = d =>
  331. {
  332. var attr = d.GetCustomAttribute(typeof(InjectionAttribute)) as InjectionAttribute;
  333. if (attr is null) return true;
  334. return attr.IsEnable(appConfiguration.AppScope,
  335. appConfiguration.GetDefaultAppScopeConfiguration().CallCenterType);
  336. //if (!attr.Enable) return false;
  337. //switch (attr.EventHandlerType)
  338. //{
  339. // case EEventHandlerType.CallCenter:
  340. // return attr.Label == appConfiguration.GetDefaultAppScopeConfiguration().CallCenterType;
  341. // default:
  342. // return attr.AppScopes.ToString()
  343. // .Split(',', StringSplitOptions.RemoveEmptyEntries)
  344. // .Any(x => string.Compare(x, appConfiguration.AppScope, StringComparison.Ordinal) == 0);
  345. //}
  346. };
  347. //cfg.RegisterServicesFromAssemblyContaining<Program>();
  348. cfg.RegisterServicesFromAssembly(typeof(ApplicationStartupExtensions).Assembly);
  349. });
  350. return services;
  351. }
  352. public static IServiceCollection RejisterCompression(this IServiceCollection services)
  353. {
  354. services.Configure<BrotliCompressionProviderOptions>(options =>
  355. {
  356. options.Level = CompressionLevel.Optimal;
  357. }).Configure<GzipCompressionProviderOptions>(options =>
  358. {
  359. options.Level = CompressionLevel.Optimal;
  360. }).AddResponseCompression(options =>
  361. {
  362. options.EnableForHttps = true;
  363. options.Providers.Add<BrotliCompressionProvider>();
  364. options.Providers.Add<GzipCompressionProvider>();
  365. options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat([
  366. "application/json; charset=utf-8",
  367. "text/html; charset=utf-8",
  368. "application/xhtml+xml",
  369. "application/atom+xml",
  370. "image/svg+xml"
  371. ]);
  372. });
  373. return services;
  374. }
  375. }
  376. }