소스 검색

Merge remote-tracking branch 'origin/master' into matser

吴树波 4 일 전
부모
커밋
ad5224c8f0
35개의 변경된 파일1216개의 추가작업 그리고 139개의 파일을 삭제
  1. 0 27
      fs-company/src/main/java/com/fs/company/controller/common/Test.java
  2. 5 3
      fs-company/src/main/java/com/fs/company/controller/crm/CrmCustomerController.java
  3. 54 9
      fs-ipad-task/src/main/java/com/fs/app/service/IpadSendServer.java
  4. 54 5
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java
  5. 30 0
      fs-service/src/main/java/com/fs/company/config/AsyncConfig.java
  6. 2 0
      fs-service/src/main/java/com/fs/company/domain/CompanyAiWorkflowExec.java
  7. 2 0
      fs-service/src/main/java/com/fs/company/domain/CompanyAiWorkflowExecLog.java
  8. 2 1
      fs-service/src/main/java/com/fs/company/domain/CompanyVoiceRoboticBusiness.java
  9. 3 0
      fs-service/src/main/java/com/fs/company/domain/CompanyVoiceRoboticCallees.java
  10. 2 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyAiWorkflowExecLogMapper.java
  11. 7 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyAiWorkflowExecMapper.java
  12. 6 1
      fs-service/src/main/java/com/fs/company/mapper/CompanyVoiceRoboticBusinessMapper.java
  13. 7 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyVoiceRoboticCalleesMapper.java
  14. 7 2
      fs-service/src/main/java/com/fs/company/mapper/CompanyWorkflowEdgeMapper.java
  15. 272 7
      fs-service/src/main/java/com/fs/company/service/impl/CompanyVoiceRoboticServiceImpl.java
  16. 81 0
      fs-service/src/main/java/com/fs/company/util/RandomNameGeneratorUtil.java
  17. 12 0
      fs-service/src/main/java/com/fs/company/vo/CompanyNodeInfoVo.java
  18. 2 0
      fs-service/src/main/java/com/fs/course/service/IFsCourseWatchLogService.java
  19. 9 0
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java
  20. 34 0
      fs-service/src/main/java/com/fs/his/config/CidPhoneConfig.java
  21. 109 0
      fs-service/src/main/java/com/fs/his/utils/Base62Utils.java
  22. 83 0
      fs-service/src/main/java/com/fs/his/utils/LinkUtil.java
  23. 2 0
      fs-service/src/main/java/com/fs/live/service/ILiveService.java
  24. 14 0
      fs-service/src/main/java/com/fs/live/service/impl/LiveServiceImpl.java
  25. 1 1
      fs-service/src/main/java/com/fs/sop/service/impl/QwSopServiceImpl.java
  26. 281 81
      fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java
  27. 0 2
      fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsServiceImpl.java
  28. 29 0
      fs-service/src/main/resources/mapper/company/CompanyAiWorkflowExecLogMapper.xml
  29. 29 0
      fs-service/src/main/resources/mapper/company/CompanyAiWorkflowExecMapper.xml
  30. 19 0
      fs-service/src/main/resources/mapper/company/CompanyVoiceRoboticBusinessMapper.xml
  31. 11 0
      fs-service/src/main/resources/mapper/company/CompanyVoiceRoboticCalleesMapper.xml
  32. 11 0
      fs-service/src/main/resources/mapper/company/CompanyWorkflowEdgeMapper.xml
  33. 6 0
      fs-user-app/src/main/java/com/fs/app/controller/CourseController.java
  34. 6 0
      fs-user-app/src/main/java/com/fs/app/controller/live/LiveController.java
  35. 24 0
      fs-user-app/src/main/java/com/fs/app/controller/store/CompanyUserScrmController.java

+ 0 - 27
fs-company/src/main/java/com/fs/company/controller/common/Test.java

@@ -1,18 +1,10 @@
 package com.fs.company.controller.common;
 
-import com.alibaba.fastjson.JSON;
-import com.fs.ad.enums.AdUploadType;
-import com.fs.common.annotation.DataSource;
-import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
-import com.fs.common.enums.DataSourceType;
 import com.fs.common.service.impl.SmsServiceImpl;
 import com.fs.company.mapper.*;
 import com.fs.company.service.ICompanyService;
-import com.fs.company.service.ICompanyUserService;
-import com.fs.company.vo.RedPacketMoneyVO;
 import com.fs.course.mapper.FsCourseRedPacketLogMapper;
-import com.fs.course.service.IFsCourseWatchLogService;
 import com.fs.course.service.ITencentCloudCosService;
 import com.fs.erp.domain.ErpDeliverys;
 import com.fs.erp.domain.ErpOrderQuery;
@@ -20,31 +12,18 @@ import com.fs.erp.dto.ErpOrderQueryRequert;
 import com.fs.erp.dto.ErpOrderQueryResponse;
 import com.fs.erp.service.IErpOrderService;
 import com.fs.fastGpt.mapper.FastGptChatSessionMapper;
-import com.fs.fastGpt.service.IFastgptEventLogTotalService;
 import com.fs.his.config.FsSysConfig;
 import com.fs.his.mapper.*;
 import com.fs.his.service.*;
 import com.fs.his.service.impl.FsPackageOrderServiceImpl;
 import com.fs.his.utils.ConfigUtil;
-import com.fs.his.utils.qrcode.QRCodeUtils;
 import com.fs.hisStore.domain.FsStoreOrderScrm;
 import com.fs.im.service.IImService;
-import com.fs.im.service.OpenIMService;
 import com.fs.qw.service.IQwAppContactWayService;
-import com.fs.qw.service.IQwCompanyService;
 import com.fs.qw.service.IQwExternalContactTransferLogService;
-import com.fs.qw.service.IQwUserService;
-import com.fs.qw.vo.AdUploadVo;
 import com.fs.qwApi.service.QwApiService;
-import com.fs.sop.service.IQwSopTempContentService;
-import com.fs.sop.service.IQwSopTempDayService;
-import com.fs.sop.service.IQwSopTempRulesService;
-import com.fs.sop.service.IQwSopTempService;
 import com.fs.system.mapper.SysConfigMapper;
-import com.google.zxing.WriterException;
-import lombok.AllArgsConstructor;
 import org.apache.commons.lang3.StringUtils;
-import org.apache.rocketmq.spring.core.RocketMQTemplate;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
@@ -54,12 +33,6 @@ import org.springframework.web.bind.annotation.RestController;
 
 import java.util.List;
 
