models – 永夜 https://www.shuijingwanwq.com 没有不值得去解决的问题,也没有不值得去学习的技术! Sun, 17 May 2026 09:38:01 +0000 zh-Hans hourly 1 https://wordpress.org/?v=7.0 基于 yiisoft/yii2-app-advanced,实现 RESTful 风格的 Web Service 服务的 API,请求参数为多模型时,数据填充、验证的实现 https://www.shuijingwanwq.com/2018/09/01/2889/ https://www.shuijingwanwq.com/2018/09/01/2889/#respond Sat, 01 Sep 2018 09:34:44 +0000 http://www.shuijingwanwq.com/?p=2889 浏览量: 87 1、参考网址:https://www.yiiframework.com/doc/guide/2.0/zh-cn/input-multiple-models ,其用于网页表单是合适的,不过 API 应用的请求参数一般并未添加表单名称(尤其是单个模型输入时),因此,不太合适,例:表名为 article_type,字段名为 code,那么请求参数名为 article_type_code,而不是:ArticleType[‘code’],网页表单如图1
参考网址:https://www.yiiframework.com/doc/guide/2.0/zh-cn/input-multiple-models ,其用于网页表单是合适的,不过 API 应用的请求参数一般并未添加表单名称(尤其是单个模型输入时),因此,不太合适,例:表名为 article_type,字段名为 code,那么请求参数名为 article_type_code,而不是:ArticleType['code'],网页表单

图1

2、现在模型数量为7个,分别为:qq_tp_app_penguin(企鹅号的第三方服务平台应用的企鹅媒体用户、QqTpAppPenguin)、article_type(文章类型、ArticleType)、article_category(文章分类、ArticleCategory)、qq_article_category_normal(企鹅号的文章类型(文章)的文章分类、QqArticleCategoryNormal)、article(文章、Article)、qq_article_normal(企鹅号的文章类型(文章)的文章、QqArticleNormal)、qq_tp_app_access_token(企鹅号的第三方服务平台应用的访问令牌(Redis)、RedisQqAuthQqTpAppAccessToken),示例中仅演示前2个模型的填充、验证的实现 3、发布文章类型:标准(普通、图文)的文章至渠道发布 /qq/v1/articles(article/create),请求参数并未严格遵循(表名_字段名)的规则,因为严格遵循的话,会导致字段名过长,且仍然可能存在不容易区分的问题,需要后续手动转换为对应模型的字段


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:启用


4、新建模型文件:\common\logics\QqArticleStandardCreateParam.php,其作用为设置输入数据验证规则中的前4条与第6条、确保所有参数值为存在的,便于转换为对应模型字段时,无需再次判断其值是否存在
<pre class="wp-block-syntaxhighlighter-code">

<?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'),
        ];
    }
}


</pre>
5、对应的语言包文件 \common\messages\en-US\model\qq-article-standard-create-param.php
<pre class="wp-block-syntaxhighlighter-code">

<?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',
];

</pre>
\common\messages\zh-CN\model\qq-article-standard-create-param.php
<pre class="wp-block-syntaxhighlighter-code">

<?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' => '原创首发作者,申请原创文章时当选择平台不是企鹅号时必填',
];

</pre>
6、在应用的模型目录中,编辑 \qq\models\Article.php,定义场景
<pre class="wp-block-syntaxhighlighter-code">

<?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());
    }
}


</pre>
7、公共目录的模型数据层,qq_tp_app_penguin(企鹅号的第三方服务平台应用的企鹅媒体用户、QqTpAppPenguin)、\common\models\QqTpAppPenguin.php,代码如下
<pre class="wp-block-syntaxhighlighter-code">

<?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());
    }
}


</pre>
8、公共目录的模型逻辑层,qq_tp_app_penguin(企鹅号的第三方服务平台应用的企鹅媒体用户、QqTpAppPenguin)、\common\logics\QqTpAppPenguin.php,代码如下
<pre class="wp-block-syntaxhighlighter-code">

<?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());
    }

}


</pre>
9、在应用的模型目录中,qq_tp_app_penguin(企鹅号的第三方服务平台应用的企鹅媒体用户、QqTpAppPenguin)、\qq\models\QqTpAppPenguin.php,定义具体场景的验证规则,代码如下
<pre class="wp-block-syntaxhighlighter-code">

<?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());
    }
}


</pre>
10、编辑方法文件 \qq\rests\article\CreateAction.php
<pre class="wp-block-syntaxhighlighter-code">

<?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.');
        }
    }
}


</pre>
11、Post http://api.channel-pub-api.localhost/qq/v1/articles?group_id=015ce30b116ce86058fa6ab4fea4ac63 ,响应:数据验证失败:企鹅号ID(UUID)不能为空。,符合预期


{
}




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


