设计一个多租户的SaaS框架需要考虑多个方面,包括租户识别、数据隔离、权限控制等。以下是一个基于ASP.NET Core 8的多租户SaaS框架的设计方案。
1. 租户识别与数据隔离
1.1 租户模型
首先,定义一个租户模型。
public class Tenant
{
public int Id { get; set; }
public string Name { get; set; }
public string Identifier { get; set; } // 租户标识符,例如域名或子域名
}
1.2 租户服务
创建一个租户
public interface ITenantService
{
Tenant GetTenant();
}
public class TenantService : ITenantService
{
private readonly HttpContext _httpContext;
private readonly Tenant _currentTenant;
public TenantService(IHttpContextAccessor httpContextAccessor)
{
_httpContext = httpContextAccessor.HttpContext;
_currentTenant = ResolveTenant();
}
private Tenant ResolveTenant()
{
// 从HttpContext中解析租户信息,例如从域名或子域名中获取
var host = _httpContext.Request.Host.Host;
// 根据host查找租户信息
// 这里假设有一个TenantRepository来获取租户信息
var tenant = TenantRepository.GetTenantByIdentifier(host);
return tenant;
}
public Tenant GetTenant()
{
return _currentTenant;
}
}
1.3 租户中间件
创建一个中间件来设置当前租户。
public class TenantMiddleware
{
private readonly RequestDelegate _next;
public TenantMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context, ITenantService tenantService)
{
var tenant = tenantService.GetTenant();
if (tenant != null)
{
context.Items["Tenant"] = tenant;
}
await _next(context);
}
}
在 Program.cs
中注册中间件。
app.UseMiddleware<TenantMiddleware>();
2. 数据隔离
2.1 数据库上下文
在数据库上下文中添加租户过滤器。
public class ApplicationDbContext : DbContext
{
private readonly Tenant _currentTenant;
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options, ITenantService tenantService)
: base(options)
{
_currentTenant = tenantService.GetTenant();
}
public DbSet<User> Users { get; set; }
public DbSet<Company> Companies { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// 添加租户过滤器
modelBuilder.Entity<User>().HasQueryFilter(u => u.TenantId == _currentTenant.Id);
modelBuilder.Entity<Company>().HasQueryFilter(c => c.TenantId == _currentTenant.Id);
}
}
2.2 用户模型
在用户模型中添加租户ID。
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public int TenantId { get; set; }
public int CompanyId { get; set; }
public int? ManagerId { get; set; }
public UserRole Role { get; set; }
}
public enum UserRole
{
Employee,
Manager,
Admin
}
3. 权限控制
3.1 权限服务
创建一个权限服务来管理用户权限。
public interface IPermissionService
{
bool CanAccess(User user, User targetUser);
}
public class PermissionService : IPermissionService
{
public bool CanAccess(User user, User targetUser)
{
if (user.Role == UserRole.Admin)
{
return true;
}
else if (user.Role == UserRole.Manager && user.CompanyId == targetUser.CompanyId)
{
return true;
}
else if (user.Id == targetUser.Id)
{
return true;
}
return false;
}
}
3.2 控制器中的权限检查
在控制器中使用权限服务进行权限检查。
public class UserController : Controller
{
private readonly ApplicationDbContext _context;
private readonly IPermissionService _permissionService;
public UserController(ApplicationDbContext context, IPermissionService permissionService)
{
_context = context;
_permissionService = permissionService;
}
public IActionResult Details(int id)
{
var user = _context.Users.Find(id);
if (user == null)
{
return NotFound();
}
var currentUser = GetCurrentUser();
if (!_permissionService.CanAccess(currentUser, user))
{
return Forbid();
}
return View(user);
}
private User GetCurrentUser()
{
// 从HttpContext中获取当前用户
var userId = int.Parse(User.FindFirst(ClaimTypes.NameIdentifier).Value);
return _context.Users.Find(userId);
}
}
4. 注册服务
在 Program.cs
中注册所有服务。
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddScoped<ITenantService, TenantService>();
builder.Services.AddScoped<IPermissionService, PermissionService>();
builder.Services.AddHttpContextAccessor();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseMiddleware<TenantMiddleware>();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();