package cn.estsh.i3plus.ext.mes.pcn.apiservice.schedulejob;

import cn.estsh.i3plus.platform.common.util.CommonConstWords;
import cn.estsh.i3plus.platform.common.util.MesConstWords;
import cn.estsh.impp.framework.base.schedule.BaseImppScheduleJob;
import cn.estsh.impp.framework.boot.init.ApplicationProperties;
import cn.estsh.impp.framework.boot.util.ImppRedis;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.quartz.JobExecutionContext;
import org.redisson.api.RLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.DigestUtils;

import javax.annotation.Resource;

/**
 * @author ns
 * @create 2021/11/15 0015 下午 17:04
 * 通过父类的方法来进行并发控制，解决xxljob不支持分布式并发的问题
 * 使用xxljob作为定时任务框架，一定要基础BaseMesScheduleJob，不然会出现并发问题
 */
public abstract class BaseMesScheduleJob extends BaseImppScheduleJob {

    public static final Logger LOGGER = LoggerFactory.getLogger(BaseMesScheduleJob.class);

    @Resource(name = CommonConstWords.IMPP_REDIS_MES_PCN)
    @Getter
    @Setter
    private ImppRedis redisMesPcn;

    @Resource(name = CommonConstWords.IMPP_REDIS_RES)
    private ImppRedis redisRes;

    private Class childClass;
    private String childDesc;

    @Getter
    @Setter
    private String wmsJobParams;

    /**
     * Redis 锁的存活时间，默认30分钟
     */
    @Getter
    @Setter
    private Integer redisLockTime = 1800;

    // 当一个 job class 需要同时运行多个 job 实例时把此属性设置为 true，默认为 false
    @Getter
    @Setter
    private boolean isMultiInstance = true;

    public BaseMesScheduleJob(Class cls, String jobDesc) {
        super(cls, jobDesc);
        this.childClass = cls;
        this.childDesc = jobDesc;
    }

    @Override
    public void executeImppJob(JobExecutionContext context, ApplicationProperties applicationProperties) {
        // 保存 job 参数
        this.wmsJobParams = super.getJobParam();
        String redisKey = MesConstWords.MES_JOB_REDIS_PREFIX + this.childClass.getName();

        if (isMultiInstance) {
            // 把 Job 参数也作为 key 的一部分，确保相同的 job class 配置多个 job instance 时可以各自运行
            redisKey = redisKey + ":" + getMd5String(this.getJobParam());
        }

        LOGGER.info("JOB REDIS KEY: " + redisKey);

        //添加redis锁（半小时失效）
//        if (redisMes.getObject(redisKey) != null) {
//            LOGGER.error("JOB REDIS KEY: " + redisKey+ " - "+this.childDesc + " 数据正在处理中，不能并发执行！");
//            // 直接返回
//            return;
//        }
        RLock rLock = (RLock) redisRes.getLock(redisKey);
        try {
            LOGGER.info("JOB REDIS KEY PUT: " + redisKey);
            //使用tryLock拿不到锁直接返回
            //rlock的leaseTime为-1，会每隔WatchdogTimeout秒去续约
            if (rLock.tryLock()){
                this.executeMesJob(context, applicationProperties);
            }else{
                LOGGER.error("JOB REDIS KEY: " + redisKey+ " - "+this.childDesc + " 数据正在处理中，不能并发执行！");
            }
            // 直接返回
            return;
//            redisMes.putObject(redisKey, redisKey, getRedisLockTime());
        } catch (Exception e) {
            LOGGER.error(ExceptionUtils.getStackTrace(e));
        } finally {
            LOGGER.info("JOB REDIS KEY DELETE: " + redisKey);
            //解除REDIS执行锁
//            redisMes.deleteKey(redisKey);
            if (rLock.isHeldByCurrentThread()) {
                rLock.unlock();
            }
        }
    }

    public abstract void executeMesJob(JobExecutionContext context, ApplicationProperties applicationProperties);

    /**
     * 获取字符串的 MD5 值
     * @param context 输入字符串
     * @return MD5 值
     */
    private String getMd5String(String context) {
        if (context == null || context.isEmpty()) {
            return "";
        }

        return DigestUtils.md5DigestAsHex(context.getBytes());
    }
}
