Log – 永夜 https://www.shuijingwanwq.com 没有不值得去解决的问题,也没有不值得去学习的技术! Sun, 17 May 2026 09:37:34 +0000 zh-Hans hourly 1 https://wordpress.org/?v=7.0 在 Rancher 中升级容器,同一个镜像地址,环境变量有容器已被替换,有容器未被替换的分析解决 https://www.shuijingwanwq.com/2020/08/17/4428/ https://www.shuijingwanwq.com/2020/08/17/4428/#respond Mon, 17 Aug 2020 12:11:00 +0000 https://www.shuijingwanwq.com/?p=4428 浏览量: 128 1、进入环境变量已被替换的容器,某个配置文件,符合预期。如图1
进入环境变量已被替换的容器,某个配置文件,符合预期。

图1

2、进入环境变量未被替换的容器,某个配置文件,不符合预期。如图2
进入环境变量未被替换的容器,某个配置文件,不符合预期。

图2

3、查看环境变量已被替换的容器的日志。有相应环境变量已被替换的提示。如图3
查看环境变量已被替换的容器的日志。有相应环境变量已被替换的提示。

图3



2020/8/11 下午3:03:11 PCS_CFG_NGINX_SERVER_NAME=pcs.wjtest.chinamcloud.cn
2020/8/11 下午3:03:11 PCS_CFG_API_HOST_INFO=http://pcsapi.wjtest.chinamcloud.cn
2020/8/11 下午3:03:11 PCS_CFG_API_BASE_URL=/v1
2020/8/11 下午3:03:11 PCS_CFG_CALLBACK_LOGIN=http://cmcgroup.wjtest.chinamcloud.cn/login/index?call_back=http://pcs.wjtest.chinamcloud.cn/#/topic/add
2020/8/11 下午3:03:11 PCS_CFG_CMC_CONSOLE_URL=https://cmcconsole.wjtest.chinamcloud.cn
2020/8/11 下午3:03:11 PCS_CFG_CMC_CONSOLE_HEADER_LEFT_CSS=/cmc/cmc_header_left.css
2020/8/11 下午3:03:11 PCS_CFG_CMC_CONSOLE_HEADER_LEFT_JS=/cmc/cmc_header_left.js
2020/8/11 下午3:03:11 PCS_CFG_YS_FLAG=申请任务
2020/8/11 下午3:03:11 PCS_CFG_BQ_FLAG=0
2020/8/11 下午3:03:11 PCS_CFG_NGINX_SERVER_NAME replace pcs.wjtest.chinamcloud.cn -> /etc/nginx/conf.d/pcs.conf
2020/8/11 下午3:03:11 PCS_CFG_API_HOST_INFO replace http://pcsapi.wjtest.chinamcloud.cn -> /etc/nginx/conf.d/pcs.conf
2020/8/11 下午3:03:11 PCS_CFG_API_BASE_URL replace /v1 -> /etc/nginx/conf.d/pcs.conf
2020/8/11 下午3:03:11 sed: -e expression #1, char 120: unknown option to `s'
2020/8/11 下午3:03:11 PCS_CFG_CALLBACK_LOGIN replace http://cmcgroup.wjtest.chinamcloud.cn/login/index?call_back=http://pcs.wjtest.chinamcloud.cn/#/topic/add -> /mcloud/www/pcs/build/config.js
2020/8/11 下午3:03:11 PCS_CFG_CMC_CONSOLE_URL replace https://cmcconsole.wjtest.chinamcloud.cn -> /mcloud/www/pcs/build/config.js
2020/8/11 下午3:03:11 PCS_CFG_CMC_CONSOLE_HEADER_LEFT_CSS replace /cmc/cmc_header_left.css -> /mcloud/www/pcs/build/config.js
2020/8/11 下午3:03:11 PCS_CFG_CMC_CONSOLE_HEADER_LEFT_JS replace /cmc/cmc_header_left.js -> /mcloud/www/pcs/build/config.js
2020/8/11 下午3:03:11 PCS_CFG_YS_FLAG replace 申请任务 -> /mcloud/www/pcs/build/config.js
2020/8/11 下午3:03:11 PCS_CFG_BQ_FLAG replace 0 -> /mcloud/www/pcs/build/config.js
2020/8/11 下午3:03:11 /config/init/cronlog.sh: line 4: LOG_NAME: unbound variable


4、查看环境变量未被替换的容器的日志。无相应环境变量已被替换的提示。且报错:Invalid Date Invalid Date grabbing logs: EOF。即无效的日期 获取日志:EOF。如图4
查看环境变量未被替换的容器的日志。无相应环境变量已被替换的提示。且报错:Invalid Date Invalid Date grabbing logs: EOF。即无效的日期 获取日志:EOF。

图4



Invalid Date Invalid Date grabbing logs: EOF


5、查看环境变量未被替换的容器,升级(Upgrade) – 命令(Command) – 入口(Entry Point):/bin/bash。而查看环境变量已被替换的容器,其值为空。其作用为配置容器启动后执行的命令。如图5
查看环境变量未被替换的容器,升级(Upgrade) - 命令(Command) - 入口(Entry Point):/bin/bash。而查看环境变量已被替换的容器,其值为空。其作用为配置容器启动后执行的命令。

图5

6、编辑环境变量未被替换的容器,升级(Upgrade) – 命令(Command) – 入口(Entry Point):。设置其值为空。重新升级。容器的日志未再报错,环境变量已被替换。此时,容器启动后执行的命令脚本为:/config/bootstrap.sh。此脚本中包含环境变量替换的实现。


#!/bin/bash
localedef -i en_US -f UTF-8 en_US.UTF-8
set -e
set -u

# Supervisord default params
SUPERVISOR_PARAMS='-c /etc/supervisord.conf'


# Create directories for supervisor's UNIX socket and logs (which might be missing
# as container might start with /data mounted from another data-container).
mkdir -p /data/conf /data/run /data/logs
chmod 711 /data/conf /data/run /data/logs

if [ "$(ls /config/init/)" ]; then
  for init in /config/init/*.sh; do
    . $init
  done
fi


# We have TTY, so probably an interactive container...
if test -t 0; then
  # Run supervisord detached...
  supervisord $SUPERVISOR_PARAMS

  # Some command(s) has been passed to container? Execute them and exit.
  # No commands provided? Run bash.
  if [[ $@ ]]; then
    eval $@
  else
    export PS1='[\u@\h : \w]\$ '
    /bin/bash
  fi

# Detached mode? Run supervisord in foreground, which will stay until container is stopped.
else
  # If some extra params were passed, execute them before.
  # @TODO It is a bit confusing that the passed command runs *before* supervisord,
  #       while in interactive mode they run *after* supervisor.
  #       Not sure about that, but maybe when any command is passed to container,
  #       it should be executed *always* after supervisord? And when the command ends,
  #       container exits as well.
  if [[ $@ ]]; then
    eval $@
  fi
  supervisord -n $SUPERVISOR_PARAMS
fi


 ]]>
https://www.shuijingwanwq.com/2020/08/17/4428/feed/ 0
NAS 存储挂载失败的分析解决 https://www.shuijingwanwq.com/2020/08/05/4358/ https://www.shuijingwanwq.com/2020/08/05/4358/#respond Wed, 05 Aug 2020 01:47:27 +0000 https://www.shuijingwanwq.com/?p=4358 浏览量: 128 1、在 Rancher 的卷中,添加:/webtv:/data。如图1
在 Rancher 的卷中,添加:/webtv:/data。

图1

2、查看容器的日志,报错:LOG_NAME: unbound variable。如图2
查看容器的日志,报错:LOG_NAME: unbound variable。

图2



2020/8/3 下午4:40:16/config/init/cronlog.sh: line 5: LOG_NAME: unbound variable
2020/8/3 下午4:40:19sh: warning: setlocale: LC_ALL: cannot change locale (en_US.UTF-8)
2020/8/3 下午4:40:19bash: warning: setlocale: LC_ALL: cannot change locale (en_US.UTF-8)
2020/8/3 下午4:40:19/bin/sh: warning: setlocale: LC_ALL: cannot change locale (en_US.UTF-8)


3、进入容器,查看 /data 下的目录列表,不符合预期。未挂载成功。如图3
进入容器,查看 /data 下的目录列表,不符合预期。未挂载成功。

图3



[root@955ea658d009 data]# ls -l
total 24
drwxr-xr-x 2 root root 4096 Apr 29 15:27 cmclogs
drwx--x--x 2 root root 4096 Jun 28 11:31 conf
drwxr-xr-x 2 root root 4096 Apr 21 16:13 log-static
drwx--x--x 2 root root 4096 Jun 28 11:31 logs
drwx--x--x 2 root root 4096 Jun 28 11:31 run
drwxr-xr-x 6 root root 4096 Jun 28 12:42 wangjie


4、在 Rancher 的调度中,添加调度规则。如图4
在 Rancher 的调度中,添加调度规则。

图4

5、再次升级后,进入容器,查看 /data 下的目录列表,符合预期。NAS 存储挂载成功。如图5
再次升级后,进入容器,查看 /data 下的目录列表,符合预期。NAS 存储挂载成功。

图5



[root@08dfcc44c6e2 /]# cd /data/
[root@08dfcc44c6e2 data]# ls -l
total 3485844
drwxr-xr-x  2 root              root                6 Sep  6  2019 96
drwxr-xr-x  3        4294967294 4294967294         24 Apr 10  2019 BSAudit
drwxr-xr-x  3 root              root               26 Jul  9  2019 ForPublic
drwxr-xr-x  4 root              root               30 Dec 26  2018 SRS
-rw-r--r--  1 root              root         14107659 May 14 10:08 catalina.out.2020-05-14.log
drwxrwxrwx  3 nginx             nginx              93 Jun 15 13:50 cmclogs
drwx--x--x  2 root              root                6 Jun 28 11:30 conf
drwxr-xr-x  3 root              root               20 Apr 10  2019 console
drwxrwxrwx 12 root              root              139 Apr 10  2019 content
drwxr-xr-x  4 root              root               47 Aug 13  2019 editor
drwxr-xr-x  3 root              root              123 May 13  2019 elastic
drwxrwxrwx  3 root              root               26 Feb 28 15:53 esdata
drwxrwxrwx  3 root              root               26 Feb 28 16:02 esdatadev
drwxrwxrwx  3 root              root               26 Jul 22 16:02 esdatadev2
drwxr-xr-x  2 root              root               19 Jul  8  2019 ffmpeg
-rw-------  1 root              root       3167531079 May  7  2019 gc.hprof
-rw-r--r--  1 root              root        387737048 May  7  2019 gc.tar.gz
-rw-r--r--  1 root              root              664 Apr 10  2019 header.txt
-rw-r--r--  1 root              root             1195 Jun 19  2019 hua-qiyun-6803fac4-b7c0-4fea-a44b-332e171e95d5-v5.json
drwxr-xr-x  2 root              root             4096 Aug  3 00:00 log-cms
drwxr-xr-x  2 nginx             nginx            4096 Aug  3 14:23 log-static
drwx--x--x  6 root              root            16384 Aug  3 16:57 logs
drwxrwxrwx  2 root              root                6 Jun  8 13:34 media
drwxr-xr-x  4 root              root               48 Jul 29  2019 models
drwxrwxrwx  3 root              root               17 Dec 11  2018 ms3
drwxr-xr-x 47 systemd-bus-proxy input            4096 Mar 25 23:40 mysql
drwxrwxrwx  4 root              root               35 Dec 11  2018 nas-tmp
-rw-r--r--  1 root              root            40956 Oct 29  2019 output.mp3
drwxr-xr-x  6 root              root               67 Mar  6 10:50 pgc
drwxr-xr-x  3 root              root               25 Dec 13  2019 pic
-rw-r--r--  1 root              root                9 Apr 10  2019 requestBodyParams.txt
-rw-r--r--  1 root              root              573 Apr 10  2019 requestRowBody.txt
drwx--x--x  2 root              root                6 Jun 28 11:30 run
drwxr-xr-x  8 root              root             4096 Mar 16 16:42 shift
drwxr-xr-x 14 root              root             4096 Mar 17 04:00 shiftdev
drwxr-xr-x  8 root              root              106 May 15 21:09 site
-rw-r--r--  1 root              root             3178 Jun 14  2019 spiderLogo.png
drwxrwxrwx  6 systemd-bus-proxy input             155 Jun 19 15:34 sqldev
drwxrwxrwx  4 root              root               41 Jun  8 13:37 trans
drwxr-xr-x  8 nginx             nginx            4096 Jun 28 13:28 version_sql
drwxr-xr-x  3 root              root               25 Dec 13  2019 video
drwxr-xr-x  2 root              root                6 Nov 27  2019 vidoe
drwxrwxrwx 36 root              root             4096 Jul 31 11:33 wangjie
drwxrwxrwx 18 root              root             4096 Jul 31 18:32 wangjiebase
drwxrwxrwx 17 root              root             4096 Jul  3 17:14 wangjiedev
drwxrwxr-x 10 root              root              102 Apr 11  2019 wangjieweb
drwxrwxrwx  4 root              root               33 Mar 29  2019 wangjiewjweb
drwxr-xr-x  3 root              root               20 May 20  2019 webtv
drwxr-xr-x  3 root              root               20 Jan 31  2019 wjdev
drwxr-xr-x  4 root              root               34 Mar 18  2019 wpdt
drwxrwxrwx  6 root              root               54 Apr  9  2019 xContent
drwxrwxrwx  5 root              root               58 Jul  3 16:49 xcontent
drwxrwxrwx  3 root              root               17 Apr 10  2019 xtrimmer
drwxrwxrwx  7 root              root              140 Jul 21 15:19 zqtest


6、分析添加调度规则前后的区别。添加前,其主机为:lb。添加后,其主机为:wjdev02。如图6、图7
添加前,其主机为:lb。

图6

添加后,其主机为:wjdev02。

图7

7、分别查看主机:lb、wjdev02 的存储,lb 上不存在 /webtv,wjdev02 上存在 /webtv。如图8、图9
分别查看主机:lb、wjdev02 的存储,lb 上不存在 /webtv

图8

分别查看主机:lb、wjdev02 的存储,wjdev02 上存在 /webtv。

图9

]]>
https://www.shuijingwanwq.com/2020/08/05/4358/feed/ 0
基于 yiisoft/yii2-app-advanced,在 GitHub 上新建仓库 yii2-app-advanced,日志消息的自动定时删除 https://www.shuijingwanwq.com/2019/10/09/3550/ https://www.shuijingwanwq.com/2019/10/09/3550/#respond Wed, 09 Oct 2019 05:59:56 +0000 http://www.shuijingwanwq.com/?p=3550 浏览量: 105

1、由于日志消息是存储于 MySQL 的 log 表中,在生产环境中,累积了大量的数据,log 表的数据长度已经达到 数百 GB ,因此,决定基于控制台命令行实现日志消息的定时删除,开发环境的 log 表的数据长度为 14.64 GB,如图1

由于日志消息是存储于 MySQL 的 log 表中,在生产环境中,累积了大量的数据,log 表的数据长度已经达到 数百 GB ,因此,决定基于控制台命令行实现日志消息的定时删除,开发环境的 log 表的数据长度为 14.64 GB

图1

2、编辑 \console\controllers\LogController.php,获取服务器当前时间,判断范围([0点,1点)),如果不在范围内,则延缓执行 10 * 60 秒,再退出

<?php
/**
 * Created by PhpStorm.
 * User: Qiang Wang
 * Date: 2019/10/08
 * Time: 13:36
 */
 
