1. Reviewing rest/ActiveController.php, I found the default list of action methods.
/**
* {@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 provides the following actions by default:
index: Lists resources by page
view: Returns the details of a specified resource
create: Creates a new resource
update: Updates an existing resource
delete: Deletes a specified resource
options: Returns the supported HTTP methods
3. A new requirement has emerged, already reflected in the prototype: when Tenant Settings – Topic Urgency is off, the field Topic Urgency is not displayed on the new topic page; when Tenant Settings – Topic Urgency is on, the field Topic Urgency is displayed on the new topic page, as shown in Figure 1.

4. In the current implementation, the RESTful API provides only 3 endpoints, namely:
create: Creates a new resource
edit: Edits an existing resource (fetches form data)
update: Updates an existing resource
5. Therefore, I decided to add a new endpoint, new: create a new resource (fetch form data), with the action method named new. The reference source is WordPress; the previous action method, edit, was also referenced from WordPress, as shown in Figure 2.

new: Creates a new resource (fetches form data)
6. Create the action method file: 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. In Postman, send a GET request to: http://api.pcs-api.localhost/v1/plans/new . The response parameters are shown in Figure 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. The response parameters include the default values of some fields. After receiving the response data, the frontend and mobile clients can render the form data based on it to ensure consistency of default values across the frontend and mobile clients. Since the default values are controlled by the API, it is more flexible to adjust them when needed. When a field does not have a default value, it is not assigned null, because assigning null would make the field type inconsistent. Instead, values are assigned based on the field type: an empty string, 0, or an empty array. However, when a field is of type number and its value is 0, the client cannot determine whether the field has a default value.
9. I decided to add another field, default_fields, to indicate whether a field’s value is a default value. Edit the action method file: 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. In Postman, send a GET request to: http://api.pcs-api.localhost/v1/plans/new . The response parameters are shown in Figure 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. For field visibility, there were 2 design options. The first option: in the response parameters, emergency could be present or absent. The second option: in the response parameters, emergency is always present, and its visibility is set to 1 or 0 in displayable_fields[’emergency’]. I ultimately chose the second option. This ensures that the number and types of fields in the response parameters remain unchanged, avoiding the need for the client to handle checks for field existence and reducing the complexity of client-side processing.
12. When uploading topic assets, the list of acceptable file extensions for frontend resources was configured by the frontend itself and implemented by convention with the API. The drawback of this approach is that once the backend is adjusted, the frontend must be adjusted accordingly, or they will fall out of sync. I have now decided to add a config parameter to the response, which will contain some restrictive rules needed for the new topic form. View the parameter configuration file: 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. I decided to add another field, config, which contains some restrictive rules needed for the new topic form. The hierarchy of configuration parameters should be kept within 3 levels if possible (to avoid excessive depth, and because the Rap documentation tool only supports 4 levels). The backend parameter configuration file can be overridden based on Rancher environment variables, which in turn affects the client. Edit the action method file: 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
}
}
