没有不值得去解决的问题,也没有不值得去学习的技术!

Implementing Data Population and Validation for Multi-Model Request Parameters in a RESTful API With yiisoft/yii2-app-advanced

Post http://api.channel-pub-api.localhost/qq/v1/articles?group_id=015ce30b116ce86058fa6ab4fea4ac63 , parameter: uuid does not exist in the Penguin media user model of the Penguin account's third-party service platform application, response: Data validation failed: Penguin account ID (UUID) is invalid. Meets expectations

1. Reference URL: https://www.yiiframework.com/doc/guide/2.0/zh-cn/input-multiple-models . It is suitable for web forms, but the request parameters of an API application generally do not include the form name (especially when inputting a single model), making it less appropriate. For example, if the table name is article_type and the field name is code, the request parameter name is article_type_code, rather than ArticleType[‘code’]. The web form is shown in Figure 1.

Reference URL: https://www.yiiframework.com/doc/guide/2.0/zh-cn/input-multiple-models . It is suitable for web forms, but the request parameters of an API application generally do not include the form name (especially when inputting a single model), making it less appropriate. For example, if the table name is article_type and the field name is code, the request parameter name is article_type_code, rather than ArticleType['code']. Web form
Figure 1

2. There are now 7 models: qq_tp_app_penguin (Penguin media user of the Penguin account’s third-party service platform application, QqTpAppPenguin), article_type (Article type, ArticleType), article_category (Article category, ArticleCategory), qq_article_category_normal (Article category of the Penguin account’s article type (article), QqArticleCategoryNormal), article (Article, Article), qq_article_normal (Article of the Penguin account’s article type (article), QqArticleNormal), qq_tp_app_access_token (Access token for the Penguin account’s third-party service platform application (Redis), RedisQqAuthQqTpAppAccessToken). The example only demonstrates the implementation of populating and validating the first 2 models.

3. Publishing an article of article type: standard (normal, image and text) to the channel publishing endpoint /qq/v1/articles (article/create). The request parameters do not strictly follow the (table_name_field_name) rule, because strict adherence would result in overly long field names and could still cause ambiguity. They need to be manually converted to the corresponding model fields later.

Plaintext
1. Request parameter list
(1) uuid: Required, Penguin account ID (UUID)
(2) article_type_code: Required, Article type code, standard: Standard (Normal)
(3) article_category_id: Required, Article category ID
(4) title: Required, Title
(5) author: Optional, Author, Default: empty string
(6) source: Required, Source, xContent: Content library; vms: Video Management System; cms: Content Management System; spider: Self-media
(7) source_user_id: Required, Source user ID
(8) source_article_id: Required, Source article ID
(9) content: Required, Article content
(10) cover_pic: Required, Article cover image
(11) cover_type: Optional, Article cover type, 1: Single image; 3: Three images, Default: 1
(12) tag: Optional, Article tags, separated by English half-width commas, Default: empty string
(13) apply: Optional, Whether to apply for an original article, 0: No; 1: Yes (requires the user to have the qualification to publish original image and text articles, otherwise invalid), Default: 0
(14) original_platform: Optional, Original first-publish platform, required when applying for an original article, Default: 0
(15) original_url: Optional, Original first-publish link, required when applying for an original article and the selected platform is not Penguin, Default: empty string
(16) original_author: Optional, Original first-publish author, required when applying for an original article and the selected platform is not Penguin, Default: empty string

2. Input data validation rules
(1) Required: uuid, article_type_code, article_category_id, title, source, source_user_id, source_article_id, content, cover_pic
(2) Default value (''): author, tag, original_url, original_author
(3) Default value (1): cover_type
(4) Default value (0): apply, original_platform
(5) Comparison: The value of article_type_code must equal standard
(6) Existence: uuid must exist in the Penguin media user model of the Penguin account's third-party service platform application, and its status must be 1: Enabled

4. Create a new model file: \common\logics\QqArticleStandardCreateParam.php. Its purpose is to set the first 4 and the 6th rules of the input data validation rules, ensuring that all parameter values exist, so that when converting to the corresponding model fields, there is no need to check again whether the value exists.

PHP
<?php
 