namespace console\controllers;
 
use Yii;
use console\models\Log;
use yii\console\Controller;
use yii\console\ExitCode;
use yii\helpers\ArrayHelper;
use yii\web\HttpException;
use yii\web\ServerErrorHttpException;
 
/**
 * 日志
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since  1.0
 */
class LogController extends Controller
{
    const SLEEP_TIME = 10 * 60; // 延缓执行 10 * 60 秒
    /**
     * 日志的删除
     *
     */
    public function actionDelete()
    {
        /* 获取服务器当前时间,判断范围([0点,1点)),如果不在范围内,则延缓执行 10 * 60 秒,再退出 */
        $time = time();
        $zeroTime = strtotime("today"); // 0点
        $oneTime = strtotime("today +1 hours"); // 1点
        echo $time . " ";
        echo date('Y-m-d H:i:s', $time) . " ";
        echo $zeroTime . " ";
        echo date('Y-m-d H:i:s', $zeroTime) . " ";
        echo $oneTime . " ";
        echo date('Y-m-d H:i:s', $oneTime) . " ";
        exit;
        if (!($time >= $zeroTime && $time < $oneTime)) {
            // 延缓执行 10 * 60 秒
            sleep(static::SLEEP_TIME);
 
            return ExitCode::OK;
        }
 
        echo 1;
        exit;
 
 
    }
}

PS E:\wwwroot\github-shuijingwan-yii2-app-advanced> ./yii log/delete
1570518464 2019-10-08 15:29:17 1570464000 2019-10-08 00:00:00 1570467600 2019-10-08 01:00:00


3、查询 1 条记录的日志时间($logTime),基于 日志时间 升序排列,如果不存在,则延缓执行 10 * 60 秒,再退出


        /* 查询 1 条记录的日志时间($logTime),基于 日志时间 升序排列,如果不存在,则延缓执行 10 * 60 秒,再退出 */
        $logItem = Log::find()->select(['log_time'])->orderBy(['log_time' => SORT_ASC])->limit(1)->one();
        if (!isset($logItem)) {
            // 延缓执行 10 * 60 秒
            sleep(static::SLEEP_TIME);

            return ExitCode::OK;
        }
        $logTime = $logItem->log_time;


4、获取服务器当前时间的前 15 天(默认值,可自定义)的值($reservedTime:保留时间,0点),如果 $logTime 大于等于 $reservedTime,则延缓执行 10 * 60 秒,再退出


        /* 获取服务器当前时间的前 15 天(默认值,可自定义)的值($reservedTime:保留时间,0点),如果 $logTime 大于等于 $reservedTime,则延缓执行 10 * 60 秒,再退出 */
        $reservedTime = strtotime("today -15 days"); // 保留时间,0点
        echo $logTime . " ";
        echo date('Y-m-d H:i:s', $logTime) . " ";
        echo $reservedTime . " ";
        echo date('Y-m-d H:i:s', $reservedTime) . " ";
        if ($logTime >= $reservedTime) {
            echo 0;
            exit;
            // 延缓执行 10 * 60 秒
            sleep(static::SLEEP_TIME);

            return ExitCode::OK;
        }
        echo 1;
        exit;



PS E:\wwwroot\github-shuijingwan-yii2-app-advanced> ./yii log/delete
1567046168.2703 2019-08-29 10:36:08 1569168000 2019-09-23 00:00:00 1


5、获取 1 条记录的日志时间($logTime)的后 1 天的值($deadlineTime:截止时间,0点),如果 $deadlineTime 大于 $reservedTime,则将 $reservedTime 赋值给 $deadlineTime(后 1 天的值时,此条件永远不满足,大于 1 天时,此条件可能满足)


        /* 获取 1 条记录的日志时间($logTime)的后 1 天的值($deadlineTime:截止时间,0点),如果 $deadlineTime 大于 $reservedTime,则将 $reservedTime 赋值给 $deadlineTime(后 1 天的值时,此条件永远不满足,大于 1 天时,此条件可能满足) */
        $logDate = date('Y-m-d', $logTime);
        $deadlineTime = strtotime("$logDate +1 days"); // 截止时间,0点
        echo $logDate . " ";
        echo date('Y-m-d H:i:s', $deadlineTime) . " ";
        if ($deadlineTime > $reservedTime) {
            $deadlineTime = $reservedTime;
            echo 1;
            exit;
        }
        echo date('Y-m-d H:i:s', $deadlineTime) . " ";
        echo 2;
        exit;



PS E:\wwwroot\github-shuijingwan-yii2-app-advanced> ./yii log/delete
2019-08-29 2019-08-30 00:00:00 2019-08-30 00:00:00 2


6、执行删除 SQL(),条件(日志时间 小于 $deadlineTime),即命令行 1 次运行仅删除 1 天的日志消息,延缓执行 60 秒,再退出。
注:执行删除 SQL(),DELETE FROM `cpa_log` WHERE `log_time` < 1570590733,删除 26 万余条记录(1 天的日志消息),发现执行时间长达 1 分余钟,如图2

执行删除 SQL(),DELETE FROM `cpa_log` WHERE `log_time` < 1570590733,删除 26 万余条记录(1 天的日志消息),发现执行时间长达 1 分余钟

图2

<?php
/**
 * Created by PhpStorm.
 * User: Qiang Wang
 * Date: 2019/10/08
 * Time: 13:36
 */
 
namespace console\controllers;
 
use Yii;
use console\models\Log;
use yii\console\Controller;
use yii\console\ExitCode;
use yii\helpers\ArrayHelper;
use yii\web\HttpException;
use yii\web\ServerErrorHttpException;
 
/**
 * 日志
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since  1.0
 */
