在 Yii 2 Starter Kit 中实现数据库连接时的动态配置,配置属性来源于多租户系统

1、多租户系统中包含多个租户,每个租户均有其自有的数据库配置信息,现有的需求是通过调用多租户系统的接口,基于响应的主机名、用户名和密码来连接数据库,而不是以应用组件的方式来配置,如图1

多租户系统中包含多个租户,每个租户均有其自有的数据库配置信息,现有的需求是通过调用多租户系统的接口,基于响应的主机名、用户名和密码来连接数据库,而不是以应用组件的方式来配置

图1

2、现在是以应用组件的方式来配置,/common/config/base.php,如图2

现在是以应用组件的方式来配置,/common/config/base.php

图2

3、打开 Dubug,然后查看 Log Messages,db 组件的属性需要在 yii/db/Connection::open 之前完成初始化定义(调用多租户系统的接口),如图3

打开 Dubug,然后查看 Log Messages,db 组件的属性需要在 yii/db/Connection::open 之前完成初始化定义(调用多租户系统的接口)

图3

4、编辑 /common/config/base.php,配置 db 组件的类:common/components/db/Connection,将所有 env 变量注释掉

        'db'=>[
            'class'=>'common/components/db/Connection',
            // 'dsn' => env('DB_DSN'),
            // 'username' => env('DB_USERNAME'),
            // 'password' => env('DB_PASSWORD'),
            // 'tablePrefix' => env('DB_TABLE_PREFIX'),
            'charset' => 'utf8',
            'enableSchemaCache' => YII_ENV_PROD,
        ],

5、新建 /common/components/db/Connection.php,继承至 /yii/db/Connection

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018/01/15
 * Time: 16:18
 */

namespace common/components/db;

use Yii;
use yii/helpers/ArrayHelper;

/**
 * 获取租户模块环境配置信息,存储至Redis,注册db组件
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since 1.0
 */

class Connection extends /yii/db/Connection
{
    public function __construct($config = [])
    {
        // ... 配置生效前的初始化过程
        $tenantConfig = [
            'dsn' => 'mysql:host=127.0.0.1;port=3306;dbname=cmcp-api',
            'username' => env('DB_USERNAME'),
            'password' => env('DB_PASSWORD'),
            'tablePrefix' => env('DB_TABLE_PREFIX'),
        ];

        if (!empty($config)) {
            $config = ArrayHelper::merge($config, $tenantConfig);
        }

        parent::__construct($config);
    }

}

6、重复第3步骤,db 组件的属性已经在 yii/db/Connection::open 之前完成初始化定义(host=localhost 变化为 host=127.0.0.1),如图4

重复第3步骤,db 组件的属性已经在 yii/db/Connection::open 之前完成初始化定义(host=localhost 变化为 host=127.0.0.1)

图4

7、现在需要调用多租户系统的接口,以其响应重新赋值 $tenantConfig(建议:此方案仅适用于存在一个租户的情况,如果存在多个租户,建议每个租户对应不同的连接组件,连接组件的命名基于租户ID,而不是对应 db 连接组件),如图5

现在需要调用多租户系统的接口,以其响应重新赋值 $tenantConfig(建议:此方案仅适用于存在一个租户的情况,如果存在多个租户,建议每个租户对应不同的连接组件,连接组件的命名基于租户ID,而不是对应 db 连接组件)

图5

8、现在还原所有修改,准备实现同时使用多个数据库(每个租户对应不同的连接组件,连接组件的命名基于租户ID),参考网址:https://github.com/yiichina/yii2/blob/master/docs/guide-zh-CN/db-active-record.md ,如图6

现在还原所有修改,准备实现同时使用多个数据库(每个租户对应不同的连接组件,连接组件的命名基于租户ID),参考网址:https://github.com/yiichina/yii2/blob/master/docs/guide-zh-CN/db-active-record.md

图6

