Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

实现多租户功能 #306

Closed
wants to merge 26 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
49386fa
加多租户支持文件
shiningrise Feb 28, 2025
fc21a25
MiniProfiler降级到4.3.8
shiningrise Feb 28, 2025
d04739d
修改ToJson的bug
shiningrise Feb 28, 2025
88d61d6
EntityConfiguration中的HasName改为HasDatabaseName
shiningrise Feb 28, 2025
410f1e6
多租户,数据库创建与迁移成功
shiningrise Mar 1, 2025
85b2714
搞定多租户种子数据
shiningrise Mar 1, 2025
6def1ab
初始化框架基础数据
shiningrise Mar 1, 2025
762de11
完成按照配置文件的多租户数据库隔离功能
shiningrise Mar 1, 2025
727875e
优化多租户功能
shiningrise Mar 1, 2025
6ed4af2
优化多租户功能
shiningrise Mar 1, 2025
7991374
租户域名简化,方便测试
shiningrise Mar 1, 2025
91fdad8
将租户配置文件存到tenants.json文件中
shiningrise Mar 1, 2025
168277b
加租户dbcontext
shiningrise Mar 2, 2025
5dafdeb
加DesignTimeDefaultDbContextFactory
shiningrise Mar 2, 2025
3622f36
修改DesignTimeDefaultDbContextFactory与DesignTimeTenantDbContextFactory,…
shiningrise Mar 2, 2025
a0684d1
重新生成迁移文件
shiningrise Mar 2, 2025
773a4e5
TenantDbContext链接主数据库
shiningrise Mar 2, 2025
992a448
重构多租户相关文件
shiningrise Mar 2, 2025
9d9535b
加全局缓存接口
shiningrise Mar 2, 2025
43617f7
重构缓存模块,提供租户缓存与全局缓存功能
shiningrise Mar 2, 2025
598ed6e
重构缓存模块,提供租户缓存与全局缓存功能
shiningrise Mar 2, 2025
8bfdec2
调整租户相关文件的位置,方便维护
shiningrise Mar 3, 2025
64bf258
HttpTenantProvider改为AddScoped注入模式
shiningrise Mar 3, 2025
b956dbc
HttpTenantProvider注入ITenantAccessor
shiningrise Mar 3, 2025
95b7c80
重构HttpTenantProvider
shiningrise Mar 3, 2025
ff1f3c1
重构HttpTenantProvider
shiningrise Mar 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ protected override Role[] SeedData(IServiceProvider provider)
{
return new[]
{
new Role() { Name = "系统管理员", Remark = "系统最高权限管理角色", IsAdmin = true, IsSystem = true }
new Role() { Name = "系统管理员", Remark = "系统最高权限管理角色", IsAdmin = true, IsSystem = true },
new Role() { Name = "租户管理员", Remark = "租户管理员", IsAdmin = true, IsSystem = true }
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public partial class EntityRoleConfiguration : EntityTypeConfigurationBase<Entit
/// <param name="builder">实体类型创建器</param>
public override void Configure(EntityTypeBuilder<EntityRole> builder)
{
builder.HasIndex(m => new { m.EntityId, m.RoleId, m.Operation }).HasName("EntityRoleIndex").IsUnique();
builder.HasIndex(m => new { m.EntityId, m.RoleId, m.Operation }).HasDatabaseName("EntityRoleIndex").IsUnique();

builder.HasOne<EntityInfo>(er => er.EntityInfo).WithMany().HasForeignKey(m => m.EntityId);
builder.HasOne<Role>(er => er.Role).WithMany().HasForeignKey(m => m.RoleId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public partial class EntityUserConfiguration : EntityTypeConfigurationBase<Entit
/// <param name="builder">ʵÌåÀàÐÍ´´½¨Æ÷</param>
public override void Configure(EntityTypeBuilder<EntityUser> builder)
{
builder.HasIndex(m => new { m.EntityId, m.UserId }).HasName("EntityUserIndex");
builder.HasIndex(m => new { m.EntityId, m.UserId }).HasDatabaseName("EntityUserIndex");

builder.HasOne<EntityInfo>(eu => eu.EntityInfo).WithMany().HasForeignKey(m => m.EntityId);
builder.HasOne<User>(eu => eu.User).WithMany().HasForeignKey(m => m.UserId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public partial class ModuleFunctionConfiguration : EntityTypeConfigurationBase<M
/// <param name="builder">实体类型创建器</param>
public override void Configure(EntityTypeBuilder<ModuleFunction> builder)
{
builder.HasIndex(m => new { m.ModuleId, m.FunctionId }).HasName("ModuleFunctionIndex").IsUnique();
builder.HasIndex(m => new { m.ModuleId, m.FunctionId }).HasDatabaseName("ModuleFunctionIndex").IsUnique();

builder.HasOne<Module>(mf => mf.Module).WithMany().HasForeignKey(m => m.ModuleId);
builder.HasOne<Function>(mf => mf.Function).WithMany().HasForeignKey(m => m.FunctionId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public partial class ModuleRoleConfiguration : EntityTypeConfigurationBase<Modul
/// <param name="builder">实体类型创建器</param>
public override void Configure(EntityTypeBuilder<ModuleRole> builder)
{
builder.HasIndex(m => new { m.ModuleId, m.RoleId }).HasName("ModuleRoleIndex").IsUnique();
builder.HasIndex(m => new { m.ModuleId, m.RoleId }).HasDatabaseName("ModuleRoleIndex").IsUnique();

builder.HasOne<Module>(mr => mr.Module).WithMany().HasForeignKey(m => m.ModuleId);
builder.HasOne<Role>(mr => mr.Role).WithMany().HasForeignKey(m => m.RoleId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public partial class ModuleUserConfiguration : EntityTypeConfigurationBase<Modul
/// <param name="builder">实体类型创建器</param>
public override void Configure(EntityTypeBuilder<ModuleUser> builder)
{
builder.HasIndex(m => new { m.ModuleId, m.UserId }).HasName("ModuleUserIndex").IsUnique();
builder.HasIndex(m => new { m.ModuleId, m.UserId }).HasDatabaseName("ModuleUserIndex").IsUnique();

builder.HasOne<Module>(mu => mu.Module).WithMany().HasForeignKey(m => m.ModuleId);
builder.HasOne<User>(mu => mu.User).WithMany().HasForeignKey(m => m.UserId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public partial class RoleConfiguration : EntityTypeConfigurationBase<Role, int>
/// <param name="builder">实体类型创建器</param>
public override void Configure(EntityTypeBuilder<Role> builder)
{
builder.HasIndex(m => new { m.NormalizedName, m.DeletedTime }).HasName("RoleNameIndex").IsUnique();
builder.HasIndex(m => new { m.NormalizedName, m.DeletedTime }).HasDatabaseName("RoleNameIndex").IsUnique();

builder.Property(m => m.ConcurrencyStamp).IsConcurrencyToken();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ public partial class UserConfiguration : EntityTypeConfigurationBase<User, int>
/// <param name="builder">实体类型创建器</param>
public override void Configure(EntityTypeBuilder<User> builder)
{
builder.HasIndex(m => new { m.NormalizedUserName, m.DeletedTime }).HasName("UserNameIndex").IsUnique();
builder.HasIndex(m => new { m.NormalizeEmail, m.DeletedTime }).HasName("EmailIndex");
builder.HasIndex(m => new { m.NormalizedUserName, m.DeletedTime }).HasDatabaseName("UserNameIndex").IsUnique();
builder.HasIndex(m => new { m.NormalizeEmail, m.DeletedTime }).HasDatabaseName("EmailIndex");

builder.Property(m => m.ConcurrencyStamp).IsConcurrencyToken();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public partial class UserLoginConfiguration : EntityTypeConfigurationBase<UserLo
/// <param name="builder">实体类型创建器</param>
public override void Configure(EntityTypeBuilder<UserLogin> builder)
{
builder.HasIndex(m => new { m.LoginProvider, m.ProviderKey }).HasName("UserLoginIndex").IsUnique();
builder.HasIndex(m => new { m.LoginProvider, m.ProviderKey }).HasDatabaseName("UserLoginIndex").IsUnique();
builder.HasOne(ul => ul.User).WithMany(u => u.UserLogins).HasForeignKey(ul => ul.UserId).IsRequired();

EntityConfigurationAppend(builder);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public partial class UserRoleConfiguration : EntityTypeConfigurationBase<UserRol
/// <param name="builder">实体类型创建器</param>
public override void Configure(EntityTypeBuilder<UserRole> builder)
{
builder.HasIndex(m => new { m.UserId, m.RoleId, m.DeletedTime }).HasName("UserRoleIndex").IsUnique();
builder.HasIndex(m => new { m.UserId, m.RoleId, m.DeletedTime }).HasDatabaseName("UserRoleIndex").IsUnique();
builder.HasOne(ur => ur.Role).WithMany(r => r.UserRoles).HasForeignKey(m => m.RoleId);
builder.HasOne(ur => ur.User).WithMany(u => u.UserRoles).HasForeignKey(m => m.UserId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public partial class UserTokenConfiguration : EntityTypeConfigurationBase<UserTo
/// <param name="builder">实体类型创建器</param>
public override void Configure(EntityTypeBuilder<UserToken> builder)
{
builder.HasIndex(m => new { m.UserId, m.LoginProvider, m.Name }).HasName("UserTokenIndex").IsUnique();
builder.HasIndex(m => new { m.UserId, m.LoginProvider, m.Name }).HasDatabaseName("UserTokenIndex").IsUnique();
builder.HasOne(ut => ut.User).WithMany(u => u.UserTokens).HasForeignKey(ut => ut.UserId).IsRequired();

EntityConfigurationAppend(builder);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using OSharp.Entity;
using OSharp.MultiTenancy;
using System;

namespace Liuliu.Demo.EntityConfiguration
{
/// <summary>
/// 租户实体类型配置
/// </summary>
public partial class TenantEntityConfiguration : EntityTypeConfigurationBase<TenantEntity, Guid>
{
public override Type DbContextType { get; } = typeof(TenantDbContext);

/// <summary>
/// 重写以实现实体类型各个属性的数据库配置
/// </summary>
/// <param name="builder">实体类型构建器</param>
public override void Configure(EntityTypeBuilder<TenantEntity> builder)
{
// 配置表名
builder.ToTable("Tenants");

// 配置属性
builder.Property(m => m.TenantId)
.IsRequired()
.HasMaxLength(50)
.HasComment("租户ID");

builder.Property(m => m.Name)
.IsRequired()
.HasMaxLength(100)
.HasComment("租户名称");

builder.Property(m => m.Host)
.IsRequired()
.HasMaxLength(100)
.HasComment("租户主机");

builder.Property(m => m.ConnectionString)
.HasMaxLength(1000)
.HasComment("连接字符串");

builder.Property(m => m.IsEnabled)
.IsRequired()
.HasDefaultValue(true)
.HasComment("是否启用");

builder.Property(m => m.CreatedTime)
.IsRequired()
.HasComment("创建时间");

builder.Property(m => m.UpdatedTime)
.HasComment("更新时间");

// 配置索引
builder.HasIndex(m => m.TenantId)
.IsUnique()
.HasDatabaseName("IX_Tenants_TenantId");

builder.HasIndex(m => m.Host)
.HasDatabaseName("IX_Tenants_Host");

EntityConfigurationAppend(builder);
}

/// <summary>
/// 额外的数据映射
/// </summary>
partial void EntityConfigurationAppend(EntityTypeBuilder<TenantEntity> builder);
}
}
23 changes: 23 additions & 0 deletions samples/web/Liuliu.Demo.Web/App_Data/tenants.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[
{
"tenantId": "tenant0",
"name": "租户0",
"host": "localhost",
"connectionString": "Server=(localdb)\\mssqllocaldb;Database=Tenant0Db;Trusted_Connection=True;MultipleActiveResultSets=true",
"isEnabled": true
},
{
"tenantId": "tenant1",
"name": "租户1",
"host": "tenant1",
"connectionString": "Server=(localdb)\\mssqllocaldb;Database=Tenant1Db;Trusted_Connection=True;MultipleActiveResultSets=true",
"isEnabled": true
},
{
"tenantId": "tenant2",
"name": "租户2",
"host": "tenant2",
"connectionString": "Server=(localdb)\\mssqllocaldb;Database=Tenant2Db;Trusted_Connection=True;MultipleActiveResultSets=true",
"isEnabled": true
}
]
96 changes: 96 additions & 0 deletions samples/web/Liuliu.Demo.Web/Controllers/TenantController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
using Microsoft.AspNetCore.Mvc;
using OSharp.MultiTenancy;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Liuliu.Demo.Web.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class TenantController : ControllerBase
{
private readonly ITenantAccessor _tenantAccessor;
private readonly ITenantStore _tenantStore;
private readonly ILogger<TenantController> _logger;

public TenantController(
ITenantAccessor tenantAccessor,
ITenantStore tenantStore,
ILogger<TenantController> logger)
{
_tenantAccessor = tenantAccessor;
_tenantStore = tenantStore;
_logger = logger;
}

/// <summary>
/// 获取当前租户信息
/// </summary>
[HttpGet("current")]
public IActionResult GetCurrentTenant()
{
var tenant = _tenantAccessor.CurrentTenant;
if (tenant == null)
{
return NotFound(new { message = "未找到当前租户信息" });
}

return Ok(new
{
tenant.TenantId,
tenant.Name,
tenant.Host,
tenant.IsEnabled
});
}

/// <summary>
/// 获取所有租户信息
/// </summary>
[HttpGet]
public async Task<IActionResult> GetAllTenants()
{
var tenants = await _tenantStore.GetAllTenantsAsync();
return Ok(tenants.Select(t => new
{
t.TenantId,
t.Name,
t.Host,
t.IsEnabled
}));
}

/// <summary>
/// 设置Cookie租户
/// </summary>
[HttpGet("set-cookie/{tenantId}")]
public IActionResult SetCookieTenant(string tenantId)
{
if (string.IsNullOrEmpty(tenantId))
{
return BadRequest(new { message = "租户ID不能为空" });
}

// 设置租户Cookie
Response.Cookies.Append("tenant", tenantId, new CookieOptions
{
Expires = DateTimeOffset.Now.AddDays(7),
Path = "/"
});

return Ok(new { message = $"已设置租户Cookie: {tenantId}" });
}

/// <summary>
/// 清除Cookie租户
/// </summary>
[HttpGet("clear-cookie")]
public IActionResult ClearCookieTenant()
{
Response.Cookies.Delete("tenant");
return Ok(new { message = "已清除租户Cookie" });
}
}
}
Loading
Loading