什么是多租户

网上有好多解释,有些上升到了架构设计,让你觉得似乎非常高深莫测,特别是目前流行的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 这个文件

// 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; }
 [display(name = "小头像")]
 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();


 }

第二步 修改注册用户的代码,注册新用户的时候需要选择所属的公司信息

[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,这个是免费开源的一个类库,功能强大

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步就实现一个简单的多租户查询数据的功能。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对www.887551.com的支持。