基于 Yii 2.0,实现 RESTful 风格的 Web Service 服务的 API,请求参数中,仅某一参数支持多个参数值(即数组、列表输入)的数据填充、验证的实现

1、POST http://api.channel-pub-api.localhost/qq/v1/articles?group_id=015ce30b116ce86058fa6ab4fea4ac63 ,如图1

图1

{
 "uuid": "e88e79faad9011e8a14554ee75d2ebc1",
 "article_type_code": "standard",
 "article_category_id": 1,
 "title": "标题 - 20180904 - 1",
 "author": "作者 - 20180904 - 1",
 "source": "spider",
 "source_user_id": 1,
 "source_article_id": 1,
 "content": "文章内容 - 20180904 - 1",
 "cover_pic": "/upload/Image/mrtp/2018/08/30/1_8a2fa998c0624c0ebee6eb72c5434e7a.gif",
 "cover_type": 1,
 "tag": "",
 "apply": 0,
 "original_platform": 0,
 "original_url": "",
 "original_author": ""
}

打印请求参数:

Array
(
    [uuid] => e88e79faad9011e8a14554ee75d2ebc1
    [article_type_code] => standard
    [article_category_id] => 1
    [title] => 标题 - 20180904 - 1
    [author] => 作者 - 20180904 - 1
    [source] => spider
    [source_user_id] => 1
    [source_article_id] => 1
    [content] => 文章内容 - 20180904 - 1
    [cover_pic] => /upload/Image/mrtp/2018/08/30/1_8a2fa998c0624c0ebee6eb72c5434e7a.gif
    [cover_type] => 1
    [tag] => 
    [apply] => 0
    [original_platform] => 0
    [original_url] => 
    [original_author] => 
    [group_id] => 015ce30b116ce86058fa6ab4fea4ac63
)

2、POST http://api.channel-pub-api.localhost/qq/v1/articles?group_id=015ce30b116ce86058fa6ab4fea4ac63 ,请求参数 uuid 为数组格式,如图2

图2

{
 "uuid": [
  "e88e79faad9011e8a14554ee75d2ebc1",
  "9f359272b00e11e8875654ee75d2ebc1"
 ],
 "article_type_code": "standard",
 "article_category_id": 1,
 "title": "标题 - 20180904 - 1",
 "author": "作者 - 20180904 - 1",
 "source": "spider",
 "source_user_id": 1,
 "source_article_id": 1,
 "content": "文章内容 - 20180904 - 1",
 "cover_pic": "/upload/Image/mrtp/2018/08/30/1_8a2fa998c0624c0ebee6eb72c5434e7a.gif",
 "cover_type": 1,
 "tag": "",
 "apply": 0,
 "original_platform": 0,
 "original_url": "",
 "original_author": ""
}

打印请求参数:

Array
(
    [uuid] => Array
        (
            [0] => e88e79faad9011e8a14554ee75d2ebc1
            [1] => 9f359272b00e11e8875654ee75d2ebc1
        )

    [article_type_code] => standard
    [article_category_id] => 1
    [title] => 标题 - 20180904 - 1
    [author] => 作者 - 20180904 - 1
    [source] => spider
    [source_user_id] => 1
    [source_article_id] => 1
    [content] => 文章内容 - 20180904 - 1
    [cover_pic] => /upload/Image/mrtp/2018/08/30/1_8a2fa998c0624c0ebee6eb72c5434e7a.gif
    [cover_type] => 1
    [tag] => 
    [apply] => 0
    [original_platform] => 0
    [original_url] => 
    [original_author] => 
    [group_id] => 015ce30b116ce86058fa6ab4fea4ac63
)


