在 Yii 2.0 上,RESTful 风格的 Web Service 服务的 API,PUT 批量更新资源的实现
1、编辑 \api\config\urlManager.php,定义路由以支持 PUT close/{id}
// 任务管理
[
'class' => 'yii\rest\UrlRule',
'controller' => ['v1/plan-task'],
'only' => ['index', 'create', 'view', 'update', 'claim', 'delete', 'finish', 'transfer', 'video-edit', 'write', 'commit-article', 'article-review', 'close'],
'tokens' => ['{id}' => '<id:\\w[\\w,:;]*>'],
'extraPatterns' => [
'PUT claim/{id}' => 'claim',
'DELETE /{id}' => '',
'PUT finish/{id}' => 'finish',
'GET {id}' => 'view',
'PUT update' => 'update',
'PUT transfer/{id}' => 'transfer',
'GET video-edit/{id}' => 'video-edit',
'GET write/{id}' => 'write',
'GET commit-article/{id}' => 'commit-article',
'GET article-review/{id}' => 'article-review',
'PUT close/{id}' => 'close',
],
],
2、编辑 \api\controllers\PlanTaskController.php,定义动作以支持 close 方法
<?php
/**
* Created by PhpStorm.
* User: WangQiang
* Date: 2018/05/05
* Time: 11:43
*/
namespace api\controllers;
use yii\rest\ActiveController;
class PlanTaskController extends ActiveController
{
public $serializer = [
'class' => 'api\rests\plan_task\Serializer',
'collectionEnvelope' => 'items',
];
/**
* @inheritdoc
*/
public function actions()
{
$actions = parent::actions();
// 禁用"options"动作
unset($actions['options']);
$actions['index']['class'] = 'api\rests\plan_task\IndexAction';
$actions['create']['class'] = 'api\rests\plan_task\CreateAction';
$actions['view']['class'] = 'api\rests\plan_task\ViewAction';
$actions['update']['class'] = 'api\rests\plan_task\UpdateAction';
$actions['delete']['class'] = 'api\rests\plan_task\DeleteAction';
$actions['claim'] = [
'class' => 'api\rests\plan_task\ClaimAction',
'modelClass' => $this->modelClass,
'checkAccess' => [$this, 'checkAccess'],
];
$actions['finish'] = [
'class' => 'api\rests\plan_task\FinishAction',
'modelClass' => $this->modelClass,
'checkAccess' => [$this, 'checkAccess'],
];
$actions['transfer'] = [
'class' => 'api\rests\plan_task\TransferAction',
'modelClass' => $this->modelClass,
'checkAccess' => [$this, 'checkAccess'],
];
$actions['video-edit'] = [
'class' => 'api\rests\plan_task\VideoEditAction',
'modelClass' => $this->modelClass,
'checkAccess' => [$this, 'checkAccess'],
];
$actions['write'] = [
'class' => 'api\rests\plan_task\WriteAction',
'modelClass' => $this->modelClass,
'checkAccess' => [$this, 'checkAccess'],
];
$actions['commit-article'] = [
'class' => 'api\rests\plan_task\CommitArticleAction',
'modelClass' => $this->modelClass,
'checkAccess' => [$this, 'checkAccess'],
];
$actions['article-review'] = [
'class' => 'api\rests\plan_task\ArticleReviewAction',
'modelClass' => $this->modelClass,
'checkAccess' => [$this, 'checkAccess'],
];
$actions['close'] = [
'class' => 'api\rests\plan_task\CloseAction',
'modelClass' => $this->modelClass,
'checkAccess' => [$this, 'checkAccess'],
];
return $actions;
}
}
3、编辑 \api\rests\plan_task\Action.php,复制 public function findModel($id) 为 public function findModels($id),以支持查找多个模型
/**
* Returns the data model based on the primary key given.
* If the data model is not found, a 404 HTTP exception will be raised.
* @param string $id the ID of the model to be loaded. If the model has a composite primary key,
* the ID must be a string of the primary key values separated by commas.
* The order of the primary key values should follow that returned by the `primaryKey()` method
* of the model.
* @return ActiveRecordInterface the model found
* @throws NotFoundHttpException if the model cannot be found
*/
public function findModels($id)
{
if ($this->findModel !== null) {
return call_user_func($this->findModel, $id, $this);
}
/* @var $modelClass ActiveRecordInterface */
$modelClass = $this->modelClass;
$keys = $modelClass::primaryKey();
if (count($keys) > 1) {
$values = explode(',', $id);
if (count($keys) === count($values)) {
$model = $modelClass::find()->where(['in', $keys, $values])->indexBy($keys)->all();
}
} elseif ($id !== null) {
$values = explode(';', $id);
$model = $modelClass::find()->where(['in', 'id', $values])->indexBy('id')->all();
}
if (!empty($model)) {
// ID的数目与模型资源数目是否相等,如果不相等,响应失败
$flipValues = array_flip($values);
if (count($model) == count($flipValues)) {
return $model;
} else {
$id = implode(";", array_keys(array_diff_key($flipValues, $model)));
}
}
throw new NotFoundHttpException(Yii::t('error', Yii::t('error', Yii::t('error', '20014'), ['id' => $id])), 20014);
}
4、所有ID皆必须存在,否则响应失败,如图1
5、新建 \api\rests\plan_task\CloseAction.php,支持 批量验证模型、批量更新模型
<?php
namespace api\rests\plan_task;
use Yii;
use yii\base\Model;
use yii\db\ActiveRecord;
use api\models\PlanTask;
use yii\web\ServerErrorHttpException;
/**
* 关闭(关闭任务)
*
* 1、请求参数列表
* (1)ids 必填,多个ID(以半角;分隔);
*
* 2、输入数据验证规则
* (1)必填:ids;
* (2)所有选题任务ID皆必须存在;
* (3)所有选题任务状态皆必须为,1:未开始;2:进行中(已认领);
* (4)选题创建用户ID为当前登录用户ID || (栏目人员配置租户ID为当前登录用户租户ID && 栏目人员配置角色标识包含栏目负责人标识 && 栏目人员配置状态的非取值范围 -1、0);
*
* 3、操作数据(事务)
* (1)更新选题任务的状态,4:关闭;
*
* For more details and usage information on CloseAction, see the [guide article on rest controllers](guide:rest-controllers).
*
* @author Qiang Wang <shuijingwanwq@163.com>
* @since 1.0
*/
class CloseAction extends Action
{
/**
* Updates an existing model.
*
* @param string $id the primary key of the model.
*
* @return \yii\db\ActiveRecordInterface the model being updated
* @throws ServerErrorHttpException if there is any error when updating the model
*/
public function run($id)
{
/* @var $model ActiveRecord */
$models = $this->findModels($id);
if ($this->checkAccess) {
call_user_func($this->checkAccess, $this->id, $models);
}
// 状态,不是 1:未开始;2:进行中(已认领) 的ID
$ids = [];
foreach ($models as $key => $model) {
$model->scenario = 'close';
$models[$key] = $model;
if ($model->status != PlanTask::PLAN_TASK_STATUS_NOT_BEGINNING && $model->status != PlanTask::PLAN_TASK_STATUS_BEGINNING) {
$ids[] = $model->id;
}
}
if (!empty($ids)) {
$ids = implode(";", $ids);
return ['code' => 20036, 'message' => Yii::t('error', Yii::t('error', Yii::t('error', '20036'), ['ids' => $ids]))];
}
// 批量验证模型
if (Model::validateMultiple($models)) {
$modelResult = $model->closeMultiple($models);
if (!$modelResult) {
return ['code' => 20038, 'message' => Yii::t('error', '20038')];
}
} else {
foreach ($models as $model) {
if ($model->hasErrors()) {
$response = Yii::$app->getResponse();
$response->setStatusCode(422, 'Data Validation Failed.');
foreach ($model->getFirstErrors() as $message) {
$firstErrors = $message;
break;
}
return ['code' => 20004, 'message' => Yii::t('error', Yii::t('error', Yii::t('error', '20004'), ['firstErrors' => $firstErrors]))];
}
}
throw new ServerErrorHttpException('Failed to update the object for unknown reason.');
}
foreach ($models as $key => $model) {
$model->task_data = unserialize($model->task_data);
$models[$key] = $model;
}
return ['code' => 10000, 'message' => Yii::t('success', '10017'), 'data' => ['items' => array_values($models)]];
}
}
6、编辑 \common\logics\PlanTask.php,支持 验证规则定义、批量更新模型
/**
* @inheritdoc
*/
public function rules()
{
$rules = [
/* 关闭(关闭任务) */
[['id'], 'validateClosePermission', 'on' => 'close'], //close
];
$parentRules = parent::rules();
return ArrayHelper::merge($rules, $parentRules);
}
/**
* Validates the close permission.
* This method serves as the inline validation for close permission.
*
* @param string $attribute the attribute currently being validated
* @param array $params the additional name-value pairs given in the rule
*/
public function validateClosePermission($attribute, $params)
{
if (!$this->hasErrors()) {
// 当前用户的身份实例,未认证用户则为 Null
$identity = Yii::$app->user->identity;
if (!$this->checkColumnOrPlanOwner($identity->group_id, $identity->login_name)) {
$this->addError($attribute, Yii::t('error', '20037'));
}
}
}
/**
* 批量关闭(关闭任务)
*
* @param $models
*
* @return bool the saved model or false if saving fails
* @throws \Throwable
*/
public function closeMultiple($models)
{
$transaction = Yii::$app->db->beginTransaction();
try {
foreach ($models as $model) {
$model->status = self::PLAN_TASK_STATUS_CLOSE;
if(!$model->save(false)) {
throw new ServerErrorHttpException(Yii::t('error', '20038'), 20038);
}
}
$transaction->commit();
return true;
} catch (\Throwable $e) {
$transaction->rollBack();
throw $e;
}
}
7、执行结果成功,批量更新模型成功,响应成功更新后的模型数据,如图2
{
"code": 10000,
"message": "关闭(关闭任务)成功",
"data": {
"items": [
{
"id": 14,
"group_id": "015ce30b116ce86058fa6ab4fea4ac63",
"config_column_id": 1,
"plan_id": 1,
"sort_order": 1,
"title": "无线成都1的任务2",
"config_task_id": 1,
"create_user_id": 8,
"create_name": "13281105967",
"exec_user_id": 186,
"exec_name": "test2",
"place": "<p>选题摘要</p>",
"task_info": "",
"task_data": [],
"ended_at": 1530696654,
"current_step_id": 0,
"status": 4,
"created_at": 1528104654,
"updated_at": 1528270437
},
{
"id": 15,
"group_id": "015ce30b116ce86058fa6ab4fea4ac63",
"config_column_id": 1,
"plan_id": 1,
"sort_order": 1,
"title": "无线成都1的任务1",
"config_task_id": 1,
"create_user_id": 8,
"create_name": "13281105967",
"exec_user_id": 185,
"exec_name": "test1",
"place": "地点",
"task_info": "<p>选题摘要</p>",
"task_data": [],
"ended_at": 1530754303,
"current_step_id": 90,
"status": 4,
"created_at": 1528162303,
"updated_at": 1528270437
}
]
}
}


近期评论