12、Post http://api.channel-pub-api.localhost/qq/v1/articles?group_id=015ce30b116ce86058fa6ab4fea4ac63 ,缺少参数:original_author 时,打印:$qqArticleStandardCreateParam,original_author 的值为空字符串,符合预期


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




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 ,参数:uuid 不存在于企鹅号的第三方服务平台应用的企鹅媒体用户模型中,响应:数据验证失败:企鹅号ID(UUID)是无效的。,符合预期,如图2
Post http://api.channel-pub-api.localhost/qq/v1/articles?group_id=015ce30b116ce86058fa6ab4fea4ac63 ,参数:uuid 不存在于企鹅号的第三方服务平台应用的企鹅媒体用户模型中,响应:数据验证失败:企鹅号ID(UUID)是无效的。,符合预期

图2



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




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


14、查看日志,其验证过程中的 SQL 语句


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


15、后续其他模型的验证可以沿用 uuid 的验证方案,便可以实现请求参数为多模型时,数据的填充、验证,\qq\rests\article\CreateAction.php 最终的代码
<pre class="wp-block-syntaxhighlighter-code">

<?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.');
        }
    }
}


</pre>
]]>
https://www.shuijingwanwq.com/2018/09/01/2889/feed/ 0
在 Yii 2.0 上,声明与需验证模型特性相关的验证规则,使用了[[yii\behaviors\TimestampBehavior]],重写 [[yii\base\Model::rules()]] 方法的技巧 https://www.shuijingwanwq.com/2018/05/30/2671/ https://www.shuijingwanwq.com/2018/05/30/2671/#respond Wed, 30 May 2018 07:47:47 +0000 http://www.shuijingwanwq.com/?p=2671 浏览量: 98 1、/common/models 目录中的模型类文件仅允许Gii工具所生成,为公共的模型数据层,如图1
/common/models 目录中的模型类文件仅允许Gii工具所生成,为公共的模型数据层

图1



    /**
     * {@inheritdoc}
     */
    public function rules()
    {
        return [
            [['group_id', 'config_column_id', 'plan_id', 'title', 'config_task_id', 'create_user_id', 'create_name', 'exec_user_id', 'exec_name', 'task_data', 'ended_at', 'created_at', 'updated_at'], 'required'],
            [['config_column_id', 'plan_id', 'sort_order', 'config_task_id', 'create_user_id', 'exec_user_id', 'ended_at', 'current_step_id', 'status', 'created_at', 'updated_at'], 'integer'],
            [['task_info', 'task_data'], 'string'],
            [['group_id', 'create_name', 'exec_name'], 'string', 'max' => 32],
            [['title'], 'string', 'max' => 64],
            [['place'], 'string', 'max' => 255],
        ];
    }


