package com.pcloud.book.personalstage.biz.impl;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import com.alibaba.fastjson.JSON;
import com.pcloud.book.base.exception.BookBizException;
import com.pcloud.book.consumer.content.ResourceConsr;
import com.pcloud.book.consumer.feedback.FeedbackConsr;
import com.pcloud.book.consumer.wechatgroup.WechatGroupConsr;
import com.pcloud.book.custom.biz.CustomPlanBiz;
import com.pcloud.book.custom.mapper.CustomPlanModuleSuggestionMapper;
import com.pcloud.book.group.biz.WeixinQrcodeBiz;
import com.pcloud.book.guide.biz.PcloudGuideBiz;
import com.pcloud.book.keywords.enums.ReplyTypeEnum;
import com.pcloud.book.mq.delay.DelayMessageSender;
import com.pcloud.book.pcloudkeyword.dao.PcloudRobotClassifyDao;
import com.pcloud.book.pcloudkeyword.dao.PcloudRobotDao;
import com.pcloud.book.pcloudkeyword.entity.PcloudRobot;
import com.pcloud.book.pcloudkeyword.entity.PcloudRobotClassify;
import com.pcloud.book.personalstage.biz.PersonalStageBiz;
import com.pcloud.book.personalstage.biz.PersonalStageJumpBiz;
import com.pcloud.book.personalstage.check.PersonalStageCheck;
import com.pcloud.book.personalstage.constant.PersonalStageConstant;
import com.pcloud.book.personalstage.dao.PersonalStageDao;
import com.pcloud.book.personalstage.dao.PersonalStageJumpDao;
import com.pcloud.book.personalstage.dao.PersonalStageJumpEmailDao;
import com.pcloud.book.personalstage.dao.PersonalStageJumpKeywordDao;
import com.pcloud.book.personalstage.dao.PersonalStageJumpLinkupDao;
import com.pcloud.book.personalstage.dao.PersonalStageProgressMessageDao;
import com.pcloud.book.personalstage.dao.PersonalStageReplyDao;
import com.pcloud.book.personalstage.dao.PersonalStageUserDao;
import com.pcloud.book.personalstage.dao.UserReplaceCodeDao;
import com.pcloud.book.personalstage.dto.BaseDelayDTO;
import com.pcloud.book.personalstage.dto.EndServiceJumpNextDTO;
import com.pcloud.book.personalstage.dto.LinkClickRecordDTO;
import com.pcloud.book.personalstage.dto.LinkupDelayDTO;
import com.pcloud.book.personalstage.dto.PaperDelayDTO;
import com.pcloud.book.personalstage.dto.PersonalStageJumpDto;
import com.pcloud.book.personalstage.dto.PersonalStageJumpKeywordDto;
import com.pcloud.book.personalstage.dto.PlanJumpNextDTO;
import com.pcloud.book.personalstage.dto.ProgressDelayDTO;
import com.pcloud.book.personalstage.dto.TimeJumpDelayDTO;
import com.pcloud.book.personalstage.dto.TimeJumpNextDTO;
import com.pcloud.book.personalstage.entity.PersonalStage;
import com.pcloud.book.personalstage.entity.PersonalStageJump;
import com.pcloud.book.personalstage.entity.PersonalStageJumpEmail;
import com.pcloud.book.personalstage.entity.PersonalStageJumpKeyword;
import com.pcloud.book.personalstage.entity.PersonalStageJumpLinkup;
import com.pcloud.book.personalstage.entity.PersonalStageProgressMessage;
import com.pcloud.book.personalstage.entity.PersonalStageUser;
import com.pcloud.book.personalstage.entity.UserReplaceCode;
import com.pcloud.book.personalstage.enums.JumpTypeEnum;
import com.pcloud.book.personalstage.enums.PersonalStageUserStateEnum;
import com.pcloud.book.personalstage.vo.request.BaseStageJumpRequestVO;
import com.pcloud.book.personalstage.vo.request.CreateStageJumpRequestVO;
import com.pcloud.book.personalstage.vo.request.UpdateStageJumpRequestVO;
import com.pcloud.book.push.dao.PersonalAppletsDao;
import com.pcloud.book.push.entity.PersonalApplets;
import com.pcloud.book.util.common.YesOrNoEnums;
import com.pcloud.common.core.aspect.ParamLog;
import com.pcloud.common.core.biz.MessageBiz;
import com.pcloud.common.core.dto.SendEmailDto;
import com.pcloud.common.core.mq.DelayQueueDTO;
import com.pcloud.common.exceptions.BizException;
import com.pcloud.common.page.PageBeanNew;
import com.pcloud.common.page.PageParam;
import com.pcloud.common.utils.DateNewUtils;
import com.pcloud.common.utils.DateUtils;
import com.pcloud.common.utils.ListUtils;
import com.pcloud.common.utils.NumberUtil;
import com.pcloud.common.utils.cache.redis.JedisClusterUtils;
import com.pcloud.common.utils.httpclient.UrlUtils;
import com.pcloud.common.utils.string.StringUtil;
import com.pcloud.contentcenter.resource.dto.ResourceDTO;
import com.pcloud.feedback.paper.dto.PaperAndQuestionDto;
import com.pcloud.wechatgroup.group.dto.GroupRobotDTO;
import com.pcloud.wechatgroup.group.dto.GroupUserDTO;
import com.sdk.wxgroup.BaseVO;
import com.sdk.wxgroup.RobotProcessTypeEnum;
import com.sdk.wxgroup.SendMessageTypeEnum;
import com.sdk.wxgroup.SendPicMessageVO;
import com.sdk.wxgroup.SendTextMessageVO;
import com.sdk.wxgroup.im.mimc.BusinessConstant;
import com.sdk.wxgroup.im.mimc.dto.SendMomentsDTO;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;

import lombok.extern.slf4j.Slf4j;

@Component("personalStageJumpBiz")
@Slf4j
public class PersonalStageJumpBizImpl implements PersonalStageJumpBiz {

    private static final Logger LOGGER = LoggerFactory.getLogger(PersonalStageJumpBizImpl.class);

    @Autowired
    private PersonalStageBiz personalStageBiz;
    @Autowired
    private PersonalStageJumpDao personalStageJumpDao;
    @Autowired
    private PersonalStageJumpKeywordDao personalStageJumpKeywordDao;
    @Autowired
    private PersonalStageJumpEmailDao personalStageJumpEmailDao;
    @Autowired
    private PersonalStageJumpLinkupDao personalStageJumpLinkupDao;
    @Autowired
    private PersonalStageUserDao personalStageUserDao;
    @Autowired
    private WechatGroupConsr wechatGroupConsr;
    @Autowired
    private WeixinQrcodeBiz weixinQrcodeBiz;
    @Autowired
    private PcloudRobotDao pcloudRobotDao;
    @Autowired
    private PcloudRobotClassifyDao pcloudRobotClassifyDao;
    @Autowired
    private MessageBiz messageBiz;
    @Autowired
    private PcloudGuideBiz pcloudGuideBiz;
    @Autowired
    private DelayMessageSender delayMessageSender;
    @Autowired
    private PersonalStageDao personalStageDao;
    @Autowired
    private PersonalStageReplyDao personalStageReplyDao;
    @Autowired
    private PersonalStageProgressMessageDao personalStageProgressMessageDao;
    @Autowired
    private FeedbackConsr feedbackConsr;
    @Autowired
    private PersonalStageCheck personalStageCheck;
    @Autowired
    private ResourceConsr resourceConsr;
    @Value("${system.env}")
    private String envStr;
    @Autowired
    private CustomPlanBiz customPlanBiz;

    @Autowired
    private PersonalAppletsDao personalAppletsDao;
    @Autowired
    private CustomPlanModuleSuggestionMapper customPlanModuleSuggestionMapper;
    @Autowired
    private UserReplaceCodeDao userReplaceCodeDao;
    @Value("${wechat.group.link.prefix}")
    private String wechatGroupLinkPrefix;


    @Value("${wechat.group.link.prefix}")
    private String wechatLinkPrefix;
    //需求单链接替换符
    private final static String PERSONAL_STAGE_PAPER_TEMPLATE = "${PROGRESS_URL&amp;ProgressId}";
    //需求单链接替换符
    private final static String PERSONAL_STAGE_PAPER_TEMPLATE1 = "${PROGRESS_URL&ProgressId}";
    //需求定制单路由  https://wechat666.raysgo.com/personalCenter/questionNaire?paperId=679
    private final static String PERSONAL_STAGE_PAPER = "/personalCenter/questionNaire?paperId=";

    @Override
    @ParamLog("新增阶段跳转")
    @Transactional(rollbackFor = Exception.class)
    public void createPersonalStageJump(CreateStageJumpRequestVO vo) {
        //校验关键词是否重复
        if (JumpTypeEnum.READER_TRIGGER.key.equals(vo.getJumpType())||JumpTypeEnum.ROBOT_TRIGGER.key.equals(vo.getJumpType())){
            this.checkKeywords(vo.getKeywords(), vo.getPersonalStageId(), null);
        }
        if (JumpTypeEnum.PAPER_TRIGGER.key.equals(vo.getJumpType())){
            this.checkPaper(vo.getPersonalStageId(), null);
        }
        if (JumpTypeEnum.END_SERVICE.key.equals(vo.getJumpType())){
            checkOnlyOneEndServiceJump(vo.getPersonalStageId(),null);
        }
        if (JumpTypeEnum.PLAN_TRIGGER.key.equals(vo.getJumpType())){
            checkOnlyOnePlanJump(vo.getPersonalStageId(),null);
        }
        if(JumpTypeEnum.TIME_TRIGGER.key.equals(vo.getJumpType())){
            if(!NumberUtil.isNumber(vo.getToStageStartTime())){
                throw new BookBizException(BookBizException.PARAM_IS_ERROR, "定时时长不能为空");
            }
            checkOnlyOneTimeJump(vo.getPersonalStageId(), null);
        }
        if (vo.getOpenEmail()){
            this.checkEmail(vo.getEmails());
        }
        checkAudioReply(vo.getLinkups());
        PersonalStageJump personalStageJump = CreateStageJumpRequestVO.valueToJumpEntity(vo);
        personalStageJumpDao.insert(personalStageJump);
        List<PersonalStageJumpKeyword> jumpKeywords = BaseStageJumpRequestVO.valueToJumpKeywords(vo, personalStageJump.getId());
        if (!ListUtils.isEmpty(jumpKeywords) && !JumpTypeEnum.PAY_TRIGGER.key.equals(vo.getJumpType())
                && !JumpTypeEnum.PAPER_TRIGGER.key.equals(vo.getJumpType())) {
            personalStageJumpKeywordDao.batchInsert(jumpKeywords);
        }
        List<PersonalStageJumpEmail> jumpEmails = BaseStageJumpRequestVO.valueToJumpEmails(vo, personalStageJump.getId());
        if (!ListUtils.isEmpty(jumpEmails)) {
            personalStageJumpEmailDao.batchInsert(jumpEmails);
        }
        List<PersonalStageJumpLinkup> jumpLinkups = BaseStageJumpRequestVO.valueToJumpLinkups(vo, personalStageJump.getId());
        if (!ListUtils.isEmpty(jumpLinkups)){
            personalStageJumpLinkupDao.batchInsert(jumpLinkups);
        }
    }

