Based on yiisoft/yii2-app-advanced, create a new repository yii2-app-advanced on github, and create a new interface application (implement RESTful-style web service services. API), adjust the default character set to: utf8mb4, the adjustment of the interface response format, the empty array is automatically converted to empty objects, and the request log message is collected in the interface application (1 request corresponds to 1 log message) to the database, and the corresponding interface to implement the log function: log list (set data filter to enable filter processing), log details
1. Set the default collation of the database as: utf8mb4_unicode_ci, as shown in Figure 1
2. Modify the default character set for database connections: utf8mb4, edit the configuration file in the development environment, \environments\dev\common\config\m ain-local.php, edit the configuration file in the production environment, \environments\prod\common\config\main-local.php
<?php
return [
'components' => [
'db' => [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=localhost;dbname=yii2advanced',
'username' => 'root',
'password' => '',
'charset' => 'utf8mb4',
],
'mailer' => [
'class' => 'yii\swiftmailer\Mailer',
'viewPath' => '@common/mail',
// send all mails to a file by default. You have to set
// 'useFileTransport' to false and configure a transport
// for the mailer to send real emails.
'useFileTransport' => true,
],
],
];
<?php
return [
'components' => [
'db' => [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=localhost;dbname=yii2advanced',
'username' => 'root',
'password' => '',
'charset' => 'utf8mb4',
],
'mailer' => [
'class' => 'yii\swiftmailer\Mailer',
'viewPath' => '@common/mail',
],
],
];
3. Execute the initialization command, as shown in Figure 2
.\init
4. Edit the database configuration, \common\config\main-local.php
'db' => [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=localhost;dbname=g-s-yii2-app-advanced',
'username' => 'g-s-yii2-app-advanced',
'password' => 'IADO0x7uK4UpaRRM',
'charset' => 'utf8mb4',
],
5. Clear the database and execute the database migration command, as shown in Figure 3
.\yii migrate
6. The database schema of the log can be initialized through application migration, and execute the following commands, as shown in Figure 4
.\yii migrate --migrationPath=@yii/log/migrations/
7. Browse the database table, the sorting rules of the user and log tables are: utf8_unicode_ci, as shown in Figure 5
8. Create a new database migration file, adjust the sorting rule: utf8mb4_unicode_ci, and execute the command, as shown in Figure 6
.\yii migrate/create update_table_options_to_log
9. Modify the default character set of the table (user, log) and the character set of all character columns, edit \console\migrations\m180620_105204_update_table_options_to_log.php
<?php
use yii\db\Migration;
/**
* Class m180620_105204_update_table_options_to_log
*/
class m180620_105204_update_table_options_to_log extends Migration
{
/**
* {@inheritdoc}
*/
public function safeUp()
{
$tableOptions = null;
if ($this->db->driverName === 'mysql') {
// http://stackoverflow.com/questions/766809/whats-the-difference-between-utf8-general-ci-and-utf8-unicode-ci
$tableOptions = 'CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE=InnoDB';
$this->execute('ALTER TABLE {{%user}} CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci');
$this->execute('ALTER TABLE {{%log}} CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci');
}
$this->addCommentOnTable('{{%user}}', '用户', $tableOptions);
$this->addCommentOnTable('{{%log}}', '日志', $tableOptions);
}
/**
* {@inheritdoc}
*/
public function safeDown()
{
echo "m180620_105204_update_table_options_to_log cannot be reverted.\n";
return false;
}
/*
// Use up()/down() to run migration code without a transaction.
public function up()
{
}
public function down()
{
echo "m180620_105204_update_table_options_to_log cannot be reverted.\n";
return false;
}
*/
}
10. One[[yii\log\DbTarget|database target]] The target exports the filtered log messages into a data table, and set the log target to dbtarget
11. The configuration file of the console application, \console\config\main.php, code
'components' => [
'log' => [
'targets' => [
[
'class' => 'yii\log\FileTarget',
'levels' => ['error', 'warning'],
],
],
],
],
12. The configuration file of the console application, \console\config\main.php, set the log target to dbtarget, and edit the code
'components' => [
'log' => [
'traceLevel' => YII_DEBUG ? 3 : 0,
'targets' => [
'file' => [
'class' => 'yii\log\FileTarget',
'levels' => ['error', 'warning'],
],
'db' => [
'class' => 'yii\log\DbTarget',
'except' => ['*'],
'prefix' => function () {
$url = !Yii::$app->request->isConsoleRequest ? Yii::$app->request->getUrl() : null;
$user = Yii::$app->has('user', true) ? Yii::$app->get('user') : null;
$userId = $user ? $user->getId(false) : '-';
return sprintf('[%s][%s][%s]', Yii::$app->id, $url, $userId);
},
'logVars' => [],
]
],
],
],
13. The configuration file of the interface application, \api\config\main.php, code
<?php
$params = array_merge(
require __DIR__ . '/../../common/config/params.php',
require __DIR__ . '/../../common/config/params-local.php',
require __DIR__ . '/params.php',
require __DIR__ . '/params-local.php'
);
return [
'id' => 'app-api',
'basePath' => dirname(__DIR__),
'bootstrap' => ['log', 'contentNegotiator'],
'controllerNamespace' => 'api\controllers',
'version' => '1.0.0',
'components' => [
'request' => [
'csrfParam' => '_csrf-api',
],
'user' => [
'identityClass' => 'api\models\User',
'enableSession' => false,
'loginUrl' => null,
'enableAutoLogin' => false,
],
'session' => [
// this is the name of the session cookie used for login on the api
'name' => 'advanced-api',
],
'log' => [
'traceLevel' => YII_DEBUG ? 3 : 0,
'targets' => [
[
'class' => 'yii\log\FileTarget',
'levels' => ['error', 'warning'],
],
],
],
'urlManager' => require __DIR__ . '/urlManager.php',
'i18n' => [
'translations' => [
'model/*'=> [
'class' => 'yii\i18n\PhpMessageSource',
'forceTranslation' => true,
'basePath'=>'@common/messages',
'fileMap'=>[
],
],
'*'=> [
'class' => 'yii\i18n\PhpMessageSource',
'forceTranslation' => true,
'basePath'=>'@api/messages',
'fileMap'=>[
],
],
],
],
'contentNegotiator' => [
'class' => 'yii\filters\ContentNegotiator',
'formats' => [
'application/json' => yii\web\Response::FORMAT_JSON,
'application/xml' => yii\web\Response::FORMAT_XML,
],
'languages' => [
'en-US',
'zh-CN',
],
],
],
'modules' => [
'v1' => [
'class' => api\modules\v1\Module::class,
],
],
'params' => $params,
];
14. The configuration file of the interface application, \api\config\main.php, set the log target to dbtarget, and edit the code
'log' => [
'traceLevel' => YII_DEBUG ? 3 : 0,
'targets' => [
'file' => [
'class' => 'yii\log\FileTarget',
'levels' => ['error', 'warning'],
],
'db' => [
'class' => 'yii\log\DbTarget',
'categories' => [
'api\behaviors\RequestLogBehavior::beforeRequest',
],
'prefix' => function () {
$url = !Yii::$app->request->isConsoleRequest ? Yii::$app->request->getUrl() : null;
$user = Yii::$app->has('user', true) ? Yii::$app->get('user') : null;
$userId = $user ? $user->getId(false) : '-';
return sprintf('[%s][%s][%s]', Yii::$app->id, $url, $userId);
},
'logVars' => [],
]
],
],
15. Delete all the tables in the database and execute the command again, as shown in Figure 7
.\yii migrate --migrationPath=@yii/log/migrations/
.\yii migrate
16. Browse the database table, the sorting rules of the user and log tables are: utf8mb4_unicode_ci, and the sorting rule of table columns is also: utf8mb4_unicode_ci, as shown in Figure 8
17. Define the request log behavior, trigger[[yii\base\Application::EVENT_BEFORE_REQUEST|EVENT_BEFORE_REQUEST]] When an event is written to the database, create a new \API\Behaviors\RequestLogBehavior.php
<?php
namespace api\behaviors;
use Yii;
use yii\base\Behavior;
/**
* Class RequestLogBehavior
* @package api\behaviors
* @author Qiang Wang <shuijingwanwq@163.com>
* @since 1.0
*/
class RequestLogBehavior extends Behavior
{
public function events()
{
return [
Yii::$app::EVENT_BEFORE_REQUEST => 'beforeRequest',
];
}
/**
* @inheritdoc
*/
public function beforeRequest($event)
{
$url = !Yii::$app->request->isConsoleRequest ? Yii::$app->request->getUrl() : null;
$requestQueryParams = Yii::$app->getRequest()->getQueryParams();
$requestBodyParams = Yii::$app->getRequest()->getBodyParams();
$user = Yii::$app->has('user', true) ? Yii::$app->get('user') : null;
$userId = $user ? $user->getId(false) : '-';
$message = [
'url' => $url,
'requestQueryParams' => $requestQueryParams,
'requestBodyParams' => $requestBodyParams,
'userId' => $userId,
'$_SERVER' => [
'HTTP_ACCEPT_LANGUAGE' => $_SERVER['HTTP_ACCEPT_LANGUAGE'],
'HTTP_ACCEPT' => $_SERVER['HTTP_ACCEPT'],
'HTTP_HOST' => $_SERVER['HTTP_HOST'],
'REMOTE_ADDR' => $_SERVER['REMOTE_ADDR'],
'REQUEST_URI' => $_SERVER['REQUEST_URI'],
'REQUEST_METHOD' => $_SERVER['REQUEST_METHOD'],
'CONTENT_TYPE' => $_SERVER['CONTENT_TYPE'],
],
];
Yii::info(serialize($message), __METHOD__);
}
}
18. Append behavior to the application body based on configuration, edit \API\config\main.php
'components' => [
],
'as requestLog' => [
'class' => api\behaviors\RequestLogBehavior::class,
],
19. View the log table, the run of the console application is not written to the log, because the[[yii\log\Target::except|except]] attribute to set all categories as blacklists, as shown in Figure 9
20. Run the interface application, and execute a non-existing interface request on Postman, the response is as follows
{
"name": "Not Found",
"message": "页面未找到。",
"code": 0,
"status": 404,
"type": "yii\\web\\NotFoundHttpException"
}
21. Run the interface application, execute 3 existing interface requests on Postman, and respond in turn as follows
{
"name": "Not Found",
"message": "User ID: 1, does not exist",
"code": 20002,
"status": 404,
"type": "yii\\web\\NotFoundHttpException"
}
{
"code": 10000,
"message": "Create user success",
"data": {
"username": "111111",
"email": "111111@163.com",
"password_hash": "$2y$13$2PjzCSRtyblFnpfgAW6HL.LUqVLzWqHcOtmKgttpcGtpXY6DtKRmy",
"auth_key": "gz-Cv8BczFGy2dFyd8ULjA_m1FK56vST",
"status": 10,
"created_at": 1529564925,
"updated_at": 1529564925,
"id": 1
}
}
{
"code": 10000,
"message": "获取用户列表成功",
"data": {
"items": [
{
"id": 1,
"username": "111111",
"auth_key": "gz-Cv8BczFGy2dFyd8ULjA_m1FK56vST",
"password_hash": "$2y$13$2PjzCSRtyblFnpfgAW6HL.LUqVLzWqHcOtmKgttpcGtpXY6DtKRmy",
"password_reset_token": null,
"email": "111111@163.com",
"status": 10,
"created_at": 1529564925,
"updated_at": 1529564925
}
],
"_links": {
"self": {
"href": "http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users?page=1"
}
},
"_meta": {
"totalCount": 1,
"pageCount": 1,
"currentPage": 1,
"perPage": 20
}
}
}
22. Check the log table, the operation of the interface application has been written to the log, because the[[yii\log\Target::categories|categories]] attribute to set the API\Behaviors\RequestLogBehavior::BeforeRequest category as a whitelist, so only The logs under the API\Behaviors\RequestLogBehavior::BeforeRequest category are written, and 4 logs are written due to a total of 4 executions, as shown in Figure 10
23. The corresponding interface to realize the log function, open the URL:http://www.github-shuijingwan-yii2-app-advanced.localhost/gii/model, the namespace is common\models, at this time, it is necessary to support internationalization to generate \common\models\log.php, as shown in Figure 11
<?php
namespace common\models;
use Yii;
/**
* This is the model class for table "{{%log}}".
*
* @property int $id
* @property int $level
* @property string $category
* @property double $log_time
* @property string $prefix
* @property string $message
*/
class Log extends \yii\db\ActiveRecord
{
/**
* @inheritdoc
*/
public static function tableName()
{
return '{{%log}}';
}
/**
* @inheritdoc
*/
public function rules()
{
return [
[['level'], 'integer'],
[['log_time'], 'number'],
[['prefix', 'message'], 'string'],
[['category'], 'string', 'max' => 255],
];
}
/**
* @inheritdoc
*/
public function attributeLabels()
{
return [
'id' => Yii::t('model/log', 'ID'),
'level' => Yii::t('model/log', 'Level'),
'category' => Yii::t('model/log', 'Category'),
'log_time' => Yii::t('model/log', 'Log Time'),
'prefix' => Yii::t('model/log', 'Prefix'),
'message' => Yii::t('model/log', 'Message'),
];
}
}
24. Create a new \common\logics\log.php, the mysql model file in the common/logics directory is related to business logic, and inherit to the \common\models\log data layer
<?php
namespace common\logics;
use Yii;
/**
* This is the model class for table "{{%log}}".
*
* @property int $id
* @property int $level
* @property string $category
* @property double $log_time
* @property string $prefix
* @property string $message
*/
class Log extends \common\models\Log
{
}
25. Create a new \common\messages\en-us\model\log.php, support the message translation of the target language when the target language is English and the United States
<?php
/**
* Created by PhpStorm.
* User: WangQiang
* Date: 2018/06/21
* Time: 15:41
*/
return [
'ID' => 'ID',
'Level' => 'Level',
'Category' => 'Category',
'Log Time' => 'Log Time',
'Prefix' => 'Prefix',
'Message' => 'Message',
];
26. Create a new \common\messages\zh-cn\model\log.php, which supports message translation when the target language is simplified Chinese
<?php
/**
* Created by PhpStorm.
* User: WangQiang
* Date: 2018/06/21
* Time: 15:44
*/
return [
'ID' => 'ID',
'Level' => '等级',
'Category' => '分类',
'Log Time' => '日志时间',
'Prefix' => '前缀',
'Message' => '消息',
];
27. Create a new \api\models\log.php, the mysql model file in the api/models directory is business logic related (only related to the API), inherit to \common\logics\log logic layer
<?php
/**
* Created by PhpStorm.
* User: WangQiang
* Date: 2018/06/21
* Time: 15:50
*/
namespace api\models;
class Log extends \common\logics\Log
{
}
28. Create new \API\modules\v1\models\log.php, inherit to \api\models\log.php
Note: \api\modules\v1\models\log (only for v1 modules) > \api\models\log (for API applications only) > \common\logics\log.php (available for multiple applications such as API, Frontend, etc.) > \common\models\log.php (limited to GII generation only) > \yii\db\ActiveRecord
<?php
/**
* Created by PhpStorm.
* User: WangQiang
* Date: 2018/06/21
* Time: 15:53
*/
namespace api\modules\v1\models;
class Log extends \api\models\Log
{
}
29. The corresponding interface to realize the log function, edit \api\config\urlmanager.php, only support list and details
<?php
return [
'class' => yii\web\UrlManager::class,
'enablePrettyUrl' => true,
'enableStrictParsing' => true,
'showScriptName' => false,
'rules' => [
[
'class' => 'yii\rest\UrlRule',
'controller' => ['v1/user'],
],
[
'class' => 'yii\rest\UrlRule',
'controller' => ['v1/log'],
'only' => ['index', 'view'],
],
],
];
30. Create a new controller class \api\controllers\logcontroller.php
<?php
/**
* Created by PhpStorm.
* User: WangQiang
* Date: 2018/06/21
* Time: 15:29
*/
namespace api\controllers;
use yii\rest\ActiveController;
class LogController extends ActiveController
{
public $serializer = [
'class' => 'api\rests\log\Serializer',
'collectionEnvelope' => 'items',
];
/**
* @inheritdoc
*/
public function actions()
{
$actions = parent::actions();
// 禁用"create"、"update"、"delete"、"options"动作
unset($actions['create'], $actions['update'], $actions['delete'], $actions['options']);
$actions['index']['class'] = 'api\rests\log\IndexAction';
$actions['view']['class'] = 'api\rests\log\ViewAction';
return $actions;
}
}
31. Create a new \API\modules\v1\controllers\logcontroller.php, by specifying[[yii\rest\ActiveController::modelClass|modelClass]] As api\modules\v1\models\log, the controller can know which model to use to obtain and process data
Note: \API\modules\v1\Controllers\logController.php (only for v1 modules) > \API\Controllers\logController.php (for API Application) > \yii\rest\ActiveController
<?php
namespace api\modules\v1\controllers;
/**
* Log controller for the `v1` module
*/
class LogController extends \api\controllers\LogController
{
public $modelClass = 'api\modules\v1\models\Log';
}
32. Copy the action.php, indexaction.php, viewaction.php, serializer.php to the directory \api\rests\log under the directory \api\rests\user
33. Edit \api\rests\log\indexaction.php, adjust namespace, inheritance, query conditions, etc.
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace api\rests\log;
use Yii;
use yii\base\DynamicModel;
use yii\data\ActiveDataProvider;
/**
* IndexAction implements the API endpoint for listing multiple models.
*
* For more details and usage information on IndexAction, see the [guide article on rest controllers](guide:rest-controllers).
*
* @author Qiang Wang <shuijingwanwq@163.com>
* @since 1.0
*/
class IndexAction extends \yii\rest\IndexAction
{
/**
* Prepares the data provider that should return the requested collection of the models.
* @return ActiveDataProvider
*/
protected function prepareDataProvider()
{
$requestParams = Yii::$app->getRequest()->getBodyParams();
if (empty($requestParams)) {
$requestParams = Yii::$app->getRequest()->getQueryParams();
}
/* 数据过滤器 */
$this->dataFilter = [
'class' => 'yii\data\ActiveDataFilter',
'searchModel' => function () {
return (new DynamicModel(['level' => null, 'category' => null, 'log_time' => null, 'prefix' => null]))
->addRule('level', 'integer')
->addRule(['category', 'prefix'], 'trim')
->addRule('log_time', 'double')
->addRule(['category', 'prefix'], 'string');
},
];
$filter = null;
if ($this->dataFilter !== null) {
$this->dataFilter = Yii::createObject($this->dataFilter);
if ($this->dataFilter->load($requestParams)) {
$filter = $this->dataFilter->build();
if ($filter === false) {
foreach ($this->dataFilter->getFirstErrors() as $message) {
$firstErrors = $message;
break;
}
return ['code' => 20803, 'message' => Yii::t('error', Yii::t('error', Yii::t('error', '20803'), ['firstErrors' => $firstErrors]))];
}
}
}
if ($this->prepareDataProvider !== null) {
return call_user_func($this->prepareDataProvider, $this, $filter);
}
/* @var $modelClass \yii\db\BaseActiveRecord */
$modelClass = $this->modelClass;
$query = $modelClass::find();
if (!empty($filter)) {
$query->andFilterWhere($filter);
}
return Yii::createObject([
'class' => ActiveDataProvider::className(),
'query' => $query,
'pagination' => [
'params' => $requestParams,
],
'sort' => [
'params' => $requestParams,
],
]);
}
}
34. Edit \api\rests\log\serializer.php, adjust the namespace, inheritance relationship, and response structure (response success: “code”: 10000,”message”,”data”; response failed: “code”: other numbers not equal to 10000, “message”) etc.
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace api\rests\log;
use Yii;
use yii\data\DataProviderInterface;
/**
* Serializer converts resource objects and collections into array representation.
*
* Serializer is mainly used by REST controllers to convert different objects into array representation
* so that they can be further turned into different formats, such as JSON, XML, by response formatters.
*
* The default implementation handles resources as [[Model]] objects and collections as objects
* implementing [[DataProviderInterface]]. You may override [[serialize()]] to handle more types.
*
* @author Qiang Wang <shuijingwanwq@163.com>
* @since 1.0
*/
class Serializer extends \yii\rest\Serializer
{
/**
* Serializes a data provider.
* @param DataProviderInterface $dataProvider
* @return array the array representation of the data provider.
*/
protected function serializeDataProvider($dataProvider)
{
if ($this->preserveKeys) {
$models = $dataProvider->getModels();
} else {
$models = array_values($dataProvider->getModels());
}
$models = $this->serializeModels($models);
if (($pagination = $dataProvider->getPagination()) !== false) {
$this->addPaginationHeaders($pagination);
}
if ($this->request->getIsHead()) {
return null;
} elseif ($this->collectionEnvelope === null) {
return $models;
}
$result = [
$this->collectionEnvelope => $models,
];
if (empty($result['items'])) {
return ['code' => 20801, 'message' => Yii::t('error', '20801')];
}
foreach ($result['items'] as $key => $item) {
$result['items'][$key]['message'] = $item['message'] = unserialize($item['message']);
if (empty($item['message']['userId'])) {
$result['items'][$key]['message']['userId'] = '0';
}
if (empty($item['message']['requestQueryParams'])) {
$result['items'][$key]['message']['requestQueryParams'] = (object)[];
}
if (empty($item['message']['requestBodyParams'])) {
$result['items'][$key]['message']['requestBodyParams'] = (object)[];
}
}
if ($pagination !== false) {
return ['code' => 10000, 'message' => Yii::t('success', '10801'), 'data' => array_merge($result, $this->serializePagination($pagination))];
}
return ['code' => 10000, 'message' => Yii::t('success', '10801'), 'data' => $result];
}
}
35. Edit the language pack file: \api\messages\zh-cn\success.php (Simplified Chinese, the response is successful)
<?php
return [
10000 => 'success',
10001 => '获取用户列表成功',
10002 => '获取用户详情成功',
10003 => '创建用户成功',
10004 => '更新用户成功',
10005 => '删除用户成功',
10801 => '获取日志列表成功',
10802 => '获取日志详情成功',
];
36. Edit the language pack file: \api\messages\zh-cn\error.php (Simplified Chinese, response failed)
<?php
return [
20000 => 'error',
20001 => '用户列表为空',
20002 => '用户ID:{id},不存在',
20003 => '用户ID:{id},的状态为已删除',
20004 => '数据验证失败:{firstErrors}',
20801 => '日志列表为空',
20802 => '日志ID:{id},不存在',
20803 => '数据过滤器验证失败:{firstErrors}',
];
37. Edit the language pack file: \api\messages\en-us\success.php (English American, the response is successful)
<?php
return [
10000 => 'success',
10001 => 'Get user list success',
10002 => 'Get user details success',
10003 => 'Create user success',
10004 => 'Update user success',
10005 => 'Delete user success',
10801 => 'Get log list success',
10802 => 'Get log details success',
];
38. Edit the language pack file: \api\messages\en-us\error.php (English American, response failed)
<?php
return [
20000 => 'error',
20001 => 'User list is empty',
20002 => 'User ID: {id}, does not exist',
20003 => 'User ID: {id}, status is deleted',
20004 => 'Data validation failed: {firstErrors}',
20801 => 'Log list is empty',
20802 => 'Log ID: {id}, does not exist',
20803 => 'Data filter validation failed: {firstErrors}',
];
39. In Postman, gethttp://api.github-shuijingwan-yii2-app-advanced.localhost/v1/logs, 200 response, the format of RequestQueryParams, RequestBodyParams is sometimes an array (when empty), sometimes an object, as shown in Figure 12
{
"code": 10000,
"message": "获取日志列表成功",
"data": {
"items": [
{
"id": 1,
"level": 4,
"category": "api\\behaviors\\RequestLogBehavior::beforeRequest",
"log_time": 1529564823.0948,
"prefix": "[app-api][/v1/menus][]",
"message": {
"url": "/v1/menus",
"requestQueryParams": {},
"requestBodyParams": {},
"userId": "0",
"$_SERVER": {
"HTTP_ACCEPT_LANGUAGE": "zh-CN",
"HTTP_ACCEPT": "application/json; version=0.0; cookie=enable",
"HTTP_HOST": "api.github-shuijingwan-yii2-app-advanced.localhost",
"REMOTE_ADDR": "127.0.0.1",
"REQUEST_URI": "/v1/menus",
"REQUEST_METHOD": "GET",
"CONTENT_TYPE": "application/x-www-form-urlencoded"
}
}
},
{
"id": 2,
"level": 4,
"category": "api\\behaviors\\RequestLogBehavior::beforeRequest",
"log_time": 1529564916.6603,
"prefix": "[app-api][/v1/users/1][]",
"message": {
"url": "/v1/users/1",
"requestQueryParams": {},
"requestBodyParams": {
"email": "222222@qq.com",
"password": "222222",
"status": "0"
},
"userId": "0",
"$_SERVER": {
"HTTP_ACCEPT_LANGUAGE": "en-US",
"HTTP_ACCEPT": "application/json; version=0.0",
"HTTP_HOST": "api.github-shuijingwan-yii2-app-advanced.localhost",
"REMOTE_ADDR": "127.0.0.1",
"REQUEST_URI": "/v1/users/1",
"REQUEST_METHOD": "PUT",
"CONTENT_TYPE": "application/x-www-form-urlencoded"
}
}
},
{
"id": 3,
"level": 4,
"category": "api\\behaviors\\RequestLogBehavior::beforeRequest",
"log_time": 1529564924.6648,
"prefix": "[app-api][/v1/users][]",
"message": {
"url": "/v1/users",
"requestQueryParams": {},
"requestBodyParams": {
"email": "111111@163.com",
"password": "111111",
"username": "111111"
},
"userId": "0",
"$_SERVER": {
"HTTP_ACCEPT_LANGUAGE": "en-US",
"HTTP_ACCEPT": "application/json; version=0.0",
"HTTP_HOST": "api.github-shuijingwan-yii2-app-advanced.localhost",
"REMOTE_ADDR": "127.0.0.1",
"REQUEST_URI": "/v1/users",
"REQUEST_METHOD": "POST",
"CONTENT_TYPE": "application/x-www-form-urlencoded"
}
}
}
],
"_links": {
"self": {
"href": "http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/logs?per-page=3&page=1"
},
"next": {
"href": "http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/logs?per-page=3&page=2"
},
"last": {
"href": "http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/logs?per-page=3&page=71"
}
},
"_meta": {
"totalCount": 212,
"pageCount": 71,
"currentPage": 1,
"perPage": 3
}
}
}
40. In Postman, gethttp://api.github-shuijingwan-yii2-app-advanced.localhost/v1/logs?filter[level]=A , 200 responses, as shown in Figure 13
{
"code": 20803,
"message": "数据过滤器验证失败:Level必须是整数。"
}
41. In Postman, gethttp://api.github-shuijingwan-yii2-app-advanced.localhost/v1/logs?filter[level]=4&filter[category][like]=RequestLogBehavior&Filter[prefix][like]=app-api&filter[log_time][gte]=1528090828&filter[log_time][lte]=1529564924.6648, test data filter, 200 response, as shown in Figure 14
filter[level]:4
filter[category][like]:RequestLogBehavior
filter[prefix][like]:app-api
filter[log_time][gte]:1528090828
filter[log_time][lte]:1529564924.6648
{
"code": 10000,
"message": "获取日志列表成功",
"data": {
"items": [
{
"id": 1,
"level": 4,
"category": "api\\behaviors\\RequestLogBehavior::beforeRequest",
"log_time": 1529564823.0948,
"prefix": "[app-api][/v1/menus][]",
"message": {
"url": "/v1/menus",
"requestQueryParams": {},
"requestBodyParams": {},
"userId": "0",
"$_SERVER": {
"HTTP_ACCEPT_LANGUAGE": "zh-CN",
"HTTP_ACCEPT": "application/json; version=0.0; cookie=enable",
"HTTP_HOST": "api.github-shuijingwan-yii2-app-advanced.localhost",
"REMOTE_ADDR": "127.0.0.1",
"REQUEST_URI": "/v1/menus",
"REQUEST_METHOD": "GET",
"CONTENT_TYPE": "application/x-www-form-urlencoded"
}
}
},
{
"id": 2,
"level": 4,
"category": "api\\behaviors\\RequestLogBehavior::beforeRequest",
"log_time": 1529564916.6603,
"prefix": "[app-api][/v1/users/1][]",
"message": {
"url": "/v1/users/1",
"requestQueryParams": {},
"requestBodyParams": {
"email": "222222@qq.com",
"password": "222222",
"status": "0"
},
"userId": "0",
"$_SERVER": {
"HTTP_ACCEPT_LANGUAGE": "en-US",
"HTTP_ACCEPT": "application/json; version=0.0",
"HTTP_HOST": "api.github-shuijingwan-yii2-app-advanced.localhost",
"REMOTE_ADDR": "127.0.0.1",
"REQUEST_URI": "/v1/users/1",
"REQUEST_METHOD": "PUT",
"CONTENT_TYPE": "application/x-www-form-urlencoded"
}
}
},
{
"id": 3,
"level": 4,
"category": "api\\behaviors\\RequestLogBehavior::beforeRequest",
"log_time": 1529564924.6648,
"prefix": "[app-api][/v1/users][]",
"message": {
"url": "/v1/users",
"requestQueryParams": {},
"requestBodyParams": {
"email": "111111@163.com",
"password": "111111",
"username": "111111"
},
"userId": "0",
"$_SERVER": {
"HTTP_ACCEPT_LANGUAGE": "en-US",
"HTTP_ACCEPT": "application/json; version=0.0",
"HTTP_HOST": "api.github-shuijingwan-yii2-app-advanced.localhost",
"REMOTE_ADDR": "127.0.0.1",
"REQUEST_URI": "/v1/users",
"REQUEST_METHOD": "POST",
"CONTENT_TYPE": "application/x-www-form-urlencoded"
}
}
}
],
"_links": {
"self": {
"href": "http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/logs?filter%5Blevel%5D=4&filter%5Bcategory%5D%5Blike%5D=RequestLogBehavior&filter%5Bprefix%5D%5Blike%5D=app-api&filter%5Blog_time%5D%5Bgte%5D=1528090828&filter%5Blog_time%5D%5Blte%5D=1529564924.6648&page=1"
}
},
"_meta": {
"totalCount": 3,
"pageCount": 1,
"currentPage": 1,
"perPage": 20
}
}
}
SELECT COUNT(*) FROM `log` WHERE (`level`='4') AND (`category` LIKE '%RequestLogBehavior%') AND (`prefix` LIKE '%app-api%') AND ((`log_time` >= '1528090828') AND (`log_time` <= '1529564924.6648'))
42. Define a search model, which should declare all available search properties and their validation rules, create new \common\logics\logsearch.php
<?php
namespace common\logics;
use Yii;
use yii\base\Model;
/**
* LogSearch represents the model behind the search form about `common\models\Log`.
*/
class LogSearch extends Model
{
public $level;
public $category;
public $log_time;
public $prefix;
/**
* @inheritdoc
*/
public function rules()
{
return [
[['level'], 'integer'],
[['log_time'], 'number'],
[['prefix', 'message'], 'string'],
[['category', 'prefix'], 'trim'],
];
}
}
43. Create a new \api\models\logsearch.php, the mysql model file in the api/models directory is business logic related (only related to the API), inherited to \common\logics\logsearch logic layer
<?php
/**
* Created by PhpStorm.
* User: WangQiang
* Date: 2018/06/22
* Time: 13:43
*/
namespace api\models;
class LogSearch extends \common\logics\LogSearch
{
}
44. Create a new \API\modules\v1\models\logsearch.php, inherit to \api\models\logsearch.php
<?php
/**
* Created by PhpStorm.
* User: WangQiang
* Date: 2018/06/22
* Time: 13:46
*/
namespace api\modules\v1\models;
class LogSearch extends \api\models\LogSearch
{
}
45. Edit \api\rests\log\indexaction.php, cancel the use Yii\Base\DynamicModel as $SearchModel, set the data filter to enable filter processing, generate SQL statements, as shown in Figure 15
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace api\rests\log;
use Yii;
use yii\data\ActiveDataProvider;
/**
* IndexAction implements the API endpoint for listing multiple models.
*
* For more details and usage information on IndexAction, see the [guide article on rest controllers](guide:rest-controllers).
*
* @author Qiang Wang <shuijingwanwq@163.com>
* @since 1.0
*/
class IndexAction extends \yii\rest\IndexAction
{
public $dataFilter = [
'class' => 'yii\data\ActiveDataFilter',
'searchModel' => 'api\modules\v1\models\LogSearch',
];
/**
* Prepares the data provider that should return the requested collection of the models.
* @return ActiveDataProvider
*/
protected function prepareDataProvider()
{
$requestParams = Yii::$app->getRequest()->getBodyParams();
if (empty($requestParams)) {
$requestParams = Yii::$app->getRequest()->getQueryParams();
}
$filter = null;
if ($this->dataFilter !== null) {
$this->dataFilter = Yii::createObject($this->dataFilter);
if ($this->dataFilter->load($requestParams)) {
$filter = $this->dataFilter->build();
if ($filter === false) {
foreach ($this->dataFilter->getFirstErrors() as $message) {
$firstErrors = $message;
break;
}
return ['code' => 20803, 'message' => Yii::t('error', Yii::t('error', Yii::t('error', '20803'), ['firstErrors' => $firstErrors]))];
}
}
}
if ($this->prepareDataProvider !== null) {
return call_user_func($this->prepareDataProvider, $this, $filter);
}
/* @var $modelClass \yii\db\BaseActiveRecord */
$modelClass = $this->modelClass;
$query = $modelClass::find();
if (!empty($filter)) {
$query->andFilterWhere($filter);
}
return Yii::createObject([
'class' => ActiveDataProvider::className(),
'query' => $query,
'pagination' => [
'params' => $requestParams,
],
'sort' => [
'params' => $requestParams,
],
]);
}
}
SELECT COUNT(*) FROM `log` WHERE (`level`='4') AND (`category` LIKE '%RequestLogBehavior%') AND (`prefix` LIKE '%app-api%') AND ((`log_time` >= '1528090828') AND (`log_time` <= '1529564924.6648'))
46, get /logs/1: Returns the details of log 1, edit \api\rests\log\action.php, adjust the namespace, inheritance, response structure, etc.
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace api\rests\log;
use Yii;
use yii\db\ActiveRecordInterface;
use yii\web\NotFoundHttpException;
/**
* Action is the base class for action classes that implement RESTful API.
*
* For more details and usage information on Action, see the [guide article on rest controllers](guide:rest-controllers).
*
* @author Qiang Wang <shuijingwanwq@163.com>
* @since 1.0
*/
class Action extends \yii\rest\Action
{
/**
* Returns the data model based on the primary key given.
* If the data model is not found, a 404 HTTP exception will be raised.
* @param string $id the ID of the model to be loaded. If the model has a composite primary key,
* the ID must be a string of the primary key values separated by commas.
* The order of the primary key values should follow that returned by the `primaryKey()` method
* of the model.
* @return ActiveRecordInterface the model found
* @throws NotFoundHttpException if the model cannot be found
*/
public function findModel($id)
{
if ($this->findModel !== null) {
return call_user_func($this->findModel, $id, $this);
}
/* @var $modelClass ActiveRecordInterface */
$modelClass = $this->modelClass;
$keys = $modelClass::primaryKey();
if (count($keys) > 1) {
$values = explode(',', $id);
if (count($keys) === count($values)) {
$model = $modelClass::findOne(array_combine($keys, $values));
}
} elseif ($id !== null) {
$model = $modelClass::findOne($id);
}
if (isset($model)) {
return $model;
}
throw new NotFoundHttpException(Yii::t('error', Yii::t('error', Yii::t('error', '20802'), ['id' => $id])), 20802);
}
}
47. Edit \api\rests\log\viewaction.php, adjust the namespace, inheritance relationship, response structure, etc. ContentNegotiator supports response content format processing and language processing. Determine the response content format and language by checking the GET parameter and the Accept HTTP header. Configure ContentNegotiator to support English American and Simplified Chinese. Configure the response component, pass the encoding options to yii\helpers\json::encode() , json_force_object | json_unescaped_slashes | json_unescaped_unicode, json_force_object: make a non-associated array output a class (Object) instead of an array. This is especially useful when the array is empty and the recipient needs a class (Object). Avoid manually handling the conversion of empty arrays.
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace api\rests\log;
use Yii;
/**
* ViewAction implements the API endpoint for returning the detailed information about a model.
*
* For more details and usage information on ViewAction, see the [guide article on rest controllers](guide:rest-controllers).
*
* @author Qiang Wang <shuijingwanwq@163.com>
* @since 1.0
*/
class ViewAction extends Action
{
/**
* Displays a model.
* @param string $id the primary key of the model.
* @return \yii\db\ActiveRecordInterface the model being displayed
*/
public function run($id)
{
$model = $this->findModel($id);
if ($this->checkAccess) {
call_user_func($this->checkAccess, $this->id, $model);
}
$model->message = $message = unserialize($model->message);
if (empty($message['userId'])) {
$message['userId'] = '0';
}
$model->message = $message;
$response = Yii::$app->response;
$response->formatters = [
yii\web\Response::FORMAT_JSON => [
'class' => 'yii\web\JsonResponseFormatter',
'encodeOptions' => 336,
],
yii\web\Response::FORMAT_XML => [
'class' => 'yii\web\XmlResponseFormatter',
],
];
return ['code' => 10000, 'message' => Yii::t('success', '10802'), 'data' => $model];
}
}
48. In Postman, gethttp://api.github-shuijingwan-yii2-app-advanced.localhost/v1/logs/3,as shown in Figure 16
{
"code": 10000,
"message": "获取日志详情成功",
"data": {
"id": 3,
"level": 4,
"category": "api\\behaviors\\RequestLogBehavior::beforeRequest",
"log_time": 1529564924.6648,
"prefix": "[app-api][/v1/users][]",
"message": {
"url": "/v1/users",
"requestQueryParams": {},
"requestBodyParams": {
"email": "111111@163.com",
"password": "111111",
"username": "111111"
},
"userId": "0",
"$_SERVER": {
"HTTP_ACCEPT_LANGUAGE": "en-US",
"HTTP_ACCEPT": "application/json; version=0.0",
"HTTP_HOST": "api.github-shuijingwan-yii2-app-advanced.localhost",
"REMOTE_ADDR": "127.0.0.1",
"REQUEST_URI": "/v1/users",
"REQUEST_METHOD": "POST",
"CONTENT_TYPE": "application/x-www-form-urlencoded"
}
}
}
}
SELECT * FROM `log` WHERE `id`='3'
49. Define the request log behavior, trigger[[yii\base\Application::EVENT_BEFORE_REQUEST|EVENT_BEFORE_REQUEST]] When the event is written to the database, edit \api\behaviors\requestLogBehavior.php, and replace null with, to keep the field format uniform.
50. The configuration file of the interface application, \api\config\main.php, set the log target to dbtarget, and set the whitelist to classify as api\behaviors\requestlogBehavior::AfterRequest Edit code to avoid message[‘userId’]If the value cannot be obtained, Note: Requests such as 404 will not be able to write to the log
'log' => [
'traceLevel' => YII_DEBUG ? 3 : 0,
'targets' => [
'file' => [
'class' => 'yii\log\FileTarget',
'levels' => ['error', 'warning'],
],
'db' => [
'class' => 'yii\log\DbTarget',
'categories' => [
'api\behaviors\RequestLogBehavior::afterRequest',
],
'prefix' => function () {
$url = !Yii::$app->request->isConsoleRequest ? Yii::$app->request->getUrl() : null;
$user = Yii::$app->has('user', true) ? Yii::$app->get('user') : null;
$userId = $user ? $user->getId(false) : '-';
return sprintf('[%s][%s][%s]', Yii::$app->id, $url, $userId);
},
'logVars' => [],
]
],
51. Define the request log behavior, trigger[[yii\base\Application::EVENT_AFTER_REQUEST|EVENT_AFTER_REQUEST]] When the event, write the log to the database, edit \api\behaviors\requestlogbehavior.php, and adjust the field to lowercase, use the corresponding field in other files, and modify it synchronously
<?php
namespace api\behaviors;
use Yii;
use yii\base\Behavior;
/**
* Class RequestLogBehavior
* @package api\behaviors
* @author Qiang Wang <shuijingwanwq@163.com>
* @since 1.0
*/
class RequestLogBehavior extends Behavior
{
public function events()
{
return [
Yii::$app::EVENT_AFTER_REQUEST => 'afterRequest',
];
}
/**
* @inheritdoc
*/
public function afterRequest($event)
{
$url = !Yii::$app->request->isConsoleRequest ? Yii::$app->request->getUrl() : '';
$requestQueryParams = Yii::$app->getRequest()->getQueryParams();
$requestBodyParams = Yii::$app->getRequest()->getBodyParams();
$user = Yii::$app->has('user', true) ? Yii::$app->get('user') : null;
$userId = $user ? $user->getId(false) : '-';
$message = [
'url' => $url,
'request_query_params' => $requestQueryParams,
'request_body_params' => $requestBodyParams,
'user_id' => $userId,
'$_SERVER' => [
'HTTP_ACCEPT_LANGUAGE' => $_SERVER['HTTP_ACCEPT_LANGUAGE'],
'HTTP_ACCEPT' => $_SERVER['HTTP_ACCEPT'],
'HTTP_HOST' => $_SERVER['HTTP_HOST'],
'REMOTE_ADDR' => $_SERVER['REMOTE_ADDR'],
'REQUEST_URI' => $_SERVER['REQUEST_URI'],
'REQUEST_METHOD' => $_SERVER['REQUEST_METHOD'],
'CONTENT_TYPE' => $_SERVER['CONTENT_TYPE'],
],
];
Yii::info(serialize($message), __METHOD__);
}
}
52. In Postman, gethttp://api.github-shuijingwan-yii2-app-advanced.localhost/v1/logs?page=77&per-page=3, 200 responses
{
"code": 10000,
"message": "获取日志列表成功",
"data": {
"items": [
{
"id": 229,
"level": 4,
"category": "api\\behaviors\\RequestLogBehavior::afterRequest",
"log_time": 1529980262.0921,
"prefix": "[app-api][/v1/logs?page=12&per-page=76][]",
"message": {
"url": "/v1/logs?page=12&per-page=76",
"request_query_params": {
"page": "12",
"per-page": "76"
},
"request_body_params": {},
"user_id": "0",
"$_SERVER": {
"HTTP_ACCEPT_LANGUAGE": "zh-CN",
"HTTP_ACCEPT": "application/json; version=0.0",
"HTTP_HOST": "api.github-shuijingwan-yii2-app-advanced.localhost",
"REMOTE_ADDR": "127.0.0.1",
"REQUEST_URI": "/v1/logs?page=12&per-page=76",
"REQUEST_METHOD": "GET",
"CONTENT_TYPE": ""
}
}
},
{
"id": 230,
"level": 4,
"category": "api\\behaviors\\RequestLogBehavior::afterRequest",
"log_time": 1529980273.5208,
"prefix": "[app-api][/v1/logs?page=76&per-page=3][]",
"message": {
"url": "/v1/logs?page=76&per-page=3",
"request_query_params": {
"page": "76",
"per-page": "3"
},
"request_body_params": {},
"user_id": "0",
"$_SERVER": {
"HTTP_ACCEPT_LANGUAGE": "zh-CN",
"HTTP_ACCEPT": "application/json; version=0.0",
"HTTP_HOST": "api.github-shuijingwan-yii2-app-advanced.localhost",
"REMOTE_ADDR": "127.0.0.1",
"REQUEST_URI": "/v1/logs?page=76&per-page=3",
"REQUEST_METHOD": "GET",
"CONTENT_TYPE": ""
}
}
}
],
"_links": {
"self": {
"href": "http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/logs?page=77&per-page=3"
},
"first": {
"href": "http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/logs?page=1&per-page=3"
},
"prev": {
"href": "http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/logs?page=76&per-page=3"
}
},
"_meta": {
"totalCount": 230,
"pageCount": 77,
"currentPage": 77,
"perPage": 3
}
}
}








![查看日志表,控制台应用的运行未写入日志,因为通过 [[yii\log\Target::except|except]] 属性来设置所有分类作为黑名单](http://www.shuijingwanwq.com/wp-content/uploads/2018/06/9.png)
![查看日志表,接口应用的运行已写入日志,因为通过 [[yii\log\Target::categories|categories]] 属性来设置 api\behaviors\RequestLogBehavior::beforeRequest 分类作为白名单,因此仅有 api\behaviors\RequestLogBehavior::beforeRequest 分类下的日志被写入,由于总计执行了4次,写入了4条日志](http://www.shuijingwanwq.com/wp-content/uploads/2018/06/10.png)


![在 Postman 中,GET http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/logs?filter[level]=a ,200响应](http://www.shuijingwanwq.com/wp-content/uploads/2018/06/13.png)
![在 Postman 中,GET http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/logs?filter[level]=4&filter[category][like]=RequestLogBehavior&filter[prefix][like]=app-api&filter[log_time][gte]=1528090828&filter[log_time][lte]=1529564924.6648 ,测试数据过滤器,200响应](http://www.shuijingwanwq.com/wp-content/uploads/2018/06/14-1.png)

