1、查看 rest/ActiveController.php,发现默认的动作方法列表

    /**
     * {@inheritdoc}
     */    public function actions()
    {
        return [
            'index' => [
                'class' => 'yii\rest\IndexAction',
                'modelClass' => $this->modelClass,
                'checkAccess' => [$this, 'checkAccess'],
            ],
            'view' => [
                'class' => 'yii\rest\ViewAction',
                'modelClass' => $this->modelClass,
                'checkAccess' => [$this, 'checkAccess'],
            ],
            'create' => [
                'class' => 'yii\rest\CreateAction',
                'modelClass' => $this->modelClass,
                'checkAccess' => [$this, 'checkAccess'],
                'scenario' => $this->createScenario,
            ],
            'update' => [
                'class' => 'yii\rest\UpdateAction',
                'modelClass' => $this->modelClass,
                'checkAccess' => [$this, 'checkAccess'],
                'scenario' => $this->updateScenario,
            ],
            'delete' => [
                'class' => 'yii\rest\DeleteAction',
                'modelClass' => $this->modelClass,
                'checkAccess' => [$this, 'checkAccess'],
            ],
            'options' => [
                'class' => 'yii\rest\OptionsAction',
            ],
        ];
    }

2、yii\rest\ActiveController 默认提供以下动作:

index:按页列出资源
view:返回指定资源的详情
create:创建新的资源
update:更新一个存在的资源
delete:删除指定的资源
options:返回支持的 HTTP 方法

3、现有一个新的需求,在原型中已经体现,当 租户设置 – 选题紧急程度为关闭时,在新建选题页面中,字段:选题紧急程度不显示;,当 租户设置 – 选题紧急程度为开启时,在新建选题页面中,字段:选题紧急程度显示,如图1

图1

4、在现阶段的实现中,RESTful API 仅提供了 3 个接口,分别为:

create:创建新的资源
edit:编辑一个存在的资源(获取表单数据)
update:更新一个存在的资源

5、因此,决定再添加一个新的接口,new:新建新的资源(获取表单数据),动作方法命名为:new,参考来源为 WordPress,之前的动作方法:edit,同样参考自 WordPress,如图2

图2

new:新建新的资源(获取表单数据)

6、新建动作方法文件:api/rests/plan/NewAction.php

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

use Yii;
use api\models\ConfigTask;
use api\models\Plan;
use api\models\redis\cmc_console\User as RedisCmcConsoleUser;
use api\services\ConfigGroupService;

