任务的入库成功、入库失败,皆需要能够查询到发布记录的设计与实现(在 Yii 2.0 中具体实现) (一)

1、获取企鹅号的应用的任务列表,列表为空,如图1

图1

2、发布文章类型:标准(普通、图文)的文章,发布成功(即入库成功),查看发布任务的 SQL 语句,如图2

图2

Begin transaction

INSERT INTO `cpa_task` (`group_id`, `source`, `source_uuid`, `source_pub_user_id`, `source_callback_url`, `channel_id`, `channel_code`, `channel_type_id`, `channel_type_code`, `status`, `created_at`, `updated_at`) VALUES ('015ce30b116ce86058fa6ab4fea4ac63', 'spider', '825e6d5e36468cc4bf536799ce3565cf', '3', 'http://scms.wjdev.chinamcloud.cn/api/thirdPush/callBack', 1, 'qq', 1, 'qq_cw', 1, 1577686722, 1577686722)

INSERT INTO `cpa_channel_app_task` (`channel_id`, `channel_code`, `channel_type_id`, `channel_type_code`, `channel_app_source_id`, `channel_app_source_uuid`, `task_id`, `have_pub_number`, `status`, `created_at`, `updated_at`, `uuid`) VALUES (1, 'qq', 1, 'qq_cw', 14, '8d72b7cc2ac911eab85a54ee75d2ebc1', 1, 3, 1, 1577686722, 1577686722, '3a1d31262acc11ea9f5754ee75d2ebc1')

INSERT INTO `cpa_qq_cw_app_task` (`channel_app_task_id`, `channel_app_task_uuid`, `qq_cw_app_id`, `task_id`, `platform_status`, `status`, `created_at`, `updated_at`) VALUES (1, '3a1d31262acc11ea9f5754ee75d2ebc1', 2, 1, 0, 1, 1577686722, 1577686722)

INSERT INTO `cpa_qq_article` (`group_id`, `article_category_id`, `title`, `author`, `source_article_id`, `qq_app_type`, `article_type_id`, `qq_article_type_id`, `qq_article_category_id`, `task_id`, `status`, `created_at`, `updated_at`) VALUES ('015ce30b116ce86058fa6ab4fea4ac63', 15, '2019 年 22 款最佳软件开发工具', 'Guru99', 1, 'cw', 1, 1, 18, 1, 1, 1577686722, 1577686722)

INSERT INTO `cpa_qq_article_normal` (`content`, `cover_pic`, `cover_type`, `tag`, `apply`, `original_platform`, `original_url`, `original_author`, `qq_article_id`, `category`, `status`, `created_at`, `updated_at`) VALUES ('市面上有海量的软件开发工具,因此,选择最佳软件开发工具可能是一项挑战。本文是 22 款顶级软件开发工具的精选列表。', 'https://static001.infoq.cn/resource/image/90/6a/9054bf66f3ce4d0a4822a1b7398c566a.png', 1, '2019,22,最佳,软件,开发工具', 0, 0, '', '', 1, 18, 1, 1577686722, 1577686722)

Commit transaction

3、获取企鹅号的应用的任务列表,列表中存在 1 条记录,查看获取企鹅号的应用的任务列表的 SQL 语句,如图3

图3

SELECT `cpa_channel_app_task`.*, `cpa_task`.`group_id`, `cpa_task`.`source`, `cpa_task`.`source_uuid`, `cpa_task`.`source_pub_user_id`, `cpa_qq_cw_app`.`penguin_name`, `cpa_channel_app_task`.`status`, `cpa_article_type`.`code` AS `article_type_code`, `cpa_article_type`.`name` AS `article_type_name`, `cpa_qq_article`.`article_category_id`, `cpa_qq_article`.`title` AS `article_title`, `cpa_qq_article`.`author` AS `article_author`, `cpa_qq_article`.`source_article_id`, `cpa_qq_transaction`.`article_url`, `cpa_pub_log`.`code` AS `pub_log_code`, `cpa_pub_log`.`message` AS `pub_log_message` FROM `cpa_channel_app_task` LEFT JOIN `cpa_task` ON `cpa_channel_app_task`.`task_id` = `cpa_task`.`id` LEFT JOIN `cpa_qq_article` ON `cpa_task`.`id` = `cpa_qq_article`.`task_id` LEFT JOIN `cpa_article_type` ON `cpa_qq_article`.`article_type_id` = `cpa_article_type`.`id` LEFT JOIN `cpa_qq_cw_app_task` ON `cpa_channel_app_task`.`id` = `cpa_qq_cw_app_task`.`channel_app_task_id` LEFT JOIN `cpa_qq_cw_app` ON `cpa_qq_cw_app_task`.`qq_cw_app_id` = `cpa_qq_cw_app`.`id` LEFT JOIN `cpa_pub_log` ON `cpa_channel_app_task`.`id` = `cpa_pub_log`.`channel_app_task_id` LEFT JOIN `cpa_qq_transaction` ON `cpa_qq_cw_app_task`.`id` = `cpa_qq_transaction`.`qq_app_task_id` AND `cpa_qq_transaction`.`type` = '1' WHERE ((`cpa_channel_app_task`.`is_deleted`=0) AND (`cpa_task`.`is_deleted`=0) AND (`cpa_qq_article`.`is_deleted`=0) AND (`cpa_article_type`.`is_deleted`=0) AND (`cpa_qq_cw_app_task`.`is_deleted`=0) AND (`cpa_qq_cw_app`.`is_deleted`=0)) AND (`cpa_task`.`group_id`='015ce30b116ce86058fa6ab4fea4ac63') ORDER BY `cpa_task`.`id` DESC LIMIT 20

4、发布文章类型:标准(普通、图文)的文章,发布失败(即未入库,因为数据验证失败),由步骤 2 可知,即使数据验证成功(),5 条插入 SQL 语句在 1 个事务中,只要其中任意 1 条 SQL 执行失败,便会回滚,入库失败。如图4

图4

5、获取企鹅号的应用的任务列表,列表中存在 1 条记录。但是,客户端希望能够获取到 2 条记录,因为,从客户端的用户方面来看待此问题,实际上是已经发布过 2 次任务,而不是 1 次。现阶段的实现方案有 2 种,第 1 种是渠道发布本身维持现有的业务逻辑不变化,发布任务的记录在客户端数据库中冗余再存储;第 2 种方案是渠道发布任务的入库成功、入库失败,皆需要能够查询到发布记录,客户端不做处理,仅直接获取渠道发布的任务列表。最终决定采用第 2 种方案,原因在于任务记录仅存在一个数据源,有利于保证唯一性与简单性,避免多个数据源导致的冗余度与复杂性。如图5

图5

6、如何来实现即使入库失败,也能够查询到发布记录呢?前提是必须要保证请求的数据入库成功,但是理论上是无法实现的。比如说:请求数据中的标题长度超出表:cpa_qq_article 的字段:title 的长度,即使在执行插入 SQL 之前不做数据验证,SQL 语句也会执行失败,进而回滚。如图6