2、/common/logics 目录中的模型类文件为业务逻辑相关,继承至 /common/models 数据层,为公共的模型逻辑层,验证规则为场景相关的规则与公共的模型数据层的规则,在必填项中删除 created_at, updated_at


    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [
            /* 指派(创建任务) */
            [ [ 'plan_id', 'title', 'config_task_id', 'exec_user_id'], 'required', 'on' => 'create' ], //create
            [ 'config_task_id', 'exist', 'targetClass' => '\common\logics\ConfigTask', 'targetAttribute' => 'id', 'filter' => [ 'status' => ConfigTask::CONFIG_TASK_STATUS_ENABLE ], 'on' => 'create' ], //create
            [ 'plan_id', 'exist', 'targetClass' => '\common\logics\Plan', 'targetAttribute' => 'id', 'filter' => [ 'status' => Plan::PLAN_STATUS_PASSED ], 'on' => 'create' ], //create
            [ 'create_user_id', 'exist', 'targetClass' => '\common\logics\ConfigColumnUser', 'targetAttribute' => [ 'create_user_id' => 'user_id', 'config_column_id' => 'config_column_id' ], 'filter' => [ 'status' => ConfigColumnUser::CONFIG_COLUMN_USER_STATUS_ENABLE ], 'on' => 'create' ], //create
            [ 'exec_user_id', 'exist', 'targetClass' => '\common\logics\ConfigColumnUser', 'targetAttribute' => [ 'exec_user_id' => 'user_id', 'config_column_id' => 'config_column_id' ], 'filter' => [ 'status' => ConfigColumnUser::CONFIG_COLUMN_USER_STATUS_ENABLE ], 'on' => 'create' ], //create
            [ [ 'place', 'task_info' ], 'default', 'value' => '', 'on' => 'create' ], //create
            /* 我的任务(获取任务列表) */
            [ [ 'status' ], 'in', 'range' => [ self::PLAN_TASK_STATUS_ALL, self::PLAN_TASK_STATUS_NOT_BEGINNING, self::PLAN_TASK_STATUS_BEGINNING, self::PLAN_TASK_STATUS_DONE ], 'on' => 'index' ], //index
            [ [ 'config_task_id' ], 'in', 'range' => array_merge([ ConfigTask::CONFIG_TASK_STATUS_ALL ], ConfigTask::find()->select('id')->where([ 'status' => ConfigTask::CONFIG_TASK_STATUS_ENABLE ])->asArray()->column()), 'on' => 'index' ], //index
            [ [ 'plan_id' ], 'default', 'value' => 0, 'on' => 'index' ], //index
            [ [ 'status', 'config_task_id' ], 'default', 'value' => self::PLAN_TASK_STATUS_ALL, 'on' => 'index' ], //index
            /* 认领(认领任务) */
            [ 'id', 'exist', 'targetClass' => '\common\logics\PlanTask', 'targetAttribute' => [ 'id', 'exec_user_id' ], 'filter' => [ 'status' => self::PLAN_TASK_STATUS_NOT_BEGINNING ], 'on' => 'claim' ], //claim
            /* 转派(转派任务) */
            [ [ 'exec_user_id' ], 'required', 'on' => 'transfer' ], //transfer
            [ 'exec_user_id', 'exist', 'targetClass' => '\common\logics\ConfigColumnUser', 'targetAttribute' => [ 'exec_user_id' => 'user_id', 'config_column_id' => 'config_column_id' ], 'filter' => [ 'status' => ConfigColumnUser::CONFIG_COLUMN_USER_STATUS_ENABLE ], 'on' => 'transfer' ], //transfer
            [ 'id', 'exist', 'targetClass' => '\common\logics\PlanTask', 'filter' => [ 'status' => self::PLAN_TASK_STATUS_NOT_BEGINNING ], 'on' => 'transfer' ], //transfer
            [['group_id', 'config_column_id', 'plan_id', 'title', 'config_task_id', 'create_user_id', 'create_name', 'exec_user_id', 'exec_name', 'task_data', 'ended_at'], 'required'],
            [['config_column_id', 'plan_id', 'sort_order', 'config_task_id', 'create_user_id', 'exec_user_id', 'ended_at', 'current_step_id', 'status', 'created_at', 'updated_at'], 'integer'],
            [['task_info', 'task_data'], 'string'],
            [['group_id', 'create_name', 'exec_name'], 'string', 'max' => 32],
            [['title'], 'string', 'max' => 64],
            [['place'], 'string', 'max' => 255],
        ];


3、公共的模型逻辑层,验证规则方法调整如下,如图2
公共的模型逻辑层,验证规则方法调整如下

图2




use yii\helpers\ArrayHelper;

class PlanTask extends \common\models\PlanTask
{
    /**
     * @inheritdoc
     */
    public function rules()
    {
        $rules = [
            /* 指派(创建任务) */
            [ [ 'plan_id', 'title', 'config_task_id', 'exec_user_id'], 'required', 'on' => 'create' ], //create
            [ 'config_task_id', 'exist', 'targetClass' => '\common\logics\ConfigTask', 'targetAttribute' => 'id', 'filter' => [ 'status' => ConfigTask::CONFIG_TASK_STATUS_ENABLE ], 'on' => 'create' ], //create
            [ 'plan_id', 'exist', 'targetClass' => '\common\logics\Plan', 'targetAttribute' => 'id', 'filter' => [ 'status' => Plan::PLAN_STATUS_PASSED ], 'on' => 'create' ], //create
            [ 'create_user_id', 'exist', 'targetClass' => '\common\logics\ConfigColumnUser', 'targetAttribute' => [ 'create_user_id' => 'user_id', 'config_column_id' => 'config_column_id' ], 'filter' => [ 'status' => ConfigColumnUser::CONFIG_COLUMN_USER_STATUS_ENABLE ], 'on' => 'create' ], //create
            [ 'exec_user_id', 'exist', 'targetClass' => '\common\logics\ConfigColumnUser', 'targetAttribute' => [ 'exec_user_id' => 'user_id', 'config_column_id' => 'config_column_id' ], 'filter' => [ 'status' => ConfigColumnUser::CONFIG_COLUMN_USER_STATUS_ENABLE ], 'on' => 'create' ], //create
            [ [ 'place', 'task_info' ], 'default', 'value' => '', 'on' => 'create' ], //create
            /* 我的任务(获取任务列表) */
            [ [ 'status' ], 'in', 'range' => [ self::PLAN_TASK_STATUS_ALL, self::PLAN_TASK_STATUS_NOT_BEGINNING, self::PLAN_TASK_STATUS_BEGINNING, self::PLAN_TASK_STATUS_DONE ], 'on' => 'index' ], //index
            [ [ 'config_task_id' ], 'in', 'range' => array_merge([ ConfigTask::CONFIG_TASK_STATUS_ALL ], ConfigTask::find()->select('id')->where([ 'status' => ConfigTask::CONFIG_TASK_STATUS_ENABLE ])->asArray()->column()), 'on' => 'index' ], //index
            [ [ 'plan_id' ], 'default', 'value' => 0, 'on' => 'index' ], //index
            [ [ 'status', 'config_task_id' ], 'default', 'value' => self::PLAN_TASK_STATUS_ALL, 'on' => 'index' ], //index
            /* 认领(认领任务) */
            [ 'id', 'exist', 'targetClass' => '\common\logics\PlanTask', 'targetAttribute' => [ 'id', 'exec_user_id' ], 'filter' => [ 'status' => self::PLAN_TASK_STATUS_NOT_BEGINNING ], 'on' => 'claim' ], //claim
            /* 转派(转派任务) */
            [ [ 'exec_user_id' ], 'required', 'on' => 'transfer' ], //transfer
            [ 'exec_user_id', 'exist', 'targetClass' => '\common\logics\ConfigColumnUser', 'targetAttribute' => [ 'exec_user_id' => 'user_id', 'config_column_id' => 'config_column_id' ], 'filter' => [ 'status' => ConfigColumnUser::CONFIG_COLUMN_USER_STATUS_ENABLE ], 'on' => 'transfer' ], //transfer
            [ 'id', 'exist', 'targetClass' => '\common\logics\PlanTask', 'filter' => [ 'status' => self::PLAN_TASK_STATUS_NOT_BEGINNING ], 'on' => 'transfer' ], //transfer
        ];
        $parentRules = parent::rules();
        return ArrayHelper::merge($rules, $parentRules);
    }
}