/**
 * 新建选题(获取表单数据):/plans/new(plan/new)
 *
 * For more details and usage information on NewAction, see the [guide article on rest controllers](guide:rest-controllers).
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since 1.0
 */class NewAction extends Action
{
    /**
     * News a new model.
     * @return array the model being displayed
     */    public function run()
    {
        if ($this->checkAccess) {
            call_user_func($this->checkAccess, $this->id);
        }

        // 当前用户的身份实例,未认证用户则为 Null
        /* @var $identity RedisCmcConsoleUser */        $identity = Yii::$app->user->identity;

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

        $model = new $modelClass;

        $data = $model->attributes;

        unset($data['id'], $data['group_id'], $data['create_user_id'], $data['create_name'], $data['emergency_is_open'], $data['material_asset_id'], $data['prev_status'], $data['is_not_isolated'], $data['is_deleted'], $data['created_at'], $data['updated_at'], $data['deleted_at']);

        // 基于租户ID获取状态为启用的租户配置数据
        $configGroupData = ConfigGroupService::getDataEnabledByGroupId($identity->group_id);

        // 给请求参数赋默认值
        $stringAttribute = ['title', 'place', 'opinion', 'content'];
        $intAttribute = ['config_column_id', 'occur_at', 'ended_at', 'importance', 'emergency', 'is_auto_task_create', 'is_united', 'status'];
        $arrayAttribute = ['keyword'];
        $time = time();
        foreach ($data as $key => $value) {
            if (in_array($key, $stringAttribute)) {
                if ($key == 'place') {
                    $data[$key] = $configGroupData['base_location_name'] . $configGroupData['base_location_address'];
                } else {
                    $data[$key] = '';
                }
            }
            if (in_array($key, $intAttribute)) {
                if ($key == 'importance') {
                    $data[$key] = $model::IMPORTANCE_DEFAULT;
                } elseif ($key == 'emergency') {
                    $data[$key] = $model::EMERGENCY_DEFAULT;
                } elseif ($key == 'occur_at') {
                    $data[$key] = $time;
                } elseif ($key == 'ended_at') {
                    $data[$key] = strtotime('+7 day', time());
                } elseif ($key == 'status') {
                    $data[$key] = $model::STATUS_EDITED;
                } else {
                    $data[$key] = 0;
                }
            }
            if (in_array($key, $arrayAttribute)) {
                $data[$key] = [];
            }
        }

        // 素材的资源列表
        $materialAssetsField = 'material_assets';
        $data[$materialAssetsField] = [];

        // 选题的参与用户列表
        $planAttendedExecUsersField = 'plan_attended_exec_user_ids';
        $planAttendedAttendedUsersField = 'plan_attended_attended_user_ids';
        $data[$planAttendedExecUsersField] = [];
        $data[$planAttendedAttendedUsersField] = [];

        // 字段列表(是否允许编辑,0:否;1:是)
        $fields = [];
        $canEditableFields = ['title', 'config_column_id', 'occur_at', 'place', 'content', 'ended_at', 'importance', 'is_auto_task_create', 'keyword', 'opinion', 'is_united', $materialAssetsField, $planAttendedExecUsersField, $planAttendedAttendedUsersField, 'status', 'emergency'];

        // 字段列表(是否显示,0:否;1:是)
        $displayableFields = [];
        $canDisplayableFields = $canEditableFields;

        // 基于租户ID查找资源(模型:任务配置)(状态:启用;是否默认:是)列表
        $configTaskEnabledIsDefaultYesItems = ConfigTask::findAllEnabledIsDefaultYesByGroupId($identity->group_id);

        foreach ($data as $fieldKey => $fieldValue) {
            $fields[$fieldKey] = 0;
            $displayableFields[$fieldKey] = 0;
            if (in_array($fieldKey, $canEditableFields)) {
                $fields[$fieldKey] = 1;
                // 是否自动创建任务 && 不存在默认的任务配置(是否允许编辑,0:否)
                if ($fieldKey == 'is_auto_task_create' && empty($configTaskEnabledIsDefaultYesItems)) {
                    $fields[$fieldKey] = 0;
                }
                // 紧急程度 && 租户配置数据的选题的紧急程度是否开启,0:否(是否允许编辑,0:否)
                if ($fieldKey == 'emergency' && $configGroupData['plan_emergency_is_open'] == $model::EMERGENCY_IS_OPEN_NO) {
                    $fields[$fieldKey] = 0;
                }
            }
            if (in_array($fieldKey, $canDisplayableFields)) {
                $displayableFields[$fieldKey] = 1;
                // 紧急程度 && 租户配置数据的选题的紧急程度是否开启,0:否(是否允许编辑,0:否)
                if ($fieldKey == 'emergency' && $configGroupData['plan_emergency_is_open'] == $model::EMERGENCY_IS_OPEN_NO) {
                    $displayableFields[$fieldKey] = 0;
                }
            }
        }

        $data['fields'] = $fields;
        $data['displayable_fields'] = $displayableFields;

        return ['code' => 10000, 'message' => Yii::t('success', '126046'), 'data' => $data];
    }
}

7、在 Postman 中 GET:http://api.pcs-api.localhost/v1/plans/new ,响应参数,如图3

图3