    @ParamLog("校验语音")
    private void checkAudioReply(List<PersonalStageJumpLinkup> linkups) {
        if (ListUtils.isEmpty(linkups)){
            return;
        }
        List<Long> audioIds=linkups.stream().filter(s->ReplyTypeEnum.AUDIO.value.equals(s.getReplyType())&&s.getResourceId()!=null).map(PersonalStageJumpLinkup::getResourceId).collect(Collectors.toList());
        personalStageCheck.checkAudioLimit60S(audioIds);
    }

    @ParamLog("人工客服结束阶段跳转配置唯一性")
    private void checkOnlyOneEndServiceJump(Long personalStageId,Long personalStageJumpId) {
        List<PersonalStageJump> stageJumps = personalStageJumpDao.getByJumpType(personalStageId, JumpTypeEnum.END_SERVICE.key);
        if (!ListUtils.isEmpty(stageJumps)){
            if (personalStageJumpId==null){
                throw new BookBizException(BookBizException.ERROR, "已有其他阶段跳转配置了人工客服结束跳转");
            }else {
                //判断是不是旧的已有的修改
                List<Long> jumpIds=stageJumps.stream().filter(s->s.getId()!=null).map(PersonalStageJump::getId).collect(Collectors.toList());
                if (!jumpIds.contains(personalStageJumpId)){
                    throw new BookBizException(BookBizException.ERROR, "已有其他阶段跳转配置了人工客服结束跳转");
                }
            }
        }
    }

    @ParamLog("人工客服结束阶段跳转配置唯一性")
    private void checkOnlyOnePlanJump(Long personalStageId,Long personalStageJumpId) {
        List<PersonalStageJump> stageJumps = personalStageJumpDao.getByJumpType(personalStageId, JumpTypeEnum.PLAN_TRIGGER.key);
        if (!ListUtils.isEmpty(stageJumps)){
            if (personalStageJumpId==null){
                throw new BookBizException(BookBizException.ERROR, "已有其他阶段跳转配置了方案跳转");
            }else {
                //判断是不是旧的已有的修改
                List<Long> jumpIds=stageJumps.stream().filter(s->s.getId()!=null).map(PersonalStageJump::getId).collect(Collectors.toList());
                if (!jumpIds.contains(personalStageJumpId)){
                    throw new BookBizException(BookBizException.ERROR, "已有其他阶段跳转配置了方案跳转");
                }
            }
        }
    }

    /**
     * 定时跳转配置唯一性
     * @param personalStageId
     * @param personalStageJumpId
     */
    private void checkOnlyOneTimeJump(Long personalStageId, Long personalStageJumpId) {
        List<PersonalStageJump> stageJumps = personalStageJumpDao.getByJumpType(personalStageId, JumpTypeEnum.TIME_TRIGGER.key);
        if (!ListUtils.isEmpty(stageJumps)){
            if (personalStageJumpId==null){
                throw new BookBizException(BookBizException.ERROR, "已有其他阶段跳转配置了定时跳转");
            }else {
                //判断是不是旧的已有的修改
                List<Long> jumpIds=stageJumps.stream().filter(s->s.getId()!=null).map(PersonalStageJump::getId).collect(Collectors.toList());
                if (!jumpIds.contains(personalStageJumpId)){
                    throw new BookBizException(BookBizException.ERROR, "已有其他阶段跳转配置了定时跳转");
                }
            }
        }
    }

    @Override
    @ParamLog("获取跳转设置列表")
    public PageBeanNew getJumpList(Long personalStageId, Integer currentPage, Integer numPerPage) {
        if(!NumberUtil.isNumber(personalStageId) || personalStageId <= 0){
            throw new BizException(BizException.PARAM_IS_NULL.getCode(), "personalStageId 值不能为空");
        }
        HashMap<String, Object> map = Maps.newHashMap();
        map.put("personalStageId", personalStageId);
        PageBeanNew<PersonalStageJumpDto> pageBeanNew = personalStageJumpDao.listPageNew(new PageParam(currentPage, numPerPage), map, "getJumpList");
        List<PersonalStageJumpDto> recordList = pageBeanNew.getRecordList();
        if(ListUtils.isEmpty(recordList)){
            return pageBeanNew;
        }
        this.fillJumpKeywords(recordList);
        this.fillJumpEmails(recordList);
        this.fillJumpLinkups(recordList);
        return pageBeanNew;
    }

    @ParamLog("填充邮件")
    private void fillJumpEmails(List<PersonalStageJumpDto> recordList) {
        List<Long> jumpIds = recordList.stream().map(x -> x.getId()).collect(Collectors.toList());
        HashMap<String, Object> map = Maps.newHashMap();
        map.put("jumpIds", jumpIds);
        List<PersonalStageJumpEmail> jumpEmails = personalStageJumpEmailDao.listBy(map);
        if(ListUtils.isEmpty(jumpEmails)){
            return;
        }
        Map<Long, List<PersonalStageJumpEmail>> mapJumpEmails = jumpEmails.stream().collect(Collectors.groupingBy(x -> x.getPersonalStageJumpId()));
        for (PersonalStageJumpDto jumpDto : recordList){
            jumpDto.setJumpEmails(mapJumpEmails.getOrDefault(jumpDto.getId(), Lists.newArrayList()));
        }
    }

    @ParamLog("填充跳转衔接语")
    private void fillJumpLinkups(List<PersonalStageJumpDto> recordList) {
        List<Long> jumpIds = recordList.stream().map(x -> x.getId()).collect(Collectors.toList());
        HashMap<String, Object> map = Maps.newHashMap();
        map.put("jumpIds", jumpIds);
        List<PersonalStageJumpLinkup> linkups = personalStageJumpLinkupDao.listBy(map);
        if(ListUtils.isEmpty(linkups)){
            return;
        }
        fillLinkupResource(linkups);
        Map<Long, List<PersonalStageJumpLinkup>> mapJumpLinkups = linkups.stream().collect(Collectors.groupingBy(x -> x.getPersonalStageJumpId()));
        for (PersonalStageJumpDto jumpDto : recordList){
            jumpDto.setJumpLinkups(mapJumpLinkups.getOrDefault(jumpDto.getId(), Lists.newArrayList()));
        }
    }

    @ParamLog("填充资源信息")
    private void fillLinkupResource(List<PersonalStageJumpLinkup> linkups) {
        if (ListUtils.isEmpty(linkups)){
            return;
        }
        List<Long> resourceIds=linkups.stream().filter(s->(ReplyTypeEnum.RESOURCE.value.equals(s.getReplyType())
                ||ReplyTypeEnum.AUDIO.value.equals(s.getReplyType()))&&s.getResourceId()!=null).map(PersonalStageJumpLinkup::getResourceId).collect(Collectors.toList());
        if (!ListUtils.isEmpty(resourceIds)){
            Map<Long, ResourceDTO> resourceDTOMap = resourceConsr.mapByIds(resourceIds);
            for (PersonalStageJumpLinkup item:linkups) {
                Integer type = item.getReplyType();
                if (ReplyTypeEnum.RESOURCE.value.equals(type) || ReplyTypeEnum.AUDIO.value.equals(type)) {
                    ResourceDTO resourceDTO = resourceDTOMap.get(item.getResourceId());
                    if (resourceDTO != null) {
                        item.setResourceName(resourceDTO.getResourceName());
                        item.setResourceUrl(resourceDTO.getFileUrl());
                        item.setResourceTypeCode(resourceDTO.getTypeCode());
                        item.setResourceTypeName(resourceDTO.getTypeName());
                        item.setFileType(resourceDTO.getFileType());
                        item.setResourcePdfItems(resourceDTO.getResourcePdfItems());
                        item.setResourceOfficeItemDTOs(resourceDTO.getResourceOfficeItemDTOs());
                        item.setFileSize(resourceDTO.getFileSize());
                    }
                }
            }
        }
        for (PersonalStageJumpLinkup item:linkups){
            Integer type = item.getReplyType();
            if (ReplyTypeEnum.PERSONALAPPLETS.value.equals(type)){
                if(item.getPersonalAppletsId()!=null) {
                    PersonalApplets byId = personalAppletsDao.getById(item.getPersonalAppletsId());
                    item.setSlogan(byId.getSlogan());
                    item.setSloganImgUrl(byId.getSloganImgUrl());
                }
            }
        }
    }

    @ParamLog("填充跳转关键字列表")
    private void fillJumpKeywords(List<PersonalStageJumpDto> recordList) {
        List<Long> jumpIds = recordList.stream().map(x -> x.getId()).collect(Collectors.toList());
        HashMap<String, Object> map = Maps.newHashMap();
        map.put("jumpIds", jumpIds);
        List<PersonalStageJumpKeyword> keywords = personalStageJumpKeywordDao.listBy(map);
        if(ListUtils.isEmpty(keywords)){
            return;
        }
        Map<Long, List<PersonalStageJumpKeyword>> mapJumpKeyword = keywords.stream().collect(Collectors.groupingBy(x -> x.getPersonalStageJumpId()));
        for (PersonalStageJumpDto jumpDto : recordList){
            jumpDto.setJumpKeywords(mapJumpKeyword.getOrDefault(jumpDto.getId(), Lists.newArrayList()));
        }
    }

