package com.yifu.cloud.plus.v1.yifu.common.core.redis;

/**
 * @program: master
 * @description: 分布式锁
 * @author: pwang
 * @create: 2020-06-01 15:51
 **/

import com.yifu.cloud.plus.v1.yifu.common.core.util.Common;
import com.yifu.cloud.plus.v1.yifu.common.core.util.SpringContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;

import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * redis分布式式锁
 *
 * @Author pwang
 * @Date 2020-06-02 14:53
 * @return
 **/
@Slf4j
public class RedisDistributedLock {

    private static final StringRedisTemplate redisTemplate = SpringContextHolder.getBean(StringRedisTemplate.class);
    private static final DefaultRedisScript<Long> LOCK_SCRIPT;
    private static final DefaultRedisScript<Object> UNLOCK_SCRIPT;
    private static String prefix = "RedisDistributedLock_";

    static {
        // 加载释放锁的脚本
        LOCK_SCRIPT = new DefaultRedisScript<>();
        LOCK_SCRIPT.setScriptSource(new ResourceScriptSource(new ClassPathResource("lock.lua")));
        LOCK_SCRIPT.setResultType(Long.class);

        // 加载释放锁的脚本
        UNLOCK_SCRIPT = new DefaultRedisScript<>();
        UNLOCK_SCRIPT.setScriptSource(new ResourceScriptSource(new ClassPathResource("unlock.lua")));
    }

    /**
     * 获取锁
     *
     * @param lockName    锁名称
     * @param releaseTime 超时时间(单位:秒)
     * @return key 解锁标识
     */
    public static String getLock(String lockName, String releaseTime) {
        // 存入的线程信息的前缀，防止与其它JVM中线程信息冲突
        String key = UUID.randomUUID().toString();
        // 执行脚本
        Long result = redisTemplate.execute(
                LOCK_SCRIPT,
                Collections.singletonList(prefix.concat(lockName)),
                key + Thread.currentThread().getId(), releaseTime);

        // 判断结果
        if (result != null && result.intValue() == 1) {
            return key;
        } else {
            return null;
        }
    }
    /**
     * 默认三秒过期
     * @Author pwang
     * @Date 2021-07-30 18:00
     * @param lockName
     * @return
     **/
    public static String getLock(String lockName) {
       return getLock(lockName,"3");
    }

    /**
     * 释放锁
     *
     * @param lockName 锁名称
     * @param key      解锁标识
     */
    public static void unlock(String lockName, String key) {
        // 执行脚本
        redisTemplate.execute(
                UNLOCK_SCRIPT,
                Collections.singletonList(prefix.concat(lockName)),
                key + Thread.currentThread().getId(), null);
    }


    /**
     * 锁在给定的等待时间内空闲，则获取锁成功 返回true， 否则返回false，作为阻塞式锁使用
     *
     * @param key                            锁键
     * @param releaseTime                    超时时间(单位:秒)
     * @param timeout                        尝试获取锁时长，建议传递500,结合实践单位，则可表示500毫秒
     * @param unit，建议传递TimeUnit.MILLISECONDS
     * @return requestId
     * @throws InterruptedException
     */
    public static String tryLock(String key, String releaseTime, long timeout, TimeUnit unit) throws InterruptedException {
        //纳秒
        long begin = System.nanoTime();
        if (null == unit) {
            unit = TimeUnit.MILLISECONDS;
        }
        do {
            //LOGGER.debug("{}尝试获得{}的锁.", value, key);
            String result = getLock(key, releaseTime);
            if (Common.isNotNull(result)) {
                /*  log.debug(value + "-成功获取{}的锁,设置锁过期时间为{}秒 ", key, timeout);*/
                return result;
            } else {
                // 存在锁 ，但可能获取不到，原因是获取的一刹那间
            }
            if (timeout == 0) {
                break;
            }
            //在其睡眠的期间，锁可能被解，也可能又被他人占用，但会尝试继续获取锁直到指定的时间
            Thread.sleep(10);


        } while ((System.nanoTime() - begin) < unit.toNanos(timeout));
        //因超时没有获得锁
        return null;
    }
    /**
     * 默认保存时间3秒，锁获取时间3000毫秒
     * @Author pwang
     * @Date 2020-06-02 15:00
     * @param key
     * @return
     **/
    public static String tryLock(String key) throws InterruptedException {
        return tryLock(key, "3", 3000, null);
    }


    /**
     * @param key
     * @param time
     * @Author: wangan
     * @Date: 2020/9/30
     * @Description:  审批时间较长。需要设置超过3秒时间
     * @return: java.lang.String
     **/
    public static String tryLockAndTime(String key,String time) throws InterruptedException {
        return tryLock(key, time, 3000, null);
    }
}