class LogController extends Controller
{
    const SLEEP_TIME = 10 * 60; // 延缓执行 10 * 60 秒
    /**
     * 日志的删除
     *
     */
    public function actionDelete()
    {
        /* 获取服务器当前时间,判断范围([0点,1点]),如果不在范围内,则延缓执行 10 * 60 秒,再退出 */
        $time = time(); // 当前时间
        $zeroTime = strtotime("today"); // 0点
        $oneTime = strtotime("today +1 hours"); // 1点
        if (($time >= $zeroTime && $time <= $oneTime)) {
            // 延缓执行 10 * 60 秒
            echo 0;
            exit;
            sleep(static::SLEEP_TIME);
 
            return ExitCode::OK;
        }
 
        /* 查询 1 条记录的日志时间($logTime),基于 日志时间 升序排列,如果不存在,则延缓执行 10 * 60 秒,再退出 */
        $logItem = Log::find()->select(['log_time'])->orderBy(['log_time' => SORT_ASC])->limit(1)->one();
        if (!isset($logItem)) {
            // 延缓执行 10 * 60 秒
            echo 1;
            exit;
            sleep(static::SLEEP_TIME);
 
            return ExitCode::OK;
        }
        $logTime = $logItem->log_time;
 
        /* 获取服务器当前时间的前 15 天(默认值,可自定义)的值($reservedTime:保留时间,0点),如果 $logTime 大于等于 $reservedTime,则延缓执行 10 * 60 秒,再退出 */
        $reservedTime = strtotime("today -15 days"); // 保留时间,0点
        echo $logTime . " ";
        echo date('Y-m-d H:i:s', $logTime) . " ";
        echo $reservedTime . " ";
        echo date('Y-m-d H:i:s', $reservedTime) . " ";
        if ($logTime >= $reservedTime) {
            echo 2;
            exit;
            // 延缓执行 10 * 60 秒
            sleep(static::SLEEP_TIME);
 
            return ExitCode::OK;
        }
 
        /* 获取 1 条记录的日志时间($logTime)的后 1 天的值($deadlineTime:截止时间,0点),如果 $deadlineTime 大于 $reservedTime,则将 $reservedTime 赋值给 $deadlineTime(后 1 天的值时,此条件永远不满足,大于 1 天时,此条件可能满足) */
        $logDate = date('Y-m-d', $logTime);
        $deadlineTime = strtotime("$logDate +1 days"); // 截止时间,0点
        echo $logDate . " ";
        echo date('Y-m-d H:i:s', $deadlineTime) . " ";
        if ($deadlineTime > $reservedTime) {
            $deadlineTime = $reservedTime;
        }
        echo date('Y-m-d H:i:s', $deadlineTime) . " ";
 
        /* 执行删除 SQL(),条件(日志时间 小于 $deadlineTime),即命令行 1 次运行仅删除 1 天的日志消息,延缓执行 60 秒,再退出 */
        Log::deleteAll(['<', 'log_time', $deadlineTime]);
        // 延缓执行 60 秒
        //sleep(60);
        //return ExitCode::OK;
        echo 3;
        exit;
    }
}
 

PS E:\wwwroot\github-shuijingwan-yii2-app-advanced> ./yii log/delete
1567046168.2703 2019-08-29 10:36:08 1569168000 2019-09-23 00:00:00 2019-08-29 2019-08-30 00:00:00 2019-08-30 00:00:00 3
PS E:\wwwroot\github-shuijingwan-yii2-app-advanced> ./yii log/delete
1567094579.432 2019-08-30 00:02:59 1569168000 2019-09-23 00:00:00 2019-08-30 2019-08-31 00:00:00 2019-08-31 00:00:00 3
PS E:\wwwroot\github-shuijingwan-yii2-app-advanced> ./yii log/delete
1567180800.2962 2019-08-31 00:00:00 1569168000 2019-09-23 00:00:00 2019-08-31 2019-09-01 00:00:00 2019-09-01 00:00:00 3
PS E:\wwwroot\github-shuijingwan-yii2-app-advanced> ./yii log/delete
1567267282.2226 2019-09-01 00:01:22 1569168000 2019-09-23 00:00:00 2019-09-01 2019-09-02 00:00:00 2019-09-02 00:00:00 3
PS E:\wwwroot\github-shuijingwan-yii2-app-advanced> ./yii log/delete
1568958887.1824 2019-09-20 13:54:47 1569168000 2019-09-23 00:00:00 2019-09-20 2019-09-21 00:00:00 2019-09-21 00:00:00
PS E:\wwwroot\github-shuijingwan-yii2-app-advanced> ./yii log/delete
1569832928.1543 2019-09-30 16:42:08 1569168000 2019-09-23 00:00:00 2
PS E:\wwwroot\github-shuijingwan-yii2-app-advanced> ./yii log/delete
1569832928.1543 2019-09-30 16:42:08 1569168000 2019-09-23 00:00:00 2


7、设置参数,日志的定时删除的保留时间(前 15 天),单位为天,编辑 \console\config\params-local.php

<?php
return [
    'logDeleteReservedTime' => 15, // 日志的定时删除的保留时间(前 15 天),单位为天
];

8、最终的代码实现,开发环境的 log 表的数据长度为 2.36 GB,符合预期,如图3

最终的代码实现,开发环境的 log 表的数据长度为 2.36 GB,符合预期

图3

<?php
/**
 * Created by PhpStorm.
 * User: Qiang Wang
 * Date: 2019/10/08
 * Time: 13:36
 */
 
namespace console\controllers;
 
use Yii;
use console\models\Log;
use yii\console\Controller;
use yii\console\ExitCode;
 
/**
 * 日志
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since  1.0
 */
class LogController extends Controller
{
    const SLEEP_TIME = 10 * 60; // 延缓执行 10 * 60 秒
    /**
     * 日志的删除
     */
    public function actionDelete()
    {
        /* 获取服务器当前时间,判断范围([0点,1点]),如果不在范围内,则延缓执行 10 * 60 秒,再退出 */
        $time = time(); // 当前时间
        $zeroTime = strtotime("today"); // 0点
        $oneTime = strtotime("today +1 hours"); // 1点
        if (!($time >= $zeroTime && $time < $oneTime)) {
            // 延缓执行 10 * 60 秒
            sleep(static::SLEEP_TIME);
 
            return ExitCode::OK;
        }
 
        /* 查询 1 条记录的日志时间($logTime),基于 日志时间 升序排列,如果不存在,则延缓执行 10 * 60 秒,再退出 */
        $logItem = Log::find()->select(['log_time'])->orderBy(['log_time' => SORT_ASC])->limit(1)->one();
        if (!isset($logItem)) {
            // 延缓执行 10 * 60 秒
            sleep(static::SLEEP_TIME);
 
            return ExitCode::OK;
        }
        $logTime = $logItem->log_time;
 
        /* 获取服务器当前时间的前 15 天(默认值,可自定义)的值($reservedTime:保留时间,0点),如果 $logTime 大于等于 $reservedTime,则延缓执行 10 * 60 秒,再退出 */
        $logDeleteReservedTime = Yii::$app->params['logDeleteReservedTime'];
        $reservedTime = strtotime("today -$logDeleteReservedTime days"); // 保留时间,0点
        if ($logTime >= $reservedTime) {
            // 延缓执行 10 * 60 秒
            sleep(static::SLEEP_TIME);
 
            return ExitCode::OK;
        }
 
        /* 获取 1 条记录的日志时间($logTime)的后 1 天的值($deadlineTime:截止时间,0点),如果 $deadlineTime 大于 $reservedTime,则将 $reservedTime 赋值给 $deadlineTime(后 1 天的值时,此条件永远不满足,大于 1 天时,此条件可能满足) */
        $logDate = date('Y-m-d', $logTime);
        $deadlineTime = strtotime("$logDate +1 days"); // 截止时间,0点
        if ($deadlineTime > $reservedTime) {
            $deadlineTime = $reservedTime;
        }
 
        /* 执行删除 SQL(),条件(日志时间 小于 $deadlineTime),即命令行 1 次运行仅删除 1 天的日志消息,延缓执行 60 秒,再退出 */
        Log::deleteAll(['<', 'log_time', $deadlineTime]);
        // 延缓执行 60 秒
        sleep(60);
        return ExitCode::OK;
    }
}
 

 

]]>
https://www.shuijingwanwq.com/2019/10/09/3550/feed/ 0
在 Yii 2.0 报错:PHP Warning: strpos(): Empty needle in E:\wwwroot\pcs-api\vendor\yiisoft\yii2\log\Target.php on line 243 的分析解决 https://www.shuijingwanwq.com/2019/04/02/3226/ https://www.shuijingwanwq.com/2019/04/02/3226/#respond Tue, 02 Apr 2019 09:46:05 +0000 http://www.shuijingwanwq.com/?p=3226 浏览量: 302 1、执行命令,报错:PHP Warning: strpos(): Empty needle in E:\wwwroot\pcs-api\vendor\yiisoft\yii2\log\Target.php on line 243,如图1
执行命令,报错:PHP Warning:  strpos(): Empty needle in E:\wwwroot\pcs-api\vendor\yiisoft\yii2\log\Target.php on line 243

图1



PS E:\wwwroot\pcs-api> ./yii cmc-console-user/sync
Exception 'yii\web\ServerErrorHttpException' with message 'Framework Service Console HTTP request failed: 租户信息不存在
'

in E:\wwwroot\pcs-api\common\services\CmcConsoleUserService.php:102

Stack trace:
#0 E:\wwwroot\pcs-api\console\controllers\CmcConsoleUserController.php(62): common\services\CmcConsoleUserService::httpG
etUserList(Array)
#1 [internal function]: console\controllers\CmcConsoleUserController->actionSync()
#2 E:\wwwroot\pcs-api\vendor\yiisoft\yii2\base\InlineAction.php(57): call_user_func_array(Array, Array)
#3 E:\wwwroot\pcs-api\vendor\yiisoft\yii2\base\Controller.php(157): yii\base\InlineAction->runWithParams(Array)
#4 E:\wwwroot\pcs-api\vendor\yiisoft\yii2\console\Controller.php(148): yii\base\Controller->runAction('sync', Array)
#5 E:\wwwroot\pcs-api\vendor\yiisoft\yii2\base\Module.php(528): yii\console\Controller->runAction('sync', Array)
#6 E:\wwwroot\pcs-api\vendor\yiisoft\yii2\console\Application.php(180): yii\base\Module->runAction('cmc-console-use...',
 Array)
#7 E:\wwwroot\pcs-api\vendor\yiisoft\yii2\console\Application.php(147): yii\console\Application->runAction('cmc-console-
use...', Array)
#8 E:\wwwroot\pcs-api\vendor\yiisoft\yii2\base\Application.php(386): yii\console\Application->handleRequest(Object(yii\c
onsole\Request))
#9 E:\wwwroot\pcs-api\yii(23): yii\base\Application->run()
#10 {main}
PHP Warning:  strpos(): Empty needle in E:\wwwroot\pcs-api\vendor\yiisoft\yii2\log\Target.php on line 243
PHP Warning:  strpos(): Empty needle in E:\wwwroot\pcs-api\vendor\yiisoft\yii2\log\Target.php on line 243
PHP Warning:  strpos(): Empty needle in E:\wwwroot\pcs-api\vendor\yiisoft\yii2\log\Target.php on line 243
PHP Warning:  strpos(): Empty needle in E:\wwwroot\pcs-api\vendor\yiisoft\yii2\log\Target.php on line 243


2、查看 \console\runtime\logs\app.log


2019-04-02 14:03:05 [-][-][-][warning][yii\log\Dispatcher::dispatch] Unable to send log via yii\log\DbTarget: PHP Warning 'yii\base\ErrorException' with message 'strpos(): Empty needle' 

in E:\wwwroot\pcs-api\vendor\yiisoft\yii2\log\Target.php:243

