package cn.quantgroup.xyqb.aspect.lock;

import cn.quantgroup.xyqb.exception.ResubmissionException;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionException;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * Redis Lock Aspect
 */
@Slf4j
@Aspect
@Component
public class RedisLockAspect {

    @Autowired
    @Qualifier("stringRedisTemplate")
    private RedisTemplate<String, String> stringRedisTemplate;

    @Pointcut("@annotation(cn.quantgroup.xyqb.aspect.lock.RedisLock)")
    private void redisLockPointCut() {
    }

    /**
     * @param pjp
     * @return
     * @throws Throwable ResubmissionException 上一个请求正在处理
     */
    @Around("redisLockPointCut()")
    private Object preventDuplicateSubmit(ProceedingJoinPoint pjp) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        Method method = methodSignature.getMethod();
        RedisLock annotation = method.getAnnotation(RedisLock.class);
        Object[] args = pjp.getArgs();
        String lockKey = null;
        int timeout = annotation.timeout();
        int expire = annotation.expire();
        String prefix = annotation.prefix();
        if (StringUtils.isBlank(prefix)) {
            prefix = method.getName();
        }
        if (StringUtils.isNotBlank(annotation.value())) {
            //指定了lockKey
            lockKey = annotation.value();
        } else if (StringUtils.isNotBlank(annotation.key())) {
            String keySPEL = annotation.key();
            try {
            	String keyPrefix = "#this";
                if (keySPEL.startsWith(keyPrefix)) {//判断是否是spel表达式
                    Expression expression = new SpelExpressionParser().parseExpression(keySPEL);
                    String value = expression.getValue(args, String.class);
                    lockKey = prefix.concat(":").concat(value);
                } else {
                    lockKey = prefix.concat(":").concat(keySPEL);
                }
            } catch (ExpressionException e) {
                log.error("key表达式“" + keySPEL + "”错误：{}", e);
                throw e;
            }
        } else {
            if (args != null && args.length > 1) {
                lockKey = prefix.concat(":").concat(DigestUtils.md5Hex(JSONObject.toJSONString(args)));
            } else {
                return pjp.proceed();
            }
        }
        cn.quantgroup.xyqb.util.RedisLock lock = new cn.quantgroup.xyqb.util.RedisLock(stringRedisTemplate, lockKey, timeout, expire);
        try {
            if (lock.lock()) {
                return pjp.proceed();
            } else {
                log.warn("调用方法失败，已有业务数据在处理中 lockKey：{}", lock.getLockKey());
                throw new ResubmissionException();
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.warn("获取锁失败：lockKey:{},exception:{}", lock.getLockKey(), e.getMessage());
            throw new ResubmissionException();
        } finally {
            lock.unlock();
        }
    }
}
