Merge branch 'refs/heads/multipleDbContext' into abp

# Conflicts:
#	Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarCoreExtensions.cs
This commit is contained in:
橙子
2024-11-19 21:44:57 +08:00
17 changed files with 853 additions and 781 deletions

View File

@@ -0,0 +1,45 @@
using Hangfire.Server;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Threading;
using Volo.Abp.Uow;
namespace Yi.Framework.BackgroundWorkers.Hangfire;
public class UnitOfWorkHangfireFilter : IServerFilter, ISingletonDependency
{
private const string CurrentJobUow = "HangfireUnitOfWork";
private readonly IUnitOfWorkManager _unitOfWorkManager;
public UnitOfWorkHangfireFilter(IUnitOfWorkManager unitOfWorkManager)
{
_unitOfWorkManager = unitOfWorkManager;
}
public void OnPerforming(PerformingContext context)
{
var uow = _unitOfWorkManager.Begin();
context.Items.Add(CurrentJobUow, uow);
}
public void OnPerformed(PerformedContext context)
{
AsyncHelper.RunSync(()=>OnPerformedAsync(context));
}
private async Task OnPerformedAsync(PerformedContext context)
{
if (context.Items.TryGetValue(CurrentJobUow, out var obj)
&& obj is IUnitOfWork uow)
{
if (context.Exception == null && !uow.IsCompleted)
{
await uow.CompleteAsync();
}
else
{
await uow.RollbackAsync();
}
uow.Dispose();
}
}
}

View File

@@ -1,11 +1,12 @@
using Microsoft.Extensions.DependencyInjection; using Hangfire;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.BackgroundWorkers; using Volo.Abp.BackgroundWorkers;
using Volo.Abp.BackgroundWorkers.Hangfire; using Volo.Abp.BackgroundWorkers.Hangfire;
namespace Yi.Framework.BackgroundWorkers.Hangfire; namespace Yi.Framework.BackgroundWorkers.Hangfire;
[DependsOn(typeof(AbpBackgroundWorkersHangfireModule))] [DependsOn(typeof(AbpBackgroundWorkersHangfireModule))]
public class YiFrameworkBackgroundWorkersHangfireModule:AbpModule public class YiFrameworkBackgroundWorkersHangfireModule : AbpModule
{ {
public override void PreConfigureServices(ServiceConfigurationContext context) public override void PreConfigureServices(ServiceConfigurationContext context)
{ {
@@ -17,13 +18,18 @@ public class YiFrameworkBackgroundWorkersHangfireModule:AbpModule
//定时任务自动注入Abp默认只有在Quartz才实现 //定时任务自动注入Abp默认只有在Quartz才实现
var backgroundWorkerManager = context.ServiceProvider.GetRequiredService<IBackgroundWorkerManager>(); var backgroundWorkerManager = context.ServiceProvider.GetRequiredService<IBackgroundWorkerManager>();
var works = context.ServiceProvider.GetServices<IHangfireBackgroundWorker>(); var works = context.ServiceProvider.GetServices<IHangfireBackgroundWorker>();
foreach (var work in works) foreach (var work in works)
{ {
//如果为空默认使用服务器本地utc时间 //如果为空默认使用服务器本地utc时间
work.TimeZone = work.TimeZone ?? TimeZoneInfo.Local; work.TimeZone ??= TimeZoneInfo.Local;
await backgroundWorkerManager.AddAsync(work); await backgroundWorkerManager.AddAsync(work);
} }
}
public override void OnPreApplicationInitialization(ApplicationInitializationContext context)
{
var services = context.ServiceProvider;
GlobalJobFilters.Filters.Add(services.GetRequiredService<UnitOfWorkHangfireFilter>());
} }
} }

View File

@@ -1,4 +1,5 @@
using SqlSugar; using SqlSugar;
using ArgumentException = System.ArgumentException;
namespace Yi.Framework.SqlSugarCore.Abstractions namespace Yi.Framework.SqlSugarCore.Abstractions
{ {
@@ -53,6 +54,5 @@ namespace Yi.Framework.SqlSugarCore.Abstractions
/// 开启Saas多租户 /// 开启Saas多租户
/// </summary> /// </summary>
public bool EnabledSaasMultiTenancy { get; set; } = false; public bool EnabledSaasMultiTenancy { get; set; } = false;
} }
} }

View File

@@ -1,24 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using SqlSugar;
namespace Yi.Framework.SqlSugarCore.Abstractions
{
public interface ISqlSugarDbConnectionCreator
{
DbConnOptions Options { get; }
Action<ISqlSugarClient> OnSqlSugarClientConfig { get; set; }
Action<object, DataAfterModel> DataExecuted { get; set; }
Action<object, DataFilterModel> DataExecuting { get; set; }
Action<string, SugarParameter[]> OnLogExecuting { get; set; }
Action<string, SugarParameter[]> OnLogExecuted { get; set; }
Action<PropertyInfo, EntityColumnInfo> EntityService { get; set; }
ConnectionConfig Build(Action<ConnectionConfig>? action = null);
void SetDbAop(ISqlSugarClient currentDb);
}
}

View File

@@ -10,14 +10,14 @@ namespace Yi.Framework.SqlSugarCore.Abstractions
{ {
public interface ISqlSugarDbContext public interface ISqlSugarDbContext
{ {
// IAbpLazyServiceProvider LazyServiceProvider { get; set; } /// <summary>
/// SqlSugarDb
/// </summary>
ISqlSugarClient SqlSugarClient { get; } ISqlSugarClient SqlSugarClient { get; }
DbConnOptions Options { get; }
/// <summary> /// <summary>
/// 数据库备份 /// 数据库备份
/// </summary> /// </summary>
void BackupDataBase(); void BackupDataBase();
void SetSqlSugarClient(ISqlSugarClient sqlSugarClient);
} }
} }

View File

@@ -0,0 +1,21 @@
using System.Reflection;
using SqlSugar;
namespace Yi.Framework.SqlSugarCore.Abstractions;
public interface ISqlSugarDbContextDependencies
{
/// <summary>
/// 执行顺序
/// </summary>
int ExecutionOrder { get; }
void OnSqlSugarClientConfig(ISqlSugarClient sqlSugarClient);
void DataExecuted(object oldValue, DataAfterModel entityInfo);
void DataExecuting(object oldValue, DataFilterModel entityInfo);
void OnLogExecuting(string sql, SugarParameter[] pars);
void OnLogExecuted(string sql, SugarParameter[] pars);
void EntityService(PropertyInfo propertyInfo, EntityColumnInfo entityColumnInfo);
}

View File

