|
|
|
@ -0,0 +1,364 @@
|
|
|
|
|
using System.Security.Claims;
|
|
|
|
|
using Infrastructure.Filters;
|
|
|
|
|
using Infrastructure.HttpUserContext;
|
|
|
|
|
using Infrastructure.Options;
|
|
|
|
|
using Infrastructure.Repository;
|
|
|
|
|
using Infrastructure.Repository.Mongo;
|
|
|
|
|
using Infrastructure.Repository.Redis;
|
|
|
|
|
using Microsoft.AspNetCore.Authentication;
|
|
|
|
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
|
|
|
|
using Microsoft.AspNetCore.Authorization;
|
|
|
|
|
using Microsoft.AspNetCore.Hosting;
|
|
|
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
|
using MongoDB.Driver;
|
|
|
|
|
using Newtonsoft.Json;
|
|
|
|
|
using Newtonsoft.Json.Converters;
|
|
|
|
|
using Newtonsoft.Json.Serialization;
|
|
|
|
|
using Serilog;
|
|
|
|
|
using SqlSugar;
|
|
|
|
|
using SqlSugar.Extensions;
|
|
|
|
|
using StackExchange.Redis;
|
|
|
|
|
|
|
|
|
|
namespace Infrastructure.Extensions;
|
|
|
|
|
|
|
|
|
|
public static class ServiceCollectionExtensions
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 配置redis连接服务,以及redis数据访问仓储
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="services"></param>
|
|
|
|
|
/// <param name="configuration"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public static IServiceCollection AddDefaultRedis(this IServiceCollection services, IConfiguration configuration)
|
|
|
|
|
{
|
|
|
|
|
ArgumentNullException.ThrowIfNull(services);
|
|
|
|
|
ArgumentNullException.ThrowIfNull(configuration);
|
|
|
|
|
var redisOptions = configuration.GetSection(RedisOptions.Name).Get<RedisOptions>();
|
|
|
|
|
if (redisOptions is null || !redisOptions.IsEnable)
|
|
|
|
|
{
|
|
|
|
|
return services;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
services.TryAddScoped<IRedisBasketRepository, RedisBasketRepository>();
|
|
|
|
|
services.TryAddSingleton<ConnectionMultiplexer>(_ =>
|
|
|
|
|
{
|
|
|
|
|
var host = configuration["REDIS_HOST"] ?? redisOptions.Host;
|
|
|
|
|
ArgumentException.ThrowIfNullOrEmpty(host);
|
|
|
|
|
var password = configuration["REDIS_PASSWORD"] ?? redisOptions.Password;
|
|
|
|
|
ArgumentException.ThrowIfNullOrEmpty(password);
|
|
|
|
|
var serviceName = configuration["REDIS_SERVICE_NAME"] ?? redisOptions.ServiceName;
|
|
|
|
|
var connectionString = string.IsNullOrEmpty(serviceName)
|
|
|
|
|
? $"{host},password={password}"
|
|
|
|
|
: $"{host},password={password},serviceName={serviceName}";
|
|
|
|
|
var redisConfig = ConfigurationOptions.Parse(connectionString, true);
|
|
|
|
|
redisConfig.ResolveDns = true;
|
|
|
|
|
return ConnectionMultiplexer.Connect(redisConfig);
|
|
|
|
|
});
|
|
|
|
|
return services;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 配置认证服务,包含jwt认证
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="services"></param>
|
|
|
|
|
/// <param name="configuration"></param>
|
|
|
|
|
/// <param name="builderOptions">自定义认证服务配置</param>
|
|
|
|
|
/// <param name="configureAuthenticationOptions">配置认证选项</param>
|
|
|
|
|
/// <param name="configureJwtBearerOptions">配置jwt认证选项</param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public static IServiceCollection AddDefaultAuthentication(
|
|
|
|
|
this IServiceCollection services,
|
|
|
|
|
IConfiguration configuration,
|
|
|
|
|
Action<AuthenticationBuilder>? builderOptions = null,
|
|
|
|
|
Action<AuthenticationOptions>? configureAuthenticationOptions = null,
|
|
|
|
|
Action<JwtBearerOptions>? configureJwtBearerOptions = null)
|
|
|
|
|
{
|
|
|
|
|
ArgumentNullException.ThrowIfNull(services);
|
|
|
|
|
ArgumentNullException.ThrowIfNull(configuration);
|
|
|
|
|
var audienceOptions = configuration.GetSection(AudienceOptions.Name).Get<AudienceOptions>();
|
|
|
|
|
if (audienceOptions is null || !audienceOptions.IsEnable)
|
|
|
|
|
{
|
|
|
|
|
return services;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
services.TryAddSingleton<IPostConfigureOptions<JwtBearerOptions>, JwtBearerOptionsPostConfigureOptions>();
|
|
|
|
|
var key = configuration["AUDIENCE_KEY"] ?? audienceOptions.Secret;
|
|
|
|
|
ArgumentException.ThrowIfNullOrEmpty(key);
|
|
|
|
|
var buffer = Encoding.UTF8.GetBytes(key);
|
|
|
|
|
var securityKey = new SymmetricSecurityKey(buffer);
|
|
|
|
|
|
|
|
|
|
var tokenValidationParameters = new TokenValidationParameters()
|
|
|
|
|
{
|
|
|
|
|
ValidateIssuerSigningKey = true,
|
|
|
|
|
IssuerSigningKey = securityKey,
|
|
|
|
|
ValidIssuer = audienceOptions.Issuer,
|
|
|
|
|
ValidateIssuer = true,
|
|
|
|
|
ValidateAudience = true,
|
|
|
|
|
ValidAudience = audienceOptions.Audience,
|
|
|
|
|
ValidateLifetime = true,
|
|
|
|
|
ClockSkew = TimeSpan.FromSeconds(0),
|
|
|
|
|
RequireExpirationTime = true,
|
|
|
|
|
RoleClaimType = ClaimTypes.Role,
|
|
|
|
|
LifetimeValidator = (before, expires, token, parameters) =>
|
|
|
|
|
before < DateTime.UtcNow - parameters.ClockSkew && DateTime.UtcNow < expires + parameters.ClockSkew
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var builder = services.AddAuthentication(options =>
|
|
|
|
|
{
|
|
|
|
|
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
|
|
|
|
options.DefaultChallengeScheme = nameof(DefaultAuthenticationHandler);
|
|
|
|
|
options.DefaultForbidScheme = nameof(DefaultAuthenticationHandler);
|
|
|
|
|
configureAuthenticationOptions?.Invoke(options);
|
|
|
|
|
});
|
|
|
|
|
builder.AddScheme<AuthenticationSchemeOptions, DefaultAuthenticationHandler>(
|
|
|
|
|
nameof(DefaultAuthenticationHandler),
|
|
|
|
|
options => { });
|
|
|
|
|
builder.AddJwtBearer(options =>
|
|
|
|
|
{
|
|
|
|
|
options.TokenValidationParameters = tokenValidationParameters;
|
|
|
|
|
configureJwtBearerOptions?.Invoke(options);
|
|
|
|
|
});
|
|
|
|
|
builderOptions?.Invoke(builder);
|
|
|
|
|
return services;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 添加权限认证,权限Policy、Roles从配置中获取
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="services"></param>
|
|
|
|
|
/// <param name="configuration"></param>
|
|
|
|
|
/// <param name="configureAuthorizeBuilder"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public static IServiceCollection AddDefaultAuthorize(
|
|
|
|
|
this IServiceCollection services,
|
|
|
|
|
IConfiguration configuration,
|
|
|
|
|
Action<AuthorizationBuilder>? configureAuthorizeBuilder = null)
|
|
|
|
|
{
|
|
|
|
|
ArgumentNullException.ThrowIfNull(services);
|
|
|
|
|
ArgumentNullException.ThrowIfNull(configuration);
|
|
|
|
|
var audienceOptions = configuration.GetSection(AudienceOptions.Name).Get<AudienceOptions>();
|
|
|
|
|
if (audienceOptions is null || !audienceOptions.IsEnable)
|
|
|
|
|
{
|
|
|
|
|
return services;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var key = configuration["AUDIENCE_KEY"] ?? audienceOptions.Secret;
|
|
|
|
|
ArgumentException.ThrowIfNullOrEmpty(key);
|
|
|
|
|
var buffer = Encoding.UTF8.GetBytes(key);
|
|
|
|
|
var securityKey = new SymmetricSecurityKey(buffer);
|
|
|
|
|
var signingCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
|
|
|
|
|
|
|
|
|
|
services.TryAddSingleton(new JwtContext(
|
|
|
|
|
audienceOptions.Issuer,
|
|
|
|
|
audienceOptions.Audience,
|
|
|
|
|
audienceOptions.Duration,
|
|
|
|
|
signingCredentials));
|
|
|
|
|
|
|
|
|
|
var builder = services.AddAuthorizationBuilder();
|
|
|
|
|
builder.AddPolicy(audienceOptions.Policy!, policy =>
|
|
|
|
|
policy.RequireRole(audienceOptions.Roles!)
|
|
|
|
|
.Build());
|
|
|
|
|
configureAuthorizeBuilder?.Invoke(builder);
|
|
|
|
|
return services;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 配置controller,包含过滤器、json序列化以及模型验证等配置
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="services"></param>
|
|
|
|
|
/// <param name="configureControllers">配置Controllers</param>
|
|
|
|
|
/// <param name="configureMvcOptions">mvc配置选项</param>
|
|
|
|
|
/// <param name="configureMvcNewtonsoftJsonOptions">json序列化配置选项</param>
|
|
|
|
|
/// <param name="configureApiBehaviorOptions">api行为配置选项</param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public static IServiceCollection AddDefaultControllers(this IServiceCollection services,
|
|
|
|
|
Action<IMvcBuilder>? configureControllers = null,
|
|
|
|
|
Action<MvcOptions>? configureMvcOptions = null,
|
|
|
|
|
Action<MvcNewtonsoftJsonOptions>? configureMvcNewtonsoftJsonOptions = null,
|
|
|
|
|
Action<ApiBehaviorOptions>? configureApiBehaviorOptions = null)
|
|
|
|
|
{
|
|
|
|
|
ArgumentNullException.ThrowIfNull(services);
|
|
|
|
|
var mvcBuilder = services.AddControllers(options =>
|
|
|
|
|
{
|
|
|
|
|
options.Filters.Add<ExceptionsFilter>();
|
|
|
|
|
options.Filters.Add<IdempotencyFilter>();
|
|
|
|
|
configureMvcOptions?.Invoke(options);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
mvcBuilder.AddNewtonsoftJson(options =>
|
|
|
|
|
{
|
|
|
|
|
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
|
|
|
|
|
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
|
|
|
|
|
options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
|
|
|
|
|
options.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Local;
|
|
|
|
|
options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
|
|
|
|
|
options.SerializerSettings.Converters.Add(new StringEnumConverter());
|
|
|
|
|
configureMvcNewtonsoftJsonOptions?.Invoke(options);
|
|
|
|
|
});
|
|
|
|
|
mvcBuilder.ConfigureApiBehaviorOptions(options =>
|
|
|
|
|
{
|
|
|
|
|
options.InvalidModelStateResponseFactory = _ =>
|
|
|
|
|
{
|
|
|
|
|
var message = new MessageData(false, "the input value not valid", 400);
|
|
|
|
|
return new OkObjectResult(message.Serialize());
|
|
|
|
|
};
|
|
|
|
|
configureApiBehaviorOptions?.Invoke(options);
|
|
|
|
|
});
|
|
|
|
|
configureControllers?.Invoke(mvcBuilder);
|
|
|
|
|
return services;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 配置Aes加密对称加密服务
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="services"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public static IServiceCollection AddAesEncryption(this IServiceCollection services)
|
|
|
|
|
{
|
|
|
|
|
ArgumentNullException.ThrowIfNull(services);
|
|
|
|
|
services.TryAddSingleton<IEncryptionService, EncryptionService>();
|
|
|
|
|
return services;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 配置mongodb连接以及仓储
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="services"></param>
|
|
|
|
|
/// <param name="configuration"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public static IServiceCollection AddMongoDbSetup(
|
|
|
|
|
this IServiceCollection services,
|
|
|
|
|
IConfiguration configuration)
|
|
|
|
|
{
|
|
|
|
|
ArgumentNullException.ThrowIfNull(services);
|
|
|
|
|
|
|
|
|
|
ArgumentNullException.ThrowIfNull(configuration);
|
|
|
|
|
|
|
|
|
|
var mongoDbOptions = configuration.GetSection(MongoDbOptions.Name).Get<MongoDbOptions>();
|
|
|
|
|
if (mongoDbOptions is null || !mongoDbOptions.IsEnable)
|
|
|
|
|
{
|
|
|
|
|
return services;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
services.TryAddSingleton<IMongoDatabase>(_ =>
|
|
|
|
|
new MongoClient(mongoDbOptions.ConnectionString)
|
|
|
|
|
.GetDatabase(mongoDbOptions.Database));
|
|
|
|
|
services.TryAddScoped(typeof(IMongoRepositoryBase<,>), typeof(MongoRepositoryBase<,>));
|
|
|
|
|
return services;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 配置数据库仓储服务
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="services"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public static IServiceCollection AddDefaultRepositoryContext(this IServiceCollection services)
|
|
|
|
|
{
|
|
|
|
|
ArgumentNullException.ThrowIfNull(services);
|
|
|
|
|
services.TryAddScoped<IUnitOfWork, UnitOfWork>();
|
|
|
|
|
services.TryAddScoped(typeof(IRepositoryBase<,>), typeof(RepositoryBase<,>));
|
|
|
|
|
return services;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 配置sqlsugar ORM连接
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="services"></param>
|
|
|
|
|
/// <param name="configuration"></param>
|
|
|
|
|
/// <param name="hostEnvironment"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public static IServiceCollection AddDefaultSqlSugarSetup(
|
|
|
|
|
this IServiceCollection services,
|
|
|
|
|
IConfiguration configuration,
|
|
|
|
|
IWebHostEnvironment hostEnvironment)
|
|
|
|
|
{
|
|
|
|
|
ArgumentNullException.ThrowIfNull(services);
|
|
|
|
|
|
|
|
|
|
ArgumentNullException.ThrowIfNull(configuration);
|
|
|
|
|
|
|
|
|
|
ArgumentNullException.ThrowIfNull(hostEnvironment);
|
|
|
|
|
|
|
|
|
|
var sqlSugarOptions = configuration.GetSection(SqlSugarOptions.Name).Get<SqlSugarOptions>();
|
|
|
|
|
if (sqlSugarOptions is null || !sqlSugarOptions.IsEnable)
|
|
|
|
|
{
|
|
|
|
|
return services;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (sqlSugarOptions.SnowFlake?.IsEnable ?? false)
|
|
|
|
|
{
|
|
|
|
|
var workerId = configuration["SNOWFLAKES_WORKERID"]?.ObjToInt() ?? sqlSugarOptions.SnowFlake?.WorkerId;
|
|
|
|
|
ArgumentNullException.ThrowIfNull(workerId);
|
|
|
|
|
SnowFlakeSingle.WorkId = (int)workerId;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var server = configuration["DB_HOST"] ?? sqlSugarOptions.Server;
|
|
|
|
|
ArgumentException.ThrowIfNullOrEmpty(server);
|
|
|
|
|
|
|
|
|
|
var port = configuration["DB_PORT"] ?? sqlSugarOptions.Port.ToString();
|
|
|
|
|
ArgumentException.ThrowIfNullOrEmpty(port);
|
|
|
|
|
|
|
|
|
|
var database = configuration["DB_DATABASE"] ?? sqlSugarOptions.Database;
|
|
|
|
|
ArgumentException.ThrowIfNullOrEmpty(database);
|
|
|
|
|
|
|
|
|
|
var user = configuration["DB_USER"] ?? sqlSugarOptions.User;
|
|
|
|
|
ArgumentException.ThrowIfNullOrEmpty(user);
|
|
|
|
|
|
|
|
|
|
var password = configuration["DB_PASSWORD"] ?? sqlSugarOptions.Password;
|
|
|
|
|
ArgumentException.ThrowIfNullOrEmpty(password);
|
|
|
|
|
|
|
|
|
|
var connectionString = $"server={server};port={port};database={database};userid={user};password={password};";
|
|
|
|
|
|
|
|
|
|
var connectionConfig = new ConnectionConfig()
|
|
|
|
|
{
|
|
|
|
|
DbType = DbType.PostgreSQL,
|
|
|
|
|
ConnectionString = connectionString,
|
|
|
|
|
InitKeyType = InitKeyType.Attribute,
|
|
|
|
|
IsAutoCloseConnection = true,
|
|
|
|
|
MoreSettings = new ConnMoreSettings()
|
|
|
|
|
{
|
|
|
|
|
PgSqlIsAutoToLower = false,
|
|
|
|
|
PgSqlIsAutoToLowerCodeFirst = false,
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var sugarScope = new SqlSugarScope(connectionConfig, config =>
|
|
|
|
|
{
|
|
|
|
|
config.QueryFilter.AddTableFilter<IDeletable>(d => !d.IsDeleted);
|
|
|
|
|
if (hostEnvironment.IsDevelopment() || hostEnvironment.IsStaging())
|
|
|
|
|
{
|
|
|
|
|
config.Aop.OnLogExecuting = (sql, parameters) => { Log.Logger.Information(sql); };
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
services.AddSingleton<ISqlSugarClient>(sugarScope);
|
|
|
|
|
return services;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 配置jwt相关上下文
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="services"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public static IServiceCollection AddDefaultTokenContext(this IServiceCollection services)
|
|
|
|
|
{
|
|
|
|
|
ArgumentNullException.ThrowIfNull(services);
|
|
|
|
|
services.TryAddSingleton<JsonWebTokenHandler>();
|
|
|
|
|
services.TryAddSingleton<DefaultTokenHandler>();
|
|
|
|
|
return services;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 配置用户上下文服务 T为主键类型
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="services"></param>
|
|
|
|
|
/// <typeparam name="T"></typeparam>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public static IServiceCollection AddDefaultUserContext<T>(this IServiceCollection services) where T : IEquatable<T>
|
|
|
|
|
{
|
|
|
|
|
ArgumentNullException.ThrowIfNull(services);
|
|
|
|
|
services.AddHttpContextAccessor();
|
|
|
|
|
services.TryAddScoped(typeof(IUserContext<T>), typeof(UserContext<T>));
|
|
|
|
|
return services;
|
|
|
|
|
}
|
|
|
|
|
}
|