diff --git a/Yi.Abp.Net8/framework/Yi.Framework.BackgroundWorkers.Hangfire/UnitOfWorkHangfireFilter.cs b/Yi.Abp.Net8/framework/Yi.Framework.BackgroundWorkers.Hangfire/UnitOfWorkHangfireFilter.cs new file mode 100644 index 00000000..47748c22 --- /dev/null +++ b/Yi.Abp.Net8/framework/Yi.Framework.BackgroundWorkers.Hangfire/UnitOfWorkHangfireFilter.cs @@ -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(); + } + } +} \ No newline at end of file diff --git a/Yi.Abp.Net8/framework/Yi.Framework.BackgroundWorkers.Hangfire/YiFrameworkBackgroundWorkersHangfireModule.cs b/Yi.Abp.Net8/framework/Yi.Framework.BackgroundWorkers.Hangfire/YiFrameworkBackgroundWorkersHangfireModule.cs index d5378d3f..fce1a566 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.BackgroundWorkers.Hangfire/YiFrameworkBackgroundWorkersHangfireModule.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.BackgroundWorkers.Hangfire/YiFrameworkBackgroundWorkersHangfireModule.cs @@ -1,11 +1,12 @@ -using Microsoft.Extensions.DependencyInjection; +using Hangfire; +using Microsoft.Extensions.DependencyInjection; using Volo.Abp.BackgroundWorkers; using Volo.Abp.BackgroundWorkers.Hangfire; namespace Yi.Framework.BackgroundWorkers.Hangfire; [DependsOn(typeof(AbpBackgroundWorkersHangfireModule))] -public class YiFrameworkBackgroundWorkersHangfireModule:AbpModule +public class YiFrameworkBackgroundWorkersHangfireModule : AbpModule { public override void PreConfigureServices(ServiceConfigurationContext context) { @@ -17,13 +18,18 @@ public class YiFrameworkBackgroundWorkersHangfireModule:AbpModule //定时任务自动注入,Abp默认只有在Quartz才实现 var backgroundWorkerManager = context.ServiceProvider.GetRequiredService(); var works = context.ServiceProvider.GetServices(); - + foreach (var work in works) { //如果为空,默认使用服务器本地utc时间 - work.TimeZone = work.TimeZone ?? TimeZoneInfo.Local; + work.TimeZone ??= TimeZoneInfo.Local; await backgroundWorkerManager.AddAsync(work); } + } + public override void OnPreApplicationInitialization(ApplicationInitializationContext context) + { + var services = context.ServiceProvider; + GlobalJobFilters.Filters.Add(services.GetRequiredService()); } } \ No newline at end of file diff --git a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/DbConnOptions.cs b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/DbConnOptions.cs index db6be5b3..6533950c 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/DbConnOptions.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/DbConnOptions.cs @@ -1,4 +1,5 @@ using SqlSugar; +using ArgumentException = System.ArgumentException; namespace Yi.Framework.SqlSugarCore.Abstractions { @@ -53,6 +54,5 @@ namespace Yi.Framework.SqlSugarCore.Abstractions /// 开启Saas多租户 /// public bool EnabledSaasMultiTenancy { get; set; } = false; - } -} +} \ No newline at end of file diff --git a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/ISqlSugarDbConnectionCreator.cs b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/ISqlSugarDbConnectionCreator.cs deleted file mode 100644 index dc5c06f1..00000000 --- a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/ISqlSugarDbConnectionCreator.cs +++ /dev/null @@ -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 OnSqlSugarClientConfig { get; set; } - Action DataExecuted { get; set; } - Action DataExecuting { get; set; } - Action OnLogExecuting { get; set; } - Action OnLogExecuted { get; set; } - Action EntityService { get; set; } - - ConnectionConfig Build(Action? action = null); - void SetDbAop(ISqlSugarClient currentDb); - } -} diff --git a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/ISqlSugarDbContext.cs b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/ISqlSugarDbContext.cs index 8fc6afa5..cd4f9dce 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/ISqlSugarDbContext.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/ISqlSugarDbContext.cs @@ -10,14 +10,14 @@ namespace Yi.Framework.SqlSugarCore.Abstractions { public interface ISqlSugarDbContext { - // IAbpLazyServiceProvider LazyServiceProvider { get; set; } + /// + /// SqlSugarDb + /// ISqlSugarClient SqlSugarClient { get; } - DbConnOptions Options { get; } /// /// 数据库备份 /// void BackupDataBase(); - void SetSqlSugarClient(ISqlSugarClient sqlSugarClient); } } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/ISqlSugarDbContextDependencies.cs b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/ISqlSugarDbContextDependencies.cs new file mode 100644 index 00000000..f41bb88d --- /dev/null +++ b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore.Abstractions/ISqlSugarDbContextDependencies.cs @@ -0,0 +1,21 @@ +using System.Reflection; +using SqlSugar; + +namespace Yi.Framework.SqlSugarCore.Abstractions; + +public interface ISqlSugarDbContextDependencies +{ + /// + /// 执行顺序 + /// + 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); +} \ No newline at end of file diff --git a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/DefaultSqlSugarDbContext.cs b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/DefaultSqlSugarDbContext.cs new file mode 100644 index 00000000..eadefe24 --- /dev/null +++ b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/DefaultSqlSugarDbContext.cs @@ -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>().Value; + protected ICurrentUser CurrentUser => LazyServiceProvider.GetRequiredService(); + protected IGuidGenerator GuidGenerator => LazyServiceProvider.LazyGetRequiredService(); + protected ILoggerFactory Logger => LazyServiceProvider.LazyGetRequiredService(); + protected ICurrentTenant CurrentTenant => LazyServiceProvider.LazyGetRequiredService(); + protected IDataFilter DataFilter => LazyServiceProvider.LazyGetRequiredService(); + protected virtual bool IsMultiTenantFilterEnabled => DataFilter?.IsEnabled() ?? false; + protected virtual bool IsSoftDeleteFilterEnabled => DataFilter?.IsEnabled() ?? false; + + protected IEntityChangeEventHelper EntityChangeEventHelper => + LazyServiceProvider.LazyGetService(NullEntityChangeEventHelper.Instance); + + protected override void CustomDataFilter(ISqlSugarClient sqlSugarClient) + { + if (IsSoftDeleteFilterEnabled) + { + sqlSugarClient.QueryFilter.AddTableFilter(u => u.IsDeleted == false); + } + + if (IsMultiTenantFilterEnabled) + { + //表达式里只能有具体值,不能运算 + var expressionCurrentTenant = CurrentTenant.Id ?? null; + sqlSugarClient.QueryFilter.AddTableFilter(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.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.Id)) + { + EntityChangeEventHelper.PublishEntityCreatedEvent(entityInfo.EntityValue); + } + + break; + case DataFilterType.UpdateByObject: + if (entityInfo.PropertyName == nameof(IEntity.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.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().LogDebug(sb.ToString()); + } + } + + public override void OnLogExecuted(string sql, SugarParameter[] pars) + { + if (Options.EnabledSqlLog) + { + var sqllog = $"=========Yi-SQL耗时{SqlSugarClient.Ado.SqlExecutionTime.TotalMilliseconds}毫秒====="; + Logger.CreateLogger().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.Id)) + { + entityColumnInfo.IsPrimarykey = true; + } + } +} \ No newline at end of file diff --git a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarCoreExtensions.cs b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarCoreExtensions.cs index a828a3d4..0c5057f8 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarCoreExtensions.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarCoreExtensions.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -9,28 +9,32 @@ using Yi.Framework.SqlSugarCore.Abstractions; namespace Yi.Framework.SqlSugarCore { - public static class SqlsugarCoreExtensions + public static class SqlSugarCoreExtensions { - public static IServiceCollection AddYiDbContext(this IServiceCollection service, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) where DbContext : class, ISqlSugarDbContext + /// + /// 新增db对象,可支持多个 + /// + /// + /// + /// + /// + public static IServiceCollection AddYiDbContext(this IServiceCollection service, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) where TDbContext : class, ISqlSugarDbContextDependencies { - service.Replace(new ServiceDescriptor(typeof(ISqlSugarDbContext), typeof(DbContext), serviceLifetime)); + service.AddTransient(); return service; } - public static IServiceCollection TryAddYiDbContext(this IServiceCollection service, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) where DbContext : class, ISqlSugarDbContext + + /// + /// 新增db对象,可支持多个 + /// + /// + /// + /// + /// + public static IServiceCollection AddYiDbContext(this IServiceCollection service, Action options) where TDbContext : class, ISqlSugarDbContextDependencies { - service.TryAdd(new ServiceDescriptor(typeof(ISqlSugarDbContext), typeof(DbContext), serviceLifetime)); - return service; - } - - - public static IServiceCollection AddYiDbContext(this IServiceCollection service, Action options) where DbContext : class, ISqlSugarDbContext - { - - service.Configure(ops => - { - options.Invoke(ops); - }); - service.AddYiDbContext(); + service.Configure(options.Invoke); + service.AddYiDbContext(); return service; } } diff --git a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarDbConnectionCreator.cs b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarDbConnectionCreator.cs deleted file mode 100644 index d62172df..00000000 --- a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarDbConnectionCreator.cs +++ /dev/null @@ -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 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? action=null) - { - var dbConnOptions = Options; - #region 组装options - if (dbConnOptions.DbType is null) - { - throw new ArgumentException("DbType配置为空"); - } - var slavaConFig = new List(); - 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 OnSqlSugarClientConfig { get; set; } - - [DisablePropertyInjection] - public Action DataExecuted { get; set; } - - [DisablePropertyInjection] - public Action DataExecuting { get; set; } - - [DisablePropertyInjection] - public Action OnLogExecuting { get; set; } - - [DisablePropertyInjection] - public Action OnLogExecuted { get; set; } - - [DisablePropertyInjection] - public Action EntityService { get; set; } - } -} diff --git a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarDbContext.cs b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarDbContext.cs index 0eaf3481..d510aef3 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarDbContext.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarDbContext.cs @@ -1,379 +1,43 @@ -using System.Collections; -using System.Reflection; -using System.Text; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; +using System.Reflection; 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; -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) { - /// - /// SqlSugar 客户端 - /// - public ISqlSugarClient SqlSugarClient { get; private set; } + SqlSugarClient = sqlSugarClient; + CustomDataFilter(sqlSugarClient); + } + protected virtual void CustomDataFilter(ISqlSugarClient sqlSugarClient) + { + } + + public virtual void DataExecuted(object oldValue, DataAfterModel entityInfo) + { + } - protected ICurrentUser CurrentUser => LazyServiceProvider.GetRequiredService(); - private IAbpLazyServiceProvider LazyServiceProvider { get; } + public virtual void DataExecuting(object oldValue, DataFilterModel entityInfo) + { + } - private IGuidGenerator GuidGenerator => LazyServiceProvider.LazyGetRequiredService(); - private ILoggerFactory Logger => LazyServiceProvider.LazyGetRequiredService(); - private ICurrentTenant CurrentTenant => LazyServiceProvider.LazyGetRequiredService(); - protected IDataFilter DataFilter => LazyServiceProvider.LazyGetRequiredService(); - protected virtual bool IsMultiTenantFilterEnabled => DataFilter?.IsEnabled() ?? false; + public virtual void OnLogExecuting(string sql, SugarParameter[] pars) + { + } - protected virtual bool IsSoftDeleteFilterEnabled => DataFilter?.IsEnabled() ?? false; + public virtual void OnLogExecuted(string sql, SugarParameter[] pars) + { + } - private IEntityChangeEventHelper EntityChangeEventHelper => - LazyServiceProvider.LazyGetService(NullEntityChangeEventHelper.Instance); - - public DbConnOptions Options => LazyServiceProvider.LazyGetRequiredService>().Value; - - private ISerializeService SerializeService => LazyServiceProvider.LazyGetRequiredService(); - - public void SetSqlSugarClient(ISqlSugarClient sqlSugarClient) - { - SqlSugarClient = sqlSugarClient; - } - - public SqlSugarDbContext(IAbpLazyServiceProvider lazyServiceProvider) - { - LazyServiceProvider = lazyServiceProvider; - var connectionCreator = LazyServiceProvider.LazyGetRequiredService(); - 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; - } - - /// - /// db切换多库支持 - /// - /// - protected virtual string GetCurrentConnectionString() - { - var connectionStringResolver = LazyServiceProvider.LazyGetRequiredService(); - 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(enumString, out DbType result)) - { - return result; - } - - // 条件不满足时返回 null - return null; - } - - - /// - /// 上下文对象扩展 - /// - /// - protected virtual void OnSqlSugarClientConfig(ISqlSugarClient sqlSugarClient) - { - //需自定义扩展 - if (IsSoftDeleteFilterEnabled) - { - sqlSugarClient.QueryFilter.AddTableFilter(u => u.IsDeleted == false); - } - - if (IsMultiTenantFilterEnabled) - { - //表达式里只能有具体值,不能运算 - var expressionCurrentTenant = CurrentTenant.Id ?? null; - sqlSugarClient.QueryFilter.AddTableFilter(u => u.TenantId == expressionCurrentTenant); - } - - CustomDataFilter(sqlSugarClient); - } - - protected virtual void CustomDataFilter(ISqlSugarClient sqlSugarClient) - { - } - - protected virtual void DataExecuted(object oldValue, DataAfterModel entityInfo) - { - } - - /// - /// 数据 - /// - /// - /// - 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.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.Id)) - { - EntityChangeEventHelper.PublishEntityCreatedEvent(entityInfo.EntityValue); - } - - break; - case DataFilterType.UpdateByObject: - if (entityInfo.PropertyName == nameof(IEntity.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.Id)) - { - //这里sqlsugar有个特殊,删除会返回批量的结果 - if (entityInfo.EntityValue is IEnumerable entityValues) - { - foreach (var entityValue in entityValues) - { - EntityChangeEventHelper.PublishEntityDeletedEvent(entityValue); - } - } - } - - break; - } - } - - /// - /// 日志 - /// - /// - /// - 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().LogDebug(sb.ToString()); - } - } - - /// - /// 日志 - /// - /// - /// - protected virtual void OnLogExecuted(string sql, SugarParameter[] pars) - { - if (Options.EnabledSqlLog) - { - var sqllog = $"=========Yi-SQL耗时{SqlSugarClient.Ado.SqlExecutionTime.TotalMilliseconds}毫秒====="; - Logger.CreateLogger().LogDebug(sqllog.ToString()); - } - } - - /// - /// 实体配置 - /// - /// - /// - 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.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("其他数据库备份未实现"); - } - } + public virtual void EntityService(PropertyInfo propertyInfo, EntityColumnInfo entityColumnInfo) + { } } \ No newline at end of file diff --git a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarDbContextFactory.cs b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarDbContextFactory.cs new file mode 100644 index 00000000..3012a94c --- /dev/null +++ b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/SqlSugarDbContextFactory.cs @@ -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 + { + /// + /// SqlSugar 客户端 + /// + public ISqlSugarClient SqlSugarClient { get; private set; } + + private IAbpLazyServiceProvider LazyServiceProvider { get; } + + private ICurrentTenant CurrentTenant => LazyServiceProvider.LazyGetRequiredService(); + public DbConnOptions Options => LazyServiceProvider.LazyGetRequiredService>().Value; + + private ISerializeService SerializeService => LazyServiceProvider.LazyGetRequiredService(); + + private IEnumerable SqlSugarDbContextDependencies => + LazyServiceProvider.LazyGetRequiredService>(); + + private static readonly ConcurrentDictionary 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); + } + + /// + /// 构建Aop-sqlsugaraop在多租户模式中,需单独设置 + /// + /// + protected virtual void SetDbAop(ISqlSugarClient sqlSugarClient) + { + //替换默认序列化器 + sqlSugarClient.CurrentConnectionConfig.ConfigureExternalServices.SerializeService = SerializeService; + + //将所有,ISqlSugarDbContextDependencies进行累加 + Action onLogExecuting = null; + Action onLogExecuted = null; + Action dataExecuting = null; + Action dataExecuted = null; + Action 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; + } + + /// + /// 构建连接配置 + /// + /// + /// + protected virtual ConnectionConfig BuildConnectionConfig(Action? action = null) + { + var dbConnOptions = Options; + + #region 组装options + + if (dbConnOptions.DbType is null) + { + throw new ArgumentException("DbType配置为空"); + } + + var slavaConFig = new List(); + 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 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; + } + + /// + /// db切换多库支持 + /// + /// + protected virtual string GetCurrentConnectionString() + { + var connectionStringResolver = LazyServiceProvider.LazyGetRequiredService(); + 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(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("其他数据库备份未实现"); + } + } + } +} \ No newline at end of file diff --git a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/Uow/UnitOfWorkSqlsugarDbContextProvider.cs b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/Uow/UnitOfWorkSqlsugarDbContextProvider.cs index 2e0fe69b..d67b24ee 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/Uow/UnitOfWorkSqlsugarDbContextProvider.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/Uow/UnitOfWorkSqlsugarDbContextProvider.cs @@ -13,8 +13,6 @@ namespace Yi.Framework.SqlSugarCore.Uow { public class UnitOfWorkSqlsugarDbContextProvider : ISugarDbContextProvider where TDbContext : ISqlSugarDbContext { - private readonly ISqlSugarDbConnectionCreator _dbConnectionCreator; - public ILogger> Logger { get; set; } public IServiceProvider ServiceProvider { get; set; } @@ -28,8 +26,7 @@ namespace Yi.Framework.SqlSugarCore.Uow IUnitOfWorkManager unitOfWorkManager, IConnectionStringResolver connectionStringResolver, ICancellationTokenProvider cancellationTokenProvider, - ICurrentTenant currentTenant, - ISqlSugarDbConnectionCreator dbConnectionCreator + ICurrentTenant currentTenant ) { UnitOfWorkManager = unitOfWorkManager; @@ -37,7 +34,6 @@ namespace Yi.Framework.SqlSugarCore.Uow CancellationTokenProvider = cancellationTokenProvider; CurrentTenant = currentTenant; Logger = NullLogger>.Instance; - _dbConnectionCreator = dbConnectionCreator; } //private static object _databaseApiLock = new object(); diff --git a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/YiFrameworkSqlSugarCoreModule.cs b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/YiFrameworkSqlSugarCoreModule.cs index 281e6dbd..7cc138ac 100644 --- a/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/YiFrameworkSqlSugarCoreModule.cs +++ b/Yi.Abp.Net8/framework/Yi.Framework.SqlSugarCore/YiFrameworkSqlSugarCoreModule.cs @@ -6,12 +6,9 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using SqlSugar; -using Volo.Abp; -using Volo.Abp.Auditing; using Volo.Abp.Data; using Volo.Abp.Domain; using Volo.Abp.Domain.Repositories; -using Volo.Abp.Modularity; using Yi.Framework.SqlSugarCore.Abstractions; using Yi.Framework.SqlSugarCore.Repositories; using Yi.Framework.SqlSugarCore.Uow; @@ -28,7 +25,7 @@ namespace Yi.Framework.SqlSugarCore var section = configuration.GetSection("DbConnOptions"); Configure(section); - service.TryAddScoped(); + service.TryAddScoped(); //不开放sqlsugarClient //service.AddTransient(x => x.GetRequiredService().SqlSugarClient); @@ -47,6 +44,7 @@ namespace Yi.Framework.SqlSugarCore //将默认db传递给abp连接字符串模块 Configure(x => { x.ConnectionStrings.Default = dbConfig.Url; }); + context.Services.AddYiDbContext(); return Task.CompletedTask; } @@ -72,7 +70,6 @@ namespace Yi.Framework.SqlSugarCore logger.LogInformation(sb.ToString()); - //Todo:准备支持多租户种子数据及CodeFirst if (options.EnabledCodeFirst) { diff --git a/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.SqlSugarCore/YiFrameworkRbacSqlSugarCoreModule.cs b/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.SqlSugarCore/YiFrameworkRbacSqlSugarCoreModule.cs index 32936300..1c85803d 100644 --- a/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.SqlSugarCore/YiFrameworkRbacSqlSugarCoreModule.cs +++ b/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.SqlSugarCore/YiFrameworkRbacSqlSugarCoreModule.cs @@ -15,7 +15,7 @@ namespace Yi.Framework.Rbac.SqlSugarCore { public override void ConfigureServices(ServiceConfigurationContext context) { - context.Services.TryAddYiDbContext(); + context.Services.AddYiDbContext(); } } } \ No newline at end of file diff --git a/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.SqlSugarCore/YiRbacDbContext.cs b/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.SqlSugarCore/YiRbacDbContext.cs index 4d00b1a9..428aed15 100644 --- a/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.SqlSugarCore/YiRbacDbContext.cs +++ b/Yi.Abp.Net8/module/rbac/Yi.Framework.Rbac.SqlSugarCore/YiRbacDbContext.cs @@ -1,5 +1,7 @@ -using SqlSugar; -using Volo.Abp.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; +using SqlSugar; +using Volo.Abp.Data; +using Volo.Abp.Users; using Yi.Framework.Rbac.Domain.Authorization; using Yi.Framework.Rbac.Domain.Entities; using Yi.Framework.Rbac.Domain.Extensions; @@ -11,19 +13,14 @@ namespace Yi.Framework.Rbac.SqlSugarCore { public class YiRbacDbContext : SqlSugarDbContext { - public YiRbacDbContext(IAbpLazyServiceProvider lazyServiceProvider) : base(lazyServiceProvider) - { - } - + protected IDataFilter DataFilter => LazyServiceProvider.LazyGetRequiredService(); + protected ICurrentUser CurrentUser => LazyServiceProvider.GetRequiredService(); protected override void CustomDataFilter(ISqlSugarClient sqlSugarClient) { if (DataFilter.IsEnabled()) { DataPermissionFilter(sqlSugarClient); } - - - base.CustomDataFilter(sqlSugarClient); } diff --git a/Yi.Abp.Net8/src/Yi.Abp.SqlSugarCore/YiDbContext.cs b/Yi.Abp.Net8/src/Yi.Abp.SqlSugarCore/YiDbContext.cs index 28fd4436..34aac630 100644 --- a/Yi.Abp.Net8/src/Yi.Abp.SqlSugarCore/YiDbContext.cs +++ b/Yi.Abp.Net8/src/Yi.Abp.SqlSugarCore/YiDbContext.cs @@ -6,10 +6,7 @@ using Yi.Framework.SqlSugarCore; namespace Yi.Abp.SqlSugarCore { - public class YiDbContext : YiRbacDbContext + public class YiDbContext : SqlSugarDbContext { - public YiDbContext(IAbpLazyServiceProvider lazyServiceProvider) : base(lazyServiceProvider) - { - } } } diff --git a/Yi.Abp.Net8/src/Yi.Abp.Web/YiAbpWebModule.cs b/Yi.Abp.Net8/src/Yi.Abp.Web/YiAbpWebModule.cs index 9a64d70b..cb092e5e 100644 --- a/Yi.Abp.Net8/src/Yi.Abp.Web/YiAbpWebModule.cs +++ b/Yi.Abp.Net8/src/Yi.Abp.Web/YiAbpWebModule.cs @@ -27,10 +27,7 @@ using Volo.Abp.AspNetCore.VirtualFileSystem; using Volo.Abp.Auditing; using Volo.Abp.Autofac; using Volo.Abp.BackgroundJobs.Hangfire; -using Volo.Abp.BackgroundWorkers; -using Volo.Abp.BackgroundWorkers.Hangfire; using Volo.Abp.Caching; -using Volo.Abp.Hangfire; using Volo.Abp.MultiTenancy; using Volo.Abp.Swashbuckle; using Yi.Abp.Application; @@ -80,7 +77,7 @@ namespace Yi.Abp.Web var configuration = context.Services.GetConfiguration(); var host = context.Services.GetHostingEnvironment(); var service = context.Services; - + //请求日志 Configure(optios => { @@ -93,13 +90,13 @@ namespace Yi.Abp.Web options.IgnoredUrls.Add("/api/app/file/"); options.IgnoredUrls.Add("/hangfire"); }); - - //采用furion格式的规范化api,默认不开启,使用abp优雅的方式 - //你没看错。。。 - //service.AddFurionUnifyResultApi(); - //配置错误处理显示详情 - Configure(options => { options.SendExceptionsDetailsToClients = true; }); + //采用furion格式的规范化api,默认不开启,使用abp优雅的方式 + //你没看错。。。 + //service.AddFurionUnifyResultApi(); + + //配置错误处理显示详情 + Configure(options => { options.SendExceptionsDetailsToClients = true; }); //动态Api Configure(options => @@ -122,207 +119,207 @@ namespace Yi.Abp.Web 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()); - // }); + //【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(options => + //请使用微软的,注意abp date又包了一层,采用DefaultJsonTypeInfoResolver统一覆盖 + Configure(options => + { + options.JsonSerializerOptions.TypeInfoResolver = new DefaultJsonTypeInfoResolver(); + options.JsonSerializerOptions.Converters.Add(new DatetimeJsonConverter()); + options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); + }); + + //设置缓存不要过期,默认滑动20分钟 + Configure(cacheOptions => + { + cacheOptions.GlobalCacheEntryOptions.SlidingExpiration = null; + //缓存key前缀 + cacheOptions.KeyPrefix = "Yi:"; + }); + + + Configure(options => { options.AutoValidate = false; }); + + //Swagger + context.Services.AddYiSwaggerGen(options => + { + options.SwaggerDoc("default", + new OpenApiInfo { Title = "Yi.Framework.Abp", Version = "v1", Description = "集大成者" }); + }); + + //跨域 + context.Services.AddCors(options => + { + options.AddPolicy(DefaultCorsPolicyName, builder => { - options.JsonSerializerOptions.TypeInfoResolver = new DefaultJsonTypeInfoResolver(); - options.JsonSerializerOptions.Converters.Add(new DatetimeJsonConverter()); - options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); + builder + .WithOrigins( + configuration["App:CorsOrigins"]! + .Split(";", StringSplitOptions.RemoveEmptyEntries) + .Select(o => o.RemovePostFix("/")) + .ToArray() + ) + .WithAbpExposedHeaders() + .SetIsOriginAllowedToAllowWildcardSubdomains() + .AllowAnyHeader() + .AllowAnyMethod() + .AllowCredentials(); }); + }); - //设置缓存不要过期,默认滑动20分钟 - Configure(cacheOptions => + //配置多租户 + Configure(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)) { - cacheOptions.GlobalCacheEntryOptions.SlidingExpiration = null; - //缓存key前缀 - cacheOptions.KeyPrefix = "Yi:"; - }); - - - Configure(options => { options.AutoValidate = false; }); - - //Swagger - context.Services.AddYiSwaggerGen(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(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)) + config.UseRedisStorage( + ConnectionMultiplexer.Connect(redisConfiguration), + new RedisStorageOptions() { - context.HttpContext.Response.Headers.RetryAfter = - ((int)retryAfter.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo); - } + InvisibilityTimeout = TimeSpan.FromHours(1), //JOB允许执行1小时 + 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 => + { + 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(); + var refreshJwtOptions = configuration.GetSection(nameof(RefreshJwtOptions)).Get(); + + 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)) }; - - //全局使用,链式表达式 - _.GlobalLimiter = PartitionedRateLimiter.CreateChained( - PartitionedRateLimiter.Create(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(); - var refreshJwtOptions = configuration.GetSection(nameof(RefreshJwtOptions)).Get(); - - context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) - .AddJwtBearer(options => + options.Events = new JwtBearerEvents { - options.TokenValidationParameters = new TokenValidationParameters + OnMessageReceived = context => { - ClockSkew = TimeSpan.Zero, - ValidateIssuerSigningKey = true, - ValidIssuer = jwtOptions.Issuer, - ValidAudience = jwtOptions.Audience, - IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtOptions.SecurityKey)) - }; - options.Events = new JwtBearerEvents - { - OnMessageReceived = context => + //优先Query中获取,再去cookies中获取 + var accessToken = context.Request.Query["access_token"]; + if (!string.IsNullOrEmpty(accessToken)) { - //优先Query中获取,再去cookies中获取 - var accessToken = context.Request.Query["access_token"]; - if (!string.IsNullOrEmpty(accessToken)) + context.Token = accessToken; + } + else + { + if (context.Request.Cookies.TryGetValue("Token", out var cookiesToken)) { - context.Token = accessToken; - } - else - { - if (context.Request.Cookies.TryGetValue("Token",out var cookiesToken)) - { - context.Token = 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; } - }; - }) - .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 refreshToken = context.Request.Query["refresh_token"]; + if (!string.IsNullOrEmpty(refreshToken)) { - var refresh_token = context.Request.Headers["refresh_token"]; - 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; + context.Token = refreshToken; } - }; - }) - .AddQQ(options => { configuration.GetSection("OAuth:QQ").Bind(options); }) - .AddGitee(options => { configuration.GetSection("OAuth:Gitee").Bind(options); }); - //授权 - context.Services.AddAuthorization(); + 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(); + + + return Task.CompletedTask; + } public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context) @@ -390,12 +387,13 @@ namespace Yi.Abp.Web //日志记录 app.UseAbpSerilogEnrichers(); - + //Hangfire定时任务面板,可配置授权,意框架支持jwt - app.UseAbpHangfireDashboard("/hangfire", options => - { - options.AsyncAuthorization = new[] { new YiTokenAuthorizationFilter(app.ApplicationServices) }; - }); + app.UseAbpHangfireDashboard("/hangfire", + options => + { + options.AsyncAuthorization = new[] { new YiTokenAuthorizationFilter(app.ApplicationServices) }; + }); //终节点 app.UseConfiguredEndpoints();