9、新建 /common/components/db/ActiveRecord.php,继承至 /yii/db/ActiveRecord,重写 [[yii/db/ActiveRecord::getDb()|getDb()]] 方法

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018/01/16
 * Time: 10:31
 */

namespace common/components/db;

use Yii;

/**
 * 获取租户模块环境配置信息,存储至Redis,注册数据库连接组件
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since 1.0
 */

class ActiveRecord extends /yii/db/ActiveRecord
{
    /**
     * Returns the database connection used by this AR class.
     * By default, the "db" application component is used as the database connection.
     * You may override this method if you want to use a different database connection.
     * @return Connection the database connection used by this AR class.
     */
    public static function getDb()
    {
        $tenantDb = new /yii/db/Connection([
            'dsn' => 'mysql:host=127.0.0.1;port=3306;dbname=cmcp-api',
            'username' => env('DB_USERNAME'),
            'password' => env('DB_PASSWORD'),
            'tablePrefix' => env('DB_TABLE_PREFIX'),
            'charset' => 'utf8',
            'enableSchemaCache' => YII_ENV_PROD,
        ]);
        return $tenantDb;
        // return Yii::$app->getDb();
    }

}

10、编辑 /common/models/KeyStorageItem.php,继承至 common/components/db/ActiveRecord

<?php

namespace common/models;

use Yii;
use yii/behaviors/TimestampBehavior;
use common/components/db/ActiveRecord;

/**
 * This is the model class for table "key_storage_item".
 *
 * @property integer $key
 * @property integer $value
 */
class KeyStorageItem extends ActiveRecord
{
}

11、编辑 /common/config/base.php,注释掉 db 组件,后期建议将 db 组件移至开发环境,以方便于 Gii 的使用

        /*
        'db'=>[
            'class'=>'yii/db/Connection',
            'dsn' => env('DB_DSN'),
            'username' => env('DB_USERNAME'),
            'password' => env('DB_PASSWORD'),
            'tablePrefix' => env('DB_TABLE_PREFIX'),
            'charset' => 'utf8',
            'enableSchemaCache' => YII_ENV_PROD,
        ],
        */

12、打开:http://frontend.cmcp-api.localhost/ ,报错:Failed to instantiate component or class “db”.,原因在于日志目标为数据库表,如图7

打开:http://frontend.cmcp-api.localhost/ ,报错:Failed to instantiate component or class "db".,原因在于日志目标为数据库表

图7

13、查看日志组件配置,/common/config/base.php

        'log' => [
            'traceLevel' => YII_DEBUG ? 3 : 0,
            'targets' => [
                'db'=>[
                    'class' => 'yii/log/DbTarget',
                    'levels' => ['error', 'warning'],
                    'except'=>['yii/web/HttpException:*', 'yii/i18n/I18N/*'],
                    'prefix'=>function () {
                        $url = !Yii::$app->request->isConsoleRequest ? Yii::$app->request->getUrl() : null;
                        return sprintf('[%s][%s]', Yii::$app->id, $url);
                    },
                    'logVars'=>[],
                    'logTable'=>'{{%system_log}}'
                ]
            ],
        ],

14、日志组件必须在引导期间就被加载,因此如果数据库连接是动态配置的,已经晚于引导阶段了,编辑日志组件配置,/common/config/base.php,保存日志消息到文件中

        'log' => [
            'traceLevel' => YII_DEBUG ? 3 : 0,
            'targets' => [
                'file'=>[
                    'class' => 'yii/log/FileTarget',
                    'levels' => ['error', 'warning'],
                    'except' => ['yii/web/HttpException:*', 'yii/i18n/I18N/*'],
                    'prefix' => function () {
                        $url = !Yii::$app->request->isConsoleRequest ? Yii::$app->request->getUrl() : null;
                        return sprintf('[%s][%s]', Yii::$app->id, $url);
                    },
                    'logVars'=>[],
                ]
            ],
        ],

15、打开:http://frontend.cmcp-api.localhost/ ,报错:Unknown component ID: db,如图8