    @Override
    @ParamLog("修改跳转阶段")
    public void updatePersonalStageJump(UpdateStageJumpRequestVO vo) {
        //校验关键词是否重复
        if (JumpTypeEnum.READER_TRIGGER.key.equals(vo.getJumpType())||JumpTypeEnum.ROBOT_TRIGGER.key.equals(vo.getJumpType())){
            this.checkKeywords(vo.getKeywords(), vo.getPersonalStageId(), vo.getPersonalStageJumpId());
        }
        if (JumpTypeEnum.PAPER_TRIGGER.key.equals(vo.getJumpType())){
            this.checkPaper(vo.getPersonalStageId(), vo.getPersonalStageJumpId());
        }
        if (JumpTypeEnum.END_SERVICE.key.equals(vo.getJumpType())){
            checkOnlyOneEndServiceJump(vo.getPersonalStageId(),vo.getPersonalStageJumpId());
        }
        if (JumpTypeEnum.PLAN_TRIGGER.key.equals(vo.getJumpType())){
            checkOnlyOnePlanJump(vo.getPersonalStageId(),vo.getPersonalStageJumpId());
        }
        if (vo.getOpenEmail()){
            this.checkEmail(vo.getEmails());
        }
        checkAudioReply(vo.getLinkups());
        PersonalStageJump personalStageJump = UpdateStageJumpRequestVO.valueToJumpEntity(vo);
        personalStageJumpDao.update(personalStageJump);
        //删除原有的关键词
        personalStageJumpKeywordDao.deleteByJumpId(vo.getPersonalStageJumpId());
        //删除原有的邮件地址
        personalStageJumpEmailDao.deleteByJumpId(vo.getPersonalStageJumpId());
        //删除原有的衔接语
        personalStageJumpLinkupDao.deleteByJumpId(vo.getPersonalStageJumpId());
        List<PersonalStageJumpKeyword> jumpKeywords = BaseStageJumpRequestVO.valueToJumpKeywords(vo, personalStageJump.getId());
        if (!ListUtils.isEmpty(jumpKeywords) && !JumpTypeEnum.PAY_TRIGGER.key.equals(vo.getJumpType())
                && !JumpTypeEnum.PAPER_TRIGGER.key.equals(vo.getJumpType())){
            personalStageJumpKeywordDao.batchInsert(jumpKeywords);
        }
        List<PersonalStageJumpEmail> jumpEmails = BaseStageJumpRequestVO.valueToJumpEmails(vo, personalStageJump.getId());
        if (!ListUtils.isEmpty(jumpEmails)){
            personalStageJumpEmailDao.batchInsert(jumpEmails);
        }
        List<PersonalStageJumpLinkup> jumpLinkups = BaseStageJumpRequestVO.valueToJumpLinkups(vo, personalStageJump.getId());
        if (!ListUtils.isEmpty(jumpLinkups)){
            personalStageJumpLinkupDao.batchInsert(jumpLinkups);
        }
    }

    @ParamLog("校验跳转关键词")
    private void checkKeywords(List<String> keywords, Long personalStageId, Long personalStageJumpId) {
        if (ListUtils.isEmpty(keywords)){
            throw new BookBizException(BookBizException.ERROR, "跳转关键词不能为空");
        }
        List<String> words = keywords.stream().distinct().collect(Collectors.toList());
        if (keywords.size() != words.size()) {
            throw new BookBizException(BookBizException.ERROR, "跳转关键词不能重复");
        }
        PersonalStageJumpKeyword personalStageJumpKeyword = personalStageJumpKeywordDao.getStageKeywordByKeywords(keywords, personalStageId);
        if (null != personalStageJumpKeyword) {
            if (null == personalStageJumpId
                    || !personalStageJumpId.equals(personalStageJumpKeyword.getPersonalStageJumpId())) {
                //如果personalStageJumpId为空，则为新增时校验,否则为修改时校验
                throw new BookBizException(BookBizException.ERROR, "跳转关键词不能重复");
            }
        }
    }

    @ParamLog("校验邮件地址")
    private void checkEmail(List<String> emails){
        if (ListUtils.isEmpty(emails)){
            throw new BookBizException(BookBizException.ERROR, "邮件地址不能为空");
        }
        List<String> fEmail = emails.stream().filter(e -> !StringUtil.isEmpty(e)).collect(Collectors.toList());
        if (ListUtils.isEmpty(fEmail) || emails.size() != fEmail.size()){
            throw new BookBizException(BookBizException.ERROR, "邮件地址不能为空");
        }
    }

    @ParamLog("校验需求单跳转是否唯一")
    private void checkPaper(Long personalStageId, Long personalStageJumpId){
        //获取阶段信息
        PersonalStage personalStage = personalStageDao.getById(personalStageId);
        if (null == personalStage || null == personalStage.getPaperId()){
            throw new BookBizException(BookBizException.ERROR, "阶段未配置需求定制单");
        }
        //获取阶段的需求单跳转
        List<PersonalStageJump> personalStageJumps = personalStageJumpDao.getByJumpType(personalStageId, JumpTypeEnum.PAPER_TRIGGER.key);
        if (ListUtils.isEmpty(personalStageJumps)){
            return;
        }
        if (null == personalStageJumpId || personalStageJumpId.equals(personalStageJumps.get(0).getId())){
            return;
        }
        throw new BookBizException(BookBizException.ERROR, "已有其他阶段跳转配置了需求单跳转");
    }

    @Override
    @ParamLog("根据id获取跳转对象")
    public PersonalStageJumpDto getJump(Long jumpId) {
        if(!NumberUtil.isNumber(jumpId) || jumpId <= 0){
            throw new BizException(BizException.PARAM_IS_NULL.getCode(), "jumpId 值不能为空");
        }
        PersonalStageJumpDto jumpDto = personalStageJumpDao.getDtoById(jumpId);
        if(jumpDto == null){
            jumpDto = new PersonalStageJumpDto();
            jumpDto.setJumpKeywords(Lists.newArrayList());
            jumpDto.setJumpLinkups(Lists.newArrayList());
            return jumpDto;
        }
        this.fillJumpKeywords(Lists.newArrayList(jumpDto));
        this.fillJumpEmails(Lists.newArrayList(jumpDto));
        this.fillJumpLinkups(Lists.newArrayList(jumpDto));
        return jumpDto;
    }

    @Override
    @ParamLog("删除跳转")
    public void deleteJump(Long jumpId) {
        if(!NumberUtil.isNumber(jumpId) || jumpId <= 0){
            throw new BizException(BizException.PARAM_IS_NULL.getCode(), "jumpId 值不能为空");
        }
        // 删除跳转
        personalStageJumpDao.deleteById(jumpId);
        // 删除关键字
        personalStageJumpKeywordDao.deleteByJumpId(jumpId);
        // 删除邮件
        personalStageJumpEmailDao.deleteByJumpId(jumpId);
    }

    @Override
    @ParamLog("开启关闭邮件提醒")
    public Long updateEmailStatus(Long jumpId, Integer openEmail) {
        if(!NumberUtil.isNumber(jumpId) || jumpId <= 0){
            throw new BizException(BizException.PARAM_IS_NULL.getCode(), "jumpId 值不能为空");
        }
        PersonalStageJump jump = new PersonalStageJump();
        jump.setId(jumpId);
        jump.setOpenEmail(YesOrNoEnums.YES.getValue().equals(openEmail));
        return personalStageJumpDao.update(jump);
    }

    /**
     *
     * @param userWxId
     * @param robotWxId
     * @param content
     * @return  false:未能跳转到下个阶段，需要后续的其它处理；
     *          true:命中关键字，已经跳转到下个阶段，无需后续处理；
     */
    @Override
    @ParamLog("处理用户阶段跳转逻辑")
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
    public Boolean handlePersonalStageJump(String userWxId, String robotWxId, String content, JumpTypeEnum jumpTypeEnum, String bookGroupUrl,String adviserBookUrl) {
        // 查询用户的当前状态信息
        PersonalStageUser currentStageUser = personalStageUserDao.getLast(userWxId, robotWxId, null);
        // 如果用户没有阶段状态，则不处理
        if(currentStageUser == null || currentStageUser.getId() <= 0) {
            return false;
        }
        // TODO 判断消息的阶段是否和用户当前阶段匹配，如果不匹配需要当做已处理状态返回，以免触发后续其它操作
        //  (此操作需要建立在用户的消息有阶段id的情况下)
        // if(personalStageId != currentStageUser.getPersonalStageId()) { return true;  }
        // 查询命中关键字跳转下一阶段的id
        PersonalStageJumpKeywordDto jumpKeywordDto = personalStageJumpKeywordDao.getByKeyword(currentStageUser.getPersonalStageId(), content, jumpTypeEnum.key);
        if(null == jumpKeywordDto || StringUtil.isEmpty(jumpKeywordDto.getKeyword())){
            // 未能命中关键词
            return false;
        }
        // 如果下一个阶段是空，则已完成
        if(null == jumpKeywordDto.getAfterPersonalStageId() || jumpKeywordDto.getAfterPersonalStageId() <=0){
            // 更新到完成状态
            // updateStageToComplete(currentStageUser);
            return true;
        }
        GroupRobotDTO groupRobotDTO = wechatGroupConsr.getGroupRobotByWxId(robotWxId);
        String ip = weixinQrcodeBiz.getRobotIpByGeneration(groupRobotDTO.getVersion());
        //处理需求单记录
        dealPaperRecord(currentStageUser.getPersonalStageId(),jumpKeywordDto.getAfterPersonalStageId(), userWxId, robotWxId);
        // 发送邮件
        sendEmail(userWxId, robotWxId, content, jumpKeywordDto);
        // 将用户置为下个阶段
        PersonalStageUser nextPersonalStageUser = personalStageBiz.nextStageAddStageUserAndWakeupDelay(robotWxId, userWxId, ip, jumpKeywordDto.getAfterPersonalStageId());
        // 发送内容衔接语
        sendJumpLinkups(userWxId, robotWxId, ip, jumpKeywordDto.getPersonalStageJumpId(),nextPersonalStageUser.getId(),bookGroupUrl,adviserBookUrl);
        // 停止发送引导语
        pcloudGuideBiz.stopPcloudGuidePush(robotWxId, userWxId);
        //存用户跳转之前的最后一次非关键词回复到表
        personalStageBiz.addUserReplaceCodeBeforeJump(currentStageUser.getPersonalStageId(),userWxId,robotWxId);
        return true;
    }

