基于 yiisoft/yii2-app-advanced,在 GitHub 上新建仓库 yii2-app-advanced,新建接口应用(实现 RESTful 风格的 Web Service 服务的 API),实现 ActiveRecord 的软删除,生成 ActiveQuery,自定义查询类 (六) (2)
1、在 Postman 中,POST http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users ,201 响应,status 值默认为 1
{ "email": "111111@163.com", "password": "111111", "username": "111111" }
{ "code": 10000, "message": "创建用户成功", "data": { "username": "111111", "email": "111111@163.com", "password_hash": "$2y$13$DcZR8PZnlOUqWXRqpbfrxelU6uJIq4dTJTsHLVcnUylOnhAX27SQe", "auth_key": "dq78OPYSn4yIsUtYI7xp90zhZIVoE3Yi", "status": 1, "created_at": 1550042976, "updated_at": 1550042976, "id": 1 } }
SELECT EXISTS(SELECT * FROM `user` WHERE `user`.`username`='111111') SELECT EXISTS(SELECT * FROM `user` WHERE `user`.`email`='111111@163.com') INSERT INTO `user` (`username`, `email`, `password_hash`, `auth_key`, `status`, `created_at`, `updated_at`) VALUES ('111111', '111111@163.com', '$2y$13$DcZR8PZnlOUqWXRqpbfrxelU6uJIq4dTJTsHLVcnUylOnhAX27SQe', 'dq78OPYSn4yIsUtYI7xp90zhZIVoE3Yi', 1, 1550042976, 1550042976)
2、在 Postman 中,DELETE http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users/1 ,200 响应
{ "code": 10000, "message": "删除用户成功" }
SELECT * FROM `user` WHERE `id`='1' UPDATE `user` SET `status`=-1, `updated_at`=1550043516 WHERE `id`=1
3、在 Postman 中,POST http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users ,422 响应,不符合预期,预期为 201 响应,因为 “username”: “111111” 已经被删除
{ "email": "111111@163.com", "password": "111111", "username": "111111" }
{ "code": 20004, "message": "数据验证失败:Username的值\"111111\"已经被占用了。" }
SELECT EXISTS(SELECT * FROM `user` WHERE `user`.`username`='111111') SELECT EXISTS(SELECT * FROM `user` WHERE `user`.`email`='111111@163.com')
4、新建 \console\migrations\m180925_054952_add_is_deleted_and_deleted_at_to_user.php。更新字段,status,状态,0:禁用;1:启用,默认:1。新增字段,is_deleted,是否被删除,0:否;1:是,默认:0。新增字段,deleted_at,删除时间,默认:0。
<?php use yii\db\Migration; /** * Class m180925_054952_add_is_deleted_and_deleted_at_to_user */ class m180925_054952_add_is_deleted_and_deleted_at_to_user extends Migration { /** * {@inheritdoc} */ public function safeUp() { $this->alterColumn('{{%user}}', 'status', $this->smallInteger(6)->notNull()->defaultValue(1)->comment('状态,0:禁用;1:启用')); $this->addColumn('{{%user}}', 'is_deleted', $this->smallInteger(6)->notNull()->defaultValue(0)->comment('是否被删除,0:否;1:是')->after('status')); $this->addColumn('{{%user}}', 'deleted_at', $this->integer()->notNull()->defaultValue(0)->comment('删除时间')->after('updated_at')); $this->dropIndex('username', '{{%user}}'); $this->dropIndex('email', '{{%user}}'); $this->createIndex('uc_username_is_deleted_deleted_at', '{{%user}}', ['username', 'is_deleted', 'deleted_at'], $unique = true); $this->createIndex('uc_email_is_deleted_deleted_at', '{{%user}}', ['email', 'is_deleted', 'deleted_at'], $unique = true); } /** * {@inheritdoc} */ public function safeDown() { echo "m180925_054952_add_is_deleted_and_deleted_at_to_user cannot be reverted.\n"; return false; } /* // Use up()/down() to run migration code without a transaction. public function up() { } public function down() { echo "m180925_054952_add_is_deleted_and_deleted_at_to_user cannot be reverted.\n"; return false; } */ }
5、新建 \console\migrations\m180925_060709_add_is_deleted_and_deleted_at_to_page.php
<?php use yii\db\Migration; /** * Class m180925_060709_add_is_deleted_and_deleted_at_to_page */ class m180925_060709_add_is_deleted_and_deleted_at_to_page extends Migration { /** * {@inheritdoc} */ public function safeUp() { $this->alterColumn('{{%page}}', 'status', $this->smallInteger(6)->notNull()->defaultValue(1)->comment('状态,0:禁用;1:草稿;2:发布')); $this->addColumn('{{%page}}', 'is_deleted', $this->smallInteger(6)->notNull()->defaultValue(0)->comment('是否被删除,0:否;1:是')->after('status')); $this->addColumn('{{%page}}', 'deleted_at', $this->integer()->notNull()->defaultValue(0)->comment('删除时间')->after('updated_at')); $this->dropIndex('uc_uuid', '{{%page}}'); $this->createIndex('uc_uuid_is_deleted_deleted_at', '{{%page}}', ['uuid', 'is_deleted', 'deleted_at'], $unique = true); } /** * {@inheritdoc} */ public function safeDown() { echo "m180925_060709_add_is_deleted_and_deleted_at_to_page cannot be reverted.\n"; return false; } /* // Use up()/down() to run migration code without a transaction. public function up() { } public function down() { echo "m180925_060709_add_is_deleted_and_deleted_at_to_page cannot be reverted.\n"; return false; } */ }
6、新建 \console\migrations\m180925_060709_add_is_deleted_and_deleted_at_to_page.php,将 status 等于 -1 的记录更新为 is_deleted 等于 1,status 等于 0,deleted_at 等于当前记录的 updated_at。
<?php use yii\db\Migration; use yii\db\Expression; /** * Class m180925_091558_update_is_deleted_value_and_deleted_at_value_to_user_and_page */ class m180925_091558_update_is_deleted_value_and_deleted_at_value_to_user_and_page extends Migration { /** * {@inheritdoc} */ public function safeUp() { $this->update('{{%user}}', ['is_deleted' => 1, 'status' => 0, 'deleted_at' => new Expression('updated_at')], ['status' => -1]); $this->update('{{%page}}', ['is_deleted' => 1, 'status' => 0, 'deleted_at' => new Expression('updated_at')], ['status' => -1]); } /** * {@inheritdoc} */ public function safeDown() { echo "m180925_091558_update_is_deleted_value_and_deleted_at_value_to_user_and_page cannot be reverted.\n"; return false; } /* // Use up()/down() to run migration code without a transaction. public function up() { } public function down() { echo "m180925_091558_update_is_deleted_value_and_deleted_at_value_to_user_and_page cannot be reverted.\n"; return false; } */ }
7、执行数据库迁移命令,查看数据库表 user 结构,符合预期,如图10
./yii migrate
8、基于 Gii 重新生成 /common/models 目录中的模型类文件,附加行为至 \common\logics\User.php,软删除时,is_deleted 的值为 1,deleted_at 的值为 当前时间戳
<?php namespace common\logics; use Yii; use yii\base\NotSupportedException; use yii\behaviors\TimestampBehavior; use yii2tech\ar\softdelete\SoftDeleteBehavior; use yii\web\IdentityInterface; use yii\helpers\ArrayHelper; /** * This is the model class for table "{{%user}}". * * @property int $id * @property string $username * @property string $auth_key * @property string $password_hash * @property string $password_reset_token * @property string $email * @property int $status * @property int $created_at * @property int $updated_at * @property string $password write-only password */ class User extends \common\models\User implements IdentityInterface { const STATUS_DISABLED = 0; //状态:禁用 const STATUS_ENABLED = 1; //状态:启用 const IS_DELETED_NO = 0; //是否被删除:否 const IS_DELETED_YES = 1; //是否被删除:是 const DELETED_AT_DEFAULT = 0; //删除时间:默认值 /** * @inheritdoc */ public function behaviors() { return [ 'timestampBehavior' => [ 'class' => TimestampBehavior::className(), 'attributes' => [ self::EVENT_BEFORE_INSERT => ['created_at', 'updated_at'], self::EVENT_BEFORE_UPDATE => 'updated_at', ] ], 'softDeleteBehavior' => [ 'class' => SoftDeleteBehavior::className(), 'softDeleteAttributeValues' => [ 'is_deleted' => self::IS_DELETED_YES, 'deleted_at' => function ($model) { return time(); }, ], ], ]; } /** * @inheritdoc */ public function rules() { $rules = [ ['is_deleted', 'default', 'value' => self::IS_DELETED_NO], ['status', 'default', 'value' => self::STATUS_ENABLED], ['status', 'in', 'range' => [self::STATUS_DISABLED, self::STATUS_ENABLED]], ['deleted_at', 'default', 'value' => self::DELETED_AT_DEFAULT], ]; $parentRules = parent::rules(); return ArrayHelper::merge($rules, $parentRules); } /** * @inheritdoc */ public static function findIdentity($id) { return static::findOne(['id' => $id, 'status' => self::STATUS_ENABLED]); } /** * @inheritdoc */ public static function findIdentityByAccessToken($token, $type = null) { throw new NotSupportedException('"findIdentityByAccessToken" is not implemented.'); } /** * Finds user by username * * @param string $username * @return static|null */ public static function findByUsername($username) { return static::findOne(['username' => $username, 'status' => self::STATUS_ENABLED]); } /** * Finds user by password reset token * * @param string $token password reset token * @return static|null */ public static function findByPasswordResetToken($token) { if (!static::isPasswordResetTokenValid($token)) { return null; } return static::findOne([ 'password_reset_token' => $token, 'status' => self::STATUS_ENABLED, ]); } /** * Finds out if password reset token is valid * * @param string $token password reset token * @return bool */ public static function isPasswordResetTokenValid($token) { if (empty($token)) { return false; } $timestamp = (int) substr($token, strrpos($token, '_') + 1); $expire = Yii::$app->params['user.passwordResetTokenExpire']; return $timestamp + $expire >= time(); } /** * @inheritdoc */ public function getId() { return $this->getPrimaryKey(); } /** * @inheritdoc */ public function getAuthKey() { return $this->auth_key; } /** * @inheritdoc */ public function validateAuthKey($authKey) { return $this->getAuthKey() === $authKey; } /** * Validates password * * @param string $password password to validate * @return bool if password provided is valid for current user */ public function validatePassword($password) { return Yii::$app->security->validatePassword($password, $this->password_hash); } /** * Generates password hash from password and sets it to the model * * @param string $password */ public function setPassword($password) { $this->password_hash = Yii::$app->security->generatePasswordHash($password); } /** * Generates "remember me" authentication key */ public function generateAuthKey() { $this->auth_key = Yii::$app->security->generateRandomString(); } /** * Generates new password reset token */ public function generatePasswordResetToken() { $this->password_reset_token = Yii::$app->security->generateRandomString() . '_' . time(); } /** * Removes password reset token */ public function removePasswordResetToken() { $this->password_reset_token = null; } /** * {@inheritdoc} * @return UserQuery the active query used by this AR class. */ public static function find() { return new UserQuery(get_called_class()); } }
9、在common/logics目录中的MySQL模型文件为业务逻辑相关,继承至 \common\models\UserQuery.php 数据层,编辑 \common\logics\UserQuery.php,其为活动查询类
<?php namespace common\logics; /** * This is the ActiveQuery class for [[User]]. * * @see User */ class UserQuery extends \common\models\UserQuery { // 是否被删除:否 public function isDeletedNo() { return $this->andWhere(['is_deleted' => User::IS_DELETED_NO]); } // 等于 状态:禁用 public function disabled() { return $this->andWhere(['status' => User::STATUS_DISABLED]); } // 等于 状态:启用 public function enabled() { return $this->andWhere(['status' => User::STATUS_ENABLED]); } }
10、软删除的状态:-1 的替换
11、在 Postman 中,POST http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users ,201 响应,status 值默认为 1
{ "email": "111111@163.com", "password": "111111", "username": "111111" }
{ "code": 10000, "message": "创建用户成功", "data": { "username": "111111", "email": "111111@163.com", "password_hash": "$2y$13$wm0kbyttNUh.2mPGBRkC8u44c9ZvQLnqfoNyyhafP5d/ELb4KcU6G", "auth_key": "qgpNse_KMphc_rskoq3HPA6piQ3KWPKU", "is_deleted": 0, "status": 1, "deleted_at": 0, "created_at": 1550046406, "updated_at": 1550046406, "id": 1 } }
SELECT EXISTS(SELECT * FROM `user` WHERE (`user`.`username`='111111') AND (`user`.`is_deleted`=0) AND (`user`.`deleted_at`=0)) SELECT EXISTS(SELECT * FROM `user` WHERE (`user`.`email`='111111@163.com') AND (`user`.`is_deleted`=0) AND (`user`.`deleted_at`=0)) INSERT INTO `user` (`username`, `email`, `password_hash`, `auth_key`, `is_deleted`, `status`, `deleted_at`, `created_at`, `updated_at`) VALUES ('111111', '111111@163.com', '$2y$13$wm0kbyttNUh.2mPGBRkC8u44c9ZvQLnqfoNyyhafP5d/ELb4KcU6G', 'qgpNse_KMphc_rskoq3HPA6piQ3KWPKU', 0, 1, 0, 1550046406, 1550046406)
12、在 Postman 中,DELETE http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users/1 ,200 响应
{ "code": 10000, "message": "删除用户成功" }
SELECT * FROM `user` WHERE `id`='1' UPDATE `user` SET `is_deleted`=1, `deleted_at`=1550046622 WHERE `id`=1
13、在 Postman 中,POST http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users ,201 响应,符合预期 “username”: “111111” 创建成功
{ "email": "111111@163.com", "password": "111111", "username": "111111" }
{ "code": 10000, "message": "创建用户成功", "data": { "username": "111111", "email": "111111@163.com", "password_hash": "$2y$13$PzqSuKYN8Np.criGjatAruZQzz/azR6rlnYX5UNMkoGBiDKv2agWO", "auth_key": "tsw1Yir0dtaVbg3jWYDrS0CY09wV2W5c", "is_deleted": 0, "status": 1, "deleted_at": 0, "created_at": 1550046672, "updated_at": 1550046672, "id": 2 } }
SELECT EXISTS(SELECT * FROM `user` WHERE (`user`.`username`='111111') AND (`user`.`is_deleted`=0) AND (`user`.`deleted_at`=0)) SELECT EXISTS(SELECT * FROM `user` WHERE (`user`.`email`='111111@163.com') AND (`user`.`is_deleted`=0) AND (`user`.`deleted_at`=0)) INSERT INTO `user` (`username`, `email`, `password_hash`, `auth_key`, `is_deleted`, `status`, `deleted_at`, `created_at`, `updated_at`) VALUES ('111111', '111111@163.com', '$2y$13$PzqSuKYN8Np.criGjatAruZQzz/azR6rlnYX5UNMkoGBiDKv2agWO', 'tsw1Yir0dtaVbg3jWYDrS0CY09wV2W5c', 0, 1, 0, 1550046672, 1550046672)
14、在 Postman 中,DELETE http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users/1 ,200 响应
{ "code": 226003, "message": "用户ID:1,的状态为已删除" }
SELECT * FROM `user` WHERE `id`='1'
15、在 Postman 中,DELETE http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users/2 ,200 响应
{ "code": 10000, "message": "删除用户成功" }
SELECT * FROM `user` WHERE `id`='2' UPDATE `user` SET `is_deleted`=1, `deleted_at`=1550047048 WHERE `id`=2
16、在 Postman 中,POST http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users ,201 响应,符合预期 “username”: “111111” 创建成功
{ "email": "111111@163.com", "password": "111111", "username": "111111" }
{ "code": 10000, "message": "创建用户成功", "data": { "username": "111111", "email": "111111@163.com", "password_hash": "$2y$13$QtoJ.eOjzgrLfLTyQIwsf.8kPz8eYW54g3dPVj1QQeAECvLp85aQK", "auth_key": "Z1y-Lzliyrgaf0Us5d4LqYHqUtFwtMYK", "is_deleted": 0, "status": 1, "deleted_at": 0, "created_at": 1550047120, "updated_at": 1550047120, "id": 3 } }
SELECT EXISTS(SELECT * FROM `user` WHERE (`user`.`username`='111111') AND (`user`.`is_deleted`=0) AND (`user`.`deleted_at`=0)) SELECT EXISTS(SELECT * FROM `user` WHERE (`user`.`email`='111111@163.com') AND (`user`.`is_deleted`=0) AND (`user`.`deleted_at`=0)) INSERT INTO `user` (`username`, `email`, `password_hash`, `auth_key`, `is_deleted`, `status`, `deleted_at`, `created_at`, `updated_at`) VALUES ('111111', '111111@163.com', '$2y$13$QtoJ.eOjzgrLfLTyQIwsf.8kPz8eYW54g3dPVj1QQeAECvLp85aQK', 'Z1y-Lzliyrgaf0Us5d4LqYHqUtFwtMYK', 0, 1, 0, 1550047120, 1550047120)
17、在 Postman 中,POST http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users ,422 响应,符合预期 “username”: “111111” 已存在,浏览用户表数据,如图11
{ "email": "111111@163.com", "password": "111111", "username": "111111" }
{ "code": 226004, "message": "数据验证失败:The combination \"111111\"-\"0\"-\"0\" of 用户名, Is Deleted and Deleted At has already been taken." }
SELECT EXISTS(SELECT * FROM `user` WHERE (`user`.`username`='111111') AND (`user`.`is_deleted`=0) AND (`user`.`deleted_at`=0)) SELECT EXISTS(SELECT * FROM `user` WHERE (`user`.`email`='111111@163.com') AND (`user`.`is_deleted`=0) AND (`user`.`deleted_at`=0))
18、在 Postman 中,PUT http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users/3 ,200响应,符合预期
{ "email": "333333@163.com", "password": "333333", "status": 1 }
{ "code": 10000, "message": "更新用户成功", "data": { "id": 3, "username": "111111", "auth_key": "qc5qVXKkHNKJC1ybac6coQqFiePH2hfE", "password_hash": "$2y$13$R6dQZgoFVT0mWV9kBl8uo.K7VtdualY.pK0fTtWHMpGfa3cqVIoXe", "password_reset_token": null, "email": "333333@163.com", "status": 1, "is_deleted": 0, "created_at": 1550047120, "updated_at": 1550049609, "deleted_at": 0 } }
SELECT * FROM `user` WHERE `id`='3' SELECT EXISTS(SELECT * FROM `user` WHERE (`user`.`email`='333333@163.com') AND ((`id` != '3') AND (`is_deleted`=0))) SELECT `user`.`id` FROM `user` WHERE (`user`.`username`='111111') AND (`user`.`is_deleted`=0) AND (`user`.`deleted_at`=0) LIMIT 2 SELECT `user`.`id` FROM `user` WHERE (`user`.`email`='333333@163.com') AND (`user`.`is_deleted`=0) AND (`user`.`deleted_at`=0) LIMIT 2 UPDATE `user` SET `auth_key`='qc5qVXKkHNKJC1ybac6coQqFiePH2hfE', `password_hash`='$2y$13$R6dQZgoFVT0mWV9kBl8uo.K7VtdualY.pK0fTtWHMpGfa3cqVIoXe', `email`='333333@163.com', `updated_at`=1550049609 WHERE `id`=3
19、建议在查询资源列表与资源详情时,默认加上条件 `is_deleted`=0,以仅查询出未被删除的资源
近期评论