打开:http://frontend.cmcp-api.localhost/ ,报错:Unknown component ID: db

图8

16、将 /common/models 下的所有模型文件,use yii/db/ActiveRecord; 替换为 use common/components/db/ActiveRecord;,如图9

use yii/db/ActiveRecord; 替换为 use common/components/db/ActiveRecord;
/yii/db/ActiveRecord 替换为 /common/components/db/ActiveRecord
将 /common/models 下的所有模型文件,use yii/db/ActiveRecord; 替换为 use common/components/db/ActiveRecord;

图9

17、打开:http://frontend.cmcp-api.localhost/ ,报错:Failed to instantiate component or class “db”.,原因在于 RBAC 组件使用数据库表存放数据,如图10

打开:http://frontend.cmcp-api.localhost/ ,报错:Failed to instantiate component or class "db".,原因在于 RBAC 组件使用数据库表存放数据

图10

18、编辑 /common/config/base.php,注释掉 RBAC 组件,因为其 db 属性默认值为 db,需要动态调整为租户ID所对应的数据库连接组件

        /*
        'authManager' => [
            'class' => 'yii/rbac/DbManager',
            'itemTable' => '{{%rbac_auth_item}}',
            'itemChildTable' => '{{%rbac_auth_item_child}}',
            'assignmentTable' => '{{%rbac_auth_assignment}}',
            'ruleTable' => '{{%rbac_auth_rule}}'
        ],
        */

19、编辑 /common/components/db/ActiveRecord.php,通过 [[yii/di/ServiceLocator::setComponents()]] 方法注册数据库连接组件、RBAC组件

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018/01/16
 * Time: 10:31
 */

namespace common/components/db;

use Yii;

/**
 * 获取租户模块环境配置信息,存储至Redis,注册数据库连接组件
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since 1.0
 */

class ActiveRecord extends /yii/db/ActiveRecord
{
    /**
     * Returns the database connection used by this AR class.
     * By default, the "db" application component is used as the database connection.
     * You may override this method if you want to use a different database connection.
     * @return Connection the database connection used by this AR class.
     */
    public static function getDb()
    {
        $tenantDb = 'defaultDb';
        // 注册数据库连接组件、RBAC组件
        Yii::$app->setComponents([
            $tenantDb => [
                'class' => 'yii/db/Connection',
                'dsn' => 'mysql:host=127.0.0.1;port=3306;dbname=cmcp-api',
                'username' => env('DB_USERNAME'),
                'password' => env('DB_PASSWORD'),
                'tablePrefix' => env('DB_TABLE_PREFIX'),
                'charset' => 'utf8',
                'enableSchemaCache' => YII_ENV_PROD,
            ],
            'authManager' => [
                'class' => 'yii/rbac/DbManager',
                'db' => $tenantDb,
                'itemTable' => '{{%rbac_auth_item}}',
                'itemChildTable' => '{{%rbac_auth_item_child}}',
                'assignmentTable' => '{{%rbac_auth_assignment}}',
                'ruleTable' => '{{%rbac_auth_rule}}'
            ],
        ]);

        return Yii::$app->$tenantDb;
    }
}

20、重复第3步骤,db 组件的属性已经在 yii/db/Connection::open 之前完成初始化定义(host=localhost 变化为 host=127.0.0.1),如图11

重复第3步骤,db 组件的属性已经在 yii/db/Connection::open 之前完成初始化定义(host=localhost 变化为 host=127.0.0.1)

图11

21、在 Postman 中,GET http://www.cmcp-api.localhost/v1/pages ,200响应,如图12

在 Postman 中,GET http://www.cmcp-api.localhost/v1/pages ,200响应

图12

22、打开网址:http://backend.cmcp-api.localhost/ ,报错:Unknown component ID: db,如图13

打开网址:http://backend.cmcp-api.localhost/ ,报错:Unknown component ID: db

图13