4、由于使用了 [[yii\behaviors\TimestampBehavior]],这个行为支持在 [[yii\db\ActiveRecord|Active Record]] 存储时自动更新它的时间戳属性,因此需要在验证规则中去掉:created_at, updated_at,设置其默认值为0,如图3
由于使用了 [[yii\behaviors\TimestampBehavior]],这个行为支持在 [[yii\db\ActiveRecord|Active Record]] 存储时自动更新它的时间戳属性,因此需要在验证规则中去掉:created_at, updated_at,设置其默认值为0

图3

5、/common/models 目录中的模型类文件仅允许Gii工具所生成,为公共的模型数据层,生成的验证规则:created_at, updated_at,已经不是必填项


    /**
     * {@inheritdoc}
     */
    public function rules()
    {
        return [
            [['group_id', 'config_column_id', 'plan_id', 'title', 'config_task_id', 'create_user_id', 'create_name', 'exec_user_id', 'exec_name', 'task_data', 'ended_at'], 'required'],
            [['config_column_id', 'plan_id', 'sort_order', 'config_task_id', 'create_user_id', 'exec_user_id', 'ended_at', 'current_step_id', 'status', 'created_at', 'updated_at'], 'integer'],
            [['task_info', 'task_data'], 'string'],
            [['group_id', 'create_name', 'exec_name'], 'string', 'max' => 32],
            [['title'], 'string', 'max' => 64],
            [['place'], 'string', 'max' => 255],
        ];
    }


]]>
https://www.shuijingwanwq.com/2018/05/30/2671/feed/ 0
基于 yiisoft/yii2-app-advanced,在 GitHub 上新建仓库 yii2-app-advanced,新建接口应用(实现 RESTful 风格的 Web Service 服务的 API),实现模型分层:数据层、逻辑层,明确公共目录、应用、模块的继承、引用关系 https://www.shuijingwanwq.com/2018/04/04/2559/ https://www.shuijingwanwq.com/2018/04/04/2559/#respond Wed, 04 Apr 2018 07:19:26 +0000 http://www.shuijingwanwq.com/?p=2559 浏览量: 1,232

1、定义与规范:
定义:
(1)数据层:models 用于定义数据相关的自动验证和自动完成和数据存取接口;
(2)逻辑层:logics 用于定义数据相关的业务逻辑;

规范:
(1)/common/models 目录中的模型类文件仅允许Gii工具所生成,为公共的模型数据层;
(2)/common/logics 目录中的模型类文件为业务逻辑相关,继承至 /common/models 数据层,为公共的模型逻辑层;
(3)/common 目录中需要引用模型类文件,仅引用 /common/logics 中的模型类文件,例:


public $modelClass = 'common\logics\User';


(4)/api/models、/backend/models、/frontend/models 目录中的模型类文件为业务逻辑相关(仅与各应用相关),继承至 /common/logics 公共逻辑层;
(5)接口、前端、后端应用目录中需要引用模型类文件,仅引用各自目录下的对应模型类文件,例:


use api\models\User;


(6)/api/modules/v1/models 目录中的模型类文件为业务逻辑相关(仅与模块相关),继承至 /api/models 应用逻辑层;
(7)模块目录中需要引用模型类文件,仅引用各自模块下的对应模型类文件,例:


public $modelClass = 'api\modules\v1\models\User';


2、在common目录中新建logics目录,用于MySQL模型的逻辑层所在目录,如图1

在common目录中新建logics目录,用于MySQL模型的逻辑层所在目录

图1

3、复制 \common\models\LoginForm.php、\common\models\User.php 至 \common\logics,如图2

复制 \common\models\LoginForm.php、\common\models\User.php 至 \common\logics

图2

4、在common/models目录中的MySQL模型文件仅为Gii工具所生成,删除 \common\models\LoginForm.php,如图3

在common/models目录中的MySQL模型文件仅为Gii工具所生成,删除 \common\models\LoginForm.php

图3

5、配置路由,使用美观的 URL ,编辑 \frontend\config\main.php


        'urlManager' => [
            'enablePrettyUrl' => true,
            'showScriptName' => false,
            'rules' => [
            ],
        ],


6、国际化的支持,新建目录 \common\messages\en-US\model、\common\messages\zh-CN\model,分别用于模型的数据层的美国英语、中文简体;新建 \common\messages\{language}\app.php、\common\messages\{language}\error.php、\common\messages\{language}\success.php,分别用于应用全局消息、应用错误消息、应用成功消息,如图4

国际化的支持,新建目录 \common\messages\en-US\model、\common\messages\zh-CN\model,分别用于模型的数据层的美国英语、中文简体;新建 \common\messages\{language}\app.php、\common\messages\{language}\error.php、\common\messages\{language}\success.php,分别用于应用全局消息、应用错误消息、应用成功消息

图4


app.php

<?php
return [
];

error.php

<?php
return [
    20000 => 'error',
];

success.php

<?php
return [
    10000 => 'success',
];

7、配置应用程序语言,源语言为美国英语,目标语言为简体中文,当源语言和目标语言相同时,是否强制进行消息翻译,默认为假,设置为真,编辑 \common\config\main.php


    'sourceLanguage' => 'en-US',
    'language' => 'zh-CN',
    'components' => [
        'i18n' => [
            'translations' => [
                '*' => [
                    'class' => 'yii\i18n\PhpMessageSource',
                    'forceTranslation' => true,
                    'basePath'=>'@common/messages',
                    'fileMap' => [
                        'app' => 'app.php',
                        'error' => 'error.php',
                        'success' => 'success.php',
                    ],
                ],
            ],
        ],
        'cache' => [
            'class' => 'yii\caching\FileCache',
        ],
    ],


8、打开网址:http://www.github-shuijingwan-yii2-app-advanced.localhost/gii/model ,选项,命名空间为common\models,此时需支持国际化,覆盖\common\models\User.php,如图5

打开网址:http://www.github-shuijingwan-yii2-app-advanced.localhost/gii/model ,选项,命名空间为common\models,此时需支持国际化,覆盖\common\models\User.php

图5

<?php
 
namespace common\models;
 
use Yii;
 
/**
 * This is the model class for table "{{%user}}".
 *
 * @property int $id
 * @property string $username
 * @property string $auth_key
 * @property string $password_hash
 * @property string $password_reset_token
 * @property string $email
 * @property int $status
 * @property int $created_at
 * @property int $updated_at
 */
class User extends \yii\db\ActiveRecord
{
    /**
     * @inheritdoc
     */
    public static function tableName()
    {
        return '{{%user}}';
    }
 
    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [
            [['username', 'auth_key', 'password_hash', 'email', 'created_at', 'updated_at'], 'required'],
            [['status', 'created_at', 'updated_at'], 'integer'],
            [['username', 'password_hash', 'password_reset_token', 'email'], 'string', 'max' => 255],
            [['auth_key'], 'string', 'max' => 32],
            [['username'], 'unique'],
            [['email'], 'unique'],
            [['password_reset_token'], 'unique'],
        ];
    }
 
    /**
     * @inheritdoc
     */
    public function attributeLabels()
    {
        return [
            'id' => Yii::t('model/user', 'ID'),
            'username' => Yii::t('model/user', 'Username'),
            'auth_key' => Yii::t('model/user', 'Auth Key'),
            'password_hash' => Yii::t('model/user', 'Password Hash'),
            'password_reset_token' => Yii::t('model/user', 'Password Reset Token'),
            'email' => Yii::t('model/user', 'Email'),
            'status' => Yii::t('model/user', 'Status'),
            'created_at' => Yii::t('model/user', 'Created At'),
            'updated_at' => Yii::t('model/user', 'Updated At'),
        ];
    }
}
 
 

9、在common/logics目录中的MySQL模型文件为业务逻辑相关,继承至 \common\models 数据层,打开网址:http://www.github-shuijingwan-yii2-app-advanced.localhost/gii/model ,选项,如图6