    @ParamLog("跳转到之前的阶段，清除用户填写需求单记录")
    public void dealPaperRecord(Long personalStageId, Long afterPersonalStageId, String userWxId, String robotId){
        PersonalStage personalStage = personalStageDao.getById(personalStageId);
        PersonalStage afterPersonalStage = personalStageDao.getById(afterPersonalStageId);
        if (null == personalStage || null == afterPersonalStage){
            return;
        }
        Boolean jumpBefore = false;
        if (null != personalStage.getSeqNum() && null!= afterPersonalStage.getSeqNum()){
            //当前阶段比下一阶段序号大
            if (personalStage.getSeqNum()>afterPersonalStage.getSeqNum()){
                 jumpBefore = true;
            }else if (personalStage.getSeqNum().equals(afterPersonalStage.getSeqNum())){
                //序号相同，当前阶段id比下一阶段大
                if (personalStageId.compareTo(afterPersonalStageId)>0){
                    jumpBefore = true;
                }
            }
        }
        if (jumpBefore){
            this.deletePaperRecord(robotId, userWxId);
        }
    }

    @ParamLog("处理衔接语跳转延时")
    @Override
    public void dealDelayLinkup(DelayQueueDTO dto) {
        LinkupDelayDTO linkupDelayDTO=(LinkupDelayDTO)dto.getMsg();
        PersonalStageJumpLinkup jumpLinkup = personalStageJumpLinkupDao.getById(linkupDelayDTO.getPersonalStageJumpLinkupId());
        if (jumpLinkup==null){
            LOGGER.info("没有该跳转衔接语！");
            return;
        }
        List<PersonalStageJumpLinkup> list = new ArrayList<>();
        list.add(jumpLinkup);
        this.replacePaperUrl(list, linkupDelayDTO.getRobotId(), linkupDelayDTO.getWxId(), jumpLinkup.getPersonalStageJumpId(),linkupDelayDTO.getIp());
        jumpLinkup = list.get(0);
        if(ReplyTypeEnum.TEXT.value.equals( jumpLinkup.getReplyType())){
            SendTextMessageVO sendTextMessageVO = new SendTextMessageVO();
            sendTextMessageVO.setContent(personalStageBiz.replaceProjectProgressUrl(jumpLinkup.getLinkupContent(),linkupDelayDTO.getRobotId(),linkupDelayDTO.getWxId(),linkupDelayDTO.getPersonalStageUserId(),linkupDelayDTO.getIp()));
            sendTextMessageVO.setAltId(linkupDelayDTO.getRobotId());
            sendTextMessageVO.setWxGroupId(dto.getKey());
            sendTextMessageVO.setIp(linkupDelayDTO.getIp());
            sendTextMessageVO.setCode(SendMessageTypeEnum.SELF.getCode());
            fillMessageBaseInfo(sendTextMessageVO, linkupDelayDTO);
            //加跳转衔接语社群书链接
            if (!StringUtil.isEmpty(linkupDelayDTO.getBookGroupUrl())){
                sendTextMessageVO.setContent(sendTextMessageVO.getContent()+linkupDelayDTO.getBookGroupUrl());
            }
            //加跳转衔接语编辑书链接
            if (!StringUtil.isEmpty(linkupDelayDTO.getAdviserBookUrl())){
                sendTextMessageVO.setContent(sendTextMessageVO.getContent()+linkupDelayDTO.getAdviserBookUrl());
                if ("没有资源".equals(linkupDelayDTO.getAdviserBookUrl())){
                    sendTextMessageVO.setContent("非常抱歉，主人。您输入的图书，小睿没有找到相关资源呢，请将其他您感兴趣的图书书名发送给我吧~");
                }
            }
            wechatGroupConsr.sendMessage(JSON.toJSONString(sendTextMessageVO));;
        } else if(ReplyTypeEnum.IMAGE.value.equals( jumpLinkup.getReplyType())) {
            SendPicMessageVO sendPicMessageVO = new SendPicMessageVO();
            sendPicMessageVO.setWxGroupId(dto.getKey());
            sendPicMessageVO.setAltId(linkupDelayDTO.getRobotId());
            sendPicMessageVO.setPicUrl(jumpLinkup.getPicUrl());
            sendPicMessageVO.setIp(linkupDelayDTO.getIp());
            sendPicMessageVO.setCode(SendMessageTypeEnum.SELF.getCode());
            fillMessageBaseInfo(sendPicMessageVO, linkupDelayDTO);
            wechatGroupConsr.sendMessage(JSON.toJSONString(sendPicMessageVO));
        } else if (ReplyTypeEnum.AUDIO.value.equals( jumpLinkup.getReplyType())){
            SendMomentsDTO sendMomentsDTO=new SendMomentsDTO();
            Map<Long, ResourceDTO> map = resourceConsr.mapByIds(Arrays.asList(jumpLinkup.getResourceId()));
            ResourceDTO resourceDTO = map.get(jumpLinkup.getResourceId());
            if (resourceDTO != null) {
                GroupRobotDTO info = wechatGroupConsr.getGroupRobotByRobotId(linkupDelayDTO.getRobotId());
                if (info!=null){
                    sendMomentsDTO.setIsMasterWx(info.getMasterWx());
                    sendMomentsDTO.setReceiveAccount(info.getMac());
                }
                sendMomentsDTO.setMomentType(BusinessConstant.MomentTypeEnum.SEND_WX_RECORD);
                sendMomentsDTO.setContent(dto.getKey());
                sendMomentsDTO.setLink(resourceDTO.getFileUrl());
                sendMomentsDTO.setIp(linkupDelayDTO.getIp());
                sendMomentsDTO.setAltId(linkupDelayDTO.getRobotId());
                sendMomentsDTO.setWxId(dto.getKey());
                fillMessageBaseInfo(sendMomentsDTO, linkupDelayDTO);
                wechatGroupConsr.sendMessage(JSON.toJSONString(sendMomentsDTO));
            }
        } else if (ReplyTypeEnum.PERSONALAPPLETS.value.equals( jumpLinkup.getReplyType())){
            SendMomentsDTO sendMomentsDTO=new SendMomentsDTO();
            List<String> userWxIds=new ArrayList<>();
            userWxIds.add(dto.getKey());
            //发送的小程序
            PersonalApplets byId = personalAppletsDao.getById(jumpLinkup.getPersonalAppletsId());
            if(byId==null){
                return;
            }
            GroupRobotDTO info = wechatGroupConsr.getGroupRobotByRobotId(linkupDelayDTO.getRobotId());
            if (info != null) {
                sendMomentsDTO.setIsMasterWx(info.getMasterWx());
                sendMomentsDTO.setReceiveAccount(info.getMac());
            }
            sendMomentsDTO.setMomentType(BusinessConstant.MomentTypeEnum.SEND_WX_MINI);
            sendMomentsDTO.setContent(byId.getAppletsId());//小程序id
            sendMomentsDTO.setLink(byId.getAppletsUrl());//小程序访问地址
            sendMomentsDTO.setImages(userWxIds);//发送人
            sendMomentsDTO.setLinkTitle(byId.getSlogan());//小程序宣传语
            sendMomentsDTO.setLinkDesc(byId.getSloganImgUrl());//小程序宣传图
            sendMomentsDTO.setIp(linkupDelayDTO.getIp());
            sendMomentsDTO.setCode(SendMessageTypeEnum.SELF.getCode());
            fillMessageBaseInfo(sendMomentsDTO, linkupDelayDTO);
            sendMomentsDTO.setAltId(linkupDelayDTO.getRobotId());
            sendMomentsDTO.setWxId(dto.getKey());
            wechatGroupConsr.sendMessage(JSON.toJSONString(sendMomentsDTO));

        }
    }

    @ParamLog("填充消息基本属性")
    private void fillMessageBaseInfo(BaseVO baseVO, BaseDelayDTO baseDelayDTO){
        baseVO.setRobotProcessType(baseDelayDTO.getRobotProcessType());
        baseVO.setMessageGroupId(baseDelayDTO.getMessageGroupId());
        baseVO.setIndex(baseDelayDTO.getIndex());
        baseVO.setCounts(baseDelayDTO.getCounts());
    }

    @ParamLog("处理用户提交表单阶段跳转")
    public boolean handlePersonalStagePaperJump(String userWxId, String robotWxId, Long paperId, String emailContent){
        if (null == paperId){
            return false;
        }
        // 查询用户的当前状态信息
        PersonalStageUser currentStageUser = personalStageUserDao.getLast(userWxId, robotWxId, null);
        // 如果用户没有阶段状态，则不处理
        if(currentStageUser == null || currentStageUser.getId() <= 0) {
            return false;
        }
        Long stageId = currentStageUser.getPersonalStageId();
        PersonalStage personalStage = personalStageDao.getById(stageId);
        Long jumpPaperId = personalStage.getPaperId();
        if (!paperId.equals(jumpPaperId)){
            //如果不是当前阶段的需求单id，则不处理
            return false;
        }
        //获取当前阶段的表单跳转
        List<PersonalStageJump> personalStageJump = personalStageJumpDao.getByJumpType(stageId, JumpTypeEnum.PAPER_TRIGGER.key);
        if (ListUtils.isEmpty(personalStageJump)){
            return false;
        }
        PersonalStageJump paperJump = personalStageJump.get(0);
        // 如果下一个阶段是空，则已完成
        if(null == paperJump.getAfterPersonalStageId() || paperJump.getAfterPersonalStageId() <=0){
            // 更新到完成状态
            updateStageToComplete(currentStageUser);
            return true;
        }
        GroupRobotDTO groupRobotDTO = wechatGroupConsr.getGroupRobotByWxId(robotWxId);
        String ip = weixinQrcodeBiz.getRobotIpByGeneration(groupRobotDTO.getVersion());
        // 发送邮件
        String content = "<br>";
        if (!StringUtil.isEmpty(emailContent)){
            content = content + emailContent;
        }
        PersonalStageJumpKeywordDto jumpKeywordDto = new PersonalStageJumpKeywordDto();
        jumpKeywordDto.setAfterPersonalStageId(paperJump.getAfterPersonalStageId());
        jumpKeywordDto.setPersonalStageJumpId(paperJump.getId());
        sendEmail(userWxId, robotWxId, content, jumpKeywordDto);
        //处理需求单记录
        dealPaperRecord(stageId,paperJump.getAfterPersonalStageId(),userWxId, robotWxId);
        // 将用户置为下个阶段
        PersonalStageUser nextPersonalStageUser = personalStageBiz.nextStageAddStageUserAndWakeupDelay(robotWxId, userWxId, ip, paperJump.getAfterPersonalStageId());
        // 发送内容衔接语
        sendJumpLinkups(userWxId, robotWxId, ip, paperJump.getId(), nextPersonalStageUser.getId(), null, null);
        // 停止发送引导语
        pcloudGuideBiz.stopPcloudGuidePush(robotWxId, userWxId);
        return true;
    }

