StartupHelper.cs 15 KB

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