Stack trace:
#0 [internal function]: yii\base\ErrorHandler->handleError(2, 'strpos(): Empty...', 'E:\\wwwroot\\pcs-...', 243, Array)
#1 E:\wwwroot\pcs-api\vendor\yiisoft\yii2\log\Target.php(243): strpos('yii\\base\\Applic...', '')
#2 E:\wwwroot\pcs-api\vendor\yiisoft\yii2\log\Target.php(124): yii\log\Target::filterMessages(Array, 0, Array, Array)
#3 E:\wwwroot\pcs-api\vendor\yiisoft\yii2\log\Dispatcher.php(189): yii\log\Target->collect(Array, false)
#4 E:\wwwroot\pcs-api\vendor\yiisoft\yii2\log\Logger.php(177): yii\log\Dispatcher->dispatch(Array, false)
#5 E:\wwwroot\pcs-api\vendor\yiisoft\yii2\log\Logger.php(124): yii\log\Logger->flush()
#6 [internal function]: yii\log\Logger->yii\log\{closure}()
#7 {main}
2019-04-02 14:03:05 [-][-][-][info][application] $_GET = []

$_POST = []

$_FILES = []

$_COOKIE = []

$_SERVER = []


3、在命令行中编辑


	var_dump(strpos("Fabio", 'b'));
	exit;




PS E:\wwwroot\pcs-api> ./yii cmc-console-user/sync
int(2)


4、在命令行中编辑,在控制台命令行中一旦抛出异常,写入日志时,就会报错:PHP Warning: strpos(): Empty needle,如图2
在命令行中编辑,在控制台命令行中一旦抛出异常,写入日志时,就会报错:PHP Warning:  strpos(): Empty needle

图2



	var_dump(strpos("Fabio", ''));
	exit;




PS E:\wwwroot\pcs-api> ./yii cmc-console-user/sync
PHP Warning 'yii\base\ErrorException' with message 'strpos(): Empty needle'

in E:\wwwroot\pcs-api\console\controllers\CmcConsoleUserController.php:37

Stack trace:
#0 [internal function]: yii\base\ErrorHandler->handleError(2, 'strpos(): Empty...', 'E:\\wwwroot\\pcs-...', 37, Array)
#1 E:\wwwroot\pcs-api\console\controllers\CmcConsoleUserController.php(37): strpos('Fabio', '')
#2 [internal function]: console\controllers\CmcConsoleUserController->actionSync()
#3 E:\wwwroot\pcs-api\vendor\yiisoft\yii2\base\InlineAction.php(57): call_user_func_array(Array, Array)
#4 E:\wwwroot\pcs-api\vendor\yiisoft\yii2\base\Controller.php(157): yii\base\InlineAction->runWithParams(Array)
#5 E:\wwwroot\pcs-api\vendor\yiisoft\yii2\console\Controller.php(148): yii\base\Controller->runAction('sync', Array)
#6 E:\wwwroot\pcs-api\vendor\yiisoft\yii2\base\Module.php(528): yii\console\Controller->runAction('sync', Array)
#7 E:\wwwroot\pcs-api\vendor\yiisoft\yii2\console\Application.php(180): yii\base\Module->runAction('cmc-console-use...',
 Array)
#8 E:\wwwroot\pcs-api\vendor\yiisoft\yii2\console\Application.php(147): yii\console\Application->runAction('cmc-console-
use...', Array)
#9 E:\wwwroot\pcs-api\vendor\yiisoft\yii2\base\Application.php(386): yii\console\Application->handleRequest(Object(yii\c
onsole\Request))
#10 E:\wwwroot\pcs-api\yii(23): yii\base\Application->run()
#11 {main}
PHP Warning:  strpos(): Empty needle in E:\wwwroot\pcs-api\vendor\yiisoft\yii2\log\Target.php on line 243
PHP Warning:  strpos(): Empty needle in E:\wwwroot\pcs-api\vendor\yiisoft\yii2\log\Target.php on line 243
PHP Warning:  strpos(): Empty needle in E:\wwwroot\pcs-api\vendor\yiisoft\yii2\log\Target.php on line 243
PHP Warning:  strpos(): Empty needle in E:\wwwroot\pcs-api\vendor\yiisoft\yii2\log\Target.php on line 243


5、编辑 \console\config\main.php,调整日志组件的配置


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


调整为:


    'components' => [
        'log' => [
            'traceLevel' => YII_DEBUG ? 3 : 0,
            'targets' => [
                'file' => [
                    'class' => 'yii\log\FileTarget',
                    'levels' => ['error', 'warning'],
                ],
                'db' => [
                    'class' => 'yii\log\DbTarget',
                    'levels' => ['error', 'warning'],
                ],
            ],
        ],
    ],


6、在控制台命令行中一旦抛出异常,写入日志时,报错:PHP Warning: strpos(): Empty needle 的问题已经得到解决  


PS E:\wwwroot\pcs-api> ./yii cmc-console-user/sync
PHP Warning 'yii\base\ErrorException' with message 'strpos(): Empty needle'

in E:\wwwroot\pcs-api\console\controllers\CmcConsoleUserController.php:37

Stack trace:
#0 [internal function]: yii\base\ErrorHandler->handleError(2, 'strpos(): Empty...', 'E:\\wwwroot\\pcs-...', 37, Array)
#1 E:\wwwroot\pcs-api\console\controllers\CmcConsoleUserController.php(37): strpos('Fabio', '')
#2 [internal function]: console\controllers\CmcConsoleUserController->actionSync()
#3 E:\wwwroot\pcs-api\vendor\yiisoft\yii2\base\InlineAction.php(57): call_user_func_array(Array, Array)
#4 E:\wwwroot\pcs-api\vendor\yiisoft\yii2\base\Controller.php(157): yii\base\InlineAction->runWithParams(Array)
#5 E:\wwwroot\pcs-api\vendor\yiisoft\yii2\console\Controller.php(148): yii\base\Controller->runAction('sync', Array)
#6 E:\wwwroot\pcs-api\vendor\yiisoft\yii2\base\Module.php(528): yii\console\Controller->runAction('sync', Array)
#7 E:\wwwroot\pcs-api\vendor\yiisoft\yii2\console\Application.php(180): yii\base\Module->runAction('cmc-console-use...',
 Array)
#8 E:\wwwroot\pcs-api\vendor\yiisoft\yii2\console\Application.php(147): yii\console\Application->runAction('cmc-console-
use...', Array)
#9 E:\wwwroot\pcs-api\vendor\yiisoft\yii2\base\Application.php(386): yii\console\Application->handleRequest(Object(yii\c
onsole\Request))
#10 E:\wwwroot\pcs-api\yii(23): yii\base\Application->run()
#11 {main}


]]>
https://www.shuijingwanwq.com/2019/04/02/3226/feed/ 0
在 Yii2 高级模板中基于 Yii2 队列扩展实现异步执行任务,附加事件处理器,在(每次成功执行作业后、在作业执行期间发生未捕获的异常时) https://www.shuijingwanwq.com/2018/10/27/2963/ https://www.shuijingwanwq.com/2018/10/27/2963/#respond Sat, 27 Oct 2018 10:17:30 +0000 http://www.shuijingwanwq.com/?p=2963 浏览量: 194

1、通过配置附加事件处理器,编辑 \environments\dev\common\config\main-local.php、\environments\prod\common\config\main-local.php,如图1

通过配置附加事件处理器
图1


        'copyAssetQueue' => [ // 复制资源文件队列
            'class' => 'yii\queue\redis\Queue',
            'redis' => 'redis', // Redis 连接组件或它的配置
            'channel' => 'cpa:queue:copy:asset', // 队列键前缀
            'ttr' => 10 * 60, // 作业处理的最长时间,单位(秒)
            'on afterExec' => ['common\components\queue\CopyAssetEventHandler', 'afterExec'], // 每次成功执行作业后
            'on afterError' => ['common\components\queue\CopyAssetEventHandler', 'afterError'], // 在作业执行期间发生未捕获的异常时
            'as log' => 'yii\queue\LogBehavior',
        ],
        'uploadAssetQueue' => [ // 上传资源文件队列
            'class' => 'yii\queue\redis\Queue',
            'redis' => 'redis', // Redis 连接组件或它的配置
            'channel' => 'cpa:queue:upload:asset', // 队列键前缀
            'ttr' => 5 * 60, // 作业处理的最长时间,单位(秒)
            'on afterExec' => ['common\components\queue\UploadAssetEventHandler', 'afterExec'], // 每次成功执行作业后
            'on afterError' => ['common\components\queue\UploadAssetEventHandler', 'afterError'], // 在作业执行期间发生未捕获的异常时
            'as log' => 'yii\queue\LogBehavior',
        ],
        'pubArticleQueue' => [ // 发布文章队列
            'class' => 'yii\queue\redis\Queue',
            'redis' => 'redis', // Redis 连接组件或它的配置
            'channel' => 'cpa:queue:pub:article', // 队列键前缀
            'ttr' => 5 * 60, // 作业处理的最长时间,单位(秒)
            'on afterExec' => ['common\components\queue\PubArticleEventHandler', 'afterExec'], // 每次成功执行作业后
            'on afterError' => ['common\components\queue\PubArticleEventHandler', 'afterError'], // 在作业执行期间发生未捕获的异常时
            'as log' => 'yii\queue\LogBehavior',
        ],
        'sourceCallbackQueue' => [ // 来源回调队列
            'class' => 'yii\queue\redis\Queue',
            'redis' => 'redis', // Redis 连接组件或它的配置
            'channel' => 'cpa:queue:source:callback', // 队列键前缀
            'ttr' => 5 * 60, // 作业处理的最长时间,单位(秒)
            'on afterExec' => ['common\components\queue\SourceCallbackEventHandler', 'afterExec'], // 每次成功执行作业后
            'on afterError' => ['common\components\queue\SourceCallbackEventHandler', 'afterError'], // 在作业执行期间发生未捕获的异常时
            'as log' => 'yii\queue\LogBehavior',
        ],


2、编辑 \qq\rests\qq_cw_app\IndexAction.php,一个推送队列的入口



        $data = [
            'channel_id' => 2, // 渠道ID
            'channel_code' => 'wx', // 渠道代码,qq:企鹅号;wx:微信公众帐号
            'channel_type_id' => 3, // 渠道的类型ID
            'channel_type_code' => 'wx', // 渠道的类型代码,qq_cw:企鹅号的内容网站应用;qq_tp:企鹅号的第三方服务平台应用;wx:微信公众帐号应用
            'source' => 'spider', // 来源,xContent:内容库;vms:视频管理系统;cms:内容管理系统;spider:自媒体
            'task_id' => 2, // 任务ID
        ];
        $assets = [
            [
                'type' => 'image',
                'channel_article_id' => 1,
                'absolute_url' => 'http://localhost/channel-pub-api/storage/spider/images/1.png',
            ],
            [
                'type' => 'video',
                'channel_article_id' => 1,
                'absolute_url' => 'http://localhost/channel-pub-api/storage/spider/videos/7月份北上广深等十大城市租金环比上涨 看东方 20180820 高清_高清.mp4',
            ],
        ];
        $assetServiceCopyAssetsAsyncResult = AssetService::copyAssetsAsync($data, $assets);
        print_r($assetServiceCopyAssetsAsyncResult);
        exit;


