package xiaoqu.home.open.redis;


import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

/***
 * 使用RedisTemplate简单实现分布式锁
 */
@Component
public class RedisDistributedLock {
	private Logger log = LoggerFactory.getLogger("Controller");
	/*** 分布式锁固定前缀 ***/
    private static final String REDIS_LOCK = "redis_lock_";
    /*** 分布式锁过期时间 ***/
    private static final Integer EXPIRE_TIME = 30;
    /*** 每次自旋睡眠时间 ***/
    private static final Integer SLEEP_TIME = 50;
    /*** 分布式锁自旋次数 ***/
    private static final Integer CYCLES = 10;
    @Autowired
    private RedisTemplate<String, String> redisTemplate4;

    /**
     * 加锁
     *
     * @param key   加锁唯一标识
     * @param value 释放锁唯一标识（建议使用线程ID作为value）
     */
    public void lock(String key, String value) {
        lock(key, value, EXPIRE_TIME);
    }

    /**
     * 加锁
     * @param key     加锁唯一标识
     * @param value   释放锁唯一标识（建议使用线程ID作为value）
     * @param timeout 超时时间（单位：S）
     */
    public boolean lock(String key, String value, Integer timeout) {
    	boolean bool = true;
        int cycles = CYCLES;
        // ----- 尝试获取锁，当获取到锁，则直接返回，否则，循环尝试获取
        while (!tryLock(key, value, timeout)) {
            // ----- 最多循环10次，当尝试了10次都没有获取到锁，抛出异常
            if (0 == (cycles--)) {
                log.error("redis try lock fail. key: {}, value: {}", key, value);
                bool = false;
                break;
            }
            
            try {
                TimeUnit.MILLISECONDS.sleep(SLEEP_TIME);
            } catch (Exception e) {
                log.error("history try lock error.", e);
            }
        }
        return bool;
    }

    /**
     * 尝试获取锁
     * @param key     加锁唯一标识
     * @param value   释放锁唯一标识（建议使用线程ID作为value）
     * @param timeout 超时时间（单位：S）
     * @return [true: 加锁成功; false: 加锁失败]
     */
    private boolean tryLock(String key, String value, Integer timeout) {
        Boolean result = redisTemplate4.opsForValue().setIfAbsent(REDIS_LOCK + key, value, timeout, TimeUnit.SECONDS);
        return result != null && result;
    }

    /**
     * 释放锁
     * @param key   加锁唯一标识
     * @param value 释放锁唯一标识（建议使用线程ID作为value）
     */
    public void unLock(String key, String value) {
        key = REDIS_LOCK + key;
        // ----- 通过value判断是否是该锁：是则释放；不是则不释放，避免误删
        if (value.equals(redisTemplate4.opsForValue().get(key))) {
        	redisTemplate4.delete(key);
        }
    }
}