<?php
declare (strict_types=1);

namespace app\service\util;

use app\exception\BusinessException;
use Predis\Client;

/**
 * @author canny
 * @date 2023/10/26 9:59
 **/
class DistributedLockService
{
    protected Client $redis;
    protected string $lockKey;
    protected string $lockValue;
    protected $lockTimeout;

    public function __construct($lockKey, $lockTimeout = 10)
    {
        $this->redis = RedisService::getRedisInstance();

        $this->lockKey = $lockKey;
        $this->lockValue = uniqid(); // 生成一个唯一的值作为锁的值
        $this->lockTimeout = $lockTimeout;
    }

    /**
     * @throws BusinessException
     */
    public function acquireLock($callback)
    {
        $result = $this->redis->set($this->lockKey, $this->lockValue, 'ex', $this->lockTimeout, 'nx');
        if (empty($result) || $result->getPayload() !== 'OK') {
            throw new BusinessException("处理中,请稍后再试");
        }
        try {
            return $callback();
        }catch (\Throwable $throwable){
            throw new BusinessException($throwable->getMessage(), $throwable->getCode());
        } finally {
            $this->releaseLock();
        }
    }

    private function releaseLock()
    {
        // 释放锁,只有锁的值匹配时才会删除锁,以避免误释放
        $script = "
            if redis.call('get', KEYS[1]) == ARGV[1] then
                return redis.call('del', KEYS[1])
            else
                return 0
            end
        ";
        $this->redis->eval($script, 1, $this->lockKey, $this->lockValue);
    }

    public function renewLock()
    {
        // 续期锁的过期时间
        $this->redis->expire($this->lockKey, $this->lockTimeout);
    }
}