图6

    /**
     * {@inheritdoc}
     */    public function rules()
    {
        return [
            [['title'], 'string', 'max' => 255], // 删除掉,不做数据验证
        ];
    }
{
    "name": "Database Exception",
    "message": "SQLSTATE[22001]: String data, right truncated: 1406 Data too long for column 'title' at row 1\nThe SQL being executed was: INSERT INTO `cpa_qq_article` (`group_id`, `article_category_id`, `title`, `author`, `source_article_id`, `qq_app_type`, `article_type_id`, `qq_article_type_id`, `qq_article_category_id`, `task_id`, `status`, `created_at`, `updated_at`) VALUES ('015ce30b116ce86058fa6ab4fea4ac63', 15, '未来 10 年,“星光中国芯工程”计划投资 100 亿元用于芯片研发及大规模产业化。北京时间 2019 年 12 月 28 日,1999-2019“星光中国芯工程”创新成果与展望报告会在人民大会堂举行。会议回顾和总结了“星光中国芯工程”20 年来在核心技术自主创新、在研发成果大规模产业化、以及在满足国家重大工程技术需求方面取得的重要进展和成功经验,并对“星光中国芯工程”未来发展进行了规划和展望。中国工程院院士、“星光中国芯工程”总指挥、中星微电子集团创建人兼首席科学家邓中翰作了“星光中国芯工程”20 年成果与展望工作报告,报告中,邓中翰表示,在物理层面智能芯片的发展已经受到了物理规律的限制,看似已经接近了极限的时候,在信息层面的技术创新还远远没有碰到天花板。1999 年,邓中翰等一批海外爱国博士企业家回国承担并启动实施“星光中国芯工程”,在北京中关村设立中星微电子公司,致力于超大规模集成电路芯片的研发、设计及产业化工作。2001 年,中国第一颗百万门级超大规模数字多媒体芯片“星光一号”诞生。其后数年间,“星光多媒体”系列芯片被苹果、三星、飞利浦、惠普、LG、索尼、戴尔等国外知名品牌规模采用,占领了全球计算机图像输入芯片 60% 以上的市场份额,彻底结束了中国无“芯”的历史。', '刘燕', 1, 'cw', 1, 1, 18, 4, 1, 1577692119, 1577692119)",
    "code": 22001,
    "type": "yii\\db\\Exception",
    "file": "E:\\wwwroot\\channel-pub-api\\vendor\\yiisoft\\yii2\\db\\Schema.php",
    "line": 674,
    "stack-trace": [
        "#0 E:\\wwwroot\\channel-pub-api\\vendor\\yiisoft\\yii2\\db\\Command.php(1295): yii\\db\\Schema->convertException(Object(PDOException), 'INSERT INTO `cp...')",
        "#1 E:\\wwwroot\\channel-pub-api\\vendor\\yiisoft\\yii2\\db\\Command.php(1091): yii\\db\\Command->internalExecute('INSERT INTO `cp...')",
        "#2 E:\\wwwroot\\channel-pub-api\\vendor\\yiisoft\\yii2\\db\\Schema.php(432): yii\\db\\Command->execute()",
        "#3 E:\\wwwroot\\channel-pub-api\\vendor\\yiisoft\\yii2\\db\\ActiveRecord.php(600): yii\\db\\Schema->insert('{{%qq_article}}', Array)",
        "#4 E:\\wwwroot\\channel-pub-api\\vendor\\yiisoft\\yii2\\db\\ActiveRecord.php(566): yii\\db\\ActiveRecord->insertInternal(NULL)",
        "#5 E:\\wwwroot\\channel-pub-api\\vendor\\yiisoft\\yii2\\db\\BaseActiveRecord.php(678): yii\\db\\ActiveRecord->insert(false, NULL)",
        "#6 E:\\wwwroot\\channel-pub-api\\common\\services\\QqArticleService.php(88): yii\\db\\BaseActiveRecord->save(false)",
        "#7 E:\\wwwroot\\channel-pub-api\\qq\\services\\QqArticleService.php(182): common\\services\\QqArticleService->create(Object(qq\\modules\\v1\\models\\QqArticle), false)",
        "#8 E:\\wwwroot\\channel-pub-api\\qq\\rests\\article\\StandardCreateAction.php(342): qq\\services\\QqArticleService->standardCreate(Object(common\\logics\\Channel), Object(common\\logics\\ChannelType), Array, Array, Object(common\\logics\\ArticleType), Object(common\\logics\\QqArticleType), Object(common\\logics\\QqArticleCategoryNormal), Object(qq\\models\\Task), Object(qq\\modules\\v1\\models\\QqArticle), Object(qq\\models\\QqArticleNormal))",
        "#9 [internal function]: qq\\rests\\article\\StandardCreateAction->run()",
        "#10 E:\\wwwroot\\channel-pub-api\\vendor\\yiisoft\\yii2\\base\\Action.php(94): call_user_func_array(Array, Array)",
        "#11 E:\\wwwroot\\channel-pub-api\\vendor\\yiisoft\\yii2\\base\\Controller.php(157): yii\\base\\Action->runWithParams(Array)",
        "#12 E:\\wwwroot\\channel-pub-api\\vendor\\yiisoft\\yii2\\base\\Module.php(528): yii\\base\\Controller->runAction('standard-create', Array)",
        "#13 E:\\wwwroot\\channel-pub-api\\vendor\\yiisoft\\yii2\\web\\Application.php(103): yii\\base\\Module->runAction('v1/article/stan...', Array)",
        "#14 E:\\wwwroot\\channel-pub-api\\vendor\\yiisoft\\yii2\\base\\Application.php(386): yii\\web\\Application->handleRequest(Object(yii\\web\\Request))",
        "#15 E:\\wwwroot\\channel-pub-api\\qq\\web\\index.php(17): yii\\base\\Application->run()",
        "#16 {main}"
    ],
    "error-info": [
        "22001",
        1406,
        "Data too long for column 'title' at row 1"
    ],
    "previous": {
        "name": "Exception",
        "message": "SQLSTATE[22001]: String data, right truncated: 1406 Data too long for column 'title' at row 1",
        "code": "22001",
        "type": "PDOException",
        "file": "E:\\wwwroot\\channel-pub-api\\vendor\\yiisoft\\yii2\\db\\Command.php",
        "line": 1290,
        "stack-trace": [
            "#0 E:\\wwwroot\\channel-pub-api\\vendor\\yiisoft\\yii2\\db\\Command.php(1290): PDOStatement->execute()",
            "#1 E:\\wwwroot\\channel-pub-api\\vendor\\yiisoft\\yii2\\db\\Command.php(1091): yii\\db\\Command->internalExecute('INSERT INTO `cp...')",
            "#2 E:\\wwwroot\\channel-pub-api\\vendor\\yiisoft\\yii2\\db\\Schema.php(432): yii\\db\\Command->execute()",
            "#3 E:\\wwwroot\\channel-pub-api\\vendor\\yiisoft\\yii2\\db\\ActiveRecord.php(600): yii\\db\\Schema->insert('{{%qq_article}}', Array)",
            "#4 E:\\wwwroot\\channel-pub-api\\vendor\\yiisoft\\yii2\\db\\ActiveRecord.php(566): yii\\db\\ActiveRecord->insertInternal(NULL)",
            "#5 E:\\wwwroot\\channel-pub-api\\vendor\\yiisoft\\yii2\\db\\BaseActiveRecord.php(678): yii\\db\\ActiveRecord->insert(false, NULL)",
            "#6 E:\\wwwroot\\channel-pub-api\\common\\services\\QqArticleService.php(88): yii\\db\\BaseActiveRecord->save(false)",
            "#7 E:\\wwwroot\\channel-pub-api\\qq\\services\\QqArticleService.php(182): common\\services\\QqArticleService->create(Object(qq\\modules\\v1\\models\\QqArticle), false)",
            "#8 E:\\wwwroot\\channel-pub-api\\qq\\rests\\article\\StandardCreateAction.php(342): qq\\services\\QqArticleService->standardCreate(Object(common\\logics\\Channel), Object(common\\logics\\ChannelType), Array, Array, Object(common\\logics\\ArticleType), Object(common\\logics\\QqArticleType), Object(common\\logics\\QqArticleCategoryNormal), Object(qq\\models\\Task), Object(qq\\modules\\v1\\models\\QqArticle), Object(qq\\models\\QqArticleNormal))",
            "#9 [internal function]: qq\\rests\\article\\StandardCreateAction->run()",
            "#10 E:\\wwwroot\\channel-pub-api\\vendor\\yiisoft\\yii2\\base\\Action.php(94): call_user_func_array(Array, Array)",
            "#11 E:\\wwwroot\\channel-pub-api\\vendor\\yiisoft\\yii2\\base\\Controller.php(157): yii\\base\\Action->runWithParams(Array)",
            "#12 E:\\wwwroot\\channel-pub-api\\vendor\\yiisoft\\yii2\\base\\Module.php(528): yii\\base\\Controller->runAction('standard-create', Array)",
            "#13 E:\\wwwroot\\channel-pub-api\\vendor\\yiisoft\\yii2\\web\\Application.php(103): yii\\base\\Module->runAction('v1/article/stan...', Array)",
            "#14 E:\\wwwroot\\channel-pub-api\\vendor\\yiisoft\\yii2\\base\\Application.php(386): yii\\web\\Application->handleRequest(Object(yii\\web\\Request))",
            "#15 E:\\wwwroot\\channel-pub-api\\qq\\web\\index.php(17): yii\\base\\Application->run()",
            "#16 {main}"
        ]
    }
}
Begin transaction