-import javax.imageio.ImageIO;
-import java.awt.image.BufferedImage;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-
 @RestController
 public class Test {
     @Autowired

+ 5 - 3
fs-company/src/main/java/com/fs/company/controller/crm/CrmCustomerController.java

@@ -242,10 +242,12 @@ public class CrmCustomerController extends BaseController
     ){
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         CrmCustomer customer=crmCustomerService.selectCrmCustomerById(customerId);
-        customer.setMobile(customer.getMobile().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
         Boolean isReceive=false;
-        if(customer.getIsReceive()!=null&&customer.getIsReceive()==1&&customer.getReceiveUserId()!=null&&loginUser.getUser().getUserId().equals(customer.getReceiveUserId())){
-            isReceive=true;
+        if (customer != null){
+            customer.setMobile(customer.getMobile().replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2"));
+            if(customer.getIsReceive()!=null&&customer.getIsReceive()==1&&customer.getReceiveUserId()!=null&&loginUser.getUser().getUserId().equals(customer.getReceiveUserId())){
+                isReceive=true;
+            }
         }
         return R.ok().put("customer",customer).put("isReceive",isReceive);
 

+ 54 - 9
fs-ipad-task/src/main/java/com/fs/app/service/IpadSendServer.java

@@ -2,6 +2,7 @@ package com.fs.app.service;
 
 import cn.hutool.core.util.ObjectUtil;
 import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.fs.common.core.redis.RedisCache;
@@ -22,6 +23,8 @@ import com.fs.course.service.IFsCoursePlaySourceConfigService;
 import com.fs.course.service.IFsCourseWatchLogService;
 import com.fs.his.domain.FsUser;
 import com.fs.his.mapper.FsUserMapper;
+import com.fs.his.utils.LinkUtil;
+import com.fs.his.utils.PhoneUtil;
 import com.fs.ipad.IpadSendUtils;
 import com.fs.ipad.vo.*;
 import com.fs.live.domain.LiveWatchLog;
@@ -278,7 +281,7 @@ public class IpadSendServer {
         }
     }
 
-        public void sendTxtAtMsg(BaseVo vo) {
+    public void sendTxtAtMsg(BaseVo vo) {
         WxSendTextAtMsgTwoDTO dto = new WxSendTextAtMsgTwoDTO();
         List<WxSendTextAtMsgTwoDTO.Contentva> contentvaList = new ArrayList<>();
         WxSendTextAtMsgTwoDTO.Contentva contentva = new WxSendTextAtMsgTwoDTO.Contentva();
@@ -287,10 +290,10 @@ public class IpadSendServer {
         contentvaList.add(contentva);
         dto.setContentva(contentvaList);
         dto.setBase(vo);
-         dto.setUuid(vo.getUuid());
+        dto.setUuid(vo.getUuid());
         dto.setSend_userid(ipadSendUtils.userIds(vo));
         dto.setIsRoom(true);
-       ipadSendUtils.sendTxtAtMsgVo(dto, vo.getServerId());
+        ipadSendUtils.sendTxtAtMsgVo(dto, vo.getServerId());
     }
 
     public void sendVoice(BaseVo vo, QwSopCourseFinishTempSetting.Setting content) {
@@ -543,12 +546,12 @@ public class IpadSendServer {
         Integer courseType = setting.getCourseType();
         String logId = qwSopLogs.getId();
         if(null != liveWatchLog){
-                    if (!QwSopLogsServiceImpl.isCourseTypeValid(courseType, liveWatchLog.getLogType())) {
-                        // 作废消息
-                        log.warn("SOP_LOG_ID:{}, 看课状态未满足,不发送", qwSopLogs.getId());
-                        qwSopLogsService.updateQwSopLogsByWatchLogType(logId, "看课状态未满足,不发送");
-                        return false;
-                    }
+            if (!QwSopLogsServiceImpl.isCourseTypeValid(courseType, liveWatchLog.getLogType())) {
+                // 作废消息
+                log.warn("SOP_LOG_ID:{}, 看课状态未满足,不发送", qwSopLogs.getId());
+                qwSopLogsService.updateQwSopLogsByWatchLogType(logId, "看课状态未满足,不发送");
+                return false;
+            }
         }
         else{
             log.warn("SOP_LOG_ID:{}, 无观看记录,不发送", qwSopLogs.getId());
@@ -723,6 +726,12 @@ public class IpadSendServer {
                     content.setSendStatus(0);
                     content.setSendRemarks("短信待发送");
                     break;
+                    //跳转APP看课链接
+                case "23":
+                    //跳转APP直播链接
+                case "24":
+                    sendAppShortLink(vo, content, miniMap);
+                    break;
                 case "99":
                     // 群发
                     sendTxtAtMsg(vo);
@@ -739,6 +748,42 @@ public class IpadSendServer {
         }
     }
 
+    private void sendAppShortLink(BaseVo vo, QwSopCourseFinishTempSetting.Setting content, Map<String, FsCoursePlaySourceConfig> miniMap) {
+//        //发送短链前,先发送一个介绍语
+//        TxtVo introduction = TxtVo.builder().content("请复制以下短链内容").build();
+//        introduction.setBase(vo);
+//        ipadSendUtils.sendTxt(introduction);
+        //发送短链内容
+        String miniProgramPage = content.getMiniprogramPage();
+        String sendShortLink = null;
+        //解析直播短链信息
+        String livePrefix = "/pages_live/livingList?link=";
+        if (miniProgramPage.startsWith(livePrefix)) {
+            JSONObject obj = JSONObject.parseObject(miniProgramPage.substring(livePrefix.length()));
+            sendShortLink = livePrefix + obj.getString("link");
+        }
+        //解析课程短链信息
+        String coursePrefix = "/pages/courseAnswer/index?link=";
+        if (miniProgramPage.startsWith(coursePrefix)) {
+            JSONObject obj = JSONObject.parseObject(miniProgramPage.substring(coursePrefix.length()));
+            sendShortLink = coursePrefix + obj.getString("link");
+        }
+        if (null == sendShortLink) {
+            log.warn("发送链接为空");
+            return;
+        }
+        sendShortLink = sendShortLink.replace(".html","");
+        String InvitationCode = LinkUtil.encryptLink(sendShortLink);
+        TxtVo txtVo = TxtVo.builder().content(InvitationCode).build();
+        txtVo.setBase(vo);
+        WxWorkResponseDTO<WxWorkSendTextMsgRespDTO> resp = ipadSendUtils.sendTxt(txtVo);
+        if (resp.getErrcode() != 0) {
+            log.debug("ID:{}-ipad接口请求返回异常:{}", vo.getId(), resp.getErrmsg());
+            content.setSendStatus(2);
+            content.setSendRemarks("发送失败:" + resp.getErrmsg());
+        }
+    }
+
     /**
      * 发送直播短链
      * @param vo

+ 54 - 5
fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java

@@ -436,7 +436,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
 
     private void processSopGroup(String sopId, List<SopUserLogsVo> userLogsVos, LocalDateTime currentTime, Map<String,
-            QwGroupChat> groupChatMap, CourseConfig config, Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap,
+                                         QwGroupChat> groupChatMap, CourseConfig config, Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap,
                                  List<Company> companies) throws Exception {
         QwSopRuleTimeVO ruleTimeVO = sopMapper.selectQwSopByClickHouseId(sopId);
 
@@ -522,7 +522,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
 
             if (logVo.getFilterMode()==2 && config != null && config.getRoomLinkAllow() != null && config.getRoomLinkAllow()) {
-                 return;
+                return;
             }
 
             LocalDate startDate = LocalDate.parse(logVo.getStartTime(), DATE_FORMATTER);
@@ -1360,6 +1360,17 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                         log.error("赋值-小程序封面地址失败-" + e);
                     }
                     break;
+                //录播
+                case "24":
+                    corpId = logVo.getCorpId();
+                    shortH5Link = createH5LiveShortLink(setting, corpId,
+                            qwUserId, companyUserId, companyId);
+
+                    sopLogs.setSendType(Integer.valueOf(setting.getContentType()));
+                    clonedContent.setLiveId(setting.getLiveId());
+
+                    setting.setMiniprogramPage(shortH5Link);
+                    break;
                 default:
                     break;
             }
@@ -1370,7 +1381,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
     }
 
     private String createRegisteredLinkByMiniApp(QwSopTempSetting.Content.Setting setting, SopUserLogsVo logVo, Date sendTime,
-                                                  String qwUserId,
+                                                 String qwUserId,
                                                  String companyUserId, String companyId, String externalId, Long fsUserId) {
         // 获取缓存的配置
         CourseConfig config;
@@ -1432,7 +1443,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
 
     public String createRegisteredGroupLinkByMiniApp(QwSopTempSetting.Content.Setting setting, SopUserLogsVo logVo, Date sendTime,
-                                                      String qwUserId,
+                                                     String qwUserId,
                                                      Long companyUserId, String companyId, String chatId) {
         // 获取缓存的配置
         CourseConfig config;
@@ -1606,6 +1617,17 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                         log.error("赋值-小程序封面地址失败-" + e);
                     }
                     break;
+                //跳转app直播
+                case "24":
+                    corpId = logVo.getCorpId();
+                    shortH5Link = createH5LiveShortLink(setting, corpId,
+                            qwUserId, companyUserId, companyId);
+
+                    sopLogs.setSendType(Integer.valueOf(setting.getContentType()));
+                    clonedContent.setLiveId(setting.getLiveId());
+
+                    setting.setMiniprogramPage(shortH5Link);
+                    break;
                 default:
                     break;
             }
@@ -1625,7 +1647,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                                      SopUserLogsVo logVo, Date sendTime, Long courseId, Long videoId, String qwUserId, String companyUserId,
                                      String companyId, String externalId, String welcomeText, String qwUserName,
                                      Long fsUserId, boolean isGroupChat, String miniAppId, QwGroupChat groupChat, CourseConfig config, Map<Long,
-            Map<Integer, List<CompanyMiniapp>>> miniMap, Integer grade, Integer sendMsgType,
+                    Map<Integer, List<CompanyMiniapp>>> miniMap, Integer grade, Integer sendMsgType,
                                      List<Company> companies,Long serverId) {
         QwExternalContact contact = null;
         if (logVo.getExternalId() != null) {
@@ -1954,6 +1976,33 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
 
                         enqueueQwSopSmsLogs(sopSmsLogs);
                     }
+                    break;
+                //跳转app看课
+                case "23":
+                    try {
+                        addWatchLogIfNeeded(sopLogs, videoId, courseId, sendTime, qwUserId, companyUserId, companyId, externalId, logVo,2);
+
+                        String shortH5link = createH5LinkByMiniApp(setting, logVo, sendTime, courseId, videoId,
+                                qwUserId, companyUserId, companyId, externalId, isOfficial, sopLogs.getFsUserId());
+
+                        setting.setMiniprogramTitle("邀请链接");
+                        setting.setMiniprogramPage(shortH5link);
+
+                    } catch (Exception e) {
+                        log.error("app看课模板解析失败:" + e);
+                    }
+                    break;
+                //app直播跳转
+                case "24":
+                    String corpId = logVo.getCorpId();
+                    String shortH5Link = createH5LiveShortLink(setting, corpId,
+                            qwUserId, companyUserId, companyId);
+
+                    sopLogs.setSendType(Integer.valueOf(setting.getContentType()));
+                    clonedContent.setLiveId(setting.getLiveId());
+
+                    setting.setMiniprogramPage(shortH5Link);
+
                     break;
                 default:
                     break;

+ 30 - 0
fs-service/src/main/java/com/fs/company/config/AsyncConfig.java

@@ -0,0 +1,30 @@
+package com.fs.company.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.ThreadPoolExecutor;
+
+@Configuration
+@EnableAsync
+public class AsyncConfig {
+    @Bean(name = "calleeTaskExecutor")
+    public Executor calleeTaskExecutor() {
+        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+        int cpuCores = Runtime.getRuntime().availableProcessors();
+        executor.setCorePoolSize(cpuCores);
+        executor.setMaxPoolSize(20);
+        executor.setQueueCapacity(1000);
+        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
+        executor.setKeepAliveSeconds(60);
+        executor.setAllowCoreThreadTimeOut(false);
+        executor.setWaitForTasksToCompleteOnShutdown(true);
+        executor.setAwaitTerminationSeconds(60);
+
+        executor.initialize();
+        return executor;
+    }
+}

+ 2 - 0
fs-service/src/main/java/com/fs/company/domain/CompanyAiWorkflowExec.java

@@ -94,4 +94,6 @@ public class CompanyAiWorkflowExec {
      */
     private Integer cidGroupNo;
 
+    //是否生成数据(0否,1是)
+    private Integer isGenerate;
 }

+ 2 - 0
fs-service/src/main/java/com/fs/company/domain/CompanyAiWorkflowExecLog.java

@@ -78,5 +78,7 @@ public class CompanyAiWorkflowExecLog {
     @Excel(name = "创建时间", width = 30, dateFormat = "yyyy-MM-dd")
     private Date createdTime;
 
+    //是否生成数据(0否,1是)
+    private Integer isGenerate;
 
 }

+ 2 - 1
fs-service/src/main/java/com/fs/company/domain/CompanyVoiceRoboticBusiness.java

@@ -45,5 +45,6 @@ public class CompanyVoiceRoboticBusiness extends BaseEntity{
     @Excel(name = "发送短信动作完成,每次加1 初始0")
     private Integer sendMsgDone;
 
-
+    //是否生成数据(0否,1是)
+    private Integer isGenerate;
 }

+ 3 - 0
fs-service/src/main/java/com/fs/company/domain/CompanyVoiceRoboticCallees.java

@@ -64,4 +64,7 @@ public class CompanyVoiceRoboticCallees{
     private String idToString;
 
     private Integer isWeCom;
+
+    //是否生成数据(0否,1是)
+    private Integer isGenerate;
 }

+ 2 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyAiWorkflowExecLogMapper.java

@@ -66,4 +66,6 @@ public interface CompanyAiWorkflowExecLogMapper extends BaseMapper<CompanyAiWork
      * @return 执行日志列表
      */
     List<CompanyAiWorkflowExecLog> selectByWorkflowInstanceId(@Param("workflowInstanceId") String workflowInstanceId);
+
+    void batchInsert(@Param("list") List<CompanyAiWorkflowExecLog> logList);
 }

+ 7 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyAiWorkflowExecMapper.java

@@ -97,4 +97,11 @@ public interface CompanyAiWorkflowExecMapper extends BaseMapper<CompanyAiWorkflo
     List<CompanyAiWorkflowExec> selectExecListWithTimeAvailableByStatusAndGroupNo(@Param("status") Integer status, @Param("groupNo") Integer groupNo);
 
     CompanyAiWorkflowExec selectExecWithTimeAvailableByInstanceId(@Param("workflowInstanceId") String workflowInstanceId);
+
+    /**
+     * 批量新增数据
+     * @param list
+     * @return int
+     * **/
+    int insertBatchInfo(@Param("list") List<CompanyAiWorkflowExec> list);
 }

+ 6 - 1
fs-service/src/main/java/com/fs/company/mapper/CompanyVoiceRoboticBusinessMapper.java

@@ -87,5 +87,10 @@ public interface CompanyVoiceRoboticBusinessMapper extends BaseMapper<CompanyVoi
 
     Integer selectUnfinishedTaskCountByRoboticId(@Param("roboticId") Long roboticId, @Param("endNodeKey") String endNodeKey);
 
-
+    /**
+     * 批量插入生成业务数据
+     * @param businessList 业务数据列表
+     * @return 影响的行数
+     */
+    int insertBatchGenerateInfo(@Param("list") List<CompanyVoiceRoboticBusiness> businessList);
 }

+ 7 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyVoiceRoboticCalleesMapper.java

@@ -86,4 +86,11 @@ public interface CompanyVoiceRoboticCalleesMapper extends BaseMapper<CompanyVoic
 
     List<CompanyVoiceRoboticCallees> selectExcludeList(@Param("list")List<CompanyWxClient> list,@Param("isWeCom") Integer isWeCom);
     List<Long> getNotFinishAddWxRobotic(@Param("roboticIds") Set<Long> roboticIds);
+
+    /**
+     * 批量插入生成数据
+     * @param list 插入数据
+     * @return int
+     * **/
+    int batchInsertGenerateInfo(@Param("list") List<CompanyVoiceRoboticCallees> list);
 }

+ 7 - 2
fs-service/src/main/java/com/fs/company/mapper/CompanyWorkflowEdgeMapper.java

@@ -2,10 +2,9 @@ package com.fs.company.mapper;
 
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import com.fs.company.domain.CompanyVoiceRobotic;
 import com.fs.company.domain.CompanyWorkflowEdge;
+import com.fs.company.vo.CompanyNodeInfoVo;
 import org.apache.ibatis.annotations.Param;
-import org.mapstruct.Mapper;
 
 import java.util.List;
 
@@ -32,4 +31,10 @@ public interface CompanyWorkflowEdgeMapper  extends BaseMapper<CompanyWorkflowEd
     Integer deleteCompanyWorkflowEdgeByWorkflowId(Long workflowId);
 
     List<CompanyWorkflowEdge> selectListByWorkflowIdAndNodeKey(@Param("workflowId")Long workflowId, @Param("nodeKey")String nodeKey);
+
+    /**
+     *获取节点信息
+     *
+     * */
+    CompanyNodeInfoVo slectNodeInfoByWorkflowId(@Param("workflowId")Long workflowId, @Param("nodeKey")String nodeKey);
 }

+ 272 - 7
fs-service/src/main/java/com/fs/company/service/impl/CompanyVoiceRoboticServiceImpl.java

@@ -22,6 +22,7 @@ import com.fs.company.domain.*;
 import com.fs.company.mapper.*;
 import com.fs.company.param.ExecutionContext;
 import com.fs.company.service.*;
+import com.fs.company.util.RandomNameGeneratorUtil;
 import com.fs.company.vo.*;
 import com.fs.company.vo.easycall.EasyCallCallPhoneVO;
 import com.fs.crm.domain.CrmCustomer;
@@ -31,22 +32,22 @@ import com.fs.crm.service.impl.CrmCustomerServiceImpl;
 import com.fs.enums.ExecutionStatusEnum;
 import com.fs.enums.NodeTypeEnum;
 import com.fs.enums.TaskTypeEnum;
+import com.fs.his.config.CidPhoneConfig;
 import com.fs.qw.domain.QwUser;
 import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.service.impl.QwExternalContactServiceImpl;
+import com.fs.system.domain.SysConfig;
+import com.fs.system.mapper.SysConfigMapper;
 import com.fs.system.mapper.SysDictDataMapper;
 import com.fs.system.service.ISysConfigService;
 import lombok.RequiredArgsConstructor;
 import lombok.Synchronized;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
+import java.time.LocalDateTime;
 import java.util.*;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.Executor;
 import java.util.stream.Collectors;
 
 import static com.fs.company.service.impl.call.node.AiCallTaskNode.EASYCALL_WORKFLOW_REDIS_KEY;
@@ -110,8 +111,18 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
     private final CompanyAiWorkflowServerMapper companyAiWorkflowServerMapper;
     private final QwUserMapper qwUserMapper;
     private final EasyCallMapper easyCallMapper;
+
+    private final SysConfigMapper sysConfigMapper;
+    private final CompanyConfigMapper companyConfigMapper;
+
+    private final CompanyWorkflowMapper companyWorkflowMapper;
+
+    private final CompanyWorkflowEdgeMapper edgeMapper;
+
     private final QwExternalContactServiceImpl qwExternalContactService;
 
+    final int BATCH_SIZE = 1500;
+
     /**
      * 查询机器人外呼任务
      *
@@ -1047,7 +1058,7 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
             buildTaskBussiness(robotic);
             // 查询业务列表
             List<CompanyVoiceRoboticBusiness> roboticBusinesseList = companyVoiceRoboticBusinessMapper
-                    .selectList(new QueryWrapper<CompanyVoiceRoboticBusiness>().eq("robotic_id", id));
+                    .selectList(new QueryWrapper<CompanyVoiceRoboticBusiness>().eq("robotic_id", id).eq("is_generate",0));
             if (roboticBusinesseList.isEmpty()) {
                 log.warn("任务没有业务数据: {}", id);
                 return;
@@ -1173,12 +1184,43 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
         return resArr;
     }
 
-    public void buildTaskBussiness(CompanyVoiceRobotic robotic){
-        List<CompanyVoiceRoboticCallees> calleesList = companyVoiceRoboticCalleesMapper.selectByRoboticId(robotic.getId());
+    public void buildTaskBussiness(CompanyVoiceRobotic robotic){    List<CompanyVoiceRoboticCallees> calleesList = companyVoiceRoboticCalleesMapper.selectByRoboticId(robotic.getId());
+        //获取电话生成配置
+        CidPhoneConfig phoneConfig = null;
+        //获取销售公司手机配置
+        CompanyConfig companyConfig = companyConfigMapper.selectCompanyConfigByKey(robotic.getCompanyId(),"cid.config");
+        //如果配置为空就获取总后台配置
+        if(companyConfig == null){
+            SysConfig sysConfig = sysConfigMapper.selectConfigByConfigKey("cid.config");
+            if(sysConfig != null){
+                phoneConfig = JSONObject.parseObject(sysConfig.getConfigValue(),CidPhoneConfig.class);
+            }
+        } else {
+            phoneConfig = JSONObject.parseObject(companyConfig.getConfigValue(),CidPhoneConfig.class);
+        }
+
+        //获取工作流主表信息
+        CompanyWorkflow workflow = companyWorkflowMapper.selectCompanyWorkflowById(robotic.getCompanyAiWorkflowId());
+
+        //获取相关节点信息
+        CompanyNodeInfoVo nodeInfoVo = edgeMapper.slectNodeInfoByWorkflowId(workflow.getWorkflowId(),workflow.getStartNodeKey());
+
         List<CompanyWxClient> companyWxClients = companyWxClientMapper.selectListByRoboticId(robotic.getId());
         Map<String, CompanyWxClient> clientMp = companyWxClients.stream().collect(Collectors.toMap(e -> e.getRoboticId() + "-" + e.getCustomerId(), e -> e));
         List<CompanyVoiceRoboticBusiness> addList = new ArrayList<>();
+        List<CompanyVoiceRoboticCallees> batchToInsert = new LinkedList<>();
         for (CompanyVoiceRoboticCallees callees : calleesList) {
+            //根据配置随机生成电话号
+            if(phoneConfig != null && phoneConfig.getEnablePhoneConfig()){//配置不为空并且开启了
+                List<CompanyVoiceRoboticCallees> roboticCallees = generatePhoneNumber(phoneConfig,callees);
+                if(!roboticCallees.isEmpty()){
+                    batchToInsert.addAll(roboticCallees);
+                    if(batchToInsert.size() >= BATCH_SIZE){
+                        flushGeneratedCalleesBatch(new LinkedList<>(batchToInsert), clientMp, robotic,workflow,nodeInfoVo);
+                        batchToInsert.clear();
+                    }
+                }
+            }
             CompanyVoiceRoboticBusiness companyVoiceRoboticBusiness = new CompanyVoiceRoboticBusiness();
             companyVoiceRoboticBusiness.setRoboticId(robotic.getId());
             companyVoiceRoboticBusiness.setCalleeId(callees.getId());
@@ -1189,7 +1231,15 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
             companyVoiceRoboticBusiness.setCreateTime(new Date());
             addList.add(companyVoiceRoboticBusiness);
         }
+
+        //处理剩余数据
+        if(!batchToInsert.isEmpty()){
+            flushGeneratedCalleesBatch(new LinkedList<>(batchToInsert), clientMp, robotic,workflow,nodeInfoVo);
+            batchToInsert.clear();
+        }
+
         companyVoiceRoboticBusinessMapper.insertBatch(addList);
+
     }
 
     private void taskJoin2Workflow(){
@@ -1315,4 +1365,219 @@ public class CompanyVoiceRoboticServiceImpl extends ServiceImpl<CompanyVoiceRobo
             return vo;
         }).collect(Collectors.toList());
     }
+
+    /**
+     * 根据配置生成手机号
+     * @param config      配置对象(包含是否启用、生成数量、起始位置、结束位置)
+     * @param callees     任务外呼电话对象
+     * @return 生成的手机号列表
+     */
+    public static List<CompanyVoiceRoboticCallees> generatePhoneNumber(CidPhoneConfig config,CompanyVoiceRoboticCallees callees) {
+        String basePhone = callees.getPhone();
+        if (basePhone == null || basePhone.length() != 11) {
+            return  null;
+        }
+
+        int start = config.getStartIndex();
+        int end = config.getEndIndex();
+        int count = config.getGenerateCount();
+
+        // 校验索引范围
+        if (start < 1 || start > 11 || end < 1 || end > 11 || start > end) {
+            return null;
+        }
+
+        int startIdx = start;
+        int endIdx = end-1;
+
+        char[] baseChars = basePhone.toCharArray();
+        //预加载随机
+        List<String> nameList = RandomNameGeneratorUtil.generateBatch(count);
+        Random random = new Random();
+        List<CompanyVoiceRoboticCallees> result = new ArrayList<>(count);
+        for (int i = 0; i < count; i++) {
+            CompanyVoiceRoboticCallees roboticCallees = new CompanyVoiceRoboticCallees();
+            // 克隆基础数组
+            char[] newChars = baseChars.clone();
+            for (int j = startIdx; j <= endIdx; j++) {
+                // 生成0-9的随机数字字符
+                newChars[j] = (char) ('0' + random.nextInt(10));
+            }
+            String phone = String.valueOf(newChars);
+            roboticCallees.setPhone(phone);//电话
+            roboticCallees.setRoboticId(callees.getRoboticId());//任务ID
+            roboticCallees.setTaskFlow(callees.getTaskFlow());//任务流程
+            roboticCallees.setRunTaskFlow(callees.getRunTaskFlow());//已执行流程
+            roboticCallees.setIsWeCom(callees.getIsWeCom());
+            roboticCallees.setUserId(0L);//用户ID
+            roboticCallees.setUserName(nameList.get(i));//姓名
+            roboticCallees.setIsGenerate(1);
+            result.add(roboticCallees);
+        }
+        return result;
+    }
+
+    /**
+     * 生成外呼任务表
+     * **/
+    public void generateVoiceRoboticBusiness(List<CompanyVoiceRoboticCallees> calleesList,
+                                             Map<String, CompanyWxClient> clientMp
+            ,CompanyVoiceRobotic robotic
+            ,CompanyWorkflow workflow
+            ,CompanyNodeInfoVo nodeInfoVo){
+        if(!calleesList.isEmpty()){
+            List<CompanyVoiceRoboticBusiness> addList = new LinkedList<>();
+            Date date =new Date();
+            for (CompanyVoiceRoboticCallees callees : calleesList){
+                CompanyVoiceRoboticBusiness companyVoiceRoboticBusiness = new CompanyVoiceRoboticBusiness();
+                companyVoiceRoboticBusiness.setRoboticId(callees.getRoboticId());
+                companyVoiceRoboticBusiness.setCalleeId(callees.getId());
+                companyVoiceRoboticBusiness.setWxClientId(clientMp.getOrDefault(callees.getRoboticId() + "-" + callees.getUserId(),new CompanyWxClient()).getId());
+                companyVoiceRoboticBusiness.setAddWxDone(0);
+                companyVoiceRoboticBusiness.setCallPhoneDone(0);
+                companyVoiceRoboticBusiness.setSendMsgDone(0);
+                companyVoiceRoboticBusiness.setCreateTime(date);
+                companyVoiceRoboticBusiness.setIsGenerate(1);
+                addList.add(companyVoiceRoboticBusiness);
+            }
+
+            //批量插入ai外呼业务对象
+            if(!addList.isEmpty()){
+                flushGeneratedBusinessBatch(addList,robotic,workflow,nodeInfoVo);
+            }
+        }
+    }
+
+    /**
+     * 刷新并批量插入生成的被叫数据,并生成对应的业务对象
+     * @param batchToInsert 待插入的被叫列表
+     * @param clientMap     微信客户映射,用于关联客户ID
+     */
+    @Async("calleeTaskExecutor")
+    public void flushGeneratedCalleesBatch(List<CompanyVoiceRoboticCallees> batchToInsert,
+                                           Map<String, CompanyWxClient> clientMap,
+                                           CompanyVoiceRobotic robotic,
+                                           CompanyWorkflow workflow,
+                                           CompanyNodeInfoVo nodeInfoVo) {
+        if (batchToInsert.isEmpty()) {
+            return;
+        }
+        int rows = companyVoiceRoboticCalleesMapper.batchInsertGenerateInfo(batchToInsert);
+        if (rows > 0) {
+            generateVoiceRoboticBusiness(batchToInsert, clientMap,robotic,workflow,nodeInfoVo);
+        }
+        batchToInsert.clear();
+    }
+
+    /**
+     * 批量插入业务对象,并创建工作流执行记录
+     */
+    private void flushGeneratedBusinessBatch(List<CompanyVoiceRoboticBusiness> batchToInsert,CompanyVoiceRobotic robotic,CompanyWorkflow workflow,CompanyNodeInfoVo nodeInfoVo){
+        int rows = companyVoiceRoboticBusinessMapper.insertBatchGenerateInfo(batchToInsert);
+        if(rows > 0){
+            LocalDateTime now = LocalDateTime.now();
+            List<CompanyAiWorkflowExec> workflowExecs = new LinkedList<>();
+            //插入执行流程代码
+            for (CompanyVoiceRoboticBusiness business : batchToInsert){
+                // 第一个节点(开始节点)
+                CompanyAiWorkflowExec startExec = new CompanyAiWorkflowExec();
+                startExec.setWorkflowInstanceId(generateInstanceId());
+                startExec.setWorkflowId(robotic.getCompanyAiWorkflowId());
+                startExec.setCurrentNodeKey(workflow.getStartNodeKey());
+                startExec.setCurrentNodeType(NodeTypeEnum.START.getValue());
+                startExec.setCurrentNodeName(NodeTypeEnum.START.getDescription());
+                startExec.setStatus(ExecutionStatusEnum.SUCCESS.getValue()); // 开始节点执行成功
+                startExec.setStartTime(now);
+
+                JSONObject variables = new JSONObject();
+                variables.put("roboticId", robotic.getId());
+                variables.put("businessId", business.getId());
+                variables.put("cidGroupNo", robotic.getCidGroupNo());
+                variables.put("runtimeRangeStart", robotic.getRuntimeRangeStart());
+                variables.put("runtimeRangeEnd", robotic.getRuntimeRangeEnd());
+                startExec.setVariables(variables.toJSONString());
+                startExec.setBusinessKey(business.getId().toString());
+                startExec.setStartNodeKey(workflow.getStartNodeKey());
+                startExec.setEndNodeKey(workflow.getEndNodeKey());
+                startExec.setCidGroupNo(robotic.getCidGroupNo());
+                startExec.setRuntimeRangeStart(robotic.getRuntimeRangeStart());
+                startExec.setRuntimeRangeEnd(robotic.getRuntimeRangeEnd());
+                startExec.setIsGenerate(1);
+                workflowExecs.add(startExec);
+                CompanyAiWorkflowExec targetExec = new CompanyAiWorkflowExec();
+
+                // 复制公共字段
+                targetExec.setWorkflowInstanceId(generateInstanceId());
+                targetExec.setWorkflowId(startExec.getWorkflowId());
+                targetExec.setCurrentNodeKey(nodeInfoVo.getTargetNodeKey());
+                targetExec.setCurrentNodeName(nodeInfoVo.getNodeName());
+                targetExec.setCurrentNodeType(NodeTypeEnum.fromCode(nodeInfoVo.getNodeType()).getValue());
+                targetExec.setStatus(ExecutionStatusEnum.FAILURE.getValue());
+                targetExec.setStartTime(now);
+                targetExec.setVariables(variables.toJSONString());
+                targetExec.setBusinessKey(startExec.getBusinessKey());
+                targetExec.setStartNodeKey(startExec.getStartNodeKey());
+                targetExec.setEndNodeKey(startExec.getEndNodeKey());
+                targetExec.setCidGroupNo(startExec.getCidGroupNo());
+                targetExec.setRuntimeRangeStart(startExec.getRuntimeRangeStart());
+                targetExec.setRuntimeRangeEnd(startExec.getRuntimeRangeEnd());
+                targetExec.setIsGenerate(1);
+                workflowExecs.add(targetExec);
+
+                if(workflowExecs.size() >= BATCH_SIZE){
+                    generateVoiceRoboticCurrentExecLogs(new LinkedList<>(workflowExecs));
+                    workflowExecs.clear();
+                }
+            }
+
+            if(!workflowExecs.isEmpty()){
+                generateVoiceRoboticCurrentExecLogs(new LinkedList<>(workflowExecs));
+                workflowExecs.clear();
+            }
+        }
+        batchToInsert.clear();
+    }
+
+    public void generateVoiceRoboticCurrentExecLogs(List<CompanyAiWorkflowExec> workflowExecs){
+        if (workflowExecs.isEmpty()) {
+            return;
+        }
+
+        int rows = companyAiWorkflowExecMapper.insertBatchInfo(workflowExecs);
+        if(rows > 0){
+
+            //插入日志记录表
+            Date date =new Date();
+            List<CompanyAiWorkflowExecLog> batch = new LinkedList<>();
+            workflowExecs.forEach(w->{
+                CompanyAiWorkflowExecLog execLog = new CompanyAiWorkflowExecLog();
+                execLog.setWorkflowInstanceId(w.getWorkflowInstanceId());
+                execLog.setNodeKey(w.getCurrentNodeKey());
+                execLog.setNodeName(w.getCurrentNodeName());
+                execLog.setNodeType(w.getCurrentNodeType());
+                execLog.setInputData(w.getVariables());
+                execLog.setStatus(w.getStatus());
+                execLog.setOutputData("null");
+                execLog.setStartTime(date);
+                execLog.setEndTime(date);
+                execLog.setIsGenerate(1);
+                batch.add(execLog);
+            });
+            if(!batch.isEmpty()){
+                companyAiWorkflowExecLogMapper.batchInsert(batch);
+                batch.clear();
+            }
+        }
+        workflowExecs.clear();
+    }
+
+    /**
+     * 生成工作流实例ID
+     */
+    private String generateInstanceId() {
+        return "wf_" + System.currentTimeMillis() + "_" +
+                UUID.randomUUID().toString().replace("-", "");
+    }
+
+
 }

+ 81 - 0
fs-service/src/main/java/com/fs/company/util/RandomNameGeneratorUtil.java

@@ -0,0 +1,81 @@
+package com.fs.company.util;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ThreadLocalRandom;
+
+//随机生成
+public class RandomNameGeneratorUtil {
+    // 常见姓氏(单姓 + 少量复姓)
+    private static final String[] SURNAMES = {
+            "赵", "钱", "孙", "李", "周", "吴", "郑", "王", "冯", "陈",
+            "褚", "卫", "蒋", "沈", "韩", "杨", "朱", "秦", "尤", "许",
+            "何", "吕", "施", "张", "孔", "曹", "严", "华", "金", "魏",
+            "陶", "姜", "戚", "谢", "邹", "喻", "柏", "水", "窦", "章",
+            "云", "苏", "潘", "葛", "奚", "范", "彭", "郎", "鲁", "韦",
+            "昌", "马", "苗", "凤", "花", "方", "俞", "任", "袁", "柳",
+            "鲍", "史", "唐", "费", "廉", "岑", "薛", "雷", "贺", "倪",
+            "汤", "殷", "罗", "毕", "郝", "邬", "安", "常", "乐", "于",
+            "时", "傅", "皮", "卞", "齐", "康", "伍", "余", "元", "卜",
+            "顾", "孟", "平", "黄", "和", "穆", "萧", "尹", "姚", "邵",
+            "湛", "汪", "祁", "毛", "禹", "狄", "米", "贝", "明", "臧",
+            "欧阳", "慕容", "上官", "司马", "夏侯", "诸葛", "东方", "皇甫", "尉迟", "公孙"
+    };
+
+    // 常用名字用字
+    private static final String[] GIVEN_NAMES = {
+            "伟", "强", "军", "勇", "杰", "涛", "斌", "鹏", "宇", "浩",
+            "鑫", "磊", "帅", "超", "俊", "帆", "波", "辉", "刚", "健",
+            "明", "亮", "峰", "松", "林", "森", "荣", "华", "富", "贵",
+            "芳", "娜", "敏", "静", "秀", "娟", "英", "华", "慧", "巧",
+            "美", "颖", "玲", "燕", "红", "丽", "艳", "倩", "婷", "娇",
+            "淑", "贞", "珠", "琴", "雪", "云", "霞", "露", "雯", "姗",
+            "欣", "怡", "晨", "曦", "阳", "光", "天", "然", "乐", "悦",
+            "思", "念", "文", "武", "双", "全", "子", "涵", "泽", "洋",
+            "博", "睿", "智", "远", "达", "通", "道", "德", "仁", "义"
+    };
+
+    // 名字长度范围
+    private static final int MIN_NAME_LENGTH = 2;
+    private static final int MAX_NAME_LENGTH = 3;
+
+    /**
+     * 生成随机姓名
+     * @return 随机姓名
+     */
+    public static String generateOne() {
+        ThreadLocalRandom rand = ThreadLocalRandom.current();
+        // 随机选择姓氏
+        String surname = SURNAMES[rand.nextInt(SURNAMES.length)];
+
+        // 随机决定名字长度
+        int nameLength = rand.nextInt(MIN_NAME_LENGTH, MAX_NAME_LENGTH + 1);
+
+        StringBuilder sb = new StringBuilder(surname);
+        for (int i = 1; i < nameLength; i++) {
+            sb.append(GIVEN_NAMES[rand.nextInt(GIVEN_NAMES.length)]);
+        }
+        return sb.toString();
+    }
+
+    /**
+     * 批量生成随机姓名
+     * @param count 生成数量
+     * @return 姓名列表
+     */
+    public static List<String> generateBatch(int count) {
+        List<String> result = new ArrayList<>(count);
+        for (int i = 0; i < count; i++) {
+            result.add(generateOne());
+        }
+        return result;
+    }
+
+    public static void main(String[] args) {
+        long start = System.currentTimeMillis();
+        List<String> names = generateBatch(100_000);
+        long end = System.currentTimeMillis();
+        System.out.println("生成10万个姓名耗时:" + (end - start) + " ms");
+        names.stream().limit(10).forEach(System.out::println);
+    }
+}

+ 12 - 0
fs-service/src/main/java/com/fs/company/vo/CompanyNodeInfoVo.java

@@ -0,0 +1,12 @@
+package com.fs.company.vo;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class CompanyNodeInfoVo implements Serializable {
+    private String targetNodeKey;
+    private String nodeName;
+    private String nodeType;
+}

+ 2 - 0
fs-service/src/main/java/com/fs/course/service/IFsCourseWatchLogService.java

@@ -168,6 +168,8 @@ public interface IFsCourseWatchLogService extends IService<FsCourseWatchLog> {
 
     R decryptLink(String url);
 
+    R decryptLinkV2(String url);
+
     List<FsCourseWatchLog> selectFsUserWatchLogByExtId(QwExternalContact qwExternalContact);
 
     FsCourseWatchLog selectFsCourseWatchLogWithUCCV(Long userId, Long companyUserId, Integer courseId, Integer videoId);

+ 9 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java

@@ -40,6 +40,7 @@ import com.fs.his.config.FsSysConfig;
 import com.fs.his.domain.FsUser;
 import com.fs.his.service.IFsUserService;
 import com.fs.his.utils.ConfigUtil;
+import com.fs.his.utils.LinkUtil;
 import com.fs.his.utils.PhoneUtil;
 import com.fs.qw.Bean.MsgBean;
 import com.fs.qw.cache.IQwExternalContactCacheService;
@@ -1743,6 +1744,14 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         return R.ok().put("data", data);
     }
 
+    @Override
+    public R decryptLinkV2(String url) {
+        String decryptLink = LinkUtil.decryptLink(url);
+        Map<String, Object> data = new HashMap<>();
+        data.put("decryptLink", decryptLink);
+        return R.ok().put("data", data);
+    }
+
     @Override
     public List<FsCourseWatchLog> selectFsUserWatchLogByExtId(QwExternalContact qwExternalContact) {
         return fsCourseWatchLogMapper.selectFsUserWatchLogByExtId(qwExternalContact);

+ 34 - 0
fs-service/src/main/java/com/fs/his/config/CidPhoneConfig.java

@@ -0,0 +1,34 @@
+package com.fs.his.config;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * cId手机号生成配置类
+ * **/
+@Data
+public class CidPhoneConfig implements Serializable {
+    /**
+     * 是否开启手机号配置
+     * true: 开启,显示生成条数输入框
+     * false: 关闭,隐藏生成条数输入框
+     */
+    private Boolean enablePhoneConfig;
+
+    /**
+     * 生成条数
+     * 当 enablePhoneConfig 为 true 时有效,表示需要生成的手机号数量
+     */
+    private Integer generateCount;
+
+    /**
+     * 开始位置(从第几位开始生成)
+     */
+    private Integer startIndex;
+
+    /**
+     * 结束位置(到第几位结束)
+     */
+    private Integer endIndex;
+}

+ 109 - 0
fs-service/src/main/java/com/fs/his/utils/Base62Utils.java

@@ -0,0 +1,109 @@
+package com.fs.his.utils;
+
+public class Base62Utils {
+    private static final String BASE62_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+    private static final int BASE62 = 62;
+    
+    /**
+     * 将字节数组转换为 Base62 字符串
+     */
+    public static String bytesToBase62(byte[] data) {
+        if (data == null || data.length == 0) {
+            return "";
+        }
+        
+        StringBuilder sb = new StringBuilder();
+        int idx = 0;
+        
+        while (idx < data.length) {
+            long value = 0;
+            int count = 0;
+            
+            for (int i = 0; i < 7 && idx < data.length; i++, idx++, count++) {
+                value = (value << 8) | (data[idx] & 0xFF);
+            }
+            
+            long[] encoded = encodeBase62(value, count);
+            for (int i = encoded.length - 1; i >= 0; i--) {
+                sb.append(BASE62_CHARS.charAt((int)encoded[i]));
+            }
+        }
+        
+        return sb.toString();
+    }
+    
+    /**
+     * 将 Base62 字符串转换为字节数组
+     */
+    public static byte[] base62ToBytes(String base62String) {
+        if (base62String == null || base62String.isEmpty()) {
+            return new byte[0];
+        }
+        
+        byte[] result = new byte[(base62String.length() * 7 + 7) / 8];
+        int resultIdx = 0;
+        int strIdx = 0;
+        
+        while (strIdx < base62String.length()) {
+            long value = 0;
+            int count = 0;
+            
+            for (int i = 0; i < 11 && strIdx < base62String.length(); i++, strIdx++) {
+                char c = base62String.charAt(strIdx);
+                int digit = decodeBase62Char(c);
+                if (digit == -1) {
+                    throw new IllegalArgumentException("Invalid Base62 character: " + c);
+                }
+                value = value * BASE62 + digit;
+                count++;
+            }
+            
+            int bytesToWrite = (count * 6 + 7) / 8;
+            for (int i = bytesToWrite - 1; i >= 0 && resultIdx < result.length; i--, resultIdx++) {
+                result[resultIdx] = (byte)((value >> (i * 8)) & 0xFF);
+            }
+        }
+        
+        return trimZeroBytes(result);
+    }
+    
+    private static long[] encodeBase62(long value, int byteCount) {
+        int outputSize = (byteCount * 8 + 5) / 6;
+        long[] result = new long[outputSize];
+        int idx = 0;
+        
+        while (value > 0) {
+            result[idx++] = value % BASE62;
+            value /= BASE62;
+        }
+        
+        return java.util.Arrays.copyOf(result, idx);
+    }
+    
+    private static int decodeBase62Char(char c) {
+        if (c >= '0' && c <= '9') return c - '0';
+        if (c >= 'A' && c <= 'Z') return c - 'A' + 10;
+        if (c >= 'a' && c <= 'z') return c - 'a' + 36;
+        return -1;
+    }
+    
+    private static byte[] trimZeroBytes(byte[] data) {
+        int nonZeroCount = 0;
+        for (byte b : data) {
+            if (b != 0) nonZeroCount++;
+        }
+        
+        if (nonZeroCount == data.length) {
+            return data;
+        }
+        
+        byte[] trimmed = new byte[nonZeroCount];
+        int idx = 0;
+        for (byte b : data) {
+            if (b != 0) {
+                trimmed[idx++] = b;
+            }
+        }
+        return trimmed;
+    }
+}

+ 83 - 0
fs-service/src/main/java/com/fs/his/utils/LinkUtil.java

@@ -0,0 +1,83 @@
+package com.fs.his.utils;
+
+import com.fs.common.utils.ParseUtils;
+import org.springframework.util.Base64Utils;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.StandardCharsets;
+
+public class LinkUtil {
+    private static final String KEY = "AESAabCdeREssREA";
+    private static final String TRANSFORMATION = "AES/ECB/PKCS5Padding";
+
+
+    public static String encryptLink(String text) {
+        String encryptedText=null;
+        try {
+            SecretKeySpec secretKey = new SecretKeySpec(KEY.getBytes(), "AES");
+            Cipher cipher = Cipher.getInstance(TRANSFORMATION);
+            cipher.init(Cipher.ENCRYPT_MODE, secretKey);
+            byte[] encryptedBytes = cipher.doFinal(text.getBytes());
+            encryptedText = Base62Utils.bytesToBase62(encryptedBytes);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return encryptedText;
+    }
+
+    public static String decryptLink(String encryptedText) {
+        String text=null;
+        try {
+            SecretKeySpec secretKey = new SecretKeySpec(KEY.getBytes(), "AES");
+            Cipher cipher = Cipher.getInstance(TRANSFORMATION);
+            cipher.init(Cipher.DECRYPT_MODE, secretKey);
+            byte[] decryptedBytes = cipher.doFinal(Base62Utils.base62ToBytes(encryptedText));
+            text = new String(decryptedBytes);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return text;
+    }
+
+    public static String decryptPhoneMk(String encryptedText) {
+        String text=null;
+        try {
+            SecretKeySpec secretKey = new SecretKeySpec(KEY.getBytes(), "AES");
+            Cipher cipher = Cipher.getInstance(TRANSFORMATION);
+            cipher.init(Cipher.DECRYPT_MODE, secretKey);
+            byte[] decryptedBytes = cipher.doFinal(Base62Utils.base62ToBytes(encryptedText));
+            text = new String(decryptedBytes);
+            text =text.replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2");
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return text;
+    }
+
+    public static String decryptAutoPhoneMk(String encryptedText) {
+        String text=null;
+        if (encryptedText!=null&&encryptedText!="") {
+            if (encryptedText.length()>11){
+                try {
+                    SecretKeySpec secretKey = new SecretKeySpec(KEY.getBytes(), "AES");
+                    Cipher cipher = Cipher.getInstance(TRANSFORMATION);
+                    cipher.init(Cipher.DECRYPT_MODE, secretKey);
+                    byte[] decryptedBytes = cipher.doFinal(Base62Utils.base62ToBytes(encryptedText));
+                    text = new String(decryptedBytes);
+                    text =text.replaceAll("(\\d{3})\\d*(\\d{4})", "$1****$2");
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }else {
+                text =  ParseUtils.parsePhone(encryptedText);
+            }
+        }
+
+        return text;
+    }
+
+    public static void main(String[] args) {
+        System.out.println(decryptLink("43fP1nkB0dzuoa8nyqZ45nbgqeXHtgOeOYbfSS53fsCDaR0Y1F4ySPuMzi8UNrY0VBL21XdcWAY232W9UOkjTTL3O"));
+    }
+}

+ 2 - 0
fs-service/src/main/java/com/fs/live/service/ILiveService.java

@@ -222,6 +222,8 @@ public interface ILiveService
 
     R liveDecryptLink(String url);
 
+    R liveDecryptLinkV2(String url);
+
     R getLiveQwUserInfo(Long qwUserId);
 
     List<Live> selectLiveListNew(Live live);

+ 14 - 0
fs-service/src/main/java/com/fs/live/service/impl/LiveServiceImpl.java

@@ -23,6 +23,7 @@ import com.fs.his.domain.FsUser;
 import com.fs.his.domain.FsUserWx;
 import com.fs.his.mapper.FsUserMapper;
 import com.fs.his.mapper.FsUserWxMapper;
+import com.fs.his.utils.LinkUtil;
 import com.fs.his.utils.PhoneUtil;
 import com.fs.hisStore.domain.FsStoreProductScrm;
 import com.fs.hisStore.mapper.FsStoreProductScrmMapper;
@@ -222,6 +223,19 @@ public class LiveServiceImpl implements ILiveService
         return R.ok().put("data", data);
     }
 
+    /**
+     * 解密链接
+     * @param url
+     * @return
+     */
+    @Override
+    public R liveDecryptLinkV2(String url) {
+        String decryptLink = LinkUtil.decryptLink(url);
+        Map<String, Object> data = new HashMap<>();
+        data.put("decryptLink", decryptLink);
+        return R.ok().put("data", data);
+    }
+
     @Override
     public R getLiveQwUserInfo(Long qwUserId) {
         QwUser qwUser = qwUserMapper.selectQwUserById(qwUserId);

+ 1 - 1
fs-service/src/main/java/com/fs/sop/service/impl/QwSopServiceImpl.java

@@ -564,7 +564,7 @@ public class QwSopServiceImpl implements IQwSopService {
                 if (ruleTimeVO.getTempStatus().equals("0")){
                     QwSop qwSop=new QwSop();
                     qwSop.setId(ruleTimeVO.getId());
-                    qwSop.setStatus(0L);
+                    qwSop.setStatus(0L);//模板不运行了,则当前任务也停用
 
                     qwSopMapper.updateQwSop(qwSop);
                     return;

+ 281 - 81
fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java

@@ -99,6 +99,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
     private static final String appLink = "https://jump.ylrztop.com/jumpapp/pages/index/index?link=";
     private static final String registeredRealLink = "/pages_course/register.html?link=";
     private static final String h5LiveShortLink = "/pages_course/livingInvite.html?s=";
+    private static final String appLiveShortLink = "/pages_live/livingList?link=";
     private static final String h5miniappLink = "/pages_course/shortLink.html?s=";
 //    private static final String miniappRealLink = "/pages/index/index?course=";
 
@@ -381,64 +382,64 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
 
         try {
 
-                //用于查询
-                SopUserLogs userLogs=new SopUserLogs();
-                userLogs.setSopId(param.getSopId());
-                userLogs.setQwUserId(param.getQwUserId());
-                userLogs.setCorpId(param.getCorpId());
-                userLogs.setStatus(1);
-                userLogs.setStartTime(param.getParamTime());
-
-                String  unionSopStartId = sopUserLogsService.selectSopUserLogsByUpdate(userLogs);
-
-                 BatchSopUserLogsInfoParamU infoParamU=new BatchSopUserLogsInfoParamU();
-                    infoParamU.setIds(param.getIds());
-                //查询sop任务开始时间营期表 是否有这个营期
-                if (!StringUtil.strIsNullOrEmpty(unionSopStartId)){
-                    infoParamU.setUserLogsId(unionSopStartId);
+            //用于查询
+            SopUserLogs userLogs=new SopUserLogs();
+            userLogs.setSopId(param.getSopId());
+            userLogs.setQwUserId(param.getQwUserId());
+            userLogs.setCorpId(param.getCorpId());
+            userLogs.setStatus(1);
+            userLogs.setStartTime(param.getParamTime());
+
+            String  unionSopStartId = sopUserLogsService.selectSopUserLogsByUpdate(userLogs);
+
+            BatchSopUserLogsInfoParamU infoParamU=new BatchSopUserLogsInfoParamU();
+            infoParamU.setIds(param.getIds());
+            //查询sop任务开始时间营期表 是否有这个营期
+            if (!StringUtil.strIsNullOrEmpty(unionSopStartId)){
+                infoParamU.setUserLogsId(unionSopStartId);
+                if (param.getIds().length>0){
+                    batchUpdateSopUserLogsInfoToTime(infoParamU);
+                }
+
+            }
+            else {
+                //如果查不出来营期 ,则新建营期
+                //查询sop的模板id
+                QwSop qwSop = qwSopService.selectQwSopById(param.getSopId());
+
+                //查询这个sop任务的员工的信息
+                QwUser qwUser = qwUserMapper.selectQwUserByQwUseridAndCorpId(param.getQwUserId(), param.getCorpId());
+
+                SopUserLogsParamByDate userLogsParamByDate = new SopUserLogsParamByDate();
+                userLogsParamByDate.setSopId(param.getSopId());
+                userLogsParamByDate.setSopTempId(qwSop.getTempId());
+                userLogsParamByDate.setQwUserId(param.getQwUserId());
+                userLogsParamByDate.setCorpId(param.getCorpId());
+                userLogsParamByDate.setStartTime(param.getParamTime());
+                userLogsParamByDate.setStatus(1);
+                userLogsParamByDate.setUserId(qwUser.getId()+"|"+qwUser.getCompanyUserId()+"|"+qwUser.getCompanyId());
+
+                //如果没有这个营期没有就先插入
+                int i1 = sopUserLogsService.insertSopUserLogsByDate(userLogsParamByDate);
+                if (i1>0){
+                    userLogs.setSopTempId(qwSop.getTempId());
+                    //获取新的
+                    String  unionSopStartIdNew = sopUserLogsService.selectSopUserLogsByUnionSopId(userLogs);
+                    infoParamU.setUserLogsId(unionSopStartIdNew);
+
                     if (param.getIds().length>0){
                         batchUpdateSopUserLogsInfoToTime(infoParamU);
                     }
 
                 }
-                else {
-                    //如果查不出来营期 ,则新建营期
-                    //查询sop的模板id
-                    QwSop qwSop = qwSopService.selectQwSopById(param.getSopId());
-
-                    //查询这个sop任务的员工的信息
-                    QwUser qwUser = qwUserMapper.selectQwUserByQwUseridAndCorpId(param.getQwUserId(), param.getCorpId());
-
-                    SopUserLogsParamByDate userLogsParamByDate = new SopUserLogsParamByDate();
-                    userLogsParamByDate.setSopId(param.getSopId());
-                    userLogsParamByDate.setSopTempId(qwSop.getTempId());
-                    userLogsParamByDate.setQwUserId(param.getQwUserId());
-                    userLogsParamByDate.setCorpId(param.getCorpId());
-                    userLogsParamByDate.setStartTime(param.getParamTime());
-                    userLogsParamByDate.setStatus(1);
-                    userLogsParamByDate.setUserId(qwUser.getId()+"|"+qwUser.getCompanyUserId()+"|"+qwUser.getCompanyId());
-
-                    //如果没有这个营期没有就先插入
-                    int i1 = sopUserLogsService.insertSopUserLogsByDate(userLogsParamByDate);
-                    if (i1>0){
-                        userLogs.setSopTempId(qwSop.getTempId());
-                        //获取新的
-                        String  unionSopStartIdNew = sopUserLogsService.selectSopUserLogsByUnionSopId(userLogs);
-                        infoParamU.setUserLogsId(unionSopStartIdNew);
-
-                        if (param.getIds().length>0){
-                            batchUpdateSopUserLogsInfoToTime(infoParamU);
-                        }
 
-                    }
 
 
-
-                }
-            } catch (ConstraintViolationException e) {
-                return R.error().put("msg", "修改营期失败:目标营期已经有此客户,请检查客户信息 是否重复,重复请联系超管 删除此营期数据");
-            } catch (Exception e) {
-                return R.error().put("msg", "修改营期失败:" + e.getMessage());
+            }
+        } catch (ConstraintViolationException e) {
+            return R.error().put("msg", "修改营期失败:目标营期已经有此客户,请检查客户信息 是否重复,重复请联系超管 删除此营期数据");
+        } catch (Exception e) {
+            return R.error().put("msg", "修改营期失败:" + e.getMessage());
         }
 
         return R.ok();
@@ -746,7 +747,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
 //                                    miniAppId = String.valueOf(luckyBagConfig.get("appId"));
 
 
-                                   if (!miniMap.isEmpty() && qwUser.getSendMsgType() == 1) {
+                                    if (!miniMap.isEmpty() && qwUser.getSendMsgType() == 1) {
                                         Map<Integer, List<CompanyMiniapp>> integerListMap = miniMap.get(Long.valueOf(companyId));
                                         if (integerListMap != null) {
                                             int listIndexNum = 1;
@@ -856,6 +857,51 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                                     log.error("浏览器看课模板解析失败:" + e);
                                 }
                                 break;
+                            //跳转app看课
+                            case "23":
+                                try {
+                                    //生成看课记录
+                                    addWatchLogIfNeeded(
+                                            param.getSopId(),
+                                            param.getVideoId(), param.getCourseId(),
+                                            Long.valueOf(groupUser.getFsUserId()), qwUserId,
+                                            companyUserId, companyId,
+                                            groupUser.getId(), param.getStartTime(),
+                                            createTime, 2
+                                    );
+                                    //生成看课短链
+                                    String shortH5link = createH5LinkByMiniAppV2(st, param.getCorpId(), createTime, param.getCourseId(), param.getVideoId(),
+                                            String.valueOf(qwUser.getId()), companyUserId, companyId, groupUser.getId(), config);
+                                    st.setMiniprogramPage(shortH5link);
+                                } catch (Exception e) {
+                                    log.error("跳转app看课模板解析失败:" + e);
+                                }
+                                break;
+                            //跳转app直播
+                            case "24":
+                                try{
+                                    sopLogs.setSendType(20);//直播
+                                    qwUserId = String.valueOf(qwUser.getId());
+                                    //生成直播短链
+                                    String shortH5Link = createH5LiveShortLinkV2(st, param.getCorpId(),
+                                            qwUser.getId(), companyUserId, companyId);
+                                    st.setMiniprogramPage(shortH5Link);
+                                    String json0 = configService.selectConfigByKey("his.config");
+                                    FSSysConfig sysConfig0 = JSON.parseObject(json0, FSSysConfig.class);
+                                    //直播观看记录
+                                    createLiveWatchLogAndInsert(
+                                            qwUser.getCompanyId().toString(),
+                                            qwUser.getCompanyUserId().toString(),
+                                            groupUser.getId().toString(),
+                                            Long.valueOf(st.getLiveId()),
+                                            sysConfig0.getAppId(),
+                                            2,
+                                            String.valueOf(qwUser.getId()),
+                                            param.getCorpId());
+                                } catch (Exception e) {
+                                    log.error("跳转app直播模板解析失败:" + e);
+                                }
+                                break;
                             //群公告
                             case "11":
                                 sopLogs.setSendType(21); // 设置为群公告类型
@@ -1043,7 +1089,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
 //                                String luckyjson = configService.selectConfigByKey("luckyBag.config");
 //                                Map<String, Object> luckyBagConfig = JSON.parseObject(luckyjson, Map.class);
 //                                miniAppId = String.valueOf(luckyBagConfig.get("appId"));
-                               if (!miniMap.isEmpty() && qwUser.getSendMsgType() == 1) {
+                                if (!miniMap.isEmpty() && qwUser.getSendMsgType() == 1) {
                                     Map<Integer, List<CompanyMiniapp>> integerListMap = miniMap.get(Long.valueOf(companyId));
                                     if (integerListMap != null) {
                                         int listIndexNum = 1;
@@ -1193,9 +1239,9 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                 sopLogs.setSort(30000000);
                 sopLogs.setSendType(5);
                 sopLogs.setExternalUserName(item.getExternalUserName());
-                   if(sendLiveMsgFinal){
-                       sopLogs.setSendType(20);
-                   }
+                if(sendLiveMsgFinal){
+                    sopLogs.setSendType(20);
+                }
 
                 Long msgNum = Long.valueOf(generateRandomNumberWithLock());
                 sopLogs.setSmsLogsId(msgNum);
@@ -1252,15 +1298,15 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
 //                                    log.warn("生成短链失败,跳过设置 URL。");
 //                                }
 //                            }else {
-                                if ("1".equals(st.getContentType())) {
-                                    String defaultName = "同学";
-                                    if(contact != null && StringUtils.isNotEmpty(contact.getName()) && !"待同步客户".equals(contact.getName())){
-                                        defaultName = contact.getName();
-                                    }
-                                    st.setValue(st.getValue()
-                                            .replaceAll("#销售称呼#",StringUtil.strIsNullOrEmpty(qwUser.getWelcomeText())?"":qwUser.getWelcomeText())
-                                            .replaceAll("#客户称呼#",contact == null || StringUtil.strIsNullOrEmpty(contact.getStageStatus()) || "0".equals(contact.getStageStatus())?defaultName:contact.getStageStatus()));
+                            if ("1".equals(st.getContentType())) {
+                                String defaultName = "同学";
+                                if(contact != null && StringUtils.isNotEmpty(contact.getName()) && !"待同步客户".equals(contact.getName())){
+                                    defaultName = contact.getName();
                                 }
+                                st.setValue(st.getValue()
+                                        .replaceAll("#销售称呼#",StringUtil.strIsNullOrEmpty(qwUser.getWelcomeText())?"":qwUser.getWelcomeText())
+                                        .replaceAll("#客户称呼#",contact == null || StringUtil.strIsNullOrEmpty(contact.getStageStatus()) || "0".equals(contact.getStageStatus())?defaultName:contact.getStageStatus()));
+                            }
 //                            }
 
                             break;
@@ -1344,7 +1390,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                             FSSysConfig sysConfig= JSON.parseObject(js,FSSysConfig.class);
                             //发个人看课记录处理
                             try {
-                                    createLiveWatchLogAndInsert(qwUser.getCompanyId().toString(), qwUser.getCompanyUserId().toString(),item.getExternalId().toString(),Long.valueOf(st.getLiveId()),sysConfig.getAppId(),2, qwUserId,param.getCorpId());
+                                createLiveWatchLogAndInsert(qwUser.getCompanyId().toString(), qwUser.getCompanyUserId().toString(),item.getExternalId().toString(),Long.valueOf(st.getLiveId()),sysConfig.getAppId(),2, qwUserId,param.getCorpId());
 
                             } catch (Exception e) {
                                 log.error("群聊创建直播看课记录失败!", e);
@@ -1600,6 +1646,49 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                                 sopSmsLogsList.add(sopSmsLogs);
                             }
                             break;
+                        //跳转app看课
+                        case "23":
+                            try {
+                                //生成看课记录
+                                addWatchLogIfNeeded(
+                                        item.getSopId(),
+                                        param.getVideoId(), param.getCourseId(),
+                                        item.getFsUserId(), String.valueOf(qwUser.getId()),
+                                        companyUserId, companyId,
+                                        item.getExternalId(), item.getStartTime(),
+                                        createTime, 2);
+                                //生成看课短链
+                                String shortH5link = createH5LinkByMiniAppV2(st, param.getCorpId(), createTime, param.getCourseId(), param.getVideoId(),
+                                        String.valueOf(qwUser.getId()), companyUserId, companyId, item.getExternalId(), config);
+                                st.setMiniprogramPage(shortH5link);
+                            } catch (Exception e) {
+                                log.error("跳转app看课模板解析失败:" + e);
+                            }
+                            break;
+                        //跳转app直播
+                        case "24":
+                            try{
+                                sopLogs.setSendType(20);//直播
+                                //生成直播短链
+                                String shortH5Link = createH5LiveShortLinkV2(st, param.getCorpId(),
+                                        qwUser.getId(), companyUserId, companyId);
+                                st.setMiniprogramPage(shortH5Link);
+                                String json1 = configService.selectConfigByKey("his.config");
+                                FSSysConfig sysConfig0 = JSON.parseObject(json1, FSSysConfig.class);
+                                //直播观看记录
+                                createLiveWatchLogAndInsert(
+                                        qwUser.getCompanyId().toString(),
+                                        qwUser.getCompanyUserId().toString(),
+                                        item.getExternalId().toString(),
+                                        Long.valueOf(st.getLiveId()),
+                                        sysConfig0.getAppId(),
+                                        2,
+                                        String.valueOf(qwUser.getId()),
+                                        param.getCorpId());
+                            } catch (Exception e) {
+                                log.error("跳转app直播模板解析失败:" + e);
+                            }
+                            break;
                         //群公告(仅用于一键群发,个人不应该有群公告)
                         case "11":
                             log.warn("群公告不能发给个人,跳过处理,sopId:{}, externalId:{}", param.getSopId(), item.getExternalId());
@@ -1695,7 +1784,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
         param.setIds(ids);
         log.info("一键群发操作日志1:{}", JSON.toJSONString(param));
         processQwSopLogsBySendMsg(param,param.getDraftStrategy());
-        return null;
+        return R.ok();
     }
 
     @Override
@@ -2003,15 +2092,15 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
 //                            log.warn("生成短链失败,跳过设置 URL。");
 //                        }
 //                    }else {
-                        if ("1".equals(st.getContentType())) {
-                            String defaultName = "同学";
-                            if(contact != null && StringUtils.isNotEmpty(contact.getName()) && !"待同步客户".equals(contact.getName())){
-                                defaultName = contact.getName();
-                            }
-                            st.setValue(st.getValue()
-                                    .replaceAll("#销售称呼#",StringUtil.strIsNullOrEmpty(qwUser.getWelcomeText())?"":qwUser.getWelcomeText())
-                                    .replaceAll("#客户称呼#",contact == null || StringUtil.strIsNullOrEmpty(contact.getStageStatus()) || "0".equals(contact.getStageStatus())?defaultName:contact.getStageStatus()));
+                    if ("1".equals(st.getContentType())) {
+                        String defaultName = "同学";
+                        if(contact != null && StringUtils.isNotEmpty(contact.getName()) && !"待同步客户".equals(contact.getName())){
+                            defaultName = contact.getName();
                         }
+                        st.setValue(st.getValue()
+                                .replaceAll("#销售称呼#",StringUtil.strIsNullOrEmpty(qwUser.getWelcomeText())?"":qwUser.getWelcomeText())
+                                .replaceAll("#客户称呼#",contact == null || StringUtil.strIsNullOrEmpty(contact.getStageStatus()) || "0".equals(contact.getStageStatus())?defaultName:contact.getStageStatus()));
+                    }
 //                    }
 
                     break;
@@ -2359,12 +2448,55 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                         sopSmsLogsList.add(sopSmsLogs);
                     }
                     break;
+                //跳转app看课
+                case "23":
+                    try {
+                        //生成看课记录
+                        addWatchLogIfNeeded(
+                                item.getSopId(),
+                                param.getVideoId(), param.getCourseId(),
+                                item.getFsUserId(), String.valueOf(qwUser.getId()),
+                                companyUserId, companyId,
+                                item.getExternalId(), item.getStartTime(),
+                                dataTime, 2);
+                        //生成看课短链
+                        String shortH5link = createH5LinkByMiniAppV2(st, param.getCorpId(), dataTime, param.getCourseId(), param.getVideoId(),
+                                String.valueOf(qwUser.getId()), companyUserId, companyId, item.getExternalId(), config);
+                        st.setMiniprogramPage(shortH5link);
+                    } catch (Exception e) {
+                        log.error("跳转app看课模板解析失败:" + e);
+                    }
+                    break;
+                //跳转app直播
+                case "24":
+                    try{
+                        sopLogs.setSendType(20);//直播
+                        qwUserId = qwUser.getId();
+                        corpId = param.getCorpId();
+                        //生成直播短链
+                        shortH5Link = createH5LiveShortLinkV2(st, corpId,
+                                qwUserId, companyUserId, companyId);
+                        st.setMiniprogramPage(shortH5Link);
+                        String json0 = configService.selectConfigByKey("his.config");
+                        FSSysConfig sysConfig0 = JSON.parseObject(json0, FSSysConfig.class);
+                        //直播观看记录
+                        createLiveWatchLogAndInsert(
+                                qwUser.getCompanyId().toString(),
+                                qwUser.getCompanyUserId().toString(),
+                                item.getExternalId().toString(),
+                                Long.valueOf(st.getLiveId()),
+                                sysConfig0.getAppId(),
+                                2,
+                                String.valueOf(qwUser.getId()),
+                                param.getCorpId());
+                    } catch (Exception e) {
+                        log.error("跳转app直播模板解析失败:" + e);
+                    }
+                    break;
                 default:
                     break;
-
             }
         }
-
         return  list;
     }
 
@@ -2404,6 +2536,38 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
         return link.getRealLink();
     }
 
+    /**
+     * 创建看课短链
+     * @param setting
+     * @param corpId
+     * @param sendTime
+     * @param courseId
+     * @param videoId
+     * @param qwUserId
+     * @param companyUserId
+     * @param companyId
+     * @param externalId
+     * @param config
+     * @return
+     */
+    String createH5LinkByMiniAppV2(QwSopCourseFinishTempSetting.Setting setting, String corpId, Date sendTime,
+                                 Integer courseId, Integer videoId, String qwUserId,
+                                 String companyUserId, String companyId, Long externalId, CourseConfig config) {
+        FsCourseLink link = createFsCourseLink(corpId, sendTime, courseId, videoId, Long.valueOf(qwUserId),
+                companyUserId, companyId, externalId, 3, null);
+        FsCourseRealLink courseMap = new FsCourseRealLink();
+        BeanUtils.copyProperties(link, courseMap);
+        String courseJson = JSON.toJSONString(courseMap);
+        String realLinkFull = appRealLink + courseJson;
+        link.setRealLink(realLinkFull);
+        Date updateTime = createUpdateTime(setting, sendTime, config);
+        link.setUpdateTime(updateTime);
+        link.setCreateTime(new Date());
+        //存短链-
+        fsCourseLinkMapper.insertFsCourseLink(link);
+        return link.getRealLink();
+    }
+
     /**
      * 创建直播短链
      * @param setting
@@ -2447,6 +2611,42 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
     }
 
 
+    /**
+     * 创建直播短链
+     * @param setting
+     * @param corpId
+     * @param qwUserId
+     * @param companyUserId
+     * @param companyId
+     * @return
+     */
+    private String createH5LiveShortLinkV2(QwSopCourseFinishTempSetting.Setting setting, String corpId, Long qwUserId, String companyUserId, String companyId) {
+        // 手动创建 FsCourseLink 对象,避免使用 BeanUtils.copyProperties
+        FsCourseLink link = new FsCourseLink();
+        link.setCompanyId(Long.parseLong(companyId));
+        link.setQwUserId(Long.valueOf(qwUserId));
+        link.setCompanyUserId(Long.parseLong(companyUserId));
+        link.setLiveId(Long.valueOf(setting.getLiveId()));
+        link.setCorpId(corpId);
+        link.setUNo(UUID.randomUUID().toString());
+        String randomString = generateRandomStringWithLock();
+        if (StringUtil.strIsNullOrEmpty(randomString)) {
+            link.setLink(UUID.randomUUID().toString().replace("-", ""));
+        } else {
+            link.setLink(randomString);
+        }
+        link.setCreateTime(new Date());
+        FsCourseRealLink courseMap = new FsCourseRealLink();
+        BeanUtils.copyProperties(link, courseMap);
+        String courseJson = JSON.toJSONString(link);
+        String realLinkFull = appLiveShortLink + courseJson;
+        link.setRealLink(realLinkFull);
+        //存短链-
+        fsCourseLinkMapper.insertFsCourseLink(link);
+        return link.getRealLink();
+    }
+
+
     public String createActivityLinkByMiniApp(QwSopCourseFinishTempSetting.Setting st, QwSopLogs sopLogs, String corpId, Date sendTime, Integer courseId, Integer videoId, String qwUserId, String companyUserId, String companyId, Long externalId, CourseConfig config, String chatId) {
         FsCourseLink link = createFsCourseLink(corpId, sendTime, courseId, videoId, Long.valueOf(qwUserId),
                 companyUserId, companyId, null, 3, chatId);
@@ -2643,8 +2843,8 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
 
     //插入观看记录
     public void addWatchLogIfNeeded(String sopId, Integer videoId, Integer courseId,
-                                     Long fsUserId, String qwUserId, String companyUserId,
-                                     String companyId, Long externalId, String startTime,Date createTime, Integer watchType) {
+                                    Long fsUserId, String qwUserId, String companyUserId,
+                                    String companyId, Long externalId, String startTime,Date createTime, Integer watchType) {
 
         try {
             FsCourseWatchLog watchLog = new FsCourseWatchLog();
@@ -2702,11 +2902,11 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
     }
 
     public String createLinkByMiniApp(QwSopCourseFinishTempSetting.Setting setting, String corpId, Date sendTime,
-                                     Integer courseId, Integer videoId, Long qwUserId,
-                                     String companyUserId, String companyId, Long externalId,CourseConfig config, String chatId) {
+                                      Integer courseId, Integer videoId, Long qwUserId,
+                                      String companyUserId, String companyId, Long externalId,CourseConfig config, String chatId) {
 
         FsCourseLink link = createFsCourseLink(corpId, sendTime, courseId, videoId, qwUserId,
-                                                companyUserId, companyId, externalId,3,chatId);
+                companyUserId, companyId, externalId,3,chatId);
 
         FsCourseRealLink courseMap = new FsCourseRealLink();
         BeanUtils.copyProperties(link,courseMap);
@@ -2791,7 +2991,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
         byAppVO.setSortLink(sortLink);
         byAppVO.setAppMsgLink(appMsgLink);
 
-            //异步生成app链接记录
+        //异步生成app链接记录
         asyncSopTestService.createFsCourseSopAppLink(link.getLink(),sendTime,updateTime,companyId,companyUserId,String.valueOf(qwUserId),
                 qwUserName,corpId,courseId,setting.getLinkTitle(),setting.getLinkImageUrl(),videoId,
                 setting.getLinkDescribe(),appMsgLink,externalId);

+ 0 - 2
fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsServiceImpl.java

@@ -107,8 +107,6 @@ public class SopUserLogsServiceImpl implements ISopUserLogsService {
     private QwGroupChatUserMapper qwGroupChatUserMapper;
     @Autowired
     private QwGroupChatMapper qwGroupChatMapper;
-
-
     @Autowired
     private ISysConfigService configService;
 

+ 29 - 0
fs-service/src/main/resources/mapper/company/CompanyAiWorkflowExecLogMapper.xml

@@ -122,4 +122,33 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         WHERE workflow_instance_id = #{workflowInstanceId}
         ORDER BY start_time ASC
     </select>
+
+    <insert id="batchInsert" parameterType="java.util.List">
+        INSERT INTO company_ai_workflow_exec_log (
+        workflow_instance_id,
+        node_key,
+        node_name,
+        node_type,
+        input_data,
+        status,
+        output_data,
+        start_time,
+        end_time,
+        is_generate
+        ) VALUES
+        <foreach collection="list" item="item" separator=",">
+            (
+            #{item.workflowInstanceId},
+            #{item.nodeKey},
+            #{item.nodeName},
+            #{item.nodeType},
+            #{item.inputData, jdbcType=OTHER},
+            #{item.status},
+            #{item.outputData},
+            #{item.startTime},
+            #{item.endTime},
+            #{item.isGenerate}
+            )
+        </foreach>
+    </insert>
 </mapper>

+ 29 - 0
fs-service/src/main/resources/mapper/company/CompanyAiWorkflowExecMapper.xml

@@ -193,4 +193,33 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
           and t1.cid_group_no = #{groupNo}
           and NOW() BETWEEN t1.runtime_range_start and t1.runtime_range_end
     </select>
+
+    <insert id="insertBatchInfo" useGeneratedKeys="true" keyProperty="id">
+        INSERT INTO company_ai_workflow_exec (
+        workflow_instance_id, workflow_id, current_node_key,
+        current_node_type, current_node_name, status,
+        start_time, variables, business_key,
+        start_node_key, end_node_key, cid_group_no,
+        runtime_range_start, runtime_range_end,is_generate
+        ) VALUES
+        <foreach collection="list" item="item" separator=",">
+            (
+            #{item.workflowInstanceId},
+            #{item.workflowId},
+            #{item.currentNodeKey},
+            #{item.currentNodeType},
+            #{item.currentNodeName},
+            #{item.status},
+            #{item.startTime},
+            #{item.variables},
+            #{item.businessKey},
+            #{item.startNodeKey},
+            #{item.endNodeKey},
+            #{item.cidGroupNo},
+            #{item.runtimeRangeStart},
+            #{item.runtimeRangeEnd},
+             #{item.isGenerate}
+            )
+        </foreach>
+    </insert>
 </mapper>

+ 19 - 0
fs-service/src/main/resources/mapper/company/CompanyVoiceRoboticBusinessMapper.xml

@@ -149,4 +149,23 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                 inner join company_ai_workflow_exec t2 on t1.id = t2.business_key
         where t1.robotic_id = #{roboticId} and t2.current_node_key != #{endNodeKey} and t2.status != 8
     </select>
+
+    <insert id="insertBatchGenerateInfo" useGeneratedKeys="true" keyProperty="id">
+        INSERT INTO company_voice_robotic_business
+        (robotic_id, callee_id, wx_client_id, add_wx_done, call_phone_done, send_msg_done, create_time, update_time, is_generate)
+        VALUES
+        <foreach collection="list" item="item" index="index" separator=",">
+            (
+            #{item.roboticId},
+            #{item.calleeId},
+            #{item.wxClientId},
+            #{item.addWxDone},
+            #{item.callPhoneDone},
+            #{item.sendMsgDone},
+            #{item.createTime},
+            #{item.updateTime},
+            #{item.isGenerate}
+            )
+        </foreach>
+    </insert>
 </mapper>

+ 11 - 0
fs-service/src/main/resources/mapper/company/CompanyVoiceRoboticCalleesMapper.xml

@@ -201,4 +201,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </if>
 
     </select>
+
+    <insert id="batchInsertGenerateInfo" useGeneratedKeys="true" keyProperty="id">
+        INSERT INTO company_voice_robotic_callees
+        (phone, robotic_id, task_flow, run_task_flow, is_we_com, user_id, user_name, is_generate)
+        VALUES
+        <foreach item="item" collection="list" separator=",">
+            (#{item.phone}, #{item.roboticId}, #{item.taskFlow},
+            #{item.runTaskFlow}, #{item.isWeCom}, #{item.userId},
+            #{item.userName}, #{item.isGenerate})
+        </foreach>
+    </insert>
 </mapper>

+ 11 - 0
fs-service/src/main/resources/mapper/company/CompanyWorkflowEdgeMapper.xml

@@ -72,4 +72,15 @@
     <select id="selectListByWorkflowIdAndNodeKey" resultType="CompanyWorkflowEdge">
         select * from company_ai_workflow_edge where workflow_id = #{workflowId} and source_node_key = #{nodeKey}
     </select>
+
+    <select id="slectNodeInfoByWorkflowId" resultType="com.fs.company.vo.CompanyNodeInfoVo">
+        SELECT
+            e.target_node_key,
+            n.node_name,
+            n.node_type
+        FROM
+            company_ai_workflow_edge e
+                INNER JOIN company_ai_workflow_node n ON e.target_node_key = n.node_key
+        WHERE e.workflow_id = #{workflowId} and e.source_node_key = #{nodeKey} LIMIT 1
+    </select>
 </mapper>

+ 6 - 0
fs-user-app/src/main/java/com/fs/app/controller/CourseController.java

@@ -376,6 +376,12 @@ public class CourseController extends  AppBaseController{
         return courseWatchLogService.decryptLink(param.getUrl());
     }
 
+    @ApiOperation("解密链接参数2.0")
+    @PostMapping("/decryptLink/v2")
+    public R decryptLinkV2(@RequestBody FsCourseEncryptLinkParam param) {
+        return courseWatchLogService.decryptLinkV2(param.getUrl());
+    }
+
 
     @ApiOperation("获取我的sop课程")
     @PostMapping("/getSopCourseH5StudyList")

+ 6 - 0
fs-user-app/src/main/java/com/fs/app/controller/live/LiveController.java

@@ -426,6 +426,12 @@ public class LiveController extends AppBaseController {
 		return liveService.liveDecryptLink(param.getUrl());
 	}
 
+	@ApiOperation("解密链接参数")
+	@PostMapping("/decryptLink/v2")
+	public R decryptLinkV2(@RequestBody FsLiveEncryptLinkParam param) {
+		return liveService.liveDecryptLink(param.getUrl());
+	}
+
 	@ApiOperation("获取微信小程序企微信息")
 	@PostMapping("/getLiveQwUserInfo/{qwUserId}")
 	public R getLiveQwUserInfo(@PathVariable Long qwUserId) {

+ 24 - 0
fs-user-app/src/main/java/com/fs/app/controller/store/CompanyUserScrmController.java

@@ -34,6 +34,7 @@ import com.fs.fastGpt.mapper.FastgptChatVoiceHomoMapper;
 import com.fs.fastgptApi.util.AudioUtils;
 import com.fs.fastgptApi.vo.AudioVO;
 import com.fs.framework.security.SecurityUtils;
+import com.fs.his.utils.PhoneUtil;
 import com.fs.sop.domain.QwSopTempVoice;
 import com.fs.sop.service.IQwSopTempVoiceService;
 import com.fs.system.oss.CloudStorageService;
@@ -57,7 +58,9 @@ import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
@@ -272,6 +275,27 @@ public class CompanyUserScrmController extends AppBaseController {
         return R.ok().put("data",WxaCode);
     }
 
+    /**
+     * 济世百康功能,将companyUserId加密后发送给客户,后续添加绑定使用
+     * @param companyUserId
+     * @return
+     */
+    @ApiOperation("获取销售邀请码")
+    @GetMapping("/getCompanyUserInvitationCode")
+    public R getCompanyUserInvitationCode(@RequestParam("companyUserId")Long companyUserId){
+        if(companyUserId == null){
+            return R.error("请登陆账号后再试!");
+        }
+        CompanyUser companyUser = companyUserMapper.selectCompanyUserByUserId(companyUserId);
+        if (companyUser == null) {
+            return R.error("销售不存在,邀请码不合法");
+        }
+        String InvitationCode = PhoneUtil.encryptPhone(String.valueOf(companyUserId));
+        Map<String, Object> data = new HashMap<>();
+        data.put("InvitationCode", InvitationCode);
+        return R.ok().put("data",data);
+    }
+
 
     @ApiOperation("上传声纹")
     @PostMapping("/addVoicePrintUrl")