{
    "code": 10000,
    "message": "新建选题(获取表单数据)成功",
    "data": {
        "title": "",
        "config_column_id": 0,
        "occur_at": 1576648175,
        "place": "南山区的基地名称详细地址",
        "content": "",
        "ended_at": 1577252975,
        "importance": 3,
        "emergency": 3,
        "is_auto_task_create": 0,
        "keyword": [],
        "opinion": "",
        "is_united": 0,
        "status": 1,
        "material_assets": [],
        "plan_attended_exec_user_ids": [],
        "plan_attended_attended_user_ids": [],
        "fields": {
            "title": 1,
            "config_column_id": 1,
            "occur_at": 1,
            "place": 1,
            "content": 1,
            "ended_at": 1,
            "importance": 1,
            "emergency": 1,
            "is_auto_task_create": 1,
            "keyword": 1,
            "opinion": 1,
            "is_united": 1,
            "status": 1,
            "material_assets": 1,
            "plan_attended_exec_user_ids": 1,
            "plan_attended_attended_user_ids": 1
        },
        "displayable_fields": {
            "title": 1,
            "config_column_id": 1,
            "occur_at": 1,
            "place": 1,
            "content": 1,
            "ended_at": 1,
            "importance": 1,
            "emergency": 1,
            "is_auto_task_create": 1,
            "keyword": 1,
            "opinion": 1,
            "is_united": 1,
            "status": 1,
            "material_assets": 1,
            "plan_attended_exec_user_ids": 1,
            "plan_attended_attended_user_ids": 1
        }
    }
}

8、在响应参数中包含一些字段的默认值,前端与移动端在接收到响应数据后,可以基于其渲染表单数据,以保证前端与移动端的默认值的一致性,且默认值由接口控制,在需要调整默认值时,更为灵活。某个字段不存在默认值时,并未赋值为:null,原因在于如果赋值为:null,字段类型无法做到固定不变化。最终基于字段类型分别赋值为:空字符串、0、空数组。不过当某字段的类型为数字时,且其值为 0,客户端无法得知某字段是否具有默认值。

9、决定再添加一个字段:default_fields,用以标识某个字段的值是否为默认值。编辑动作方法文件:api/rests/plan/NewAction.php

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

use Yii;
use api\models\ConfigTask;
use api\models\Plan;
use api\models\redis\cmc_console\User as RedisCmcConsoleUser;
use api\services\ConfigGroupService;