23、编辑 /backend/models/SystemLog.php,继承至 /common/components/db/ActiveRecord,如图14

/yii/db/ActiveRecord 替换为 /common/components/db/ActiveRecord
编辑 /backend/models/SystemLog.php,继承至 /common/components/db/ActiveRecord

图14

24、打开网址:http://backend.cmcp-api.localhost/ ,200响应

25、现在需要调用多租户系统的接口,以其响应直接初始化使用数据库连接、RBAC,基于已经安装的 Yii 2 的 HTTP 客户端扩展,如图15

现在需要调用多租户系统的接口,以其响应直接初始化使用数据库连接、RBAC,基于已经安装的 Yii 2 的 HTTP 客户端扩展

图15

26、编辑 /.env.dist、/.env,新增多租户的相关配置

# 多租户
# ----
TENANT_HOST_INFO       = http://wjdev.chinamcloud.com:8600 # HOME URL
TENANT_BASE_URL        = /interface # BASE URL
TENANT_APP_NAME        = cmcpapi # 模块英文名称
TENANT_SECRET          = 94030d307b160f04b88592cb9bebdd4c # 模块Secret

27、编辑 /common/config/bootstrap.php,设置别名

Yii::setAlias('@tenantUrl', env('TENANT_HOST_INFO') . env('TENANT_BASE_URL'));

28、通过应用组件配置客户端,编辑 /common/config/web.php,如图16

        'tenantHttp' => [
            'class' => 'yii/httpclient/Client',
            'baseUrl' => Yii::getAlias('@tenantUrl'),
            'transport' => 'yii/httpclient/CurlTransport'
        ],
通过应用组件配置客户端,编辑 /common/config/web.php

图16

29、通过配置日志记录 HTTP 发送的请求并分析其执行情况,编辑 /common/config/base.php,如图17

                'httpRequest'=>[
                    'class' => 'yii/log/FileTarget',
                    'logFile' => '@runtime/logs/http-request.log',
                    'categories' => ['yii/httpclient/*'],
                ]
通过配置日志记录 HTTP 发送的请求并分析其执行情况,编辑 /common/config/base.php

图17

30、yii2 HTTP 客户端扩展提供了一个可以与 yii 调试模块集成的调试面板,并显示已执行的HTTP 请求,启用调试面板,如图18

    $config['modules']['debug'] = [
        'class' => 'yii/debug/Module',
        'allowedIPs' => ['127.0.0.1', '::1', '192.168.33.1', '172.17.42.1', '172.17.0.1', '192.168.99.1'],
        'panels' => [
            'httpclient' => [
                'class' => 'yii/httpclient/debug/HttpClientPanel',
            ],
        ],
    ];
yii2 HTTP 客户端扩展提供了一个可以与 yii 调试模块集成的调试面板,并显示已执行的HTTP 请求,启用调试面板

图18

31、刷新页面之后,查看日志面板,新增 HTTP Client,如图19

刷新页面之后,查看日志面板,新增 HTTP Client

图19

32、新建 /common/logics/http/tenant/Env.php,http目录表示此目录下的模型数据源自于HTTP请求,tenant目录表示多租户系统相关的模型,Env.php表示环境配置模型

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018/01/17
 * Time: 15:20
 */

namespace common/logics/http/tenant;

use Yii;
use yii/base/Model;
use yii/web/ServerErrorHttpException;

/**
 * 多租户的模块环境配置
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since 1.0
 */
class Env extends Model
{
    public $app_name;
    public $secret;
    public $tenant_id;

    public function attributeLabels()
    {
        return [
            'app_name' => /Yii::t('model/http/tenant/env', 'App Name'),
            'secret' => /Yii::t('model/http/tenant/env', 'Secret'),
            'tenant_id' => /Yii::t('model/http/tenant/env', 'Tenant ID'),
        ];
    }