namespace common\logics;
 
use Yii;
use yii\base\Model;
 
/**
 * QqArticleStandardCreateParam
 */
class QqArticleStandardCreateParam extends Model
{
    public $uuid;
    public $article_type_code;
    public $article_category_id;
    public $title;
    public $author;
    public $source;
    public $source_user_id;
    public $source_article_id;
    public $content;
    public $cover_pic;
    public $cover_type;
    public $tag;
    public $apply;
    public $original_platform;
    public $original_url;
    public $original_author;
 
 
    /**
     * {@inheritdoc}
     */
    public function rules()
    {
        return [
            [['uuid', 'article_type_code', 'article_category_id', 'title', 'source', 'source_user_id', 'source_article_id', 'content', 'cover_pic'], 'required'],
            [['author', 'tag', 'original_url', 'original_author'], 'default', 'value' => ''],
            [['cover_type'], 'default', 'value' => 1],
            [['apply', 'original_platform'], 'default', 'value' => 0],
            ['article_type_code', 'compare', 'compareValue' => ArticleType::CODE_STANDARD],
        ];
    }
 
    /**
     * {@inheritdoc}
     */
    public function attributeLabels()
    {
        return [
            'uuid' => Yii::t('model/qq-article-standard-create-param', 'Uuid'),
            'article_type_code' => Yii::t('model/qq-article-standard-create-param', 'Article Type Code'),
            'article_category_id' => Yii::t('model/qq-article-standard-create-param', 'Article Category Id'),
            'title' => Yii::t('model/qq-article-standard-create-param', 'Title'),
            'author' => Yii::t('model/qq-article-standard-create-param', 'Author'),
            'source' => Yii::t('model/qq-article-standard-create-param', 'Source'),
            'source_user_id' => Yii::t('model/qq-article-standard-create-param', 'Source User Id'),
            'source_article_id' => Yii::t('model/qq-article-standard-create-param', 'Source Article Id'),
            'content' => Yii::t('model/qq-article-standard-create-param', 'Content'),
            'cover_pic' => Yii::t('model/qq-article-standard-create-param', 'Cover Pic'),
            'cover_type' => Yii::t('model/qq-article-standard-create-param', 'Cover Type'),
            'tag' => Yii::t('model/qq-article-standard-create-param', 'Tag'),
            'apply' => Yii::t('model/qq-article-standard-create-param', 'Apply'),
            'original_platform' => Yii::t('model/qq-article-standard-create-param', 'Original Platform'),
            'original_url' => Yii::t('model/qq-article-standard-create-param', 'Original Url'),
            'original_author' => Yii::t('model/qq-article-standard-create-param', 'Original Author'),
        ];
    }
}

5. Corresponding language pack file
\common\messages\en-US\model\qq-article-standard-create-param.php

PHP
<?php 
/** * Created by PhpStorm. * User: WangQiang * Date: 2018/08/31 * Time: 18:17 */ 
return [ 
    'Uuid' => 'Uuid',
    'Article Type Code' => 'Article Type Code',
    'Article Category Id' => 'Article Category Id',
    'Title' => 'Title',
    'Author' => 'Author',
    'Source' => 'Source',
    'Source User Id' => 'Source User Id',
    'Source Article Id' => 'Source Article Id',
    'Content' => 'Content',
    'Cover Pic' => 'Cover Pic',
    'Cover Type' => 'Cover Type',
    'Tag' => 'Tag',
    'Category' => 'Category',
    'Apply' => 'Apply',
    'Original Platform' => 'Original Platform',
    'Original Url' => 'Original Url',
    'Original Author' => 'Original Author',
];

\common\messages\zh-CN\model\qq-article-standard-create-param.php