INSERT INTO `cpa_task` (`group_id`, `source`, `source_uuid`, `source_pub_user_id`, `source_callback_url`, `channel_id`, `channel_code`, `channel_type_id`, `channel_type_code`, `status`, `created_at`, `updated_at`) VALUES ('015ce30b116ce86058fa6ab4fea4ac63', 'spider', '825e6d5e36468cc4bf536799ce3565cf', '3', 'http://scms.wjdev.chinamcloud.cn/api/thirdPush/callBack', 1, 'qq', 1, 'qq_cw', 1, 1577692119, 1577692119)

INSERT INTO `cpa_channel_app_task` (`channel_id`, `channel_code`, `channel_type_id`, `channel_type_code`, `channel_app_source_id`, `channel_app_source_uuid`, `task_id`, `have_pub_number`, `status`, `created_at`, `updated_at`, `uuid`) VALUES (1, 'qq', 1, 'qq_cw', 14, '8d72b7cc2ac911eab85a54ee75d2ebc1', 4, 3, 1, 1577692119, 1577692119, 'caf880d62ad811ea88f654ee75d2ebc1')

INSERT INTO `cpa_qq_cw_app_task` (`channel_app_task_id`, `channel_app_task_uuid`, `qq_cw_app_id`, `task_id`, `platform_status`, `status`, `created_at`, `updated_at`) VALUES (4, 'caf880d62ad811ea88f654ee75d2ebc1', 2, 4, 0, 1, 1577692119, 1577692119)

INSERT INTO `cpa_qq_article` (`group_id`, `article_category_id`, `title`, `author`, `source_article_id`, `qq_app_type`, `article_type_id`, `qq_article_type_id`, `qq_article_category_id`, `task_id`, `status`, `created_at`, `updated_at`) VALUES ('015ce30b116ce86058fa6ab4fea4ac63', 15, '未来 10 年,“星光中国芯工程”计划投资 100 亿元用于芯片研发及大规模产业化。北京时间 2019 年 12 月 28 日,1999-2019“星光中国芯工程”创新成果与展望报告会在人民大会堂举行。会议回顾和总结了“星光中国芯工程”20 年来在核心技术自主创新、在研发成果大规模产业化、以及在满足国家重大工程技术需求方面取得的重要进展和成功经验,并对“星光中国芯工程”未来发展进行了规划和展望。中国工程院院士、“星光中国芯工程”总指挥、中星微电子集团创建人兼首席科学家邓中翰作了“星光中国芯工程”20 年成果与展望工作报告,报告中,邓中翰表示,在物理层面智能芯片的发展已经受到了物理规律的限制,看似已经接近了极限的时候,在信息层面的技术创新还远远没有碰到天花板。1999 年,邓中翰等一批海外爱国博士企业家回国承担并启动实施“星光中国芯工程”,在北京中关村设立中星微电子公司,致力于超大规模集成电路芯片的研发、设计及产业化工作。2001 年,中国第一颗百万门级超大规模数字多媒体芯片“星光一号”诞生。其后数年间,“星光多媒体”系列芯片被苹果、三星、飞利浦、惠普、LG、索尼、戴尔等国外知名品牌规模采用,占领了全球计算机图像输入芯片 60% 以上的市场份额,彻底结束了中国无“芯”的历史。', '刘燕', 1, 'cw', 1, 1, 18, 4, 1, 1577692119, 1577692119)

Roll back transaction

7、实施新的数据库迁移,新建表:预发布日志(pre_pub_log),最终的表结构,表中的字段类型与任务表中的字段类型保持一致,但是字符串类型的字段,加大其长度,尽可能提升其入库成功率,基于接口动作方法的返回码,当返回码不等于 10000 时,插入记录,如图7

图7

8、实现预发布日志服务,创建预发布日志,尽最大可能避免抛出异常,一旦抛出异常,则无法插入记录至预发布日志。common/services/PrePubLogService.php

<?php
/**
 * Created by PhpStorm.
 * User: Qiang Wang
 * Date: 2020/01/03
 * Time: 17:51
 */
namespace common\services;

use Yii;
use common\logics\ArticleType;
use common\logics\Channel;
use common\logics\ChannelAppSource;
use common\logics\PrePubLog;
use common\behaviors\UUIDBehavior;
use yii\db\Exception;
use yii\helpers\ArrayHelper;

class PrePubLogService extends Service
{
    /**
     * 批量创建预发布日志
     * @param string $groupId 租户ID
     * 格式如下:015ce30b116ce86058fa6ab4fea4ac63
     *
     * @param int $code 返回码
     * 格式如下:226004
     *
     * @param string $message 说明
     * 格式如下:数据验证失败:文章封面图不能为空。
     *
     * @param string $channelCode 渠道代码
     * 格式如下:qq
     *
     * @param string $type 任务类型,preview:预览;pub:发布;revoke:撤回
     * 格式如下:pub
     *
     * @param string $articleTypeCode 文章类型代码
     * 格式如下:standard
     *
     * @param array $requestParams 请求参数
     * 格式如下:
     * [
     *     'channel_app_source_uuids' => [
     *         '18cf06d22ac911eaa31854ee75d2ebc0',
     *         '8d72b7cc2ac911eab85a54ee75d2ebc1',
     *     ],
     *     'source' => 'spider',
     *     'source_uuid' => '825e6d5e36468cc4bf536799ce3565cf',
     *     'source_pub_user_id' => 3,
     *     'source_callback_url' => 'http://scms.wjdev.chinamcloud.cn/api/thirdPush/callBack',
     *     'article_category_id' => 15,
     *     'title' => '未来 10 年,“星光中国芯工程”计划投资 100 亿元用于芯片研发及大规模产业化',
     *     'author' => '刘燕',
     *     'source_article_id' => 1,
     *     ...
     * ]
     *
     * @return int number of rows affected by the execution.
     * @throws Exception execution failed
     */    public function createMultiple($groupId, $code, $message, $channelCode, $type, $articleTypeCode, $requestParams)
    {
        // 基于代码查找单个资源(渠道)
        $channelItem = Channel::findOneByCode($channelCode);
        if (isset($channelItem)) {
            $channelId = $channelItem->id;
        } else {
            $channelId = 0;
        }

        // 基于代码查找单个资源(文章类型)
        $articleTypeItem = ArticleType::findOneByCode($articleTypeCode);
        if (isset($articleTypeItem)) {
            $articleTypeId = $articleTypeItem->id;
            $articleTypeName = $articleTypeItem->name;
        } else {
            $articleTypeId = 0;
            $articleTypeName = '';
        }

        // 渠道的应用的来源ID(UUID)
        $channelAppSourceUuids = $requestParams['channel_app_source_uuids'] ?? []; // 渠道的应用的来源ID(UUID)
        if (!is_array($channelAppSourceUuids)) {
            $channelAppSourceUuids = [$channelAppSourceUuids];
        }

        $time = time();

        // 公共字段列表
        $prePubLogCommonFields = [
            'group_id' => $groupId,
            'channel_id' => $channelId,
            'channel_code' => $channelCode,
            'type' => $type,
            'article_type_id' => $articleTypeId,
            'article_type_code' => $articleTypeCode,
            'article_type_name' => $articleTypeName,
            'article_category_id' => $requestParams['article_category_id'] ?? 0,
            'article_title' => $requestParams['title'] ?? '',
            'article_author' => $requestParams['author'] ?? '',
            'source' => $requestParams['source'] ?? '',
            'source_uuid' => $requestParams['source_uuid'] ?? '',
            'source_pub_user_id' => $requestParams['source_pub_user_id'] ?? 0,
            'source_callback_url' => $requestParams['source_callback_url'] ?? '',
            'source_article_id' => $requestParams['source_article_id'] ?? '',
            'pub_log_code' => $code,
            'pub_log_message' => $message,
            'pub_log_body' => serialize($requestParams),
            'status' => PrePubLog::STATUS_PUBLISH_ERROR,
            'is_deleted' => PrePubLog::IS_DELETED_NO,
            'created_at' => $time,
            'updated_at' => $time,
            'deleted_at' => PrePubLog::DELETED_AT_DEFAULT,
        ];
        foreach ($channelAppSourceUuids as $channelAppSourceUuid) {

            $uuidBehavior = new UUIDBehavior();
            $uuid = $uuidBehavior->createUUID(); // 预发布日志ID(UUID)

            // 基于UUID查找单个资源
            $channelAppSourceItem = ChannelAppSource::findOneByUuid($channelAppSourceUuid);
            if (isset($channelAppSourceItem)) {
                $prePubLogRows[] = ArrayHelper::merge($prePubLogCommonFields, [
                    'uuid' => $uuid,
                    'channel_type_id' => $channelAppSourceItem->channel_type_id,
                    'channel_type_code' => $channelAppSourceItem->channel_type_code,
                    'channel_app_source_id' => $channelAppSourceItem->id,
                    'channel_app_source_uuid' => $channelAppSourceItem->uuid,
                ]);
            } else {
                $prePubLogRows[] = ArrayHelper::merge($prePubLogCommonFields, [
                    'uuid' => $uuid,
                    'channel_type_id' => 0,
                    'channel_type_code' => '',
                    'channel_app_source_id' => 0,
                    'channel_app_source_uuid' => $channelAppSourceUuid,
                ]);
            }
        }

        /* 创建MySQL模型(预发布日志) */        $prePubLogTable = PrePubLog::tableName();
        if (isset($prePubLogRows)) {
            $prePubLogColumns = array_keys($prePubLogRows[0]);
            return Yii::$app->db->createCommand()->batchInsert($prePubLogTable, $prePubLogColumns, $prePubLogRows)->execute();
        }
        return 0;
    }
}