3、查看 \qq\rests\article\CreateAction.php,当 uuid 的值为单个字符串时,其填充与验证代码


    /**
     * Creates a new model.
     * @return \yii\db\ActiveRecordInterface the model newly created
     * @throws ServerErrorHttpException if there is any error when creating the model
     */    public function run()
    {
        if ($this->checkAccess) {
            call_user_func($this->checkAccess, $this->id);
        }

        $requestParams = Yii::$app->getRequest()->getBodyParams();
        /* 判断请求体参数中租户ID是否存在 */        if (!isset($requestParams['group_id'])) {
            $requestParams = ArrayHelper::merge($requestParams, ['group_id' => Yii::$app->params['groupId']]);
        }

        /* 标准(普通、图文)的文章发布参数 */        $qqArticleStandardCreateParam = new QqArticleStandardCreateParam();
        // 把请求数据填充到模型中
        if (!$qqArticleStandardCreateParam->load($requestParams, '')) {
            return ['code' => 40009, 'message' => Yii::t('error', '40009')];
        }
        // 验证模型
        if (!$qqArticleStandardCreateParam->validate()) {
            $qqArticleStandardCreateParamResult = self::handleValidateError($qqArticleStandardCreateParam);
            if ($qqArticleStandardCreateParamResult['status'] === false) {
                return ['code' => $qqArticleStandardCreateParamResult['code'], 'message' => $qqArticleStandardCreateParamResult['message']];
            }
        }

        /* 基于文章类型代码定义场景 */        $this->scenario = 'qq_article_' . $qqArticleStandardCreateParam->article_type_code . '_create';

        /* 实例化多个模型 */
        // 企鹅号的第三方服务平台应用的企鹅媒体用户
        $qqTpAppPenguin = new QqTpAppPenguin([
            'scenario' => $this->scenario,
        ]);
        // 转换标准(普通、图文)的文章发布参数,多模型的填充、验证的实现
        $requestParams[$qqTpAppPenguin->formName()]['uuid'] = $qqArticleStandardCreateParam->uuid;
        $qqTpAppPenguinResult = self::handleLoadAndValidate($qqTpAppPenguin, $requestParams);
        if ($qqTpAppPenguinResult['status'] === false) {
            return ['code' => $qqTpAppPenguinResult['code'], 'message' => $qqTpAppPenguinResult['message']];
        }

    }

    /**
     * 处理模型填充与验证
     * @param object $model 模型
     * @param array $requestParams 请求参数
     * @return array
     * 格式如下:
     *
     * [
     *     'status' => true, // 成功
     * ]
     *
     * [
     *     'status' => false, // 失败
     *     'code' => 20004, // 返回码
     *     'message' => '数据验证失败:企鹅号ID(UUID)是无效的。', // 说明
     * ]
     *
     * @throws ServerErrorHttpException
     */    public static function handleLoadAndValidate($model, $requestParams)
    {
        // 把请求数据填充到模型中
        if (!$model->load($requestParams)) {
            return ['status' => false, 'code' => 40009, 'message' => Yii::t('error', '40009')];
        }
        // 验证模型
        if (!$model->validate()) {
            return self::handleValidateError($model);
        }

        return ['status' => true];
    }

    /**
     * 处理模型错误
     * @param object $model 模型
     * @return array
     * 格式如下:
     *
     * [
     *     'status' => false, // 失败
     *     'code' => 20004, // 返回码
     *     'message' => '数据验证失败:代码是无效的。', // 说明
     * ]
     *
     * @throws ServerErrorHttpException
     */    public static function handleValidateError($model)
    {
        if ($model->hasErrors()) {
            $response = Yii::$app->getResponse();
            $response->setStatusCode(422, 'Data Validation Failed.');
            foreach ($model->getFirstErrors() as $message) {
                $firstErrors = $message;
                break;
            }
            return ['status' => false, 'code' => 20004, 'message' => Yii::t('error', Yii::t('error', Yii::t('error', '20004'), ['firstErrors' => $firstErrors]))];
        } elseif (!$model->hasErrors()) {
            throw new ServerErrorHttpException('Failed to create the object for unknown reason.');
        }
    }