PHP
<?php 
/** * Created by PhpStorm. * User: WangQiang * Date: 2018/08/31 * Time: 18:26 */ 
return [ 
    'Uuid' => '企鹅号ID(UUID)',
    'Article Type Code' => '文章类型代码',
    'Article Category Id' => '文章分类ID',
    'Title' => '标题',
    'Author' => '作者',
    'Source' => '来源',
    'Source User Id' => '来源用户ID',
    'Source Article Id' => '来源文章ID',
    'Content' => '文章内容',
    'Cover Pic' => '文章封面图',
    'Cover Type' => '文章封面类型,1:单图;3:三图',
    'Tag' => '文章标签,以英文半角逗号分隔',
    'Category' => '文章分类编号,即企鹅号的文章类型(文章)的文章分类ID',
    'Apply' => '是否申请原创文章,0:否;1:是(需要用户具有发表图文原创文章资格否则无效)',
    'Original Platform' => '原创首发平台,申请原创文章时必填',
    'Original Url' => '原创首发链接,申请原创文章时当选择平台不是企鹅号时必填',
    'Original Author' => '原创首发作者,申请原创文章时当选择平台不是企鹅号时必填',
];

6. In the application’s model directory, edit \qq\models\Article.php to define the scenarios.

PHP
<?php
 
namespace qq\models;
 
/**
 * This is the model class for table "{{%article}}".
 *
 * @see Article
 */
class Article extends \common\logics\Article
{
    const SCENARIO_STANDARD_CREATE = 'qq_article_standard_create';
 
    /**
     * {@inheritdoc}
     */
    public function scenarios()
    {
        $scenarios = parent::scenarios();
 
        return $scenarios;
    }
 
    /**
     * @inheritdoc
     */
    public function rules()
    {
        $rules = [
        ];
        $parentRules = parent::rules();
 
        return ArrayHelper::merge($rules, $parentRules);
    }
 
    /**
     * {@inheritdoc}
     * @return ArticleQuery the active query used by this AR class.
     */
    public static function find()
    {
        return new ArticleQuery(get_called_class());
    }
}

7. In the common directory’s model data layer, qq_tp_app_penguin (Penguin media user of the Penguin account’s third-party service platform application, QqTpAppPenguin), \common\models\QqTpAppPenguin.php, the code is as follows.

PHP
<?php
 
namespace common\models;
 
use Yii;
 
/**
 * This is the model class for table "{{%qq_tp_app_penguin}}".
 *
 * @property int $id
 * @property string $uuid 企鹅号ID(UUID)
 * @property int $qq_tp_app_id 企鹅号的第三方服务平台应用ID
 * @property string $openid 授权第三方用户的企鹅媒体用户唯一标识
 * @property int $status 状态,-1:删除;0:禁用;1:启用
 * @property int $created_at 创建时间
 * @property int $updated_at 更新时间
 */
class QqTpAppPenguin extends \yii\db\ActiveRecord
{
    /**
     * {@inheritdoc}
     */
    public static function tableName()
    {
        return '{{%qq_tp_app_penguin}}';
    }
 
    /**
     * {@inheritdoc}
     */
    public function rules()
    {
        return [
            [['uuid', 'qq_tp_app_id', 'openid'], 'required'],
            [['qq_tp_app_id', 'status', 'created_at', 'updated_at'], 'integer'],
            [['uuid'], 'string', 'max' => 64],
            [['openid'], 'string', 'max' => 32],
            [['uuid'], 'unique'],
            [['openid'], 'unique'],
        ];
    }
 
    /**
     * {@inheritdoc}
     */
    public function attributeLabels()
    {
        return [
            'id' => Yii::t('model/qq-tp-app-penguin', 'ID'),
            'uuid' => Yii::t('model/qq-tp-app-penguin', 'Uuid'),
            'qq_tp_app_id' => Yii::t('model/qq-tp-app-penguin', 'Qq Tp App ID'),
            'openid' => Yii::t('model/qq-tp-app-penguin', 'Openid'),
            'status' => Yii::t('model/qq-tp-app-penguin', 'Status'),
            'created_at' => Yii::t('model/qq-tp-app-penguin', 'Created At'),
            'updated_at' => Yii::t('model/qq-tp-app-penguin', 'Updated At'),
        ];
    }
 
    /**
     * {@inheritdoc}
     * @return QqTpAppPenguinQuery the active query used by this AR class.
     */
    public static function find()
    {
        return new QqTpAppPenguinQuery(get_called_class());
    }
}
 