9、创建预发布日志过滤器:qq/filters/PrePubLogFilter.php,继承 yii\base\ActionFilter 类并覆盖 afterAction() 方法来创建动作的过滤器,在动作执行之后执行。判断返回码,如果不等于 10000,则操作数据(批量创建预发布日志)。

<?php
/**
 * Created by PhpStorm.
 * User: Qiang Wang
 * Date: 2019/12/31
 * Time: 17:25
 */
namespace qq\filters;

use Yii;
use qq\models\ArticleType;
use qq\models\Channel;
use qq\models\Task;
use qq\services\PrePubLogService;
use yii\base\ActionFilter;
use yii\base\InvalidConfigException;
use yii\db\Exception;

/**
 * 预发布日志过滤器
 * @package qq\filters
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since 1.0
 */class PrePubLogFilter extends ActionFilter
{
    /**
     * {@inheritdoc}
     * @throws InvalidConfigException if a registered parser does not implement the [[RequestParserInterface]].
     * @throws Exception execution failed
     */    public function afterAction($action, $result)
    {
        if ($result['code'] !== 10000) {

            $groupId = Yii::$app->params['groupId']; // 租户ID
            $channelCode = Channel::CODE_QQ; // 渠道代码
            $type = Task::TYPE_PUB; // 任务类型,preview:预览;pub:发布;revoke:撤回

            $articleTypeCode = ArticleType::CODE_STANDARD; // 文章类型代码
            if ($action->id == 'video-create') {
                $articleTypeCode = ArticleType::CODE_VIDEO;
            } elseif ($action->id == 'images-create') {
                $articleTypeCode = ArticleType::CODE_IMAGES;
            }

            // 返回所有请求参数
            $requestParams = Yii::$app->getRequest()->getBodyParams();

            /* 操作数据(批量创建预发布日志) */            $prePubLogService = new PrePubLogService();
            $prePubLogService->createMultiple($groupId, $result['code'], $result['message'], $channelCode, $type, $articleTypeCode, $requestParams);
        }

        return parent::afterAction($action, $result);
    }
}

10、使用过滤器,过滤器本质上是一类特殊的行为, 所以使用过滤器和使用行为一样。 可以在控制器类中覆盖它的 behaviors() 方法来声明过滤器,编辑控制器:qq/controllers/ArticleController.php,配置 only 属性明确指定控制器应用到哪些动作。

<?php
/**
 * Created by PhpStorm.
 * User: Qiang Wang
 * Date: 2018/08/31
 * Time: 10:43
 */
namespace qq\controllers;

use qq\filters\PrePubLogFilter;
use yii\rest\ActiveController;

/**
 * Class ArticleController
 * @package qq\controllers
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since 1.0
 */class ArticleController extends ActiveController
{
    public $serializer = [
        'class' => 'qq\rests\article\Serializer',
        'collectionEnvelope' => 'items',
    ];

    /**
     * {@inheritdoc}
     */    public function behaviors()
    {
        $behaviors = parent::behaviors();
        $behaviors['prePubLogFilter'] = [
            'class' => PrePubLogFilter::className(),
            'only' => ['standard-create', 'video-create', 'images-create'],
        ];
        return $behaviors;
    }

    /**
     * @inheritdoc
     */    public function actions()
    {
        $actions = parent::actions();
        // 禁用"create"、"update"、"delete"、"options"动作
        unset($actions['create'], $actions['update'], $actions['delete'], $actions['options']);
        $actions['index']['class'] = 'qq\rests\article\IndexAction';
        $actions['view']['class'] = 'qq\rests\article\ViewAction';
        $actions['standard-create'] = [
            'class' => 'qq\rests\article\StandardCreateAction',
            'modelClass' => $this->modelClass,
            'checkAccess' => [$this, 'checkAccess'],
        ];
        $actions['video-create'] = [
            'class' => 'qq\rests\article\VideoCreateAction',
            'modelClass' => $this->modelClass,
            'checkAccess' => [$this, 'checkAccess'],
        ];
        $actions['images-create'] = [
            'class' => 'qq\rests\article\ImagesCreateAction',
            'modelClass' => $this->modelClass,
            'checkAccess' => [$this, 'checkAccess'],
        ];
        return $actions;
    }
}

11、发布文章类型:标准(普通、图文)的文章,发布失败(即未入库,因为数据验证失败),此时,当返回码不等于 10000 时,插入 2 条记录(预发布日志),因为有 2 个帐号,查看 SQL

