在 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 } ] } }
近期评论