    @Override
    public void sendPaperEmail(String userWxId, String robotWxId, String emailContent) {
        GroupUserDTO wxUserInfo = wechatGroupConsr.getWxUserInfoByWxUserId(userWxId);
        if(wxUserInfo==null){
            LOGGER.info("未找到用户信息，无法发送阶段跳转邮件");
        }
        PcloudRobot pcloudRobot = pcloudRobotDao.getByWxId(robotWxId);
        PcloudRobotClassify robotClassify = pcloudRobotClassifyDao.getById(pcloudRobot.getRobotType().longValue());
        if (robotClassify == null|| robotClassify.getKeywordClassifyId() == null){
            LOGGER.info("小号分类id为空，无法发送阶段跳转邮件");
            return;
        }
        List<String> emailList = new ArrayList<>();
        if ("pro".equalsIgnoreCase(envStr)){
            emailList.add("leibin@dcrays.cn");
            emailList.add("zhoulei@dcrays.cn");
        }else {
            emailList.add("zhoulei@dcrays.cn");
        }
        SendEmailDto sendEmailDto = new SendEmailDto();
        sendEmailDto.setTypeCode("feedback_notice_email");
        Map<String, Object> content = new HashMap<>();
        content.put("nickname", wxUserInfo.getNickName());
        content.put("wxUserId", userWxId);
        content.put("robotClassifyName", robotClassify.getClassifyName());
        content.put("robotNumber", pcloudRobot.getUniqueNumber());
        content.put("robotId", robotWxId);
        content.put("time", DateNewUtils.getLongDateStr());
        if (!StringUtil.isEmpty(emailContent)){
            content.put("content", emailContent);
        }
        sendEmailDto.setContent(content);
        for (String email: emailList){
            sendEmailDto.setToEmail(email);
            messageBiz.sendEmail(sendEmailDto);
        }
    }

    @ParamLog("获取定制化阶段是否含有人工客服结束跳转")
    @Override
    public Boolean getHasEndServiceJump(Long personalStageId) {
        if (personalStageId==null){
            throw new BookBizException(BookBizException.PARAM_IS_NULL,"参数为空！");
        }
        List<PersonalStageJump> jumps = personalStageJumpDao.getByJumpType(personalStageId, JumpTypeEnum.END_SERVICE.key);
        if (ListUtils.isEmpty(jumps)){
            return false;
        }
        return true;
    }

    @Transactional(rollbackFor = Exception.class)
    @ParamLog("获取定制化阶段是否含有人工客服结束跳转")
    @Override
    public void endServiceJumpNext(EndServiceJumpNextDTO endServiceJumpNextDTO) {
        if (endServiceJumpNextDTO==null){
            throw new BookBizException(BookBizException.PARAM_IS_NULL,"参数为空！");
        }
        if (endServiceJumpNextDTO.getPersonalStageId()==null){
            throw new BookBizException(BookBizException.PARAM_IS_NULL,"阶段id参数为空！");
        }
        if (StringUtil.isEmpty(endServiceJumpNextDTO.getRobotId())){
            throw new BookBizException(BookBizException.PARAM_IS_NULL,"机器人id参数为空！");
        }
        if (StringUtil.isEmpty(endServiceJumpNextDTO.getWxId())){
            throw new BookBizException(BookBizException.PARAM_IS_NULL,"用户id参数为空！");
        }
        String wxId=endServiceJumpNextDTO.getWxId();
        String robotId=endServiceJumpNextDTO.getRobotId();
        Long personalStageId=endServiceJumpNextDTO.getPersonalStageId();
        PersonalStageUser last = personalStageUserDao.getLast(wxId, robotId, personalStageId);
        if (last==null){
            throw new BookBizException(BookBizException.ERROR,"用户无阶段！");
        }
        if (!last.getPersonalStageId().equals(personalStageId)){
            throw new BookBizException(BookBizException.ERROR,"用户已不在当前阶段！");
        }
        //查询跳转
        List<PersonalStageJump> jumps = personalStageJumpDao.getByJumpType(personalStageId, JumpTypeEnum.END_SERVICE.key);
        if (ListUtils.isEmpty(jumps)){
            throw new BookBizException(BookBizException.ERROR,"没有该类型的跳转的阶段！");
        }
        PersonalStageJump personalStageJump=jumps.get(0);
        Long afterStageId=personalStageJump.getAfterPersonalStageId();
        Long stageJumpId=personalStageJump.getId();
        if (afterStageId==null){
            throw new BookBizException(BookBizException.ERROR,"没有下一阶段！");
        }
        if (personalStageJump.getOpenEmail()!=null&&personalStageJump.getOpenEmail()){
            PersonalStageJumpKeywordDto jumpKeywordDto = new PersonalStageJumpKeywordDto();
            jumpKeywordDto.setAfterPersonalStageId(afterStageId);
            jumpKeywordDto.setPersonalStageJumpId(stageJumpId);
            String content = "如果读者提交了对方案的反馈意见，请到“定制服务”中查看。";
            sendEmail(wxId, robotId, content, jumpKeywordDto);
        }
        GroupRobotDTO groupRobotDTO = wechatGroupConsr.getGroupRobotByWxId(robotId);
        String ip = weixinQrcodeBiz.getRobotIpByGeneration(groupRobotDTO.getVersion());
        //处理需求单记录
        dealPaperRecord(personalStageId,afterStageId,wxId, robotId);
        // 将用户置为下个阶段
        PersonalStageUser nextPersonalStageUser = personalStageBiz.nextStageAddStageUserAndWakeupDelay(robotId, wxId, ip, afterStageId);
        // 发送内容衔接语
        sendJumpLinkups(wxId, robotId, ip, stageJumpId, nextPersonalStageUser.getId(),null,null);
        // 停止发送引导语
        pcloudGuideBiz.stopPcloudGuidePush(robotId, wxId);
    }

    @Transactional(rollbackFor = Exception.class)
    @ParamLog("获取定制化阶段是否含有方案跳转")
    @Override
    public void planJumpNext(PlanJumpNextDTO planJumpNextDTO) {
        if (planJumpNextDTO==null){
            throw new BookBizException(BookBizException.PARAM_IS_NULL,"参数为空！");
        }
        if (planJumpNextDTO.getPersonalStageId()==null){
            throw new BookBizException(BookBizException.PARAM_IS_NULL,"阶段id参数为空！");
        }
        if (StringUtil.isEmpty(planJumpNextDTO.getRobotWxId())){
            throw new BookBizException(BookBizException.PARAM_IS_NULL,"机器人id参数为空！");
        }
        if (StringUtil.isEmpty(planJumpNextDTO.getWxId())){
            throw new BookBizException(BookBizException.PARAM_IS_NULL,"用户id参数为空！");
        }
        String wxId=planJumpNextDTO.getWxId();
        String robotId=planJumpNextDTO.getRobotWxId();
        Long personalStageId=planJumpNextDTO.getPersonalStageId();
        PersonalStageUser last = personalStageUserDao.getLast(wxId, robotId, personalStageId);
        if (last==null){
            LOGGER.info("用户无阶段，无法进行方案跳转！");
            return;
        }
        if (!last.getPersonalStageId().equals(personalStageId)){
            LOGGER.info("用户已不在当前阶段，无法进行方案跳转！");
            return;
        }
        //查询跳转
        List<PersonalStageJump> jumps = personalStageJumpDao.getByJumpType(personalStageId, JumpTypeEnum.PLAN_TRIGGER.key);
        if (ListUtils.isEmpty(jumps)){
            LOGGER.info("没有该类型的跳转的阶段，无法进行方案跳转！");
            return;
        }
        PersonalStageJump personalStageJump=jumps.get(0);
        Long afterStageId=personalStageJump.getAfterPersonalStageId();
        Long stageJumpId=personalStageJump.getId();
        if (afterStageId==null){
            LOGGER.info("没有下一阶段，无法进行方案跳转！");
            return;
        }
        if (personalStageJump.getOpenEmail()!=null&&personalStageJump.getOpenEmail()){
            PersonalStageJumpKeywordDto jumpKeywordDto = new PersonalStageJumpKeywordDto();
            jumpKeywordDto.setAfterPersonalStageId(afterStageId);
            jumpKeywordDto.setPersonalStageJumpId(stageJumpId);
            String content = "如果读者提交了对方案的反馈意见，请到“定制服务”中查看。";
            sendEmail(wxId, robotId, content, jumpKeywordDto);
        }
        GroupRobotDTO groupRobotDTO = wechatGroupConsr.getGroupRobotByWxId(robotId);
        String ip = weixinQrcodeBiz.getRobotIpByGeneration(groupRobotDTO.getVersion());
        //处理需求单记录
        dealPaperRecord(personalStageId,afterStageId,wxId, robotId);
        // 将用户置为下个阶段
        PersonalStageUser nextPersonalStageUser = personalStageBiz.nextStageAddStageUserAndWakeupDelay(robotId, wxId, ip, afterStageId);
        // 发送内容衔接语
        sendJumpLinkups(wxId, robotId, ip, stageJumpId, nextPersonalStageUser.getId(),null,null);
        // 停止发送引导语
        pcloudGuideBiz.stopPcloudGuidePush(robotId, wxId);
    }

    @ParamLog("设置延迟定时跳转")
    public void setDelayTimeJump(TimeJumpNextDTO timeJumpNextDTO){
        if (timeJumpNextDTO==null){
            throw new BookBizException(BookBizException.PARAM_IS_NULL,"参数为空！");
        }
        if (timeJumpNextDTO.getPersonalStageId()==null){
            throw new BookBizException(BookBizException.PARAM_IS_NULL,"阶段id参数为空！");
        }
        if (StringUtil.isEmpty(timeJumpNextDTO.getRobotWxId())){
            throw new BookBizException(BookBizException.PARAM_IS_NULL,"机器人id参数为空！");
        }
        if (StringUtil.isEmpty(timeJumpNextDTO.getWxId())){
            throw new BookBizException(BookBizException.PARAM_IS_NULL,"用户id参数为空！");
        }
        //查询跳转
        List<PersonalStageJump> jumps = personalStageJumpDao.getByJumpType(timeJumpNextDTO.getPersonalStageId(), JumpTypeEnum.TIME_TRIGGER.key);
        if (ListUtils.isEmpty(jumps)){
            LOGGER.info("没有该类型的跳转的阶段，无法设置定时跳转！");
            return;
        }
        PersonalStageJump personalStageJump=jumps.get(0);
        Long afterStageId=personalStageJump.getAfterPersonalStageId();
        if (afterStageId==null){
            LOGGER.info("没有下一阶段，无法设置定时跳转！");
            return;
        }
        Integer toStageStartTime=personalStageJump.getToStageStartTime();
        TimeJumpDelayDTO delayDTO = new TimeJumpDelayDTO();
        delayDTO.setRobotId(timeJumpNextDTO.getRobotWxId());
        delayDTO.setWxId(timeJumpNextDTO.getWxId());
        delayDTO.setIp(timeJumpNextDTO.getIp());
        delayDTO.setPersonalStageId(timeJumpNextDTO.getPersonalStageId());
        delayDTO.setPersonalStageUserId(timeJumpNextDTO.getPersonalStageUserId());
        delayDTO.setPersonalStageCreateTime(DateUtils.formatDate(new Date(),"yyyy-MM-dd HH:mm:ss"));
        DelayQueueDTO delayQueueDTO = DelayQueueDTO.builder().key(timeJumpNextDTO.getWxId()).type(PersonalStageConstant.PERSONALSTAGE_DELAY_TIME_JUMP).msg(delayDTO).timeout(toStageStartTime*60*1000).build();
        delayMessageSender.send(delayQueueDTO);
    }