4、参考网址:https://www.yiiframework.com/doc/guide/2.0/zh-cn/input-tabular-input ,编辑 \qq\rests\article\CreateAction.php,当 uuid 的值为数组时,其填充与验证代码


    /**
     * Creates a new model.
     * @return \yii\db\ActiveRecordInterface the model newly created
     * @throws ServerErrorHttpException if there is any error when creating the model
     */    public function run()
    {
        if ($this->checkAccess) {
            call_user_func($this->checkAccess, $this->id);
        }

        $requestParams = Yii::$app->getRequest()->getBodyParams();
        /* 判断请求体参数中租户ID是否存在 */        if (!isset($requestParams['group_id'])) {
            $requestParams = ArrayHelper::merge($requestParams, ['group_id' => Yii::$app->params['groupId']]);
        }

        /* 标准(普通、图文)的文章发布参数 */        $qqArticleStandardCreateParam = new QqArticleStandardCreateParam();
        // 把请求数据填充到模型中
        if (!$qqArticleStandardCreateParam->load($requestParams, '')) {
            return ['code' => 40009, 'message' => Yii::t('error', '40009')];
        }
        // 验证模型
        if (!$qqArticleStandardCreateParam->validate()) {
            $qqArticleStandardCreateParamResult = self::handleValidateError($qqArticleStandardCreateParam);
            if ($qqArticleStandardCreateParamResult['status'] === false) {
                return ['code' => $qqArticleStandardCreateParamResult['code'], 'message' => $qqArticleStandardCreateParamResult['message']];
            }
        }

        /* 基于文章类型代码定义场景 */        $this->scenario = 'qq_article_' . $qqArticleStandardCreateParam->article_type_code . '_create';

        /* 实例化多个模型 */
        // 企鹅号的第三方服务平台应用的企鹅媒体用户
        if (!is_array($requestParams['uuid'])) {
            return ['code' => 40009, 'message' => Yii::t('error', '40009')];
        }
        $count = count($requestParams['uuid']);
        // 创建一个初始的 $qqTpAppPenguins 数组包含一个默认的模型
        $qqTpAppPenguins = [new QqTpAppPenguin([
            'scenario' => $this->scenario,
        ])];
        for($i = 1; $i < $count; $i++) {
            $qqTpAppPenguins[] = new QqTpAppPenguin([
                'scenario' => $this->scenario,
            ]);
        }

        foreach ($requestParams['uuid'] as $key => $uuid) {
            // 转换标准(普通、图文)的文章发布参数,多模型的填充、验证的实现
            $requestParams[$qqTpAppPenguins[0]->formName()][$key]['uuid'] = $uuid;
        }
        // 批量填充模型属性
        if (!Model::loadMultiple($qqTpAppPenguins, $requestParams, $qqTpAppPenguins[0]->formName())) {
            return ['code' => 40009, 'message' => Yii::t('error', '40009')];
        }
        // 批量验证模型
        if (!Model::validateMultiple($qqTpAppPenguins)) {
            $qqTpAppPenguinsResult = self::handleValidateMultipleError($qqTpAppPenguins);
            if ($qqTpAppPenguinsResult['status'] === false) {
                return ['code' => $qqTpAppPenguinsResult['code'], 'message' => $qqTpAppPenguinsResult['message']];
            }
        }

    }

    /**
     * 处理模型填充与验证
     * @param object $model 模型
     * @param array $requestParams 请求参数
     * @return array
     * 格式如下:
     *
     * [
     *     'status' => true, // 成功
     * ]
     *
     * [
     *     'status' => false, // 失败
     *     'code' => 20004, // 返回码
     *     'message' => '数据验证失败:企鹅号ID(UUID)是无效的。', // 说明
     * ]
     *
     * @throws ServerErrorHttpException
     */    public static function handleLoadAndValidate($model, $requestParams)
    {
        // 把请求数据填充到模型中
        if (!$model->load($requestParams)) {
            return ['status' => false, 'code' => 40009, 'message' => Yii::t('error', '40009')];
        }
        // 验证模型
        if (!$model->validate()) {
            return self::handleValidateError($model);
        }

        return ['status' => true];
    }

    /**
     * 处理模型错误
     * @param object $model 模型
     * @return array
     * 格式如下:
     *
     * [
     *     'status' => false, // 失败
     *     'code' => 20004, // 返回码
     *     'message' => '数据验证失败:代码是无效的。', // 说明
     * ]
     *
     * @throws ServerErrorHttpException
     */    public static function handleValidateError($model)
    {
        if ($model->hasErrors()) {
            $response = Yii::$app->getResponse();
            $response->setStatusCode(422, 'Data Validation Failed.');
            foreach ($model->getFirstErrors() as $message) {
                $firstErrors = $message;
                break;
            }
            return ['status' => false, 'code' => 20004, 'message' => Yii::t('error', Yii::t('error', Yii::t('error', '20004'), ['firstErrors' => $firstErrors]))];
        } elseif (!$model->hasErrors()) {
            throw new ServerErrorHttpException('Failed to create the object for unknown reason.');
        }
    }

    /**
     * 处理模型错误
     * @param array $models 模型列表
     * @return array
     * 格式如下:
     *
     * [
     *     'status' => false, // 失败
     *     'code' => 20004, // 返回码
     *     'message' => '数据验证失败:代码是无效的。', // 说明
     * ]
     *
     * @throws ServerErrorHttpException
     */    public static function handleValidateMultipleError($models)
    {
        foreach ($models as $model) {
            if ($model->hasErrors()) {
                $response = Yii::$app->getResponse();
                $response->setStatusCode(422, 'Data Validation Failed.');
                foreach ($model->getFirstErrors() as $message) {
                    $firstErrors = $message;
                    break;
                }
                return ['status' => false, 'code' => 20004, 'message' => Yii::t('error', Yii::t('error', Yii::t('error', '20004'), ['firstErrors' => $firstErrors]))];
            }
        }

        throw new ServerErrorHttpException('Failed to create the object for unknown reason.');
    }