{
    "code": 226004,
    "message": "数据验证失败:标题只能包含至多255个字符。"
}
INSERT INTO `cpa_pre_pub_log` (`group_id`, `channel_id`, `channel_code`, `type`, `article_type_id`, `article_type_code`, `article_type_name`, `article_category_id`, `article_title`, `article_author`, `source`, `source_uuid`, `source_pub_user_id`, `source_callback_url`, `source_article_id`, `pub_log_code`, `pub_log_message`, `pub_log_body`, `status`, `is_deleted`, `created_at`, `updated_at`, `deleted_at`, `uuid`, `channel_type_id`, `channel_type_code`, `channel_app_source_id`, `channel_app_source_uuid`) VALUES ('015ce30b116ce86058fa6ab4fea4ac63', 1, 'qq', 'pub', 1, 'standard', '标准(普通)', 15, '未来 10 年,“星光中国芯工程”计划投资 100 亿元用于芯片研发及大规模产业化。北京时间 2019 年 12 月 28 日,1999-2019“星光中国芯工程”创新成果与展望报告会在人民大会堂举行。会议回顾和总结了“星光中国芯工程”20 年来在核心技术自主创新、在研发成果大规模产业化、以及在满足国家重大工程技术需求方面取得的重要进展和成功经验,并对“星光中国芯工程”未来发展进行了规划和展望。中国工程院院士、“星光中国芯工程”总指挥、中星微电子集团创建人兼首席科学家邓中翰作了“星光中国芯工程”20 年成果与展望工作报告,报告中,邓中翰表示,在物理层面智能芯片的发展已经受到了物理规律的限制,看似已经接近了极限的时候,在信息层面的技术创新还远远没有碰到天花板。1999 年,邓中翰等一批海外爱国博士企业家回国承担并启动实施“星光中国芯工程”,在北京中关村设立中星微电子公司,致力于超大规模集成电路芯片的研发、设计及产业化工作。2001 年,中国第一颗百万门级超大规模数字多媒体芯片“星光一号”诞生。其后数年间,“星光多媒体”系列芯片被苹果、三星、飞利浦、惠普、LG、索尼、戴尔等国外知名品牌规模采用,占领了全球计算机图像输入芯片 60% 以上的市场份额,彻底结束了中国无“芯”的历史。', '高琳', 'spider', '825e6d5e36468cc4bf536799ce3565cf', 3, 'http://scms.wjdev.chinamcloud.cn/api/thirdPush/callBack', 1, 226004, '数据验证失败:标题只能包含至多255个字符。', 'a:17:{s:24:\"channel_app_source_uuids\";a:2:{i:0;s:32:\"8d72b7cc2ac911eab85a54ee75d2ebc1\";i:1;s:32:\"18cf06d22ac911eaa31854ee75d2ebc1\";}s:6:\"source\";s:6:\"spider\";s:11:\"source_uuid\";s:32:\"825e6d5e36468cc4bf536799ce3565cf\";s:18:\"source_pub_user_id\";i:3;s:19:\"source_callback_url\";s:55:\"http://scms.wjdev.chinamcloud.cn/api/thirdPush/callBack\";s:19:\"article_category_id\";i:15;s:5:\"title\";s:1525:\"未来 10 年,“星光中国芯工程”计划投资 100 亿元用于芯片研发及大规模产业化。北京时间 2019 年 12 月 28 日,1999-2019“星光中国芯工程”创新成果与展望报告会在人民大会堂举行。会议回顾和总结了“星光中国芯工程”20 年来在核心技术自主创新、在研发成果大规模产业化、以及在满足国家重大工程技术需求方面取得的重要进展和成功经验,并对“星光中国芯工程”未来发展进行了规划和展望。中国工程院院士、“星光中国芯工程”总指挥、中星微电子集团创建人兼首席科学家邓中翰作了“星光中国芯工程”20 年成果与展望工作报告,报告中,邓中翰表示,在物理层面智能芯片的发展已经受到了物理规律的限制,看似已经接近了极限的时候,在信息层面的技术创新还远远没有碰到天花板。1999 年,邓中翰等一批海外爱国博士企业家回国承担并启动实施“星光中国芯工程”,在北京中关村设立中星微电子公司,致力于超大规模集成电路芯片的研发、设计及产业化工作。2001 年,中国第一颗百万门级超大规模数字多媒体芯片“星光一号”诞生。其后数年间,“星光多媒体”系列芯片被苹果、三星、飞利浦、惠普、LG、索尼、戴尔等国外知名品牌规模采用,占领了全球计算机图像输入芯片 60% 以上的市场份额,彻底结束了中国无“芯”的历史。\";s:6:\"author\";s:6:\"高琳\";s:17:\"source_article_id\";i:1;s:7:\"content\";s:1525:\"未来 10 年,“星光中国芯工程”计划投资 100 亿元用于芯片研发及大规模产业化。北京时间 2019 年 12 月 28 日,1999-2019“星光中国芯工程”创新成果与展望报告会在人民大会堂举行。会议回顾和总结了“星光中国芯工程”20 年来在核心技术自主创新、在研发成果大规模产业化、以及在满足国家重大工程技术需求方面取得的重要进展和成功经验,并对“星光中国芯工程”未来发展进行了规划和展望。中国工程院院士、“星光中国芯工程”总指挥、中星微电子集团创建人兼首席科学家邓中翰作了“星光中国芯工程”20 年成果与展望工作报告,报告中,邓中翰表示,在物理层面智能芯片的发展已经受到了物理规律的限制,看似已经接近了极限的时候,在信息层面的技术创新还远远没有碰到天花板。1999 年,邓中翰等一批海外爱国博士企业家回国承担并启动实施“星光中国芯工程”,在北京中关村设立中星微电子公司,致力于超大规模集成电路芯片的研发、设计及产业化工作。2001 年,中国第一颗百万门级超大规模数字多媒体芯片“星光一号”诞生。其后数年间,“星光多媒体”系列芯片被苹果、三星、飞利浦、惠普、LG、索尼、戴尔等国外知名品牌规模采用,占领了全球计算机图像输入芯片 60% 以上的市场份额,彻底结束了中国无“芯”的历史。\";s:10:\"cover_pics\";a:1:{i:0;s:84:\"https://static001.infoq.cn/resource/image/7c/18/7c55c468adf1d616f48681c23e9b9518.png\";}s:10:\"cover_type\";i:1;s:3:\"tag\";s:62:\"百度,发布,推理引擎,Paddle Lite,华为,NPU,在线编译\";s:5:\"apply\";i:0;s:17:\"original_platform\";i:0;s:12:\"original_url\";s:0:\"\";s:15:\"original_author\";s:0:\"\";}', 3, 0, 1578361765, 1578361765, 0, 'effddba230ef11ea991a54ee75d2ebc1', 1, 'qq_cw', 14, '8d72b7cc2ac911eab85a54ee75d2ebc1'), ('015ce30b116ce86058fa6ab4fea4ac63', 1, 'qq', 'pub', 1, 'standard', '标准(普通)', 15, '未来 10 年,“星光中国芯工程”计划投资 100 亿元用于芯片研发及大规模产业化。北京时间 2019 年 12 月 28 日,1999-2019“星光中国芯工程”创新成果与展望报告会在人民大会堂举行。会议回顾和总结了“星光中国芯工程”20 年来在核心技术自主创新、在研发成果大规模产业化、以及在满足国家重大工程技术需求方面取得的重要进展和成功经验,并对“星光中国芯工程”未来发展进行了规划和展望。中国工程院院士、“星光中国芯工程”总指挥、中星微电子集团创建人兼首席科学家邓中翰作了“星光中国芯工程”20 年成果与展望工作报告,报告中,邓中翰表示,在物理层面智能芯片的发展已经受到了物理规律的限制,看似已经接近了极限的时候,在信息层面的技术创新还远远没有碰到天花板。1999 年,邓中翰等一批海外爱国博士企业家回国承担并启动实施“星光中国芯工程”,在北京中关村设立中星微电子公司,致力于超大规模集成电路芯片的研发、设计及产业化工作。2001 年,中国第一颗百万门级超大规模数字多媒体芯片“星光一号”诞生。其后数年间,“星光多媒体”系列芯片被苹果、三星、飞利浦、惠普、LG、索尼、戴尔等国外知名品牌规模采用,占领了全球计算机图像输入芯片 60% 以上的市场份额,彻底结束了中国无“芯”的历史。', '高琳', 'spider', '825e6d5e36468cc4bf536799ce3565cf', 3, 'http://scms.wjdev.chinamcloud.cn/api/thirdPush/callBack', 1, 226004, '数据验证失败:标题只能包含至多255个字符。', 'a:17:{s:24:\"channel_app_source_uuids\";a:2:{i:0;s:32:\"8d72b7cc2ac911eab85a54ee75d2ebc1\";i:1;s:32:\"18cf06d22ac911eaa31854ee75d2ebc1\";}s:6:\"source\";s:6:\"spider\";s:11:\"source_uuid\";s:32:\"825e6d5e36468cc4bf536799ce3565cf\";s:18:\"source_pub_user_id\";i:3;s:19:\"source_callback_url\";s:55:\"http://scms.wjdev.chinamcloud.cn/api/thirdPush/callBack\";s:19:\"article_category_id\";i:15;s:5:\"title\";s:1525:\"未来 10 年,“星光中国芯工程”计划投资 100 亿元用于芯片研发及大规模产业化。北京时间 2019 年 12 月 28 日,1999-2019“星光中国芯工程”创新成果与展望报告会在人民大会堂举行。会议回顾和总结了“星光中国芯工程”20 年来在核心技术自主创新、在研发成果大规模产业化、以及在满足国家重大工程技术需求方面取得的重要进展和成功经验,并对“星光中国芯工程”未来发展进行了规划和展望。中国工程院院士、“星光中国芯工程”总指挥、中星微电子集团创建人兼首席科学家邓中翰作了“星光中国芯工程”20 年成果与展望工作报告,报告中,邓中翰表示,在物理层面智能芯片的发展已经受到了物理规律的限制,看似已经接近了极限的时候,在信息层面的技术创新还远远没有碰到天花板。1999 年,邓中翰等一批海外爱国博士企业家回国承担并启动实施“星光中国芯工程”,在北京中关村设立中星微电子公司,致力于超大规模集成电路芯片的研发、设计及产业化工作。2001 年,中国第一颗百万门级超大规模数字多媒体芯片“星光一号”诞生。其后数年间,“星光多媒体”系列芯片被苹果、三星、飞利浦、惠普、LG、索尼、戴尔等国外知名品牌规模采用,占领了全球计算机图像输入芯片 60% 以上的市场份额,彻底结束了中国无“芯”的历史。\";s:6:\"author\";s:6:\"高琳\";s:17:\"source_article_id\";i:1;s:7:\"content\";s:1525:\"未来 10 年,“星光中国芯工程”计划投资 100 亿元用于芯片研发及大规模产业化。北京时间 2019 年 12 月 28 日,1999-2019“星光中国芯工程”创新成果与展望报告会在人民大会堂举行。会议回顾和总结了“星光中国芯工程”20 年来在核心技术自主创新、在研发成果大规模产业化、以及在满足国家重大工程技术需求方面取得的重要进展和成功经验,并对“星光中国芯工程”未来发展进行了规划和展望。中国工程院院士、“星光中国芯工程”总指挥、中星微电子集团创建人兼首席科学家邓中翰作了“星光中国芯工程”20 年成果与展望工作报告,报告中,邓中翰表示,在物理层面智能芯片的发展已经受到了物理规律的限制,看似已经接近了极限的时候,在信息层面的技术创新还远远没有碰到天花板。1999 年,邓中翰等一批海外爱国博士企业家回国承担并启动实施“星光中国芯工程”,在北京中关村设立中星微电子公司,致力于超大规模集成电路芯片的研发、设计及产业化工作。2001 年,中国第一颗百万门级超大规模数字多媒体芯片“星光一号”诞生。其后数年间,“星光多媒体”系列芯片被苹果、三星、飞利浦、惠普、LG、索尼、戴尔等国外知名品牌规模采用,占领了全球计算机图像输入芯片 60% 以上的市场份额,彻底结束了中国无“芯”的历史。\";s:10:\"cover_pics\";a:1:{i:0;s:84:\"https://static001.infoq.cn/resource/image/7c/18/7c55c468adf1d616f48681c23e9b9518.png\";}s:10:\"cover_type\";i:1;s:3:\"tag\";s:62:\"百度,发布,推理引擎,Paddle Lite,华为,NPU,在线编译\";s:5:\"apply\";i:0;s:17:\"original_platform\";i:0;s:12:\"original_url\";s:0:\"\";s:15:\"original_author\";s:0:\"\";}', 3, 0, 1578361765, 1578361765, 0, 'effe253a30ef11eaa2e454ee75d2ebc1', 1, 'qq_cw', 13, '18cf06d22ac911eaa31854ee75d2ebc1')