@@ -0,0 +1,207 @@
using System.Collections;
using System.Reflection;
using System.Text;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using SqlSugar;
using Volo.Abp.Auditing;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Entities.Events;
using Volo.Abp.Guids;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Users;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.SqlSugarCore;
public class DefaultSqlSugarDbContext : SqlSugarDbContext
{
protected DbConnOptions Options => LazyServiceProvider.LazyGetRequiredService<IOptions<DbConnOptions>>().Value;
protected ICurrentUser CurrentUser => LazyServiceProvider.GetRequiredService<ICurrentUser>();
protected IGuidGenerator GuidGenerator => LazyServiceProvider.LazyGetRequiredService<IGuidGenerator>();
protected ILoggerFactory Logger => LazyServiceProvider.LazyGetRequiredService<ILoggerFactory>();
protected ICurrentTenant CurrentTenant => LazyServiceProvider.LazyGetRequiredService<ICurrentTenant>();
protected IDataFilter DataFilter => LazyServiceProvider.LazyGetRequiredService<IDataFilter>();
protected virtual bool IsMultiTenantFilterEnabled => DataFilter?.IsEnabled<IMultiTenant>() ?? false;
protected virtual bool IsSoftDeleteFilterEnabled => DataFilter?.IsEnabled<ISoftDelete>() ?? false;
protected IEntityChangeEventHelper EntityChangeEventHelper =>
LazyServiceProvider.LazyGetService<IEntityChangeEventHelper>(NullEntityChangeEventHelper.Instance);
protected override void CustomDataFilter(ISqlSugarClient sqlSugarClient)
{
if (IsSoftDeleteFilterEnabled)
{
sqlSugarClient.QueryFilter.AddTableFilter<ISoftDelete>(u => u.IsDeleted == false);
}
if (IsMultiTenantFilterEnabled)
{
//表达式里只能有具体值,不能运算
var expressionCurrentTenant = CurrentTenant.Id ?? null;
sqlSugarClient.QueryFilter.AddTableFilter<IMultiTenant>(u => u.TenantId == expressionCurrentTenant);
}
}
public override void DataExecuting(object oldValue, DataFilterModel entityInfo)
{
//审计日志
switch (entityInfo.OperationType)
{
case DataFilterType.UpdateByObject:
if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.LastModificationTime)))
{
if (!DateTime.MinValue.Equals(oldValue))
{
entityInfo.SetValue(DateTime.Now);
}
}
else if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.LastModifierId)))
{
if (typeof(Guid?) == entityInfo.EntityColumnInfo.PropertyInfo.PropertyType)
{
if (CurrentUser.Id != null)
{
entityInfo.SetValue(CurrentUser.Id);
}
}
}
break;
case DataFilterType.InsertByObject:
if (entityInfo.PropertyName.Equals(nameof(IEntity<Guid>.Id)))
{
//类型为guid
if (typeof(Guid) == entityInfo.EntityColumnInfo.PropertyInfo.PropertyType)
{
//主键为空或者为默认最小值
if (Guid.Empty.Equals(oldValue))
{
entityInfo.SetValue(GuidGenerator.Create());
}
}
}
else if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreationTime)))
{
//为空或者为默认最小值
if (DateTime.MinValue.Equals(oldValue))
{
entityInfo.SetValue(DateTime.Now);
}
}
else if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreatorId)))
{
//类型为guid
if (typeof(Guid?) == entityInfo.EntityColumnInfo.PropertyInfo.PropertyType)
{
if (CurrentUser.Id is not null)
{
entityInfo.SetValue(CurrentUser.Id);
}
}
}
else if (entityInfo.PropertyName.Equals(nameof(IMultiTenant.TenantId)))
{
if (CurrentTenant.Id is not null)
{
entityInfo.SetValue(CurrentTenant.Id);
}
}
break;
}
//领域事件
switch (entityInfo.OperationType)
{
case DataFilterType.InsertByObject:
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
{
EntityChangeEventHelper.PublishEntityCreatedEvent(entityInfo.EntityValue);
}
break;
case DataFilterType.UpdateByObject:
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
{
//软删除,发布的是删除事件
if (entityInfo.EntityValue is ISoftDelete softDelete)
{
if (softDelete.IsDeleted == true)
{
EntityChangeEventHelper.PublishEntityDeletedEvent(entityInfo.EntityValue);
}
}
else
{
EntityChangeEventHelper.PublishEntityUpdatedEvent(entityInfo.EntityValue);
}
}
break;
case DataFilterType.DeleteByObject:
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
{
//这里sqlsugar有个特殊删除会返回批量的结果
if (entityInfo.EntityValue is IEnumerable entityValues)
{
foreach (var entityValue in entityValues)
{
EntityChangeEventHelper.PublishEntityDeletedEvent(entityValue);
}
}
}
break;
}
}
public override void OnLogExecuting(string sql, SugarParameter[] pars)
{
if (Options.EnabledSqlLog)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine();
sb.AppendLine("==========Yi-SQL执行:==========");
sb.AppendLine(UtilMethods.GetSqlString(DbType.SqlServer, sql, pars));
sb.AppendLine("===============================");
Logger.CreateLogger<DefaultSqlSugarDbContext>().LogDebug(sb.ToString());
}
}
public override void OnLogExecuted(string sql, SugarParameter[] pars)
{
if (Options.EnabledSqlLog)
{
var sqllog = $"=========Yi-SQL耗时{SqlSugarClient.Ado.SqlExecutionTime.TotalMilliseconds}毫秒=====";
Logger.CreateLogger<SqlSugarDbContext>().LogDebug(sqllog.ToString());
}
}
public override void EntityService(PropertyInfo propertyInfo, EntityColumnInfo entityColumnInfo)
{
if (propertyInfo.Name == nameof(IHasConcurrencyStamp.ConcurrencyStamp)) //带版本号并发更新
{
entityColumnInfo.IsEnableUpdateVersionValidation = true;
}
if (propertyInfo.PropertyType == typeof(ExtraPropertyDictionary))
{
entityColumnInfo.IsIgnore = true;
}
if (propertyInfo.Name == nameof(Entity<object>.Id))
{
entityColumnInfo.IsPrimarykey = true;
}
}
}

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@@ -9,28 +9,32 @@ using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.SqlSugarCore namespace Yi.Framework.SqlSugarCore
{ {
public static class SqlsugarCoreExtensions public static class SqlSugarCoreExtensions
{ {
public static IServiceCollection AddYiDbContext<DbContext>(this IServiceCollection service, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) where DbContext : class, ISqlSugarDbContext /// <summary>
/// 新增db对象可支持多个
/// </summary>
/// <param name="service"></param>
/// <param name="serviceLifetime"></param>
/// <typeparam name="TDbContext"></typeparam>
/// <returns></returns>
public static IServiceCollection AddYiDbContext<TDbContext>(this IServiceCollection service, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) where TDbContext : class, ISqlSugarDbContextDependencies
{ {
service.Replace(new ServiceDescriptor(typeof(ISqlSugarDbContext), typeof(DbContext), serviceLifetime)); service.AddTransient<ISqlSugarDbContextDependencies, TDbContext>();
return service; return service;
} }
public static IServiceCollection TryAddYiDbContext<DbContext>(this IServiceCollection service, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) where DbContext : class, ISqlSugarDbContext
/// <summary>
/// 新增db对象可支持多个
/// </summary>
/// <param name="service"></param>
/// <param name="options"></param>
/// <typeparam name="TDbContext"></typeparam>
/// <returns></returns>
public static IServiceCollection AddYiDbContext<TDbContext>(this IServiceCollection service, Action<DbConnOptions> options) where TDbContext : class, ISqlSugarDbContextDependencies
{ {
service.TryAdd(new ServiceDescriptor(typeof(ISqlSugarDbContext), typeof(DbContext), serviceLifetime)); service.Configure<DbConnOptions>(options.Invoke);
return service; service.AddYiDbContext<TDbContext>();
}
public static IServiceCollection AddYiDbContext<DbContext>(this IServiceCollection service, Action<DbConnOptions> options) where DbContext : class, ISqlSugarDbContext
{
service.Configure<DbConnOptions>(ops =>
{
options.Invoke(ops);
});
service.AddYiDbContext<DbContext>();
return service; return service;
} }
} }

View File

@@ -1,123 +0,0 @@
using System.Reflection;
using Microsoft.Extensions.Options;
using SqlSugar;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.SqlSugarCore
{
public class SqlSugarDbConnectionCreator: ISqlSugarDbConnectionCreator,ITransientDependency
{
public SqlSugarDbConnectionCreator(IOptions<DbConnOptions> options)
{
Options = options.Value;
}
public DbConnOptions Options { get; }
public void SetDbAop(ISqlSugarClient currentDb)
{
currentDb.Aop.OnLogExecuting = this.OnLogExecuting;
currentDb.Aop.OnLogExecuted = this.OnLogExecuted;
currentDb.Aop.DataExecuting = this.DataExecuting;
currentDb.Aop.DataExecuted = this.DataExecuted;
OnSqlSugarClientConfig(currentDb);
}
public ConnectionConfig Build(Action<ConnectionConfig>? action=null)
{
var dbConnOptions = Options;
#region options
if (dbConnOptions.DbType is null)
{
throw new ArgumentException("DbType配置为空");
}
var slavaConFig = new List<SlaveConnectionConfig>();
if (dbConnOptions.EnabledReadWrite)
{
if (dbConnOptions.ReadUrl is null)
{
throw new ArgumentException("读写分离为空");
}
var readCon = dbConnOptions.ReadUrl;
readCon.ForEach(s =>
{
//如果是动态saas分库这里的连接串都不能写死需要动态添加这里只配置共享库的连接
slavaConFig.Add(new SlaveConnectionConfig() { ConnectionString = s });
});
}
#endregion
#region config
var connectionConfig = new ConnectionConfig()
{
ConfigId= ConnectionStrings.DefaultConnectionStringName,
DbType = dbConnOptions.DbType ?? DbType.Sqlite,
ConnectionString = dbConnOptions.Url,
IsAutoCloseConnection = true,
SlaveConnectionConfigs = slavaConFig,
//设置codefirst非空值判断
ConfigureExternalServices = new ConfigureExternalServices
{
// 处理表
EntityNameService = (type, entity) =>
{
if (dbConnOptions.EnableUnderLine && !entity.DbTableName.Contains('_'))
entity.DbTableName = UtilMethods.ToUnderLine(entity.DbTableName);// 驼峰转下划线
},
EntityService = (c, p) =>
{
if (new NullabilityInfoContext()
.Create(c).WriteState is NullabilityState.Nullable)
{
p.IsNullable = true;
}
if (dbConnOptions.EnableUnderLine && !p.IsIgnore && !p.DbColumnName.Contains('_'))
p.DbColumnName = UtilMethods.ToUnderLine(p.DbColumnName);// 驼峰转下划线
EntityService(c, p);
}
},
//这里多租户有个坑,无效的
AopEvents = new AopEvents
{
DataExecuted = DataExecuted,
DataExecuting = DataExecuting,
OnLogExecuted = OnLogExecuted,
OnLogExecuting = OnLogExecuting
}
};
if (action is not null)
{
action.Invoke(connectionConfig);
}
#endregion
return connectionConfig;
}
[DisablePropertyInjection]
public Action<ISqlSugarClient> OnSqlSugarClientConfig { get; set; }
[DisablePropertyInjection]
public Action<object, DataAfterModel> DataExecuted { get; set; }
[DisablePropertyInjection]
public Action<object, DataFilterModel> DataExecuting { get; set; }
[DisablePropertyInjection]
public Action<string, SugarParameter[]> OnLogExecuting { get; set; }
[DisablePropertyInjection]
public Action<string, SugarParameter[]> OnLogExecuted { get; set; }
[DisablePropertyInjection]
public Action<PropertyInfo, EntityColumnInfo> EntityService { get; set; }
}
}

View File

@@ -1,379 +1,43 @@
using System.Collections; using System.Reflection;
using System.Reflection;
using System.Text;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using SqlSugar; using SqlSugar;
using Volo.Abp.Auditing;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Entities.Events;
using Volo.Abp.Guids;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Users;
using Yi.Framework.SqlSugarCore.Abstractions; using Yi.Framework.SqlSugarCore.Abstractions;
using Check = Volo.Abp.Check;
namespace Yi.Framework.SqlSugarCore namespace Yi.Framework.SqlSugarCore;
public abstract class SqlSugarDbContext : ISqlSugarDbContextDependencies
{ {
public class SqlSugarDbContext : ISqlSugarDbContext //属性注入
public IAbpLazyServiceProvider LazyServiceProvider { get; set; }
protected ISqlSugarClient SqlSugarClient { get;private set; }
public int ExecutionOrder => 0;
public void OnSqlSugarClientConfig(ISqlSugarClient sqlSugarClient)
{ {
/// <summary> SqlSugarClient = sqlSugarClient;
/// SqlSugar 客户端 CustomDataFilter(sqlSugarClient);
/// </summary> }
public ISqlSugarClient SqlSugarClient { get; private set; } protected virtual void CustomDataFilter(ISqlSugarClient sqlSugarClient)
{
}
public virtual void DataExecuted(object oldValue, DataAfterModel entityInfo)
{
}
protected ICurrentUser CurrentUser => LazyServiceProvider.GetRequiredService<ICurrentUser>(); public virtual void DataExecuting(object oldValue, DataFilterModel entityInfo)
private IAbpLazyServiceProvider LazyServiceProvider { get; } {
}
private IGuidGenerator GuidGenerator => LazyServiceProvider.LazyGetRequiredService<IGuidGenerator>(); public virtual void OnLogExecuting(string sql, SugarParameter[] pars)
private ILoggerFactory Logger => LazyServiceProvider.LazyGetRequiredService<ILoggerFactory>(); {
private ICurrentTenant CurrentTenant => LazyServiceProvider.LazyGetRequiredService<ICurrentTenant>(); }
protected IDataFilter DataFilter => LazyServiceProvider.LazyGetRequiredService<IDataFilter>();
protected virtual bool IsMultiTenantFilterEnabled => DataFilter?.IsEnabled<IMultiTenant>() ?? false;
protected virtual bool IsSoftDeleteFilterEnabled => DataFilter?.IsEnabled<ISoftDelete>() ?? false; public virtual void OnLogExecuted(string sql, SugarParameter[] pars)
{
}
private IEntityChangeEventHelper EntityChangeEventHelper => public virtual void EntityService(PropertyInfo propertyInfo, EntityColumnInfo entityColumnInfo)
LazyServiceProvider.LazyGetService<IEntityChangeEventHelper>(NullEntityChangeEventHelper.Instance); {
public DbConnOptions Options => LazyServiceProvider.LazyGetRequiredService<IOptions<DbConnOptions>>().Value;
private ISerializeService SerializeService => LazyServiceProvider.LazyGetRequiredService<ISerializeService>();
public void SetSqlSugarClient(ISqlSugarClient sqlSugarClient)
{
SqlSugarClient = sqlSugarClient;
}
public SqlSugarDbContext(IAbpLazyServiceProvider lazyServiceProvider)
{
LazyServiceProvider = lazyServiceProvider;
var connectionCreator = LazyServiceProvider.LazyGetRequiredService<ISqlSugarDbConnectionCreator>();
connectionCreator.OnSqlSugarClientConfig = OnSqlSugarClientConfig;
connectionCreator.EntityService = EntityService;
connectionCreator.DataExecuting = DataExecuting;
connectionCreator.DataExecuted = DataExecuted;
connectionCreator.OnLogExecuting = OnLogExecuting;
connectionCreator.OnLogExecuted = OnLogExecuted;
SqlSugarClient = new SqlSugarClient(connectionCreator.Build(action: options =>
{
options.ConnectionString = GetCurrentConnectionString();
options.DbType = GetCurrentDbType();
}));
//统一使用aop处理
connectionCreator.SetDbAop(SqlSugarClient);
//替换默认序列化器
SqlSugarClient.CurrentConnectionConfig.ConfigureExternalServices.SerializeService = SerializeService;
}
/// <summary>
/// db切换多库支持
/// </summary>
/// <returns></returns>
protected virtual string GetCurrentConnectionString()
{
var connectionStringResolver = LazyServiceProvider.LazyGetRequiredService<IConnectionStringResolver>();
var connectionString =
connectionStringResolver.ResolveAsync().ConfigureAwait(false).GetAwaiter().GetResult();
if (string.IsNullOrWhiteSpace(connectionString))
{
Check.NotNull(Options.Url, "dbUrl未配置");
}
return connectionString!;
}
protected virtual DbType GetCurrentDbType()
{
if (CurrentTenant.Name is not null)
{
var dbTypeFromTenantName = GetDbTypeFromTenantName(CurrentTenant.Name);
if (dbTypeFromTenantName is not null)
{
return dbTypeFromTenantName.Value;
}
}
Check.NotNull(Options.DbType, "默认DbType未配置");
return Options.DbType!.Value;
}
//根据租户name进行匹配db类型: Test_Sqlite[来自AI]
private DbType? GetDbTypeFromTenantName(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
return null;
}
// 查找下划线的位置
int underscoreIndex = name.LastIndexOf('_');
if (underscoreIndex == -1 || underscoreIndex == name.Length - 1)
{
return null;
}
// 提取 枚举 部分
string enumString = name.Substring(underscoreIndex + 1);
// 尝试将 尾缀 转换为枚举
if (Enum.TryParse<DbType>(enumString, out DbType result))
{
return result;
}
// 条件不满足时返回 null
return null;
}
/// <summary>
/// 上下文对象扩展
/// </summary>
/// <param name="sqlSugarClient"></param>
protected virtual void OnSqlSugarClientConfig(ISqlSugarClient sqlSugarClient)
{
//需自定义扩展
if (IsSoftDeleteFilterEnabled)
{
sqlSugarClient.QueryFilter.AddTableFilter<ISoftDelete>(u => u.IsDeleted == false);
}
if (IsMultiTenantFilterEnabled)
{
//表达式里只能有具体值,不能运算
var expressionCurrentTenant = CurrentTenant.Id ?? null;
sqlSugarClient.QueryFilter.AddTableFilter<IMultiTenant>(u => u.TenantId == expressionCurrentTenant);
}
CustomDataFilter(sqlSugarClient);
}
protected virtual void CustomDataFilter(ISqlSugarClient sqlSugarClient)
{
}
protected virtual void DataExecuted(object oldValue, DataAfterModel entityInfo)
{
}
/// <summary>
/// 数据
/// </summary>
/// <param name="oldValue"></param>
/// <param name="entityInfo"></param>
protected virtual void DataExecuting(object oldValue, DataFilterModel entityInfo)
{
//审计日志
switch (entityInfo.OperationType)
{
case DataFilterType.UpdateByObject:
if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.LastModificationTime)))
{
if (!DateTime.MinValue.Equals(oldValue))
{
entityInfo.SetValue(DateTime.Now);
}
}
else if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.LastModifierId)))
{
if (typeof(Guid?) == entityInfo.EntityColumnInfo.PropertyInfo.PropertyType)
{
if (CurrentUser.Id != null)
{
entityInfo.SetValue(CurrentUser.Id);
}
}
}
break;
case DataFilterType.InsertByObject:
if (entityInfo.PropertyName.Equals(nameof(IEntity<Guid>.Id)))
{
//类型为guid
if (typeof(Guid) == entityInfo.EntityColumnInfo.PropertyInfo.PropertyType)
{
//主键为空或者为默认最小值
if (Guid.Empty.Equals(oldValue))
{
entityInfo.SetValue(GuidGenerator.Create());
}
}
}
else if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreationTime)))
{
//为空或者为默认最小值
if (DateTime.MinValue.Equals(oldValue))
{
entityInfo.SetValue(DateTime.Now);
}
}
else if (entityInfo.PropertyName.Equals(nameof(IAuditedObject.CreatorId)))
{
//类型为guid
if (typeof(Guid?) == entityInfo.EntityColumnInfo.PropertyInfo.PropertyType)
{
if (CurrentUser.Id is not null)
{
entityInfo.SetValue(CurrentUser.Id);
}
}
}
else if (entityInfo.PropertyName.Equals(nameof(IMultiTenant.TenantId)))
{
if (CurrentTenant.Id is not null)
{
entityInfo.SetValue(CurrentTenant.Id);
}
}
break;
}
//领域事件
switch (entityInfo.OperationType)
{
case DataFilterType.InsertByObject:
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
{
EntityChangeEventHelper.PublishEntityCreatedEvent(entityInfo.EntityValue);
}
break;
case DataFilterType.UpdateByObject:
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
{
//软删除,发布的是删除事件
if (entityInfo.EntityValue is ISoftDelete softDelete)
{
if (softDelete.IsDeleted == true)
{
EntityChangeEventHelper.PublishEntityDeletedEvent(entityInfo.EntityValue);
}
}
else
{
EntityChangeEventHelper.PublishEntityUpdatedEvent(entityInfo.EntityValue);
}
}
break;
case DataFilterType.DeleteByObject:
if (entityInfo.PropertyName == nameof(IEntity<object>.Id))
{
//这里sqlsugar有个特殊删除会返回批量的结果
if (entityInfo.EntityValue is IEnumerable entityValues)
{
foreach (var entityValue in entityValues)
{
EntityChangeEventHelper.PublishEntityDeletedEvent(entityValue);
}
}
}
break;
}
}
/// <summary>
/// 日志
/// </summary>
/// <param name="sql"></param>
/// <param name="pars"></param>
protected virtual void OnLogExecuting(string sql, SugarParameter[] pars)
{
if (Options.EnabledSqlLog)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine();
sb.AppendLine("==========Yi-SQL执行:==========");
sb.AppendLine(UtilMethods.GetSqlString(DbType.SqlServer, sql, pars));
sb.AppendLine("===============================");
Logger.CreateLogger<SqlSugarDbContext>().LogDebug(sb.ToString());
}
}
/// <summary>
/// 日志
/// </summary>
/// <param name="sql"></param>
/// <param name="pars"></param>
protected virtual void OnLogExecuted(string sql, SugarParameter[] pars)
{
if (Options.EnabledSqlLog)
{
var sqllog = $"=========Yi-SQL耗时{SqlSugarClient.Ado.SqlExecutionTime.TotalMilliseconds}毫秒=====";
Logger.CreateLogger<SqlSugarDbContext>().LogDebug(sqllog.ToString());
}
}
/// <summary>
/// 实体配置
/// </summary>
/// <param name="property"></param>
/// <param name="column"></param>
protected virtual void EntityService(PropertyInfo property, EntityColumnInfo column)
{
if (property.Name == nameof(IHasConcurrencyStamp.ConcurrencyStamp)) //带版本号并发更新
{
column.IsEnableUpdateVersionValidation = true;
}
if (property.PropertyType == typeof(ExtraPropertyDictionary))
{
column.IsIgnore = true;
}
if (property.Name == nameof(Entity<object>.Id))
{
column.IsPrimarykey = true;
}
}
public void BackupDataBase()
{
string directoryName = "database_backup";
string fileName = DateTime.Now.ToString($"yyyyMMdd_HHmmss") + $"_{SqlSugarClient.Ado.Connection.Database}";
if (!Directory.Exists(directoryName))
{
Directory.CreateDirectory(directoryName);
}
switch (Options.DbType)
{
case DbType.MySql:
//MySql
SqlSugarClient.DbMaintenance.BackupDataBase(SqlSugarClient.Ado.Connection.Database,
$"{Path.Combine(directoryName, fileName)}.sql"); //mysql 只支持.net core
break;
case DbType.Sqlite:
//Sqlite
SqlSugarClient.DbMaintenance.BackupDataBase(null, $"{fileName}.db"); //sqlite 只支持.net core
break;
case DbType.SqlServer:
//SqlServer
SqlSugarClient.DbMaintenance.BackupDataBase(SqlSugarClient.Ado.Connection.Database,
$"{Path.Combine(directoryName, fileName)}.bak" /*服务器路径*/); //第一个参数库名
break;
default:
throw new NotImplementedException("其他数据库备份未实现");
}
}
} }
} }