5、POST http://api.channel-pub-api.localhost/qq/v1/articles?group_id=015ce30b116ce86058fa6ab4fea4ac63 ,请求参数 uuid 为字符串格式,响应失败,符合预期

{
 "uuid": "e88e79faad9011e8a14554ee75d2ebc1",
 "article_type_code": "standard",
 "article_category_id": 1,
 "title": "标题 - 20180904 - 1",
 "author": "作者 - 20180904 - 1",
 "source": "spider",
 "source_user_id": 1,
 "source_article_id": 1,
 "content": "文章内容 - 20180904 - 1",
 "cover_pic": "/upload/Image/mrtp/2018/08/30/1_8a2fa998c0624c0ebee6eb72c5434e7a.gif",
 "cover_type": 1,
 "tag": "",
 "apply": 0,
 "original_platform": 0,
 "original_url": "",
 "original_author": ""
}
{
    "code": 40009,
    "message": "数据模型填充失败"
}

6、POST http://api.channel-pub-api.localhost/qq/v1/articles?group_id=015ce30b116ce86058fa6ab4fea4ac63 ,请求参数 uuid 为数组格式,只存在1个键值对,其值错误,响应失败,符合预期

{
 "uuid": [
  "e88e79faad9011e8a14554ee75d2ebc10"
 ],
 "article_type_code": "standard",
 "article_category_id": 1,
 "title": "标题 - 20180904 - 1",
 "author": "作者 - 20180904 - 1",
 "source": "spider",
 "source_user_id": 1,
 "source_article_id": 1,
 "content": "文章内容 - 20180904 - 1",
 "cover_pic": "/upload/Image/mrtp/2018/08/30/1_8a2fa998c0624c0ebee6eb72c5434e7a.gif",
 "cover_type": 1,
 "tag": "",
 "apply": 0,
 "original_platform": 0,
 "original_url": "",
 "original_author": ""
}
{
    "code": 20004,
    "message": "数据验证失败:企鹅号ID(UUID)是无效的。"
}
SELECT EXISTS(SELECT * FROM `cpa_qq_tp_app_penguin` WHERE (`cpa_qq_tp_app_penguin`.`uuid`='e88e79faad9011e8a14554ee75d2ebc10') AND (`status`=1))

7、POST http://api.channel-pub-api.localhost/qq/v1/articles?group_id=015ce30b116ce86058fa6ab4fea4ac63 ,请求参数 uuid 为数组格式,存在2个键值对,其值皆错误,响应失败,符合预期

{
 "uuid": [
  "e88e79faad9011e8a14554ee75d2ebc15",
  "9f359272b00e11e8875654ee75d2ebc16"
 ],
 "article_type_code": "standard",
 "article_category_id": 1,
 "title": "标题 - 20180904 - 1",
 "author": "作者 - 20180904 - 1",
 "source": "spider",
 "source_user_id": 1,
 "source_article_id": 1,
 "content": "文章内容 - 20180904 - 1",
 "cover_pic": "/upload/Image/mrtp/2018/08/30/1_8a2fa998c0624c0ebee6eb72c5434e7a.gif",
 "cover_type": 1,
 "tag": "",
 "apply": 0,
 "original_platform": 0,
 "original_url": "",
 "original_author": ""
}
{
    "code": 20004,
    "message": "数据验证失败:企鹅号ID(UUID)是无效的。"
}
SELECT EXISTS(SELECT * FROM `cpa_qq_tp_app_penguin` WHERE (`cpa_qq_tp_app_penguin`.`uuid`='e88e79faad9011e8a14554ee75d2ebc15') AND (`status`=1))
SELECT EXISTS(SELECT * FROM `cpa_qq_tp_app_penguin` WHERE (`cpa_qq_tp_app_penguin`.`uuid`='9f359272b00e11e8875654ee75d2ebc16') AND (`status`=1))

8、POST http://api.channel-pub-api.localhost/qq/v1/articles?group_id=015ce30b116ce86058fa6ab4fea4ac63 ,请求参数 uuid 为数组格式,存在2个键值对,第1个值正确,第2个值错误,响应失败,符合预期