在common/logics目录中的MySQL模型文件为业务逻辑相关,继承至 \common\models 数据层,打开网址:http://www.github-shuijingwan-yii2-app-advanced.localhost/gii/model ,选项
图6

10、基于 diff ,编辑 \common\logics\User.php,如图7

11、在common/logics目录中的MySQL模型文件为业务逻辑相关,继承至 \common\models\User 数据层

<?php
 
namespace common\logics;
 
use Yii;
use yii\base\NotSupportedException;
use yii\behaviors\TimestampBehavior;
use yii\web\IdentityInterface;
 
/**
 * This is the model class for table "{{%user}}".
 *
 * @property int $id
 * @property string $username
 * @property string $auth_key
 * @property string $password_hash
 * @property string $password_reset_token
 * @property string $email
 * @property int $status
 * @property int $created_at
 * @property int $updated_at
 * @property string $password write-only password
 */
class User extends \common\models\User implements IdentityInterface
{
    const STATUS_DELETED = 0;
    const STATUS_ACTIVE = 10;
 
    /**
     * @inheritdoc
     */
    public function behaviors()
    {
        return [
            TimestampBehavior::className(),
        ];
    }
 
    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [
            [['username', 'auth_key', 'password_hash', 'email'], 'required'],
            [['status', 'created_at', 'updated_at'], 'integer'],
            [['username', 'password_hash', 'password_reset_token', 'email'], 'string', 'max' => 255],
            [['auth_key'], 'string', 'max' => 32],
            [['username'], 'unique'],
            [['email'], 'unique'],
            [['password_reset_token'], 'unique'],
            ['status', 'default', 'value' => self::STATUS_ACTIVE],
            ['status', 'in', 'range' => [self::STATUS_ACTIVE, self::STATUS_DELETED]],
        ];
    }
 
    /**
     * @inheritdoc
     */
    public static function findIdentity($id)
    {
        return static::findOne(['id' => $id, 'status' => self::STATUS_ACTIVE]);
    }
 
    /**
     * @inheritdoc
     */
    public static function findIdentityByAccessToken($token, $type = null)
    {
        throw new NotSupportedException('"findIdentityByAccessToken" is not implemented.');
    }
 
    /**
     * Finds user by username
     *
     * @param string $username
     * @return static|null
     */
    public static function findByUsername($username)
    {
        return static::findOne(['username' => $username, 'status' => self::STATUS_ACTIVE]);
    }
 
    /**
     * Finds user by password reset token
     *
     * @param string $token password reset token
     * @return static|null
     */
    public static function findByPasswordResetToken($token)
    {
        if (!static::isPasswordResetTokenValid($token)) {
            return null;
        }
 
        return static::findOne([
            'password_reset_token' => $token,
            'status' => self::STATUS_ACTIVE,
        ]);
    }
 
    /**
     * Finds out if password reset token is valid
     *
     * @param string $token password reset token
     * @return bool
     */
    public static function isPasswordResetTokenValid($token)
    {
        if (empty($token)) {
            return false;
        }
 
        $timestamp = (int) substr($token, strrpos($token, '_') + 1);
        $expire = Yii::$app->params['user.passwordResetTokenExpire'];
        return $timestamp + $expire >= time();
    }
 
    /**
     * @inheritdoc
     */
    public function getId()
    {
        return $this->getPrimaryKey();
    }
 
    /**
     * @inheritdoc
     */
    public function getAuthKey()
    {
        return $this->auth_key;
    }
 
    /**
     * @inheritdoc
     */
    public function validateAuthKey($authKey)
    {
        return $this->getAuthKey() === $authKey;
    }
 
    /**
     * Validates password
     *
     * @param string $password password to validate
     * @return bool if password provided is valid for current user
     */
    public function validatePassword($password)
    {
        return Yii::$app->security->validatePassword($password, $this->password_hash);
    }
 
    /**
     * Generates password hash from password and sets it to the model
     *
     * @param string $password
     */
    public function setPassword($password)
    {
        $this->password_hash = Yii::$app->security->generatePasswordHash($password);
    }
 
    /**
     * Generates "remember me" authentication key
     */
    public function generateAuthKey()
    {
        $this->auth_key = Yii::$app->security->generateRandomString();
    }
 
    /**
     * Generates new password reset token
     */
    public function generatePasswordResetToken()
    {
        $this->password_reset_token = Yii::$app->security->generateRandomString() . '_' . time();
    }
 
    /**
     * Removes password reset token
     */
    public function removePasswordResetToken()
    {
        $this->password_reset_token = null;
    }
}
 

12、新建 \common\messages\en-US\model\user.php,支持目标语言为英语美国时的消息翻译

<?php
/**
 * Created by PhpStorm.
 * User: WangQiang
 * Date: 2018/04/04
 * Time: 10:34
 */
 
