1、打开桌面应用端,界面,如图1
2、接口:我的选题(获取选题列表),在 Postman 中的响应结构,如图2
3、在移动应用端的原型设计,如图3
4、现阶段的需求:后续迭代开发阶段,桌面端与移动端,在同一个接口:我的选题(获取选题列表)中,不可避免地会存在一定的差异性,所以,决定分别规划出对应的路由以及对应的 Action 入口文件,但是,在当前阶段/后续迭代开发阶段中的大部分接口,桌面端与移动端是完全一致的,所以,希望能够复用 Action 中的实现,只有当存在差异的时候,才覆盖微调。其理念可参考(用于移动和桌面的单独站点):https://developer.mozilla.org/en-US/docs/Web/Guide/Mobile/Separate_sites ,打开 http://m.youtube.com/ ,在桌面端与移动端的显示(会自动判断请求端,如果为桌面端,自动跳转至:https://www.youtube.com/ ),如图4、图5
5、urlManager 应用程序组件的配置,/api/config/urlManager.php
<?php
return [
'class' => yii/web/UrlManager::class,
'enablePrettyUrl' => true,
'enableStrictParsing' => true,
'showScriptName' => false,
'rules' => [
[
'class' => 'yii/rest/UrlRule',
'controller' => ['v1/user'],
],
/* 选题管理 */
[
'class' => 'yii/rest/UrlRule',
'controller' => ['v1/plan'],
'only' => ['index', 'create', 'view', 'update', 'delete', 'have', 'wait-review', 'cmc-group', 'edit', 'submit', 'refuse', 'pass', 'return', 'disable', 'enable', 'invite', 'invite-accept'],
'tokens' => ['{id}' => '<id://w[//w,:;]*>'],
'extraPatterns' => [
'GET have' => 'have',
'GET wait-review' => 'wait-review',
'GET cmc-group/{id}' => 'cmc-group',
'GET edit/{id}' => 'edit',
'PUT submit/{id}' => 'submit',
'PUT refuse/{id}' => 'refuse',
'PUT pass/{id}' => 'pass',
'PUT return/{id}' => 'return',
'PUT disable/{id}' => 'disable',
'PUT enable/{id}' => 'enable',
'POST invite/{id}' => 'invite',
'PUT invite-accept/{id}' => 'invite-accept',
],
],
],
];
6、控制器类:/api/controllers/PlanController.php,通过 actions() 方法申明
<?php
namespace api/controllers;
use yii/rest/ActiveController;
class PlanController extends ActiveController
{
public $serializer = [
'class' => 'api/rests/plan/Serializer',
'collectionEnvelope' => 'items',
];
/**
* @inheritdoc
*/
public function actions()
{
$actions = parent::actions();
// 禁用"options"动作
unset($actions['options']);
$actions['index']['class'] = 'api/rests/plan/IndexAction';
$actions['create']['class'] = 'api/rests/plan/CreateAction';
$actions['view']['class'] = 'api/rests/plan/ViewAction';
$actions['update']['class'] = 'api/rests/plan/UpdateAction';
$actions['delete']['class'] = 'api/rests/plan/DeleteAction';
$actions['have'] = [
'class' => 'api/rests/plan/HaveAction',
'modelClass' => $this->modelClass,
'checkAccess' => [$this, 'checkAccess'],
];
$actions['wait-review'] = [
'class' => 'api/rests/plan/WaitReviewAction',
'modelClass' => $this->modelClass,
'checkAccess' => [$this, 'checkAccess'],
];
$actions['cmc-group'] = [
'class' => 'api/rests/plan/CmcGroupAction',
'modelClass' => $this->modelClass,
'checkAccess' => [$this, 'checkAccess'],
];
$actions['edit'] = [
'class' => 'api/rests/plan/EditAction',
'modelClass' => $this->modelClass,
'checkAccess' => [$this, 'checkAccess'],
];
$actions['submit'] = [
'class' => 'api/rests/plan/SubmitAction',
'modelClass' => $this->modelClass,
'checkAccess' => [$this, 'checkAccess'],
];
$actions['refuse'] = [
'class' => 'api/rests/plan/RefuseAction',
'modelClass' => $this->modelClass,
'checkAccess' => [$this, 'checkAccess'],
];
$actions['pass'] = [
'class' => 'api/rests/plan/PassAction',
'modelClass' => $this->modelClass,
'checkAccess' => [$this, 'checkAccess'],
];
$actions['return'] = [
'class' => 'api/rests/plan/ReturnAction',
'modelClass' => $this->modelClass,
'checkAccess' => [$this, 'checkAccess'],
];
$actions['disable'] = [
'class' => 'api/rests/plan/DisableAction',
'modelClass' => $this->modelClass,
'checkAccess' => [$this, 'checkAccess'],
];
$actions['enable'] = [
'class' => 'api/rests/plan/EnableAction',
'modelClass' => $this->modelClass,
'checkAccess' => [$this, 'checkAccess'],
];
$actions['invite'] = [
'class' => 'api/rests/plan/InviteAction',
'modelClass' => $this->modelClass,
'checkAccess' => [$this, 'checkAccess'],
];
$actions['invite-accept'] = [
'class' => 'api/rests/plan/InviteAcceptAction',
'modelClass' => $this->modelClass,
'checkAccess' => [$this, 'checkAccess'],
];
return $actions;
}
}
7、使用模块,将不同版本的代码隔离,/api/modules/v1/controllers/PlanController.php
<?php
namespace api/modules/v1/controllers;
/**
* Plan controller for the `v1` module
*/
class PlanController extends /api/controllers/PlanController
{
public $modelClass = 'api/modules/v1/models/Plan';
}
8、Action 动作文件:/api/rests/plan/HaveAction.php 继承至 /yii/rest/ActiveController 默认提供的动作:/yii/rest/IndexAction
<?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/Plan;
use api/models/PlanQuery;
use api/models/redis/cmc_console/User as RedisCmcConsoleUser;
use yii/base/InvalidConfigException;
use yii/data/ActiveDataProvider;
use yii/web/UnprocessableEntityHttpException;
/**
* 我的选题(获取选题列表):/plans/have(plan/have)
*
* 1、请求参数列表
* (1)filter[created_at][gte]:可选,选题的开始时间,默认:null
* (2)filter[created_at][lte]:可选,选题的结束时间,默认:null
* (3)filter[status]:可选,选题状态,0:禁用;1:编辑;2:待审;3:通过;4:拒绝;5:指派;6:完成,默认:null
* (4)filter[title][like]:可选,选题标题,默认:null
*
* 2、输入数据验证规则
* (1)整数:created_at、status
* (2)字符串(最大长度:64):title
* (3)范围([0, 1, 2, 3, 4, 5, 6]):status
*
* 3、查询规则
* (1)栏目是否被删除,0:否
* (2)选题是否被删除,0:否
* (3)(选题的租户ID为当前租户ID && 选题创建用户ID为当前登录用户ID && 栏目人员是否被删除,0:否) (选题的租户ID为当前租户ID && 选题执行(负责)用户ID为当前登录用户ID && 栏目人员是否被删除,0:否) || (选题的租户ID为当前租户ID && 栏目人员配置角色标识包含栏目负责人标识) || (选题模型的是否跨租户(不隔离),1:是 && 选题与租户的关联模型的关联的租户ID为当前租户ID && 选题与租户的关联模型的是否邀请者,0:否 && 选题与租户的关联模型的接受状态,0:待接受;2:已拒绝 && 选题与租户的关联模型是否被删除,0:否) || (选题模型的是否跨租户(不隔离),1:是 && 选题与租户的关联模型的关联的租户ID为当前租户ID && 选题与租户的关联模型的是否邀请者,0:否 && 选题与租户的关联模型的接受状态,1:已接受 && 选题与租户的关联模型的接受用户ID为当前登录用户ID && 选题与租户的关联模型是否被删除,0:否 && 栏目人员是否被删除,0:否) || (选题模型的是否跨租户(不隔离),1:是 && 选题与租户的关联模型的关联的租户ID为当前租户ID && 选题与租户的关联模型的是否邀请者,0:否 && 选题与租户的关联模型的接受状态,1:已接受 && 选题与租户的关联模型是否被删除,0:否 && 栏目人员配置角色标识包含栏目负责人标识)
*
* For more details and usage information on IndexAction, see the [guide article on rest controllers](guide:rest-controllers).
*
* @author Qiang Wang <shuijingwanwq@163.com>
* @since 1.0
*/
class HaveAction extends /yii/rest/IndexAction
{
public $dataFilter = [
'class' => 'yii/data/ActiveDataFilter',
'searchModel' => 'api/models/PlanSearch',
'attributeMap' => [
'created_at' => '{{%plan}}.[[created_at]]',
'status' => '{{%plan}}.[[status]]',
'title' => '{{%plan}}.[[title]]',
],
];
/**
* Prepares the data provider that should return the requested collection of the models.
* @return ActiveDataProvider
* @throws InvalidConfigException if a registered parser does not implement the [[RequestParserInterface]].
* @throws UnprocessableEntityHttpException
*/
protected function prepareDataProvider()
{
$requestParams = Yii::$app->getRequest()->getBodyParams();
if (empty($requestParams)) {
$requestParams = Yii::$app->getRequest()->getQueryParams();
}
$filter = null;
if ($this->dataFilter !== null) {
$this->dataFilter = Yii::createObject($this->dataFilter);
if ($this->dataFilter->load($requestParams)) {
$filter = $this->dataFilter->build();
if ($filter === false) {
$firstError = '';
foreach ($this->dataFilter->getFirstErrors() as $message) {
$firstError = $message;
break;
}
throw new UnprocessableEntityHttpException(Yii::t('error', Yii::t('error', Yii::t('error', '224003'), ['first_error' => $firstError])), 224003);
}
}
}
if ($this->prepareDataProvider !== null) {
return call_user_func($this->prepareDataProvider, $this, $filter);
}
// 当前用户的身份实例,未认证用户则为 Null
/* @var $identity RedisCmcConsoleUser */
$identity = Yii::$app->user->identity;
/* @var $modelClass Plan */
$modelClass = $this->modelClass;
/* @var $haveQuery PlanQuery */
// 获取查询对象(我的选题(获取选题列表))
$haveQuery = $modelClass::getHaveQuery($identity);
$query = $haveQuery->orderBy([$modelClass::tableName() . '.id' => SORT_DESC]);
if (!empty($filter)) {
$query->andFilterWhere($filter);
}
return Yii::createObject([
'class' => ActiveDataProvider::className(),
'query' => $query,
'pagination' => [
'params' => $requestParams,
],
'sort' => [
'params' => $requestParams,
],
]);
}
}
9、由于同一个接口,其业务逻辑基本上是一致的,而业务逻辑的实现存在于目录:/common/models、/common/logics、/common/services、/api/models、/api/services。因此,无需要基于模块来区分,可基于控制器的子目录来实现。新建移动端目录:/api/controllers/mobile,复制 /api/controllers/PlanController.php 至 /api/controllers/mobile/PlanController.php
10、urlManager 应用程序组件的配置,/api/config/urlManager.php,新增:移动端 – 选题
/* 移动端 */
// 移动端 - 选题
[
'class' => 'yii/rest/UrlRule',
'controller' => ['v1/mobile/plan'],
'only' => ['index', 'create', 'view', 'update', 'delete', 'have', 'wait-review', 'cmc-group', 'edit', 'submit', 'refuse', 'pass', 'return', 'disable', 'enable', 'invite', 'invite-accept'],
'tokens' => ['{id}' => '<id://w[//w,:;]*>'],
'extraPatterns' => [
'GET have' => 'have',
'GET wait-review' => 'wait-review',
'GET cmc-group/{id}' => 'cmc-group',
'GET edit/{id}' => 'edit',
'PUT submit/{id}' => 'submit',
'PUT refuse/{id}' => 'refuse',
'PUT pass/{id}' => 'pass',
'PUT return/{id}' => 'return',
'PUT disable/{id}' => 'disable',
'PUT enable/{id}' => 'enable',
'POST invite/{id}' => 'invite',
'PUT invite-accept/{id}' => 'invite-accept',
],
],
11、控制器类:/api/controllers/mobile/PlanController.php,继承至:/api/controllers/PlanController,通过 actions() 方法申明,覆盖方法类文件
<?php
namespace api/controllers/mobile;
class PlanController extends /api/controllers/PlanController
{
public $serializer = [
'class' => 'api/rests/mobile/plan/Serializer',
'collectionEnvelope' => 'items',
];
/**
* @inheritdoc
*/
public function actions()
{
$actions = parent::actions();
$actions['index']['class'] = 'api/rests/mobile/plan/IndexAction';
$actions['create']['class'] = 'api/rests/mobile/plan/CreateAction';
$actions['view']['class'] = 'api/rests/mobile/plan/ViewAction';
$actions['update']['class'] = 'api/rests/mobile/plan/UpdateAction';
$actions['delete']['class'] = 'api/rests/mobile/plan/DeleteAction';
$actions['have']['class'] = 'api/rests/mobile/plan/HaveAction';
$actions['wait-review']['class'] = 'api/rests/mobile/plan/WaitReviewAction';
$actions['cmc-group']['class'] = 'api/rests/mobile/plan/CmcGroupAction';
$actions['edit']['class'] = 'api/rests/mobile/plan/EditAction';
$actions['submit']['class'] = 'api/rests/mobile/plan/SubmitAction';
$actions['refuse']['class'] = 'api/rests/mobile/plan/RefuseAction';
$actions['pass']['class'] = 'api/rests/mobile/plan/PassAction';
$actions['return']['class'] = 'api/rests/mobile/plan/ReturnAction';
$actions['disable']['class'] = 'api/rests/mobile/plan/DisableAction';
$actions['enable']['class'] = 'api/rests/mobile/plan/EnableAction';
$actions['invite']['class'] = 'api/rests/mobile/plan/InviteAction';
$actions['invite-accept']['class'] = 'api/rests/mobile/plan/InviteAcceptAction';
return $actions;
}
}
12、使用模块,将不同版本的代码隔离,复制 /api/modules/v1/controllers/PlanController.php 至 /api/modules/v1/controllers/mobile/PlanController.php,其继承至 /api/controllers/mobile/PlanController
<?php
namespace api/modules/v1/controllers/mobile;
/**
* Plan controller for the `v1` module
*/
class PlanController extends /api/controllers/mobile/PlanController
{
public $modelClass = 'api/modules/v1/models/Plan';
}
13、复制 /api/rests/plan 至 /api/rests/mobile/plan,批量替换命名空间,如图6
14、编辑 Action 文件:/api/rests/mobile/plan/HaveAction.php,继承至 /api/rests/plan/HaveAction,由于此接口的移动端与桌面端暂无差异,因此,文件中的 run() 方法可删除
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace api/rests/mobile/plan;
class HaveAction extends /api/rests/plan/HaveAction
{
}
15、资源对象转换为数组,编辑数据序列化文件:/api/rests/mobile/plan/Serializer.php,继承至 /api/rests/plan/Serializer,由于此接口的移动端与桌面端暂无差异,因此,文件中的 serializeDataProvider() 方法可删除
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace api/rests/mobile/plan;
class Serializer extends /api/rests/plan/Serializer
{
}
16、在 Postman 中打开桌面端接口,如图7
17、在 Postman 中打开移动端接口,如图8
18、如果此接口的移动端与桌面端存在差异,可覆盖 run() 方法,进行调整的。后续争取做到 run() 方法中一部份可复用的代码抽取出来,实现为一个方法(放置于文件:/api/rests/plan/HaveAction.php),这样的话,即使存在差异,也仅需要微调了。
原创文章,作者:Maggie-Hunter,如若转载,请注明出处:https://blog.ytso.com/tech/webdev/181343.html