    @Transactional(rollbackFor = Exception.class)
    @ParamLog("处理延迟定时跳转")
    @Override
    public void dealDelayTimeJump(DelayQueueDTO dto) {
        TimeJumpDelayDTO delayDTO = (TimeJumpDelayDTO) dto.getMsg();
        String wxId = delayDTO.getWxId();
        String robotId = delayDTO.getRobotId();
        Long personalStageId = delayDTO.getPersonalStageId();
        PersonalStageUser last = personalStageUserDao.getLast(wxId, robotId, personalStageId);
        if (last==null){
            LOGGER.info("用户无阶段，无法进行定时跳转！");
            return;
        }
        if (!last.getPersonalStageId().equals(personalStageId)){
            LOGGER.info("用户已不在当前阶段，无法进行定时跳转！");
            return;
        }
        //查询跳转
        List<PersonalStageJump> jumps = personalStageJumpDao.getByJumpType(personalStageId, JumpTypeEnum.TIME_TRIGGER.key);
        if (ListUtils.isEmpty(jumps)){
            LOGGER.info("没有该类型的跳转的阶段，无法进行定时跳转！");
            return;
        }
        PersonalStageJump personalStageJump=jumps.get(0);
        Long afterStageId=personalStageJump.getAfterPersonalStageId();
        Long stageJumpId=personalStageJump.getId();
        if (afterStageId==null){
            LOGGER.info("没有下一阶段，无法进行定时跳转！");
            return;
        }
        // 查询用户最后一次输入书名对应的权益（方案）
        // 查询用户最近搜索的书
        UserReplaceCode lastOneUserReplace = userReplaceCodeDao.getLastOneUserReplace(wxId, robotId, 1);
        if (lastOneUserReplace == null){
            LOGGER.info("未找到用户输入的书名，无法进行跳转");
            return;
        }

        if (personalStageJump.getOpenEmail()!=null && personalStageJump.getOpenEmail()){
            PersonalStageJumpKeywordDto jumpKeywordDto = new PersonalStageJumpKeywordDto();
            jumpKeywordDto.setAfterPersonalStageId(afterStageId);
            jumpKeywordDto.setPersonalStageJumpId(stageJumpId);
            String content = "读者触发了定时跳转";
            sendEmail(wxId, robotId, content, jumpKeywordDto);
        }
        GroupRobotDTO groupRobotDTO = wechatGroupConsr.getGroupRobotByWxId(robotId);
        String ip = weixinQrcodeBiz.getRobotIpByGeneration(groupRobotDTO.getVersion());
        //处理需求单记录
        dealPaperRecord(personalStageId,afterStageId,wxId, robotId);
        // 将用户置为下个阶段
        PersonalStageUser nextPersonalStageUser = personalStageBiz.nextStageAddStageUserAndWakeupDelay(robotId, wxId, ip, afterStageId);
        // 发送内容衔接语
        sendJumpLinkups(wxId, robotId, ip, stageJumpId, nextPersonalStageUser.getId(),null,null);
        // 停止发送引导语
        pcloudGuideBiz.stopPcloudGuidePush(robotId, wxId);
    }



    @ParamLog("发送阶段跳转邮件")
    private void sendEmail(String userWxId, String robotWxId, String content, PersonalStageJumpKeywordDto jumpKeywordDto) {
        try {
            List<PersonalStageJumpEmail> jumpEmails = personalStageJumpEmailDao.getByJumpId(jumpKeywordDto.getPersonalStageJumpId());
            if(ListUtils.isEmpty(jumpEmails)){
                return;
            }
            PersonalStageJump personalStageJump = personalStageJumpDao.getById(jumpKeywordDto.getPersonalStageJumpId());
            if(personalStageJump == null || personalStageJump.getOpenEmail() == null || !personalStageJump.getOpenEmail()){
                LOGGER.info("无需发送邮件");
                return;
            }
            GroupUserDTO wxUserInfo = wechatGroupConsr.getWxUserInfoByWxUserId(userWxId);
            if(wxUserInfo==null){
                LOGGER.info("未找到用户信息，无法发送阶段跳转邮件");
            }
            PcloudRobot pcloudRobot = pcloudRobotDao.getByWxId(robotWxId);
            PcloudRobotClassify robotClassify = pcloudRobotClassifyDao.getById(pcloudRobot.getRobotType().longValue());
            if (robotClassify == null|| robotClassify.getKeywordClassifyId() == null){
                LOGGER.info("小号分类id为空，无法发送阶段跳转邮件");
                return;
            }
            PersonalStage personalStage = personalStageBiz.getPersonalStage(jumpKeywordDto.getAfterPersonalStageId());
            if (personalStage == null || StringUtil.isEmpty(personalStage.getName())){
                LOGGER.info("未找到阶段信息，无法发送阶段跳转邮件");
                return;
            }
            for (PersonalStageJumpEmail email : jumpEmails){
                sendStageChangeEmail(email.getEmail(), content, wxUserInfo.getNickName(), userWxId, robotClassify.getClassifyName(), pcloudRobot.getUniqueNumber(), robotWxId, personalStage.getName());
            }
        } catch (Exception e){
            LOGGER.info("阶段跳转邮件发送失败：" + e.getMessage(), e);
        }
    }

    /**
     * 发送阶段流转邮件
     * @param toEmail 接收人邮件
     * @param customizedService 定制服务需求（即触发阶段流转的关键词）
     * @param nickname 读者昵称
     * @param wxUserId 读者ID
     * @param robotClassifyName 小号分类名称
     * @param robotNumber 小号编号
     * @param robotId 小号ID
     * @param afterStageName 流转后阶段名称
     */
    public void sendStageChangeEmail(String toEmail, String customizedService, String nickname, String wxUserId,
                                     String robotClassifyName, String robotNumber, String robotId, String afterStageName) {
        try {
            SendEmailDto sendEmailDto = new SendEmailDto();
            sendEmailDto.setToEmail(toEmail);
            sendEmailDto.setTypeCode("stage_change_notice");
            Map<String, Object> content = new HashMap<>();
            content.put("customizedService", customizedService);
            content.put("nickname", nickname);
            content.put("wxUserId", wxUserId);
            content.put("robotClassifyName", robotClassifyName);
            content.put("robotNumber", robotNumber);
            content.put("robotId", robotId);
            //跳转时间需要换行
            content.put("afterStageName", afterStageName+"<br><br>");
            content.put("time", DateNewUtils.getLongDateStr());
            sendEmailDto.setContent(content);
            messageBiz.sendEmail(sendEmailDto);
        } catch (Exception e) {
            LOGGER.error("发送阶段流转邮件出错：" + e.getMessage(), e);
        }
    }