return [
    'ID' => 'ID',
    'Username' => 'Username',
    'Auth Key' => 'Auth Key',
    'Password Hash' => 'Password Hash',
    'Password Reset Token' => 'Password Reset Token',
    'Email' => 'Email',
    'Status' => 'Status',
    'Created At' => 'Created At',
    'Updated At' => 'Updated At',
];

13、新建 \common\messages\zh-CN\model\user.php,支持目标语言为简体中文时的消息翻译

<?php
/**
 * Created by PhpStorm.
 * User: WangQiang
 * Date: 2018/04/04
 * Time: 10:39
 */
 
return [
    'ID' => 'ID',
    'Username' => '用户名',
    'Auth Key' => '认证密钥',
    'Password Hash' => '密码哈希',
    'Password Reset Token' => '密码重置令牌',
    'Email' => '邮箱',
    'Status' => '状态',
    'Created At' => '创建时间',
    'Updated At' => '更新时间',
];

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

<?php
/**
 * Created by PhpStorm.
 * User: WangQiang
 * Date: 2018/04/04
 * Time: 10:44
 */
 
namespace api\models;
 
class User extends \common\logics\User
{
 
}

15、复制\api\models\User.php 至 \frontend\models\User.php、\backend\models\User.php,调整为各自的命名空间
\frontend\models\User.php

<?php
/**
 * Created by PhpStorm.
 * User: WangQiang
 * Date: 2018/04/04
 * Time: 10:44
 */
 
namespace frontend\models;
 
class User extends \common\logics\User
{
 
}

\backend\models\User.php

<?php
/**
 * Created by PhpStorm.
 * User: WangQiang
 * Date: 2018/04/04
 * Time: 10:44
 */
 
namespace backend\models;
 
class User extends \common\logics\User
{
 
}

16、在 api 应用中搜索 use common\models\User;,替换为:use api\models\User;,在前台、后台应用中同样类似处理,如图8

在 api 应用中搜索 use common\models\User;,替换为:use api\models\User;,在前台、后台应用中同样类似处理

图8

17、在 api 应用中搜索 common\models\User,替换为:api\models\User,在前台、后台应用中同样类似处理,如图9

在 api 应用中搜索 common\models\User,替换为:api\models\User,在前台、后台应用中同样类似处理

图9

18、编辑 \common\logics\LoginForm.php,调整命名空间

<?php
namespace common\logics;
 
use Yii;
use yii\base\Model;
 
/**
 * Login form
 */
class LoginForm extends Model
{
    public $username;
    public $password;
    public $rememberMe = true;
 
    private $_user;
 
 
    /**
     * {@inheritdoc}
     */
    public function rules()
    {
        return [
            // username and password are both required
            [['username', 'password'], 'required'],
            // rememberMe must be a boolean value
            ['rememberMe', 'boolean'],
            // password is validated by validatePassword()
            ['password', 'validatePassword'],
        ];
    }
 
    /**
     * Validates the password.
     * This method serves as the inline validation for password.
     *
     * @param string $attribute the attribute currently being validated
     * @param array $params the additional name-value pairs given in the rule
     */
    public function validatePassword($attribute, $params)
    {
        if (!$this->hasErrors()) {
            $user = $this->getUser();
            if (!$user || !$user->validatePassword($this->password)) {
                $this->addError($attribute, 'Incorrect username or password.');
            }
        }
    }
 
    /**
     * Logs in a user using the provided username and password.
     *
     * @return bool whether the user is logged in successfully
     */
    public function login()
    {
        if ($this->validate()) {
            return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600 * 24 * 30 : 0);
        }
         
        return false;
    }
 
    /**
     * Finds user by [[username]]
     *
     * @return User|null
     */
    protected function getUser()
    {
        if ($this->_user === null) {
            $this->_user = User::findByUsername($this->username);
        }
 
        return $this->_user;
    }
}
 
 

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

<?php
/**
 * Created by PhpStorm.
 * User: WangQiang
 * Date: 2018/04/04
 * Time: 11:10
 */
 
namespace api\models;
 
 
class LoginForm extends \common\logics\LoginForm
{
 
}

20、复制\api\models\LoginForm.php 至 \frontend\models\LoginForm.php、\backend\models\LoginForm.php,调整为各自的命名空间
\frontend\models\LoginForm.php

<?php
/**
 * Created by PhpStorm.
 * User: WangQiang
 * Date: 2018/04/04
 * Time: 11:10
 */
 
namespace frontend\models;
 
 
class LoginForm extends \common\logics\LoginForm
{
 
}

\backend\models\LoginForm.php

<?php
/**
 * Created by PhpStorm.
 * User: WangQiang
 * Date: 2018/04/04
 * Time: 11:10
 */
 
namespace backend\models;
 
 
class LoginForm extends \common\logics\LoginForm
{
 
}

