Explorar el Código

今正的-新客对话

三七 hace 3 semanas
padre
commit
59ea1263c9

+ 9 - 1
fs-company/src/main/java/com/fs/company/controller/company/CompanyVoiceRoboticCallLogAddwxController.java

@@ -7,6 +7,7 @@ import java.util.Map;
 import java.util.Set;
 import java.util.stream.Collectors;
 
+import com.fs.common.utils.ServletUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.company.domain.CompanyVoiceRoboticCallLogCallphone;
 import com.fs.company.domain.CompanyWxClient;
@@ -14,6 +15,8 @@ import com.fs.company.vo.CompanyVoiceRoboticCallLogAddWxExportVO;
 import com.fs.company.vo.CompanyVoiceRoboticCallLogAddwxVO;
 import com.fs.company.vo.CompanyVoiceRoboticCallLogCallPhoneVO;
 import com.fs.company.vo.CompanyVoiceRoboticCallLogCount;
+import com.fs.framework.security.LoginUser;
+import com.fs.framework.service.TokenService;
 import org.springframework.beans.BeanUtils;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -36,7 +39,7 @@ import com.fs.common.core.page.TableDataInfo;
 
 /**
  * 调用日志_加微信Controller
- * 
+ *
  * @author fs
  * @date 2026-01-15
  */
@@ -47,6 +50,9 @@ public class CompanyVoiceRoboticCallLogAddwxController extends BaseController
     @Autowired
     private ICompanyVoiceRoboticCallLogAddwxService companyVoiceRoboticCallLogAddwxService;
 
+    @Autowired
+    private TokenService tokenService;
+
     /**
      * 查询调用日志_加微信列表
      */
@@ -88,6 +94,8 @@ public class CompanyVoiceRoboticCallLogAddwxController extends BaseController
     public TableDataInfo listAll(CompanyVoiceRoboticCallLogAddwx companyVoiceRoboticCallLogAddwx)
     {
         startPage();
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        companyVoiceRoboticCallLogAddwx.setCompanyId(loginUser.getCompany().getCompanyId());
         List<CompanyVoiceRoboticCallLogAddwxVO> list = companyVoiceRoboticCallLogAddwxService.listAll(companyVoiceRoboticCallLogAddwx);
         TableDataInfo dataTable = getDataTable(list);
         Map<String, Long> countMap = companyVoiceRoboticCallLogAddwxService.countListAll(companyVoiceRoboticCallLogAddwx);

+ 34 - 0
fs-ipad-task/src/main/java/com/fs/app/service/IpadSendServer.java

@@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.exception.base.BaseException;
+import com.fs.common.utils.CloudHostUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.date.DateUtil;
 import com.fs.common.utils.spring.SpringUtils;
@@ -470,6 +471,39 @@ public class IpadSendServer {
                 }
             }
         }
+
+        //新客对话
+        if (qwSopLogs.getSendType() == 4 && CloudHostUtils.hasCloudHostName("今正科技")) {
+            Long fsUserId = qwExternalContactMapper.selectQwExternalContactTimeById(qwSopLogs.getExternalId());
+
+            List<QwSopCourseFinishTempSetting.Setting> settingList = setting.getSetting();
+
+            // 检查是否存在符合条件的内容
+            boolean hasValidContent;
+
+            if (fsUserId == null) {
+                // 用户未注册:需要存在标记为 2(未注册) 或 3(通用) 的内容
+                hasValidContent = settingList.stream()
+                        .anyMatch(item -> "2".equals(item.getOnlyUnregistered()) || "3".equals(item.getOnlyUnregistered()));
+
+                if (!hasValidContent) {
+                    log.warn("SOP_LOG_ID:{}, 新客对话-用户未注册不发送已注册内容", qwSopLogs.getId());
+                    qwSopLogsService.updateQwSopLogsByWatchLogType(qwSopLogs.getId(), "新客对话-用户未注册不发送已注册内容");
+                    return false;
+                }
+            } else {
+                // 用户已注册:需要存在标记为 1(已注册) 或 3(通用) 的内容
+                hasValidContent = settingList.stream()
+                        .anyMatch(item -> "1".equals(item.getOnlyUnregistered()) || "3".equals(item.getOnlyUnregistered()));
+
+                if (!hasValidContent) {
+                    log.warn("SOP_LOG_ID:{}, 新客对话-用户已注册且不发送已注册内容", qwSopLogs.getId());
+                    qwSopLogsService.updateQwSopLogsByWatchLogType(qwSopLogs.getId(), "新客对话-用户已注册且不发送已注册内容");
+                    return false;
+                }
+            }
+        }
+
         return true;
     }
 

+ 28 - 4
fs-qw-task/src/main/java/com/fs/app/controller/CommonController.java

@@ -35,6 +35,7 @@ import com.fs.qwApi.service.QwApiService;
 import com.fs.sop.mapper.QwSopLogsMapper;
 import com.fs.sop.mapper.QwSopMapper;
 import com.fs.sop.mapper.SopUserLogsMapper;
+import com.fs.sop.params.QwSopAutoByTags;
 import com.fs.sop.service.*;
 import com.fs.sop.vo.QwSopLogsDoSendListTVO;
 import com.fs.store.service.IFsUserCourseCountService;