3、编辑 \common\services\AssetService.php,复制来源的资源文件至渠道发布的资源目录,队列任务执行成功后,调用相应服务,否则,插入发布日志(异步)



    /**
     * 复制来源的资源文件至渠道发布的资源目录,队列任务执行成功后,调用相应服务,否则,插入发布日志(异步)
     * @param array $data 数据
     * 格式如下:
     * [
     *     'channel_id' => 1, // 渠道ID
     *     'channel_code' => 'qq', // 渠道代码,qq:企鹅号;wx:微信公众帐号
     *     'channel_type_id' => 1, // 渠道的类型ID
     *     'channel_type_code' => 'qq_cw', // 渠道的类型代码,qq_cw:企鹅号的内容网站应用;qq_tp:企鹅号的第三方服务平台应用;wx:微信公众帐号应用
     *     'source' => 'spider', // 来源,xContent:内容库;vms:视频管理系统;cms:内容管理系统;spider:自媒体
     *     'task_id' => 1, // 任务ID
     * ]
     *
     * @param array $assets 来源的资源文件的绝对URL
     * 格式如下:
     * [
     *     [
     *         'type' => 'image', // 资源文件的类型,image:图片;video:视频
     *         'channel_article_id' => 1, // 渠道的文章ID
     *         'absolute_url' => 'http://localhost/spider/storage/spider/images/1.png', // 来源的资源文件的绝对URL
     *     ],
     *     [
     *         'type' => 'video', // 资源文件的类型,image:图片;video:视频
     *         'channel_article_id' => 1, // 渠道的文章ID
     *         'absolute_url' => 'http://localhost/channel-pub-api/storage/spider/videos/7月份北上广深等十大城市租金环比上涨 看东方 20180820 高清_高清.mp4', // 来源的资源文件的绝对URL
     *     ],
     * ]
     *
     * @return array $channelPubApiAssetAbsolutePaths 渠道发布的资源文件的相对路径
     * 格式如下:
     * [
     *     [
     *         'type' => 'image',
     *         'channel_article_id' => 1,
     *         'relative_path' => '/2018/09/20/1537439889.2333.1441541478.png',
     *     ],
     *     [
     *         'type' => 'video',
     *         'channel_article_id' => 1,
     *         'relative_path' => '/2018/09/20/1537439889.2403.62871268.mp4',
     *     ],
     * ]
     *
     * @throws Exception execution failed
     */
    public static function copyAssetsAsync($data, $assets)
    {
        $assetData = [];
        $time = time();
        foreach ($assets as $key => $asset) {
            $assetData[] = [
                'channel_id' => $data['channel_id'],
                'channel_code' => $data['channel_code'],
                'channel_type_id' => $data['channel_type_id'],
                'channel_type_code' => $data['channel_type_code'],
                'source' => $data['source'],
                'type' => $asset['type'],
                'absolute_url' => $asset['absolute_url'],
                'relative_path' => '',
                'size' => 0,
                'task_id' => $data['task_id'],
                'channel_article_id' => $asset['channel_article_id'],
                'status' => Asset::STATUS_ENABLED,
                'is_deleted' => Asset::IS_DELETED_NO,
                'created_at' => $time,
                'updated_at' => $time,
                'deleted_at' => Asset::DELETED_AT_DEFAULT,
            ];
        }

        // 批量创建资源
        $asset = new Asset();
        $assetCreateMultipleResult = $asset->createMultiple($assetData);

        // 将任务发送到队列(复制资源文件队列),通过标准工作人员进行处理
        Yii::$app->copyAssetQueue->push(new CopyAssetJob([
            'taskId' => $data['task_id'],
        ]));
    }


4、编辑 \common\jobs\CopyAssetJob.php,队列的任务类

<pre class="wp-block-syntaxhighlighter-code">

<?php
/**
 * Created by PhpStorm.
 * User: Qiang Wang
 * Date: 2018/10/22
 * Time: 17:10
 */

namespace common\jobs;

use Yii;
use common\logics\Asset;
use common\services\TaskService;
use common\services\AssetService;
use yii\web\ServerErrorHttpException;

/**
 * 复制来源的资源文件至渠道发布的资源目录
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since 1.0
 */
class CopyAssetJob extends Job
{
    public $taskId;

    /*
     * @throws ServerErrorHttpException 如果基于任务ID查找状态为启用的资源列表为空,将抛出 500 HTTP 异常
     */
    public function execute($queue)
    {
        // 基于ID查找状态为启用的单个数据模型(任务)
        $taskEnabledItem = TaskService::findModelEnabledById($this->taskId);

        // 基于任务ID查找状态为启用的资源列表
        $assetEnabledItems = Asset::findAllEnabledByTaskId($this->taskId);

        if (empty($assetEnabledItems)) {
            throw new ServerErrorHttpException(Yii::t('common/error', Yii::t('common/error', Yii::t('common/error', '35020'), ['task_id' => $this->taskId])), 35020);
        }

        $source = $taskEnabledItem->source;
        $assets = [];
        foreach ($assetEnabledItems as $assetEnabledItem) {
            $assets[] = [
                'type' => $assetEnabledItem->type,
                'absolute_url' => $assetEnabledItem->absolute_url,
            ];
        }

        // 复制来源的资源文件至渠道发布的资源目录,返回相对路径(同步)
        $assetServiceCopyAssetsSyncResult = AssetService::copyAssetsSync($source, $assets);
        foreach ($assetEnabledItems as $key => $assetEnabledItem) {
            $assetEnabledItem->relative_path = $assetServiceCopyAssetsSyncResult[$key]['relative_path'];
            // 取得文件大小,单位(字节)
            $assetEnabledItem->size = filesize(Yii::$app->params['channelPubApi']['asset'][$assetEnabledItem->type]['basePath'] . $assetServiceCopyAssetsSyncResult[$key]['relative_path']);
            $assetEnabledItems[$key] = $assetEnabledItem;
        }

        // 批量更新资源
        $assetEnabledItem->updateMultiple($assetEnabledItems);
    }
}

</pre>

5、编辑 \common\components\queue\CopyAssetEventHandler.php,在配置中所定义的事件处理器,当复制资源文件队列,每次成功执行作业后(afterExec),将调用相应服务进行后续处理,即推送任务至上传资源文件队列;当复制资源文件队列,在作业执行期间发生未捕获的异常时(afterError),插入发布日志,将作业推送至来源回调队列

<pre class="wp-block-syntaxhighlighter-code">

<?php
/**
 * Created by PhpStorm.
 * User: Qiang Wang
 * Date: 2018/10/23
 * Time: 14:23
 */

namespace common\components\queue;

use Yii;
use common\logics\ChannelAppTask;
use common\logics\PubLog;
use common\services\PubLogService;
use common\services\TaskService;
use common\services\SourceCallbackService;
use yii\helpers\Json;
use yii\base\Component;
use yii\queue\ExecEvent;
use yii\web\NotFoundHttpException;
use yii\web\UnprocessableEntityHttpException;
use yii\web\ServerErrorHttpException;
use yii\db\Exception;

/**
 * Class CopyAssetEventHandler
 * @package common\components\queue
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since 1.0
 */
class CopyAssetEventHandler extends Component
{
    /**
     * @param ExecEvent $event
     * @throws NotFoundHttpException 如果未找到数据模型,将抛出 404 HTTP 异常
     * @throws UnprocessableEntityHttpException 如果找到数据模型,状态未启用,将抛出 422 HTTP 异常
     * @throws ServerErrorHttpException 如果基于任务ID查找状态为启用的资源列表为空,将抛出 500 HTTP 异常
     * @throws Exception execution failed
     */
    public static function afterExec(ExecEvent $event)
    {
        $taskId = $event->job->taskId;
        // 基于ID查找状态为启用的单个数据模型(任务)
        $taskEnabledItem = TaskService::findModelEnabledById($taskId);

        // 基于任务ID查找状态为启用的资源列表
        $channelAppTaskEnabledItems = ChannelAppTask::findAllEnabledByTaskId($taskId);

        if (empty($channelAppTaskEnabledItems)) {
            throw new ServerErrorHttpException(Yii::t('common/error', Yii::t('common/error', Yii::t('common/error', '35021'), ['task_id' => $taskId])), 35021);
        }

        try {
            // 调用相应服务进行后续处理
            $serviceClass = 'common\services\\' . str_replace(' ', '', ucwords(str_replace('_', ' ', $taskEnabledItem->channel_type_code))) . 'AssetService'; // 例:common\services\QqCwAssetService
            $serviceAction = 'copyAssetExecHandler';
            $serviceClass::$serviceAction($taskEnabledItem->id);
        } catch (\Throwable $e) {
            $pubLogData = [];
            $time = time();
            foreach ($channelAppTaskEnabledItems as $channelAppTaskEnabledItem) {
                $pubLogData[] = [
                    'channel_id' => $channelAppTaskEnabledItem['channel_id'],
                    'channel_code' => $channelAppTaskEnabledItem['channel_code'],
                    'channel_type_id' => $channelAppTaskEnabledItem['channel_type_id'],
                    'channel_type_code' => $channelAppTaskEnabledItem['channel_type_code'],
                    'task_id' => $channelAppTaskEnabledItem['task_id'],
                    'channel_app_task_id' => $channelAppTaskEnabledItem['id'],
                    'channel_app_task_uuid' => $channelAppTaskEnabledItem['uuid'],
                    'code' => $e->getCode(),
                    'message' => $e->getMessage(),
                    'data' => Json::encode([]),
                    'status' => PubLog::STATUS_ERROR,
                    'is_deleted' => PubLog::IS_DELETED_NO,
                    'created_at' => $time,
                    'updated_at' => $time,
                    'deleted_at' => PubLog::DELETED_AT_DEFAULT,
                ];
            }

            // 发布任务成功后,调用相应服务失败,插入发布日志,将作业推送至来源回调队列(异步)
            SourceCallbackService::errorAsync($pubLogData);
        }
    }

    /**
     * @param ExecEvent $event
     * @throws NotFoundHttpException 如果未找到数据模型,将抛出 404 HTTP 异常
     * @throws UnprocessableEntityHttpException 如果找到数据模型,状态未启用,将抛出 422 HTTP 异常
     * @throws ServerErrorHttpException 如果基于任务ID查找状态为启用的资源列表为空,将抛出 500 HTTP 异常
     * @throws Exception execution failed
     */
    public static function afterError(ExecEvent $event)
    {
        $taskId = $event->job->taskId;
        // 基于ID查找状态为启用的单个数据模型(任务)
        $taskEnabledItem = TaskService::findModelEnabledById($taskId);

        // 基于任务ID查找状态为启用的资源列表
        $channelAppTaskEnabledItems = ChannelAppTask::findAllEnabledByTaskId($taskId);

        if (empty($channelAppTaskEnabledItems)) {
            throw new ServerErrorHttpException(Yii::t('common/error', Yii::t('common/error', Yii::t('common/error', '35021'), ['task_id' => $taskId])), 35021);
        }

        $pubLogData = [];
        $time = time();
        foreach ($channelAppTaskEnabledItems as $channelAppTaskEnabledItem) {
            $pubLogData[] = [
                'channel_id' => $channelAppTaskEnabledItem['channel_id'],
                'channel_code' => $channelAppTaskEnabledItem['channel_code'],
                'channel_type_id' => $channelAppTaskEnabledItem['channel_type_id'],
                'channel_type_code' => $channelAppTaskEnabledItem['channel_type_code'],
                'task_id' => $channelAppTaskEnabledItem['task_id'],
                'channel_app_task_id' => $channelAppTaskEnabledItem['id'],
                'channel_app_task_uuid' => $channelAppTaskEnabledItem['uuid'],
                'code' => $event->error->getCode(),
                'message' => $event->error->getMessage(),
                'data' => Json::encode([]),
                'status' => PubLog::STATUS_ERROR,
                'is_deleted' => PubLog::IS_DELETED_NO,
                'created_at' => $time,
                'updated_at' => $time,
                'deleted_at' => PubLog::DELETED_AT_DEFAULT,
            ];
        }

        // 发布任务失败后,插入发布日志,将作业推送至来源回调队列(异步)
        SourceCallbackService::errorAsync($pubLogData);
    }
}