    /**
     * 返回租户模块环境配置信息
     *
     * @return array|false
     *
     * 格式如下:
     *
     * 租户模块环境配置信息
     * [
     *     'message' => '', //说明
     *     'data' => [], //数据
     * ]
     *
     * 失败(将错误保存在 [[yii/base/Model::errors]] 属性中)
     * false
     *
     * @throws ServerErrorHttpException 如果响应状态码不等于20x
     */
    public function getTenantEnv()
    {
        $this->app_name = env('TENANT_APP_NAME');
        $this->secret = env('TENANT_SECRET');
        /* 租户ID后续从请求参数中获取 */
        $this->tenant_id = 'default';
        $response = Yii::$app->tenantHttp->createRequest()
            ->setMethod('get')
            ->setUrl('getTenantEnvs')
            ->setData([
                'appname' => $this->app_name,
                'secret' => $this->secret,
                'tenantid1' => $this->tenant_id,
            ])
            ->send();
        // 检查响应状态码是否等于20x
        if ($response->isOk) {
            // 检查业务逻辑是否成功
            if ($response->data['returnCode'] === 0) {
                return ['message' => $response->data['returnDesc'], 'data' => $response->data['returnData']];
            } else {
                $this->addError('tenant_id', $response->data['returnDesc']);
                return false;
            }
        } else {
            throw new ServerErrorHttpException(Yii::t('error', Yii::t('error', Yii::t('error', '20005'), ['statusCode' => $response->getStatusCode()])));
        }

    }

}

33、新建语言包文件,/common/messages/en/model/http/tenant/env.php、/common/messages/zh/model/http/tenant/env.php,如图20
/common/messages/en/model/http/tenant/env.php

return [
    'App Name' => 'English name of the module',
    'Secret' => 'Secret module in multi-tenant system',
    'Tenant ID' => 'Tenant ID',
];

/common/messages/zh/model/http/tenant/env.php

return [
    'App Name' => '模块英文名称',
    'Secret' => '模块Secret',
    'Tenant ID' => '租户ID',
];
新建语言包文件,/common/messages/en/model/http/tenant/env.php、/common/messages/zh/model/http/tenant/env.php

图20

34、编辑语言包文件,/api/messages/en/error.php、/api/messages/zh/error.php
/api/messages/en/error.php

    20005 => 'Multitenant HTTP request failed with status code: {statusCode}',
    20006 => 'Multitenant HTTP request failed: {firstErrors}',

/api/messages/zh/error.php

    20005 => '多租户HTTP请求失败,状态码:{statusCode}',
    20006 => '多租户HTTP请求失败:{firstErrors}',

35、复制 /api/messages/en/app.php、/api/messages/en/error.php、/api/messages/zh/app.php、/api/messages/zh/error.php 至 /common/messages/en/app.php、/common/messages/en/error.php、/common/messages/zh/app.php、/common/messages/zh/error.php,编辑
/common/messages/zh/error.php

return [
    20000 => 'error',
    20005 => '多租户HTTP请求失败,状态码:{statusCode}',
    20006 => '多租户HTTP请求失败:{firstErrors}',
];

36、编辑 /common/components/db/ActiveRecord.php,数据库配置信息源自于租户环境配置接口

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018/01/16
 * Time: 10:31
 */

namespace common/components/db;

use Yii;
use common/logics/http/tenant/Env;
use yii/web/ServerErrorHttpException;

/**
 * 获取租户模块环境配置信息,存储至Redis,注册数据库连接组件
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since 1.0
 */

