基于 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
1 2 3 4 5 | { "email": "111111@163.com", "password": "111111", "username": "111111" } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | { "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 } } |
1 2 3 | 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 响应
1 2 3 4 | { "code": 10000, "message": "删除用户成功" } |
1 2 | 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” 已经被删除
1 2 3 4 5 | { "email": "111111@163.com", "password": "111111", "username": "111111" } |
1 2 3 4 | { "code": 20004, "message": "数据验证失败:Username的值\"111111\"已经被占用了。" } |
1 2 | 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。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | <?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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | <?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。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | <?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
1 | ./yii migrate |
8、基于 Gii 重新生成 /common/models 目录中的模型类文件,附加行为至 \common\logics\User.php,软删除时,is_deleted 的值为 1,deleted_at 的值为 当前时间戳
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 | <?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,其为活动查询类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | <?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
1 2 3 4 5 | { "email": "111111@163.com", "password": "111111", "username": "111111" } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | { "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 } } |
1 2 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$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 响应
1 2 3 4 | { "code": 10000, "message": "删除用户成功" } |
1 2 | 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” 创建成功
1 2 3 4 5 | { "email": "111111@163.com", "password": "111111", "username": "111111" } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | { "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 } } |
1 2 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$PzqSuKYN8Np.criGjatAruZQzz/azR6rlnYX5UNMkoGBiDKv2agWO' , 'tsw1Yir0dtaVbg3jWYDrS0CY09wV2W5c' , 0, 1, 0, 1550046672, 1550046672) |
14、在 Postman 中,DELETE http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users/1 ,200 响应
1 2 3 4 | { "code": 226003, "message": "用户ID:1,的状态为已删除" } |
1 | SELECT * FROM ` user ` WHERE `id`= '1' |
15、在 Postman 中,DELETE http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users/2 ,200 响应
1 2 3 4 | { "code": 10000, "message": "删除用户成功" } |
1 2 | 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” 创建成功
1 2 3 4 5 | { "email": "111111@163.com", "password": "111111", "username": "111111" } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | { "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 } } |
1 2 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
1 2 3 4 5 | { "email": "111111@163.com", "password": "111111", "username": "111111" } |
1 2 3 4 | { "code": 226004, "message": "数据验证失败:The combination \"111111\"-\"0\"-\"0\" of 用户名, Is Deleted and Deleted At has already been taken." } |
1 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)) |
18、在 Postman 中,PUT http://api.github-shuijingwan-yii2-app-advanced.localhost/v1/users/3 ,200响应,符合预期
1 2 3 4 5 | { "email": "333333@163.com", "password": "333333", "status": 1 } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | { "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 } } |
1 2 3 4 5 | 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,以仅查询出未被删除的资源
近期评论