@@ -52,10 +53,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 import com.fs.app.task.qwTask;
 
-import java.time.Duration;
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.time.ZoneId;
+import java.time.*;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
 
@@ -165,7 +163,33 @@ public class CommonController {
     @Autowired
     private SopWxLogsTaskService sopWxLogsTaskService;
 
+    @Autowired
+    private AsyncQwAiChatSopService asyncQwAiChatSopService;
+
+    @GetMapping("/newRoomLinkAllow")
+    public void newRoomLinkAllow() {
+
+
+        Set<String> combinedTagsSet = new HashSet<>();
+        combinedTagsSet.add("et_-lxDwAAlzHGQGKcgw0fNRGnGN-blw");
+        List<String> combinedTagsList = new ArrayList<>(combinedTagsSet);
+
+        //分时段里符合的标签-再用来创建自动SOP
+        QwSopAutoByTags qwSopAutoByTags = new QwSopAutoByTags();
+        qwSopAutoByTags.setQwUserId("32189");
+        qwSopAutoByTags.setCorpId("wwce259dbd92b6f0e7");
+        qwSopAutoByTags.setTagsIdsSelectList(combinedTagsList);
+        qwSopAutoByTags.setSendType(2);
 
+        QwUser qwUser = qwUserMapper.selectQwUserById(32189L);
+        LocalDate currentDate = LocalDate.now();
+//        LocalDate currentDate = LocalDate.now().plusDays(3);
+        LocalTime localTime = LocalTime.now();
+
+        asyncQwAiChatSopService.executeQwAiChatSop(qwSopAutoByTags, "ShiGuoWei", qwUser, "wm6JoFEQAAtVLeG1vCBPYu_S6wRaAb_g"
+                , "西红柿", 16292365L, null, currentDate, localTime);
+
+    }
 
     @GetMapping("/roomLinkAllow")
     public R roomLinkAllow() {

+ 62 - 1
fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java

@@ -7,6 +7,7 @@ import com.alibaba.fastjson.JSONArray;
 import com.fs.common.annotation.Excel;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
+import com.fs.common.utils.CloudHostUtils;
 import com.fs.company.domain.CompanyConfig;
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.mapper.CompanyConfigMapper;
@@ -51,6 +52,7 @@ import com.fs.qw.domain.*;
 import com.fs.qw.mapper.*;
 import com.fs.qw.param.QwAutoTagsRulesTags;
 import com.fs.qw.service.*;
+import com.fs.qw.vo.QwSopCourseFinishTempSetting;
 import com.fs.qwApi.domain.QwResult;
 import com.fs.qwApi.param.QwEditUserTagParam;
 import com.fs.qwApi.param.QwSendMsgParam;
@@ -489,7 +491,12 @@ public class AiHookServiceImpl implements AiHookService {
                 if(!contentEmj.isEmpty()){
                     addSaveAiMsg(1,1,contentEmj,user,fastGptChatSession.getSessionId(),role.getRoleId(),qwExternalContacts,fastGptChatSession.getUserId(),null,null,null);
                     //通过用户发送的对话去查询用户是否为新客,是就删除sop,否就不做处理
-                    cleanNewUserDialogue(user, qwExternalContacts);
+                    if (CloudHostUtils.hasCloudHostName("今正科技")){
+                        cleanNewUserDialogueSXJZ(user, qwExternalContacts);
+                    }else {
+                        cleanNewUserDialogue(user, qwExternalContacts);
+                    }
+
                     //用户是未回复状态
                     if(qwExternalContacts.getIsReply() == 0){
                         qwExternalContactMapper.updateQwExternalContactIsRePlyById(qwExternalContacts.getId());
@@ -827,6 +834,60 @@ public class AiHookServiceImpl implements AiHookService {
         }
     }
 
+    private void cleanNewUserDialogueSXJZ(QwUser user, QwExternalContact qwExternalContacts) {
+        String redisKey = "qwNewChat:" + user.getQwUserId() + ":" + user.getCorpId() + ":" + qwExternalContacts.getExternalUserId();
+        String key  = (String) redisCache.getCacheObject(redisKey);
+        if(!StringUtil.strIsNullOrEmpty(key)){
+            try {
+                QwSopLogs qwSopLogs = new QwSopLogs();
+                qwSopLogs.setQwUserid(user.getQwUserId());
+                qwSopLogs.setCorpId(user.getCorpId());
+                qwSopLogs.setExternalUserId(qwExternalContacts.getExternalUserId());
+                qwSopLogs.setSendStatus(3L);
+                qwSopLogs.setSendType(4);
+                List<QwSopLogs> qwSopLogsList = qwSopLogsMapper.selectQwSopLogsList(qwSopLogs);
+
+                if(qwSopLogsList != null && !qwSopLogsList.isEmpty()){
+                    List<QwSopLogs> logsToUpdate = new ArrayList<>();
+
+                    for (QwSopLogs sopLogs : qwSopLogsList) {
+                        if (sopLogs.getContentJson() != null) {
+                            try {
+                                QwSopCourseFinishTempSetting setting = JSON.parseObject(sopLogs.getContentJson(), QwSopCourseFinishTempSetting.class);
+
+                                if (setting != null && setting.getSetting() != null) {
+                                    List<QwSopCourseFinishTempSetting.Setting> settingList = setting.getSetting();
+
+                                    // 检查是否存在 onlyUnregistered == 3 的内容
+                                    boolean hasUniversalContent = settingList.stream()
+                                            .anyMatch(item -> "3".equals(item.getOnlyUnregistered()));
+
+                                    if (hasUniversalContent) {
+                                        logsToUpdate.add(sopLogs);
+                                    }
+                                }
+                            } catch (Exception e) {
+                                sopLogs.setRemark("内容解析失败作废");
+                                logsToUpdate.add(sopLogs);
+                                log.error("解析SOP日志contentJson失败,logId:{}", sopLogs.getId(), e);
+                            }
+                        }
+                    }
+
+                    // 批量作废包含通用内容(onlyUnregistered=3)的SOP日志
+                    if (!logsToUpdate.isEmpty()) {
+                        qwSopLogsMapper.batchUpdateQwSopLogsNewUserById(logsToUpdate);
+                        log.info("批量作废包含通用内容的新客对话SOP,数量:{}", logsToUpdate.size());
+                    }
+                }
+            } catch (Exception e) {
+                log.error("停用新客对话sop失败:" + redisKey + "原因:" + e);
+            }finally {
+                redisCache.deleteObject(redisKey);
+            }
+        }
+    }
+
 
 
     /**

+ 2 - 2
fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java

@@ -478,8 +478,8 @@ public interface QwExternalContactMapper extends BaseMapper<QwExternalContact> {
     @Select("select id,external_user_id,tag_ids from qw_external_contact where  qw_user_id=#{id} ")
     List<QwExternalContact> selectExternalUserIdsByQwUserId(Long id);
 
-    @Select("select id,first_time,qw_user_id,create_time from qw_external_contact where  id=#{id} ")
-    QwExternalContact selectQwExternalContactTimeById(Long id);
+    @Select("select fs_user_id from qw_external_contact where  id=#{id} ")
+    Long selectQwExternalContactTimeById(@Param("id") Long id);
 
     @Select("select id,user_id,corp_id,external_user_id,name,remark,status from qw_external_contact where  id=#{id} ")
     QwExternalContact selectQwExternalContactByRemark(Long id);

+ 47 - 5
fs-service/src/main/java/com/fs/qw/service/AsyncQwAiChatSopService.java

@@ -43,6 +43,7 @@ import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.LocalTime;
 import java.time.ZoneId;
+import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Date;
@@ -98,7 +99,6 @@ public class AsyncQwAiChatSopService {
                                    QwUser qwUser, String externalUserID, String externalContactName,
                                    Long externalId, Long fsUserId, LocalDate currentDate, LocalTime localTime) {
 
-
         QwExternalContact contact;
         if (externalId != null) {
             contact = qwExternalContactMapper.selectById(externalId);
@@ -130,9 +130,33 @@ public class AsyncQwAiChatSopService {
 
             qwSopAiRuleTimeVOS.forEach(item -> {
 
+                LocalDateTime deadLineTime = item.getDeadLineTime();
+
+                // 计算剩余天数
+                Long daysRemaining;
+                if (deadLineTime != null) {
+                    LocalDate deadLineDate = deadLineTime.toLocalDate();
+                    daysRemaining = ChronoUnit.DAYS.between(currentDate, deadLineDate) + 1; // 同一天为1天
+
+                    if (daysRemaining < 1) {
+                        log.warn("截止时间已过期,跳过SOP任务: sopId={}", item.getId());
+                        return;
+                    }
+                } else {
+                    daysRemaining = null;
+                }
+
                 List<QwSopTempContent> tempContentList = qwSopTempContentMapper.selectQwSopTempContentByTempIdAndRules(item.getTempId());
 
                 tempContentList.forEach(content -> {
+
+                    // 解析dayNum,如果有截止时间且dayNum大于剩余天数则跳过
+                    Integer dayNum = content.getDayNum();
+                    if (daysRemaining != null && dayNum != null && dayNum > daysRemaining) {
+                        log.debug("dayNum={} 大于剩余天数={},跳过此内容", dayNum, daysRemaining);
+                        return;
+                    }
+
                     QwSopLogs sopLogs = new QwSopLogs();
                     sopLogs.setQwUserKey(qwUser.getId());
                     sopLogs.setQwUserid(userID);
@@ -152,12 +176,30 @@ public class AsyncQwAiChatSopService {
                     List<QwSopTempSetting.Content.Setting> settingList = new ArrayList<>();
                     QwSopTempSetting.Content.Setting setting = JSON.parseObject(content.getContent(), QwSopTempSetting.Content.Setting.class);
 
-                    LocalDateTime dateTime = LocalDateTime.of(currentDate, localTime);
-                    LocalDateTime expiryDateTime = dateTime.plusMinutes(setting.getIntervalTime());
-                    String sendTime = DateUtil.formatLocalDateTime(expiryDateTime);
+
+                    LocalDateTime dateTime;
+                    // 如果dayNum > 1,需要根据setting中的time计算新的发送时间
+                    if (dayNum != null && dayNum > 1 && setting.getTime() != null && !setting.getTime().isEmpty()) {
+                        // 解析time字段,格式为"09:00"
+                        String[] timeParts = setting.getTime().split(":");
+                        int hour = Integer.parseInt(timeParts[0]);
+                        int minute = Integer.parseInt(timeParts[1]);
+
+                        // 计算目标日期:当前日期 + (dayNum - 1)天
+                        LocalDate targetDate = currentDate.plusDays(dayNum - 1);
+                        dateTime = LocalDateTime.of(targetDate, LocalTime.of(hour, minute));
+                    } else {
+                        // dayNum为1或没有时间配置,使用原有逻辑
+                        dateTime = LocalDateTime.of(currentDate, localTime);
+                        LocalDateTime expiryDateTime = dateTime.plusMinutes(setting.getIntervalTime());
+                        dateTime = expiryDateTime;
+                    }
+
+                    String sendTime = DateUtil.formatLocalDateTime(dateTime);
                     sopLogs.setSendTime(sendTime);
 
-                    Date expirySendTime = Date.from(expiryDateTime.atZone(ZoneId.of("Asia/Shanghai")).toInstant());
+                    Date expirySendTime = Date.from(dateTime.atZone(ZoneId.of("Asia/Shanghai")).toInstant());
+
                     //过滤违禁词
                     if ("1".equals(setting.getContentType())) {
                         sopUserLogsInfoService.replaceContent(setting.getContentType(), setting.getValue(), setting::setValue, words); // 替换 value

+ 371 - 0
fs-service/src/main/java/com/fs/qw/service/AsyncQwAiChatSopServiceCopy.java

@@ -0,0 +1,371 @@
+//package com.fs.qw.service;
+//
+//import com.alibaba.fastjson.JSON;
+//import com.fs.common.utils.CloudHostUtils;
+//import com.fs.common.utils.StringUtils;
+//import com.fs.common.utils.date.DateUtil;
+//import com.fs.company.service.ICompanyMiniappService;
+//import com.fs.course.config.CourseConfig;
+//import com.fs.course.domain.FsCourseLink;
+//import com.fs.course.domain.FsCourseRealLink;
+//import com.fs.course.domain.FsCourseWatchLog;
+//import com.fs.course.mapper.FsCourseLinkMapper;
+//import com.fs.course.mapper.FsCourseWatchLogMapper;
+//import com.fs.fastGpt.domain.FastGptChatReplaceWords;
+//import com.fs.fastGpt.mapper.FastGptChatReplaceWordsMapper;
+//import com.fs.qw.domain.QwCompany;
+//import com.fs.qw.domain.QwExternalContact;
+//import com.fs.qw.domain.QwExternalContactInfo;
+//import com.fs.qw.domain.QwUser;
+//import com.fs.qw.mapper.QwExternalContactInfoMapper;
+//import com.fs.qw.mapper.QwExternalContactMapper;
+//import com.fs.qw.vo.QwSopRuleTimeVO;
+//import com.fs.qw.vo.QwSopTempSetting;
+//import com.fs.sop.domain.QwSopLogs;
+//import com.fs.sop.domain.QwSopTempContent;
+//import com.fs.sop.domain.QwSopTempVoice;
+//import com.fs.sop.mapper.QwSopLogsMapper;
+//import com.fs.sop.mapper.QwSopMapper;
+//import com.fs.sop.mapper.QwSopTempContentMapper;
+//import com.fs.sop.params.QwSopAutoByTags;
+//import com.fs.sop.service.IQwSopTempVoiceService;
+//import com.fs.sop.service.impl.SopUserLogsInfoServiceImpl;
+//import com.fs.system.service.ISysConfigService;
+//import com.fs.voice.utils.StringUtil;
+//import lombok.AllArgsConstructor;
+//import lombok.extern.slf4j.Slf4j;
+//import org.joda.time.DateTime;
+//import org.springframework.beans.BeanUtils;
+//import org.springframework.beans.factory.annotation.Autowired;
+//import org.springframework.scheduling.annotation.Async;
+//import org.springframework.stereotype.Service;
+//
+//import java.time.LocalDate;
+//import java.time.LocalDateTime;
+//import java.time.LocalTime;
+//import java.time.ZoneId;
+//import java.time.temporal.ChronoUnit;
+//import java.util.ArrayList;
+//import java.util.Collections;
+//import java.util.Date;
+//import java.util.List;
+//
+//@Slf4j
+//@Service
+//@AllArgsConstructor
+//public class AsyncQwAiChatSopServiceCopy {
+//
+//    private static final String miniappRealLink = "/pages_course/video.html?course=";
+//
+//    @Autowired
+//    private QwSopMapper qwSopMapper;
+//
+//    @Autowired
+//    private QwSopTempContentMapper qwSopTempContentMapper;
+//
+//    @Autowired
+//    private ISysConfigService configService;
+//
+//    @Autowired
+//    private SopUserLogsInfoServiceImpl sopUserLogsInfoService;
+//    @Autowired
+//    private FastGptChatReplaceWordsMapper fastGptChatReplaceWordsMapper;
+//
+//    @Autowired
+//    private FsCourseWatchLogMapper fsCourseWatchLogMapper;
+//    @Autowired
+//    private QwExternalContactMapper qwExternalContactMapper;
+//
+//    @Autowired
+//    private QwSopLogsMapper qwSopLogsMapper;
+//
+//    @Autowired
+//    private FsCourseLinkMapper fsCourseLinkMapper;
+//
+//    @Autowired
+//    private IQwCompanyService iQwCompanyService;
+//
+//    @Autowired
+//    private ICompanyMiniappService companyMiniappService;
+//
+//
+//    @Autowired
+//    private IQwSopTempVoiceService sopTempVoiceService;
+//
+//    @Autowired
+//    private QwExternalContactInfoMapper qwExternalContactInfoMapper;
+//
+////    @Async("threadPoolTaskExecutor")
+//    public void executeQwAiChatSop(QwSopAutoByTags qwSopAutoByTags, String userID,
+//                                   QwUser qwUser, String externalUserID, String externalContactName,
+//                                   Long externalId, Long fsUserId, LocalDate currentDate, LocalTime localTime) {
+//
+//        QwExternalContact contact;
+//        if (externalId != null) {
+//            contact = qwExternalContactMapper.selectById(externalId);
+//        } else {
+//            contact = null;
+//        }
+//        //新客对话任务
+//        List<QwSopRuleTimeVO> qwSopAiRuleTimeVOS = qwSopMapper.selectQwAiSopAutoByTagsByForeach(qwSopAutoByTags);
+//        List<FastGptChatReplaceWords> words = fastGptChatReplaceWordsMapper.selectAllFastGptChatReplaceWords();
+//        List<QwSopLogs> sopLogsList = new ArrayList<>(Collections.emptyList());
+//
+//
+//        String json = configService.selectConfigByKey("course.config");
+//        CourseConfig config = JSON.parseObject(json, CourseConfig.class);
+//
+//        if (config == null) {
+//            log.error("配置为空-新客对话创建失败");
+//            return;
+//        }
+//
+//        QwCompany qwCompany = iQwCompanyService.getQwCompanyByRedis(qwUser.getCorpId());
+//
+//        if (qwCompany == null) {
+//            log.error("企业微信主体未配置默认小程序-新客对话创建失败");
+//            return;
+//        }
+//
+//        if (qwSopAiRuleTimeVOS != null && !qwSopAiRuleTimeVOS.isEmpty()) {
+//
+//            qwSopAiRuleTimeVOS.forEach(item -> {
+//
+//
+//                List<QwSopTempContent> tempContentList = qwSopTempContentMapper.selectQwSopTempContentByTempIdAndRules(item.getTempId());
+//
+//                tempContentList.forEach(content -> {
+//
+//                    QwSopLogs sopLogs = new QwSopLogs();
+//                    sopLogs.setQwUserKey(qwUser.getId());
+//                    sopLogs.setQwUserid(userID);
+//                    sopLogs.setExternalUserId(externalUserID);
+//                    sopLogs.setExternalId(externalId);
+//                    sopLogs.setLogType(2);
+//                    sopLogs.setSendStatus(3L);
+//                    sopLogs.setCompanyId(qwUser.getCompanyId());
+//                    sopLogs.setReceivingStatus(0L);
+//                    sopLogs.setSopId(item.getId());
+//                    sopLogs.setCorpId(qwUser.getCorpId());
+//                    sopLogs.setFsUserId(fsUserId);
+//                    sopLogs.setSort(99999999);
+//                    sopLogs.setSendType(4);
+//                    sopLogs.setExternalUserName(externalContactName);
+//
+//                    List<QwSopTempSetting.Content.Setting> settingList = new ArrayList<>();
+//                    QwSopTempSetting.Content.Setting setting = JSON.parseObject(content.getContent(), QwSopTempSetting.Content.Setting.class);
+//
+//                    LocalDateTime dateTime = LocalDateTime.of(currentDate, localTime);
+//                    LocalDateTime expiryDateTime = dateTime.plusMinutes(setting.getIntervalTime());
+//                    String sendTime = DateUtil.formatLocalDateTime(expiryDateTime);
+//                    sopLogs.setSendTime(sendTime);
+//
+//                    Date expirySendTime = Date.from(expiryDateTime.atZone(ZoneId.of("Asia/Shanghai")).toInstant());
+//                    //过滤违禁词
+//                    if ("1".equals(setting.getContentType())) {
+//                        sopUserLogsInfoService.replaceContent(setting.getContentType(), setting.getValue(), setting::setValue, words); // 替换 value
+//                    }
+//                    //过滤违禁词
+//                    if ("3".equals(setting.getContentType())) {
+//                        sopUserLogsInfoService.replaceContent(setting.getContentType(), setting.getLinkTitle(), setting::setLinkTitle, words); // 替换 linkTitle
+//                        sopUserLogsInfoService.replaceContent(setting.getContentType(), setting.getLinkDescribe(), setting::setLinkDescribe, words); // 替换 linkTitle
+//                    }
+//                    switch (setting.getContentType()) {
+//                        //文字和短链一起
+//                        case "1":
+//                        case "3":
+//
+//                            if ("1".equals(setting.getContentType())) {
+//                                String defaultName = "同学";
+//                                if (contact != null && StringUtils.isNotEmpty(contact.getName()) && !"待同步客户".equals(contact.getName())) {
+//                                    defaultName = contact.getName();
+//                                }
+//                                setting.setValue(setting.getValue()
+//                                        .replaceAll("#销售称呼#", StringUtil.strIsNullOrEmpty(qwUser.getWelcomeText()) ? "" : qwUser.getWelcomeText())
+//                                        .replaceAll("#客户称呼#", contact == null || StringUtil.strIsNullOrEmpty(contact.getStageStatus()) || "0".equals(contact.getStageStatus()) ? defaultName : contact.getStageStatus()));
+//                            }
+//
+//
+//                            break;
+//                        //小程序单独
+//                        case "4":
+//                            addWatchLogIfNeededByNewChat(item.getId(), content.getVideoId(), content.getCourseId(), fsUserId, qwUser.getId(), qwUser.getCompanyUserId(), qwUser.getCompanyId(),
+//                                    externalId, sendTime, expirySendTime, 2);
+//
+//                            String linkByMiniApp = createLinkByMiniAppByNewChat(setting.getExpiresDays(), qwUser.getCorpId(), expirySendTime, content.getCourseId(), content.getVideoId(),
+//                                    qwUser.getId(), String.valueOf(qwUser.getCompanyUserId()), String.valueOf(qwUser.getCompanyId()), externalId, config);
+//
+//
+//                            setting.setMiniprogramAppid(qwCompany.getMiniAppId());
+//
+//                            String miniprogramTitle = setting.getMiniprogramTitle();
+//                            int maxLength = 17;
+//                            setting.setMiniprogramTitle(miniprogramTitle.length() > maxLength ? miniprogramTitle.substring(0, maxLength) + "..." : miniprogramTitle);
+//                            setting.setMiniprogramPage(linkByMiniApp);
+//                            break;
+//                        case "7":
+//
+//                            createVoiceUrlByNewChat(setting, qwUser.getCompanyUserId());
+//                            break;
+//                        default:
+//                            break;
+//
+//                    }
+//
+//                    settingList.add(setting);
+//
+//                    QwSopTempSetting.Content clonedContent = new QwSopTempSetting.Content();
+//                    clonedContent.setContentType(setting.getContentType());
+//                    clonedContent.setCourseId(Long.valueOf(content.getCourseId()));
+//                    clonedContent.setVideoId(Long.valueOf(content.getVideoId()));
+//                    clonedContent.setSetting(settingList);
+//                    clonedContent.setType(content.getType());
+//                    clonedContent.setCourseType(0);
+//                    sopLogs.setContentJson(JSON.toJSONString(clonedContent));
+//                    sopLogsList.add(sopLogs);
+//                });
+//
+//
+//            });
+//
+//            //批量插入 发送记录
+//            if (!sopLogsList.isEmpty()) {
+//                processAndInsertQwSopLogsBySendMsg(sopLogsList);
+//            }
+//        }
+//
+//    }
+//
+//    private void processAndInsertQwSopLogsBySendMsg(List<QwSopLogs> sopLogsList) {
+//        // 定义批量插入的大小
+//        int batchSize = 500;
+//
+//        // 循环处理外部用户 ID,每次处理批量大小的子集
+//        for (int i = 0; i < sopLogsList.size(); i += batchSize) {
+//
+//            int endIndex = Math.min(i + batchSize, sopLogsList.size());
+//            List<QwSopLogs> batchList = sopLogsList.subList(i, endIndex);  // 获取当前批次的子集
+//
+//            // 直接使用批次数据进行批量更新,不需要额外的 List
+//            try {
+//                qwSopLogsMapper.batchInsertQwSopLogsOneTouch(batchList);
+//            } catch (Exception e) {
+//                // 记录异常日志,方便后续排查问题
+//                log.error("批量执行一键群发时发生异常,处理的批次起始索引为: " + i, e);
+//            }
+//        }
+//    }
+//
+//    //插入观看记录
+//    private Long addWatchLogIfNeededByNewChat(String sopId, Integer videoId, Integer courseId,
+//                                              Long fsUserId, Long qwUserId, Long companyUserId,
+//                                              Long companyId, Long externalId, String startTime, Date createTime, Integer watchType) {
+//
+//        try {
+//            FsCourseWatchLog watchLog = new FsCourseWatchLog();
+//            watchLog.setVideoId(Long.valueOf(videoId));
+//            watchLog.setQwExternalContactId(externalId);
+//            watchLog.setSendType(2);
+//            watchLog.setQwUserId(qwUserId);
+//            watchLog.setSopId(sopId);
+//            watchLog.setDuration(0L);
+//            watchLog.setCourseId(Long.valueOf(courseId));
+//            watchLog.setCompanyUserId(companyUserId);
+//            watchLog.setCompanyId(companyId);
+//            watchLog.setCreateTime(createTime);
+//            watchLog.setUpdateTime(createTime);
+//            watchLog.setLogType(3);
+//            watchLog.setUserId(fsUserId);
+//            if (!CloudHostUtils.hasCloudHostName("木易华康")) {
+//                watchLog.setWatchType(watchType);
+//            }
+//            watchLog.setCampPeriodTime(sopUserLogsInfoService.convertStringToDate(startTime, "yyyy-MM-dd HH:mm:ss"));
+//
+//            //存看课记录
+//            fsCourseWatchLogMapper.insertOrUpdateFsCourseWatchLog(watchLog);
+//            return watchLog.getLogId();
+//        } catch (Exception e) {
+//            log.error("一键群发失败-插入观看记录失败:" + e.getMessage());
+//            return null;
+//        }
+//    }
+//
+//    private String createLinkByMiniAppByNewChat(Integer expiresDays, String corpId, Date sendTime,
+//                                                Integer courseId, Integer videoId, Long qwUserId,
+//                                                String companyUserId, String companyId, Long externalId, CourseConfig config) {
+//
+//        try {
+//            FsCourseLink link = sopUserLogsInfoService.createFsCourseLink(corpId, sendTime, courseId, videoId, qwUserId,
+//                    companyUserId, companyId, externalId, 3, null);
+//
+//            FsCourseRealLink courseMap = new FsCourseRealLink();
+//            BeanUtils.copyProperties(link, courseMap);
+//
+//            String courseJson = JSON.toJSONString(courseMap);
+//            String realLinkFull = miniappRealLink + courseJson;
+//            link.setRealLink(realLinkFull);
+//
+//            Date updateTime = createUpdateTimeByNewChat(expiresDays, sendTime, config);
+//
+//            link.setUpdateTime(updateTime);
+//            //存短链-
+//            fsCourseLinkMapper.insertFsCourseLink(link);
+//            return link.getRealLink();
+//        } catch (Exception e) {
+//            log.error("创建新客对话短链失败:{}|{}|{}|{}|{}", corpId, sendTime, courseId, videoId, qwUserId);
+//            log.error("e", e);
+//        }
+//        return null;
+//    }
+//
+//    public Date createUpdateTimeByNewChat(Integer expiresDays, Date sendTime, CourseConfig config) {
+//
+//
+//        Integer expDays = (expiresDays == null || expiresDays == 0)
+//                ? config.getVideoLinkExpireDate()
+//                : expiresDays;
+//
+////         使用 Java 8 时间 API 计算过期时间
+//        LocalDateTime sendDateTime = sendTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
+//        LocalDateTime expireDateTime = sendDateTime.plusDays(expDays - 1);
+//        expireDateTime = expireDateTime.toLocalDate().atTime(23, 59, 59);
+//        Date updateTime = Date.from(expireDateTime.atZone(ZoneId.systemDefault()).toInstant());
+//
+//        return updateTime;
+//    }
+//
+//    private void createVoiceUrlByNewChat(QwSopTempSetting.Content.Setting setting, Long companyUserId) {
+//        QwSopTempVoice qwSopTempVoice = sopTempVoiceService.selectQwSopTempVoiceByCompanyUserIdAndVoiceTxt(companyUserId, setting.getValue());
+//        if (qwSopTempVoice != null && qwSopTempVoice.getVoiceUrl() != null && qwSopTempVoice.getRecordType() == 1) {
+//            setting.setVoiceUrl(qwSopTempVoice.getVoiceUrl());
+//            setting.setVoiceDuration(String.valueOf(qwSopTempVoice.getDuration()));
+//        } else if (qwSopTempVoice == null) {
+//            if (companyUserId != null && setting.getValue() != null) {
+//                qwSopTempVoice = new QwSopTempVoice();
+//                qwSopTempVoice.setCompanyUserId(companyUserId);
+//                qwSopTempVoice.setVoiceTxt(setting.getValue());
+//                qwSopTempVoice.setRecordType(0);
+//                sopTempVoiceService.insertQwSopTempVoice(qwSopTempVoice);
+//            }
+//        }
+//    }
+//
+//    /**
+//     * 在职转接 -AI用户信息 一起转
+//     */
+//    @Async("threadPoolTaskExecutor")
+//    public void executeQwSopJobTransfer(Long externalContactId, Long externalId) {
+//        try {
+//            QwExternalContactInfo contactInfo = qwExternalContactInfoMapper.selectQwExternalContactInfoByExternalContactId(externalContactId);
+//            if (contactInfo != null) {
+//                contactInfo.setExternalContactId(externalId);
+//                qwExternalContactInfoMapper.insertQwExternalContactInfo(contactInfo);
+//            }
+//        } catch (Exception e) {
+//            log.error("在职转接-转接客户AI信息失败:{},{}", externalContactId, externalId, e);
+//        }
+//
+//    }
+//
+//
+//}

+ 8 - 1
fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java

@@ -6063,12 +6063,19 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
 
     public Boolean getSopAiChatByRedis(String qwUserId, String corpId, String externalUserId) {
 
+
         try {
             String key = (String) redisCache.getCacheObject("qwNewChat:" + qwUserId + ":" + corpId + ":" + externalUserId);
             if (!StringUtil.strIsNullOrEmpty(key)) {
                 return true;
             } else {
-                redisCache.setCacheObject("qwNewChat:" + qwUserId + ":" + corpId + ":" + externalUserId, "1", 1, TimeUnit.DAYS);
+                // 今正的新客对话,缓存7天
+                if (CloudHostUtils.hasCloudHostName("今正科技")){
+                    redisCache.setCacheObject("qwNewChat:" + qwUserId + ":" + corpId + ":" + externalUserId, "1", 7, TimeUnit.DAYS);
+                }else {
+                    redisCache.setCacheObject("qwNewChat:" + qwUserId + ":" + corpId + ":" + externalUserId, "1", 1, TimeUnit.DAYS);
+                }
+
                 return false;
             }
         } catch (Exception e) {

+ 1 - 0
fs-service/src/main/java/com/fs/qw/vo/QwSopCourseFinishTempSetting.java

@@ -63,6 +63,7 @@ public class QwSopCourseFinishTempSetting implements Serializable,Cloneable{
         private String externalUserId;
         //文本-图片-链接-小程序-文件-视频-语音-视频号-app
         private String contentType;
+        private String onlyUnregistered;
         //文本
         private String value;
         //图片

+ 7 - 0
fs-service/src/main/java/com/fs/qw/vo/QwSopRuleTimeVO.java

@@ -5,6 +5,7 @@ import com.fs.common.annotation.Excel;
 import com.fs.common.core.domain.BaseEntity;
 import lombok.Data;
 
+import java.time.LocalDateTime;
 import java.util.Date;
 
 //查出要执行的SOP
@@ -55,6 +56,12 @@ public class QwSopRuleTimeVO extends BaseEntity {
     */
     @JsonFormat(pattern = "yyyy-MM-dd")
     private Date startTime;
+
+    /**
+    * 截至时间
+    */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime deadLineTime;
     /**
      *  是否开启新客户添加自动创建sop 1 否 2 是
      */

+ 3 - 1
fs-service/src/main/java/com/fs/qw/vo/QwSopTempSetting.java

@@ -44,7 +44,7 @@ public class QwSopTempSetting implements Serializable{
         private String addTag;
 
         private String delTag;
-        
+
         private Integer isAtAll;
 
         private Long liveId;
@@ -76,6 +76,8 @@ public class QwSopTempSetting implements Serializable{
             private Long id;
             private Long sopLogId;
             private Integer intervalTime;
+            private String time;
+            private String onlyUnregistered;
             private String talkType;
             /**
              * 员工的id(用于发送)

+ 3 - 0
fs-service/src/main/java/com/fs/sop/domain/QwSop.java

@@ -57,6 +57,9 @@ public class QwSop implements Serializable
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private String startTime;
 
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private String deadLineTime;
+
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private String createTime;
 

+ 6 - 0
fs-service/src/main/java/com/fs/sop/domain/QwSopTempContent.java

@@ -58,4 +58,10 @@ public class QwSopTempContent{
      */
     @TableField(exist = false)
     private Integer videoId;
+
+    /**
+    * 第几天
+    */
+    @TableField(exist = false)
+    private Integer dayNum;
 }

+ 3 - 1
fs-service/src/main/java/com/fs/sop/mapper/QwSopTempContentMapper.java

@@ -96,10 +96,12 @@ public interface QwSopTempContentMapper extends BaseMapper<QwSopTempContent>{
             "  tc.content,\n" +
             "  tr.course_id,\n" +
             "  tr.video_id," +
-            "  tr.content_type as type " +
+            "  tr.content_type as type," +
+            "  td.day_num  " +
             "FROM\n" +
             "  qw_sop_temp_content tc " +
             "left join qw_sop_temp_rules tr on  tc.rules_id=tr.id " +
+            "LEFT JOIN qw_sop_temp_day  td on tc.day_id=td.id " +
             "where tc.temp_id=#{tempId}")
     List<QwSopTempContent> selectQwSopTempContentByTempIdAndRules(@Param("tempId") String tempId);
 

+ 5 - 0
fs-service/src/main/resources/mapper/sop/QwSopMapper.xml

@@ -41,6 +41,7 @@
         <result property="autoGroup"    column="auto_group"    />
         <result property="autoGroupLevel"    column="auto_group_level"    />
         <result property="groupName"    column="group_name"    />
+        <result property="deadLineTime"    column="dead_line_time"    />
     </resultMap>
 
     <sql id="selectQwSopVo">
@@ -134,6 +135,7 @@
     <select id="selectQwAiSopAutoByTagsByForeach"  resultType="com.fs.qw.vo.QwSopRuleTimeVO">
         SELECT
         qs.*,
+        qs.dead_line_time AS deadLineTime,
         qst.name AS temp_name,
         qst.setting AS temp_setting,
         qst.status AS temp_status,
@@ -324,6 +326,7 @@
             <if test="data.autoGroupLevel != null">auto_group_level,</if>
             <if test="data.groupName != null">group_name,</if>
             <if test="data.isFixed != null">is_fixed,</if>
+            <if test="data.deadLineTime != null">dead_line_time,</if>
         </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="data.name != null">#{data.name},</if>
@@ -357,6 +360,7 @@
             <if test="data.autoGroupLevel != null">#{data.autoGroupLevel},</if>
             <if test="data.groupName != null">#{data.groupName},</if>
             <if test="data.isFixed != null">#{data.isFixed},</if>
+            <if test="data.deadLineTime != null">#{data.deadLineTime},</if>
         </trim>
     </insert>
 
@@ -537,6 +541,7 @@
             <if test="data.autoGroupLevel != null">auto_group_level = #{data.autoGroupLevel},</if>
             <if test="data.groupName != null">group_name = #{data.groupName},</if>
             <if test="data.isFixed != null">is_fixed = #{data.isFixed},</if>
+            <if test="data.deadLineTime != null">dead_line_time = #{data.deadLineTime},</if>
         </trim>
         where id = #{data.id}
     </update>