21、在 api 应用中搜索 use common\models\LoginForm;,替换为:use api\models\LoginForm;,在前台、后台应用中同样类似处理,如图10

在 api 应用中搜索 use common\models\LoginForm;,替换为:use api\models\LoginForm;,在前台、后台应用中同样类似处理

图10

22、在 api 应用中搜索 common\models\LoginForm,替换为:api\models\LoginForm,在前台、后台应用中同样类似处理,如图11

在 api 应用中搜索 common\models\LoginForm,替换为:api\models\LoginForm,在前台、后台应用中同样类似处理

图11

23、在 common 目录中搜索 common\models\User,替换为:common\logics\User,除了 \common\logics\User.php 例外,如图12

在 common 目录中搜索 common\models\User,替换为:common\logics\User,除了 \common\logics\User.php 例外

图12

24、在 common 目录中搜索 common\models\LoginForm,替换为:common\logics\LoginForm,如图13

在 common 目录中搜索 common\models\LoginForm,替换为:common\logics\LoginForm

图13

25、测试注册功能,打开网址:http://www.github-shuijingwan-yii2-app-advanced.localhost/site/signup ,符合预期,如图14

测试注册功能,打开网址:http://www.github-shuijingwan-yii2-app-advanced.localhost/site/signup ,符合预期

图14

26、查看数据库,如图15

查看数据库

图15

 

]]>
https://www.shuijingwanwq.com/2018/04/04/2559/feed/ 0
在 Yii 2.0 高级模板 中实现 模型分层:数据层、逻辑层 的流程 https://www.shuijingwanwq.com/2017/08/15/1713/ https://www.shuijingwanwq.com/2017/08/15/1713/#respond Tue, 15 Aug 2017 07:10:48 +0000 http://www.shuijingwanwq.com/?p=1713 浏览量: 117

1、定义:
数据层:models 用于定义数据相关的自动验证和自动完成和数据存取接口。
逻辑层:logics 用于定义数据相关的业务逻辑。

2、在common目录中新建logics目录,用于MySQL模型的逻辑层所在目录,如图1

在common目录中新建logics目录,用于MySQL模型的逻辑层所在目录

图1

3、在common/logics目录中新建redis目录,用于Redis(ActiveRecord)模型的逻辑层所在目录,如图2

在common/logics目录中新建redis目录,用于Redis(ActiveRecord)模型的逻辑层所在目录

图2

4、在common/models目录中的MySQL模型文件为Gii工具所生成,如图3

在common/models目录中的MySQL模型文件为Gii工具所生成

图3

5、在common/models/redis目录中的Redis(ActiveRecord)模型文件为模型字段结构的定义,如图4

在common/models/redis目录中的Redis(ActiveRecord)模型文件为模型字段结构的定义

图4

6、在common/logics目录中的MySQL模型文件为业务逻辑相关,继承至 \common\models\Game 数据层,如图5

api\models
common\logics

在common/logics目录中的MySQL模型文件为业务逻辑相关,继承至 \common\models\Game 数据层

图5

7、在common/logics/redis目录中的Redis(ActiveRecord)模型文件为业务逻辑相关,继承至 \common\models\redis\Game 数据层,如图6

api\models\redis
common\logics\redis

api\models
common\models

Api
Common

common\models\redis\Lock
common\logics\redis\Lock

common\models\redis\Live
common\logics\redis\Live

$api
$common

在common/logics/redis目录中的Redis(ActiveRecord)模型文件为业务逻辑相关,继承至 \common\models\redis\Game 数据层

图6

8、在common/logics/redis目录中的Redis(原生命令)模型文件为业务逻辑相关,不需要继承至 \common\models\redis\Game 数据层,而是直接继承至 \yii\redis\ActiveRecord,如图7

在common/logics/redis目录中的Redis(原生命令)模型文件为业务逻辑相关,不需要继承至 \common\models\redis\Game 数据层,而是直接继承至 \yii\redis\ActiveRecord

图7

9、在api/models目录中的MySQL模型文件为业务逻辑相关,继承至 \common\logics\Game 逻辑层,如图8

\common\models
\common\logics

在api/models目录中的MySQL模型文件为业务逻辑相关,继承至 \common\logics\Game 逻辑层

图8

10、在api/models/redis目录中的Redis(ActiveRecord/原生命令)模型文件为业务逻辑相关,继承至 \common\logics\redis\Game 逻辑层,如图9

common\models\redis\Lock
common\logics\redis\Lock

\common\models\redis
\common\logics\redis

在api/models/redis目录中的Redis(ActiveRecord/原生命令)模型文件为业务逻辑相关,继承至 \common\logics\redis\Game 逻辑层

图9

]]>
https://www.shuijingwanwq.com/2017/08/15/1713/feed/ 0