12、查看 预发布日志 表,存在 2 条记录,符合预期,如图8

图8

13、获取企鹅号的应用的任务列表,调整查询规则,基于 UNION ALL 语法,合并 预发布日志 的结果到一个结果集中。编辑 qq/rests/qq_app_task/IndexAction.php

<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */
namespace qq\rests\qq_app_task;

use Yii;
use qq\models\ArticleType;
use qq\models\ChannelAppTask;
use qq\models\PrePubLog;
use qq\models\PubLog;
use qq\models\QqArticle;
use qq\models\QqCwApp;
use qq\models\QqCwAppTask;
use qq\models\QqTransaction;
use qq\models\Task;
use yii\data\ActiveDataProvider;
use yii\db\Expression;
use yii\db\Query;
use yii\base\InvalidConfigException;
use yii\web\UnprocessableEntityHttpException;

/**
 * 获取企鹅号的应用的任务列表:/qq-app-tasks(qq-app-task/index)
 *
 * 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' => 'qq\models\QqAppTaskSearch',
        'attributeMap' => [
            'group_id' => '{{%task}}.[[group_id]]',
            'channel_type_code' => '{{%channel_app_task}}.[[channel_type_code]]',
            'source' => '{{%task}}.[[source]]',
            'source_uuid' => '{{%task}}.[[source_uuid]]',
            'source_pub_user_id' => '{{%task}}.[[source_pub_user_id]]',
            'channel_app_source_uuid' => '{{%channel_app_task}}.[[channel_app_source_uuid]]',
            'penguin_name' => '{{%qq_cw_app}}.[[penguin_name]]',
            'status' => '{{%channel_app_task}}.[[status]]',
            'article_type_code' => '{{%article_type}}.[[code]]',
            'article_category_id' => '{{%qq_article}}.[[article_category_id]]',
            'article_title' => '{{%qq_article}}.[[title]]',
            'article_author' => '{{%qq_article}}.[[author]]',
            'source_article_id' => '{{%qq_article}}.[[source_article_id]]',
            'created_at' => '{{%channel_app_task}}.[[created_at]]',
        ],
    ];

    public $prePubLogDataFilter = [
        'class' => 'yii\data\ActiveDataFilter',
        'searchModel' => 'qq\models\QqAppTaskSearch',
        'attributeMap' => [
            'group_id' => '{{%pre_pub_log}}.[[group_id]]',
            'channel_type_code' => '{{%pre_pub_log}}.[[channel_type_code]]',
            'source' => '{{%pre_pub_log}}.[[source]]',
            'source_uuid' => '{{%pre_pub_log}}.[[source_uuid]]',
            'source_pub_user_id' => '{{%pre_pub_log}}.[[source_pub_user_id]]',
            'channel_app_source_uuid' => '{{%pre_pub_log}}.[[channel_app_source_uuid]]',
            'penguin_name' => '{{%qq_cw_app}}.[[penguin_name]]',
            'status' => '{{%pre_pub_log}}.[[status]]',
            'article_type_code' => '{{%pre_pub_log}}.[[article_type_code]]',
            'article_category_id' => '{{%pre_pub_log}}.[[article_category_id]]',
            'article_title' => '{{%pre_pub_log}}.[[article_title]]',
            'article_author' => '{{%pre_pub_log}}.[[article_author]]',
            'source_article_id' => '{{%pre_pub_log}}.[[source_article_id]]',
            'created_at' => '{{%pre_pub_log}}.[[created_at]]',
        ],
    ];

    /**
     * Prepares the data provider that should return the requested collection of the models.
     * @return ActiveDataProvider
     * @throws InvalidConfigException if the configuration is invalid.
     * @throws UnprocessableEntityHttpException
     */    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) {
                    $firstError = '';
                    foreach ($this->dataFilter->getFirstErrors() as $message) {
                        $firstError = $message;
                        break;
                    }
                    throw new UnprocessableEntityHttpException(Yii::t('error', Yii::t('error', Yii::t('error', '224003'), ['first_error' => $firstError])), 224003);
                }
            }
        }

        $prePubLogFilter = null;
        if ($this->prePubLogDataFilter !== null) {
            $this->prePubLogDataFilter = Yii::createObject($this->prePubLogDataFilter);
            if ($this->prePubLogDataFilter->load($requestParams)) {
                $prePubLogFilter = $this->prePubLogDataFilter->build();
                if ($prePubLogFilter === false) {
                    $firstError = '';
                    foreach ($this->prePubLogDataFilter->getFirstErrors() as $message) {
                        $firstError = $message;
                        break;
                    }
                    throw new UnprocessableEntityHttpException(Yii::t('error', Yii::t('error', Yii::t('error', '224003'), ['first_error' => $firstError])), 224003);
                }
            }
        }

        if ($this->prepareDataProvider !== null) {
            return call_user_func($this->prepareDataProvider, $this, $filter);
        }

        /* @var $modelClass ChannelAppTask */        $modelClass = $this->modelClass;

        $channelAppTaskQuery = $modelClass::find()
            ->select([
                new Expression("'channel_app_task'" . " AS 'model'"),
                ChannelAppTask::tableName() . '.id',
                Task::tableName() . '.group_id',
                ChannelAppTask::tableName() . '.uuid',
                ChannelAppTask::tableName() . '.channel_id',
                ChannelAppTask::tableName() . '.channel_code',
                ChannelAppTask::tableName() . '.channel_type_id',
                ChannelAppTask::tableName() . '.channel_type_code',
                ChannelAppTask::tableName() . '.channel_app_source_id',
                ChannelAppTask::tableName() . '.channel_app_source_uuid',
                Task::tableName() . '.type',
                'article_type_id' => ArticleType::tableName() . '.id',
                'article_type_code' => ArticleType::tableName() . '.code',
                'article_type_name' => ArticleType::tableName() . '.name',
                QqArticle::tableName() . '.article_category_id',
                'article_title' => QqArticle::tableName() . '.title',
                'article_author' => QqArticle::tableName() . '.author',
                Task::tableName() . '.source',
                Task::tableName() . '.source_uuid',
                Task::tableName() . '.source_pub_user_id',
                Task::tableName() . '.source_callback_url',
                QqArticle::tableName() . '.source_article_id',
                'pub_log_code' => PubLog::tableName() . '.code',
                'pub_log_message' => PubLog::tableName() . '.message',
                ChannelAppTask::tableName() . '.status',
                ChannelAppTask::tableName() . '.is_deleted',
                ChannelAppTask::tableName() . '.created_at',
                ChannelAppTask::tableName() . '.updated_at',
                ChannelAppTask::tableName() . '.deleted_at',
                ChannelAppTask::tableName() . '.task_id',
                ChannelAppTask::tableName() . '.have_pub_number',
                QqCwApp::tableName() . '.penguin_name',
                QqTransaction::tableName() . '.article_url',
            ])
            ->joinWith(['task.qqArticle.articleType'], false)
            ->joinWith(['qqCwAppTask.qqCwApp'], false)
            ->joinWith(['qqCwAppTask'], false)
            ->leftJoin(QqTransaction::tableName(), QqCwAppTask::tableName() . '.[[id]] = ' . QqTransaction::tableName() . '.[[qq_app_task_id]] AND ' . QqTransaction::tableName() . '.[[type]] = \'' . QqTransaction::TYPE_ARTICLE . '\'')
            ->joinWith(['pubLog'], false)
            ->where([
                ChannelAppTask::tableName() . '.is_deleted' => ChannelAppTask::IS_DELETED_NO,
                Task::tableName() . '.is_deleted' => Task::IS_DELETED_NO,
                QqArticle::tableName() . '.is_deleted' => QqArticle::IS_DELETED_NO,
                ArticleType::tableName() . '.is_deleted' => ArticleType::IS_DELETED_NO,
                QqCwAppTask::tableName() . '.is_deleted' => QqCwAppTask::IS_DELETED_NO,
                QqCwApp::tableName() . '.is_deleted' => QqCwApp::IS_DELETED_NO,
            ]);
        if (!empty($filter)) {
            $channelAppTaskQuery->andFilterWhere($filter);
        }

        $prePubLogQuery = PrePubLog::find()
            ->select([
                new Expression("'pre_pub_log'" . " AS 'model'"),
                PrePubLog::tableName() . '.id',
                PrePubLog::tableName() . '.group_id',
                PrePubLog::tableName() . '.uuid',
                PrePubLog::tableName() . '.channel_id',
                PrePubLog::tableName() . '.channel_code',
                PrePubLog::tableName() . '.channel_type_id',
                PrePubLog::tableName() . '.channel_type_code',
                PrePubLog::tableName() . '.channel_app_source_id',
                PrePubLog::tableName() . '.channel_app_source_uuid',
                PrePubLog::tableName() . '.type',
                PrePubLog::tableName() . '.article_type_id',
                PrePubLog::tableName() . '.article_type_code',
                PrePubLog::tableName() . '.article_type_name',
                PrePubLog::tableName() . '.article_category_id',
                PrePubLog::tableName() . '.article_title',
                PrePubLog::tableName() . '.article_author',
                PrePubLog::tableName() . '.source',
                PrePubLog::tableName() . '.source_uuid',
                PrePubLog::tableName() . '.source_pub_user_id',
                PrePubLog::tableName() . '.source_callback_url',
                PrePubLog::tableName() . '.source_article_id',
                PrePubLog::tableName() . '.pub_log_code',
                PrePubLog::tableName() . '.pub_log_message',
                PrePubLog::tableName() . '.status',
                PrePubLog::tableName() . '.is_deleted',
                PrePubLog::tableName() . '.created_at',
                PrePubLog::tableName() . '.updated_at',
                PrePubLog::tableName() . '.deleted_at',
                new Expression(0 . " AS 'task_id'"),
                new Expression(ChannelAppTask::HAVE_PUB_NUMBER_DEFAULT . " AS 'have_pub_number'"),
                QqCwApp::tableName() . '.penguin_name',
                new Expression("''" . " AS 'article_url'"),
            ])
            ->joinWith(['qqCwApp'], false)
            ->where([
                PrePubLog::tableName() . '.is_deleted' => ChannelAppTask::IS_DELETED_NO,
                QqCwApp::tableName() . '.is_deleted' => QqCwApp::IS_DELETED_NO,
            ]);
        if (!empty($prePubLogFilter)) {
            $prePubLogQuery->andFilterWhere($prePubLogFilter);
        }

        $query = (new Query())
            ->from(['model' => $channelAppTaskQuery->union($prePubLogQuery, true)])
            ->orderBy(['created_at' => SORT_DESC, 'id' => SORT_DESC]);

        return Yii::createObject([
            'class' => ActiveDataProvider::className(),
            'query' => $query,
            'pagination' => [
                'params' => $requestParams,
            ],
            'sort' => [
                'params' => $requestParams,
            ],
        ]);
    }
}

