StartupHelper.cs 16 KB

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