diff --git a/framework/NPin.Framework.SqlSugarCore.Abstractions/DbConnOptions.cs b/framework/NPin.Framework.SqlSugarCore.Abstractions/DbConnOptions.cs index 330f426..3c380a0 100644 --- a/framework/NPin.Framework.SqlSugarCore.Abstractions/DbConnOptions.cs +++ b/framework/NPin.Framework.SqlSugarCore.Abstractions/DbConnOptions.cs @@ -48,58 +48,4 @@ public class DbConnOptions /// 开启Saas多租户 /// public bool EnabledSaasMultiTenancy { get; set; } = false; - - - /// - /// 默认租户库连接,如果不填,那就是默认库的地址 - /// - public string? MasterSaasMultiTenancyUrl { get; set; } - - - /// - /// Saas租户连接 - /// - public List? SaasMultiTenancy { get; set; } - - public static string MasterTenantName = "Master"; - public static string DefaultTenantName = "Default"; - - public SaasMultiTenancyOptions GetDefaultSaasMultiTenancy() - { - return new SaasMultiTenancyOptions { Name = DefaultTenantName, Url = Url }; - } - - public SaasMultiTenancyOptions? GetMasterSaasMultiTenancy() - { - if (EnabledSaasMultiTenancy == false) - { - return null; - } - - if (string.IsNullOrEmpty(MasterSaasMultiTenancyUrl)) - { - return new SaasMultiTenancyOptions { Name = MasterTenantName, Url = Url }; - } - else - { - return new SaasMultiTenancyOptions() - { - Name = MasterTenantName, - Url = MasterSaasMultiTenancyUrl - }; - } - } -} - -public class SaasMultiTenancyOptions -{ - /// - /// 租户名称标识 - /// - public string Name { get; set; } - - /// - /// 连接Url - /// - public string Url { get; set; } } \ No newline at end of file diff --git a/framework/NPin.Framework.SqlSugarCore.Abstractions/MasterTenantAttribute.cs b/framework/NPin.Framework.SqlSugarCore.Abstractions/DefaultTenantTableAttribute.cs similarity index 62% rename from framework/NPin.Framework.SqlSugarCore.Abstractions/MasterTenantAttribute.cs rename to framework/NPin.Framework.SqlSugarCore.Abstractions/DefaultTenantTableAttribute.cs index c61ad57..bdb4a6d 100644 --- a/framework/NPin.Framework.SqlSugarCore.Abstractions/MasterTenantAttribute.cs +++ b/framework/NPin.Framework.SqlSugarCore.Abstractions/DefaultTenantTableAttribute.cs @@ -1,6 +1,6 @@ -namespace NPin.Framework.SqlSugarCore.Abstractions; - -[AttributeUsage(AttributeTargets.Class)] -public class MasterTenantAttribute : Attribute -{ +namespace NPin.Framework.SqlSugarCore.Abstractions; + +[AttributeUsage(AttributeTargets.Class)] +public class DefaultTenantTableAttribute : Attribute +{ } \ No newline at end of file diff --git a/framework/NPin.Framework.SqlSugarCore/SqlSugarDbContext.cs b/framework/NPin.Framework.SqlSugarCore/SqlSugarDbContext.cs index df79538..eb788c8 100644 --- a/framework/NPin.Framework.SqlSugarCore/SqlSugarDbContext.cs +++ b/framework/NPin.Framework.SqlSugarCore/SqlSugarDbContext.cs @@ -22,6 +22,10 @@ public class SqlSugarDbContext : ISqlSugarDbContext public ISqlSugarClient SqlSugarClient { get; private set; } public DbConnOptions Options => LazyServiceProvider.LazyGetRequiredService>().Value; + + private AbpDbConnectionOptions _connectionOptions => + LazyServiceProvider.LazyGetRequiredService>().Value; + public ICurrentUser CurrentUser => LazyServiceProvider.GetRequiredService(); public ICurrentTenant CurrentTenant => LazyServiceProvider.LazyGetRequiredService(); public IDataFilter DataFilter => LazyServiceProvider.LazyGetRequiredService(); @@ -66,22 +70,23 @@ public class SqlSugarDbContext : ISqlSugarDbContext /// protected virtual string GetCurrentConnectionString() { + var defaultUrl = Options.Url ?? + _connectionOptions.GetConnectionStringOrNull(ConnectionStrings.DefaultConnectionStringName); + // 如果未开启多租户,返回db url 或者 默认连接字符串 + if (!Options.EnabledSaasMultiTenancy) + { + return defaultUrl; + } + + // 开启多租户 var connectionStringResolver = LazyServiceProvider.LazyGetRequiredService(); var connectionString = connectionStringResolver.ResolveAsync().Result; - //没有检测到使用多租户功能,默认使用默认库即可 + // 没有检测到使用多租户功能,默认使用默认库即可 if (string.IsNullOrWhiteSpace(connectionString)) { Volo.Abp.Check.NotNull(Options.Url, "租户默认库Default未找到"); - connectionString = Options.Url; - } - - //如果当前租户是主库,单独使用主要库 - if (CurrentTenant.Name == DbConnOptions.MasterTenantName) - { - var conStrOrNull = Options.GetMasterSaasMultiTenancy(); - Volo.Abp.Check.NotNull(conStrOrNull, "租户主库Master未找到"); - connectionString = conStrOrNull.Url; + connectionString = defaultUrl; } return connectionString!; @@ -254,6 +259,10 @@ public class SqlSugarDbContext : ISqlSugarDbContext column.IsNullable = true; } + if (property.Name == "ConcurrencyStamp") + { + column.IsIgnore = true; + } if (property.PropertyType == typeof(ExtraPropertyDictionary)) { column.IsIgnore = true; diff --git a/module/tenant-management/NPin.Framework.TenantManagement.Application/TenantService.cs b/module/tenant-management/NPin.Framework.TenantManagement.Application/TenantService.cs index 946b3a4..e1ac697 100644 --- a/module/tenant-management/NPin.Framework.TenantManagement.Application/TenantService.cs +++ b/module/tenant-management/NPin.Framework.TenantManagement.Application/TenantService.cs @@ -7,11 +7,10 @@ using NPin.Framework.TenantManagement.Application.Contracts; using NPin.Framework.TenantManagement.Application.Contracts.Dtos; using NPin.Framework.TenantManagement.Domain; using SqlSugar; -using Volo.Abp; using Volo.Abp.Application.Dtos; using Volo.Abp.Data; using Volo.Abp.Domain.Repositories; -using Volo.Abp.Modularity; +using Volo.Abp.Uow; namespace NPin.Framework.TenantManagement.Application; @@ -114,6 +113,7 @@ public class TenantService : NPinCrudAppService(); - var db = await _repository.GetDbContextAsync(); - // 尝试创建数据库 - db.DbMaintenance.CreateDatabase(); + // 没有数据库,不能创建工作单元,先创建库再关闭 + ISqlSugarClient db = null; + using (var uow = UnitOfWorkManager.Begin(requiresNew: true, isTransactional: false)) + { + db = await _repository.GetDbContextAsync(); + // 尝试创建数据库 + db.DbMaintenance.CreateDatabase(); + await uow.CompleteAsync(); + } List types = new List(); foreach (var module in moduleContainer.Modules) @@ -141,6 +147,7 @@ public class TenantService : NPinCrudAppService x.GetCustomAttribute() == null) .Where(x => x.GetCustomAttribute() != null) + .Where(x => x.GetCustomAttribute() is null) .Where(x => x.GetCustomAttribute() is null)); } diff --git a/module/tenant-management/NPin.Framework.TenantManagement.Domain/NPinMultiTenantConnectionStringResolver.cs b/module/tenant-management/NPin.Framework.TenantManagement.Domain/NPinMultiTenantConnectionStringResolver.cs index 165ddba..a18db7c 100644 --- a/module/tenant-management/NPin.Framework.TenantManagement.Domain/NPinMultiTenantConnectionStringResolver.cs +++ b/module/tenant-management/NPin.Framework.TenantManagement.Domain/NPinMultiTenantConnectionStringResolver.cs @@ -1,152 +1,162 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using Volo.Abp; -using Volo.Abp.Data; -using Volo.Abp.DependencyInjection; -using Volo.Abp.MultiTenancy; - -namespace NPin.Framework.TenantManagement.Domain; - -/// -/// 数据库连接字符串解析器,替代原有 -/// -[Dependency(ReplaceServices = true)] -public class NPinMultiTenantConnectionStringResolver : DefaultConnectionStringResolver -{ - private readonly ICurrentTenant _currentTenant; - private readonly IServiceProvider _serviceProvider; - - public NPinMultiTenantConnectionStringResolver(IOptionsMonitor options, - ICurrentTenant currentTenant, IServiceProvider serviceProvider) : base(options) - { - _currentTenant = currentTenant; - _serviceProvider = serviceProvider; - } - - public override async Task ResolveAsync(string? connectionStringName = null) - { - if (_currentTenant.Id == null) - { - // 无租户信息,使用原有解析器 - return await base.ResolveAsync(connectionStringName); - } - - // 通过Store寻找租户缓存 - var tenant = await FindTenantConfigurationAsync(_currentTenant.Id.Value); - if (tenant == null || tenant.ConnectionStrings.IsNullOrEmpty()) - { - // 找不到租户缓存,使用原有解析器 - return await base.ResolveAsync(connectionStringName); - } - - var tenantDefaultConnectionString = tenant.ConnectionStrings?.Default; - - var connString = tenant.ConnectionStrings?.FirstOrDefault().Value; - if (!connString.IsNullOrWhiteSpace()) - { - return connString; - } - - // 库隔离 - var database = Options.Databases.GetMappedDatabaseOrNull(connectionStringName); - if (database is { IsUsedByTenants: true }) - { - connString = tenant.ConnectionStrings?.GetOrDefault(database.DatabaseName); - if (!connString.IsNullOrWhiteSpace()) - { - return connString; - } - } - - if (!tenantDefaultConnectionString.IsNullOrWhiteSpace()) - { - return tenantDefaultConnectionString; - } - - return await base.ResolveAsync(connectionStringName); - } - - protected virtual async Task FindTenantConfigurationAsync(Guid tenantId) - { - using var serviceScope = _serviceProvider.CreateScope(); - - var tenantStore = serviceScope - .ServiceProvider - .GetRequiredService(); - - return await tenantStore.FindAsync(tenantId); - } - - [Obsolete("Use ResolveAsync Instead.")] - public override string Resolve(string? connectionStringName = null) - { - if (_currentTenant.Id == null) - { - // 无租户信息,使用原有解析器 - return base.Resolve(connectionStringName); - } - - // 通过Store寻找租户缓存 - var tenant = FindTenantConfiguration(_currentTenant.Id.Value); - if (tenant == null || tenant.ConnectionStrings.IsNullOrEmpty()) - { - // 找不到租户缓存,使用原有解析器 - return base.Resolve(connectionStringName); - } - - var tenantDefaultConnectionString = tenant.ConnectionStrings?.Default; - - // Requesting default connection string... - if (connectionStringName == null || - connectionStringName == ConnectionStrings.DefaultConnectionStringName) - { - //Return tenant's default or global default - return !tenantDefaultConnectionString.IsNullOrWhiteSpace() - ? tenantDefaultConnectionString! - : Options.ConnectionStrings.Default!; - } - - // Requesting specific connection string... - var connString = tenant.ConnectionStrings?.GetOrDefault(connectionStringName); - if (!connString.IsNullOrWhiteSpace()) - { - // Found for the tenant - return connString!; - } - - // Fallback to tenant's default connection string if available - if (!tenantDefaultConnectionString.IsNullOrWhiteSpace()) - { - return tenantDefaultConnectionString!; - } - - // Try to find the specific connection string for given name - var connStringInOptions = Options.ConnectionStrings.GetOrDefault(connectionStringName); - if (!connStringInOptions.IsNullOrWhiteSpace()) - { - return connStringInOptions!; - } - - // Fallback to the global default connection string - var defaultConnectionString = Options.ConnectionStrings.Default; - if (!defaultConnectionString.IsNullOrWhiteSpace()) - { - return defaultConnectionString!; - } - - throw new AbpException("No connection string defined!"); - } - - - [Obsolete("Use FindTenantConfigurationAsync Instead.")] - protected virtual TenantConfiguration? FindTenantConfiguration(Guid tenantId) - { - using var serviceScope = _serviceProvider.CreateScope(); - - var tenantStore = serviceScope - .ServiceProvider - .GetRequiredService(); - - return tenantStore.Find(tenantId); - } +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; +using Volo.Abp.MultiTenancy; + +namespace NPin.Framework.TenantManagement.Domain; + +/// +/// 数据库连接字符串解析器,替代原有 +/// +[Dependency(ReplaceServices = true)] +public class NPinMultiTenantConnectionStringResolver : DefaultConnectionStringResolver +{ + private readonly ICurrentTenant _currentTenant; + private readonly IServiceProvider _serviceProvider; + + public NPinMultiTenantConnectionStringResolver(IOptionsMonitor options, + ICurrentTenant currentTenant, IServiceProvider serviceProvider) : base(options) + { + _currentTenant = currentTenant; + _serviceProvider = serviceProvider; + } + + public override async Task ResolveAsync(string? connectionStringName = null) + { + if (_currentTenant.Id == null) + { + // 无租户信息,使用原有解析器 + return await base.ResolveAsync(connectionStringName); + } + + // 通过Store寻找租户缓存 + var tenant = await FindTenantConfigurationAsync(_currentTenant.Id.Value); + if (tenant == null || tenant.ConnectionStrings.IsNullOrEmpty()) + { + // 找不到租户缓存,使用原有解析器 + return await base.ResolveAsync(connectionStringName); + } + + var tenantDefaultConnectionString = tenant.ConnectionStrings?.Default; + // Requesting default connection string... + if (connectionStringName == null || + connectionStringName == ConnectionStrings.DefaultConnectionStringName) + { + // Return tenant's default or global default + return !tenantDefaultConnectionString.IsNullOrWhiteSpace() + ? tenantDefaultConnectionString! + : Options.ConnectionStrings.Default!; + } + + // Requesting specific connection string... + var connString = tenant.ConnectionStrings?.FirstOrDefault().Value; + if (!connString.IsNullOrWhiteSpace()) + { + // Found for the tenant + return connString!; + } + + // 库隔离 + var database = Options.Databases.GetMappedDatabaseOrNull(connectionStringName); + if (database is { IsUsedByTenants: true }) + { + connString = tenant.ConnectionStrings?.GetOrDefault(database.DatabaseName); + if (!connString.IsNullOrWhiteSpace()) + { + return connString; + } + } + + if (!tenantDefaultConnectionString.IsNullOrWhiteSpace()) + { + return tenantDefaultConnectionString; + } + + return await base.ResolveAsync(connectionStringName); + } + + protected virtual async Task FindTenantConfigurationAsync(Guid tenantId) + { + using var serviceScope = _serviceProvider.CreateScope(); + + var tenantStore = serviceScope + .ServiceProvider + .GetRequiredService(); + + return await tenantStore.FindAsync(tenantId); + } + + [Obsolete("Use ResolveAsync Instead.")] + public override string Resolve(string? connectionStringName = null) + { + if (_currentTenant.Id == null) + { + // 无租户信息,使用原有解析器 + return base.Resolve(connectionStringName); + } + + // 通过Store寻找租户缓存 + var tenant = FindTenantConfiguration(_currentTenant.Id.Value); + if (tenant == null || tenant.ConnectionStrings.IsNullOrEmpty()) + { + // 找不到租户缓存,使用原有解析器 + return base.Resolve(connectionStringName); + } + + var tenantDefaultConnectionString = tenant.ConnectionStrings?.Default; + + // Requesting default connection string... + if (connectionStringName == null || + connectionStringName == ConnectionStrings.DefaultConnectionStringName) + { + //Return tenant's default or global default + return !tenantDefaultConnectionString.IsNullOrWhiteSpace() + ? tenantDefaultConnectionString! + : Options.ConnectionStrings.Default!; + } + + // Requesting specific connection string... + var connString = tenant.ConnectionStrings?.GetOrDefault(connectionStringName); + if (!connString.IsNullOrWhiteSpace()) + { + // Found for the tenant + return connString!; + } + + // Fallback to tenant's default connection string if available + if (!tenantDefaultConnectionString.IsNullOrWhiteSpace()) + { + return tenantDefaultConnectionString!; + } + + // Try to find the specific connection string for given name + var connStringInOptions = Options.ConnectionStrings.GetOrDefault(connectionStringName); + if (!connStringInOptions.IsNullOrWhiteSpace()) + { + return connStringInOptions!; + } + + // Fallback to the global default connection string + var defaultConnectionString = Options.ConnectionStrings.Default; + if (!defaultConnectionString.IsNullOrWhiteSpace()) + { + return defaultConnectionString!; + } + + throw new AbpException("No connection string defined!"); + } + + + [Obsolete("Use FindTenantConfigurationAsync Instead.")] + protected virtual TenantConfiguration? FindTenantConfiguration(Guid tenantId) + { + using var serviceScope = _serviceProvider.CreateScope(); + + var tenantStore = serviceScope + .ServiceProvider + .GetRequiredService(); + + return tenantStore.Find(tenantId); + } } \ No newline at end of file diff --git a/module/tenant-management/NPin.Framework.TenantManagement.Domain/NPinTenantConfigurationProvider.cs b/module/tenant-management/NPin.Framework.TenantManagement.Domain/NPinTenantConfigurationProvider.cs new file mode 100644 index 0000000..c28fe63 --- /dev/null +++ b/module/tenant-management/NPin.Framework.TenantManagement.Domain/NPinTenantConfigurationProvider.cs @@ -0,0 +1,73 @@ +using Microsoft.Extensions.Localization; +using Volo.Abp.DependencyInjection; +using Volo.Abp.MultiTenancy; +using Volo.Abp.MultiTenancy.Localization; + +namespace NPin.Framework.TenantManagement.Domain; + +[Dependency(ReplaceServices = true)] +public class NPinTenantConfigurationProvider : ITenantConfigurationProvider, ITransientDependency +{ + protected virtual ITenantResolver TenantResolver { get; } + protected virtual ITenantStore TenantStore { get; } + protected virtual ITenantResolveResultAccessor TenantResolveResultAccessor { get; } + protected virtual IStringLocalizer StringLocalizer { get; } + + public NPinTenantConfigurationProvider( + ITenantResolver tenantResolver, + ITenantStore tenantStore, + ITenantResolveResultAccessor tenantResolveResultAccessor, + IStringLocalizer stringLocalizer) + { + TenantResolver = tenantResolver; + TenantStore = tenantStore; + TenantResolveResultAccessor = tenantResolveResultAccessor; + StringLocalizer = stringLocalizer; + } + + public async Task GetAsync(bool saveResolveResult = false) + { + var resolveResult = await TenantResolver.ResolveTenantIdOrNameAsync(); + if (saveResolveResult) + { + TenantResolveResultAccessor.Result = resolveResult; + } + + TenantConfiguration? tenant = null; + if (resolveResult.TenantIdOrName != null) + { + tenant = await FindTenantAsync(resolveResult.TenantIdOrName); + if (tenant == null) + { + throw new BusinessException( + code: "Volo.AbpIo.MultiTenancy:010001", + message: StringLocalizer["TenantNotFoundMessage"], + details: StringLocalizer["TenantNotFoundDetails", resolveResult.TenantIdOrName] + ); + } + + if (!tenant.IsActive) + { + throw new BusinessException( + code: "Volo.AbpIo.MultiTenancy:010002", + message: StringLocalizer["TenantNotActiveMessage"], + details: StringLocalizer["TenantNotActiveDetails", resolveResult.TenantIdOrName] + ); + } + } + + return tenant; + } + + protected virtual async Task FindTenantAsync(string tenantIdOrName) + { + if (Guid.TryParse(tenantIdOrName, out var parsedTenantId)) + { + return await TenantStore.FindAsync(parsedTenantId); + } + else + { + return await TenantStore.FindAsync(tenantIdOrName); + } + } +} \ No newline at end of file diff --git a/module/tenant-management/NPin.Framework.TenantManagement.Domain/SqlSugarTenantStore.cs b/module/tenant-management/NPin.Framework.TenantManagement.Domain/SqlSugarTenantStore.cs index df00a03..3c1321b 100644 --- a/module/tenant-management/NPin.Framework.TenantManagement.Domain/SqlSugarTenantStore.cs +++ b/module/tenant-management/NPin.Framework.TenantManagement.Domain/SqlSugarTenantStore.cs @@ -1,124 +1,110 @@ -using JetBrains.Annotations; -using NPin.Framework.SqlSugarCore.Abstractions; -using Volo.Abp; -using Volo.Abp.Caching; -using Volo.Abp.Data; -using Volo.Abp.MultiTenancy; - -namespace NPin.Framework.TenantManagement.Domain; - -/// -/// 实现TenantStore -/// -public class SqlSugarTenantStore: ITenantStore -{ - private ISqlSugarTenantRepository TenantRepository { get; } - protected ICurrentTenant CurrentTenant { get; } - protected IDistributedCache Cache { get; } - - public SqlSugarTenantStore(ISqlSugarTenantRepository tenantRepository, ICurrentTenant currentTenant, IDistributedCache cache) - { - TenantRepository = tenantRepository; - CurrentTenant = currentTenant; - Cache = cache; - } - - - public Task FindAsync(string name) - { - throw new NotImplementedException(); - } - - public Task FindAsync(Guid id) - { - throw new NotImplementedException(); - } - - protected virtual async Task GetCacheItemAsync(Guid? id, string name) - { - var cacheKey = CalculateCacheKey(id, name); - var cacheItem = await Cache.GetAsync(cacheKey, considerUow: true); - if (cacheItem != null) - { - return cacheItem; - } - - if (id.HasValue) - { - using (CurrentTenant.Change(null)) - { - var tenant = await TenantRepository.FindAsync(id.Value); - return await SetCacheAsync(cacheKey, tenant); - } - } - - if (!name.IsNullOrWhiteSpace()) - { - using (CurrentTenant.Change(null)) - { - var tenant = await TenantRepository.FindByNameAsync(name); - return await SetCacheAsync(cacheKey, tenant); - } - } - - throw new AbpException("Id和Name不能都为空"); - } - - protected virtual async Task SetCacheAsync(string cacheKey, TenantAggregateRoot? tenant) - { - var tenantConfiguration = tenant != null ? MapToConfiguration(tenant) : null; - var cacheItem = new TenantCacheItem(tenantConfiguration); - await Cache.SetAsync(cacheKey, cacheItem, considerUow: true); - return cacheItem; - } - - private TenantConfiguration MapToConfiguration(TenantAggregateRoot tenantAggregateRoot) - { - var tenantConfiguration = new TenantConfiguration - { - Id = tenantAggregateRoot.Id, - Name = tenantAggregateRoot.Name, - ConnectionStrings = MapToString(tenantAggregateRoot.TenantConnectionString), - IsActive = true - }; - return tenantConfiguration; - } - - private ConnectionStrings? MapToString(string tenantConnectionString) - { - - //tenantConnectionString = tenantConnectionString.TrimEnd(';'); - //var strSpiteds = tenantConnectionString.Split(";"); - //if (strSpiteds.Count() == 0) - //{ - // return null; - - //} - - var connectionStrings = new ConnectionStrings(); - //foreach (string strSpited in strSpiteds) - //{ - // var key = strSpited.Split('=')[0]; - // var value = strSpited.Split('=')[1]; - // connectionStrings[key] = value; - //} - connectionStrings["test"] = tenantConnectionString; - - return connectionStrings; - } - - protected virtual string CalculateCacheKey(Guid? id, string name) - { - return TenantCacheItem.CalculateCacheKey(id, name); - } - - public TenantConfiguration? Find(string name) - { - throw new NotImplementedException("请使用异步方法"); - } - - public TenantConfiguration? Find(Guid id) - { - throw new NotImplementedException("请使用异步方法"); - } +using JetBrains.Annotations; +using NPin.Framework.SqlSugarCore.Abstractions; +using Volo.Abp; +using Volo.Abp.Caching; +using Volo.Abp.Data; +using Volo.Abp.MultiTenancy; + +namespace NPin.Framework.TenantManagement.Domain; + +/// +/// 实现TenantStore +/// +public class SqlSugarTenantStore : ITenantStore +{ + private ISqlSugarTenantRepository TenantRepository { get; } + protected ICurrentTenant CurrentTenant { get; } + protected IDistributedCache Cache { get; } + + public SqlSugarTenantStore(ISqlSugarTenantRepository tenantRepository, ICurrentTenant currentTenant, + IDistributedCache cache) + { + TenantRepository = tenantRepository; + CurrentTenant = currentTenant; + Cache = cache; + } + + public TenantConfiguration? Find(string name) + { + throw new NotImplementedException("请使用异步方法"); + } + + public TenantConfiguration? Find(Guid id) + { + throw new NotImplementedException("请使用异步方法"); + } + + public async Task FindAsync(string name) + { + return (await GetCacheItemAsync(null, name)).Value; + } + + public async Task FindAsync(Guid id) + { + return (await GetCacheItemAsync(id, null)).Value; + } + + protected virtual async Task GetCacheItemAsync(Guid? id, string? name) + { + var cacheKey = CalculateCacheKey(id, name); + var cacheItem = await Cache.GetAsync(cacheKey, considerUow: true); + if (cacheItem != null) + { + return cacheItem; + } + + if (id.HasValue) + { + using (CurrentTenant.Change(null)) + { + var tenant = await TenantRepository.FindAsync(id.Value); + return await SetCacheAsync(cacheKey, tenant); + } + } + + if (!name.IsNullOrWhiteSpace()) + { + using (CurrentTenant.Change(null)) + { + var tenant = await TenantRepository.FindByNameAsync(name); + return await SetCacheAsync(cacheKey, tenant); + } + } + + throw new AbpException("Id和Name不能都为空"); + } + + protected virtual async Task SetCacheAsync(string cacheKey, TenantAggregateRoot? tenant) + { + var tenantConfiguration = tenant != null ? MapToConfiguration(tenant) : null; + var cacheItem = new TenantCacheItem(tenantConfiguration); + await Cache.SetAsync(cacheKey, cacheItem, considerUow: true); + return cacheItem; + } + + private TenantConfiguration MapToConfiguration(TenantAggregateRoot tenantAggregateRoot) + { + var tenantConfiguration = new TenantConfiguration + { + Id = tenantAggregateRoot.Id, + Name = tenantAggregateRoot.Name, + ConnectionStrings = MapToString(tenantAggregateRoot.TenantConnectionString), + IsActive = true + }; + return tenantConfiguration; + } + + private ConnectionStrings MapToString(string tenantConnectionString) + { + var connectionStrings = new ConnectionStrings + { + [ConnectionStrings.DefaultConnectionStringName] = tenantConnectionString + }; + return connectionStrings; + } + + protected virtual string CalculateCacheKey(Guid? id, string name) + { + return TenantCacheItem.CalculateCacheKey(id, name); + } } \ No newline at end of file diff --git a/module/tenant-management/NPin.Framework.TenantManagement.Domain/TenantAggregateRoot.cs b/module/tenant-management/NPin.Framework.TenantManagement.Domain/TenantAggregateRoot.cs index 9a92cef..bf7b99a 100644 --- a/module/tenant-management/NPin.Framework.TenantManagement.Domain/TenantAggregateRoot.cs +++ b/module/tenant-management/NPin.Framework.TenantManagement.Domain/TenantAggregateRoot.cs @@ -10,7 +10,7 @@ using Check = Volo.Abp.Check; namespace NPin.Framework.TenantManagement.Domain; [SugarTable("SysTenant", "租户表")] -[MasterTenant] +[DefaultTenantTable] public class TenantAggregateRoot : FullAuditedAggregateRoot, IHasEntityVersion { [SugarColumn(IsPrimaryKey = true)] public override Guid Id { get; protected set; } diff --git a/module/tenant-management/NPin.Framework.TenantManagement.Domain/TenantManagementExtensions.cs b/module/tenant-management/NPin.Framework.TenantManagement.Domain/TenantManagementExtensions.cs index 19e3fed..5b403c2 100644 --- a/module/tenant-management/NPin.Framework.TenantManagement.Domain/TenantManagementExtensions.cs +++ b/module/tenant-management/NPin.Framework.TenantManagement.Domain/TenantManagementExtensions.cs @@ -1,17 +1,12 @@ -using NPin.Framework.SqlSugarCore.Abstractions; -using Volo.Abp.MultiTenancy; - -namespace NPin.Framework.TenantManagement.Domain; - -public static class TenantManagementExtensions -{ - public static IDisposable ChangeMaster(this ICurrentTenant currentTenant) - { - return currentTenant.Change(null, DbConnOptions.MasterTenantName); - } - - public static IDisposable ChangeDefault(this ICurrentTenant currentTenant) - { - return currentTenant.Change(null, DbConnOptions.DefaultTenantName); - } +using Volo.Abp.Data; +using Volo.Abp.MultiTenancy; + +namespace NPin.Framework.TenantManagement.Domain; + +public static class TenantManagementExtensions +{ + public static IDisposable ChangeDefault(this ICurrentTenant currentTenant) + { + return currentTenant.Change(null, ConnectionStrings.DefaultConnectionStringName); + } } \ No newline at end of file diff --git a/module/upms/NPin.Framework.Upms.Domain.Shared/Caches/UserInfoCacheItem.cs b/module/upms/NPin.Framework.Upms.Domain.Shared/Caches/UserInfoCacheItem.cs new file mode 100644 index 0000000..245d89c --- /dev/null +++ b/module/upms/NPin.Framework.Upms.Domain.Shared/Caches/UserInfoCacheItem.cs @@ -0,0 +1,34 @@ +using NPin.Framework.Upms.Domain.Shared.Dtos; + +namespace NPin.Framework.Upms.Domain.Shared.Caches; + +public class UserInfoCacheItem +{ + /// + /// 用户完整信息 + /// + public UserFullDto Info { get; set; } + + public UserInfoCacheItem(UserFullDto info) + { + Info = info; + } +} + +public class UserInfoCacheKey +{ + /// + /// 用户ID + /// + public Guid UserId { get; set; } + + public UserInfoCacheKey(Guid userId) + { + UserId = userId; + } + + public override string ToString() + { + return $"User:{UserId}"; + } +} \ No newline at end of file diff --git a/module/upms/NPin.Framework.Upms.Domain.Shared/Dtos/UserDto.cs b/module/upms/NPin.Framework.Upms.Domain.Shared/Dtos/UserDto.cs index bdcd095..6591c42 100644 --- a/module/upms/NPin.Framework.Upms.Domain.Shared/Dtos/UserDto.cs +++ b/module/upms/NPin.Framework.Upms.Domain.Shared/Dtos/UserDto.cs @@ -11,12 +11,13 @@ public class UserFullDto public UserDto User { get; set; } = new(); // Relations - public HashSet Roles { get; set; } = []; - public HashSet Posts { get; set; } = []; - public HashSet Organizations { get; set; } = []; + public List Roles { get; set; } = []; + public List Posts { get; set; } = []; + public List Organizations { get; set; } = []; - public HashSet PostCodes { get; set; } = []; public HashSet RoleCodes { get; set; } = []; + public HashSet PostCodes { get; set; } = []; + public HashSet OrganizationCodes { get; set; } = []; public HashSet PermissionCodes { get; set; } = []; } diff --git a/module/upms/NPin.Framework.Upms.Domain.Shared/Etos/UserCreatedEventArgs.cs b/module/upms/NPin.Framework.Upms.Domain.Shared/Etos/UserCreatedEventArgs.cs index 9a0b383..e069401 100644 --- a/module/upms/NPin.Framework.Upms.Domain.Shared/Etos/UserCreatedEventArgs.cs +++ b/module/upms/NPin.Framework.Upms.Domain.Shared/Etos/UserCreatedEventArgs.cs @@ -1,9 +1,14 @@ -namespace NPin.Framework.Upms.Domain.Shared.Etos; - -/// -/// 用户创建事件参数 -/// -public class UserCreatedEventArgs -{ - public Guid UserId { get; set; } +namespace NPin.Framework.Upms.Domain.Shared.Etos; + +/// +/// 用户创建事件参数 +/// +public class UserCreatedEventArgs +{ + public Guid UserId { get; set; } + + public UserCreatedEventArgs(Guid userId) + { + UserId = userId; + } } \ No newline at end of file diff --git a/module/upms/NPin.Framework.Upms.Domain/Authorization/RefreshTokenMiddleware.cs b/module/upms/NPin.Framework.Upms.Domain/Authorization/RefreshTokenMiddleware.cs index 38ec12a..0d93ced 100644 --- a/module/upms/NPin.Framework.Upms.Domain/Authorization/RefreshTokenMiddleware.cs +++ b/module/upms/NPin.Framework.Upms.Domain/Authorization/RefreshTokenMiddleware.cs @@ -1,49 +1,49 @@ -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using NPin.Framework.Upms.Domain.Shared.Consts; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Security.Claims; - -namespace NPin.Framework.Upms.Domain.Authorization; - -/// -/// RefreshToken 处理中间件 -/// -public class RefreshTokenMiddleware : IMiddleware, ITransientDependency -{ - public async Task InvokeAsync(HttpContext context, RequestDelegate next) - { - var refreshToken = context.Request.Headers["refresh_token"].ToString(); - if (!refreshToken.IsNullOrEmpty()) - { - var authResult = await context.AuthenticateAsync(TokenTypeConst.Refresh); - // Token刷新成功 - if (authResult.Succeeded) - { - var userId = Guid.Parse(authResult.Principal.FindFirst(AbpClaimTypes.UserId).Value); - // TODO - // var accessToken = - // var refreshToken = - context.Response.Headers["access_token"] = ""; - context.Response.Headers["refresh_token"] = ""; - - // 请求头替换 - context.Request.Headers["Authorization"] = $"Bearer {""}"; - } - } - await next(context); - } -} - -/// -/// 扩展 -/// -public static class RefreshTokenExtensions -{ - public static IApplicationBuilder UseRefreshToken(this IApplicationBuilder builder) - { - builder.UseMiddleware(); - return builder; - } +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using NPin.Framework.Upms.Domain.Shared.Consts; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Security.Claims; + +namespace NPin.Framework.Upms.Domain.Authorization; + +/// +/// RefreshToken 处理中间件 +/// +public class RefreshTokenMiddleware : IMiddleware, ITransientDependency +{ + public async Task InvokeAsync(HttpContext context, RequestDelegate next) + { + var refreshToken = context.Request.Headers["refresh_token"].ToString(); + if (!refreshToken.IsNullOrEmpty()) + { + var authResult = await context.AuthenticateAsync(TokenTypeConst.Refresh); + // Token刷新成功 + if (authResult.Succeeded) + { + var userId = Guid.Parse(authResult.Principal.FindFirst(AbpClaimTypes.UserId).Value); + // TODO + var accessToken = ""; + // var refreshToken = + context.Response.Headers["access_token"] = ""; + context.Response.Headers["refresh_token"] = ""; + + // 请求头替换 + context.Request.Headers["Authorization"] = $"Bearer {accessToken}"; + } + } + await next(context); + } +} + +/// +/// 扩展 +/// +public static class RefreshTokenExtensions +{ + public static IApplicationBuilder UseRefreshToken(this IApplicationBuilder builder) + { + builder.UseMiddleware(); + return builder; + } } \ No newline at end of file diff --git a/module/upms/NPin.Framework.Upms.Domain/Managers/AccountManager.cs b/module/upms/NPin.Framework.Upms.Domain/Managers/AccountManager.cs index 16f4c04..febe58e 100644 --- a/module/upms/NPin.Framework.Upms.Domain/Managers/AccountManager.cs +++ b/module/upms/NPin.Framework.Upms.Domain/Managers/AccountManager.cs @@ -1,12 +1,173 @@ -using Volo.Abp.Domain.Services; - -namespace NPin.Framework.Upms.Domain.Managers; - -public interface IAccountManager -{ -} - -public class AccountManager : DomainService, IAccountManager -{ - // private readonly +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Text; +using Mapster; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Tokens; +using NPin.Framework.Upms.Domain.Entities; +using NPin.Framework.Upms.Domain.Repositories; +using NPin.Framework.Upms.Domain.Shared.Consts; +using NPin.Framework.Upms.Domain.Shared.Dtos; +using NPin.Framework.Upms.Domain.Shared.Etos; +using NPin.Framework.Upms.Domain.Shared.Options; +using Volo.Abp.Domain.Services; +using Volo.Abp.EventBus.Local; +using Volo.Abp.Security.Claims; + +namespace NPin.Framework.Upms.Domain.Managers; + +public interface IAccountManager +{ + /// + /// 创建 RefreshToken + /// + /// + /// + string CreateRefreshToken(Guid userId); + + /// + /// 获取 AccessToken + /// + /// + /// + Task CreateAccessTokenAsync(Guid userId); + + /// + /// 重置密码 + /// + /// + /// + /// + Task RestPasswordAsync(Guid userId, string password); + + /// + /// 修改密码 + /// + /// + /// + /// + /// + Task UpdatePasswordAsync(Guid userId, string newPassword, string oldPassword); +} + +public class AccountManager : DomainService, IAccountManager +{ + private readonly IUserRepository _repository; + private readonly ILocalEventBus _localEventBus; + private readonly JwtOptions _jwtOptions; + private readonly RefreshJwtOptions _refreshJwtOptions; + private readonly UserManager _userManager; + private IHttpContextAccessor _httpContextAccessor; + + public AccountManager( + IUserRepository repository, + ILocalEventBus localEventBus, + IOptions jwtOptions, + IOptions refreshJwtOptions, + UserManager userManager, + IHttpContextAccessor httpContextAccessor) + { + _repository = repository; + _localEventBus = localEventBus; + _jwtOptions = jwtOptions.Value; + _refreshJwtOptions = refreshJwtOptions.Value; + _userManager = userManager; + _httpContextAccessor = httpContextAccessor; + } + + /// + /// 根据用户Id获取AccessToken + /// + /// + /// + public async Task CreateAccessTokenAsync(Guid userId) + { + var userInfo = await _userManager.GetInfoByCacheAsync(userId); + if (!userInfo.User.IsEnabled) + { + throw new UserFriendlyException("该用户已被禁用,请联系管理员进行恢复"); + } + + // http请求 + if (_httpContextAccessor.HttpContext is not null) + { + // TODO eto 与 entity 保证 + var loginLogEntity = LoginLogEntity.GetInfoByHttpContext(_httpContextAccessor.HttpContext); + var loginEto = loginLogEntity.Adapt(); + loginEto.Username = userInfo.User.Username; + loginEto.UserId = userInfo.User.Id; + // 异步 + _ = _localEventBus.PublishAsync(loginEto); + } + + return CreateAccessToken(UserInfoToClaims(userInfo)); + } + + private string CreateAccessToken(List> kvs) + { + var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtOptions.SecurityKey)); + // RSA2 + var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); + var claims = kvs.Select(x => new Claim(x.Key, x.Value)).ToList(); + var token = new JwtSecurityToken( + issuer: _jwtOptions.Issuer, + audience: _jwtOptions.Audience, + claims: claims, + expires: DateTime.Now.AddMinutes(_jwtOptions.ExpiresMinuteTime), + notBefore: DateTime.Now, + signingCredentials: credentials); + return new JwtSecurityTokenHandler().WriteToken(token); + } + + public string CreateRefreshToken(Guid userId) + { + throw new NotImplementedException(); + } + + private List> UserInfoToClaims(UserFullDto dto) + { + var claims = new List>(); + + AddToClaim(claims, AbpClaimTypes.UserId, dto.User.Id.ToString()); + AddToClaim(claims, AbpClaimTypes.UserName, dto.User.Username); + + // TODO 各种 Claim + + // 超级管理员 + if (UserConst.Admin.Equals(dto.User.Username)) + { + AddToClaim(claims, TokenTypeConst.Permission, UserConst.AdminPermissionCode); + AddToClaim(claims, TokenTypeConst.Roles, UserConst.AdminRoleCode); + } + else + { + } + + return claims; + } + + private void AddToClaim(List> claims, string key, string value) + { + claims.Add(new KeyValuePair(key, value)); + } + + /// + /// 重置密码(仅执行最终重置,无前序判断) + /// + /// + /// + /// + public async Task RestPasswordAsync(Guid userId, string password) + { + var user = await _repository.GetByIdAsync(userId); + user.EncryptPassword.Password = password; + user.BuildPassword(); + return await _repository.UpdateAsync(user); + } + + public Task UpdatePasswordAsync(Guid userId, string newPassword, string oldPassword) + { + throw new NotImplementedException(); + } } \ No newline at end of file diff --git a/module/upms/NPin.Framework.Upms.Domain/Managers/UserManager.cs b/module/upms/NPin.Framework.Upms.Domain/Managers/UserManager.cs new file mode 100644 index 0000000..e39236a --- /dev/null +++ b/module/upms/NPin.Framework.Upms.Domain/Managers/UserManager.cs @@ -0,0 +1,274 @@ +using System.Text.RegularExpressions; +using Mapster; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using NPin.Framework.SqlSugarCore.Abstractions; +using NPin.Framework.Upms.Domain.Entities; +using NPin.Framework.Upms.Domain.Entities.ValueObjects; +using NPin.Framework.Upms.Domain.Repositories; +using NPin.Framework.Upms.Domain.Shared.Caches; +using NPin.Framework.Upms.Domain.Shared.Consts; +using NPin.Framework.Upms.Domain.Shared.Dtos; +using NPin.Framework.Upms.Domain.Shared.Etos; +using NPin.Framework.Upms.Domain.Shared.Options; +using Volo.Abp.Authorization; +using Volo.Abp.Caching; +using Volo.Abp.Domain.Services; +using Volo.Abp.EventBus.Local; +using Volo.Abp.Uow; + +namespace NPin.Framework.Upms.Domain.Managers; + +/// +/// 用户管理器 领域服务 +/// +public partial class UserManager : DomainService +{ + [GeneratedRegex("^[a-zA-Z0-9]+$")] + private static partial Regex UsernameRegex(); + + private readonly IUserRepository _repository; + private readonly ISqlSugarRepository _repositoryRole; + + private readonly ISqlSugarRepository _repositoryUserRole; + private readonly ISqlSugarRepository _repositoryUserPost; + private readonly ISqlSugarRepository _repositoryUserOrg; + + private IDistributedCache _userCache; + private ILocalEventBus _localEventBus; + + public UserManager( + IUserRepository repository, + ISqlSugarRepository repositoryUserRole, + ISqlSugarRepository repositoryUserPost, + ISqlSugarRepository repositoryUserOrg, + IDistributedCache userCache, + ILocalEventBus localEventBus, ISqlSugarRepository repositoryRole) + { + _repository = repository; + _repositoryUserRole = repositoryUserRole; + _repositoryUserPost = repositoryUserPost; + _repositoryUserOrg = repositoryUserOrg; + _userCache = userCache; + _localEventBus = localEventBus; + _repositoryRole = repositoryRole; + } + + /// + /// 创建用户 + /// + /// + public async Task CreateAsync(UserEntity entity) + { + await ValidateUser(entity); + + var returnEntity = await _repository.InsertReturnEntityAsync(entity); + entity = returnEntity; + + // 触发事件 + await _localEventBus.PublishAsync(new UserCreatedEventArgs(entity.Id)); + } + + private async Task ValidateUser(UserEntity entity) + { + // TODO 不一定非要用户名,这里需要更自由的逻辑 + if (entity.Username is UserConst.Admin or UserConst.TenantAdmin) + { + throw new UserFriendlyException("无效的用户名"); + } + + if (entity.Username.Length < 2) + { + throw new UserFriendlyException("用户名长度错误,需大于2个字符"); + } + + // 正则表达式,匹配只包含数字和字母的字符串 + var isMatch = UsernameRegex().IsMatch(entity.Username); + if (!isMatch) + { + throw new UserFriendlyException("用户名不能包含除【字母】与【数字】的其他字符"); + } + + // 密码长度判断 + // TODO(需要读取配置) + if (entity.EncryptPassword.Password.Length < 6) + { + throw new UserFriendlyException($"密码格式错误,长度需大于等于{6}位"); + } + + if (!string.IsNullOrEmpty(entity.PhoneNumber)) + { + if (await _repository.IsAnyAsync(x => x.PhoneNumber == entity.PhoneNumber)) + { + throw new UserFriendlyException("手机号重复"); + } + } + } + + /// + /// 设置默认角色 + /// + /// + public async Task SetDefaultRoleAsync(Guid userId) + { + // 检查默认角色是否存在,不存在不处理 + var role = await _repositoryRole.GetFirstAsync(x => x.Code == UserConst.DefaultRoleCode); + if (role is not null) + { + await SetRoleAsync([userId], [role.Id]); + } + } + + /// + /// 给用户设置角色 + /// + /// + /// + [UnitOfWork] + public async Task SetRoleAsync(List userIds, List roleIds) + { + // 删除用户之前的所有关系(物理) + await _repositoryUserRole.DeleteAsync(u => userIds.Contains(u.UserId)); + + var entities = (from userId in userIds + from roleId in roleIds + select new UserRoleEntity { UserId = userId, RoleId = roleId }) + .ToList(); + + await _repositoryUserRole.InsertRangeAsync(entities); + } + + /// + /// 设置用户岗位 + /// + /// + /// + [UnitOfWork] + public async Task SetPostAsync(List userIds, List postIds) + { + // 删除用户之前的所有关系(物理) + await _repositoryUserPost.DeleteAsync(u => userIds.Contains(u.UserId)); + + var entities = (from userId in userIds + from postId in postIds + select new UserPostEntity { UserId = userId, PostId = postId }) + .ToList(); + + await _repositoryUserPost.InsertRangeAsync(entities); + } + + /// + /// 设置用户组织机构 + /// + /// + /// + [UnitOfWork] + public async Task SetOrganizationAsync(List userIds, List orgIds) + { + // 删除用户之前的所有关系(物理) + await _repositoryUserOrg.DeleteAsync(u => userIds.Contains(u.UserId)); + + var entities = (from userId in userIds + from orgId in orgIds + select new UserOrganizationEntity { UserId = userId, OrganizationId = orgId }) + .ToList(); + + await _repositoryUserOrg.InsertRangeAsync(entities); + } + + /// + /// 从缓存中获取用户信息,若缓存不存在则查库 + /// + /// + /// + /// 库中无此用户,403 + public async Task GetInfoByCacheAsync(Guid userId) + { + // 1. 缓存获取 + UserFullDto ret = null; + var tokenExpires = LazyServiceProvider.GetRequiredService>().Value.ExpiresMinuteTime; + var cached = await _userCache.GetOrAddAsync(new UserInfoCacheKey(userId), + async () => + { + // 2. 库查询 + var user = await _repository.GetAllInfoAsync(userId); + var dto = EntityMapToDto(user); + ret = dto ?? throw new AbpAuthorizationException(); + + return new UserInfoCacheItem(dto); + }, + () => new DistributedCacheEntryOptions + { + AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(tokenExpires) + }); + + if (cached is not null) + { + ret = cached.Info; + } + + return ret!; + } + + private UserFullDto? EntityMapToDto(UserEntity? entity) + { + if (entity is null) + { + return null; + } + var ret = new UserFullDto(); + + // 密码过滤 + entity.EncryptPassword = new EncryptPasswordValueObject(); + + // 超级管理员特殊处理 + if (UserConst.Admin.Equals(entity.Username)) + { + ret.User = _userCache.Adapt(); + ret.RoleCodes.Add(UserConst.AdminRoleCode); + ret.PermissionCodes.Add(UserConst.AdminPermissionCode); + return ret; + } + + // 角色 + var roleDtoList = new List(); + var roleCodes = new HashSet(); + foreach (var role in entity.Roles) + { + roleDtoList.Add(role.Adapt()); + roleCodes.Add(role.Code); + } + + ret.Roles = roleDtoList; + ret.RoleCodes = roleCodes; + + // 岗位 + var postDtoList = new List(); + var postCodes = new HashSet(); + foreach (var post in entity.Posts) + { + postDtoList.Add(post.Adapt()); + postCodes.Add(post.Code); + } + + ret.Posts = postDtoList; + ret.PostCodes = postCodes; + + // 组织结构 + var orgDtoList = new List(); + var orgCodes = new HashSet(); + foreach (var org in entity.Organizations) + { + orgDtoList.Add(org.Adapt()); + orgCodes.Add(org.Code); + } + + ret.Organizations = orgDtoList; + ret.OrganizationCodes = orgCodes; + + ret.User = entity.Adapt(); + // TODO permissionCode + return ret; + } +} \ No newline at end of file diff --git a/module/upms/NPin.Framework.Upms.Domain/Repositories/IUserRepository.cs b/module/upms/NPin.Framework.Upms.Domain/Repositories/IUserRepository.cs index 5703863..2eafe2e 100644 --- a/module/upms/NPin.Framework.Upms.Domain/Repositories/IUserRepository.cs +++ b/module/upms/NPin.Framework.Upms.Domain/Repositories/IUserRepository.cs @@ -1,9 +1,22 @@ -using NPin.Framework.SqlSugarCore.Abstractions; -using NPin.Framework.Upms.Domain.Entities; - -namespace NPin.Framework.Upms.Domain.Repositories; - -public interface IUserRepository: ISqlSugarRepository -{ - // Task<> +using NPin.Framework.SqlSugarCore.Abstractions; +using NPin.Framework.Upms.Domain.Entities; + +namespace NPin.Framework.Upms.Domain.Repositories; + +public interface IUserRepository : ISqlSugarRepository +{ + /// + /// 获取用户所有信息,即所有的关联关系 + /// + /// + /// + Task GetAllInfoAsync(Guid userId); + + /// + /// 获取用户列表 + /// 包含所有信息,即所有的关联关系 + /// + /// + /// + Task> GetListUserAllInfoAsync(List userIds); } \ No newline at end of file diff --git a/module/upms/NPin.Framework.Upms.SqlSugarCore/Repositories/UserRepository.cs b/module/upms/NPin.Framework.Upms.SqlSugarCore/Repositories/UserRepository.cs new file mode 100644 index 0000000..58f485b --- /dev/null +++ b/module/upms/NPin.Framework.Upms.SqlSugarCore/Repositories/UserRepository.cs @@ -0,0 +1,38 @@ +using NPin.Framework.SqlSugarCore.Abstractions; +using NPin.Framework.SqlSugarCore.Repositories; +using NPin.Framework.Upms.Domain.Entities; +using NPin.Framework.Upms.Domain.Repositories; +using Volo.Abp.DependencyInjection; + +namespace NPin.Framework.Upms.SqlSugarCore.Repositories; + +public class UserRepository : SqlSugarRepository, IUserRepository, ITransientDependency +{ + public UserRepository(ISugarDbContextProvider sugarDbContextProvider) : base( + sugarDbContextProvider) + { + } + + public async Task GetAllInfoAsync(Guid userId) + { + var ret = await DbQueryable + .Includes(u => u.Metadata) + .Includes(u => u.Roles.Where(r => r.IsDeleted == false).ToList()) + .Includes(u => u.Posts.Where(p => p.IsDeleted == false).ToList()) + .Includes(u => u.Organizations.Where(o => o.IsDeleted == false).ToList()) + .InSingleAsync(userId); + return ret; + } + + public async Task> GetListUserAllInfoAsync(List userIds) + { + var ret = await DbQueryable + .Where(u => userIds.Contains(u.Id)) + .Includes(u => u.Metadata) + .Includes(u => u.Roles.Where(r => r.IsDeleted == false).ToList()) + .Includes(u => u.Posts.Where(p => p.IsDeleted == false).ToList()) + .Includes(u => u.Organizations.Where(o => o.IsDeleted == false).ToList()) + .ToListAsync(); + return ret; + } +} \ No newline at end of file