redis, RedisKeyUtil::lockUserStamina($userID), RedisLock::FLAG_CATCH_EXCEPTIONS); if (!$lock->acquire(10)) { $userDbData = User::findById($userID, false); return [ 'ok' => false, 'status' => ResultStatusServices::RS_SAME_TIME_OPERATION, 'data' => $userDbData ? self::formatClientData($userDbData) : self::defaultClientData(), ]; } try { return call_user_func($callback, $userID); } finally { $lock->release(); } } public static function consumeStealUnlocked($userID) { $check = self::checkStealUnlocked($userID); if (empty($check['ok'])) { return $check; } $userDbData = User::findById($userID, false); $now = time(); $wasFull = (int)$userDbData->stamina >= (int)$userDbData->stamina_max; $userDbData->stamina -= self::STEAL_COST; if ($wasFull) { $userDbData->stamina_recover_time = $now; } self::save($userDbData); return [ 'ok' => true, 'status' => ResultStatusServices::RS_OPERATION_SUCCESS, 'data' => self::formatClientData($userDbData), ]; } public static function checkStealUnlocked($userID) { $userDbData = User::findById($userID, false); if (!$userDbData) { return [ 'ok' => false, 'status' => ResultStatusServices::RS_NOT_FIND_USER, 'data' => self::defaultClientData(), ]; } $now = time(); self::recover($userDbData, $now); if ((int)$userDbData->stamina < self::STEAL_COST) { self::save($userDbData); return [ 'ok' => false, 'status' => ResultStatusServices::RS_STAMINA_NOT_ENOUGH, 'data' => self::formatClientData($userDbData), ]; } self::save($userDbData); return [ 'ok' => true, 'status' => ResultStatusServices::RS_OPERATION_SUCCESS, 'data' => self::formatClientData($userDbData), ]; } public static function increaseMax($userID, $amount, $fillAddedCapacity = true) { if ($amount <= 0) { return [ 'ok' => false, 'status' => ResultStatusServices::RS_PARAM_ERROR, 'data' => self::getClientData($userID), ]; } return self::setMax($userID, self::currentMax($userID) + (int)$amount, $fillAddedCapacity); } public static function setMax($userID, $maxStamina, $fillAddedCapacity = true) { $lock = new RedisLock(Yii::$app->redis, RedisKeyUtil::lockUserStamina($userID), RedisLock::FLAG_CATCH_EXCEPTIONS); if (!$lock->acquire(10)) { return [ 'ok' => false, 'status' => ResultStatusServices::RS_SAME_TIME_OPERATION, 'data' => self::getClientData($userID), ]; } try { $userDbData = User::findById($userID, false); if (!$userDbData) { return [ 'ok' => false, 'status' => ResultStatusServices::RS_NOT_FIND_USER, 'data' => self::defaultClientData(), ]; } self::recover($userDbData, time()); $oldMax = self::normalizeMax((int)$userDbData->stamina_max); $newMax = self::normalizeMax((int)$maxStamina); $userDbData->stamina_max = $newMax; if ($fillAddedCapacity && $newMax > $oldMax) { $userDbData->stamina = min($newMax, (int)$userDbData->stamina + ($newMax - $oldMax)); } else { $userDbData->stamina = min((int)$userDbData->stamina, $newMax); } if ((int)$userDbData->stamina >= $newMax) { $userDbData->stamina_recover_time = time(); } self::save($userDbData); return [ 'ok' => true, 'status' => ResultStatusServices::RS_OPERATION_SUCCESS, 'data' => self::formatClientData($userDbData), ]; } finally { $lock->release(); } } private static function currentMax($userID) { $userDbData = User::findById($userID, false); if (!$userDbData) { return self::MAX_STAMINA; } return self::normalizeMax((int)$userDbData->stamina_max); } private static function recover(User $userDbData, $now) { $maxStamina = self::normalizeMax((int)$userDbData->stamina_max); $userDbData->stamina_max = $maxStamina; $userDbData->stamina = max(0, min($maxStamina, (int)$userDbData->stamina)); if ((int)$userDbData->stamina >= $maxStamina) { $userDbData->stamina = $maxStamina; $userDbData->stamina_recover_time = $now; return; } if ((int)$userDbData->stamina_recover_time <= 0) { $userDbData->stamina_recover_time = $now; return; } $elapsed = $now - (int)$userDbData->stamina_recover_time; if ($elapsed < self::RECOVER_INTERVAL) { return; } $recoverTimes = (int)floor($elapsed / self::RECOVER_INTERVAL); $userDbData->stamina = min($maxStamina, (int)$userDbData->stamina + $recoverTimes * self::RECOVER_AMOUNT); if ((int)$userDbData->stamina >= $maxStamina) { $userDbData->stamina_recover_time = $now; } else { $userDbData->stamina_recover_time += $recoverTimes * self::RECOVER_INTERVAL; } } private static function save(User $userDbData) { return $userDbData->save(true, ['stamina', 'stamina_max', 'stamina_recover_time']); } private static function formatClientData(User $userDbData) { $maxStamina = self::normalizeMax((int)$userDbData->stamina_max); $stamina = max(0, min($maxStamina, (int)$userDbData->stamina)); $nextRecoverTime = 0; if ($stamina < $maxStamina) { $nextRecoverTime = (int)$userDbData->stamina_recover_time + self::RECOVER_INTERVAL; } return [ 'stamina' => $stamina, 'stamina_max' => $maxStamina, 'stamina_next_recover_time' => $nextRecoverTime, 'stamina_recover_amount' => self::RECOVER_AMOUNT, 'stamina_recover_interval' => self::RECOVER_INTERVAL, 'stamina_cost_steal' => self::STEAL_COST, ]; } private static function defaultClientData() { return [ 'stamina' => self::MAX_STAMINA, 'stamina_max' => self::MAX_STAMINA, 'stamina_next_recover_time' => 0, 'stamina_recover_amount' => self::RECOVER_AMOUNT, 'stamina_recover_interval' => self::RECOVER_INTERVAL, 'stamina_cost_steal' => self::STEAL_COST, ]; } private static function normalizeMax($maxStamina) { return max(1, (int)$maxStamina); } }