/**
 * 新建选题(获取表单数据):/plans/new(plan/new)
 *
 * For more details and usage information on NewAction, see the [guide article on rest controllers](guide:rest-controllers).
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since 1.0
 */class NewAction extends Action
{
    /**
     * News a new model.
     * @return array the model being displayed
     */    public function run()
    {
        if ($this->checkAccess) {
            call_user_func($this->checkAccess, $this->id);
        }

        // 当前用户的身份实例,未认证用户则为 Null
        /* @var $identity RedisCmcConsoleUser */        $identity = Yii::$app->user->identity;

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

        $model = new $modelClass;

        $data = $model->attributes;

        unset($data['id'], $data['group_id'], $data['create_user_id'], $data['create_name'], $data['emergency_is_open'], $data['material_asset_id'], $data['prev_status'], $data['is_not_isolated'], $data['is_deleted'], $data['created_at'], $data['updated_at'], $data['deleted_at']);

        // 基于租户ID获取状态为启用的租户配置数据
        $configGroupData = ConfigGroupService::getDataEnabledByGroupId($identity->group_id);

        // 给请求参数赋默认值
        $stringAttribute = ['title', 'place', 'opinion', 'content'];
        $intAttribute = ['config_column_id', 'occur_at', 'ended_at', 'importance', 'emergency', 'is_auto_task_create', 'is_united', 'status'];
        $arrayAttribute = ['keyword'];
        $time = time();
        foreach ($data as $key => $value) {
            if (in_array($key, $stringAttribute)) {
                if ($key == 'place') {
                    $data[$key] = $configGroupData['base_location_name'] . $configGroupData['base_location_address'];
                } else {
                    $data[$key] = '';
                }
            }
            if (in_array($key, $intAttribute)) {
                if ($key == 'importance') {
                    $data[$key] = $model::IMPORTANCE_DEFAULT;
                } elseif ($key == 'emergency') {
                    $data[$key] = $model::EMERGENCY_DEFAULT;
                } elseif ($key == 'occur_at') {
                    $data[$key] = $time;
                } elseif ($key == 'ended_at') {
                    $data[$key] = strtotime('+7 day', time());
                } elseif ($key == 'status') {
                    $data[$key] = $model::STATUS_EDITED;
                } else {
                    $data[$key] = 0;
                }
            }
            if (in_array($key, $arrayAttribute)) {
                $data[$key] = [];
            }
        }

        // 素材的资源列表
        $materialAssetsField = 'material_assets';
        $data[$materialAssetsField] = [];

        // 选题的参与用户列表
        $planAttendedExecUsersField = 'plan_attended_exec_user_ids';
        $planAttendedAttendedUsersField = 'plan_attended_attended_user_ids';
        $data[$planAttendedExecUsersField] = [];
        $data[$planAttendedAttendedUsersField] = [];

        // 字段列表(是否允许编辑,0:否;1:是)
        $fields = [];
        $canEditableFields = ['title', 'config_column_id', 'occur_at', 'place', 'content', 'ended_at', 'importance', 'is_auto_task_create', 'keyword', 'opinion', 'is_united', $materialAssetsField, $planAttendedExecUsersField, $planAttendedAttendedUsersField, 'status', 'emergency'];

        // 字段列表(是否默认,0:否;1:是)
        $defaultFields = [];
        $canDefaultFields = ['occur_at', 'place', 'ended_at', 'importance', 'emergency', 'is_auto_task_create', 'is_united', 'status'];

        // 字段列表(是否显示,0:否;1:是)
        $displayableFields = [];
        $canDisplayableFields = $canEditableFields;

        // 基于租户ID查找资源(模型:任务配置)(状态:启用;是否默认:是)列表
        $configTaskEnabledIsDefaultYesItems = ConfigTask::findAllEnabledIsDefaultYesByGroupId($identity->group_id);

        foreach ($data as $fieldKey => $fieldValue) {
            $fields[$fieldKey] = 0;
            $displayableFields[$fieldKey] = 0;
            $defaultFields[$fieldKey] = 0;
            if (in_array($fieldKey, $canEditableFields)) {
                $fields[$fieldKey] = 1;
                // 是否自动创建任务 && 不存在默认的任务配置(是否允许编辑,0:否)
                if ($fieldKey == 'is_auto_task_create' && empty($configTaskEnabledIsDefaultYesItems)) {
                    $fields[$fieldKey] = 0;
                }
            }
            if (in_array($fieldKey, $canDefaultFields)) {
                $defaultFields[$fieldKey] = 1;
            }
            if (in_array($fieldKey, $canDisplayableFields)) {
                $displayableFields[$fieldKey] = 1;
                // 紧急程度 && 租户配置数据的选题的紧急程度是否开启,0:否(是否允许编辑,0:否)
                if ($fieldKey == 'emergency' && $configGroupData['plan_emergency_is_open'] == $model::EMERGENCY_IS_OPEN_NO) {
                    $displayableFields[$fieldKey] = 0;
                }
            }
        }

        $data['fields'] = $fields;
        $data['displayable_fields'] = $displayableFields;
        $data['default_fields'] = $defaultFields;

        return ['code' => 10000, 'message' => Yii::t('success', '126046'), 'data' => $data];
    }
}

10、在 Postman 中 GET:http://api.pcs-api.localhost/v1/plans/new ,响应参数,如图4

图4