</pre>

6、打开网址:http://api.channel-pub-api.localhost/qq/v1/qq-cw-apps?group_id=spider ,推送任务至队列

7、info 命令打印关于队列状态的信息,复制资源文件队列中1个任务状态为等待,上传资源文件队列中0个任务状态为等待



.\yii copy-asset-queue/info --color=0
.\yii upload-asset-queue/info --color=0




Jobs
- waiting: 1
- delayed: 0
- reserved: 0
- done: 0

Jobs
- waiting: 0
- delayed: 0
- reserved: 0
- done: 0


8、run 命令获取并执行循环中的任务(复制资源文件队列),直到队列为空,复制资源文件队列中的任务成功执行后,将下一步的上传任务推送至上传资源文件队列,复制资源文件队列中0个任务状态为等待,复制资源文件队列中1个任务状态为完成,上传资源文件队列中1个任务状态为等待,如图2

run 命令获取并执行循环中的任务(复制资源文件队列),直到队列为空,复制资源文件队列中的任务成功执行后,将下一步的上传任务推送至上传资源文件队列,复制资源文件队列中0个任务状态为等待,复制资源文件队列中1个任务状态为完成,上传资源文件队列中1个任务状态为等待
图2


.\yii copy-asset-queue/run --verbose=1 --isolate=1 --color=0
.\yii copy-asset-queue/info
.\yii upload-asset-queue/info --color=0




2018-10-27 17:23:54 [pid: 5216] - Worker is started
2018-10-27 17:23:54 [1] common\jobs\CopyAssetJob (attempt: 1, pid: 5216) - Started
2018-10-27 17:23:55 [1] common\jobs\CopyAssetJob (attempt: 1, pid: 5216) - Done (0.249 s)
2018-10-27 17:23:55 [pid: 5216] - Worker is stopped (0:00:01)

Jobs
- waiting: 0
- delayed: 0
- reserved: 0
- done: 1

Jobs
- waiting: 1
- delayed: 0
- reserved: 0
- done: 0


9、编辑 \common\jobs\CopyAssetJob.php,队列的任务类,特意抛出一个异常,以测试当复制资源文件队列,在作业执行期间发生未捕获的异常时(afterError),插入发布日志,将作业推送至来源回调队列

<pre class="wp-block-syntaxhighlighter-code">

<?php
/**
 * Created by PhpStorm.
 * User: Qiang Wang
 * Date: 2018/10/22
 * Time: 17:10
 */

namespace common\jobs;

use Yii;
use common\logics\Asset;
use common\services\TaskService;
use common\services\AssetService;
use yii\web\ServerErrorHttpException;

/**
 * 复制来源的资源文件至渠道发布的资源目录
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since 1.0
 */
class CopyAssetJob extends Job
{
    public $taskId;

    /*
     * @throws ServerErrorHttpException 如果基于任务ID查找状态为启用的资源列表为空,将抛出 500 HTTP 异常
     */
    public function execute($queue)
    {
        // 基于ID查找状态为启用的单个数据模型(任务)
        $taskEnabledItem = TaskService::findModelEnabledById($this->taskId);

        // 基于任务ID查找状态为启用的资源列表
        $assetEnabledItems = Asset::findAllEnabledByTaskId($this->taskId);

        throw new ServerErrorHttpException(Yii::t('common/error', Yii::t('common/error', Yii::t('common/error', '35020'), ['task_id' => $this->taskId])), 35020);
        if (empty($assetEnabledItems)) {
            throw new ServerErrorHttpException(Yii::t('common/error', Yii::t('common/error', Yii::t('common/error', '35020'), ['task_id' => $this->taskId])), 35020);
        }

        $source = $taskEnabledItem->source;
        $assets = [];
        foreach ($assetEnabledItems as $assetEnabledItem) {
            $assets[] = [
                'type' => $assetEnabledItem->type,
                'absolute_url' => $assetEnabledItem->absolute_url,
            ];
        }

        // 复制来源的资源文件至渠道发布的资源目录,返回相对路径(同步)
        $assetServiceCopyAssetsSyncResult = AssetService::copyAssetsSync($source, $assets);
        foreach ($assetEnabledItems as $key => $assetEnabledItem) {
            $assetEnabledItem->relative_path = $assetServiceCopyAssetsSyncResult[$key]['relative_path'];
            // 取得文件大小,单位(字节)
            $assetEnabledItem->size = filesize(Yii::$app->params['channelPubApi']['asset'][$assetEnabledItem->type]['basePath'] . $assetServiceCopyAssetsSyncResult[$key]['relative_path']);
            $assetEnabledItems[$key] = $assetEnabledItem;
        }

        // 批量更新资源
        $assetEnabledItem->updateMultiple($assetEnabledItems);
    }
}

</pre>

10、清空 Redis,即清空队列中的数据,打开网址:http://api.channel-pub-api.localhost/qq/v1/qq-cw-apps?group_id=spider ,推送任务至队列

11、info 命令打印关于队列状态的信息,复制资源文件队列中1个任务状态为等待,上传资源文件队列中0个任务状态为等待,来源回调队列中0个任务状态为等待



.\yii copy-asset-queue/info --color=0
.\yii upload-asset-queue/info --color=0
.\yii source-callback-queue/info --color=0




Jobs
- waiting: 1
- delayed: 0
- reserved: 0
- done: 0

Jobs
- waiting: 0
- delayed: 0
- reserved: 0
- done: 0

Jobs
- waiting: 0
- delayed: 0
- reserved: 0
- done: 0


12、run 命令获取并执行循环中的任务(复制资源文件队列),直到队列为空,复制资源文件队列中的任务执行失败(因为任务中有抛出未捕获的异常)后,将下一步的上传任务推送至上传资源文件队列,复制资源文件队列中0个任务状态为等待,复制资源文件队列中1个任务状态为完成,上传资源文件队列中0个任务状态为等待,来源回调队列中1个任务状态为等待,如图3

run 命令获取并执行循环中的任务(复制资源文件队列),直到队列为空,复制资源文件队列中的任务执行失败(因为任务中有抛出未捕获的异常)后,将下一步的上传任务推送至上传资源文件队列,复制资源文件队列中0个任务状态为等待,复制资源文件队列中1个任务状态为完成,上传资源文件队列中0个任务状态为等待,来源回调队列中1个任务状态为等待
图3


.\yii copy-asset-queue/run --verbose=1 --isolate=1 --color=0
.\yii copy-asset-queue/info --color=0
.\yii upload-asset-queue/info --color=0
.\yii source-callback-queue/info --color=0




2018-10-27 17:37:10 [pid: 32132] - Worker is started
2018-10-27 17:37:11 [1] common\jobs\CopyAssetJob (attempt: 1, pid: 32132) - Started
2018-10-27 17:37:11 [1] common\jobs\CopyAssetJob (attempt: 1, pid: 32132) - Error (0.232 s)
> yii\web\ServerErrorHttpException: Based on task ID: 2, find the list of enabled resources is empty
2018-10-27 17:37:11 [pid: 32132] - Worker is stopped (0:00:01)

Jobs
- waiting: 0
- delayed: 0
- reserved: 0
- done: 1

Jobs
- waiting: 0
- delayed: 0
- reserved: 0
- done: 0

Jobs
- waiting: 1
- delayed: 0
- reserved: 0
- done: 0


13、查看系统日志表的异常信息,即 log 表中的 message,有助于后续开发人员的分析工作,如图4

查看系统日志表的异常信息,即 log 表中的 message,有助于后续开发人员的分析工作
图4


[1] common\jobs\CopyAssetJob (attempt: 1, PID: 32132) is finished with error: yii\web\ServerErrorHttpException: Based on task ID: 2, find the list of enabled resources is empty in E:\wwwroot\channel-pub-api\common\jobs\CopyAssetJob.php:38
 Stack trace:
 #0 E:\wwwroot\channel-pub-api\vendor\yiisoft\yii2-queue\src\Queue.php(214): common\jobs\CopyAssetJob->execute(Object(yii\queue\redis\Queue))
 #1 E:\wwwroot\channel-pub-api\vendor\yiisoft\yii2-queue\src\cli\Queue.php(162): yii\queue\Queue->handleMessage('1', 'O:24:"common\\jo...', '600', '1')
 #2 E:\wwwroot\channel-pub-api\vendor\yiisoft\yii2-queue\src\cli\Command.php(145): yii\queue\cli\Queue->execute('1', 'O:24:"common\\jo...', '600', '1', '32132')
 #3 [internal function]: yii\queue\cli\Command->actionExec('1', '600', '1', '32132')
 #4 E:\wwwroot\channel-pub-api\vendor\yiisoft\yii2\base\InlineAction.php(57): call_user_func_array(Array, Array)
 #5 E:\wwwroot\channel-pub-api\vendor\yiisoft\yii2\base\Controller.php(157): yii\base\InlineAction->runWithParams(Array)
 #6 E:\wwwroot\channel-pub-api\vendor\yiisoft\yii2\console\Controller.php(148): yii\base\Controller->runAction('exec', Array)
 #7 E:\wwwroot\channel-pub-api\vendor\yiisoft\yii2\base\Module.php(528): yii\console\Controller->runAction('exec', Array)
 #8 E:\wwwroot\channel-pub-api\vendor\yiisoft\yii2\console\Application.php(180): yii\base\Module->runAction('copy-asset-queu...', Array)
 #9 E:\wwwroot\channel-pub-api\vendor\yiisoft\yii2\console\Application.php(147): yii\console\Application->runAction('copy-asset-queu...', Array)
 #10 E:\wwwroot\channel-pub-api\vendor\yiisoft\yii2\base\Application.php(386): yii\console\Application->handleRequest(Object(yii\console\Request))
 #11 E:\wwwroot\channel-pub-api\yii(23): yii\base\Application->run()
 #12 {main}.


14、发布任务失败后,插入发布日志,将作业推送至来源回调队列(异步),查看发布日志表中的 message,即 pub_log,此为业务数据,最终会显示给用户,如图5

发布任务失败后,插入发布日志,将作业推送至来源回调队列(异步),查看发布日志表中的 message,即 pub_log,此为业务数据,最终会显示给用户
图5