View File

@@ -0,0 +1,287 @@
using System.Collections.Concurrent;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using SqlSugar;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;
using Volo.Abp.Threading;
using Volo.Abp.Users;
using Yi.Framework.SqlSugarCore.Abstractions;
using Check = Volo.Abp.Check;
namespace Yi.Framework.SqlSugarCore
{
public class SqlSugarDbContextFactory : ISqlSugarDbContext
{
/// <summary>
/// SqlSugar 客户端
/// </summary>
public ISqlSugarClient SqlSugarClient { get; private set; }
private IAbpLazyServiceProvider LazyServiceProvider { get; }
private ICurrentTenant CurrentTenant => LazyServiceProvider.LazyGetRequiredService<ICurrentTenant>();
public DbConnOptions Options => LazyServiceProvider.LazyGetRequiredService<IOptions<DbConnOptions>>().Value;
private ISerializeService SerializeService => LazyServiceProvider.LazyGetRequiredService<ISerializeService>();
private IEnumerable<ISqlSugarDbContextDependencies> SqlSugarDbContextDependencies =>
LazyServiceProvider.LazyGetRequiredService<IEnumerable<ISqlSugarDbContextDependencies>>();
private static readonly ConcurrentDictionary<string, ConnectionConfig> ConnectionConfigCache = new();
public SqlSugarDbContextFactory(IAbpLazyServiceProvider lazyServiceProvider)
{
LazyServiceProvider = lazyServiceProvider;
var connectionString = GetCurrentConnectionString();
//获取连接配置操作,需要进行缓存
var connectionConfig = ConnectionConfigCache.GetOrAdd(connectionString, (_) =>
BuildConnectionConfig(action: options =>
{
options.ConnectionString = connectionString;
options.DbType = GetCurrentDbType();
}));
SqlSugarClient = new SqlSugarClient(connectionConfig);
//生命周期以下都可以直接使用sqlsugardb了
// Aop及多租户连接字符串和类型需要单独设置
// Aop操作不能进行缓存
SetDbAop(SqlSugarClient);
}
/// <summary>
/// 构建Aop-sqlsugaraop在多租户模式中需单独设置
/// </summary>
/// <param name="sqlSugarClient"></param>
protected virtual void SetDbAop(ISqlSugarClient sqlSugarClient)
{
//替换默认序列化器
sqlSugarClient.CurrentConnectionConfig.ConfigureExternalServices.SerializeService = SerializeService;
//将所有ISqlSugarDbContextDependencies进行累加
Action<string, SugarParameter[]> onLogExecuting = null;
Action<string, SugarParameter[]> onLogExecuted = null;
Action<object, DataFilterModel> dataExecuting = null;
Action<object, DataAfterModel> dataExecuted = null;
Action<ISqlSugarClient> onSqlSugarClientConfig = null;
foreach (var dependency in SqlSugarDbContextDependencies.OrderBy(x => x.ExecutionOrder))
{
onLogExecuting += dependency.OnLogExecuting;
onLogExecuted += dependency.OnLogExecuted;
dataExecuting += dependency.DataExecuting;
dataExecuted += dependency.DataExecuted;
onSqlSugarClientConfig += dependency.OnSqlSugarClientConfig;
}
//最先存放db操作
onSqlSugarClientConfig(sqlSugarClient);
sqlSugarClient.Aop.OnLogExecuting =onLogExecuting;
sqlSugarClient.Aop.OnLogExecuted = onLogExecuted;
sqlSugarClient.Aop.DataExecuting =dataExecuting;
sqlSugarClient.Aop.DataExecuted =dataExecuted;
}
/// <summary>
/// 构建连接配置
/// </summary>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
protected virtual ConnectionConfig BuildConnectionConfig(Action<ConnectionConfig>? action = null)
{
var dbConnOptions = Options;
#region options
if (dbConnOptions.DbType is null)
{
throw new ArgumentException("DbType配置为空");
}
var slavaConFig = new List<SlaveConnectionConfig>();
if (dbConnOptions.EnabledReadWrite)
{
if (dbConnOptions.ReadUrl is null)
{
throw new ArgumentException("读写分离为空");
}
var readCon = dbConnOptions.ReadUrl;
readCon.ForEach(s =>
{
//如果是动态saas分库这里的连接串都不能写死需要动态添加这里只配置共享库的连接
slavaConFig.Add(new SlaveConnectionConfig() { ConnectionString = s });
});
}
#endregion
#region config
var connectionConfig = new ConnectionConfig()
{
ConfigId = ConnectionStrings.DefaultConnectionStringName,
DbType = dbConnOptions.DbType ?? DbType.Sqlite,
ConnectionString = dbConnOptions.Url,
IsAutoCloseConnection = true,
SlaveConnectionConfigs = slavaConFig,
//设置codefirst非空值判断
ConfigureExternalServices = new ConfigureExternalServices
{
// 处理表
EntityNameService = (type, entity) =>
{
if (dbConnOptions.EnableUnderLine && !entity.DbTableName.Contains('_'))
entity.DbTableName = UtilMethods.ToUnderLine(entity.DbTableName); // 驼峰转下划线
},
EntityService = (c, p) =>
{
if (new NullabilityInfoContext()
.Create(c).WriteState is NullabilityState.Nullable)
{
p.IsNullable = true;
}
if (dbConnOptions.EnableUnderLine && !p.IsIgnore && !p.DbColumnName.Contains('_'))
p.DbColumnName = UtilMethods.ToUnderLine(p.DbColumnName); // 驼峰转下划线
//将所有ISqlSugarDbContextDependencies的EntityService进行累加
//额外的实体服务需要这里配置,
Action<PropertyInfo, EntityColumnInfo> entityService = null;
foreach (var dependency in SqlSugarDbContextDependencies.OrderBy(x => x.ExecutionOrder))
{
entityService += dependency.EntityService;
}
entityService(c, p);
}
},
//这里多租户有个坑,这里配置是无效的
// AopEvents = new AopEvents
// {
// DataExecuted = DataExecuted,
// DataExecuting = DataExecuting,
// OnLogExecuted = OnLogExecuted,
// OnLogExecuting = OnLogExecuting
// }
};
if (action is not null)
{
action.Invoke(connectionConfig);
}
#endregion
return connectionConfig;
}
/// <summary>
/// db切换多库支持
/// </summary>
/// <returns></returns>
protected virtual string GetCurrentConnectionString()
{
var connectionStringResolver = LazyServiceProvider.LazyGetRequiredService<IConnectionStringResolver>();
var connectionString =
AsyncHelper.RunSync(() => connectionStringResolver.ResolveAsync());
if (string.IsNullOrWhiteSpace(connectionString))
{
Check.NotNull(Options.Url, "dbUrl未配置");
}
return connectionString!;
}
protected virtual DbType GetCurrentDbType()
{
if (CurrentTenant.Name is not null)
{
var dbTypeFromTenantName = GetDbTypeFromTenantName(CurrentTenant.Name);
if (dbTypeFromTenantName is not null)
{
return dbTypeFromTenantName.Value;
}
}
Check.NotNull(Options.DbType, "默认DbType未配置");
return Options.DbType!.Value;
}
//根据租户name进行匹配db类型: Test_Sqlite[来自AI]
private DbType? GetDbTypeFromTenantName(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
return null;
}
// 查找下划线的位置
int underscoreIndex = name.LastIndexOf('_');
if (underscoreIndex == -1 || underscoreIndex == name.Length - 1)
{
return null;
}
// 提取 枚举 部分
string enumString = name.Substring(underscoreIndex + 1);
// 尝试将 尾缀 转换为枚举
if (Enum.TryParse<DbType>(enumString, out DbType result))
{
return result;
}
// 条件不满足时返回 null
return null;
}
public virtual void BackupDataBase()
{
string directoryName = "database_backup";
string fileName = DateTime.Now.ToString($"yyyyMMdd_HHmmss") + $"_{SqlSugarClient.Ado.Connection.Database}";
if (!Directory.Exists(directoryName))
{
Directory.CreateDirectory(directoryName);
}
switch (Options.DbType)
{
case DbType.MySql:
//MySql
SqlSugarClient.DbMaintenance.BackupDataBase(SqlSugarClient.Ado.Connection.Database,
$"{Path.Combine(directoryName, fileName)}.sql"); //mysql 只支持.net core
break;
case DbType.Sqlite:
//Sqlite
SqlSugarClient.DbMaintenance.BackupDataBase(null, $"{fileName}.db"); //sqlite 只支持.net core
break;
case DbType.SqlServer:
//SqlServer
SqlSugarClient.DbMaintenance.BackupDataBase(SqlSugarClient.Ado.Connection.Database,
$"{Path.Combine(directoryName, fileName)}.bak" /*服务器路径*/); //第一个参数库名
break;
default:
throw new NotImplementedException("其他数据库备份未实现");
}
}
}
}