{
    "code": 10000,
    "message": "新建选题(获取表单数据)成功",
    "data": {
        "title": "",
        "config_column_id": 0,
        "occur_at": 1576720714,
        "place": "南山区的基地名称详细地址",
        "content": "",
        "ended_at": 1577325514,
        "importance": 3,
        "emergency": 3,
        "is_auto_task_create": 0,
        "keyword": [],
        "opinion": "",
        "is_united": 0,
        "status": 1,
        "material_assets": [],
        "plan_attended_exec_user_ids": [],
        "plan_attended_attended_user_ids": [],
        "fields": {
            "title": 1,
            "config_column_id": 1,
            "occur_at": 1,
            "place": 1,
            "content": 1,
            "ended_at": 1,
            "importance": 1,
            "emergency": 1,
            "is_auto_task_create": 1,
            "keyword": 1,
            "opinion": 1,
            "is_united": 1,
            "status": 1,
            "material_assets": 1,
            "plan_attended_exec_user_ids": 1,
            "plan_attended_attended_user_ids": 1
        },
        "displayable_fields": {
            "title": 1,
            "config_column_id": 1,
            "occur_at": 1,
            "place": 1,
            "content": 1,
            "ended_at": 1,
            "importance": 1,
            "emergency": 1,
            "is_auto_task_create": 1,
            "keyword": 1,
            "opinion": 1,
            "is_united": 1,
            "status": 1,
            "material_assets": 1,
            "plan_attended_exec_user_ids": 1,
            "plan_attended_attended_user_ids": 1
        },
        "default_fields": {
            "title": 0,
            "config_column_id": 0,
            "occur_at": 1,
            "place": 1,
            "content": 0,
            "ended_at": 1,
            "importance": 1,
            "emergency": 1,
            "is_auto_task_create": 1,
            "keyword": 0,
            "opinion": 0,
            "is_united": 1,
            "status": 1,
            "material_assets": 0,
            "plan_attended_exec_user_ids": 0,
            "plan_attended_attended_user_ids": 0
        }
    }
}

11、字段是否显示,在设计时,存在 2 种方案,第 1 种方案为:响应参数中,emergency 可以存在|不存在;第 2 种方案为:响应参数中,emergency 一直存在,然后在 displayable_fields[’emergency’] 中设置其值为 1|0。最终决定使用第 2 种方案。原因在于,以保证响应参数的字段数量与类型上皆无变化,避免客户端对于字段是否存在的判断处理,降低客户端判断处理的复杂度。