Based on task ID: 2, find the list of enabled resources is empty


]]>
https://www.shuijingwanwq.com/2018/10/27/2963/feed/ 0
将 Sentry 9 与 Yii 2 高级项目模板 集成的实现 https://www.shuijingwanwq.com/2018/08/24/2861/ https://www.shuijingwanwq.com/2018/08/24/2861/#respond Fri, 24 Aug 2018 02:11:47 +0000 http://www.shuijingwanwq.com/?p=2861 浏览量: 127 1、在配置您的应用程序的 All 选项中,不存在 Yii2 框架,只能够选择 PHP,如图1
在配置您的应用程序的 All 选项中,不存在 Yii2 框架,只能够选择 PHP

图1

2、为 Sentry 安装 PHP 集成。推荐的方法是使用 Composer,不过决定暂不采用此方案,如图2   3、在 GitHub 中搜索 yii2 sentry,决定基于 hellowearemito/yii2-sentry 实现,如图3
为 Sentry 安装 PHP 集成。推荐的方法是使用 Composer,不过决定暂不采用此方案

图2

3、在 GitHub 中搜索 yii2 sentry,决定基于 hellowearemito/yii2-sentry 实现,如图3
在 GitHub 中搜索 yii2 sentry,决定基于 hellowearemito/yii2-sentry 实现

图3

4、打开网址:https://github.com/hellowearemito/yii2-sentry ,执行命令,如图4
打开网址:https://github.com/hellowearemito/yii2-sentry ,执行命令

图4



composer require --prefer-dist mito/yii2-sentry "~1.0.0"


5、安装扩展后,在开发环境的配置文件中设置配置,编辑 \environments\dev\api\config\main-local.php


return [
    'components' => [
        'request' => [
            // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
            'cookieValidationKey' => '',
        ],
        'sentry' => [
            'class' => 'mito\sentry\Component',
            'enabled' => false, // 设置为 false 以跳过收集错误,即禁用 Sentry,默认:true
            'dsn' => 'YOUR-PRIVATE-DSN', // 私有 DSN
            'environment' => 'development', // 环境,development:开发环境;production:生产环境,默认:production
            'jsNotifier' => false, // 收集 JS 错误,默认:false
            'jsOptions' => [ // raven-js 配置参数
                'whitelistUrls' => [ // 收集JS错误的网址
                    'http://api.gitlab-php-yii2-app-advanced-cmc.localhost',
                ],
            ],
        ],
    ],
];


6、安装扩展后,在生产环境的配置文件中设置配置,编辑 \environments\prod\api\config\main-local.php


return [
    'components' => [
        'request' => [
            // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
            'cookieValidationKey' => '',
        ],
        'sentry' => [
            'class' => 'mito\sentry\Component',
            'enabled' => false, // 设置为 false 以跳过收集错误,即禁用 Sentry,默认:true
            'dsn' => 'YOUR-PRIVATE-DSN', // 私有 DSN
            'environment' => 'production', // 环境,development:开发环境;production:生产环境,默认:production
            'jsNotifier' => false, // 收集 JS 错误,默认:false
            'jsOptions' => [ // raven-js 配置参数
                'whitelistUrls' => [ // 收集JS错误的网址
                    'http://api.gitlab-php-yii2-app-advanced-cmc.localhost',
                ],
            ],
        ],
    ],
];


7、配置日志组件,以支持将日志发送至 Sentry,编辑 \api\config\main.php


    'components' => [
        'log' => [
            'traceLevel' => YII_DEBUG ? 3 : 0,
            'targets' => [
                'sentry' => [
                    'class' => 'mito\sentry\Target',
                    'levels' => ['error', 'warning'],
                ],
            ],
        ],
    ],


8、初始化选择开发环境,编辑 \api\config\main-local.php,如图5
初始化选择开发环境,编辑 \api\config\main-local.php

图5



init
0
yes
All


9、私有 DSN 的值,需要从 /settings/sentry/pcs-api/install/php/ 的 Configuration 中复制,如图6
私有 DSN 的值,需要从 /settings/sentry/pcs-api/install/php/ 的 Configuration 中复制

图6

10、GET 请求接口,响应 302,如图7
GET 请求接口,响应 302

图7



{
    "name": "Found",
    "message": "框架服务控制台HTTP请求失败:登录超时",
    "code": 20039,
    "status": 302,
    "type": "yii\\web\\HttpException"
}


11、查看 Unresolved Issues,即尚未解决的问题,符合预期,对应的日志已经发送至 Sentry,如图8
查看 Unresolved Issues,即尚未解决的问题,符合预期,对应的日志已经发送至 Sentry

图8

12、初始化选择生产环境,编辑 \api\config\main-local.php,如图9
初始化选择生产环境,编辑 \api\config\main-local.php

图9



init
1
yes
All


13、GET 请求接口,响应 404,如图10
GET 请求接口,响应 404

图10



{
    "name": "Not Found",
    "message": "用户ID:10,不存在",
    "code": 20002,
    "status": 404
}


14、查看 Unresolved Issues,即尚未解决的问题,符合预期,对应的日志已经发送至 Sentry,如图11
查看 Unresolved Issues,即尚未解决的问题,符合预期,对应的日志已经发送至 Sentry

图11

15、初始化选择开发环境后,不编辑 \api\config\main-local.php,如图12
初始化选择开发环境后,不编辑 \api\config\main-local.php

图12

16、GET 请求接口,响应 404,如图13
GET 请求接口,响应 404

图13



{
    "name": "Not Found",
    "message": "用户ID:11,不存在",
    "code": 20002,
    "status": 404,
    "type": "yii\\web\\NotFoundHttpException"
}


17、查看 Unresolved Issues,即尚未解决的问题,符合预期,对应的日志未发送至 Sentry,因为已禁用 Sentry,如图14
查看 Unresolved Issues,即尚未解决的问题,符合预期,对应的日志未发送至 Sentry,因为已禁用 Sentry

图14

18、再次重复第 15、16、17步骤,第15步骤的环境选择生产环境,发现对应的日志未发送至 Sentry,符合预期,因为已禁用 Sentry,如图15
再次重复第 15、16、17步骤,第15步骤的环境选择生产环境,发现对应的日志未发送至 Sentry,符合预期,因为已禁用 Sentry

图15

]]>
https://www.shuijingwanwq.com/2018/08/24/2861/feed/ 0
基于 yiisoft/yii2-app-advanced,在 GitHub 上新建仓库 yii2-app-advanced,新建接口应用(实现 RESTful 风格的 Web Service 服务的 API),调整默认字符集为:utf8mb4,接口响应格式的调整,空数组自动转换为空对象,在接口应用中收集请求日志消息(1个请求对应1条日志消息)至数据库,且实现日志功能的相应接口:日志列表(设置数据过滤器以启用筛选器处理)、日志详情 https://www.shuijingwanwq.com/2018/06/22/2710/ https://www.shuijingwanwq.com/2018/06/22/2710/#respond Fri, 22 Jun 2018 11:00:19 +0000 http://www.shuijingwanwq.com/?p=2710 浏览量: 199

1、设置数据库的默认排序规则为:utf8mb4_unicode_ci,如图1

设置数据库的默认排序规则为:utf8mb4_unicode_ci

图1

2、修改用于数据库连接的默认字符集为:utf8mb4,编辑开发环境下的配置文件,\environments\dev\common\config\main-local.php,编辑生产环境下的配置文件,\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、执行初始化命令,如图2

执行初始化命令

图2


.\init


4、编辑数据库配置,\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、清空数据库,执行数据库迁移命令,如图3

清空数据库,执行数据库迁移命令

图3


.\yii migrate


6、日志的数据库模式可以通过应用迁移来初始化,执行如下命令,如图4

日志的数据库模式可以通过应用迁移来初始化,执行如下命令

图4


.\yii migrate --migrationPath=@yii/log/migrations/


7、浏览数据库表,user、log表的排序规则为:utf8_unicode_ci,如图5

浏览数据库表,user、log表的排序规则为:utf8_unicode_ci

图5

8、新建一个数据库迁移文件,调整排序规则为:utf8mb4_unicode_ci,执行命令,如图6

新建一个数据库迁移文件,调整排序规则为:utf8mb4_unicode_ci,执行命令

图6


.\yii migrate/create update_table_options_to_log


9、修改表(user、log)默认的字符集和所有字符列的字符集,编辑 \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、一个 [[yii\log\DbTarget|database target]] 目标导出已经过滤的日志消息到一个数据的表里面,设置日志目标为 DbTarget

11、控制台应用的配置文件,\console\config\main.php,代码


    'components' => [
        'log' => [
            'targets' => [
                [
                    'class' => 'yii\log\FileTarget',
                    'levels' => ['error', 'warning'],
                ],
            ],
        ],
    ],


12、控制台应用的配置文件,\console\config\main.php,设置日志目标为 DbTarget,编辑代码


    '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、接口应用的配置文件,\api\config\main.php,代码

<?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、接口应用的配置文件,\api\config\main.php,设置日志目标为 DbTarget,编辑代码


        '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、删除数据库中的所有表,重新执行命令,如图7

删除数据库中的所有表,重新执行命令

图7


.\yii migrate --migrationPath=@yii/log/migrations/
.\yii migrate


16、浏览数据库表,user、log表的排序规则为:utf8mb4_unicode_ci,且表列的排序规则也为:utf8mb4_unicode_ci,如图8

浏览数据库表,user、log表的排序规则为:utf8mb4_unicode_ci,且表列的排序规则也为:utf8mb4_unicode_ci

图8

17、定义请求日志行为,触发 [[yii\base\Application::EVENT_BEFORE_REQUEST|EVENT_BEFORE_REQUEST]] 事件时,写入日志至数据库,新建 \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、基于配置将行为附加到应用主体,编辑 \api\config\main.php


    'components' => [
    ],
    'as requestLog' => [
        'class' => api\behaviors\RequestLogBehavior::class,
    ],


19、查看日志表,控制台应用的运行未写入日志,因为通过 [[yii\log\Target::except|except]] 属性来设置所有分类作为黑名单,如图9

查看日志表,控制台应用的运行未写入日志,因为通过 [[yii\log\Target::except|except]] 属性来设置所有分类作为黑名单

图9

20、运行接口应用,在 Postman 上执行1个不存在的接口请求,响应如下


{
    "name": "Not Found",
    "message": "页面未找到。",
    "code": 0,
    "status": 404,
    "type": "yii\\web\\NotFoundHttpException"
}


21、运行接口应用,在 Postman 上执行3个已存在的接口请求,依次响应如下