8. In the common directory’s model logic layer, qq_tp_app_penguin (Penguin media user of the Penguin account’s third-party service platform application, QqTpAppPenguin), \common\logics\QqTpAppPenguin.php, the code is as follows.

PHP
<?php
 
namespace common\logics;
 
use Yii;
use yii\behaviors\TimestampBehavior;
use yii2tech\ar\softdelete\SoftDeleteBehavior;
use common\behaviors\UUIDBehavior;
use yii\helpers\ArrayHelper;
 
class QqTpAppPenguin extends \common\models\QqTpAppPenguin
{
    const STATUS_DELETED = -1; //状态:删除
    const STATUS_DISABLED = 0; //状态:禁用
    const STATUS_ENABLED = 1; //状态:启用
 
    /**
     * @inheritdoc
     */
    public function behaviors()
    {
        return [
            'timestampBehavior' => [
                'class' => TimestampBehavior::className(),
                'attributes' => [
                    self::EVENT_BEFORE_INSERT => ['created_at', 'updated_at'],
                    self::EVENT_BEFORE_UPDATE => 'updated_at',
                    SoftDeleteBehavior::EVENT_BEFORE_SOFT_DELETE => 'updated_at',
                ]
            ],
            'uuid' => [
                'class' => UUIDBehavior::className(),
                'column' => 'uuid',
            ],
            'softDeleteBehavior' => [
                'class' => SoftDeleteBehavior::className(),
                'softDeleteAttributeValues' => [
                    'status' => self::STATUS_DELETED
                ],
            ],
        ];
    }
 
    /**
     * {@inheritdoc}
     */
    public function scenarios()
    {
        $scenarios = parent::scenarios();
 
        return $scenarios;
    }
 
    /**
     * @inheritdoc
     */
    public function rules()
    {
        $rules = [
        ];
        $parentRules = parent::rules();
 
        return ArrayHelper::merge($rules, $parentRules);
    }
 
    /**
     * {@inheritdoc}
     * @return QqTpAppPenguinQuery the active query used by this AR class.
     */
    public static function find()
    {
        return new QqTpAppPenguinQuery(get_called_class());
    }
 
}

9. In the application’s model directory, qq_tp_app_penguin (Penguin media user of the Penguin account’s third-party service platform application, QqTpAppPenguin), \qq\models\QqTpAppPenguin.php, define the validation rules for the specific scenario. The code is as follows.

PHP
<?php
/**
 * Created by PhpStorm.
 * User: WangQiang
 * Date: 2018/08/28
 * Time: 15:10
 */
 
namespace qq\models;
 
use Yii;
use yii\helpers\ArrayHelper;
use yii\web\ServerErrorHttpException;
 
/**
 * This is the model class for table "{{%qq_tp_app_penguin}}".
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since 1.0
 */
class QqTpAppPenguin extends \common\logics\QqTpAppPenguin
{
    /**
     * {@inheritdoc}
     */
    public function scenarios()
    {
        $scenarios = parent::scenarios();
        $scenarios[Article::SCENARIO_STANDARD_CREATE] = ['uuid'];

        return $scenarios;
    }

    /**
     * @inheritdoc
     */
    public function rules()
    {
        $rules = [
            /* 发布文章类型:标准(普通、图文)的文章 */
            [['uuid'], 'required', 'on' => Article::SCENARIO_STANDARD_CREATE],
            ['uuid', 'exist', 'filter' => ['status' => self::STATUS_ENABLED], 'on' => Article::SCENARIO_STANDARD_CREATE],
            [['qq_tp_app_id', 'openid'], 'required'],
        ];
        $parentRules = parent::rules();
        unset($parentRules[0], $parentRules[4]);

        return ArrayHelper::merge($rules, $parentRules);
    }

    /**
     * {@inheritdoc}
     * @return QqTpAppPenguinQuery the active query used by this AR class.
     */
    public static function find()
    {
        return new QqTpAppPenguinQuery(get_called_class());
    }
}


10. Edit the method file \qq\rests\article\CreateAction.php.

PHP
<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */
 
namespace qq\rests\article;
 
