


'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,
],
<pre class="wp-block-syntaxhighlighter-code">
<?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);
}
}
</pre>



<pre class="wp-block-syntaxhighlighter-code">
<?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();
}
}
</pre>
<pre class="wp-block-syntaxhighlighter-code">
<?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
{
}
</pre>
/*
'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,
],
*/

'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}}'
]
],
],
'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'=>[],
]
],
],

use yii\db\ActiveRecord; 替换为 use common\components\db\ActiveRecord;
\yii\db\ActiveRecord 替换为 \common\components\db\ActiveRecord


/*
'authManager' => [
'class' => 'yii\rbac\DbManager',
'itemTable' => '{{%rbac_auth_item}}',
'itemChildTable' => '{{%rbac_auth_item_child}}',
'assignmentTable' => '{{%rbac_auth_assignment}}',
'ruleTable' => '{{%rbac_auth_rule}}'
],
*/
<pre class="wp-block-syntaxhighlighter-code">
<?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;
}
}
</pre>



\yii\db\ActiveRecord 替换为 \common\components\db\ActiveRecord


# 多租户
# ----
TENANT_HOST_INFO = http://wjdev.chinamcloud.com:8600 # HOME URL
TENANT_BASE_URL = /interface # BASE URL
TENANT_APP_NAME = cmcpapi # 模块英文名称
TENANT_SECRET = 94030d307b160f04b88592cb9bebdd4c # 模块Secret
Yii::setAlias('@tenantUrl', env('TENANT_HOST_INFO') . env('TENANT_BASE_URL'));
'tenantHttp' => [
'class' => 'yii\httpclient\Client',
'baseUrl' => Yii::getAlias('@tenantUrl'),
'transport' => 'yii\httpclient\CurlTransport'
],

'httpRequest'=>[
'class' => 'yii\log\FileTarget',
'logFile' => '@runtime/logs/http-request.log',
'categories' => ['yii\httpclient\*'],
]

$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',
],
],
];


<pre class="wp-block-syntaxhighlighter-code">
<?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()])));
}
}
}
</pre>
return [
'App Name' => 'English name of the module',
'Secret' => 'Secret module in multi-tenant system',
'Tenant ID' => 'Tenant ID',
];
return [
'App Name' => '模块英文名称',
'Secret' => '模块Secret',
'Tenant ID' => '租户ID',
];

20005 => 'Multitenant HTTP request failed with status code: {statusCode}',
20006 => 'Multitenant HTTP request failed: {firstErrors}',
20005 => '多租户HTTP请求失败,状态码:{statusCode}',
20006 => '多租户HTTP请求失败:{firstErrors}',
return [
20000 => 'error',
20005 => '多租户HTTP请求失败,状态码:{statusCode}',
20006 => '多租户HTTP请求失败:{firstErrors}',
];
<pre class="wp-block-syntaxhighlighter-code">
<?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;
}
}
</pre>


TENANT_APP_NAME = cmcpapi1 # 模块英文名称


![后台报错:Unable to send log via yii\log\FileTarget: Exception (Database Exception) 'yii\db\Exception' with message 'SQLSTATE[HY000] [1040] Too many connections'](https://media.shuijingwanwq.com/2018/01/25-1.png)
// 检查数据库连接组件、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}}'
],
]);
}

![后台应用报错:'SQLSTATE[HY000] [1040] Too many connections' 已经解决](https://media.shuijingwanwq.com/2018/01/27-1.png)

YII_ENV = prod
# Redis
# ----
REDIS_HOSTNAME = localhost # 主机名/IP地址
REDIS_PORT = 6379 # 端口
#REDIS_PASSWORD = # 密码
REDIS_DATABASE = 0 # 数据库
# Redis cache
# ----
REDIS_CACHE_KEY_PREFIX = ca: # 唯一键前缀

'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'),
],
// 检查数据库连接组件、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}}'
],
]);
}

<pre class="wp-block-syntaxhighlighter-code">
<?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;
}
}
}
</pre>


PHP / Laravel / Yii2 老项目维护与长期技术支持
如果你的 PHP / Laravel / Yii2 项目已经上线,但遇到原开发离职、Bug 长期无人修复、接口不稳定、性能下降、代码难以接手等问题,可以联系我做一次远程技术排查。
适合以下情况:
✅ 老旧 PHP 系统无人维护
✅ Laravel / Yii2 项目 Bug 修复
✅ 后台管理系统小功能迭代
✅ RESTful API 接口排查
✅ MySQL / Redis / Nginx 性能问题
✅ 长期远程兼职维护
可先从一次小问题开始:
✅ 线上报错排查
✅ 接口异常分析
✅ 慢查询与性能瓶颈定位
✅ 代码结构初步评估
✅ 部署环境与日志检查
如需咨询,请联系我,并注明:PHP 维护咨询。
联系方式:
Telegram:@shuijingwan
微信:13980074657
邮箱:shuijingwanwq@gmail.com

发表回复