|
|
|
|
using AspNetCoreRateLimit;
|
|
|
|
|
using ATS.NonCustodial.AdminUi.Attributes;
|
|
|
|
|
using ATS.NonCustodial.AdminUi.Auth;
|
|
|
|
|
using ATS.NonCustodial.AdminUi.Extensions;
|
|
|
|
|
using ATS.NonCustodial.AdminUi.Extensions.Host;
|
|
|
|
|
using ATS.NonCustodial.AdminUi.Extensions.ImSignalR;
|
|
|
|
|
using ATS.NonCustodial.AdminUi.Filters;
|
|
|
|
|
using ATS.NonCustodial.AdminUi.Helpers;
|
|
|
|
|
using ATS.NonCustodial.AdminUi.Helpers.Logs;
|
|
|
|
|
using ATS.NonCustodial.Application.Contracts.Interfaces.Business.IM.Notifies;
|
|
|
|
|
using ATS.NonCustodial.Application.Impl.Business.IM;
|
|
|
|
|
using ATS.NonCustodial.DynamicApi;
|
|
|
|
|
using ATS.NonCustodial.Shared.Common.Auth;
|
|
|
|
|
using ATS.NonCustodial.Shared.Common.Constants;
|
|
|
|
|
using ATS.NonCustodial.Shared.Common.Enums;
|
|
|
|
|
using ATS.NonCustodial.Shared.Tools.Cache;
|
|
|
|
|
using IdentityServer4.AccessTokenValidation;
|
|
|
|
|
using Microsoft.AspNetCore.Authentication;
|
|
|
|
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
|
|
|
|
using Microsoft.AspNetCore.Builder;
|
|
|
|
|
using Microsoft.AspNetCore.Hosting;
|
|
|
|
|
using Microsoft.AspNetCore.Http;
|
|
|
|
|
using Microsoft.AspNetCore.Http.Connections;
|
|
|
|
|
using Microsoft.AspNetCore.HttpOverrides;
|
|
|
|
|
using Microsoft.AspNetCore.Mvc.Controllers;
|
|
|
|
|
using Microsoft.AspNetCore.SignalR;
|
|
|
|
|
using Microsoft.Extensions.Configuration;
|
|
|
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
|
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
|
|
|
|
using Microsoft.Extensions.DependencyModel;
|
|
|
|
|
using Microsoft.Extensions.Hosting;
|
|
|
|
|
using Microsoft.IdentityModel.Tokens;
|
|
|
|
|
using Microsoft.OpenApi.Any;
|
|
|
|
|
using Microsoft.OpenApi.Models;
|
|
|
|
|
using Newtonsoft.Json;
|
|
|
|
|
using Newtonsoft.Json.Serialization;
|
|
|
|
|
using Serilog;
|
|
|
|
|
using System.IdentityModel.Tokens.Jwt;
|
|
|
|
|
using System.Reflection;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using ATS.NonCustodial.Shared.Helpers.Http.HttpPolly;
|
|
|
|
|
using Yitter.IdGenerator;
|
|
|
|
|
using IConfiguration = Microsoft.Extensions.Configuration.IConfiguration;
|
|
|
|
|
|
|
|
|
|
namespace ATS.NonCustodial.AdminUi.Configurations
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Startup==> ServiceCollection 注入扩展
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// Author:mxg
|
|
|
|
|
/// CreatedTimed:2022-05-30 08:57 PM
|
|
|
|
|
public static class AdminServerCollectionExtension
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// This method gets called by the runtime. Use this method to add services to the container.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="services"></param>
|
|
|
|
|
/// <param name="adminUiOptions"></param>
|
|
|
|
|
/// <param name="configuration"></param>
|
|
|
|
|
/// <param name="assemblies"></param>
|
|
|
|
|
public static void AddAdminServices(this IServiceCollection services, AdminUiOptions adminUiOptions, IConfiguration configuration, Assembly[] assemblies)
|
|
|
|
|
{
|
|
|
|
|
//雪花漂移算法
|
|
|
|
|
YitIdHelper.SetIdGenerator(new IdGeneratorOptions(1) { WorkerIdBitLength = 6 });
|
|
|
|
|
|
|
|
|
|
//权限处理
|
|
|
|
|
services.AddScoped<IPermissionHandler, PermissionHandler>();
|
|
|
|
|
services.AddScoped<IHttpPollyHelper, HttpPollyHelper>();
|
|
|
|
|
|
|
|
|
|
// ClaimType不被更改
|
|
|
|
|
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
|
|
|
|
|
|
|
|
|
|
//用户信息
|
|
|
|
|
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
|
|
|
|
|
if (adminUiOptions.IdentityServerConfiguration.Enable) services.TryAddSingleton<IUser, UserIdentityServer>(); //is4
|
|
|
|
|
else services.TryAddSingleton<IUser, User>(); //jwt
|
|
|
|
|
|
|
|
|
|
//AutoMapper映射配置
|
|
|
|
|
services.AddAutoMapper((serviceProvider, cfg) =>
|
|
|
|
|
{
|
|
|
|
|
// 你可以在这里进行全局性的配置
|
|
|
|
|
cfg.AllowNullCollections = true; // 示例:允许空集合映射
|
|
|
|
|
|
|
|
|
|
// 如果你需要从服务提供器(IServiceProvider)解析服务,以便在映射配置中使用,可以使用 serviceProvider 参数
|
|
|
|
|
// var someService = serviceProvider.GetRequiredService<ISomeService>();
|
|
|
|
|
// cfg.ConstructServicesUsing(type => serviceProvider.GetService(type)); // 另一种设置服务构造函数的方法
|
|
|
|
|
}, assemblies);
|
|
|
|
|
|
|
|
|
|
//注册job
|
|
|
|
|
services.InitialQuartzJob(adminUiOptions);
|
|
|
|
|
|
|
|
|
|
//Cors 跨域
|
|
|
|
|
services.AddAdminApiCors(adminUiOptions);
|
|
|
|
|
|
|
|
|
|
//个推
|
|
|
|
|
services.AddSingleton(adminUiOptions.TweetsConfigConfiguration);
|
|
|
|
|
services.AddSingleton(adminUiOptions.SmsConfiguration);
|
|
|
|
|
|
|
|
|
|
#region 身份认证授权
|
|
|
|
|
|
|
|
|
|
//signalR:https://docs.microsoft.com/zh-cn/aspnet/core/signalr/authn-and-authz?view=aspnetcore-6.0
|
|
|
|
|
var jwtConfig = adminUiOptions.JwtConfiguration;
|
|
|
|
|
services.TryAddSingleton(jwtConfig);
|
|
|
|
|
if (adminUiOptions.IdentityServerConfiguration.Enable)
|
|
|
|
|
{
|
|
|
|
|
//is4
|
|
|
|
|
services.AddAuthentication(options =>
|
|
|
|
|
{
|
|
|
|
|
options.DefaultScheme = IdentityServerAuthenticationDefaults.AuthenticationScheme;
|
|
|
|
|
options.DefaultChallengeScheme = nameof(ResponseAuthenticationHandler); //401
|
|
|
|
|
options.DefaultForbidScheme = nameof(ResponseAuthenticationHandler); //403
|
|
|
|
|
})
|
|
|
|
|
.AddJwtBearer(options =>
|
|
|
|
|
{
|
|
|
|
|
options.Authority = adminUiOptions.IdentityServerConfiguration.Url;
|
|
|
|
|
options.RequireHttpsMetadata = false;
|
|
|
|
|
options.Audience = "ATS.NonCustodial.Admin.Api";
|
|
|
|
|
|
|
|
|
|
//重点在于这里;判断是SignalR的路径
|
|
|
|
|
options.Events = new JwtBearerEvents
|
|
|
|
|
{
|
|
|
|
|
OnMessageReceived = (context) =>
|
|
|
|
|
{
|
|
|
|
|
if (!context.HttpContext.Request.Path.HasValue) return Task.CompletedTask;
|
|
|
|
|
|
|
|
|
|
//重点在于这里;判断是SignalR的路径
|
|
|
|
|
var accessToken = context.HttpContext.Request.Query["access_token"];
|
|
|
|
|
var path = context.HttpContext.Request.Path;
|
|
|
|
|
if (string.IsNullOrWhiteSpace(accessToken) || !path.StartsWithSegments("/nonCustodialHub")) return Task.CompletedTask;
|
|
|
|
|
|
|
|
|
|
context.Token = accessToken;
|
|
|
|
|
return Task.CompletedTask;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
})
|
|
|
|
|
.AddScheme<AuthenticationSchemeOptions, ResponseAuthenticationHandler>(nameof(ResponseAuthenticationHandler), o => { });
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
//jwt
|
|
|
|
|
services.AddAuthentication(options =>
|
|
|
|
|
{
|
|
|
|
|
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
|
|
|
|
|
options.DefaultChallengeScheme = nameof(ResponseAuthenticationHandler); //401
|
|
|
|
|
options.DefaultForbidScheme = nameof(ResponseAuthenticationHandler); //403
|
|
|
|
|
})
|
|
|
|
|
.AddJwtBearer(options =>
|
|
|
|
|
{
|
|
|
|
|
options.TokenValidationParameters = new TokenValidationParameters
|
|
|
|
|
{
|
|
|
|
|
ValidateIssuer = true,
|
|
|
|
|
ValidateAudience = true,
|
|
|
|
|
ValidateLifetime = true,
|
|
|
|
|
ValidateIssuerSigningKey = true,
|
|
|
|
|
ValidIssuer = jwtConfig.Issuer,
|
|
|
|
|
ValidAudience = jwtConfig.Audience,
|
|
|
|
|
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtConfig.SymmetricSecurityKey)),
|
|
|
|
|
ClockSkew = TimeSpan.Zero
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//重点在于这里;判断是SignalR的路径(https://www.cnblogs.com/fger/p/11811190.html)
|
|
|
|
|
options.Events = new JwtBearerEvents
|
|
|
|
|
{
|
|
|
|
|
OnMessageReceived = (context) =>
|
|
|
|
|
{
|
|
|
|
|
if (!context.HttpContext.Request.Path.HasValue) return Task.CompletedTask;
|
|
|
|
|
|
|
|
|
|
//重点在于这里;判断是SignalR的路径
|
|
|
|
|
var accessToken = context.HttpContext.Request.Query["access_token"];
|
|
|
|
|
var path = context.HttpContext.Request.Path;
|
|
|
|
|
if (string.IsNullOrWhiteSpace(accessToken) || !path.StartsWithSegments("/nonCustodialHub")) return Task.CompletedTask;
|
|
|
|
|
|
|
|
|
|
context.Token = accessToken;
|
|
|
|
|
return Task.CompletedTask;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
//此处为权限验证失败后触发的事件
|
|
|
|
|
OnChallenge = context =>
|
|
|
|
|
{
|
|
|
|
|
//此处代码为终止.Net Core默认的返回类型和数据结果,这个很重要哦,必须
|
|
|
|
|
context.HandleResponse();
|
|
|
|
|
//自定义自己想要返回的数据结果,我这里要返回的是Json对象,通过引用Newtonsoft.Json库进行转换
|
|
|
|
|
var payload = new { StatusCode = 0, Message = "身份认证失败!" };
|
|
|
|
|
//自定义返回的数据类型
|
|
|
|
|
context.Response.ContentType = "application/json";
|
|
|
|
|
//自定义返回状态码,默认为401 我这里改成 200
|
|
|
|
|
context.Response.StatusCode = StatusCodes.Status200OK;
|
|
|
|
|
//context.Response.StatusCode = StatusCodes.Status401Unauthorized;
|
|
|
|
|
//输出Json数据结果
|
|
|
|
|
context.Response.WriteAsync(Convert.ToString(payload));
|
|
|
|
|
return Task.FromResult(0);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
})
|
|
|
|
|
.AddScheme<AuthenticationSchemeOptions, ResponseAuthenticationHandler>(nameof(ResponseAuthenticationHandler), o => { });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion 身份认证授权
|
|
|
|
|
|
|
|
|
|
#region Swagger Api文档
|
|
|
|
|
|
|
|
|
|
if (adminUiOptions.SwaggerConfiguration.Enable)
|
|
|
|
|
{
|
|
|
|
|
services.AddSwaggerGen(options =>
|
|
|
|
|
{
|
|
|
|
|
typeof(ApiVersionEnum).GetEnumNames().ToList().ForEach(version =>
|
|
|
|
|
{
|
|
|
|
|
options.SwaggerDoc(version, new OpenApiInfo
|
|
|
|
|
{
|
|
|
|
|
Version = version,
|
|
|
|
|
Title = "ATS.NonCustodial.Admin.Api"
|
|
|
|
|
});
|
|
|
|
|
//c.OrderActionsBy(o => o.RelativePath);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
options.SchemaFilter<EnumSchemaFilter>();
|
|
|
|
|
|
|
|
|
|
options.CustomOperationIds(apiDesc =>
|
|
|
|
|
{
|
|
|
|
|
var controllerAction = apiDesc.ActionDescriptor as ControllerActionDescriptor;
|
|
|
|
|
return controllerAction?.ControllerName + "-" + controllerAction?.ActionName;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
options.ResolveConflictingActions(apiDescription => apiDescription.First());
|
|
|
|
|
options.CustomSchemaIds(x => x.FullName);
|
|
|
|
|
options.DocInclusionPredicate((docName, description) => true);
|
|
|
|
|
|
|
|
|
|
var xmlFiles = Directory.GetFiles(Path.Combine(AppContext.BaseDirectory, "xml"), "*.xml");
|
|
|
|
|
if (xmlFiles.Length > 0)
|
|
|
|
|
{
|
|
|
|
|
foreach (var xmlFile in xmlFiles)
|
|
|
|
|
{
|
|
|
|
|
options.IncludeXmlComments(xmlFile, true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var server = new OpenApiServer()
|
|
|
|
|
{
|
|
|
|
|
Url = adminUiOptions.SwaggerConfiguration.Url,
|
|
|
|
|
Description = ""
|
|
|
|
|
};
|
|
|
|
|
server.Extensions.Add("extensions", new OpenApiObject
|
|
|
|
|
{
|
|
|
|
|
["copyright"] = new OpenApiString(adminUiOptions.SwaggerConfiguration.Footer)
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
//options.AddServer(server);
|
|
|
|
|
|
|
|
|
|
#region 添加设置Token的按钮
|
|
|
|
|
|
|
|
|
|
if (adminUiOptions.IdentityServerConfiguration.Enable)
|
|
|
|
|
{
|
|
|
|
|
//添加Jwt验证设置
|
|
|
|
|
options.AddSecurityRequirement(new OpenApiSecurityRequirement()
|
|
|
|
|
{
|
|
|
|
|
{
|
|
|
|
|
new OpenApiSecurityScheme
|
|
|
|
|
{
|
|
|
|
|
Reference = new OpenApiReference
|
|
|
|
|
{
|
|
|
|
|
Id = "oauth2",
|
|
|
|
|
Type = ReferenceType.SecurityScheme
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
new List<string>()
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
//统一认证
|
|
|
|
|
options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
|
|
|
|
|
{
|
|
|
|
|
Type = SecuritySchemeType.OAuth2,
|
|
|
|
|
Description = "oauth2登录授权",
|
|
|
|
|
Flows = new OpenApiOAuthFlows
|
|
|
|
|
{
|
|
|
|
|
Implicit = new OpenApiOAuthFlow
|
|
|
|
|
{
|
|
|
|
|
AuthorizationUrl = new Uri($"{adminUiOptions.IdentityServerConfiguration.Url}/connect/authorize"),
|
|
|
|
|
Scopes = new Dictionary<string, string>
|
|
|
|
|
{
|
|
|
|
|
{ "ATS.NonCustodial.Admin.Api", "ATS.NonCustodial.Admin.Api" }
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
//AuthorizationCode = new OpenApiOAuthFlow
|
|
|
|
|
//{
|
|
|
|
|
// AuthorizationUrl = new Uri($"{adminApiConfiguration.IdentityServerBaseUrl}/connect/authorize"),
|
|
|
|
|
// TokenUrl = new Uri($"{adminApiConfiguration.IdentityServerBaseUrl}/connect/token"),
|
|
|
|
|
// Scopes = new Dictionary<string, string> {
|
|
|
|
|
// {
|
|
|
|
|
// adminApiConfiguration.OidcApiName, adminApiConfiguration.ApiName }
|
|
|
|
|
// }
|
|
|
|
|
//}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
options.OperationFilter<AuthorizeCheckOperationFilter>();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
//添加Jwt验证设置
|
|
|
|
|
options.AddSecurityRequirement(new OpenApiSecurityRequirement()
|
|
|
|
|
{
|
|
|
|
|
{
|
|
|
|
|
new OpenApiSecurityScheme
|
|
|
|
|
{
|
|
|
|
|
Reference = new OpenApiReference
|
|
|
|
|
{
|
|
|
|
|
Id = "Bearer",
|
|
|
|
|
Type = ReferenceType.SecurityScheme
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
new List<string>()
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
|
|
|
|
|
{
|
|
|
|
|
Description = "Value: Bearer {token}",
|
|
|
|
|
Name = "Authorization",
|
|
|
|
|
In = ParameterLocation.Header,
|
|
|
|
|
Type = SecuritySchemeType.ApiKey
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion 添加设置Token的按钮
|
|
|
|
|
|
|
|
|
|
//注册Api过滤器
|
|
|
|
|
options.DocumentFilter<ApiToSwaggerFilter>();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion Swagger Api文档
|
|
|
|
|
|
|
|
|
|
//操作日志
|
|
|
|
|
if (adminUiOptions.LogConfiguration.EnableOperationLog) services.AddScoped<ILogHandler, LogHandler>();
|
|
|
|
|
|
|
|
|
|
#region 控制器
|
|
|
|
|
|
|
|
|
|
services.AddControllers(options =>
|
|
|
|
|
{
|
|
|
|
|
options.Filters.Add<ControllerExceptionFilter>();
|
|
|
|
|
options.Filters.Add<ValidateInputFilter>();
|
|
|
|
|
options.Filters.Add<ValidatePermissionAttribute>();
|
|
|
|
|
if (adminUiOptions.LogConfiguration.EnableOperationLog)
|
|
|
|
|
{
|
|
|
|
|
options.Filters.Add<ControllerLogFilter>();
|
|
|
|
|
}
|
|
|
|
|
//禁止去除ActionAsync后缀
|
|
|
|
|
//options.SuppressAsyncSuffixInActionNames = false;
|
|
|
|
|
})
|
|
|
|
|
//.AddFluentValidation(config =>
|
|
|
|
|
//{
|
|
|
|
|
// var assembly = Assembly.LoadFrom(Path.Combine(basePath, "ATS.NonCustodial.Admin.dll"));
|
|
|
|
|
// config.RegisterValidatorsFromAssembly(assembly);
|
|
|
|
|
//})
|
|
|
|
|
.AddNewtonsoftJson(options =>
|
|
|
|
|
{
|
|
|
|
|
//忽略循环引用
|
|
|
|
|
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
|
|
|
|
|
//使用驼峰 首字母小写
|
|
|
|
|
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
|
|
|
|
|
//设置时间格式
|
|
|
|
|
options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
|
|
|
|
|
})
|
|
|
|
|
.AddControllersAsServices();
|
|
|
|
|
|
|
|
|
|
#endregion 控制器
|
|
|
|
|
|
|
|
|
|
services.AddHttpClient();
|
|
|
|
|
|
|
|
|
|
#region 缓存
|
|
|
|
|
|
|
|
|
|
var cacheConfig = adminUiOptions.CacheConfiguration;
|
|
|
|
|
if (cacheConfig.Type == CacheTypeEnum.Redis)
|
|
|
|
|
{
|
|
|
|
|
var csredis = new CSRedis.CSRedisClient(cacheConfig.Redis.ConnectionString);
|
|
|
|
|
RedisHelper.Initialization(csredis);
|
|
|
|
|
services.AddSingleton<ICacheTool, RedisCacheTool>();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
services.AddMemoryCache();
|
|
|
|
|
services.AddSingleton<ICacheTool, MemoryCacheTool>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion 缓存
|
|
|
|
|
|
|
|
|
|
#region singalR
|
|
|
|
|
|
|
|
|
|
// Change to use Name as the user identifier for SignalR
|
|
|
|
|
// WARNING: This requires that the source of your JWT token
|
|
|
|
|
// ensures that the Name claim is unique!
|
|
|
|
|
// If the Name claim isn't unique, users could receive messages
|
|
|
|
|
// intended for a different user!
|
|
|
|
|
services.AddSingleton<IUserIdProvider, NameUserIdProvider>();
|
|
|
|
|
|
|
|
|
|
//SignalR https://docs.microsoft.com/zh-cn/aspnet/core/signalr/configuration?view=aspnetcore-6.0&tabs=dotnet
|
|
|
|
|
services
|
|
|
|
|
.AddSignalR()
|
|
|
|
|
.AddHubOptions<NonCustodialHub>(options =>
|
|
|
|
|
{
|
|
|
|
|
const int keepAliveIntervalInSeconds = 60;
|
|
|
|
|
options.EnableDetailedErrors = true;
|
|
|
|
|
//客户端发保持连接请求到服务端最长间隔,默认30秒
|
|
|
|
|
options.ClientTimeoutInterval = TimeSpan.FromSeconds(2 * keepAliveIntervalInSeconds);
|
|
|
|
|
options.HandshakeTimeout = TimeSpan.FromSeconds(keepAliveIntervalInSeconds);
|
|
|
|
|
//服务端发保持连接请求到客户端间隔,默认15秒
|
|
|
|
|
options.KeepAliveInterval = TimeSpan.FromSeconds(keepAliveIntervalInSeconds);
|
|
|
|
|
})
|
|
|
|
|
.AddJsonProtocol(options =>
|
|
|
|
|
{
|
|
|
|
|
options.PayloadSerializerOptions.PropertyNamingPolicy = null;
|
|
|
|
|
});
|
|
|
|
|
services.AddSingleton(typeof(IClientNotifyService), typeof(ClientNotifyService));
|
|
|
|
|
|
|
|
|
|
#endregion singalR
|
|
|
|
|
|
|
|
|
|
//性能分析 (https://miniprofiler.com/)
|
|
|
|
|
//默认的index.html页面可以从如下链接下载
|
|
|
|
|
//https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/master/src/Swashbuckle.AspNetCore.SwaggerUI/index.html
|
|
|
|
|
if (adminUiOptions.SwaggerConfiguration.EnableMiniProfiler) services.AddMiniProfiler();//AddComtorizedMiniProfiler();
|
|
|
|
|
|
|
|
|
|
//动态api
|
|
|
|
|
services.AddDynamicApi(options =>
|
|
|
|
|
{
|
|
|
|
|
var assemblies = DependencyContext.Default.RuntimeLibraries
|
|
|
|
|
.Where(a => a.Name.EndsWith("Service"))
|
|
|
|
|
.Select(o => Assembly.Load(new AssemblyName(o.Name))).ToArray();
|
|
|
|
|
options.AddAssemblyOptions(assemblies);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <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>
|
|
|
|
|
/// <param name="adminUiOptions"></param>
|
|
|
|
|
/// <param name="indexStream"></param>
|
|
|
|
|
public static void ConfigApp(IApplicationBuilder app, IWebHostEnvironment env, AdminUiOptions adminUiOptions, Stream? indexStream)
|
|
|
|
|
{
|
|
|
|
|
//IP限流
|
|
|
|
|
if (adminUiOptions.RateLimitConfiguration.Enable) app.UseIpRateLimiting();
|
|
|
|
|
|
|
|
|
|
if (env.IsDevelopment())
|
|
|
|
|
{
|
|
|
|
|
app.UseDeveloperExceptionPage();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
app.UseExceptionHandler("/Error");
|
|
|
|
|
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
|
|
|
|
app.UseHsts();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//静态文件
|
|
|
|
|
app.UseDefaultFiles();
|
|
|
|
|
|
|
|
|
|
app.UseHttpsRedirection();
|
|
|
|
|
app.UseStaticFiles();
|
|
|
|
|
app.UseUploadConfig();
|
|
|
|
|
|
|
|
|
|
//激活中间件,启用MiniProfiler服务,放在UseEndpoints方法之前
|
|
|
|
|
if (adminUiOptions.SwaggerConfiguration.EnableMiniProfiler) app.UseMiniProfiler();
|
|
|
|
|
//路由
|
|
|
|
|
app.UseRouting();
|
|
|
|
|
|
|
|
|
|
//跨域
|
|
|
|
|
app.UseCors(AdminConstant.requestPolicyName);
|
|
|
|
|
|
|
|
|
|
//认证
|
|
|
|
|
app.UseAuthentication();
|
|
|
|
|
|
|
|
|
|
//授权
|
|
|
|
|
app.UseAuthorization();
|
|
|
|
|
|
|
|
|
|
//配置端点(include signalR)
|
|
|
|
|
app.UseEndpoints(endpoints =>
|
|
|
|
|
{
|
|
|
|
|
endpoints.MapControllers();
|
|
|
|
|
|
|
|
|
|
//即时通讯
|
|
|
|
|
endpoints.MapHub<NonCustodialHub>("/nonCustodialHub", options =>
|
|
|
|
|
{
|
|
|
|
|
options.Transports =
|
|
|
|
|
HttpTransportType.WebSockets |
|
|
|
|
|
HttpTransportType.LongPolling;
|
|
|
|
|
options.LongPolling.PollTimeout = TimeSpan.FromSeconds(30);
|
|
|
|
|
}).RequireCors(t =>
|
|
|
|
|
t.WithOrigins(adminUiOptions.UrlsConfiguration.CorUrls.Any()
|
|
|
|
|
? adminUiOptions.UrlsConfiguration.CorUrls.ToArray()
|
|
|
|
|
: new string[] { })
|
|
|
|
|
.AllowAnyMethod()
|
|
|
|
|
.AllowAnyHeader()
|
|
|
|
|
.AllowCredentials()
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
//解决Ubuntu下Nginx代理不能获取IP问题
|
|
|
|
|
app.UseForwardedHeaders(new ForwardedHeadersOptions
|
|
|
|
|
{
|
|
|
|
|
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
#region Swagger Api文档
|
|
|
|
|
|
|
|
|
|
if (!env.IsDevelopment() && !adminUiOptions.SwaggerConfiguration.Enable) return;
|
|
|
|
|
|
|
|
|
|
app.UseSwagger();
|
|
|
|
|
app.UseSwaggerUI(c =>
|
|
|
|
|
{
|
|
|
|
|
typeof(ApiVersionEnum).GetEnumNames().OrderByDescending(e => e).ToList().ForEach(version =>
|
|
|
|
|
{
|
|
|
|
|
c.SwaggerEndpoint($"/swagger/{version}/swagger.json", $"ATS.NonCustodial.Admin {version}");
|
|
|
|
|
});
|
|
|
|
|
c.RoutePrefix = "";//直接根目录访问,如果是IIS发布可以注释该语句,并打开launchSettings.launchUrl
|
|
|
|
|
c.DocExpansion(Swashbuckle.AspNetCore.SwaggerUI.DocExpansion.None);//折叠Api
|
|
|
|
|
|
|
|
|
|
c.OAuthClientId(adminUiOptions.AdminApiConfiguration.OidcSwaggerUIClientId);
|
|
|
|
|
c.OAuthAppName(adminUiOptions.AdminApiConfiguration.ApiName);
|
|
|
|
|
c.OAuthUsePkce();
|
|
|
|
|
|
|
|
|
|
//c.DefaultModelsExpandDepth(-1);//不显示Models
|
|
|
|
|
if (!adminUiOptions.SwaggerConfiguration.EnableMiniProfiler) return;
|
|
|
|
|
// Gets or sets a Stream function for retrieving the swagger-ui page
|
|
|
|
|
if (indexStream != null) c.IndexStream = () => indexStream;
|
|
|
|
|
|
|
|
|
|
c.InjectJavascript("/swagger/mini-profiler.js?v=4.2.22+2.0");
|
|
|
|
|
c.InjectStylesheet("/swagger/mini-profiler.css?v=4.2.22+2.0");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
#endregion Swagger Api文档
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Program==> SerialLog and Kestral
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="args"></param>
|
|
|
|
|
/// <param name="host"></param>
|
|
|
|
|
/// <param name="webHost"></param>
|
|
|
|
|
/// <param name="webApplicationBuilder"></param>
|
|
|
|
|
/// <param name="adminUiOptions"></param>
|
|
|
|
|
public static void ConfigureProgram(string[] args, IHostBuilder host, IWebHostBuilder webHost, WebApplicationBuilder webApplicationBuilder, AdminUiOptions adminUiOptions)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
Log.Logger = new LoggerConfiguration().CreateBootstrapLogger();
|
|
|
|
|
|
|
|
|
|
// //configure SerialLog
|
|
|
|
|
// host.UseSerilog((hostContext, loggerConfig) =>
|
|
|
|
|
// {
|
|
|
|
|
// loggerConfig
|
|
|
|
|
// .ReadFrom.Configuration(hostContext.Configuration)
|
|
|
|
|
// .Enrich.WithProperty("ApplicationName", hostContext.HostingEnvironment.ApplicationName)
|
|
|
|
|
//#if DEBUG
|
|
|
|
|
// .WriteTo.Console();
|
|
|
|
|
//#endif
|
|
|
|
|
// });
|
|
|
|
|
|
|
|
|
|
//配置Kestrel服务器
|
|
|
|
|
webHost
|
|
|
|
|
.ConfigureAppConfiguration((hostContext, configApp) =>
|
|
|
|
|
{
|
|
|
|
|
var configurationRoot = configApp.Build();
|
|
|
|
|
configApp.AddJsonFile("configs/appsettings.json", optional: true, reloadOnChange: true);
|
|
|
|
|
configApp.AddJsonFile("configs/serilog.json", optional: true, reloadOnChange: true);
|
|
|
|
|
configApp.AddJsonFile("seeds/datadictionary.json", optional: true, reloadOnChange: true);
|
|
|
|
|
configApp.AddJsonFile("seeds/identitydata.json", optional: true, reloadOnChange: true);
|
|
|
|
|
configApp.AddJsonFile("seeds/swagger.json", optional: true, reloadOnChange: true);
|
|
|
|
|
|
|
|
|
|
var env = hostContext.HostingEnvironment;
|
|
|
|
|
configApp.AddJsonFile($"configs/appsettings.{env.EnvironmentName}.json", optional: true,
|
|
|
|
|
reloadOnChange: true);
|
|
|
|
|
configApp.AddJsonFile($"configs/serilog.{env.EnvironmentName}.json", optional: true,
|
|
|
|
|
reloadOnChange: true);
|
|
|
|
|
configApp.AddEnvironmentVariables();
|
|
|
|
|
configApp.AddCommandLine(args);
|
|
|
|
|
})
|
|
|
|
|
//https://docs.microsoft.com/zh-cn/dotnet/api/microsoft.aspnetcore.http.features.formoptions?view=aspnetcore-6.0
|
|
|
|
|
.ConfigureKestrel((context, options) =>
|
|
|
|
|
{
|
|
|
|
|
//https://docs.microsoft.com/zh-cn/dotnet/api/microsoft.aspnetcore.mvc.requestformlimitsattribute?view=aspnetcore-6.0
|
|
|
|
|
options.Limits.MaxConcurrentConnections = 100;
|
|
|
|
|
options.Limits.MaxConcurrentUpgradedConnections = 100;
|
|
|
|
|
//设置应用服务器Kestrel请求体最大为100MB
|
|
|
|
|
options.Limits.MaxRequestBodySize = int.MaxValue;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Log.Fatal(ex, "Host terminated unexpectedly");
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
Log.CloseAndFlush();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|