use Yii;
use yii\base\Model;
use qq\models\QqArticleStandardCreateParam;
use qq\models\ArticleType;
use qq\models\Article;
use qq\models\QqTpAppPenguin;
use qq\services\QqArticleService;
use yii\helpers\Url;
use yii\helpers\ArrayHelper;
use yii\web\ServerErrorHttpException;
 
/**
 * 发布文章类型:标准(普通、图文)的文章至渠道发布
 *
 * 1、请求参数列表
 * (1)uuid:必填,企鹅号ID(UUID)
 * (2)article_type_code:必填,文章类型代码,standard:标准(普通)
 * (3)article_category_id:必填,文章分类ID
 * (4)title:必填,标题
 * (5)author:可选,作者,默认:空字符串
 * (6)source:必填,来源,xContent:内容库;vms:视频管理系统;cms:内容管理系统;spider:自媒体
 * (7)source_user_id:必填,来源用户ID
 * (8)source_article_id:必填,来源文章ID
 * (9)content:必填,文章内容
 * (10)cover_pic:必填,文章封面图
 * (11)cover_type:可选,文章封面类型,1:单图;3:三图,默认:1
 * (12)tag:可选,文章标签,以英文半角逗号分隔,默认:空字符串
 * (13)apply:可选,是否申请原创文章,0:否;1:是(需要用户具有发表图文原创文章资格否则无效),默认:0
 * (14)original_platform:可选,原创首发平台,申请原创文章时必填,默认:0
 * (15)original_url:可选,原创首发链接,申请原创文章时当选择平台不是企鹅号时必填,默认:空字符串
 * (16)original_author:可选,原创首发作者,申请原创文章时当选择平台不是企鹅号时必填,默认:空字符串
 *
 * 2、输入数据验证规则
 * (1)必填:uuid、article_type_code、article_category_id、title、source、source_user_id、source_article_id、content、cover_pic
 * (2)默认值(''):author、tag、original_url、original_author
 * (3)默认值(1):cover_type
 * (4)默认值(0):apply、original_platform
 * (5)比对:article_type_code 其值必须等于 standard
 * (6)存在性:uuid 必须存在于企鹅号的第三方服务平台应用的企鹅媒体用户模型中,且其状态为 1:启用
 *
 * For more details and usage information on CreateAction, see the [guide article on rest controllers](guide:rest-controllers).
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since 1.0
 */
class CreateAction extends Action
{
    /**
     * @var string the scenario to be assigned to the new model before it is validated and saved.
     */
    public $scenario = Model::SCENARIO_DEFAULT;
    /**
     * @var string the name of the view action. This property is need to create the URL when the model is successfully created.
     */
    public $viewAction = 'view';