    @ParamLog("发送内容衔接语")
    private void sendJumpLinkups(String userWxId, String robotWxId, String ip, Long jumpId, Long personalStageUserId, String bookGroupUrl,String adviserBookUrl) {
        List<PersonalStageJumpLinkup> linkups = personalStageJumpLinkupDao.getByJumpId(jumpId);
        if(ListUtils.isEmpty(linkups)){
            return;
        }
        this.replacePaperUrl(linkups, robotWxId, userWxId, jumpId, ip);
        String messageGroupId = UUID.randomUUID().toString();
        int count = linkups.size();
        for (PersonalStageJumpLinkup jumpLinkup : linkups){
            int i = linkups.indexOf(jumpLinkup);
            if (jumpLinkup.getToStageStartTime()!=null&&jumpLinkup.getToStageStartTime()>0){
                LinkupDelayDTO linkupDelayDTO=new LinkupDelayDTO();
                linkupDelayDTO.setPersonalStageJumpLinkupId(jumpLinkup.getId());
                linkupDelayDTO.setRobotId(robotWxId);
                linkupDelayDTO.setWxId(userWxId);
                linkupDelayDTO.setIp(ip);
                linkupDelayDTO.setPersonalStageUserId(personalStageUserId);
                linkupDelayDTO.setRobotProcessType(RobotProcessTypeEnum.PERSONAL_STAGE_JUMP);
                linkupDelayDTO.setMessageGroupId(messageGroupId);
                linkupDelayDTO.setIndex(i);
                linkupDelayDTO.setCounts(count);
                if (i==0&&ReplyTypeEnum.TEXT.value.equals( jumpLinkup.getReplyType())){
                    linkupDelayDTO.setBookGroupUrl(bookGroupUrl);
                    linkupDelayDTO.setAdviserBookUrl(adviserBookUrl);
                }
                DelayQueueDTO delayQueueDTO = DelayQueueDTO.builder().key(userWxId).type(PersonalStageConstant.PERSONALSTAGE_DELAY_LINKUP).msg(linkupDelayDTO).timeout(jumpLinkup.getToStageStartTime()*1000).build();
                delayMessageSender.send(delayQueueDTO);
                LOGGER.info("增加跳转衔接语延时"+delayQueueDTO.toString());
            }else {
                if(ReplyTypeEnum.TEXT.value.equals( jumpLinkup.getReplyType())){
                    SendTextMessageVO sendTextMessageVO = new SendTextMessageVO();
                    sendTextMessageVO.setContent(personalStageBiz.replaceProjectProgressUrl(jumpLinkup.getLinkupContent(),robotWxId,userWxId,personalStageUserId,ip));
                    if (i==0){
                        //加跳转衔接语社群书链接
                        if (!StringUtil.isEmpty(bookGroupUrl)){
                            sendTextMessageVO.setContent(sendTextMessageVO.getContent()+bookGroupUrl);
                        }
                        //加跳转衔接语编辑书链接
                        if (!StringUtil.isEmpty(adviserBookUrl)){
                            sendTextMessageVO.setContent(sendTextMessageVO.getContent()+adviserBookUrl);
                            if ("没有资源".equals(adviserBookUrl)){
                                sendTextMessageVO.setContent("非常抱歉，主人。您输入的图书，小睿没有找到相关资源呢，请将其他您感兴趣的图书书名发送给我吧~");
                            }
                        }
                    }
                    sendTextMessageVO.setAltId(robotWxId);
                    sendTextMessageVO.setWxGroupId(userWxId);
                    sendTextMessageVO.setIp(ip);
                    sendTextMessageVO.setCode(SendMessageTypeEnum.SELF.getCode());
                    sendTextMessageVO.setRobotProcessType(RobotProcessTypeEnum.PERSONAL_STAGE_JUMP);
                    sendTextMessageVO.setMessageGroupId(messageGroupId);
                    sendTextMessageVO.setIndex(i);
                    sendTextMessageVO.setCounts(count);
                    wechatGroupConsr.sendMessage(JSON.toJSONString(sendTextMessageVO));;
                } else if(ReplyTypeEnum.IMAGE.value.equals( jumpLinkup.getReplyType())) {
                    SendPicMessageVO sendPicMessageVO = new SendPicMessageVO();
                    sendPicMessageVO.setWxGroupId(userWxId);
                    sendPicMessageVO.setAltId(robotWxId);
                    sendPicMessageVO.setPicUrl(jumpLinkup.getPicUrl());
                    sendPicMessageVO.setIp(ip);
                    sendPicMessageVO.setCode(SendMessageTypeEnum.SELF.getCode());
                    sendPicMessageVO.setRobotProcessType(RobotProcessTypeEnum.PERSONAL_STAGE_JUMP);
                    sendPicMessageVO.setMessageGroupId(messageGroupId);
                    sendPicMessageVO.setIndex(i);
                    sendPicMessageVO.setCounts(count);
                    wechatGroupConsr.sendMessage(JSON.toJSONString(sendPicMessageVO));
                } else if (ReplyTypeEnum.AUDIO.value.equals( jumpLinkup.getReplyType())){
                    SendMomentsDTO sendMomentsDTO=new SendMomentsDTO();
                    Map<Long, ResourceDTO> map = resourceConsr.mapByIds(Arrays.asList(jumpLinkup.getResourceId()));
                    ResourceDTO resourceDTO = map.get(jumpLinkup.getResourceId());
                    if (resourceDTO != null) {
                        GroupRobotDTO info = wechatGroupConsr.getGroupRobotByRobotId(robotWxId);
                        if (info!=null){
                            sendMomentsDTO.setIsMasterWx(info.getMasterWx());
                            sendMomentsDTO.setReceiveAccount(info.getMac());
                        }
                        sendMomentsDTO.setMomentType(BusinessConstant.MomentTypeEnum.SEND_WX_RECORD);
                        sendMomentsDTO.setContent(userWxId);
                        sendMomentsDTO.setLink(resourceDTO.getFileUrl());
                        sendMomentsDTO.setIp(ip);
                        sendMomentsDTO.setAltId(robotWxId);
                        sendMomentsDTO.setWxId(userWxId);
                        sendMomentsDTO.setRobotProcessType(RobotProcessTypeEnum.PERSONAL_STAGE_JUMP);
                        sendMomentsDTO.setMessageGroupId(messageGroupId);
                        sendMomentsDTO.setIndex(i);
                        sendMomentsDTO.setCounts(count);
                        wechatGroupConsr.sendMessage(JSON.toJSONString(sendMomentsDTO));
                    }
                }
            }
        }
    }

    @ParamLog("更新到完成状态")
    private void updateStageToComplete(PersonalStageUser currentStageUser) {
        PersonalStageUser updateStageUser = new PersonalStageUser();
        updateStageUser.setId(currentStageUser.getId());
        updateStageUser.setState(PersonalStageUserStateEnum.FINISH.value);
        updateStageUser.setUpdateTime(new Date());
        personalStageUserDao.update(updateStageUser);
    }

    @ParamLog("替换需求定制单链接")
    private void replacePaperUrl(List<PersonalStageJumpLinkup> linkups, String robotId, String userWxId, Long jumpId, String ip){
        PersonalStageJumpDto stageJump = personalStageJumpDao.getDtoById(jumpId);
        if (null == stageJump){
            return;
        }
        PersonalStage personalStage = personalStageDao.getById(stageJump.getPersonalStageId());
        if (null == personalStage){
            return;
        }
        for (PersonalStageJumpLinkup linkup : linkups){
            if (ReplyTypeEnum.TEXT.value.equals(linkup.getReplyType())) {
                if (linkup.getLinkupContent().indexOf(PERSONAL_STAGE_PAPER_TEMPLATE) > -1 ||
                        linkup.getLinkupContent().indexOf(PERSONAL_STAGE_PAPER_TEMPLATE1) > -1) {//包含链接
                    if (null == personalStage.getPaperId()) {
                        linkup.setLinkupContent(linkup.getLinkupContent().replace(PERSONAL_STAGE_PAPER_TEMPLATE, ""));
                        linkup.setLinkupContent(linkup.getLinkupContent().replace(PERSONAL_STAGE_PAPER_TEMPLATE1, ""));
                        continue;
                    }
                    String longUrl = wechatLinkPrefix.concat(PERSONAL_STAGE_PAPER).concat(personalStage.getPaperId().toString()).concat("&wxId=" + userWxId + "&robotWxId=" + robotId);
                    //由于前端给的替换符中的“&”可能会是“&amp;”,所以两种替换符都要换掉
                    String shortLink = UrlUtils.getShortUrl4Own(longUrl);
                    linkup.setLinkupContent(linkup.getLinkupContent().replace(PERSONAL_STAGE_PAPER_TEMPLATE, shortLink));
                    linkup.setLinkupContent(linkup.getLinkupContent().replace(PERSONAL_STAGE_PAPER_TEMPLATE1, shortLink));
                    //添加需求单延迟发送队列
                    addPaperDelay(userWxId, robotId, ip, personalStage.getPaperId());
                }
            }
        }
    }

    @Override
    @ParamLog("添加进度单延时")
    public void addProgressDelay(String userWxId, String robotWxId, String ip, Integer progressId){
        PersonalStageProgressMessage progressMessage = personalStageProgressMessageDao.getWaitClickProgress(progressId);
        if (null==progressMessage || null == progressMessage.getMinutes()){
            return;
        }
        Integer time = progressMessage.getMinutes()*1000;
        PersonalStageUser currentStageUser = personalStageUserDao.getLast(userWxId, robotWxId, null);
        if (null==currentStageUser){
            return;
        }
        Long personalStageId = currentStageUser.getPersonalStageId();
        ProgressDelayDTO progressDelayDTO = new ProgressDelayDTO();
        progressDelayDTO.setIp(ip);
        progressDelayDTO.setRobotId(robotWxId);
        progressDelayDTO.setWxId(userWxId);
        progressDelayDTO.setPersonalStageId(personalStageId);
        progressDelayDTO.setProgressId(progressId);
        DelayQueueDTO delayQueueDTONew = DelayQueueDTO.builder().key(userWxId).type(PersonalStageConstant.PERSONALSTAGE_DELAY_PROGRESS).msg(progressDelayDTO).timeout(time).build();
        delayMessageSender.send(delayQueueDTONew);
        String key = "BOOK:LINK_PROGRESS:"+ userWxId + "-" +robotWxId;
        JedisClusterUtils.del(key);
    }

    @Override
    @ParamLog("添加进度单/需求单点击记录")
    public void addLinkClickRecord(LinkClickRecordDTO linkClickRecordDTO) {
        //链接类型（1进度单2需求单）
        Integer linkType = linkClickRecordDTO.getLinkType();
        String userWxId = linkClickRecordDTO.getWxId();
        String robotId = linkClickRecordDTO.getRobotId();
        if (linkType == 1){
            String key = "BOOK:LINK_PROGRESS:"+ userWxId + "-" +robotId +"-"+linkClickRecordDTO.getProgressId();
            JedisClusterUtils.setJson(key, userWxId, 5*3600);
        }else if (linkType == 2){
            String key = "BOOK:LINK_PAPER:"+ userWxId + "-" +robotId+"-"+linkClickRecordDTO.getPaperId();
            JedisClusterUtils.setJson(key, userWxId, 5*3600);
        }
    }

    @Override
    @ParamLog("进度单延迟处理")
    public void delayProgress(DelayQueueDTO dto) {
        ProgressDelayDTO progressDelayDTO=(ProgressDelayDTO) dto.getMsg();
        Integer progressId = progressDelayDTO.getProgressId();
        PersonalStageProgressMessage progressMessage = personalStageProgressMessageDao.getWaitClickProgress(progressId);
        if (null==progressMessage || StringUtil.isEmpty(progressMessage.getContent())){
            return;
        }
        String userWxId=dto.getKey();
        String robotId = progressDelayDTO.getRobotId();
        Long personalStageId = progressDelayDTO.getPersonalStageId();
        String ip = progressDelayDTO.getIp();
        //是否点击过进度单链接
        String key = "BOOK:LINK_PROGRESS:"+ userWxId + "-" +robotId + "-"+progressId;
        String value = JedisClusterUtils.getJson(key, String.class);
        if (!StringUtil.isEmpty(value)){
            log.info("用户已点击进度单链接");
            JedisClusterUtils.del(key);
            return;
        }
        // 查询用户是否在当前阶段
        PersonalStageUser currentStageUser = personalStageUserDao.getLast(userWxId, robotId, null);
        if (null != currentStageUser && !personalStageId.equals(currentStageUser.getPersonalStageId())){
            log.info("用户不在当前阶段");
            return;
        }
        SendTextMessageVO sendTextMessageVO = new SendTextMessageVO();
        sendTextMessageVO.setContent(progressMessage.getContent());
        sendTextMessageVO.setAltId(robotId);
        sendTextMessageVO.setWxGroupId(userWxId);
        sendTextMessageVO.setIp(ip);
        sendTextMessageVO.setCode(SendMessageTypeEnum.SELF.getCode());
        sendTextMessageVO.setRobotProcessType(RobotProcessTypeEnum.PERSONAL_STAGE_DELAY_PROGRESS);
        sendTextMessageVO.setMessageGroupId(UUID.randomUUID().toString());
        sendTextMessageVO.setIndex(0);
        sendTextMessageVO.setCounts(1);
        wechatGroupConsr.sendMessage(JSON.toJSONString(sendTextMessageVO));;
    }

