在 Yii 2.0 中,添加新的 RESTful API,用于新建资源(渲染表单数据)的实现
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
4、在现阶段的实现中,RESTful API 仅提供了 3 个接口,分别为:
create:创建新的资源 edit:编辑一个存在的资源(获取表单数据) update:更新一个存在的资源
5、因此,决定再添加一个新的接口,new:新建新的资源(获取表单数据),动作方法命名为:new,参考来源为 WordPress,之前的动作方法:edit,同样参考自 WordPress,如图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
{ "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
{ "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 } }
近期评论