14、获取企鹅号的应用的任务列表,列表中存在 2 条记录,查看获取企鹅号的应用的任务列表的请求参数、响应参数、 SQL 语句,符合预期,如图9

图9

group_id:015ce30b116ce86058fa6ab4fea4ac63
filter[group_id]:015ce30b116ce86058fa6ab4fea4ac63
filter[channel_type_code]:qq_cw
filter[penguin_name][like]:华栖云秀
{
    "code": 10000,
    "message": "获取企鹅号的应用的任务列表成功",
    "data": {
        "items": [
            {
                "model": "pre_pub_log",
                "id": 4,
                "group_id": "015ce30b116ce86058fa6ab4fea4ac63",
                "uuid": "effddba230ef11ea991a54ee75d2ebc1",
                "channel_id": 1,
                "channel_code": "qq",
                "channel_type_id": 1,
                "channel_type_code": "qq_cw",
                "channel_app_source_id": 14,
                "channel_app_source_uuid": "8d72b7cc2ac911eab85a54ee75d2ebc1",
                "type": "pub",
                "article_type_id": "1",
                "article_type_code": "standard",
                "article_type_name": "标准(普通)",
                "article_category_id": 15,
                "article_title": "未来 10 年,“星光中国芯工程”计划投资 100 亿元用于芯片研发及大规模产业化。北京时间 2019 年 12 月 28 日,1999-2019“星光中国芯工程”创新成果与展望报告会在人民大会堂举行。会议回顾和总结了“星光中国芯工程”20 年来在核心技术自主创新、在研发成果大规模产业化、以及在满足国家重大工程技术需求方面取得的重要进展和成功经验,并对“星光中国芯工程”未来发展进行了规划和展望。中国工程院院士、“星光中国芯工程”总指挥、中星微电子集团创建人兼首席科学家邓中翰作了“星光中国芯工程”20 年成果与展望工作报告,报告中,邓中翰表示,在物理层面智能芯片的发展已经受到了物理规律的限制,看似已经接近了极限的时候,在信息层面的技术创新还远远没有碰到天花板。1999 年,邓中翰等一批海外爱国博士企业家回国承担并启动实施“星光中国芯工程”,在北京中关村设立中星微电子公司,致力于超大规模集成电路芯片的研发、设计及产业化工作。2001 年,中国第一颗百万门级超大规模数字多媒体芯片“星光一号”诞生。其后数年间,“星光多媒体”系列芯片被苹果、三星、飞利浦、惠普、LG、索尼、戴尔等国外知名品牌规模采用,占领了全球计算机图像输入芯片 60% 以上的市场份额,彻底结束了中国无“芯”的历史。",
                "article_author": "高琳",
                "source": "spider",
                "source_uuid": "825e6d5e36468cc4bf536799ce3565cf",
                "source_pub_user_id": 3,
                "source_callback_url": "http://scms.wjdev.chinamcloud.cn/api/thirdPush/callBack",
                "source_article_id": 1,
                "pub_log_code": "226004",
                "pub_log_message": "数据验证失败:标题只能包含至多255个字符。",
                "status": 3,
                "is_deleted": 0,
                "created_at": 1578361765,
                "updated_at": 1578361765,
                "deleted_at": 0,
                "task_id": 0,
                "have_pub_number": "3",
                "penguin_name": "华栖云秀",
                "article_url": ""
            },
            {
                "model": "channel_app_task",
                "id": 1,
                "group_id": "015ce30b116ce86058fa6ab4fea4ac63",
                "uuid": "3a1d31262acc11ea9f5754ee75d2ebc1",
                "channel_id": 1,
                "channel_code": "qq",
                "channel_type_id": 1,
                "channel_type_code": "qq_cw",
                "channel_app_source_id": 14,
                "channel_app_source_uuid": "8d72b7cc2ac911eab85a54ee75d2ebc1",
                "type": "pub",
                "article_type_id": "1",
                "article_type_code": "standard",
                "article_type_name": "标准(普通)",
                "article_category_id": 15,
                "article_title": "2019 年 22 款最佳软件开发工具",
                "article_author": "Guru99",
                "source": "spider",
                "source_uuid": "825e6d5e36468cc4bf536799ce3565cf",
                "source_pub_user_id": 3,
                "source_callback_url": "http://scms.wjdev.chinamcloud.cn/api/thirdPush/callBack",
                "source_article_id": 1,
                "pub_log_code": "",
                "pub_log_message": "",
                "status": 4,
                "is_deleted": 0,
                "created_at": 1577686722,
                "updated_at": 1577690249,
                "deleted_at": 0,
                "task_id": 1,
                "have_pub_number": "3",
                "penguin_name": "华栖云秀",
                "article_url": ""
            }
        ],
        "_links": {
            "self": {
                "href": "http://api.channel-pub-api.localhost/qq/v1/qq-app-tasks?group_id=015ce30b116ce86058fa6ab4fea4ac63&filter%5Bgroup_id%5D=015ce30b116ce86058fa6ab4fea4ac63&filter%5Bchannel_type_code%5D=qq_cw&filter%5Bpenguin_name%5D%5Blike%5D=%E5%8D%8E%E6%A0%96%E4%BA%91%E7%A7%80&page=1"
            }
        },
        "_meta": {
            "totalCount": 2,
            "pageCount": 1,
            "currentPage": 1,
            "perPage": 20
        }
    }
}
SELECT * FROM ((SELECT 'channel_app_task' AS 'model', `cpa_channel_app_task`.`id`, `cpa_task`.`group_id`, `cpa_channel_app_task`.`uuid`, `cpa_channel_app_task`.`channel_id`, `cpa_channel_app_task`.`channel_code`, `cpa_channel_app_task`.`channel_type_id`, `cpa_channel_app_task`.`channel_type_code`, `cpa_channel_app_task`.`channel_app_source_id`, `cpa_channel_app_task`.`channel_app_source_uuid`, `cpa_task`.`type`, `cpa_article_type`.`id` AS `article_type_id`, `cpa_article_type`.`code` AS `article_type_code`, `cpa_article_type`.`name` AS `article_type_name`, `cpa_qq_article`.`article_category_id`, `cpa_qq_article`.`title` AS `article_title`, `cpa_qq_article`.`author` AS `article_author`, `cpa_task`.`source`, `cpa_task`.`source_uuid`, `cpa_task`.`source_pub_user_id`, `cpa_task`.`source_callback_url`, `cpa_qq_article`.`source_article_id`, `cpa_pub_log`.`code` AS `pub_log_code`, `cpa_pub_log`.`message` AS `pub_log_message`, `cpa_channel_app_task`.`status`, `cpa_channel_app_task`.`is_deleted`, `cpa_channel_app_task`.`created_at`, `cpa_channel_app_task`.`updated_at`, `cpa_channel_app_task`.`deleted_at`, `cpa_channel_app_task`.`task_id`, `cpa_channel_app_task`.`have_pub_number`, `cpa_qq_cw_app`.`penguin_name`, `cpa_qq_transaction`.`article_url` FROM `cpa_channel_app_task` LEFT JOIN `cpa_task` ON `cpa_channel_app_task`.`task_id` = `cpa_task`.`id` LEFT JOIN `cpa_qq_article` ON `cpa_task`.`id` = `cpa_qq_article`.`task_id` LEFT JOIN `cpa_article_type` ON `cpa_qq_article`.`article_type_id` = `cpa_article_type`.`id` LEFT JOIN `cpa_qq_cw_app_task` ON `cpa_channel_app_task`.`id` = `cpa_qq_cw_app_task`.`channel_app_task_id` LEFT JOIN `cpa_qq_cw_app` ON `cpa_qq_cw_app_task`.`qq_cw_app_id` = `cpa_qq_cw_app`.`id` LEFT JOIN `cpa_pub_log` ON `cpa_channel_app_task`.`id` = `cpa_pub_log`.`channel_app_task_id` LEFT JOIN `cpa_qq_transaction` ON `cpa_qq_cw_app_task`.`id` = `cpa_qq_transaction`.`qq_app_task_id` AND `cpa_qq_transaction`.`type` = '1' WHERE ((`cpa_channel_app_task`.`is_deleted`=0) AND (`cpa_task`.`is_deleted`=0) AND (`cpa_qq_article`.`is_deleted`=0) AND (`cpa_article_type`.`is_deleted`=0) AND (`cpa_qq_cw_app_task`.`is_deleted`=0) AND (`cpa_qq_cw_app`.`is_deleted`=0)) AND ((`cpa_task`.`group_id`='015ce30b116ce86058fa6ab4fea4ac63') AND (`cpa_channel_app_task`.`channel_type_code`='qq_cw') AND (`cpa_qq_cw_app`.`penguin_name` LIKE '%华栖云秀%'))) UNION ALL ( SELECT 'pre_pub_log' AS 'model', `cpa_pre_pub_log`.`id`, `cpa_pre_pub_log`.`group_id`, `cpa_pre_pub_log`.`uuid`, `cpa_pre_pub_log`.`channel_id`, `cpa_pre_pub_log`.`channel_code`, `cpa_pre_pub_log`.`channel_type_id`, `cpa_pre_pub_log`.`channel_type_code`, `cpa_pre_pub_log`.`channel_app_source_id`, `cpa_pre_pub_log`.`channel_app_source_uuid`, `cpa_pre_pub_log`.`type`, `cpa_pre_pub_log`.`article_type_id`, `cpa_pre_pub_log`.`article_type_code`, `cpa_pre_pub_log`.`article_type_name`, `cpa_pre_pub_log`.`article_category_id`, `cpa_pre_pub_log`.`article_title`, `cpa_pre_pub_log`.`article_author`, `cpa_pre_pub_log`.`source`, `cpa_pre_pub_log`.`source_uuid`, `cpa_pre_pub_log`.`source_pub_user_id`, `cpa_pre_pub_log`.`source_callback_url`, `cpa_pre_pub_log`.`source_article_id`, `cpa_pre_pub_log`.`pub_log_code`, `cpa_pre_pub_log`.`pub_log_message`, `cpa_pre_pub_log`.`status`, `cpa_pre_pub_log`.`is_deleted`, `cpa_pre_pub_log`.`created_at`, `cpa_pre_pub_log`.`updated_at`, `cpa_pre_pub_log`.`deleted_at`, 0 AS 'task_id', 3 AS 'have_pub_number', `cpa_qq_cw_app`.`penguin_name`, '' AS 'article_url' FROM `cpa_pre_pub_log` LEFT JOIN `cpa_qq_cw_app` ON `cpa_pre_pub_log`.`channel_app_source_id` = `cpa_qq_cw_app`.`channel_app_source_id` WHERE ((`cpa_pre_pub_log`.`is_deleted`=0) AND (`cpa_qq_cw_app`.`is_deleted`=0)) AND ((`cpa_pre_pub_log`.`group_id`='015ce30b116ce86058fa6ab4fea4ac63') AND (`cpa_pre_pub_log`.`channel_type_code`='qq_cw') AND (`cpa_qq_cw_app`.`penguin_name` LIKE '%华栖云秀%')) )) `model` ORDER BY `created_at` DESC, `id` DESC LIMIT 20

15、仍然存在问题,当响应状态码为 404、302 时,未执行到过滤器方法:afterAction($action, $result),预发布日志表中未插入记录,有待于后续进一步分析处理,如图10

图10

{
    "name": "Not Found",
    "message": "渠道的应用的来源UUID:8d72b7cc2ac911eab85a54ee75d2ebc0,不存在",
    "code": 202001,
    "status": 404,
    "type": "yii\\web\\NotFoundHttpException"
}
{
    "name": "Found",
    "message": "微博的微连接的网页应用的用户的访问令牌的渠道的应用的来源ID(UUID):bad753742ad511ea9d2054ee75d2ebc1,的访问令牌已失效",
    "code": 202136,
    "status": 302,
    "type": "yii\\web\\HttpException"
}
永夜