Under Yii 2, realize the logical isolation of multi-tenant, that is, there is a tenant ID field in each table of the database, customize the active query class, and add the query conditions of the tenant ID by default in each query.
1. Dynamic configuration of database connection in Yii 2 Starter Kit, the configuration attribute comes from the multi-tenant system,https://www.shuijingwanwq.com/2018/01/18/2328/, multi-tenant physical isolation implementation
2. In the public cloud, the system should serve multiple tenants at the same time, and multiple tenants share a database. Therefore, it is necessary to realize the logical isolation of multi-tenancy, that is, there are tenant ID fields in each table of the database. The table structure is shown in Figure 1
3. /common/models The model class file in the directory is only allowed to be generated by the GII tool, which is a common model data layer, \common\models\configcolumn.php, \common\models\configColumnQuery.php
32],
[['group_id', 'code'], 'unique', 'targetAttribute' => ['group_id', 'code']],
[['group_id', 'name'], 'unique', 'targetAttribute' => ['group_id', 'name']],
];
}
/**
* {@inheritdoc}
*/
public function attributeLabels()
{
return [
'id' => Yii::t('model/config-column', 'ID'),
'group_id' => Yii::t('model/config-column', 'Group ID'),
'code' => Yii::t('model/config-column', 'Code'),
'name' => Yii::t('model/config-column', 'Name'),
'status' => Yii::t('model/config-column', 'Status'),
'created_at' => Yii::t('model/config-column', 'Created At'),
'updated_at' => Yii::t('model/config-column', 'Updated At'),
];
}
/**
* {@inheritdoc}
* @return ConfigColumnQuery the active query used by this AR class.
*/
public static function find()
{
return new ConfigColumnQuery(get_called_class());
}
}
andWhere('[[status]]=1');
}*/
/**
* {@inheritdoc}
* @return ConfigColumn[]|array
*/
public function all($db = null)
{
return parent::all($db);
}
/**
* {@inheritdoc}
* @return ConfigColumn|array|null
*/
public function one($db = null)
{
return parent::one($db);
}
}
4. The model class file in the /common/logics directory is business logic related, inherited to the /common/models data layer, is the public model logic layer, \common\logics\configcolumn.php
[
'class' => TimestampBehavior::className(),
'attributes' => [
self::EVENT_BEFORE_INSERT => ['created_at', 'updated_at'],
self::EVENT_BEFORE_UPDATE => 'updated_at',
SoftDeleteBehavior::EVENT_BEFORE_SOFT_DELETE => 'updated_at',
]
],
'softDeleteBehavior' => [
'class' => SoftDeleteBehavior::className(),
'softDeleteAttributeValues' => [
'status' => self::STATUS_DELETED
],
],
];
}
/**
* @inheritdoc
*/
public function rules()
{
$rules = [
];
$parentRules = parent::rules();
return ArrayHelper::merge($rules, $parentRules);
}
/**
* {@inheritdoc}
* @return ConfigColumnQuery the active query used by this AR class.
*/
public static function find()
{
return new ConfigColumnQuery(get_called_class());
}
}
5. The model class files in the /common/logics directory are business logic related, inherited to /common/models Data layer, the common model logic layer, \common\logics\configColumnQuery.php
andWhere(['!=', 'status', ConfigColumn::STATUS_DELETED]);
}
// 等于 状态:禁用
public function disabled()
{
return $this->andWhere(['status' => ConfigColumn::STATUS_DISABLED]);
}
// 等于 状态:启用
public function enabled()
{
return $this->andWhere(['status' => ConfigColumn::STATUS_ENABLED]);
}
}
6. The model class file in the /api/models directory is business logic related (only related to the interface application), inherited to the /common/logics public logic layer, \api\models\configColumn.php
7. The model class file in the /api/models directory is business logic related (only related to the interface application), inherited to /common/logics Public logic layer, \api\models\configColumnQuery.php, custom active query class, the query conditions of the tenant ID are added by default in each query
andOnCondition(['group_id' => Yii::$app->params['groupId']]);
parent::init();
}
}
8. Create method, \api\rests\config_column\createaction.php
checkAccess) {
call_user_func($this->checkAccess, $this->id);
}
// 当前用户的身份实例,未认证用户则为 Null
$identity = Yii::$app->user->identity;
/* @var $model \yii\db\ActiveRecord */
$model = new $this->modelClass([
'scenario' => $this->createScenario,
]);
$model->load(Yii::$app->getRequest()->getBodyParams(), '');
$model->group_id = $identity->group_id;
if ($model->save()) {
$response = Yii::$app->getResponse();
$response->setStatusCode(201);
$id = implode(',', array_values($model->getPrimaryKey(true)));
$response->getHeaders()->set('Location', Url::toRoute([$this->viewAction, 'id' => $id], true));
} elseif ($model->hasErrors()) {
$response = Yii::$app->getResponse();
$response->setStatusCode(422, 'Data Validation Failed.');
foreach ($model->getFirstErrors() as $message) {
$firstErrors = $message;
break;
}
return ['code' => 20004, 'message' => Yii::t('error', Yii::t('error', Yii::t('error', '20004'), ['firstErrors' => $firstErrors]))];
} elseif (!$model->hasErrors()) {
throw new ServerErrorHttpException('Failed to create the object for unknown reason.');
}
return ['code' => 10000, 'message' => Yii::t('success', '11003'), 'data' => $model];
}
}
9. Posthttp://api.pcs-api.localhost/v1/config-columns?login_id=e56db1b43546a110431ac38409ed8e9e&login_tid=00be7753cb5ddca8bef997fa648e416d, 201 response, in line with expectations
{
"code": 10000,
"message": "创建栏目设置成功",
"data": {
"code": "wxkm",
"name": "无线昆明",
"status": "0",
"group_id": "015ce30b116ce86058fa6ab4fea4ac63",
"created_at": 1531447852,
"updated_at": 1531447852,
"id": 3
}
}
10. View the generated SQL statement. When executing uniqueness, the condition of group_id is duplicated due to the default of adding the query condition of the tenant ID in each query.
SELECT EXISTS(SELECT * FROM `pa_config_column` WHERE (`pa_config_column`.`group_id`='015ce30b116ce86058fa6ab4fea4ac63') AND (`pa_config_column`.`code`='wxkm') AND (`group_id`='015ce30b116ce86058fa6ab4fea4ac63'))
INSERT INTO `pa_config_column` (`code`, `name`, `status`, `group_id`, `created_at`, `updated_at`) VALUES ('wxkm', '无线昆明', 0, '015ce30b116ce86058fa6ab4fea4ac63', 1531447852, 1531447852)
11. The specification when executing the query: there is no need to manually define the query conditions of group_id, the model class file in the /common/logics directory is business logic related, inherited to /common/models The data layer, the common model logic layer, \common\logics\configcolumn.php, redefine the validation rule
/**
* @inheritdoc
*/
public function rules()
{
$rules = [
[['status'], 'in', 'range' => [self::STATUS_DISABLED, self::STATUS_ENABLED]],
[['code'], 'match', 'pattern' => '/^[a-z0-9]+$/', 'message' => Yii::t('app', '{attribute} should contain lowercase letters and numbers.')],
[['code'], 'unique', 'targetAttribute' => ['code']],
[['name'], 'unique', 'targetAttribute' => ['name']],
];
$parentRules = parent::rules();
unset($parentRules[3], $parentRules[4]);
return ArrayHelper::merge($rules, $parentRules);
}
12. After executing the POST request, check the generated sql statement again, which is in line with the expectations, and the condition of group_id is only once
SELECT EXISTS(SELECT * FROM `pa_config_column` WHERE (`pa_config_column`.`code`='wxbj') AND (`group_id`='015ce30b116ce86058fa6ab4fea4ac63'))
INSERT INTO `pa_config_column` (`code`, `name`, `status`, `group_id`, `created_at`, `updated_at`) VALUES ('wxbj', '无线北京', 0, '015ce30b116ce86058fa6ab4fea4ac63', 1531448313, 1531448313)
13. Posthttp://api.pcs-api.localhost/v1/config-columns?login_id=e56db1b43546a110431ac38409ed8e9e&login_tid=00be7753cb5ddca8bef997fa648e416d, 422 response, in line with expectations
Response before adjusting rules()
{
"code": 20004,
"message": "数据验证失败:The combination \"015ce30b116ce86058fa6ab4fea4ac63\"-\"wxcq\" of 租户ID and 栏目代码 has already been taken."
}
Response after adjusting rules()
{
"code": 20004,
"message": "数据验证失败:栏目代码的值\"wxbj\"已经被占用了。"
}
