什么是多租户
网上有好多解释,有些上升到了架构设计,让你觉得似乎非常高深莫测,特别是目前流行的ABP架构中就有提到多租户(IMustHaveTenant),其实说的简单一点就是再每一张数据库的表中添加一个TenantId的字段,用于区分属于不同的租户(或是说不同的用户组)的数据。关键是现实的方式必须对开发人员来说是透明的,不需要关注这个字段的信息,由后台或是封装在基类中实现数据的筛选和更新。
基本原理
从新用户注册时就必须指定用户的TenantId,我的例子是用CompanyId,公司信息做为TenantId,哪些用户属于不同的公司,每个用户将来只能修改和查询属于本公司的数据。
接下来就是用户登录的时候获取用户信息的时候把TenantId保存起来,asp.net mvc(不是 core) 是通过 Identity 2.0实现的认证和授权,这里需要重写部分代码来实现。
最后用户对数据查询/修改/新增时把用户信息中TenantId,这里就需要设定一个Filter(过滤器)和每次SaveChange的插入TenantId
如何实现
第一步,扩展 Asp.net Identity user 属性,必须新增一个TenantId字段,根据Asp.net Mvc 自带的项目模板修改IdentityModels.cs 这个文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
// You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
public class ApplicationUser : IdentityUser
{
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager, string authenticationType)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync( this , authenticationType);
// Add custom user claims here
userIdentity.AddClaim( new Claim( "http://schemas.microsoft.com/identity/claims/tenantid" , this .TenantId.ToString()));
userIdentity.AddClaim( new Claim( "CompanyName" , this .CompanyName));
userIdentity.AddClaim( new Claim( "EnabledChat" , this .EnabledChat.ToString()));
userIdentity.AddClaim( new Claim( "FullName" , this .FullName));
userIdentity.AddClaim( new Claim( "AvatarsX50" , this .AvatarsX50));
userIdentity.AddClaim( new Claim( "AvatarsX120" , this .AvatarsX120));
return userIdentity;
}
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync( this , DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
[Display(Name = "全名" )]
public string FullName { get ; set ; }
[Display(Name = "性别" )]
public int Gender { get ; set ; }
public int AccountType { get ; set ; }
[Display(Name = "所属公司" )]
public string CompanyCode { get ; set ; }
[Display(Name = "公司名称" )]
public string CompanyName { get ; set ; }
[Display(Name = "是否在线" )]
public bool IsOnline { get ; set ; }
[Display(Name = "是否开启聊天功能" )]
public bool EnabledChat { get ; set ; }
public string AvatarsX50 { get ; set ; }
[Display(Name = "大头像" )]
public string AvatarsX120 { get ; set ; }
[Display(Name = "租户ID" )]
public int TenantId { get ; set ; }
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base ( "DefaultConnection" , throwIfV1Schema: false ) => Database.SetInitializer<ApplicationDbContext>( null );
public static ApplicationDbContext Create() => new ApplicationDbContext();
}
|
第二步 修改注册用户的代码,注册新用户的时候需要选择所属的公司信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(AccountRegistrationModel viewModel)
{
var data = this ._companyService.Queryable().Select(x => new ListItem() { Value = x.Id.ToString(), Text = x.Name });
this .ViewBag.companylist = data;
// Ensure we have a valid viewModel to work with
if (! this .ModelState.IsValid)
{
return this .View(viewModel);
}
// Try to create a user with the given identity
try
{
// Prepare the identity with the provided information
var user = new ApplicationUser
{
UserName = viewModel.Username,
FullName = viewModel.Lastname + "." + viewModel.Firstname,
CompanyCode = viewModel.CompanyCode,
CompanyName = viewModel.CompanyName,
TenantId=viewModel.TenantId,
Email = viewModel.Email,
AccountType = 0
};
var result = await this .UserManager.CreateAsync(user, viewModel.Password);
// If the user could not be created
if (!result.Succeeded)
{
// Add all errors to the page so they can be used to display what went wrong
this .AddErrors(result);
return this .View(viewModel);
}
// If the user was able to be created we can sign it in immediately
// Note: Consider using the email verification proces
await this .SignInAsync(user, true );
return this .RedirectToLocal();
}
catch (DbEntityValidationException ex)
{
// Add all errors to the page so they can be used to display what went wrong
this .AddErrors(ex);
return this .View(viewModel);
}
}
AccountController.cs
|
第三步 读取登录用户的TenantId 在用户查询和新增修改时把TenantId插入到表中,这里需要引用
Z.EntityFramework.Plus,这个是免费开源的一个类库,功能强大
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
|
public StoreContext()
: base ( "Name=DefaultConnection" ) {
//获取登录用户信息,tenantid
var claimsidentity = (ClaimsIdentity)HttpContext.Current.User.Identity;
var tenantclaim = claimsidentity?.FindFirst( "http://schemas.microsoft.com/identity/claims/tenantid" );
var tenantid = Convert.ToInt32(tenantclaim?.Value);
//设置当对Work对象进行查询时默认添加过滤条件
QueryFilterManager.Filter<Work>(q => q.Where(x => x.TenantId == tenantid));
//设置当对Order对象进行查询时默认添加过滤条件
QueryFilterManager.Filter<Order>(q => q.Where(x => x.TenantId == tenantid));
}
public override Task< int > SaveChangesAsync(CancellationToken cancellationToken)
{
var currentDateTime = DateTime.Now;
var claimsidentity = (ClaimsIdentity)HttpContext.Current.User.Identity;
var tenantclaim = claimsidentity?.FindFirst( "http://schemas.microsoft.com/identity/claims/tenantid" );
var tenantid = Convert.ToInt32(tenantclaim?.Value);
foreach (var auditableEntity in this .ChangeTracker.Entries<Entity>())
{
if (auditableEntity.State == EntityState.Added || auditableEntity.State == EntityState.Modified)
{
//auditableEntity.Entity.LastModifiedDate = currentDateTime;
switch (auditableEntity.State)
{
case EntityState.Added:
auditableEntity.Property( "LastModifiedDate" ).IsModified = false ;
auditableEntity.Property( "LastModifiedBy" ).IsModified = false ;
auditableEntity.Entity.CreatedDate = currentDateTime;
auditableEntity.Entity.CreatedBy = claimsidentity.Name;
auditableEntity.Entity.TenantId = tenantid;
break ;
case EntityState.Modified:
auditableEntity.Property( "CreatedDate" ).IsModified = false ;
auditableEntity.Property( "CreatedBy" ).IsModified = false ;
auditableEntity.Entity.LastModifiedDate = currentDateTime;
auditableEntity.Entity.LastModifiedBy = claimsidentity.Name;
auditableEntity.Entity.TenantId = tenantid;
//if (auditableEntity.Property(p => p.Created).IsModified || auditableEntity.Property(p => p.CreatedBy).IsModified)
//{
// throw new DbEntityValidationException(string.Format("Attempt to change created audit trails on a modified {0}", auditableEntity.Entity.GetType().FullName));
//}
break ;
}
}
}
return base .SaveChangesAsync(cancellationToken);
}
public override int SaveChanges()
{
var currentDateTime = DateTime.Now;
var claimsidentity =(ClaimsIdentity)HttpContext.Current.User.Identity;
var tenantclaim = claimsidentity?.FindFirst( "http://schemas.microsoft.com/identity/claims/tenantid" );
var tenantid = Convert.ToInt32(tenantclaim?.Value);
foreach (var auditableEntity in this .ChangeTracker.Entries<Entity>())
{
if (auditableEntity.State == EntityState.Added || auditableEntity.State == EntityState.Modified)
{
auditableEntity.Entity.LastModifiedDate = currentDateTime;
switch (auditableEntity.State)
{
case EntityState.Added:
auditableEntity.Property( "LastModifiedDate" ).IsModified = false ;
auditableEntity.Property( "LastModifiedBy" ).IsModified = false ;
auditableEntity.Entity.CreatedDate = currentDateTime;
auditableEntity.Entity.CreatedBy = claimsidentity.Name;
auditableEntity.Entity.TenantId = tenantid;
break ;
case EntityState.Modified:
auditableEntity.Property( "CreatedDate" ).IsModified = false ;
auditableEntity.Property( "CreatedBy" ).IsModified = false ;
auditableEntity.Entity.LastModifiedDate = currentDateTime;
auditableEntity.Entity.LastModifiedBy = claimsidentity.Name;
auditableEntity.Entity.TenantId = tenantid;
break ;
}
}
}
return base .SaveChanges();
}
DbContext.cs
|
经过以上3步就实现一个简单的多租户查询数据的功能。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对服务器之家的支持。
原文链接:https://www.cnblogs.com/neozhu/p/11489931.html