    @Override
    @ParamLog("添加需求单延迟")
    public void addPaperDelay(String userWxId, String robotWxId, String ip, Long paperId) {
        PaperAndQuestionDto questionDto = feedbackConsr.getPaperByPaperId(paperId);
        if (null==questionDto||null==questionDto.getNotClickWaitTime()){
            return;
        }
        Integer time = questionDto.getNotClickWaitTime()*1000;
        PersonalStageUser currentStageUser = personalStageUserDao.getLast(userWxId, robotWxId, null);
        if (null==currentStageUser){
            return;
        }
        Long personalStageId = currentStageUser.getPersonalStageId();
        PaperDelayDTO paperDelayDTO = new PaperDelayDTO();
        paperDelayDTO.setIp(ip);
        paperDelayDTO.setRobotId(robotWxId);
        paperDelayDTO.setWxId(userWxId);
        paperDelayDTO.setPersonalStageId(personalStageId);
        paperDelayDTO.setPaperId(paperId);
        DelayQueueDTO delayQueueDTONew = DelayQueueDTO.builder().key(userWxId).type(PersonalStageConstant.PERSONALSTAGE_DELAY_PAPER).msg(paperDelayDTO).timeout(time).build();
        delayMessageSender.send(delayQueueDTONew);
        String key = "BOOK:LINK_PAPER:"+ userWxId + "-" +robotWxId+"-"+paperId;
        JedisClusterUtils.del(key);
    }

    @Override
    @ParamLog("需求单延迟处理")
    public void delayPaper(DelayQueueDTO dto) {
        String userWxId=dto.getKey();
        PaperDelayDTO paperDelayDTO=(PaperDelayDTO) dto.getMsg();
        String robotId = paperDelayDTO.getRobotId();
        Long personalStageId = paperDelayDTO.getPersonalStageId();
        String ip = paperDelayDTO.getIp();
        Long paperId = paperDelayDTO.getPaperId();
        PaperAndQuestionDto questionDto = feedbackConsr.getPaperByPaperId(paperId);
        if (null==questionDto || StringUtil.isEmpty(questionDto.getNotClickReplyText())){
            return;
        }
        //是否点击过需求单链接
        String key = "BOOK:LINK_PAPER:"+ userWxId + "-" +robotId+"-"+paperId;
        String value = JedisClusterUtils.getJson(key, String.class);
        //当前阶段是否发送
        String sendKey = "BOOK:LINK_PAPER_SEND:"+ userWxId + "-" +robotId+"-"+paperId +"-"+personalStageId;
        String send = JedisClusterUtils.getJson(sendKey,String.class);
        if (!StringUtil.isEmpty(value)){
            log.info("用户已点击需求单链接"+paperId);
//            JedisClusterUtils.del(key);
            return;
        }
        if (!StringUtil.isEmpty(send)){
            log.info("用户已发送需求单未点链接回复");
            return;
        }
        // 查询用户是否在当前阶段
        PersonalStageUser currentStageUser = personalStageUserDao.getLast(userWxId, robotId, null);
        if (null != currentStageUser && !personalStageId.equals(currentStageUser.getPersonalStageId())){
            log.info("用户不在当前阶段");
            return;
        }
        SendTextMessageVO sendTextMessageVO = new SendTextMessageVO();
        sendTextMessageVO.setContent(questionDto.getNotClickReplyText());
        sendTextMessageVO.setAltId(robotId);
        sendTextMessageVO.setWxGroupId(userWxId);
        sendTextMessageVO.setIp(ip);
        sendTextMessageVO.setCode(SendMessageTypeEnum.SELF.getCode());
        sendTextMessageVO.setRobotProcessType(RobotProcessTypeEnum.PERSONAL_STAGE_DELAY_PAPER);
        sendTextMessageVO.setMessageGroupId(UUID.randomUUID().toString());
        sendTextMessageVO.setIndex(0);
        sendTextMessageVO.setCounts(1);
        wechatGroupConsr.sendMessage(JSON.toJSONString(sendTextMessageVO));;
        JedisClusterUtils.setJson(sendKey,userWxId,30);
    }

    @Override
    public Map<String, Object> getUserInputPaperInfo(String robotWxId, String userWxId) {
        Map<String, Object> map = new HashMap<>();
        //书名
        String bookKey ="BOOK:USER_SEND_CONTENT:" + userWxId + "-" +robotWxId;
        String userSendContent = JedisClusterUtils.getJson(bookKey, String.class);
        //阅读偏好
        String readingKey ="BOOK:READING_STYLE:" + userWxId + "-" + robotWxId;
        String readingStyle = JedisClusterUtils.getJson(readingKey, String.class);
        if (!StringUtil.isEmpty(userSendContent)){
            map.put("书名",userSendContent);
        }
        if (!StringUtil.isEmpty(readingStyle)){
            map.put("阅读偏好",readingStyle);
        }
        return map;
    }

    @Override
    @ParamLog("删除用户的需求单记录")
    public void deletePaperRecord(String robotId, String userWxId){
        PcloudRobot pcloudRobot =  pcloudRobotDao.getByWxId(robotId);
        if (null!=pcloudRobot && null!=pcloudRobot.getRobotType()){
            List<Long> paperIds = personalStageDao.getPaperIdListByRobotClassify(pcloudRobot.getRobotType().longValue());
            if (!ListUtils.isEmpty(paperIds)){
               for (Long paperId:paperIds){
                   String paperKey = "FEEDBACK:USER_RECORD:"+userWxId+"-"+paperId;
                   JedisClusterUtils.del(paperKey);
               }
            }
        }
        //删除书名和阅读偏好缓存
        String bookKey ="BOOK:USER_SEND_CONTENT:" + userWxId + "-" +robotId;
        String readingKey ="BOOK:READING_STYLE:" + userWxId + "-" + robotId;
        JedisClusterUtils.del(bookKey);
        JedisClusterUtils.del(readingKey);
        //清除定制单填写记录
        List<Integer> planIds = customPlanBiz.getPlanIdListByUser(userWxId);
        if (!ListUtils.isEmpty(planIds)){
            for (Integer planId : planIds){
                String planKey = "BOOK:CUSTOMER_PLAN_SUGGESTION_RECORD:"+userWxId+"-"+planId;
                JedisClusterUtils.del(planKey);
            }
        }
    }

    @Override
    @ParamLog("无等级未收录图书，但book中有，处理阶段跳转")
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
    public Boolean handleBookExistJump(String userWxId, String robotWxId, String content, JumpTypeEnum jumpTypeEnum, String lastBookName) {
        // 查询用户的当前状态信息
        PersonalStageUser currentStageUser = personalStageUserDao.getLast(userWxId, robotWxId, null);
        // 如果用户没有阶段状态，则不处理
        if(currentStageUser == null || currentStageUser.getId() <= 0) {
            return false;
        }
        // 查询命中关键字跳转下一阶段的id
        PersonalStageJumpKeywordDto jumpKeywordDto = personalStageJumpKeywordDao.getByKeyword(currentStageUser.getPersonalStageId(), content, jumpTypeEnum.key);
        if(null == jumpKeywordDto || StringUtil.isEmpty(jumpKeywordDto.getKeyword())){
            // 未能命中关键词
            return false;
        }
        // 如果下一个阶段是空，则已完成
        if(null == jumpKeywordDto.getAfterPersonalStageId() || jumpKeywordDto.getAfterPersonalStageId() <=0){
            return true;
        }
        GroupRobotDTO groupRobotDTO = wechatGroupConsr.getGroupRobotByWxId(robotWxId);
        String ip = weixinQrcodeBiz.getRobotIpByGeneration(groupRobotDTO.getVersion());
        //处理需求单记录
        dealPaperRecord(currentStageUser.getPersonalStageId(),jumpKeywordDto.getAfterPersonalStageId(), userWxId, robotWxId);
        // 发送邮件
        sendEmail(userWxId, robotWxId, content, jumpKeywordDto);
        // 将用户置为下个阶段
        PersonalStageUser nextPersonalStageUser = personalStageBiz.nextStageAddStageUserAndWakeupDelay(robotWxId, userWxId, ip, jumpKeywordDto.getAfterPersonalStageId());
        // 发送内容衔接语
        Boolean revertSuccess = true;
        try {
            lastBookName = URLEncoder.encode(lastBookName, "utf-8");

        } catch (UnsupportedEncodingException e) {
            log.error("书名编码失败:{}; {}", lastBookName, e.getMessage());
            revertSuccess = false;
        }
        String h5link = wechatGroupLinkPrefix + "/dialog/search?wxId=" + userWxId + "&robotWxId=" + robotWxId;
        if (revertSuccess) {//编码成功，拼参数
            h5link = h5link + "&bookName=" + lastBookName;
        }
        String linkUrl = UrlUtils.getShortUrl4Own(h5link);
        String jumpLinkup="小睿已经找到本书了，不过非常遗憾呢，小睿还在努力学习这本书，主人可以尝试搜索其他感兴趣的书名哦。你可以点击网址："+linkUrl+"查看本书相关信息";
        SendTextMessageVO sendTextMessageVO = new SendTextMessageVO();
        sendTextMessageVO.setContent(jumpLinkup);
        sendTextMessageVO.setAltId(robotWxId);
        sendTextMessageVO.setWxGroupId(userWxId);
        sendTextMessageVO.setIp(ip);
        sendTextMessageVO.setCode(SendMessageTypeEnum.SELF.getCode());
        sendTextMessageVO.setRobotProcessType(RobotProcessTypeEnum.PERSONAL_STAGE_JUMP);
        sendTextMessageVO.setMessageGroupId(UUID.randomUUID().toString());
        sendTextMessageVO.setIndex(0);
        sendTextMessageVO.setCounts(1);
        wechatGroupConsr.sendMessage(JSON.toJSONString(sendTextMessageVO));
        // 停止发送引导语
        pcloudGuideBiz.stopPcloudGuidePush(robotWxId, userWxId);
        //存用户跳转之前的最后一次非关键词回复到表
        personalStageBiz.addUserReplaceCodeBeforeJump(currentStageUser.getPersonalStageId(),userWxId,robotWxId);
        return true;
    }

}