12、上传选题素材时,前端的资源的可接受上传的文件扩展名列表,是由前端自行配置的,与接口基于约定实现,但是,其弊端在于,一旦后端有所调整,前端也需要相应调整,否则就不一致,现在决定在响应参数中添加:config,在此参数中包含新建选题表单所需要的一些限制性规则。查看参数配置文件:common/config/params-local.php

    // 策划指挥系统接口
    'pcsApi' => [
        'asset' => [ // 资源
            'basePath' => 'E:/wwwroot/pcs-api/storage', // BASE PATH
            'tempDir' => '/tmp', // TEMP DIR
            'hostInfo' => 'http://127.0.0.1/pcs-api/storage', // HOME URL
            'baseUrl' => '', // BASE URL
            'upload' => [ // 上传
                'extensions' => 'ogg, pdf, xml, zip, gz, mp4, mp3, wav, webm, gif, jpeg, jpg, png, webp, svg, svgz, tiff, css, csv, txt, vcf, vcard, mov, qt, mkv, mk3d, mka, mks, wmv, flv', // 可接受上传的文件扩展名列表
                'mimeTypes' => 'application/ogg, application/pdf, application/xml, application/zip, application/gzip, audio/mp4, audio/mpeg, audio/ogg, audio/vnd.wave, audio/webm, image/gif, image/jpeg, image/png, image/webp, image/svg+xml, image/tiff, text/css, text/csv, text/plain, text/vcard, text/xml, video/mpeg, video/mp4, video/ogg, video/quicktime, video/webm, video/x-matroska, video/x-ms-wmv, video/x-flv', // 可接受上传的 MIME 类型列表
                'minSize' => null, // 上传文件所需最少多少 Byte 的大小
                'maxSize' => 1024 * 1024 * 1024, // 上传文件所需最多多少 Byte 的大小
                'maxFiles' => 10, // 给定属性最多能承载多少个文件
                'scenario' => [ // 场景
                    'configGroupBaseLocationIcon' => [ // 租户设置的基地的图标
                        'extensions' => 'jpeg, jpg, png', // 可接受上传的文件扩展名列表
                        'mimeTypes' => 'image/jpeg, image/png', // 可接受上传的 MIME 类型列表
                        'minSize' => null, // 上传文件所需最少多少 Byte 的大小
                        'maxSize' => 1024 * 1024 * 2, // 上传文件所需最多多少 Byte 的大小
                        'maxFiles' => 2, // 给定属性最多能承载多少个文件
                        'minWidth' => 50, // 图片的最小宽度
                        'maxWidth' => 200, // 图片的最大宽度
                        'minHeight' => 50, // 图片的最小高度
                        'maxHeight' => 200, // 图片的最大高度
                    ],
                    'configUserGisAvatarCustomize' => [ // 用户设置的GIS大屏的自定义的头像
                        'extensions' => 'jpeg, jpg, png', // 可接受上传的文件扩展名列表
                        'mimeTypes' => 'image/jpeg, image/png', // 可接受上传的 MIME 类型列表
                        'minSize' => null, // 上传文件所需最少多少 Byte 的大小
                        'maxSize' => 1024 * 1024 * 2, // 上传文件所需最多多少 Byte 的大小
                        'maxFiles' => 2, // 给定属性最多能承载多少个文件
                        'minWidth' => 50, // 图片的最小宽度
                        'maxWidth' => 200, // 图片的最大宽度
                        'minHeight' => 50, // 图片的最小高度
                        'maxHeight' => 200, // 图片的最大高度
                    ],
                ],
            ],
        ],
    ],

13、决定再添加一个字段:config,在此参数中包含新建选题表单所需要的一些限制性规则,配置参数的层级尽量控制在 3 级以内(既避免层级过深,且 Rap 文档工具仅支持 4 级)。后端的参数配置文件,可基于 Rancher 环境变量进行配置覆盖,继而可影响客户端。编辑动作方法文件:api/rests/plan/NewAction.php

        $assetUpload = Yii::$app->params['pcsApi']['asset']['upload'];
        $data['config']['asset_upload'] = [
            'extensions' => $assetUpload['extensions'] ?? '',
            'mime_types' => $assetUpload['mimeTypes'] ?? '',
            'min_size' => $assetUpload['minSize'] ?? 0,
            'max_size' => $assetUpload['maxSize'] ?? 0,
            'max_files' => $assetUpload['maxFiles'] ?? 1,
        ];
        "config": {
            "asset_upload": {
                "extensions": "ogg, pdf, xml, zip, gz, mp4, mp3, wav, webm, gif, jpeg, jpg, png, webp, svg, svgz, tiff, css, csv, txt, vcf, vcard, mov, qt, mkv, mk3d, mka, mks, wmv, flv",
                "mime_types": "application/ogg, application/pdf, application/xml, application/zip, application/gzip, audio/mp4, audio/mpeg, audio/ogg, audio/vnd.wave, audio/webm, image/gif, image/jpeg, image/png, image/webp, image/svg+xml, image/tiff, text/css, text/csv, text/plain, text/vcard, text/xml, video/mpeg, video/mp4, video/ogg, video/quicktime, video/webm, video/x-matroska, video/x-ms-wmv, video/x-flv",
                "min_size": 0,
                "max_size": 1073741824,
                "max_files": 10
            }
        }

 

永夜