View File

@@ -13,8 +13,6 @@ namespace Yi.Framework.SqlSugarCore.Uow
{ {
public class UnitOfWorkSqlsugarDbContextProvider<TDbContext> : ISugarDbContextProvider<TDbContext> where TDbContext : ISqlSugarDbContext public class UnitOfWorkSqlsugarDbContextProvider<TDbContext> : ISugarDbContextProvider<TDbContext> where TDbContext : ISqlSugarDbContext
{ {
private readonly ISqlSugarDbConnectionCreator _dbConnectionCreator;
public ILogger<UnitOfWorkSqlsugarDbContextProvider<TDbContext>> Logger { get; set; } public ILogger<UnitOfWorkSqlsugarDbContextProvider<TDbContext>> Logger { get; set; }
public IServiceProvider ServiceProvider { get; set; } public IServiceProvider ServiceProvider { get; set; }
@@ -28,8 +26,7 @@ namespace Yi.Framework.SqlSugarCore.Uow
IUnitOfWorkManager unitOfWorkManager, IUnitOfWorkManager unitOfWorkManager,
IConnectionStringResolver connectionStringResolver, IConnectionStringResolver connectionStringResolver,
ICancellationTokenProvider cancellationTokenProvider, ICancellationTokenProvider cancellationTokenProvider,
ICurrentTenant currentTenant, ICurrentTenant currentTenant
ISqlSugarDbConnectionCreator dbConnectionCreator
) )
{ {
UnitOfWorkManager = unitOfWorkManager; UnitOfWorkManager = unitOfWorkManager;
@@ -37,7 +34,6 @@ namespace Yi.Framework.SqlSugarCore.Uow
CancellationTokenProvider = cancellationTokenProvider; CancellationTokenProvider = cancellationTokenProvider;
CurrentTenant = currentTenant; CurrentTenant = currentTenant;
Logger = NullLogger<UnitOfWorkSqlsugarDbContextProvider<TDbContext>>.Instance; Logger = NullLogger<UnitOfWorkSqlsugarDbContextProvider<TDbContext>>.Instance;
_dbConnectionCreator = dbConnectionCreator;
} }
//private static object _databaseApiLock = new object(); //private static object _databaseApiLock = new object();

View File

@@ -6,12 +6,9 @@ using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using SqlSugar; using SqlSugar;
using Volo.Abp;
using Volo.Abp.Auditing;
using Volo.Abp.Data; using Volo.Abp.Data;
using Volo.Abp.Domain; using Volo.Abp.Domain;
using Volo.Abp.Domain.Repositories; using Volo.Abp.Domain.Repositories;
using Volo.Abp.Modularity;
using Yi.Framework.SqlSugarCore.Abstractions; using Yi.Framework.SqlSugarCore.Abstractions;
using Yi.Framework.SqlSugarCore.Repositories; using Yi.Framework.SqlSugarCore.Repositories;
using Yi.Framework.SqlSugarCore.Uow; using Yi.Framework.SqlSugarCore.Uow;
@@ -28,7 +25,7 @@ namespace Yi.Framework.SqlSugarCore
var section = configuration.GetSection("DbConnOptions"); var section = configuration.GetSection("DbConnOptions");
Configure<DbConnOptions>(section); Configure<DbConnOptions>(section);
service.TryAddScoped<ISqlSugarDbContext, SqlSugarDbContext>(); service.TryAddScoped<ISqlSugarDbContext, SqlSugarDbContextFactory>();
//不开放sqlsugarClient //不开放sqlsugarClient
//service.AddTransient<ISqlSugarClient>(x => x.GetRequiredService<ISqlsugarDbContext>().SqlSugarClient); //service.AddTransient<ISqlSugarClient>(x => x.GetRequiredService<ISqlsugarDbContext>().SqlSugarClient);
@@ -47,6 +44,7 @@ namespace Yi.Framework.SqlSugarCore
//将默认db传递给abp连接字符串模块 //将默认db传递给abp连接字符串模块
Configure<AbpDbConnectionOptions>(x => { x.ConnectionStrings.Default = dbConfig.Url; }); Configure<AbpDbConnectionOptions>(x => { x.ConnectionStrings.Default = dbConfig.Url; });
context.Services.AddYiDbContext<DefaultSqlSugarDbContext>();
return Task.CompletedTask; return Task.CompletedTask;
} }
@@ -72,7 +70,6 @@ namespace Yi.Framework.SqlSugarCore
logger.LogInformation(sb.ToString()); logger.LogInformation(sb.ToString());
//Todo准备支持多租户种子数据及CodeFirst
if (options.EnabledCodeFirst) if (options.EnabledCodeFirst)
{ {

View File

@@ -15,7 +15,7 @@ namespace Yi.Framework.Rbac.SqlSugarCore
{ {
public override void ConfigureServices(ServiceConfigurationContext context) public override void ConfigureServices(ServiceConfigurationContext context)
{ {
context.Services.TryAddYiDbContext<YiRbacDbContext>(); context.Services.AddYiDbContext<YiRbacDbContext>();
} }
} }
} }

