在 Yii 2.0 中,一键多渠道发布(即在一个接口请求中,批量调用多个接口请求) 的实现
1、在之前的发布界面上,一次发布仅支持一个渠道,因此,3 个渠道的发布需要分别调用 3 次接口(APP、网易号、微博),如图1
2、在现在的发布界面上,准备一次发布可发布至多个渠道上,原型,如图2
3、在渠道发布接口中,每一个渠道的任务(文章)的发布是隔离开的,不支持在一次发布中,发布至多个渠道,如图3
4、现阶段需要支持在一次发布中,发布至多个渠道。渠道发布提供一个一键多渠道发布的接口。有 2 种方案,第 1 种方案:在接口内部通过 HTTP 调用其他渠道的发布接口。第 2 种方案:在接口内部的控制器动作中调用其他控制器动作。因为,一个接口的入口本质是控制器动作。最终决定采纳第 2 种方案。
5、编辑 /api/controllers/TaskGroupController.php,覆盖 afterAction() 方法。在其中运行其他操作ID。
<?php /** * Created by PhpStorm. * User: Qiang Wang * Date: 2020/03/16 * Time: 14:47 */ namespace api\controllers; use Yii; use yii\base\InvalidConfigException; use yii\db\Exception; use yii\rest\ActiveController; /** * Class TaskGroupController * @package api\controllers * * @author Qiang Wang <shuijingwanwq@163.com> * @since 1.0 */ class TaskGroupController extends ActiveController { public $serializer = [ 'class' => 'api\rests\task_group\Serializer', 'collectionEnvelope' => 'items', ]; /** * @inheritdoc */ public function actions() { $actions = parent::actions(); // 禁用"view"、"update"、"delete"、"options"动作 unset($actions['view'], $actions['update'], $actions['delete'], $actions['options']); $actions['index']['class'] = 'api\rests\task_group\IndexAction'; $actions['create']['class'] = 'api\rests\task_group\CreateAction'; return $actions; } /** * {@inheritdoc} * @throws InvalidConfigException if a registered parser does not implement the [[RequestParserInterface]]. * @throws Exception execution failed */ public function afterAction($action, $result) { if ($action->id == 'create' && $result['code'] === 10000) { // 返回所有请求参数 $request = Yii::$app->request; $requestParams = $request->getBodyParams(); // 添加请求参数 $requestParams['task_group']['id'] = $result['data']['id']; $requestParams['task_group']['uuid'] = $result['data']['uuid']; $request->setBodyParams($requestParams); $requestParams = $request->getBodyParams(); // 运行操作ID:index $this->runAction('index'); } return parent::afterAction($action, $result); } }
6、请求:IndexAction,报错:Method Not Allowed,Method Not Allowed. This URL can only handle the following request methods: GET, HEAD.由此看来,在接口内部的控制器动作中调用其他控制器动作。是会受到请求类型的限制的。而基于安全考虑,暂时不允许放开相应的限制。如图4
{ "name": "Method Not Allowed", "message": "Method Not Allowed. This URL can only handle the following request methods: GET, HEAD.", "code": 0, "status": 405, "type": "yii\\web\\MethodNotAllowedHttpException" }
7、而且每一个渠道的发布接口是分布于 qq、wx 等独立的一个个应用中,在 api 应用中是无法运行 qq、wx 等应用下的操作ID的。因此,决定采纳第 1 种方案:在接口内部通过 HTTP 调用其他渠道的发布接口。如图5
8、编辑 /api/controllers/TaskGroupController.php,在 behaviors() 方法中添加过滤器:taskGroupFilter
<?php /** * Created by PhpStorm. * User: Qiang Wang * Date: 2020/03/16 * Time: 14:47 */ namespace api\controllers; use api\filters\TaskGroupFilter; use yii\rest\ActiveController; /** * Class TaskGroupController * @package api\controllers * * @author Qiang Wang <shuijingwanwq@163.com> * @since 1.0 */ class TaskGroupController extends ActiveController { public $serializer = [ 'class' => 'api\rests\task_group\Serializer', 'collectionEnvelope' => 'items', ]; /** * {@inheritdoc} */ public function behaviors() { $behaviors = parent::behaviors(); $behaviors['taskGroupFilter'] = [ 'class' => TaskGroupFilter::className(), 'only' => ['create'], ]; return $behaviors; } /** * @inheritdoc */ public function actions() { $actions = parent::actions(); // 禁用"view"、"update"、"delete"、"options"动作 unset($actions['view'], $actions['update'], $actions['delete'], $actions['options']); $actions['index']['class'] = 'api\rests\task_group\IndexAction'; $actions['create']['class'] = 'api\rests\task_group\CreateAction'; return $actions; } }
9、任务组(即一键多渠道发布)过滤器,新建 /api/filters/TaskGroupFilter.php
<?php /** * Created by PhpStorm. * User: Qiang Wang * Date: 2020/03/18 * Time: 16:27 */ namespace api\filters; use Yii; use api\services\TaskGroupService; use yii\base\ActionFilter; use yii\base\InvalidConfigException; use yii\httpclient\Exception; /** * 任务组(即一键多渠道发布)过滤器 * @package api\filters * * @author Qiang Wang <shuijingwanwq@163.com> * @since 1.0 */ class TaskGroupFilter extends ActionFilter { /** * {@inheritdoc} * * @throws Exception * @throws InvalidConfigException */ public function afterAction($action, $result) { if ($result['code'] === 10000) { // 返回所有请求参数 $request = Yii::$app->request; $requestParams = $request->getBodyParams(); // 添加请求参数 $requestParams['task_group']['id'] = $result['data']['id']; $requestParams['task_group']['uuid'] = $result['data']['uuid']; /* 操作数据(一键发布至多渠道)(同步) */ TaskGroupService::createMultipleSync($requestParams); } return parent::afterAction($action, $result); } }
10、实现任务组服务类的一键发布至多渠道(同步)方法。暂时仅实现了企鹅号、微信。createMultipleSync($requestParams),编辑 /common/services/TaskGroupService.php
<?php /** * Created by PhpStorm. * User: Qiang Wang * Date: 2020/03/18 * Time: 10:23 */ namespace common\services; use common\logics\ArticleType; use common\logics\Task; use common\logics\WxArticle; use common\logics\WxArticleNews; use Yii; use common\logics\Channel; use common\logics\QqArticleNormal; use common\logics\http\channel_pub_api\Article as HttpChannelPubApiArticle; use yii\base\InvalidConfigException; use yii\httpclient\Exception; use yii\web\ServerErrorHttpException; /** * Class TaskGroupService * @package common\services * * @author Qiang Wang <shuijingwanwq@163.com> * @since 1.0 */ class TaskGroupService extends Service { /** * 创建任务组 * @param object $model 对象 * * @param bool $runValidation 保存记录之前是否执行验证 (调用 [[validate()]]),默认为 true * * @return array * 格式如下: * * [ * 'status' => true, // 成功 * 'data' => [ // object * ] * ] * * [ * 'status' => false, // 失败 * 'code' => 202184, // 返回码 * 'message' => '', // 说明 * ] * * @throws ServerErrorHttpException */ public function create($model, $runValidation = true) { if ($model->save($runValidation)) { return ['status' => true, 'data' => $model]; } elseif ($model->hasErrors()) { $firstError = ''; foreach ($model->getFirstErrors() as $message) { $firstError = $message; break; } return ['status' => false, 'code' => 202184, 'message' => Yii::t('error', Yii::t('error', Yii::t('error', '202184'), ['first_error' => $firstError]))]; } elseif (!$model->hasErrors()) { throw new ServerErrorHttpException('Failed to create the object (task group) for unknown reason.'); } } /** * 一键发布至多渠道(同步) * * @param array $requestParams 请求参数 * * @throws Exception * @throws InvalidConfigException */ public static function createMultipleSync($requestParams) { // 请求数据 $requestDatas = []; // 判断渠道:企鹅号是否存在,准备对应渠道的数据 if (isset($requestParams[Channel::CODE_QQ])) { foreach ($requestParams[Channel::CODE_QQ] as $key => $value) { $value['source'] = $requestParams['task_group']['source']; $value['source_uuid'] = $requestParams['task_group']['source_uuid']; $value['source_pub_user_id'] = $requestParams['task_group']['source_pub_user_id']; $value['source_callback_url'] = $requestParams['task_group']['source_callback_url']; $value['task_group_id'] = $requestParams['task_group']['id']; $value['task_group_uuid'] = $requestParams['task_group']['uuid']; $value['cover_pics'] = $value['covers'] ?? []; $value['apply'] = $value['original'] ?? QqArticleNormal::APPLY_DEFAULT; unset($value['covers'], $value['original']); $requestDatas[] = [ 'url' => 'qq/v1/articles/standard', 'data' => $value, ]; } } // 判断渠道:微信公众帐号是否存在,准备对应渠道的数据 if (isset($requestParams[Channel::CODE_WX])) { foreach ($requestParams[Channel::CODE_WX] as $key => $value) { $value['app_ids'] = $value['channel_app_source_uuids'] ?? []; $value['source'] = $requestParams['task_group']['source']; $value['source_uuid'] = $requestParams['task_group']['source_uuid']; $value['source_pub_user_id'] = $requestParams['task_group']['source_pub_user_id']; $value['source_callback_url'] = $requestParams['task_group']['source_callback_url']; $value['task_group_id'] = $requestParams['task_group']['id']; $value['task_group_uuid'] = $requestParams['task_group']['uuid']; $value['thumb'] = $value['covers'][0] ?? ''; $value['show_cover_pic'] = $value['cover_show'] ?? WxArticleNews::SHOW_COVER_PIC_DEFAULT; $value['url'] = $value['source_url'] ?? ''; unset($value['covers'], $value['cover_show'], $value['source_url']); $value['code'] = ArticleType::CODE_STANDARD; $value['type'] = Task::TYPE_PUB; $value['wx_send_type'] = WxArticle::WX_SEND_TYPE_MASS; $requestDatas[] = [ 'url' => 'wx/v1/wx-articles/article', 'data' => $value, ]; } } // 批量发布文章类型:标准(普通、图文)的文章 $httpChannelPubApiArticle = new HttpChannelPubApiArticle(); $httpChannelPubApiArticle->batchPostArticlesStandard(Yii::$app->params['groupId'], $requestDatas); } }
11、实现 HTTP 模型类,渠道发布接口的基础类。新建 /common/logics/http/channel_pub_api/Model.php
<?php /** * Created by PhpStorm. * User: Qiang Wang * Date: 2020/03/20 * Time: 13:56 */ namespace common\logics\http\channel_pub_api; use Yii; use yii\httpclient\Client; use yii\httpclient\CurlTransport; use yii\web\ServerErrorHttpException; /** * 渠道发布接口的基础类 * * @author Qiang Wang <shuijingwanwq@163.com> * @since 1.0 */ class Model extends \yii\base\Model { private $_httpClient; /* * 创建 HTTP 客户端对象 * * @return object the created object * @throws InvalidConfigException if a registered parser does not implement the [[RequestParserInterface]]. */ public function getHttpClient() { if (!is_object($this->_httpClient)) { $this->_httpClient = Yii::createObject([ 'class' => Client::className(), 'baseUrl' => Yii::$app->params['channelPubApi']['hostInfo'] . Yii::$app->params['channelPubApi']['baseUrl'], 'requestConfig' => [ 'format' => Client::FORMAT_JSON ], 'transport' => CurlTransport::className(), ]); } return $this->_httpClient; } /* * 响应对象的处理 * * @param object $response 响应对象 * * @return array|bool * * 格式如下: * * 渠道发布接口的响应信息 * [ * 'message' => '', //说明 * 'data' => [], //数据 * ] * * 失败(将错误保存在 [[yii\base\Model::errors]] 属性中) * false * * @throws ServerErrorHttpException 如果响应状态码不等于20x */ public function responseHandler($response) { // 检查响应状态码是否等于20x $responseCode = $response->data['code'] ?? 0; // 返回码 if ($response->isOk) { // 检查业务逻辑是否成功 if ($responseCode === 10000) { return ['message' => $response->data['message'], 'data' => $response->data['data']]; } else { $this->addError('id', Yii::t('error', Yii::t('error', Yii::t('error', '202186'), ['code' => $responseCode, 'message' => $response->data['message']]))); return !$this->hasErrors(); } } else { $responseMessage = $response->data['message'] ?? ''; // 说明 throw new ServerErrorHttpException(Yii::t('error', Yii::t('error', Yii::t('error', '202185'), ['status_code' => $response->statusCode, 'code' => $responseCode, 'message' => $responseMessage])), 202185); } } }
12、实现 HTTP 模型类,渠道发布接口的文章。批量发布文章类型:标准(普通、图文)的文章。新建 /common/logics/http/channel_pub_api/Article.php
<?php /** * Created by PhpStorm. * User: Qiang Wang * Date: 2020/03/20 * Time: 14:19 */ namespace common\logics\http\channel_pub_api; use Yii; use yii\base\InvalidConfigException; use yii\httpclient\Client; use yii\httpclient\Exception; /** * 渠道发布接口的文章 * * @author Qiang Wang <shuijingwanwq@163.com> * @since 1.0 */ class Article extends Model { /** * 批量发布文章类型:标准(普通、图文)的文章 * * @param string $groupId 租户ID * @param array $requestDatas 请求数据 * * @throws Exception * @throws InvalidConfigException */ public function batchPostArticlesStandard($groupId, $requestDatas) { $client = new Client(); $requests = []; foreach ($requestDatas as $requestData) { $requests[] = $this->httpClient->createRequest() ->setMethod('post') ->setUrl($requestData['url'] . '?group_id=' . $groupId) ->setData($requestData['data']); } $client->batchSend($requests); } }
13、POST 请求:http://api.channel-pub-api.localhost/v1/task-groups?group_id=015ce30b116ce86058fa6ab4fea4ac63 。创建任务组(即一键多渠道发布)。成功响应。如图6
{ "source": { "channel_app_source_uuids": [ "55f62c8c67fc11ea809554ee75d2ebc1", "36cf694a67fc11eaa79d54ee75d2ebc1", "29473624681311eaab8e54ee75d2ebc1" ], "title": "北京小汤山医院启用,设千张床位", "content": "3月16日晚拍摄的北京小汤山医院局部(无人机照片)。记者从北京市疫情防控领导小组获悉,为做好境外输入人员疫情防控工作,3月16日起,启用北京小汤山医院。医院设床位1000余张,将主要用于境外来(返)京人员中需筛查人员、疑似病例及轻型、普通型确诊患者的筛查和治疗。", "source": "spider", "source_uuid": "825e6d5e36468cc4bf536799ce3565cf", "source_pub_user_id": 1, "source_callback_url": "http://scms.wjdev.chinamcloud.cn/api/thirdPush/callBack", "task_group_source_article_id": 1, "baijia_source_article_id": 2, "baijia_author": "鞠焕宗", "baijia_covers": [ "http://pic-bucket.ws.126.net/photo/0001/2020-03-17/F7TNMMRG00AN0001NOS.jpg", "http://pic-bucket.ws.126.net/photo/0001/2020-03-17/F7TNMMRH00AN0001NOS.jpg", "http://pic-bucket.ws.126.net/photo/0001/2020-03-17/F7TNMMRI00AN0001NOS.jpg" ], "baijia_original": 0, "baijia_original_url": "http://news.163.com/photoview/00AN0001/2307424.html", "netease_source_article_id": 3, "netease_author": "鞠焕宗", "netease_article_category_id": 417, "netease_covers": [ "http://pic-bucket.ws.126.net/photo/0001/2020-03-17/F7TNMMRG00AN0001NOS.jpg", "http://pic-bucket.ws.126.net/photo/0001/2020-03-17/F7TNMMRH00AN0001NOS.jpg", "http://pic-bucket.ws.126.net/photo/0001/2020-03-17/F7TNMMRI00AN0001NOS.jpg" ], "netease_cover_type": "threeImg", "netease_original": 0, "qq_source_article_id": 4, "qq_author": "鞠焕宗", "qq_article_category_id": 24, "qq_tag": "北京,疫情,防控,境外,输入,小汤山,医院", "qq_covers": [ "http://pic-bucket.ws.126.net/photo/0001/2020-03-17/F7TNMMRG00AN0001NOS.jpg", "http://pic-bucket.ws.126.net/photo/0001/2020-03-17/F7TNMMRH00AN0001NOS.jpg", "http://pic-bucket.ws.126.net/photo/0001/2020-03-17/F7TNMMRI00AN0001NOS.jpg" ], "qq_cover_type": 3, "qq_original": 0, "qq_original_platform": 0, "qq_original_url": "", "qq_original_author": "", "weibo_source_article_id": 5, "weibo_summary": "记者从北京市疫情防控领导小组获悉,为做好境外输入人员疫情防控工作,3月16日起,启用北京小汤山医院。医院设床位1000余张,将主要用于境外来(返)京人员中需筛查人员、疑似病例及轻型、普通型确诊患者的筛查和治疗。", "weibo_link_content": "记者从北京市疫情防控领导小组获悉,为做好境外输入人员疫情防控工作,3月16日起,启用北京小汤山医院。医院设床位1000余张,将主要用于境外来(返)京人员中需筛查人员、疑似病例及轻型、普通型确诊患者的筛查和治疗。", "weibo_covers": [ "http://pic-bucket.ws.126.net/photo/0001/2020-03-17/F7TNMMRG00AN0001NOS.jpg" ], "wx_source_article_id": 6, "wx_author": "鞠焕宗", "wx_covers": [ "http://pic-bucket.ws.126.net/photo/0001/2020-03-17/F7TNMMRG00AN0001NOS.jpg" ], "wx_cover_show": 1, "wx_description": "记者从北京市疫情防控领导小组获悉,为做好境外输入人员疫情防控工作,3月16日起,启用北京小汤山医院。医院设床位1000余张,将主要用于境外来(返)京人员中需筛查人员、疑似病例及轻型、普通型确诊患者的筛查和治疗。", "wx_source_url": "http://news.163.com/photoview/00AN0001/2307424.html", "toutiao_source_article_id": 7, "toutiao_source_url": "http://news.163.com/photoview/00AN0001/2307424.html" }, "task_group": { "source": "spider", "source_uuid": "825e6d5e36468cc4bf536799ce3565cf", "source_pub_user_id": 1, "source_callback_url": "http://scms.wjdev.chinamcloud.cn/api/thirdPush/callBack", "source_article_id": 1 }, "baijia": [ { "channel_app_source_uuids": [ "29473624681311eaab8e54ee75d2ebc1" ], "title": "北京小汤山医院启用,设千张床位", "content": "3月16日晚拍摄的北京小汤山医院局部(无人机照片)。记者从北京市疫情防控领导小组获悉,为做好境外输入人员疫情防控工作,3月16日起,启用北京小汤山医院。医院设床位1000余张,将主要用于境外来(返)京人员中需筛查人员、疑似病例及轻型、普通型确诊患者的筛查和治疗。", "source_article_id": 2, "author": "鞠焕宗", "covers": [ "http://pic-bucket.ws.126.net/photo/0001/2020-03-17/F7TNMMRG00AN0001NOS.jpg", "http://pic-bucket.ws.126.net/photo/0001/2020-03-17/F7TNMMRH00AN0001NOS.jpg", "http://pic-bucket.ws.126.net/photo/0001/2020-03-17/F7TNMMRI00AN0001NOS.jpg" ], "original": 0, "original_url": "http://news.163.com/photoview/00AN0001/2307424.html" } ], "netease": [ { "channel_app_source_uuids": [ "29473624681311eaab8e54ee75d2ebc1" ], "title": "北京小汤山医院启用,设千张床位", "content": "3月16日晚拍摄的北京小汤山医院局部(无人机照片)。记者从北京市疫情防控领导小组获悉,为做好境外输入人员疫情防控工作,3月16日起,启用北京小汤山医院。医院设床位1000余张,将主要用于境外来(返)京人员中需筛查人员、疑似病例及轻型、普通型确诊患者的筛查和治疗。", "source_article_id": 3, "author": "鞠焕宗", "article_category_id": 417, "covers": [ "http://pic-bucket.ws.126.net/photo/0001/2020-03-17/F7TNMMRG00AN0001NOS.jpg", "http://pic-bucket.ws.126.net/photo/0001/2020-03-17/F7TNMMRH00AN0001NOS.jpg", "http://pic-bucket.ws.126.net/photo/0001/2020-03-17/F7TNMMRI00AN0001NOS.jpg" ], "cover_type": "threeImg", "original": 0 } ], "qq": [ { "channel_app_source_uuids": [ "55f62c8c67fc11ea809554ee75d2ebc1" ], "title": "北京小汤山医院启用,设千张床位", "content": "3月16日晚拍摄的北京小汤山医院局部(无人机照片)。记者从北京市疫情防控领导小组获悉,为做好境外输入人员疫情防控工作,3月16日起,启用北京小汤山医院。医院设床位1000余张,将主要用于境外来(返)京人员中需筛查人员、疑似病例及轻型、普通型确诊患者的筛查和治疗。", "source_article_id": 4, "author": "鞠焕宗", "article_category_id": 24, "tag": "北京,疫情,防控,境外,输入,小汤山,医院", "covers": [ "http://pic-bucket.ws.126.net/photo/0001/2020-03-17/F7TNMMRG00AN0001NOS.jpg", "http://pic-bucket.ws.126.net/photo/0001/2020-03-17/F7TNMMRH00AN0001NOS.jpg", "http://pic-bucket.ws.126.net/photo/0001/2020-03-17/F7TNMMRI00AN0001NOS.jpg" ], "cover_type": 3, "original": 0, "original_platform": 0, "original_url": "", "original_author": "" }, { "channel_app_source_uuids": [ "36cf694a67fc11eaa79d54ee75d2ebc1" ], "title": "北京小汤山医院启用,设千张床位", "content": "3月16日晚拍摄的北京小汤山医院局部(无人机照片)。记者从北京市疫情防控领导小组获悉,为做好境外输入人员疫情防控工作,3月16日起,启用北京小汤山医院。医院设床位1000余张,将主要用于境外来(返)京人员中需筛查人员、疑似病例及轻型、普通型确诊患者的筛查和治疗。", "source_article_id": 4, "author": "鞠焕宗", "article_category_id": 24, "tag": "北京,疫情,防控,境外,输入,小汤山,医院", "covers": [ "http://pic-bucket.ws.126.net/photo/0001/2020-03-17/F7TNMMRJ00AN0001NOS.jpg", "http://pic-bucket.ws.126.net/photo/0001/2020-03-17/F7TNMMRH00AN0001NOS.jpg", "http://pic-bucket.ws.126.net/photo/0001/2020-03-17/F7TNMMRI00AN0001NOS.jpg" ], "cover_type": 3, "original": 0, "original_platform": 0, "original_url": "", "original_author": "" } ], "weibo": [ { "channel_app_source_uuids": [ "渠道的应用的来源ID(UUID)" ], "title": "北京小汤山医院启用,设千张床位", "content": "内容", "source_article_id": "来源文章ID", "summary": "导语", "link_content": "链接内容", "covers": [ "封面图" ] } ], "wx": [ { "channel_app_source_uuids": [ "渠道的应用的来源ID(UUID)" ], "title": "北京小汤山医院启用,设千张床位", "content": "内容", "source_article_id": "来源文章ID", "author": "作者", "covers": [ "封面图" ], "cover_show": "封面图是否显示", "description": "描述", "source_url": "来源链接" } ], "toutiao": [ { "channel_app_source_uuids": [ "渠道的应用的来源ID(UUID)" ], "title": "北京小汤山医院启用,设千张床位", "content": "内容", "source_article_id": "来源文章ID", "source_url": "来源链接" } ] }
{ "code": 10000, "message": "已发布文章至渠道发布,待发布至多渠道,请稍候", "data": { "source_article_id": 1, "is_deleted": 0, "deleted_at": 0, "status": 1, "created_at": 1584758252, "updated_at": 1584758252, "uuid": "eba334aa6b1c11ea8d7154ee75d2ebc1", "id": 63 } }
14、查看 api 应用的 Log Messages,分别请求了 2 次 企鹅号与 1 次 微信。如图7
15、查看 qq 应用的 Log Messages,分别请求了 2 次 企鹅号。如图8
16、查看 wx 应用的 Log Messages,分别请求了 1 次 微信。图9
17、查看 MySQl 表中的数据,分别写入了 2 次 企鹅号与 1 次 微信。符合预期。其他渠道按照相类似的规则皆可以如此处理。如图10
近期评论