class ActiveRecord extends /yii/db/ActiveRecord
{
    /**
     * Returns the database connection used by this AR class.
     * By default, the "db" application component is used as the database connection.
     * You may override this method if you want to use a different database connection.
     * @return Connection the database connection used by this AR class.
     */
    public static function getDb()
    {
        $env = new Env();
        $tenantEnv = $env->getTenantEnv();

        if ($tenantEnv === false) {
            if ($env->hasErrors()) {
                foreach ($env->getFirstErrors() as $message) {
                    $firstErrors = $message;
                }
                throw new ServerErrorHttpException(Yii::t('error', Yii::t('error', Yii::t('error', '20006'), ['firstErrors' => $firstErrors])));
            } elseif (!$env->hasErrors()) {
                throw new ServerErrorHttpException('Multi-tenant HTTP requests fail for unknown reasons.');
            }
        }

        $tenantDb = $tenantEnv['data']['tenantid'] . 'Db';
        // 注册数据库连接组件、RBAC组件
        Yii::$app->setComponents([
            $tenantDb => [
                'class' => 'yii/db/Connection',
                'dsn' => 'mysql:host=' .$tenantEnv['data']['db_info']['host'] . ';port=3306;dbname=' . $tenantEnv['data']['db_info']['database'] . '',
                'username' => $tenantEnv['data']['db_info']['login'],
                'password' => $tenantEnv['data']['db_info']['password'],
                'tablePrefix' => $tenantEnv['data']['db_info']['prefix'],
                'charset' => 'utf8',
                'enableSchemaCache' => YII_ENV_PROD,
            ],
            'authManager' => [
                'class' => 'yii/rbac/DbManager',
                'db' => $tenantDb,
                'itemTable' => '{{%rbac_auth_item}}',
                'itemChildTable' => '{{%rbac_auth_item_child}}',
                'assignmentTable' => '{{%rbac_auth_assignment}}',
                'ruleTable' => '{{%rbac_auth_rule}}'
            ],
        ]);
        
        return Yii::$app->$tenantDb;
    }
}

37、在 Postman 中 GET http://www.cmcp-api.localhost/v1/pages ,200响应,如图21

在 Postman 中 GET http://www.cmcp-api.localhost/v1/pages ,200响应

图21

38、在 Postman 中 GET 多租户的环境配置接口,一个参数是错误的,如图22

在 Postman 中 GET 多租户的环境配置接口,一个参数是错误的

图22

39、编辑 /.env,TENANT_APP_NAME的值是错误的

TENANT_APP_NAME        = cmcpapi1 # 模块英文名称

40、在 Postman 中 GET http://www.cmcp-api.localhost/v1/pages ,500响应,如图23

在 Postman 中 GET http://www.cmcp-api.localhost/v1/pages ,500响应

图23

41、查看日志,发现接口应用与后台应用分别请求多租户接口的次数为8、139次,后台报错:Unable to send log via yii/log/FileTarget: Exception (Database Exception) ‘yii/db/Exception’ with message ‘SQLSTATE[HY000] [1040] Too many connections’ ,如图24、25

查看日志,发现接口应用与后台应用分别请求多租户接口的次数为8、139次

图24

后台报错:Unable to send log via yii/log/FileTarget: Exception (Database Exception) 'yii/db/Exception' with message 'SQLSTATE[HY000] [1040] Too many connections'

图25

42、检查数据库连接组件、RBAC组件是否被注册,如果已经被注册,则无需覆盖,编辑 /common/components/db/ActiveRecord.php,如图26

        // 检查数据库连接组件、RBAC组件是否被注册
        if (!(Yii::$app->has($tenantDb) && Yii::$app->has('authManager'))) {
            // 注册数据库连接组件、RBAC组件
            Yii::$app->setComponents([
                $tenantDb => [
                    'class' => 'yii/db/Connection',
                    'dsn' => 'mysql:host=' . $tenantEnv['data']['db_info']['host'] . ';port=3306;dbname=' . $tenantEnv['data']['db_info']['database'] . '',
                    'username' => $tenantEnv['data']['db_info']['login'],
                    'password' => $tenantEnv['data']['db_info']['password'],
                    'tablePrefix' => $tenantEnv['data']['db_info']['prefix'],
                    'charset' => 'utf8',
                    'enableSchemaCache' => YII_ENV_PROD,
                ],
                'authManager' => [
                    'class' => 'yii/rbac/DbManager',
                    'db' => $tenantDb,
                    'itemTable' => '{{%rbac_auth_item}}',
                    'itemChildTable' => '{{%rbac_auth_item_child}}',
                    'assignmentTable' => '{{%rbac_auth_assignment}}',
                    'ruleTable' => '{{%rbac_auth_rule}}'
                ],
            ]);
        }