View File

@@ -1,5 +1,7 @@
using SqlSugar; using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.DependencyInjection; using SqlSugar;
using Volo.Abp.Data;
using Volo.Abp.Users;
using Yi.Framework.Rbac.Domain.Authorization; using Yi.Framework.Rbac.Domain.Authorization;
using Yi.Framework.Rbac.Domain.Entities; using Yi.Framework.Rbac.Domain.Entities;
using Yi.Framework.Rbac.Domain.Extensions; using Yi.Framework.Rbac.Domain.Extensions;
@@ -11,19 +13,14 @@ namespace Yi.Framework.Rbac.SqlSugarCore
{ {
public class YiRbacDbContext : SqlSugarDbContext public class YiRbacDbContext : SqlSugarDbContext
{ {
public YiRbacDbContext(IAbpLazyServiceProvider lazyServiceProvider) : base(lazyServiceProvider) protected IDataFilter DataFilter => LazyServiceProvider.LazyGetRequiredService<IDataFilter>();
{ protected ICurrentUser CurrentUser => LazyServiceProvider.GetRequiredService<ICurrentUser>();
}
protected override void CustomDataFilter(ISqlSugarClient sqlSugarClient) protected override void CustomDataFilter(ISqlSugarClient sqlSugarClient)
{ {
if (DataFilter.IsEnabled<IDataPermission>()) if (DataFilter.IsEnabled<IDataPermission>())
{ {
DataPermissionFilter(sqlSugarClient); DataPermissionFilter(sqlSugarClient);
} }
base.CustomDataFilter(sqlSugarClient);
} }

View File

@@ -6,10 +6,7 @@ using Yi.Framework.SqlSugarCore;
namespace Yi.Abp.SqlSugarCore namespace Yi.Abp.SqlSugarCore
{ {
public class YiDbContext : YiRbacDbContext public class YiDbContext : SqlSugarDbContext
{ {
public YiDbContext(IAbpLazyServiceProvider lazyServiceProvider) : base(lazyServiceProvider)
{
}
} }
} }

View File

@@ -22,10 +22,7 @@ using Volo.Abp.AspNetCore.Serilog;
using Volo.Abp.Auditing; using Volo.Abp.Auditing;
using Volo.Abp.Autofac; using Volo.Abp.Autofac;
using Volo.Abp.BackgroundJobs.Hangfire; using Volo.Abp.BackgroundJobs.Hangfire;
using Volo.Abp.BackgroundWorkers;
using Volo.Abp.BackgroundWorkers.Hangfire;
using Volo.Abp.Caching; using Volo.Abp.Caching;
using Volo.Abp.Hangfire;
using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy;
using Volo.Abp.Swashbuckle; using Volo.Abp.Swashbuckle;
using Yi.Abp.Application; using Yi.Abp.Application;
@@ -74,7 +71,7 @@ namespace Yi.Abp.Web
var configuration = context.Services.GetConfiguration(); var configuration = context.Services.GetConfiguration();
var host = context.Services.GetHostingEnvironment(); var host = context.Services.GetHostingEnvironment();
var service = context.Services; var service = context.Services;
//请求日志 //请求日志
Configure<AbpAuditingOptions>(optios => Configure<AbpAuditingOptions>(optios =>
{ {
@@ -87,236 +84,236 @@ namespace Yi.Abp.Web
options.IgnoredUrls.Add("/api/app/file/"); options.IgnoredUrls.Add("/api/app/file/");
options.IgnoredUrls.Add("/hangfire"); options.IgnoredUrls.Add("/hangfire");
}); });
//采用furion格式的规范化api默认不开启使用abp优雅的方式 //采用furion格式的规范化api默认不开启使用abp优雅的方式
//你没看错。。。 //你没看错。。。
//service.AddFurionUnifyResultApi(); //service.AddFurionUnifyResultApi();
//配置错误处理显示详情 //配置错误处理显示详情
Configure<AbpExceptionHandlingOptions>(options => { options.SendExceptionsDetailsToClients = true; }); Configure<AbpExceptionHandlingOptions>(options => { options.SendExceptionsDetailsToClients = true; });
//动态Api //动态Api
Configure<AbpAspNetCoreMvcOptions>(options => Configure<AbpAspNetCoreMvcOptions>(options =>
{
options.ConventionalControllers.Create(typeof(YiAbpApplicationModule).Assembly,
options => options.RemoteServiceName = "default");
options.ConventionalControllers.Create(typeof(YiFrameworkRbacApplicationModule).Assembly,
options => options.RemoteServiceName = "rbac");
options.ConventionalControllers.Create(typeof(YiFrameworkBbsApplicationModule).Assembly,
options => options.RemoteServiceName = "bbs");
options.ConventionalControllers.Create(typeof(YiFrameworkChatHubApplicationModule).Assembly,
options => options.RemoteServiceName = "chat-hub");
options.ConventionalControllers.Create(
typeof(YiFrameworkTenantManagementApplicationModule).Assembly,
options => options.RemoteServiceName = "tenant-management");
options.ConventionalControllers.Create(typeof(YiFrameworkCodeGenApplicationModule).Assembly,
options => options.RemoteServiceName = "code-gen");
//统一前缀
options.ConventionalControllers.ConventionalControllerSettings.ForEach(x => x.RootPath = "api/app");
});
//【NewtonsoftJson严重问题逆天】设置api格式留给后人铭记
// service.AddControllers().AddNewtonsoftJson(options =>
// {
// options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
// options.SerializerSettings.Converters.Add(new StringEnumConverter());
// });
//请使用微软的注意abp date又包了一层采用DefaultJsonTypeInfoResolver统一覆盖
Configure<JsonOptions>(options =>
{
options.JsonSerializerOptions.TypeInfoResolver = new DefaultJsonTypeInfoResolver();
options.JsonSerializerOptions.Converters.Add(new DatetimeJsonConverter());
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
});
//设置缓存不要过期默认滑动20分钟
Configure<AbpDistributedCacheOptions>(cacheOptions =>
{
cacheOptions.GlobalCacheEntryOptions.SlidingExpiration = null;
//缓存key前缀
cacheOptions.KeyPrefix = "Yi:";
});
Configure<AbpAntiForgeryOptions>(options => { options.AutoValidate = false; });
//Swagger
context.Services.AddYiSwaggerGen<YiAbpWebModule>(options =>
{
options.SwaggerDoc("default",
new OpenApiInfo { Title = "Yi.Framework.Abp", Version = "v1", Description = "集大成者" });
});
//跨域
context.Services.AddCors(options =>
{
options.AddPolicy(DefaultCorsPolicyName, builder =>
{ {
options.ConventionalControllers.Create(typeof(YiAbpApplicationModule).Assembly, builder
options => options.RemoteServiceName = "default"); .WithOrigins(
options.ConventionalControllers.Create(typeof(YiFrameworkRbacApplicationModule).Assembly, configuration["App:CorsOrigins"]!
options => options.RemoteServiceName = "rbac"); .Split(";", StringSplitOptions.RemoveEmptyEntries)
options.ConventionalControllers.Create(typeof(YiFrameworkBbsApplicationModule).Assembly, .Select(o => o.RemovePostFix("/"))
options => options.RemoteServiceName = "bbs"); .ToArray()
options.ConventionalControllers.Create(typeof(YiFrameworkChatHubApplicationModule).Assembly, )
options => options.RemoteServiceName = "chat-hub"); .WithAbpExposedHeaders()
options.ConventionalControllers.Create( .SetIsOriginAllowedToAllowWildcardSubdomains()
typeof(YiFrameworkTenantManagementApplicationModule).Assembly, .AllowAnyHeader()
options => options.RemoteServiceName = "tenant-management"); .AllowAnyMethod()
options.ConventionalControllers.Create(typeof(YiFrameworkCodeGenApplicationModule).Assembly, .AllowCredentials();
options => options.RemoteServiceName = "code-gen");
//统一前缀
options.ConventionalControllers.ConventionalControllerSettings.ForEach(x => x.RootPath = "api/app");
}); });
});
//【NewtonsoftJson严重问题逆天】设置api格式留给后人铭记 //配置多租户
// service.AddControllers().AddNewtonsoftJson(options => Configure<AbpTenantResolveOptions>(options =>
// { {
// options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss"; //基于cookie jwt不好用有坑
// options.SerializerSettings.Converters.Add(new StringEnumConverter()); options.TenantResolvers.Clear();
// }); options.TenantResolvers.Add(new HeaderTenantResolveContributor());
//options.TenantResolvers.Add(new HeaderTenantResolveContributor());
//options.TenantResolvers.Add(new CookieTenantResolveContributor());
//options.TenantResolvers.RemoveAll(x => x.Name == CookieTenantResolveContributor.ContributorName);
});
//请使用微软的注意abp date又包了一层采用DefaultJsonTypeInfoResolver统一覆盖 //配置Hangfire定时任务存储开启redis后优先使用redis
Configure<JsonOptions>(options => var redisConfiguration = configuration["Redis:Configuration"];
var redisEnabled = configuration["Redis:IsEnabled"];
context.Services.AddHangfire(config=>
{
if (redisEnabled.IsNullOrEmpty() || bool.Parse(redisEnabled))
{ {
options.JsonSerializerOptions.TypeInfoResolver = new DefaultJsonTypeInfoResolver(); config.UseRedisStorage(
options.JsonSerializerOptions.Converters.Add(new DatetimeJsonConverter()); ConnectionMultiplexer.Connect(redisConfiguration),
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); new RedisStorageOptions()
});
//设置缓存不要过期默认滑动20分钟
Configure<AbpDistributedCacheOptions>(cacheOptions =>
{
cacheOptions.GlobalCacheEntryOptions.SlidingExpiration = null;
//缓存key前缀
cacheOptions.KeyPrefix = "Yi:";
});
Configure<AbpAntiForgeryOptions>(options => { options.AutoValidate = false; });
//Swagger
context.Services.AddYiSwaggerGen<YiAbpWebModule>(options =>
{
options.SwaggerDoc("default",
new OpenApiInfo { Title = "Yi.Framework.Abp", Version = "v1", Description = "集大成者" });
});
//跨域
context.Services.AddCors(options =>
{
options.AddPolicy(DefaultCorsPolicyName, builder =>
{
builder
.WithOrigins(
configuration["App:CorsOrigins"]!
.Split(";", StringSplitOptions.RemoveEmptyEntries)
.Select(o => o.RemovePostFix("/"))
.ToArray()
)
.WithAbpExposedHeaders()
.SetIsOriginAllowedToAllowWildcardSubdomains()
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
//配置多租户
Configure<AbpTenantResolveOptions>(options =>
{
//基于cookie jwt不好用有坑
options.TenantResolvers.Clear();
options.TenantResolvers.Add(new HeaderTenantResolveContributor());
//options.TenantResolvers.Add(new HeaderTenantResolveContributor());
//options.TenantResolvers.Add(new CookieTenantResolveContributor());
//options.TenantResolvers.RemoveAll(x => x.Name == CookieTenantResolveContributor.ContributorName);
});
//配置Hangfire定时任务存储开启redis后优先使用redis
var redisConfiguration = configuration["Redis:Configuration"];
var redisEnabled = configuration["Redis:IsEnabled"];
context.Services.AddHangfire(config =>
{
if (redisEnabled.IsNullOrEmpty() || bool.Parse(redisEnabled))
{
config.UseRedisStorage(
ConnectionMultiplexer.Connect(redisConfiguration),
new RedisStorageOptions()
{
InvisibilityTimeout = TimeSpan.FromHours(1), //JOB允许执行1小时
Prefix = "Yi:HangfireJob:"
}).WithJobExpirationTimeout(TimeSpan.FromHours(1));
}
else
{
config.UseMemoryStorage();
}
});
//速率限制
//每60秒限制100个请求滑块添加分6段
service.AddRateLimiter(_ =>
{
_.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
_.OnRejected = (context, _) =>
{
if (context.Lease.TryGetMetadata(MetadataName.RetryAfter, out var retryAfter))
{ {
context.HttpContext.Response.Headers.RetryAfter = InvisibilityTimeout = TimeSpan.FromHours(1), //JOB允许执行1小时
((int)retryAfter.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo); Prefix = "Yi:HangfireJob:"
} }).WithJobExpirationTimeout(TimeSpan.FromHours(1));
}
else
{
config.UseMemoryStorage();
}
});
context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests; //速率限制
context.HttpContext.Response.WriteAsync("Too many requests. Please try again later."); //每60秒限制100个请求滑块添加分6段
service.AddRateLimiter(_ =>
{
_.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
_.OnRejected = (context, _) =>
{
if (context.Lease.TryGetMetadata(MetadataName.RetryAfter, out var retryAfter))
{
context.HttpContext.Response.Headers.RetryAfter =
((int)retryAfter.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo);
}
return new ValueTask(); context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
context.HttpContext.Response.WriteAsync("Too many requests. Please try again later.");
return new ValueTask();
};
//全局使用,链式表达式
_.GlobalLimiter = PartitionedRateLimiter.CreateChained(
PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
{
var userAgent = httpContext.Request.Headers.UserAgent.ToString();
return RateLimitPartition.GetSlidingWindowLimiter
(userAgent, _ =>
new SlidingWindowRateLimiterOptions
{
PermitLimit = 1000,
Window = TimeSpan.FromSeconds(60),
SegmentsPerWindow = 6,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst
});
}));
});
//jwt鉴权
var jwtOptions = configuration.GetSection(nameof(JwtOptions)).Get<JwtOptions>();
var refreshJwtOptions = configuration.GetSection(nameof(RefreshJwtOptions)).Get<RefreshJwtOptions>();
context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ClockSkew = TimeSpan.Zero,
ValidateIssuerSigningKey = true,
ValidIssuer = jwtOptions.Issuer,
ValidAudience = jwtOptions.Audience,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtOptions.SecurityKey))
}; };
options.Events = new JwtBearerEvents
//全局使用,链式表达式
_.GlobalLimiter = PartitionedRateLimiter.CreateChained(
PartitionedRateLimiter.Create<HttpContext, string>(httpContext =>
{
var userAgent = httpContext.Request.Headers.UserAgent.ToString();
return RateLimitPartition.GetSlidingWindowLimiter
(userAgent, _ =>
new SlidingWindowRateLimiterOptions
{
PermitLimit = 1000,
Window = TimeSpan.FromSeconds(60),
SegmentsPerWindow = 6,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst
});
}));
});
//jwt鉴权
var jwtOptions = configuration.GetSection(nameof(JwtOptions)).Get<JwtOptions>();
var refreshJwtOptions = configuration.GetSection(nameof(RefreshJwtOptions)).Get<RefreshJwtOptions>();
context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{ {
options.TokenValidationParameters = new TokenValidationParameters OnMessageReceived = context =>
{ {
ClockSkew = TimeSpan.Zero, //优先Query中获取再去cookies中获取
ValidateIssuerSigningKey = true, var accessToken = context.Request.Query["access_token"];
ValidIssuer = jwtOptions.Issuer, if (!string.IsNullOrEmpty(accessToken))
ValidAudience = jwtOptions.Audience,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtOptions.SecurityKey))
};
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{ {
//优先Query中获取再去cookies中获取 context.Token = accessToken;
var accessToken = context.Request.Query["access_token"]; }
if (!string.IsNullOrEmpty(accessToken)) else
{
if (context.Request.Cookies.TryGetValue("Token", out var cookiesToken))
{ {
context.Token = accessToken; context.Token = cookiesToken;
}
else
{
if (context.Request.Cookies.TryGetValue("Token",out var cookiesToken))
{
context.Token = cookiesToken;
}
} }
}
return Task.CompletedTask;
}
};
})
.AddJwtBearer(TokenTypeConst.Refresh, options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ClockSkew = TimeSpan.Zero,
ValidateIssuerSigningKey = true,
ValidIssuer = refreshJwtOptions.Issuer,
ValidAudience = refreshJwtOptions.Audience,
IssuerSigningKey =
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(refreshJwtOptions.SecurityKey))
};
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var refresh_token = context.Request.Headers["refresh_token"];
if (!string.IsNullOrEmpty(refresh_token))
{
context.Token = refresh_token;
return Task.CompletedTask; return Task.CompletedTask;
} }
};
}) var refreshToken = context.Request.Query["refresh_token"];
.AddJwtBearer(TokenTypeConst.Refresh, options => if (!string.IsNullOrEmpty(refreshToken))
{
options.TokenValidationParameters = new TokenValidationParameters
{
ClockSkew = TimeSpan.Zero,
ValidateIssuerSigningKey = true,
ValidIssuer = refreshJwtOptions.Issuer,
ValidAudience = refreshJwtOptions.Audience,
IssuerSigningKey =
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(refreshJwtOptions.SecurityKey))
};
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{ {
var refresh_token = context.Request.Headers["refresh_token"]; context.Token = refreshToken;
if (!string.IsNullOrEmpty(refresh_token))
{
context.Token = refresh_token;
return Task.CompletedTask;
}
var refreshToken = context.Request.Query["refresh_token"];
if (!string.IsNullOrEmpty(refreshToken))
{
context.Token = refreshToken;
}
return Task.CompletedTask;
} }
};
})
.AddQQ(options => { configuration.GetSection("OAuth:QQ").Bind(options); })
.AddGitee(options => { configuration.GetSection("OAuth:Gitee").Bind(options); });
//授权 return Task.CompletedTask;
context.Services.AddAuthorization(); }
};
})
.AddQQ(options => { configuration.GetSection("OAuth:QQ").Bind(options); })
.AddGitee(options => { configuration.GetSection("OAuth:Gitee").Bind(options); });
//授权
context.Services.AddAuthorization();
return Task.CompletedTask;
}
return Task.CompletedTask;
}
public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context) public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context)
@@ -373,12 +370,13 @@ namespace Yi.Abp.Web
//日志记录 //日志记录
app.UseAbpSerilogEnrichers(); app.UseAbpSerilogEnrichers();
//Hangfire定时任务面板可配置授权意框架支持jwt //Hangfire定时任务面板可配置授权意框架支持jwt
app.UseAbpHangfireDashboard("/hangfire", options => app.UseAbpHangfireDashboard("/hangfire",
{ options =>
options.AsyncAuthorization = new[] { new YiTokenAuthorizationFilter(app.ApplicationServices) }; {
}); options.AsyncAuthorization = new[] { new YiTokenAuthorizationFilter(app.ApplicationServices) };
});
//终节点 //终节点
app.UseConfiguredEndpoints(); app.UseConfiguredEndpoints();