feat: 添加多租户模块
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
namespace Yi.Framework.MultiTenancy;
|
||||
|
||||
/// <summary>
|
||||
/// 租户信息
|
||||
/// </summary>
|
||||
public class BasicTenantInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 租户ID
|
||||
/// </summary>
|
||||
public Guid? TenantId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 租户ID 名称
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary/>
|
||||
public BasicTenantInfo(Guid? tenantId, string? name = null)
|
||||
{
|
||||
TenantId = tenantId;
|
||||
Name = name ?? string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using Yi.Framework.Core.Utils;
|
||||
|
||||
namespace Yi.Framework.MultiTenancy;
|
||||
|
||||
/// <summary>
|
||||
/// 当前租户实现
|
||||
/// </summary>
|
||||
public class CurrentTenant : ICurrentTenant
|
||||
{
|
||||
private readonly ICurrentTenantAccessor _currentTenantAccessor;
|
||||
|
||||
/// <summary/>
|
||||
public CurrentTenant(ICurrentTenantAccessor currentTenantAccessor)
|
||||
{
|
||||
_currentTenantAccessor = currentTenantAccessor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否有效
|
||||
/// </summary>
|
||||
public virtual bool IsAvailable => Id != Guid.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 租户ID
|
||||
/// </summary>
|
||||
public virtual Guid Id => _currentTenantAccessor.Current?.TenantId ?? Guid.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 租户名称
|
||||
/// </summary>
|
||||
public string? Name => _currentTenantAccessor.Current?.Name;
|
||||
|
||||
/// <summary>
|
||||
/// 替换租户
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="name"></param>
|
||||
/// <returns></returns>
|
||||
public IDisposable Change(Guid? id, string? name = null)
|
||||
{
|
||||
return SetCurrent(id, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置当前租户
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private IDisposable SetCurrent(Guid? tenantId, string? name = null)
|
||||
{
|
||||
BasicTenantInfo? parentScope = _currentTenantAccessor.Current;
|
||||
_currentTenantAccessor.Current = new BasicTenantInfo(tenantId, name);
|
||||
|
||||
return new DisposeAction<ValueTuple<ICurrentTenantAccessor, BasicTenantInfo>>(static ((ICurrentTenantAccessor, BasicTenantInfo) state) =>
|
||||
{
|
||||
(ICurrentTenantAccessor currentTenantAccessor, BasicTenantInfo parentScope) = state;
|
||||
currentTenantAccessor.Current = parentScope;
|
||||
},
|
||||
(_currentTenantAccessor, parentScope));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
namespace Yi.Framework.MultiTenancy;
|
||||
|
||||
/// <summary>
|
||||
/// 默认租户访问器实现
|
||||
/// </summary>
|
||||
public class DefaultCurrentTenantAccessor : ICurrentTenantAccessor
|
||||
{
|
||||
private ITenantResolver _tenantResolver;
|
||||
|
||||
/// <summary/>
|
||||
public DefaultCurrentTenantAccessor(ITenantResolver tenantResolver)
|
||||
{
|
||||
_tenantResolver = tenantResolver;
|
||||
TenantResolveResult? tenantResolveResult = _tenantResolver.ResolveTenantIdOrNameAsync().Result;
|
||||
string? tenantIdStr = tenantResolveResult.TenantIdOrName;
|
||||
|
||||
Current = Guid.TryParse(tenantIdStr, out Guid tehnantId)
|
||||
? new BasicTenantInfo(tehnantId)
|
||||
: new BasicTenantInfo(Guid.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前租户信息
|
||||
/// </summary>
|
||||
public BasicTenantInfo Current { get; set; }
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Yi.Framework.MultiTenancy.ResolveContributor;
|
||||
|
||||
namespace Yi.Framework.MultiTenancy.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// 租户注入扩展方法
|
||||
/// </summary>
|
||||
public static class MultiTenancyExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 注入 租户
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
/// <returns></returns>
|
||||
public static IServiceCollection AddCurrentTenant(this IServiceCollection services)
|
||||
{
|
||||
services.Configure<TenantResolveOptions>(option =>
|
||||
{
|
||||
//添加租户解析器,默认添加从当前用户中获取
|
||||
|
||||
//添加从httpheader,解析TenantId配置的租户id
|
||||
option.TenantResolvers.Add(new HttpHeaderTenantResolveContributor());
|
||||
|
||||
//添加配置租户解析器,解析TenantId配置的租户id
|
||||
option.TenantResolvers.Add(new ConfigurationTenantResolveContributor());
|
||||
});
|
||||
|
||||
//添加租户解析器注入
|
||||
services.AddTransient<ITenantResolver, TenantResolver>();
|
||||
|
||||
//添加当前租户
|
||||
services.AddTransient<ICurrentTenant, CurrentTenant>();
|
||||
|
||||
//添加默认访问器
|
||||
services.AddTransient<ICurrentTenantAccessor, DefaultCurrentTenantAccessor>();
|
||||
|
||||
return services;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
namespace Yi.Framework.MultiTenancy;
|
||||
|
||||
/// <summary>
|
||||
/// 当前租户接口
|
||||
/// </summary>
|
||||
public interface ICurrentTenant
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否有效
|
||||
/// </summary>
|
||||
bool IsAvailable { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 租户ID
|
||||
/// </summary>
|
||||
Guid Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 租户名称
|
||||
/// </summary>
|
||||
string? Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 替换租户
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="name"></param>
|
||||
IDisposable Change(Guid? id, string? name = null);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Yi.Framework.MultiTenancy;
|
||||
|
||||
/// <summary>
|
||||
/// 租户访问器
|
||||
/// </summary>
|
||||
public interface ICurrentTenantAccessor
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前租户信息
|
||||
/// </summary>
|
||||
BasicTenantInfo Current { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace Yi.Framework.MultiTenancy;
|
||||
|
||||
/// <summary>
|
||||
/// 解析器上下文
|
||||
/// (作用于各个<see cref="ITenantResolveContributor.ResolveAsync(ITenantResolveContext)"/>)之间
|
||||
/// </summary>
|
||||
public interface ITenantResolveContext
|
||||
{
|
||||
/// <summary/>
|
||||
IServiceProvider ServiceProvider { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 租户ID 或 名称
|
||||
/// </summary>
|
||||
string TenantIdOrName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否已处理
|
||||
/// </summary>
|
||||
bool Handled { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace Yi.Framework.MultiTenancy;
|
||||
|
||||
/// <summary>
|
||||
/// 租户解析器贡献者
|
||||
/// </summary>
|
||||
public interface ITenantResolveContributor
|
||||
{
|
||||
/// <summary>
|
||||
/// 贡献者名称
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 解析
|
||||
/// </summary>
|
||||
/// <param name="context">解析器上下文</param>
|
||||
/// <returns></returns>
|
||||
Task ResolveAsync(ITenantResolveContext context);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Yi.Framework.MultiTenancy;
|
||||
|
||||
/// <summary>
|
||||
/// 租户解析器接口
|
||||
/// </summary>
|
||||
public interface ITenantResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// 解析租户Id或名称
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<TenantResolveResult> ResolveTenantIdOrNameAsync();
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Yi.Framework.Data.Entities;
|
||||
using Yi.Framework.MultiTenancy;
|
||||
|
||||
namespace Yi.Framework.MultiTenancy.ResolveContributor;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="IConfiguration"/>租户解析器贡献者
|
||||
/// </summary>
|
||||
public class ConfigurationTenantResolveContributor : TenantResolveContributorBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 租户解析器贡献者基类
|
||||
/// </summary>
|
||||
public override string Name => "Configuration";
|
||||
|
||||
/// <summary>
|
||||
/// 解析
|
||||
/// </summary>
|
||||
/// <param name="context">解析器上下文</param>
|
||||
/// <returns></returns>
|
||||
public override Task ResolveAsync(ITenantResolveContext context)
|
||||
{
|
||||
IConfiguration? configuration = context.ServiceProvider.GetRequiredService<IConfiguration>();
|
||||
|
||||
string? tenantIdStr = configuration.GetValue<string>(nameof(IMultiTenant.TenantId));
|
||||
|
||||
if (Guid.TryParse(tenantIdStr, out Guid tenantId))
|
||||
{
|
||||
if (tenantId != Guid.Empty)
|
||||
{
|
||||
context.TenantIdOrName = tenantId.ToString();
|
||||
context.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
context.Handled = false;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Yi.Framework.Core.CurrentUsers;
|
||||
|
||||
namespace Yi.Framework.MultiTenancy.ResolveContributor;
|
||||
|
||||
/// <summary>
|
||||
/// 当前用户中获取租户
|
||||
/// </summary>
|
||||
public class CurrentUserTenantResolveContributor : TenantResolveContributorBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 贡献者名称
|
||||
/// </summary>
|
||||
public const string ContributorName = "CurrentUser";
|
||||
|
||||
/// <summary>
|
||||
/// 贡献者名称
|
||||
/// </summary>
|
||||
public override string Name => ContributorName;
|
||||
|
||||
/// <summary>
|
||||
/// 解析
|
||||
/// </summary>
|
||||
/// <param name="context">解析器上下文</param>
|
||||
/// <returns></returns>
|
||||
public override Task ResolveAsync(ITenantResolveContext context)
|
||||
{
|
||||
ICurrentUser currentUser = context.ServiceProvider.GetRequiredService<ICurrentUser>();
|
||||
if (currentUser.TenantId != Guid.Empty)
|
||||
{
|
||||
context.Handled = true;
|
||||
context.TenantIdOrName = currentUser.TenantId.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Handled = false;
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Yi.Framework.Data.Entities;
|
||||
using Yi.Framework.MultiTenancy;
|
||||
|
||||
namespace Yi.Framework.MultiTenancy.ResolveContributor;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="HttpContext"/>租户解析器贡献者
|
||||
/// </summary>
|
||||
public class HttpHeaderTenantResolveContributor : TenantResolveContributorBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 贡献者名称
|
||||
/// </summary>
|
||||
public override string Name => "HttpHeader";
|
||||
|
||||
/// <summary>
|
||||
/// 解析
|
||||
/// </summary>
|
||||
/// <param name="context">解析器上下文</param>
|
||||
/// <returns></returns>
|
||||
public override Task ResolveAsync(ITenantResolveContext context)
|
||||
{
|
||||
IHttpContextAccessor? httpContextAccessor = context.ServiceProvider.GetService<IHttpContextAccessor>();
|
||||
|
||||
//如果没有注入http对象,直接跳出
|
||||
if (httpContextAccessor is null)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
HttpContext? httpContext = httpContextAccessor.HttpContext;
|
||||
if (httpContext is not null)
|
||||
{
|
||||
string? tenantId = httpContext.Request.Headers
|
||||
.Where(x => x.Key == nameof(IMultiTenant.TenantId))
|
||||
.Select(x => x.Value.ToString())
|
||||
.FirstOrDefault();
|
||||
|
||||
if (tenantId is not null)
|
||||
{
|
||||
if (Guid.TryParse(tenantId, out Guid tid))
|
||||
{
|
||||
context.TenantIdOrName = tid.ToString();
|
||||
context.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
namespace Yi.Framework.MultiTenancy;
|
||||
|
||||
/// <summary>
|
||||
/// 解析器上下文
|
||||
/// </summary>
|
||||
public class TenantResolveContext : ITenantResolveContext
|
||||
{
|
||||
/// <summary/>
|
||||
public TenantResolveContext(IServiceProvider serviceProvider)
|
||||
{
|
||||
ServiceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
/// <summary/>
|
||||
public IServiceProvider ServiceProvider { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 租户ID 或 名称
|
||||
/// </summary>
|
||||
public string TenantIdOrName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否已处理
|
||||
/// </summary>
|
||||
public bool Handled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否已经处理或者<see cref="TenantIdOrName"/>为有效值
|
||||
/// </summary>
|
||||
public bool HasResolvedTenantOrHost()
|
||||
{
|
||||
return Handled
|
||||
|| TenantIdOrName != Guid.Empty.ToString() && !string.IsNullOrWhiteSpace(TenantIdOrName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace Yi.Framework.MultiTenancy;
|
||||
|
||||
/// <summary>
|
||||
/// 租户解析器贡献者基类
|
||||
/// </summary>
|
||||
public abstract class TenantResolveContributorBase : ITenantResolveContributor
|
||||
{
|
||||
/// <summary>
|
||||
/// 贡献者名称
|
||||
/// </summary>
|
||||
public abstract string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 解析
|
||||
/// </summary>
|
||||
/// <param name="context">解析器上下文</param>
|
||||
/// <returns></returns>
|
||||
public abstract Task ResolveAsync(ITenantResolveContext context);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
|
||||
|
||||
using Yi.Framework.MultiTenancy.ResolveContributor;
|
||||
|
||||
namespace Yi.Framework.MultiTenancy;
|
||||
|
||||
/// <summary>
|
||||
/// 解析器属性
|
||||
/// </summary>
|
||||
public class TenantResolveOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
public TenantResolveOptions()
|
||||
{
|
||||
TenantResolvers = new List<ITenantResolveContributor>
|
||||
{
|
||||
new CurrentUserTenantResolveContributor()
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析器贡献者。由这帮东西为框架提供 租户ID
|
||||
/// </summary>
|
||||
public List<ITenantResolveContributor> TenantResolvers { get; }
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace Yi.Framework.MultiTenancy;
|
||||
|
||||
/// <summary>
|
||||
/// 租户解析器结果
|
||||
/// </summary>
|
||||
public class TenantResolveResult
|
||||
{
|
||||
/// <summary>
|
||||
/// 租户ID 或 名称
|
||||
/// </summary>
|
||||
public string TenantIdOrName { get; set; } = Guid.Empty.ToString();
|
||||
|
||||
/// <summary>
|
||||
/// 存储遍历过的<see cref="ITenantResolveContributor.Name"/>
|
||||
/// </summary>
|
||||
public List<string> AppliedResolvers { get; } = new List<string>();
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Yi.Framework.MultiTenancy;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class TenantResolver : ITenantResolver
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly TenantResolveOptions _options;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
public TenantResolver(IOptions<TenantResolveOptions> options, IServiceProvider serviceProvider)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
_options = options.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析租户Id或名称
|
||||
/// </summary>
|
||||
public virtual async Task<TenantResolveResult> ResolveTenantIdOrNameAsync()
|
||||
{
|
||||
TenantResolveResult? result = new TenantResolveResult();
|
||||
|
||||
using (IServiceScope? serviceScope = _serviceProvider.CreateScope())
|
||||
{
|
||||
TenantResolveContext? context = new TenantResolveContext(serviceScope.ServiceProvider);
|
||||
|
||||
foreach (ITenantResolveContributor tenantResolver in _options.TenantResolvers)
|
||||
{
|
||||
await tenantResolver.ResolveAsync(context);
|
||||
|
||||
result.AppliedResolvers.Add(tenantResolver.Name);
|
||||
|
||||
if (context.HasResolvedTenantOrHost())
|
||||
{
|
||||
result.TenantIdOrName = context.TenantIdOrName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -6,4 +6,9 @@
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Yi.Framework.Core\Yi.Framework.Core.csproj" />
|
||||
<ProjectReference Include="..\Yi.Framework.Data\Yi.Framework.Data.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,7 +1,20 @@
|
||||
namespace Yi.Framework.MultiTenancy
|
||||
{
|
||||
public class YiFrameworkMultiTenancyModule
|
||||
{
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using StartupModules;
|
||||
using Yi.Framework.MultiTenancy.Extensions;
|
||||
|
||||
namespace Yi.Framework.MultiTenancy
|
||||
{
|
||||
public class YiFrameworkMultiTenancyModule : IStartupModule
|
||||
{
|
||||
public void Configure(IApplicationBuilder app, ConfigureMiddlewareContext context)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void ConfigureServices(IServiceCollection services, ConfigureServicesContext context)
|
||||
{
|
||||
services.AddCurrentTenant();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,15 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using StartupModules;
|
||||
using Yi.Framework.Core.Attributes;
|
||||
using Yi.Framework.Core;
|
||||
using Yi.Framework.Ddd;
|
||||
|
||||
namespace Yi.Framework.FileManager
|
||||
{
|
||||
[DependsOn(
|
||||
typeof(YiFrameworkDddModule)
|
||||
)]
|
||||
public class YiFrameworkFileManagerModule : IStartupModule
|
||||
{
|
||||
public void Configure(IApplicationBuilder app, ConfigureMiddlewareContext context)
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using StartupModules;
|
||||
using Yi.Framework.Core;
|
||||
using Yi.Framework.Core.Attributes;
|
||||
using Yi.Framework.Ddd;
|
||||
|
||||
namespace Yi.Framework.OperLogManager
|
||||
{
|
||||
[DependsOn(typeof(YiFrameworkCoreModule))]
|
||||
[DependsOn(
|
||||
typeof(YiFrameworkDddModule)
|
||||
)]
|
||||
public class YiFrameworkOperLogManagerModule : IStartupModule
|
||||
{
|
||||
public void Configure(IApplicationBuilder app, ConfigureMiddlewareContext context)
|
||||
|
||||
Reference in New Issue
Block a user