检查数据库连接组件、RBAC组件是否被注册,如果已经被注册,则无需覆盖,编辑 /common/components/db/ActiveRecord.php

图26

43、后台应用报错:’SQLSTATE[HY000] [1040] Too many connections’ 已经解决,如图27

后台应用报错:'SQLSTATE[HY000] [1040] Too many connections' 已经解决

图27

44、至此,数据库连接时的动态配置,配置属性来源于多租户系统已经基本上实现了,后续应该会实现缓存(多租户系统的响应),因为一次请求中,对于多租户系统的HTTP请求多达上百次,而每次HTTP请求平均耗时为100ms左右,如图28

至此,数据库连接时的动态配置,配置属性来源于多租户系统已经基本上实现了,后续应该会实现缓存(多租户系统的响应),因为一次请求中,对于多租户系统的HTTP请求多达上百次,而每次HTTP请求平均耗时为100ms左右

图28

45、开启 Schema 缓存(仅在生产环境中开启),且缓存至 Redis 中,编辑 /.env.dist、/.env,如图29

YII_ENV     = prod

# Redis
# ----
REDIS_HOSTNAME         = localhost # 主机名/IP地址
REDIS_PORT             = 6379 # 端口
#REDIS_PASSWORD        =  # 密码
REDIS_DATABASE         = 0 # 数据库

# Redis cache
# ----
REDIS_CACHE_KEY_PREFIX = ca: # 唯一键前缀
开启 Schema 缓存(仅在生产环境中开启),且缓存至 Redis 中,编辑 /.env.dist、/.env

图29

46、编辑 /common/config/base.php,在应用程序配置中配置 Redis 连接,且配置缓存组件

        'redisCache' => [
            'class' => 'yii/redis/Cache',
            'keyPrefix' => env('REDIS_CACHE_KEY_PREFIX'), // 唯一键前缀
        ],

        'redis' => [
            'class' => 'yii/redis/Connection',
            'hostname' => env('REDIS_HOSTNAME'),
            'port' => env('REDIS_PORT'),
            'password' => env('REDIS_PASSWORD'),
            'database' => env('REDIS_DATABASE'),
        ],

47、在数据库连接中开启 Schema 缓存,编辑 /common/components/db/ActiveRecord.php

        // 检查数据库连接组件、RBAC组件是否被注册
        if (!(Yii::$app->has($tenantDb) && Yii::$app->has('authManager'))) {
            // 注册数据库连接组件、RBAC组件
            Yii::$app->setComponents([
                $tenantDb => [
                    'class' => 'yii/db/Connection',
                    'dsn' => 'mysql:host=' . $tenantEnv['data']['db_info']['host'] . ';port=3306;dbname=' . $tenantEnv['data']['db_info']['database'] . '',
                    'username' => $tenantEnv['data']['db_info']['login'],
                    'password' => $tenantEnv['data']['db_info']['password'],
                    'tablePrefix' => $tenantEnv['data']['db_info']['prefix'],
                    'charset' => 'utf8',
                    'enableSchemaCache' => YII_ENV_PROD,
                    'schemaCache' => 'redisCache',
                ],
                'authManager' => [
                    'class' => 'yii/rbac/DbManager',
                    'db' => $tenantDb,
                    'itemTable' => '{{%rbac_auth_item}}',
                    'itemChildTable' => '{{%rbac_auth_item_child}}',
                    'assignmentTable' => '{{%rbac_auth_assignment}}',
                    'ruleTable' => '{{%rbac_auth_rule}}'
                ],
            ]);
        }