    /**
     * 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']];
        }

        return ['code' => 10000, 'message' => Yii::t('app', '10003'), 'data' => ''];
    }

    /**
     * 处理模型填充与验证
     * @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.');
        }
    }
}


11. Post http://api.channel-pub-api.localhost/qq/v1/articles?group_id=015ce30b116ce86058fa6ab4fea4ac63 , response: Data validation failed: Penguin account ID (UUID) cannot be blank. This meets expectations.

JSON

{
}

JSON

{
    "code": 20004,
    "message": "数据验证失败:企鹅号ID(UUID)不能为空。"
}

12. Post http://api.channel-pub-api.localhost/qq/v1/articles?group_id=015ce30b116ce86058fa6ab4fea4ac63 , when the parameter: original_author is missing, print: $qqArticleStandardCreateParam, the value of original_author is an empty string, which meets expectations.

JSON

{
	"uuid": "e88e79faad9011e8a14554ee75d2ebc1",
	"article_type_code": "standard",
	"article_category_id": 1,
	"title": "标题 - 20180901 - 1",
	"author": "作者 - 20180901 - 1",
	"source": "spider",
	"source_user_id": 1,
	"source_article_id": 1,
	"content": "文章内容 - 20180901 - 1",
	"cover_pic": "http://b.hiphotos.baidu.com/image/pic/item/1e30e924b899a901a5be490c10950a7b0208f505.jpg",
	"cover_type": 1,
	"tag": "",
	"apply": 0,
	"original_platform": 0,
	"original_url": ""
}

Plaintext
qq\models\QqArticleStandardCreateParam Object
(
    [uuid] => e88e79faad9011e8a14554ee75d2ebc1
    [article_type_code] => standard
    [article_category_id] => 1
    [title] => 标题 - 20180901 - 1
    [author] => 作者 - 20180901 - 1
    [source] => spider
    [source_user_id] => 1
    [source_article_id] => 1
    [content] => 文章内容 - 20180901 - 1
    [cover_pic] => http://b.hiphotos.baidu.com/image/pic/item/1e30e924b899a901a5be490c10950a7b0208f505.jpg
    [cover_type] => 1
    [tag] => 
    [apply] => 0
    [original_platform] => 0
    [original_url] => 
    [original_author] => 
    [_errors:yii\base\Model:private] => Array
        (
        )
)

13. Post http://api.channel-pub-api.localhost/qq/v1/articles?group_id=015ce30b116ce86058fa6ab4fea4ac63 , parameter: uuid does not exist in the Penguin media user model of the Penguin account’s third-party service platform application, response: Data validation failed: Penguin account ID (UUID) is invalid. This meets expectations, as shown in Figure 2.

Post http://api.channel-pub-api.localhost/qq/v1/articles?group_id=015ce30b116ce86058fa6ab4fea4ac63 , parameter: uuid does not exist in the Penguin media user model of the Penguin account's third-party service platform application, response: Data validation failed: Penguin account ID (UUID) is invalid. Meets expectations
Figure 2
JSON

{
	"uuid": "e88e79faad9011e8a14554ee75d2ebc10",
	"article_type_code": "standard",
	"article_category_id": 1,
	"title": "标题 - 20180901 - 1",
	"author": "作者 - 20180901 - 1",
	"source": "spider",
	"source_user_id": 1,
	"source_article_id": 1,
	"content": "文章内容 - 20180901 - 1",
	"cover_pic": "http://b.hiphotos.baidu.com/image/pic/item/1e30e924b899a901a5be490c10950a7b0208f505.jpg",
	"cover_type": 1,
	"tag": "",
	"apply": 0,
	"original_platform": 0,
	"original_url": "",
	"original_author": ""
}

JSON

{
    "code": 20004,
    "message": "数据验证失败:企鹅号ID(UUID)是无效的。"
}

14. View the log for the SQL statements executed during the validation process.

SQL

	SELECT EXISTS(SELECT * FROM `cpa_qq_tp_app_penguin` WHERE (`cpa_qq_tp_app_penguin`.`uuid`='e88e79faad9011e8a14554ee75d2ebc10') AND (`status`=1))

15. The validation for subsequent models can follow the same validation scheme as uuid, thereby achieving data population and validation when request parameters involve multiple models. The final code for \qq\rests\article\CreateAction.php is as follows.

SQL
<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */
 
namespace qq\rests\article;
 
use Yii;
use yii\base\Model;
use qq\models\QqArticleStandardCreateParam;
use qq\models\ArticleCategory;
use qq\models\QqArticleCategoryNormal;
use qq\models\Article;
use qq\models\QqTpAppPenguin;
use qq\models\QqArticleNormal;
use qq\models\redis\qq_auth\QqTpAppAccessToken as RedisQqAuthQqTpAppAccessToken;
use qq\services\QqArticleService;
use yii\helpers\Url;
use yii\helpers\ArrayHelper;
use yii\web\ServerErrorHttpException;
use yii\web\HttpException;
 
/**
 * 发布文章类型:标准(普通、图文)的文章至渠道发布
 *
 * 1、请求参数列表
 * (1)uuid:必填,企鹅号ID(UUID)
 * (2)article_type_code:必填,文章类型代码,standard:标准(普通)
 * (3)article_category_id:必填,文章分类ID
 * (4)title:必填,标题
 * (5)author:可选,作者,默认:空字符串
 * (6)source:必填,来源,xContent:内容库;vms:视频管理系统;cms:内容管理系统;spider:自媒体
 * (7)source_user_id:必填,来源用户ID
 * (8)source_article_id:必填,来源文章ID
 * (9)content:必填,文章内容
 * (10)cover_pic:必填,文章封面图
 * (11)cover_type:可选,文章封面类型,1:单图;3:三图,默认:1
 * (12)tag:可选,文章标签,以英文半角逗号分隔,默认:空字符串
 * (13)apply:可选,是否申请原创文章,0:否;1:是(需要用户具有发表图文原创文章资格否则无效),默认:0
 * (14)original_platform:可选,原创首发平台,申请原创文章时必填,默认:0
 * (15)original_url:可选,原创首发链接,申请原创文章时当选择平台不是企鹅号时必填,默认:空字符串
 * (16)original_author:可选,原创首发作者,申请原创文章时当选择平台不是企鹅号时必填,默认:空字符串
 *
 * 2、输入数据验证规则
 * (1)必填:uuid、article_type_code、article_category_id、title、source、source_user_id、source_article_id、content、cover_pic
 * (2)默认值(''):author、tag、original_url、original_author
 * (3)默认值(1):cover_type
 * (4)默认值(0):apply、original_platform
 * (5)比对:article_type_code 其值必须等于 standard
 * (6)存在性:uuid 必须存在于企鹅号的第三方服务平台应用的企鹅媒体用户模型中,且其状态为 1:启用
 * (7)存在性:article_category_id 必须存在于文章分类模型中,且其状态为 1:启用
 * (8)存在性:article_category_id 必须存在于企鹅号的文章类型(文章)的文章分类模型中,且其状态为 1:启用
 * (9)字符串(最大长度:32):group_id、source
 * (10)字符串(最大长度:64):author
 * (11)字符串(最大长度:255):title
 * (12)整数:source_user_id、source_article_id
 * (13)范围(['xContent', 'vms', 'cms', 'spider']):source
 * (14)字符串:content
 * (15)字符串(最大长度:64):original_author
 * (16)字符串(最大长度:255):cover_pic、tag、original_url
 * (17)整数:cover_type、apply、original_platform
 * (18)范围([1, 3]):cover_type
 * (19)范围([0, 1]):apply
 * (20)范围([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]):original_platform
 * (21)网址:original_url
 * (22)必填:original_platform(当 apply 等于 1 时)
 * (23)范围([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]):original_platform(当 apply 等于 1 时)
 * (24)必填:original_url(当 apply 等于 1 且 original_platform 不等于 1 时)
 * (25)必填:original_author(当 apply 等于 1 且 original_platform 不等于 1 时)
 * (26)存在性:uuid 必须存在于企鹅号的第三方服务平台应用的访问令牌(Redis)模型中,且其状态为 1:启用
 * (27)用户刷新令牌有效截止时间必须 大于等于 服务器时间
 *
 * 3、操作数据(事务)
 * (1)插入数据
 *
 * For more details and usage information on CreateAction, see the [guide article on rest controllers](guide:rest-controllers).
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since 1.0
 */
class CreateAction extends Action
{
    /**
     * @var string the scenario to be assigned to the new model before it is validated and saved.
     */
    public $scenario = Model::SCENARIO_DEFAULT;
    /**
     * @var string the name of the view action. This property is need to create the URL when the model is successfully created.
     */
    public $viewAction = 'view';


