在 Yii 2 Starter Kit 中实现数据库连接时的动态配置,配置属性来源于多租户系统
1、多租户系统中包含多个租户,每个租户均有其自有的数据库配置信息,现有的需求是通过调用多租户系统的接口,基于响应的主机名、用户名和密码来连接数据库,而不是以应用组件的方式来配置,如图1
2、现在是以应用组件的方式来配置,\common\config\base.php,如图2
3、打开 Dubug,然后查看 Log Messages,db 组件的属性需要在 yii\db\Connection::open 之前完成初始化定义(调用多租户系统的接口),如图3
4、编辑 \common\config\base.php,配置 db 组件的类:common\components\db\Connection,将所有 env 变量注释掉
1 2 3 4 5 6 7 8 9 | '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
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 | <?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
7、现在需要调用多租户系统的接口,以其响应重新赋值 $tenantConfig(建议:此方案仅适用于存在一个租户的情况,如果存在多个租户,建议每个租户对应不同的连接组件,连接组件的命名基于租户ID,而不是对应 db 连接组件),如图5
8、现在还原所有修改,准备实现同时使用多个数据库(每个租户对应不同的连接组件,连接组件的命名基于租户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()]] 方法
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 | <?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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <?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 的使用
1 2 3 4 5 6 7 8 9 10 11 | /* '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
13、查看日志组件配置,\common\config\base.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | '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,保存日志消息到文件中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | '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
16、将 \common\models 下的所有模型文件,use yii\db\ActiveRecord; 替换为 use common\components\db\ActiveRecord;,如图9
1 2 | use yii\db\ActiveRecord; 替换为 use common\components\db\ActiveRecord; \yii\db\ActiveRecord 替换为 \common\components\db\ActiveRecord |
17、打开:http://frontend.cmcp-api.localhost/ ,报错:Failed to instantiate component or class “db”.,原因在于 RBAC 组件使用数据库表存放数据,如图10
18、编辑 \common\config\base.php,注释掉 RBAC 组件,因为其 db 属性默认值为 db,需要动态调整为租户ID所对应的数据库连接组件
1 2 3 4 5 6 7 8 9 | /* '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组件
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 | <?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
21、在 Postman 中,GET http://www.cmcp-api.localhost/v1/pages ,200响应,如图12
22、打开网址:http://backend.cmcp-api.localhost/ ,报错:Unknown component ID: db,如图13
23、编辑 \backend\models\SystemLog.php,继承至 \common\components\db\ActiveRecord,如图14
1 | \yii\db\ActiveRecord 替换为 \common\components\db\ActiveRecord |
24、打开网址:http://backend.cmcp-api.localhost/ ,200响应
25、现在需要调用多租户系统的接口,以其响应直接初始化使用数据库连接、RBAC,基于已经安装的 Yii 2 的 HTTP 客户端扩展,如图15
26、编辑 \.env.dist、\.env,新增多租户的相关配置
1 2 3 4 5 6 | # 多租户 # ---- 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,设置别名
1 | Yii::setAlias( '@tenantUrl' , env( 'TENANT_HOST_INFO' ) . env( 'TENANT_BASE_URL' )); |
28、通过应用组件配置客户端,编辑 \common\config\web.php,如图16
1 2 3 4 5 | 'tenantHttp' => [ 'class' => 'yii\httpclient\Client' , 'baseUrl' => Yii::getAlias( '@tenantUrl' ), 'transport' => 'yii\httpclient\CurlTransport' ], |
29、通过配置日志记录 HTTP 发送的请求并分析其执行情况,编辑 \common\config\base.php,如图17
1 2 3 4 5 | 'httpRequest' =>[ 'class' => 'yii\log\FileTarget' , 'logFile' => '@runtime/logs/http-request.log' , 'categories' => [ 'yii\httpclient\*' ], ] |
30、yii2 HTTP 客户端扩展提供了一个可以与 yii 调试模块集成的调试面板,并显示已执行的HTTP 请求,启用调试面板,如图18
1 2 3 4 5 6 7 8 9 | $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' , ], ], ]; |
31、刷新页面之后,查看日志面板,新增 HTTP Client,如图19
32、新建 \common\logics\http\tenant\Env.php,http目录表示此目录下的模型数据源自于HTTP请求,tenant目录表示多租户系统相关的模型,Env.php表示环境配置模型
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 84 | <?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
1 2 3 4 5 | 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
1 2 3 4 5 | return [ 'App Name' => '模块英文名称' , 'Secret' => '模块Secret' , 'Tenant ID' => '租户ID' , ]; |
34、编辑语言包文件,\api\messages\en\error.php、\api\messages\zh\error.php
\api\messages\en\error.php
1 2 | 20005 => 'Multitenant HTTP request failed with status code: {statusCode}' , 20006 => 'Multitenant HTTP request failed: {firstErrors}' , |
\api\messages\zh\error.php
1 2 | 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
1 2 3 4 5 | return [ 20000 => 'error' , 20005 => '多租户HTTP请求失败,状态码:{statusCode}' , 20006 => '多租户HTTP请求失败:{firstErrors}' , ]; |
36、编辑 \common\components\db\ActiveRecord.php,数据库配置信息源自于租户环境配置接口
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 | <?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
38、在 Postman 中 GET 多租户的环境配置接口,一个参数是错误的,如图22
39、编辑 \.env,TENANT_APP_NAME的值是错误的
1 | TENANT_APP_NAME = cmcpapi1 # 模块英文名称 |
40、在 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
42、检查数据库连接组件、RBAC组件是否被注册,如果已经被注册,则无需覆盖,编辑 \common\components\db\ActiveRecord.php,如图26
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // 检查数据库连接组件、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}}' ], ]); } |
43、后台应用报错:’SQLSTATE[HY000] [1040] Too many connections’ 已经解决,如图27
44、至此,数据库连接时的动态配置,配置属性来源于多租户系统已经基本上实现了,后续应该会实现缓存(多租户系统的响应),因为一次请求中,对于多租户系统的HTTP请求多达上百次,而每次HTTP请求平均耗时为100ms左右,如图2845、开启 Schema 缓存(仅在生产环境中开启),且缓存至 Redis 中,编辑 \.env.dist、\.env,如图29
1 2 3 4 5 6 7 8 9 10 11 12 | YII_ENV = prod # Redis # ---- REDIS_HOSTNAME = localhost # 主机名/IP地址 REDIS_PORT = 6379 # 端口 #REDIS_PASSWORD = # 密码 REDIS_DATABASE = 0 # 数据库 # Redis cache # ---- REDIS_CACHE_KEY_PREFIX = ca: # 唯一键前缀 |
46、编辑 \common\config\base.php,在应用程序配置中配置 Redis 连接,且配置缓存组件
1 2 3 4 5 6 7 8 9 10 11 12 | '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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | // 检查数据库连接组件、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
49、实现缓存(多租户系统的响应),编辑 \common\logics\http\tenant\Env.php,运行后台应用后,查看 Redis 中的数据,多租户系统的响应数据已经被缓存了,如图31
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 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | <?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 ; } } } |
50、清空Redis,一次请求中,对于多租户系统的HTTP请求仅有1次,如图32
51、默认情况下,缓存中的数据会永久存留,除非它被某些缓存策略强制移除(例如:缓存空间已满,最老的数据会被移除),后续准备实现清除对应缓存数据的接口,以便于多租户系统的数据发生变化时,可以调用清除对应缓存数据的接口
2 条回复
[…] […]
[…] 在 Yii 2 Starter Kit 中数据库迁移的多租户实现 – 永夜 – 水井湾发表在《在 Yii 2 Starter Kit 中实现数据库连接时的动态配置,配置属性来源于多租户系…》 […]