{
    "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、查看日志表,接口应用的运行已写入日志,因为通过 [[yii\log\Target::categories|categories]] 属性来设置 api\behaviors\RequestLogBehavior::beforeRequest 分类作为白名单,因此仅有 api\behaviors\RequestLogBehavior::beforeRequest 分类下的日志被写入,由于总计执行了4次,写入了4条日志,如图10

查看日志表,接口应用的运行已写入日志,因为通过 [[yii\log\Target::categories|categories]] 属性来设置 api\behaviors\RequestLogBehavior::beforeRequest 分类作为白名单,因此仅有 api\behaviors\RequestLogBehavior::beforeRequest 分类下的日志被写入,由于总计执行了4次,写入了4条日志

图10


23、实现日志功能的相应接口,打开网址:http://www.github-shuijingwan-yii2-app-advanced.localhost/gii/model ,选项,命名空间为common\models,此时需支持国际化,生成 \common\models\Log.php,如图11

实现日志功能的相应接口,打开网址:http://www.github-shuijingwan-yii2-app-advanced.localhost/gii/model ,选项,命名空间为common\models,此时需支持国际化,生成 \common\models\Log.php

图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、新建 \common\logics\Log.php,在common/logics目录中的MySQL模型文件为业务逻辑相关,继承至 \common\models\Log 数据层

<?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、新建 \common\messages\en-US\model\log.php,支持目标语言为英语美国时的消息翻译

<?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、新建 \common\messages\zh-CN\model\log.php,支持目标语言为简体中文时的消息翻译

<?php
/**
 * Created by PhpStorm.
 * User: WangQiang
 * Date: 2018/06/21
 * Time: 15:44
 */
 
return [
    'ID' => 'ID',
    'Level' => '等级',
    'Category' => '分类',
    'Log Time' => '日志时间',
    'Prefix' => '前缀',
    'Message' => '消息',
];
 

27、新建 \api\models\Log.php,在api/models目录中的MySQL模型文件为业务逻辑相关(仅与api相关),继承至 \common\logics\Log 逻辑层

<?php
/**
 * Created by PhpStorm.
 * User: WangQiang
 * Date: 2018/06/21
 * Time: 15:50
 */
 
namespace api\models;
 
class Log extends \common\logics\Log
{
 
}
 

28、新建 \api\modules\v1\models\Log.php,继承至 \api\models\Log.php
注:\api\modules\v1\models\Log(仅用于 v1 模块) > \api\models\Log(仅用于 api 应用) > \common\logics\Log.php(可用于 api、frontend 等多个应用) > \common\models\Log.php(仅限于 Gii 生成) > \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、实现日志功能的相应接口,编辑 \api\config\urlManager.php,仅支持列表与详情

<?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、新建控制器类 \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、新建 \api\modules\v1\controllers\LogController.php,通过指定 [[yii\rest\ActiveController::modelClass|modelClass]] 作为 api\modules\v1\models\Log, 控制器就能知道使用哪个模型去获取和处理数据
注:\api\modules\v1\controllers\LogController.php(仅用于 v1 模块) > \api\controllers\LogController.php(仅用于 api 应用) > \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、复制目录 \api\rests\user 下的 Action.php、IndexAction.php、ViewAction.php、Serializer.php 至目录 \api\rests\log

33、编辑 \api\rests\log\IndexAction.php,调整命名空间、继承关系、查询条件等

<?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、编辑 \api\rests\log\Serializer.php,调整命名空间、继承关系、响应结构(响应成功:”code”: 10000,”message”,”data”;响应失败:”code”: 不等于10000的其他数字,”message”)等

<?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、编辑语言包文件:\api\messages\zh-CN\success.php(简体中文、响应成功)

<?php
return [
    10000 => 'success',
    10001 => '获取用户列表成功',
    10002 => '获取用户详情成功',
    10003 => '创建用户成功',
    10004 => '更新用户成功',
    10005 => '删除用户成功',
    10801 => '获取日志列表成功',
    10802 => '获取日志详情成功',
];

36、编辑语言包文件:\api\messages\zh-CN\error.php(简体中文、响应失败)

<?php
return [
    20000 => 'error',
    20001 => '用户列表为空',
    20002 => '用户ID:{id},不存在',
    20003 => '用户ID:{id},的状态为已删除',
    20004 => '数据验证失败:{firstErrors}',
    20801 => '日志列表为空',
    20802 => '日志ID:{id},不存在',
    20803 => '数据过滤器验证失败:{firstErrors}',
];

37、编辑语言包文件:\api\messages\en-US\success.php(英语美国、响应成功)

<?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、编辑语言包文件:\api\messages\en-US\error.php(英语美国、响应失败)

<?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、在 Postman 中,GET http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/logs ,200响应,requestQueryParams、requestBodyParams的格式有时为数组(为空时),有时为对象,如图12

在 Postman 中,GET http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/logs?per-page=3 ,200响应

图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、在 Postman 中,GET http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/logs?filter[level]=a ,200响应,如图13

在 Postman 中,GET http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/logs?filter[level]=a ,200响应

图13


{
    "code": 20803,
    "message": "数据过滤器验证失败:Level必须是整数。"
}


41、在 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响应,如图14

在 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响应

图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、定义一个搜索模型,此搜索模型应声明所有可用的搜索属性及其验证规则,新建 \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、新建 \api\models\LogSearch.php,在api/models目录中的MySQL模型文件为业务逻辑相关(仅与api相关),继承至 \common\logics\LogSearch 逻辑层

<?php
/**
 * Created by PhpStorm.
 * User: WangQiang
 * Date: 2018/06/22
 * Time: 13:43
 */
 
namespace api\models;
 
class LogSearch extends \common\logics\LogSearch
{
 
}

44、新建 \api\modules\v1\models\LogSearch.php,继承至 \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、编辑 \api\rests\log\IndexAction.php,取消使用 yii\base\DynamicModel实例作为$searchModel,设置数据过滤器以启用筛选器处理,生成SQL语句,如图15

编辑 \api\rests\log\IndexAction.php,取消使用 yii\base\DynamicModel实例作为$searchModel,设置数据过滤器以启用筛选器处理,生成SQL语句

图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: 返回日志 1 的详细信息,编辑 \api\rests\log\Action.php,调整命名空间、继承关系、响应结构等

<?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、编辑 \api\rests\log\ViewAction.php,调整命名空间、继承关系、响应结构等。ContentNegotiator支持响应内容格式处理和语言处理。 通过检查 GET 参数和 Accept HTTP头部来决定响应内容格式和语言。配置ContentNegotiator支持英语美国和简体中文。配置响应组件,传递给 yii\helpers\Json::encode() 的编码选项,JSON_FORCE_OBJECT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE,JSON_FORCE_OBJECT:使一个非关联数组输出一个类(Object)而非数组。在数组为空而接受者需要一个类(Object)的时候尤其有用。避免手动处理空数组的转换。

<?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、在 Postman 中,GET http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/logs/3, 如图16

在 Postman 中,GET http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/logs/3

图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、定义请求日志行为,触发 [[yii\base\Application::EVENT_BEFORE_REQUEST|EVENT_BEFORE_REQUEST]] 事件时,写入日志至数据库,编辑 \api\behaviors\RequestLogBehavior.php,将 null 替换为 ”,以保持字段格式统一。

50、接口应用的配置文件,\api\config\main.php,设置日志目标为 DbTarget,设置白名单分类为 api\behaviors\RequestLogBehavior::afterRequest 编辑代码,避免 message[‘userId’] 无法获取值的情况,注:会导致404等请求无法写入日志


        '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、定义请求日志行为,触发 [[yii\base\Application::EVENT_AFTER_REQUEST|EVENT_AFTER_REQUEST]] 事件时,写入日志至数据库,编辑 \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_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、在 Postman 中,GET http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/logs?page=77&per-page=3 ,200响应


{
    "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
        }
    }
}


]]>
https://www.shuijingwanwq.com/2018/06/22/2710/feed/ 0
在 Rancher 中出现 Rolling-Back (Expected state running but got stopped),无法再次升级的解决 https://www.shuijingwanwq.com/2018/06/13/2696/ https://www.shuijingwanwq.com/2018/06/13/2696/#respond Wed, 13 Jun 2018 11:24:13 +0000 http://www.shuijingwanwq.com/?p=2696 浏览量: 111 1、在 Rancher 中升级失败,回滚时出现 Rolling-Back (Expected state running but got stopped),如图1
在 Rancher 中升级失败,回滚时出现 Rolling-Back (Expected state running but got stopped)

图1

2、点击查看日志的链接,如图2
点击查看日志的链接

图2



2018/6/13 下午6:26:01/config/init/config0.sh: line 2: PCS_API_CFG_DB_HOST: unbound variable
2018/6/13 下午6:26:06/config/init/config0.sh: line 2: PCS_API_CFG_DB_HOST: unbound variable
2018/6/13 下午6:26:20/config/init/config0.sh: line 2: PCS_API_CFG_DB_HOST: unbound variable
2018/6/13 下午6:26:36/config/init/config0.sh: line 2: PCS_API_CFG_DB_HOST: unbound variable
2018/6/13 下午6:26:50/config/init/config0.sh: line 2: PCS_API_CFG_DB_HOST: unbound variable
2018/6/13 下午6:27:06/config/init/config0.sh: line 2: PCS_API_CFG_DB_HOST: unbound variable
2018/6/13 下午6:27:21/config/init/config0.sh: line 2: PCS_API_CFG_DB_HOST: unbound variable
2018/6/13 下午6:27:36/config/init/config0.sh: line 2: PCS_API_CFG_DB_HOST: unbound variable
2018/6/13 下午6:27:51/config/init/config0.sh: line 2: PCS_API_CFG_DB_HOST: unbound variable
2018/6/13 下午6:28:20/config/init/config0.sh: line 2: PCS_API_CFG_DB_HOST: unbound variable
2018/6/13 下午6:28:50/config/init/config0.sh: line 2: PCS_API_CFG_DB_HOST: unbound variable
2018/6/13 下午6:29:06/config/init/config0.sh: line 2: PCS_API_CFG_DB_HOST: unbound variable
2018/6/13 下午6:30:06/config/init/config0.sh: line 2: PCS_API_CFG_DB_HOST: unbound variable
2018/6/13 下午6:31:06/config/init/config0.sh: line 2: PCS_API_CFG_DB_HOST: unbound variable
2018/6/13 下午6:32:06/config/init/config0.sh: line 2: PCS_API_CFG_DB_HOST: unbound variable
2018/6/13 下午6:32:11/config/init/config0.sh: line 2: PCS_API_CFG_DB_HOST: unbound variable
2018/6/13 下午6:33:21/config/init/config0.sh: line 2: PCS_API_CFG_DB_HOST: unbound variable
2018/6/13 下午6:34:21/config/init/config0.sh: line 2: PCS_API_CFG_DB_HOST: unbound variable


3、日志: webtv-pcs-1,提示:PCS_API_CFG_DB_HOST,未绑定变量(可以明确的是,之前是绑定了环境变量的,原因暂未找到),如图3
日志: webtv-pcs-1,提示:PCS_API_CFG_DB_HOST,未绑定变量(可以明确的是,之前是绑定了环境变量的,原因暂未找到)

图3

4、克隆服务为 pcs-backup,选择镜像,registry-vpc.cn-beijing.aliyuncs.com/cmc/pcs-api:dev_dev-0.4.7,升级,如图4、图5
克隆服务为 pcs-backup

图4

 
克隆服务为 pcs-backup,选择镜像,registry-vpc.cn-beijing.aliyuncs.com/cmc/pcs-api:dev_dev-0.4.7,升级

图5

5、pcs-backup 升级成功后,删除服务 pcs,如图6
pcs-backup 升级成功后,删除服务 pcs

图6

6、编辑 pcs-backup,修改名称为 pcs,如图7
编辑 pcs-backup,修改名称为 pcs

图7

]]>
https://www.shuijingwanwq.com/2018/06/13/2696/feed/ 0