48、运行后台应用后,查看 Redis 中的数据,数据表的结构已经被缓存了,如图30

运行后台应用后,查看 Redis 中的数据,数据表的结构已经被缓存了

图30

49、实现缓存(多租户系统的响应),编辑 /common/logics/http/tenant/Env.php,运行后台应用后,查看 Redis 中的数据,多租户系统的响应数据已经被缓存了,如图31

<?php
/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2018/01/17
 * Time: 15:20
 */

namespace common/logics/http/tenant;

use Yii;
use yii/base/Model;
use yii/web/ServerErrorHttpException;

/**
 * 多租户的模块环境配置
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since 1.0
 */
class Env extends Model
{
    public $app_name;
    public $secret;
    public $tenant_id;

    public function attributeLabels()
    {
        return [
            'app_name' => /Yii::t('model/http/tenant/env', 'App Name'),
            'secret' => /Yii::t('model/http/tenant/env', 'Secret'),
            'tenant_id' => /Yii::t('model/http/tenant/env', 'Tenant ID'),
        ];
    }

    /**
     * 返回租户模块环境配置信息
     *
     * @return array|false
     *
     * 格式如下:
     *
     * 租户模块环境配置信息
     * [
     *     'message' => '', //说明
     *     'data' => [], //数据
     * ]
     *
     * 失败(将错误保存在 [[yii/base/Model::errors]] 属性中)
     * false
     *
     * @throws ServerErrorHttpException 如果响应状态码不等于20x
     */
    public function getTenantEnv()
    {
        /* 租户ID后续从请求参数中获取 */
        $this->tenant_id = 'default';

        // 设置多租户数据的缓存键
        $redisCache = Yii::$app->redisCache;
        $tenantKey = 'tenant:' . $this->tenant_id;

        // 从缓存中取回多租户数据
        $tenantData = $redisCache[$tenantKey];

        if ($tenantData === false) {
            $this->app_name = env('TENANT_APP_NAME');
            $this->secret = env('TENANT_SECRET');

            $response = Yii::$app->tenantHttp->createRequest()
                ->setMethod('get')
                ->setUrl('getTenantEnv')
                ->setData([
                    'appname' => $this->app_name,
                    'secret' => $this->secret,
                    'tenantid' => $this->tenant_id,
                ])
                ->send();
            // 检查响应状态码是否等于20x
            if ($response->isOk) {
                // 检查业务逻辑是否成功
                if ($response->data['returnCode'] === 0) {
                    $tenantData = ['message' => $response->data['returnDesc'], 'data' => $response->data['returnData']];
                    // 将多租户数据存放到缓存供下次使用
                    $redisCache[$tenantKey] = $tenantData;
                    return $tenantData;
                } else {
                    $this->addError('tenant_id', $response->data['returnDesc']);
                    return false;
                }
            } else {
                throw new ServerErrorHttpException(Yii::t('error', Yii::t('error', Yii::t('error', '20005'), ['statusCode' => $response->getStatusCode()])));
            }
        } else {
            return $tenantData;
        }

    }

}
实现缓存(多租户系统的响应),编辑 /common/logics/http/tenant/Env.php,运行后台应用后,查看 Redis 中的数据,多租户系统的响应数据已经被缓存了

图31

50、清空Redis,一次请求中,对于多租户系统的HTTP请求仅有1次,如图32

一次请求中,对于多租户系统的HTTP请求仅有1次

图32

51、默认情况下,缓存中的数据会永久存留,除非它被某些缓存策略强制移除(例如:缓存空间已满,最老的数据会被移除),后续准备实现清除对应缓存数据的接口,以便于多租户系统的数据发生变化时,可以调用清除对应缓存数据的接口

原创文章,作者:Maggie-Hunter,如若转载,请注明出处:https://blog.ytso.com/180928.html

(0)
上一篇 2021年10月31日
下一篇 2021年10月31日

相关推荐

发表回复

登录后才能评论