    /**
     * 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']];
        }

        // 文章分类
        $articleCategory = new ArticleCategory([
            'scenario' => $this->scenario,
        ]);
        // 转换标准(普通、图文)的文章发布参数,多模型的填充、验证的实现
        $requestParams[$articleCategory->formName()]['id'] = $qqArticleStandardCreateParam->article_category_id;
        $articleCategoryResult = self::handleLoadAndValidate($articleCategory, $requestParams);
        if ($articleCategoryResult['status'] === false) {
            return ['code' => $articleCategoryResult['code'], 'message' => $articleCategoryResult['message']];
        }

        // 如果当前场景为:qq_article_standard_create
        if ($this->scenario == Article::SCENARIO_STANDARD_CREATE) {
            // 文章分类
            $qqArticleCategoryNormal = new QqArticleCategoryNormal([
                'scenario' => $this->scenario,
            ]);
            // 转换标准(普通、图文)的文章发布参数,多模型的填充、验证的实现
            $requestParams[$qqArticleCategoryNormal->formName()]['article_category_id'] = $qqArticleStandardCreateParam->article_category_id;
            $qqArticleCategoryNormalResult = self::handleLoadAndValidate($qqArticleCategoryNormal, $requestParams);
            if ($qqArticleCategoryNormalResult['status'] === false) {
                return ['code' => $qqArticleCategoryNormalResult['code'], 'message' => $qqArticleCategoryNormalResult['message']];
            }
        }

        // 文章
        $model = new $this->modelClass([
            'scenario' => $this->scenario,
        ]);
        // 转换标准(普通、图文)的文章发布参数,多模型的填充、验证的实现
        $requestParams[$model->formName()] = [
            'group_id' => $requestParams['group_id'],
            'title' => $qqArticleStandardCreateParam->title,
            'author' => $qqArticleStandardCreateParam->author,
            'source' => $qqArticleStandardCreateParam->source,
            'source_user_id' => $qqArticleStandardCreateParam->source_user_id,
            'source_article_id' => $qqArticleStandardCreateParam->source_article_id,
        ];
        $modelResult = self::handleLoadAndValidate($model, $requestParams);
        if ($modelResult['status'] === false) {
            return ['code' => $modelResult['code'], 'message' => $modelResult['message']];
        }

        // 如果当前场景为:qq_article_standard_create
        if ($this->scenario == Article::SCENARIO_STANDARD_CREATE) {
            // 企鹅号的文章类型(文章)的文章
            $qqArticleNormal = new QqArticleNormal([
                'scenario' => $this->scenario,
            ]);
            // 转换标准(普通、图文)的文章发布参数,多模型的填充、验证的实现
            $requestParams[$qqArticleNormal->formName()] = [
                'content' => $qqArticleStandardCreateParam->content,
                'cover_pic' => $qqArticleStandardCreateParam->cover_pic,
                'cover_type' => $qqArticleStandardCreateParam->cover_type,
                'tag' => $qqArticleStandardCreateParam->tag,
                'apply' => $qqArticleStandardCreateParam->apply,
                'original_platform' => $qqArticleStandardCreateParam->original_platform,
                'original_url' => $qqArticleStandardCreateParam->original_url,
                'original_author' => $qqArticleStandardCreateParam->original_author,
            ];
            $qqArticleNormalResult = self::handleLoadAndValidate($qqArticleNormal, $requestParams);
            if ($qqArticleNormalResult['status'] === false) {
                return ['code' => $qqArticleNormalResult['code'], 'message' => $qqArticleNormalResult['message']];
            }
        }

        // 企鹅号的第三方服务平台应用的访问令牌(Redis)
        $redisQqAuthQqTpAppAccessToken = new RedisQqAuthQqTpAppAccessToken([
            'scenario' => $this->scenario,
        ]);
        // 转换标准(普通、图文)的文章发布参数,多模型的填充、验证的实现
        $requestParams[$redisQqAuthQqTpAppAccessToken->formName()]['qq_tp_app_penguin_uuid'] = $qqArticleStandardCreateParam->uuid;
        $redisQqAuthQqTpAppAccessTokenResult = self::handleLoadAndValidate($redisQqAuthQqTpAppAccessToken, $requestParams);
        if ($redisQqAuthQqTpAppAccessTokenResult['status'] === false) {
            throw new HttpException(302, Yii::t('error', '40008'), 40008);
        }

        return ['code' => 10000, 'message' => Yii::t('app', '10003'), 'data' => ''];
    }

    /**
     * 处理模型填充与验证
     * @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.');
        }
    }
}


关于作者 · 持续学习与技术实践

我是一名拥有 15 年以上开发经验的技术从业者,自 2013 年起持续运营个人技术博客,记录工作与个人项目中遇到的真实问题,以及解决问题过程中的探索、实践和思考。

除了技术写作,我也在持续开发和维护自己的独立项目,探索新技术和新工具在实际场景中的应用。我始终相信,没有不值得去解决的问题,也没有不值得去学习的技术。

如果这篇文章对你有所帮助,欢迎浏览博客中的其他文章,也欢迎交流技术经验与想法。

关于我  ·  GitHub  ·  邮件联系