{
 "uuid": [
  "e88e79faad9011e8a14554ee75d2ebc1",
  "9f359272b00e11e8875654ee75d2ebc16"
 ],
 "article_type_code": "standard",
 "article_category_id": 1,
 "title": "标题 - 20180904 - 1",
 "author": "作者 - 20180904 - 1",
 "source": "spider",
 "source_user_id": 1,
 "source_article_id": 1,
 "content": "文章内容 - 20180904 - 1",
 "cover_pic": "/upload/Image/mrtp/2018/08/30/1_8a2fa998c0624c0ebee6eb72c5434e7a.gif",
 "cover_type": 1,
 "tag": "",
 "apply": 0,
 "original_platform": 0,
 "original_url": "",
 "original_author": ""
}
{
    "code": 20004,
    "message": "数据验证失败:企鹅号ID(UUID)是无效的。"
}
SELECT EXISTS(SELECT * FROM `cpa_qq_tp_app_penguin` WHERE (`cpa_qq_tp_app_penguin`.`uuid`='e88e79faad9011e8a14554ee75d2ebc1') AND (`status`=1))
SELECT EXISTS(SELECT * FROM `cpa_qq_tp_app_penguin` WHERE (`cpa_qq_tp_app_penguin`.`uuid`='9f359272b00e11e8875654ee75d2ebc16') AND (`status`=1))

9、POST http://api.channel-pub-api.localhost/qq/v1/articles?group_id=015ce30b116ce86058fa6ab4fea4ac63 ,请求参数 uuid 为数组格式,存在2个键值对,第1个值错误,第2个值正确,响应失败,符合预期

{
 "uuid": [
  "e88e79faad9011e8a14554ee75d2ebc13",
  "9f359272b00e11e8875654ee75d2ebc1"
 ],
 "article_type_code": "standard",
 "article_category_id": 1,
 "title": "标题 - 20180904 - 1",
 "author": "作者 - 20180904 - 1",
 "source": "spider",
 "source_user_id": 1,
 "source_article_id": 1,
 "content": "文章内容 - 20180904 - 1",
 "cover_pic": "/upload/Image/mrtp/2018/08/30/1_8a2fa998c0624c0ebee6eb72c5434e7a.gif",
 "cover_type": 1,
 "tag": "",
 "apply": 0,
 "original_platform": 0,
 "original_url": "",
 "original_author": ""
}
{
    "code": 20004,
    "message": "数据验证失败:企鹅号ID(UUID)是无效的。"
}
SELECT EXISTS(SELECT * FROM `cpa_qq_tp_app_penguin` WHERE (`cpa_qq_tp_app_penguin`.`uuid`='e88e79faad9011e8a14554ee75d2ebc13') AND (`status`=1))
SELECT EXISTS(SELECT * FROM `cpa_qq_tp_app_penguin` WHERE (`cpa_qq_tp_app_penguin`.`uuid`='9f359272b00e11e8875654ee75d2ebc1') AND (`status`=1))

10、POST http://api.channel-pub-api.localhost/qq/v1/articles?group_id=015ce30b116ce86058fa6ab4fea4ac63 ,请求参数 uuid 为数组格式,存在2个键值对,其值皆正确,响应成功,符合预期

{
 "uuid": [
  "e88e79faad9011e8a14554ee75d2ebc1",
  "9f359272b00e11e8875654ee75d2ebc1"
 ],
 "article_type_code": "standard",
 "article_category_id": 1,
 "title": "标题 - 20180904 - 1",
 "author": "作者 - 20180904 - 1",
 "source": "spider",
 "source_user_id": 1,
 "source_article_id": 1,
 "content": "文章内容 - 20180904 - 1",
 "cover_pic": "/upload/Image/mrtp/2018/08/30/1_8a2fa998c0624c0ebee6eb72c5434e7a.gif",
 "cover_type": 1,
 "tag": "",
 "apply": 0,
 "original_platform": 0,
 "original_url": "",
 "original_author": ""
}
{
    "code": 10000,
    "message": "发布文章类型:标准(普通、图文)的文章成功"
}
SELECT EXISTS(SELECT * FROM `cpa_qq_tp_app_penguin` WHERE (`cpa_qq_tp_app_penguin`.`uuid`='e88e79faad9011e8a14554ee75d2ebc1') AND (`status`=1))
SELECT EXISTS(SELECT * FROM `cpa_qq_tp_app_penguin` WHERE (`cpa_qq_tp_app_penguin`.`uuid`='9f359272b00e11e8875654ee75d2ebc1') AND (`status`=1))

11、可以确定的结论:数组中存在几个键值对,则会执行同样数量的验证过程,不会因为某个值验证失败,后续的值就不再验证,暂时告一段落,后续优化,以实现当某个值验证失败后,后续的值就不再验证,而是直接响应失败才是。

永夜