Просмотр исходного кода

Merge remote-tracking branch 'origin/master'

yys 2 недель назад
Родитель
Сommit
5dbcded9c3
25 измененных файлов с 715 добавлено и 392 удалено
  1. 3 3
      fs-admin/src/main/java/com/fs/his/task/Task.java
  2. 1 1
      fs-admin/src/main/java/com/fs/quartz/saas/TenantJobDispatcherJob.java
  3. 4 1
      fs-common/src/main/java/com/fs/common/core/domain/model/TenantPrincipal.java
  4. 6 3
      fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactController.java
  5. 2 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwUserController.java
  6. 241 230
      fs-qw-api-msg/src/main/java/com/fs/app/controller/QwMsgController.java
  7. 11 2
      fs-qw-api/pom.xml
  8. 5 1
      fs-service/pom.xml
  9. 14 0
      fs-service/src/main/java/com/fs/fastGpt/domain/FastGptChatConversation.java
  10. 1 1
      fs-service/src/main/java/com/fs/fastGpt/service/AiHookService.java
  11. 95 88
      fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java
  12. 0 4
      fs-service/src/main/java/com/fs/fastgptApi/service/Impl/ChatServiceImpl.java
  13. 1 0
      fs-service/src/main/java/com/fs/qw/param/QwLoginHookParam.java
  14. 53 2
      fs-service/src/main/java/com/fs/qw/service/impl/AsyncChatSopService.java
  15. 58 7
      fs-service/src/main/java/com/fs/qw/service/impl/AsyncSopService.java
  16. 115 27
      fs-service/src/main/java/com/fs/qw/service/impl/AsyncSopTestService.java
  17. 54 2
      fs-service/src/main/java/com/fs/qw/service/impl/AsyncWxSopService.java
  18. 16 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java
  19. 13 6
      fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceImpl.java
  20. 2 2
      fs-service/src/main/java/com/fs/qwApi/config/OpenQwConfig.java
  21. 0 2
      fs-service/src/main/java/com/fs/sop/mapper/QwSopMapper.java
  22. 5 5
      fs-service/src/main/java/com/fs/sop/service/impl/QwSopServiceImpl.java
  23. 5 2
      fs-service/src/main/java/com/fs/statis/service/impl/StatisticsServiceImpl.java
  24. 6 1
      fs-service/src/main/java/com/fs/tenant/service/impl/TenantInfoServiceImpl.java
  25. 4 2
      fs-service/src/main/resources/application-dev.yml

+ 3 - 3
fs-admin/src/main/java/com/fs/his/task/Task.java

@@ -245,7 +245,7 @@ public class Task {
     /**
      * 定时任务,处理ai禁止回复之后的消息
      */
-    public void forbidTimeMsgTask() {
+    /*public void forbidTimeMsgTask() {
         Map<String, Object> cacheMap = redisCache.hGetAll(DELAY_MSG);
         for (String key : cacheMap.keySet()) {
             String value = (String) cacheMap.get(key);
@@ -268,7 +268,7 @@ public class Task {
                     Long sender = jsonObject.getLong("sender");
                     Integer type = jsonObject.getInteger("type");
 
-                    aiHookService.qwHookNotifyAiReply(qwUserId,sender,content,uid,type);
+                    aiHookService.qwHookNotifyAiReply(qwUserId,sender,content,uid,type,tendId);
                     //删除已经处理的缓存
                     redisCache.hDel(DELAY_MSG,key);
                 }
@@ -276,7 +276,7 @@ public class Task {
                 log.error("个人定时消息处理异常,会话id:{},文本:{}",sessionId,value,e);
             }
         }
-    }
+    }*/
 
     /**
      * sop任务token消耗统计

+ 1 - 1
fs-admin/src/main/java/com/fs/quartz/saas/TenantJobDispatcherJob.java

@@ -1,6 +1,7 @@
 package com.fs.quartz.saas;
 
 import com.alibaba.fastjson.JSONObject;
+import com.fs.common.core.domain.model.TenantPrincipal;
 import com.fs.common.enums.DataSourceType;
 import com.fs.common.utils.StringUtils;
 import com.fs.config.saas.ProjectConfig;
@@ -8,7 +9,6 @@ import com.fs.core.config.TenantConfigContext;
 import com.fs.framework.datasource.DynamicDataSourceContextHolder;
 import com.fs.framework.datasource.TenantDataSourceManager;
 import com.fs.quartz.domain.SysJob;
-import com.fs.quartz.domain.TenantPrincipal;
 import com.fs.quartz.mapper.SysJobMapper;
 import com.fs.quartz.util.CronUtils;
 import com.fs.quartz.util.JobInvokeUtil;

+ 4 - 1
fs-quartz/src/main/java/com/fs/quartz/domain/TenantPrincipal.java → fs-common/src/main/java/com/fs/common/core/domain/model/TenantPrincipal.java

@@ -1,8 +1,11 @@
-package com.fs.quartz.domain;
+package com.fs.common.core.domain.model;
 
 import lombok.AllArgsConstructor;
 import lombok.Getter;
 
+/**
+ * 租户身份标识
+ */
 @Getter
 @AllArgsConstructor
 public class TenantPrincipal {

+ 6 - 3
fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactController.java

@@ -688,9 +688,12 @@ public class QwExternalContactController extends BaseController
     public  TableDataInfo getMiniCustomer(QwFsUserParam qwFsUserParam){
         startPage();
         List<QwFsUserVO> list = fsUserService.selectQwFsUserListVO(qwFsUserParam);
-        if (list==null||list.size()==0){
-            qwFsUserParam.setPhone(encryptPhone(qwFsUserParam.getPhone()));
-            list = fsUserService.selectQwFsUserListVO(qwFsUserParam);
+        if (list==null|| list.isEmpty()){
+            String phone = qwFsUserParam.getPhone();
+            if (StringUtils.isNotBlank(phone)){
+                qwFsUserParam.setPhone(encryptPhone(phone));
+                list = fsUserService.selectQwFsUserListVO(qwFsUserParam);
+            }
         }
         if (list != null) {
             for (QwFsUserVO vo : list) {

+ 2 - 0
fs-company/src/main/java/com/fs/company/controller/qw/QwUserController.java

@@ -234,6 +234,8 @@ public class QwUserController extends BaseController
     @PreAuthorize("@ss.hasPermi('qw:user:login')")
     @PostMapping("/loginQwIpad")
     public R loginQwIpad(@RequestBody QwLoginHookParam loginParam){
+        LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
+        loginParam.setTenantId(loginUser.getTenantId());
         return qwUserService.loginQwIpad(loginParam);
     }
 

+ 241 - 230
fs-qw-api-msg/src/main/java/com/fs/app/controller/QwMsgController.java

@@ -9,6 +9,7 @@ import com.fs.common.utils.uuid.IdUtils;
 import com.fs.fastGpt.domain.FastGptRole;
 import com.fs.fastGpt.service.AiHookService;
 import com.fs.fastGpt.service.IFastGptRoleService;
+import com.fs.framework.datasource.TenantDataSourceUtil;
 import com.fs.his.domain.FsStoreOrder;
 import com.fs.his.dto.ExpressInfoDTO;
 import com.fs.his.dto.TracesDTO;
@@ -27,6 +28,7 @@ import com.fs.qw.service.IQwUserVoiceLogService;
 import com.fs.sop.mapper.QwSopLogsMapper;
 import com.fs.sop.mapper.SopUserLogsInfoMapper;
 import com.fs.sop.params.GetQwSopLogsByJsApiParam;
+import com.fs.tenant.service.TenantInfoService;
 import com.fs.voice.utils.StringUtil;
 import com.fs.wxwork.dto.*;
 import com.fs.wxwork.service.WxWorkService;
@@ -34,6 +36,8 @@ import io.swagger.annotations.Api;
 import lombok.extern.slf4j.Slf4j;
 import org.json.JSONObject;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.*;
@@ -50,7 +54,8 @@ import static com.fs.his.utils.PhoneUtil.decryptPhone;
 @Slf4j
 public class QwMsgController {
     @Autowired
-     RedisCache redisCache;
+    @Qualifier("redisTemplate")
+    RedisTemplate<Object, Object> redisForObject;
     @Autowired
     AiHookService aiHookService;
     @Autowired
@@ -80,11 +85,15 @@ public class QwMsgController {
     @Autowired
     private IFsStoreOrderService storeOrderService;
 
+    @Autowired
+    private TenantDataSourceUtil tenantDataSourceUtil;
+
+
     @GetMapping("/sendExpressInfo/{orderId}")
     public R sendExpressInfo(@PathVariable Long orderId){
-        String isSend = redisCache.getCacheObject("fs:express:info:send:" +orderId);
+        Object isSend = redisForObject.opsForValue().get("fs:express:info:send:" +orderId);
         if(isSend == null){
-            redisCache.setCacheObject("fs:express:info:send:" +orderId, "1",2, TimeUnit.MINUTES);
+            redisForObject.opsForValue().set("fs:express:info:send:" +orderId, "1",2, TimeUnit.MINUTES);
             FsStoreOrder order = fsStoreOrderService.selectFsStoreOrderByOrderId(orderId);
             if(order != null && order.getUserId() != null){
                 List<QwExternalContact> qwExternalContact = externalContactService.selectQwExternalContactByFsUserIdAndCompany(order.getUserId(),order.getCompanyUserId());
@@ -195,256 +204,257 @@ public class QwMsgController {
         return expressInfoDTO;
     }
 
-
-    @PostMapping("/callback/{serverId}")
+    @PostMapping("/callback/{serverId}/{tenantId}")
     @ResponseBody
-    public Map<String,String> callback(@RequestBody String json,@PathVariable Long serverId ){
-      //  System.out.println(json);
+    public Map<String,String> callback(@RequestBody String json,@PathVariable Long serverId ,@PathVariable Long tenantId){
+
+        //  System.out.println(json);
         WxWorkMsgResp wxWorkMsgResp = JSON.parseObject(json, WxWorkMsgResp.class);
         Integer type = wxWorkMsgResp.getType();
         HashMap<String, String> map = new HashMap<>();
         map.put("errorcode","0");
         map.put("errmsg","ok");
-        Long id = redisCache.getCacheObject("qrCode:uuid:"+wxWorkMsgResp.getUuid());
-        if (id==null){
-            return map;
-        }
-        switch (type){
-            case 100001:
-                log.info("id:{}, 扫码返回", id);
-                break;
-            case 100002:
-                log.info("id:{}, 取消扫码", id);
-              //  redisCache.setCacheObject("qrCodeUid:"+wxWorkMsgResp.getUuid(),100002,10, TimeUnit.MINUTES);
-                break;
-            case 100003:
-                log.info("id:{}, 确认扫码返回", id);
-
-                break;
-            case 104001:
-                log.info("id:{}, 登录成功", id);
-
-                JSONObject jsonObject = new JSONObject(wxWorkMsgResp.getJson());
-                QwUser qu = qwUserMapper.selectQwUserById(id);
-                Long corpId = jsonObject.getLong("Corpid");
-                log.info("id:{}, 回调中的"+corpId, id);
-                String sCorpId = wxWorkService.getCorpId(wxWorkMsgResp.getUuid(), corpId,serverId);
-                if (sCorpId==null&& sCorpId.isEmpty()){
-                    break;
-                }
-                if (!qu.getCorpId().equals(sCorpId)){
-                    redisCache.setCacheObject("qrCodeUid:"+wxWorkMsgResp.getUuid(),22,10, TimeUnit.MINUTES);
-                    log.info("id:{}, 公司不匹配不给登录", id);
-                    WxWorkGetQrCodeDTO wxWorkGetQrCodeDTO = new WxWorkGetQrCodeDTO();
-                    wxWorkGetQrCodeDTO.setUuid(wxWorkMsgResp.getUuid());
-                    wxWorkService.LoginOut(wxWorkGetQrCodeDTO,serverId);
-                    log.info("id:{}, 调用退出登录", id);
-                    break;
-                }
-                if (!qu.getQwUserId().equals(jsonObject.get("acctid"))){
-                    redisCache.setCacheObject("qrCodeUid:"+wxWorkMsgResp.getUuid(),23,10, TimeUnit.MINUTES);
-                    log.info("id:{}, 账号不匹配不给登录", id);
-                    WxWorkGetQrCodeDTO wxWorkGetQrCodeDTO = new WxWorkGetQrCodeDTO();
-                    wxWorkGetQrCodeDTO.setUuid(wxWorkMsgResp.getUuid());
-                    wxWorkService.LoginOut(wxWorkGetQrCodeDTO,serverId);
-                    log.info("id:{}, 调用退出登录", id);
+        tenantDataSourceUtil.execute(tenantId , () -> {
+            Object o = redisForObject.opsForValue().get("qrCode:uuid:" + wxWorkMsgResp.getUuid());
+            Long id = (Long) o;
+            if (id==null){
+                return;
+            }
+            switch (type){
+                case 100001:
+                    log.info("id:{}, 扫码返回", id);
                     break;
-                }
-                QwUser qwUser = new QwUser();
-                qwUser.setId(id);
-                qwUser.setVid(jsonObject.get("Vid").toString());
-                qwUser.setIpadStatus(1);
-                qwUserMapper.updateQwUser(qwUser);
-                log.info("id:{}, 存Vid", id);
-                redisCache.setCacheObject("qrCodeUid:"+wxWorkMsgResp.getUuid(),104001,10, TimeUnit.MINUTES);
-                break;
-            case 100006:
-
-                break;
-            case 100004:
-                log.info("id:{}, 需要验证二维码消息", id);
-                redisCache.setCacheObject("qrCodeUid:"+wxWorkMsgResp.getUuid(),100004,10, TimeUnit.MINUTES);
-                break;
-            case 100012:
-                log.info("id:{}, 需要二次验证:"+wxWorkMsgResp.getJson(), id);
-
-                redisCache.setCacheObject("qrCodeUid:"+wxWorkMsgResp.getUuid(),100012,10, TimeUnit.MINUTES);
-
-                break;
-            case 100005:
-
-                log.info("id:{}, 手机端结束登录:"+wxWorkMsgResp.getJson(), id);
-                JSONObject jsonObject1 = new JSONObject(wxWorkMsgResp.getJson());
-                qwUserStatus(wxWorkMsgResp.getUuid(),0, jsonObject1.getString("msg"));
-                break;
-            case 100008:
-                QwUser vidUser = qwUserMapper.selectQwUserById(id);
-                if (vidUser.getUid().equals(wxWorkMsgResp.getUuid())){
-                    log.info("id:{}, 当前账号在其他设备登录:"+wxWorkMsgResp.getJson(), id);
-                    JSONObject jsonObject2 = new JSONObject(wxWorkMsgResp.getJson());
-                    qwUserStatus(wxWorkMsgResp.getUuid(),0, jsonObject2.getString("msg"));
-                }
-                log.info("id:{}, 当前账号重新登录:"+wxWorkMsgResp.getJson(), id);
-                break;
-            case 100007:
-                log.info("id:{}, 异常断开:"+wxWorkMsgResp.getJson(), id);
-                JSONObject jsonObject3 = new JSONObject(wxWorkMsgResp.getJson());
-                qwUserStatus(wxWorkMsgResp.getUuid(),0, "异常断开 - " + jsonObject3.getString("msg"));
-                break;
-            case 100009:
-                log.info("id:{}, 二次验证:"+wxWorkMsgResp.getJson(), id);
-                JSONObject jsonObject4 = new JSONObject(wxWorkMsgResp.getJson());
-                qwUserStatus(wxWorkMsgResp.getUuid(),0, "二次验证 - " + jsonObject4.getString("msg"));
-                break;
-            case 102001:
-            case 102002:
-                WxWorkMessageDTO wxWorkMessageDTO1 = JSON.parseObject(wxWorkMsgResp.getJson(), WxWorkMessageDTO.class);
-                handleSopBlockOrDel(id,wxWorkMessageDTO1,wxWorkMsgResp.getUuid(),5);
-                break;
-            case 102000:
-                WxWorkMessageDTO wxWorkMessageDTO = JSON.parseObject(wxWorkMsgResp.getJson(), WxWorkMessageDTO.class);
-                if (wxWorkMessageDTO.getIs_room()!=0){
+                case 100002:
+                    log.info("id:{}, 取消扫码", id);
+                    //  redisCache.setCacheObject("qrCodeUid:"+wxWorkMsgResp.getUuid(),100002,10, TimeUnit.MINUTES);
                     break;
-                }
-                if (wxWorkMessageDTO.getReferid()!=0){
+                case 100003:
+                    log.info("id:{}, 确认扫码返回", id);
+
                     break;
-                }
-                if (wxWorkMessageDTO.getMsgtype()==2||wxWorkMessageDTO.getMsgtype()==0||wxWorkMessageDTO.getMsgtype()==16||wxWorkMessageDTO.getMsgtype() == 101||wxWorkMessageDTO.getMsgtype() == 104){
-
-                    String content = wxWorkMessageDTO.getContent();
-                    log.info("id:{}, 接收人:"+wxWorkMessageDTO.getReceiver(), id);
-                    log.info("id:{}, 发送人:"+wxWorkMessageDTO.getSender(), id);
-                    log.info("id:{}, 内容:"+content, id);
-                    Long receiver = wxWorkMessageDTO.getReceiver();
-                    Long sender = wxWorkMessageDTO.getSender();
-                    if(wxWorkMessageDTO.getMsgtype()==16){
-                        WxwSpeechToTextEntityDTO ste = new WxwSpeechToTextEntityDTO();
-                        ste.setMsgid(wxWorkMessageDTO.getMsg_id());
-                        ste.setUuid(wxWorkMsgResp.getUuid());
-                        WxWorkResponseDTO<WxwSpeechToTextEntityRespDTO> dto = wxWorkService.SpeechToTextEntity(ste, serverId);
-                        System.out.println(dto);
-                        if(dto.getErrcode() != 0 || Objects.isNull(dto.getData()) || StringUtils.isBlank(dto.getData().getText())){
-                            try {
-                                TimeUnit.SECONDS.sleep(2); // 阻塞2秒
-                            } catch (InterruptedException e) {
-                                Thread.currentThread().interrupt(); // 处理中断异常
-                                log.info("id:{}, 第一次语音转换失败", id);
-                            }
-                            dto = wxWorkService.SpeechToTextEntity(ste, serverId);
-                        }
-                        WxwSpeechToTextEntityRespDTO data = dto.getData();
-                        content = data.getText();
-                        if(content == null || content.isEmpty()){
-                            content = "==语音转换失败==";
-                        }
-                        log.info("id:{}, 语音消息"+content, id);
+                case 104001:
+                    log.info("id:{}, 登录成功", id);
+
+                    JSONObject jsonObject = new JSONObject(wxWorkMsgResp.getJson());
+                    QwUser qu = qwUserMapper.selectQwUserById(id);
+                    Long corpId = jsonObject.getLong("Corpid");
+                    log.info("id:{}, 回调中的"+corpId, id);
+                    String sCorpId = wxWorkService.getCorpId(wxWorkMsgResp.getUuid(), corpId,serverId);
+                    if (sCorpId==null&& sCorpId.isEmpty()){
+                        break;
                     }
-                    else if (wxWorkMessageDTO.getMsgtype() == 101){
-                        content = processImageMessage(serverId, wxWorkMessageDTO, wxWorkMsgResp);
-                        log.info("id:{}, 用户发送图片"+content, id);
+                    if (!qu.getCorpId().equals(sCorpId)){
+                        redisForObject.opsForValue().set("qrCodeUid:"+wxWorkMsgResp.getUuid(),22,10, TimeUnit.MINUTES);
+                        log.info("id:{}, 公司不匹配不给登录", id);
+                        WxWorkGetQrCodeDTO wxWorkGetQrCodeDTO = new WxWorkGetQrCodeDTO();
+                        wxWorkGetQrCodeDTO.setUuid(wxWorkMsgResp.getUuid());
+                        wxWorkService.LoginOut(wxWorkGetQrCodeDTO,serverId);
+                        log.info("id:{}, 调用退出登录", id);
+                        break;
                     }
-                    // gif 表情消息
-                    else if (wxWorkMessageDTO.getMsgtype() == 104){
-                        content = wxWorkMessageDTO.getUrl();
-                        log.info("id:{}, 用户发送表情"+content, id);
-                    }//视频号
-
-                    if (2000000000000000L-receiver>0){
-                        log.info("id:{}, 客户发送", id);
-                        aiHookService.qwHookNotifyAiReply(id,sender,content,wxWorkMsgResp.getUuid(),wxWorkMessageDTO.getMsgtype());
-                    }else {
-                        log.info("销售发送");
-                        aiHookService.qwHookNotifyAddMsgNew(id,receiver,content,wxWorkMsgResp.getUuid(),1);
+                    if (!qu.getQwUserId().equals(jsonObject.get("acctid"))){
+                        redisForObject.opsForValue().set("qrCodeUid:"+wxWorkMsgResp.getUuid(),23,10, TimeUnit.MINUTES);
+                        log.info("id:{}, 账号不匹配不给登录", id);
+                        WxWorkGetQrCodeDTO wxWorkGetQrCodeDTO = new WxWorkGetQrCodeDTO();
+                        wxWorkGetQrCodeDTO.setUuid(wxWorkMsgResp.getUuid());
+                        wxWorkService.LoginOut(wxWorkGetQrCodeDTO,serverId);
+                        log.info("id:{}, 调用退出登录", id);
+                        break;
                     }
+                    QwUser qwUser = new QwUser();
+                    qwUser.setId(id);
+                    qwUser.setVid(jsonObject.get("Vid").toString());
+                    qwUser.setIpadStatus(1);
+                    qwUserMapper.updateQwUser(qwUser);
+                    log.info("id:{}, 存Vid", id);
+                    redisForObject.opsForValue().set("qrCodeUid:"+wxWorkMsgResp.getUuid(),104001,10, TimeUnit.MINUTES);
+                    break;
+                case 100006:
 
-                }
-                //视频号
-                if (wxWorkMessageDTO.getMsgtype()==141){
-                    QwUser qwUserByAppKey = qwUserMapper.selectQwUserById(id);
-                    log.info("进入到视频号:{}",qwUserByAppKey);
-
-                    if(qwUserByAppKey.getVideoGetStatus() != null && qwUserByAppKey.getVideoGetStatus() == 1){
-                        QwUserVideo qwUserVideo = qwUserVideoService.selectByObjectId(wxWorkMessageDTO.getObjectId(), qwUserByAppKey.getId());
-                        if(qwUserVideo == null){
-                            QwUserVideo userVideo=new QwUserVideo();
-                            userVideo.setSenderName(wxWorkMessageDTO.getSender_name());
-                            userVideo.setAppKey(qwUserByAppKey.getAppKey());
-                            userVideo.setNickName(wxWorkMessageDTO.getNickname());
-                            userVideo.setObjectId(wxWorkMessageDTO.getObjectId());
-                            userVideo.setCoverUrl(wxWorkMessageDTO.getCover_url());
-                            userVideo.setThumbUrl(wxWorkMessageDTO.getThumb_url());
-                            userVideo.setAvatar(wxWorkMessageDTO.getAvatar());
-                            userVideo.setDesc(wxWorkMessageDTO.getDesc());
-                            userVideo.setUrl(wxWorkMessageDTO.getUrl());
-                            userVideo.setExtras(wxWorkMessageDTO.getExtras());
-                            userVideo.setObjectNonceId(wxWorkMessageDTO.getObjectNonceId());
-                            userVideo.setQwUserId(qwUserByAppKey.getId());
-                            userVideo.setCompanyUserId(qwUserByAppKey.getCompanyUserId());
-                            userVideo.setCompanyId(qwUserByAppKey.getCompanyId());
-                            qwUserVideoService.insertQwUserVideo(userVideo);
-                            log.info("存储完成:userVideo={}",userVideo);
-                        }
+                    break;
+                case 100004:
+                    log.info("id:{}, 需要验证二维码消息", id);
+                    redisForObject.opsForValue().set("qrCodeUid:"+wxWorkMsgResp.getUuid(),100004,10, TimeUnit.MINUTES);
+                    break;
+                case 100012:
+                    log.info("id:{}, 需要二次验证:"+wxWorkMsgResp.getJson(), id);
+
+                    redisForObject.opsForValue().set("qrCodeUid:"+wxWorkMsgResp.getUuid(),100012,10, TimeUnit.MINUTES);
+
+                    break;
+                case 100005:
+
+                    log.info("id:{}, 手机端结束登录:"+wxWorkMsgResp.getJson(), id);
+                    JSONObject jsonObject1 = new JSONObject(wxWorkMsgResp.getJson());
+                    qwUserStatus(wxWorkMsgResp.getUuid(),0, jsonObject1.getString("msg"));
+                    break;
+                case 100008:
+                    QwUser vidUser = qwUserMapper.selectQwUserById(id);
+                    if (vidUser.getUid().equals(wxWorkMsgResp.getUuid())){
+                        log.info("id:{}, 当前账号在其他设备登录:"+wxWorkMsgResp.getJson(), id);
+                        JSONObject jsonObject2 = new JSONObject(wxWorkMsgResp.getJson());
+                        qwUserStatus(wxWorkMsgResp.getUuid(),0, jsonObject2.getString("msg"));
                     }
-                }
-                //语音通话
-                if (wxWorkMessageDTO.getMsgtype()==40){
-                    if (wxWorkMessageDTO.getRecordtype()==null){
+                    log.info("id:{}, 当前账号重新登录:"+wxWorkMsgResp.getJson(), id);
+                    break;
+                case 100007:
+                    log.info("id:{}, 异常断开:"+wxWorkMsgResp.getJson(), id);
+                    JSONObject jsonObject3 = new JSONObject(wxWorkMsgResp.getJson());
+                    qwUserStatus(wxWorkMsgResp.getUuid(),0, "异常断开 - " + jsonObject3.getString("msg"));
+                    break;
+                case 100009:
+                    log.info("id:{}, 二次验证:"+wxWorkMsgResp.getJson(), id);
+                    JSONObject jsonObject4 = new JSONObject(wxWorkMsgResp.getJson());
+                    qwUserStatus(wxWorkMsgResp.getUuid(),0, "二次验证 - " + jsonObject4.getString("msg"));
+                    break;
+                case 102001:
+                case 102002:
+                    WxWorkMessageDTO wxWorkMessageDTO1 = JSON.parseObject(wxWorkMsgResp.getJson(), WxWorkMessageDTO.class);
+                    handleSopBlockOrDel(id,wxWorkMessageDTO1,wxWorkMsgResp.getUuid(),5);
+                    break;
+                case 102000:
+                    WxWorkMessageDTO wxWorkMessageDTO = JSON.parseObject(wxWorkMsgResp.getJson(), WxWorkMessageDTO.class);
+                    if (wxWorkMessageDTO.getIs_room()!=0){
                         break;
                     }
-                    Long receiver = wxWorkMessageDTO.getReceiver();
-                    Long extId=null;
-                    long totalSeconds=0L;
-                    if (2000000000000000L-receiver>0){
-                         extId = wxWorkMessageDTO.getSender();
-                        log.info("id:{}, 客户发起", id);
-                    }else {
-                        log.info("id:{}, 销售发起", id);
-                        extId = wxWorkMessageDTO.getReceiver();
+                    if (wxWorkMessageDTO.getReferid()!=0){
+                        break;
                     }
-                    Integer recordType = wxWorkMessageDTO.getRecordtype();
-                    if (recordType==5){
-                        String recordwording = wxWorkMessageDTO.getRecordwording();
-                        //String recordwording = "通话时长01:20:07";
-                        if (recordwording != null && !recordwording.isEmpty()){
-                            // 同时匹配 "HH:mm:ss" 和 "mm:ss" 格式
-                            Pattern pattern = Pattern.compile("(\\d+):(\\d+)(?::(\\d+))?");
-                            Matcher matcher = pattern.matcher(recordwording);
-                            if (matcher.find()) {
-                                int hours = 0;
-                                int minutes;
-                                int seconds;
-
-                                // 如果有小时部分(匹配到3个组)
-                                if (matcher.group(3) != null) {
-                                    hours = Integer.parseInt(matcher.group(1));
-                                    minutes = Integer.parseInt(matcher.group(2));
-                                    seconds = Integer.parseInt(matcher.group(3));
-                                }
-                                else {// 只有分钟和秒(匹配到2个组)
-                                    minutes = Integer.parseInt(matcher.group(1));
-                                    seconds = Integer.parseInt(matcher.group(2));
+                    if (wxWorkMessageDTO.getMsgtype()==2||wxWorkMessageDTO.getMsgtype()==0||wxWorkMessageDTO.getMsgtype()==16||wxWorkMessageDTO.getMsgtype() == 101||wxWorkMessageDTO.getMsgtype() == 104){
+
+                        String content = wxWorkMessageDTO.getContent();
+                        log.info("id:{}, 接收人:"+wxWorkMessageDTO.getReceiver(), id);
+                        log.info("id:{}, 发送人:"+wxWorkMessageDTO.getSender(), id);
+                        log.info("id:{}, 内容:"+content, id);
+                        Long receiver = wxWorkMessageDTO.getReceiver();
+                        Long sender = wxWorkMessageDTO.getSender();
+                        if(wxWorkMessageDTO.getMsgtype()==16){
+                            WxwSpeechToTextEntityDTO ste = new WxwSpeechToTextEntityDTO();
+                            ste.setMsgid(wxWorkMessageDTO.getMsg_id());
+                            ste.setUuid(wxWorkMsgResp.getUuid());
+                            WxWorkResponseDTO<WxwSpeechToTextEntityRespDTO> dto = wxWorkService.SpeechToTextEntity(ste, serverId);
+                            System.out.println(dto);
+                            if(dto.getErrcode() != 0 || Objects.isNull(dto.getData()) || StringUtils.isBlank(dto.getData().getText())){
+                                try {
+                                    TimeUnit.SECONDS.sleep(2); // 阻塞2秒
+                                } catch (InterruptedException e) {
+                                    Thread.currentThread().interrupt(); // 处理中断异常
+                                    log.info("id:{}, 第一次语音转换失败", id);
                                 }
-                                totalSeconds = hours * 3600L + minutes * 60L + seconds;
-                                log.info("id:{}, 总通话秒数: " + totalSeconds, id);
+                                dto = wxWorkService.SpeechToTextEntity(ste, serverId);
                             }
+                            WxwSpeechToTextEntityRespDTO data = dto.getData();
+                            content = data.getText();
+                            if(content == null || content.isEmpty()){
+                                content = "==语音转换失败==";
+                            }
+                            log.info("id:{}, 语音消息"+content, id);
+                        }
+                        else if (wxWorkMessageDTO.getMsgtype() == 101){
+                            content = processImageMessage(serverId, wxWorkMessageDTO, wxWorkMsgResp);
+                            log.info("id:{}, 用户发送图片"+content, id);
+                        }
+                        // gif 表情消息
+                        else if (wxWorkMessageDTO.getMsgtype() == 104){
+                            content = wxWorkMessageDTO.getUrl();
+                            log.info("id:{}, 用户发送表情"+content, id);
+                        }//视频号
+
+                        final String finalContent = content;
+                        if (2000000000000000L-receiver>0){
+                            log.info("id:{}, 客户发送", id);
+                            aiHookService.qwHookNotifyAiReply(id,sender,finalContent,wxWorkMsgResp.getUuid(),wxWorkMessageDTO.getMsgtype(),tenantId);
+                        }else {
+                            log.info("销售发送");
+                            aiHookService.qwHookNotifyAddMsgNew(id,receiver,content,wxWorkMsgResp.getUuid(),1);
                         }
-                    } else if (recordType==2){
-                        log.info("id:{}, 通话挂断", id);
-                    }else if (recordType==3){
-                        log.info("通话未接听");
-                    }else {
-                        break;
-                    }
-
-                    qwUserVoiceLogService.addQuUserVoiceByIpadCallback(id,extId,recordType,totalSeconds,wxWorkMsgResp.getUuid());
-                }
-
-                break;
 
-        }
+                    }
+                    //视频号
+                    if (wxWorkMessageDTO.getMsgtype()==141){
+                        QwUser qwUserByAppKey = qwUserMapper.selectQwUserById(id);
+                        log.info("进入到视频号:{}",qwUserByAppKey);
+
+                        if(qwUserByAppKey.getVideoGetStatus() != null && qwUserByAppKey.getVideoGetStatus() == 1){
+                            QwUserVideo qwUserVideo = qwUserVideoService.selectByObjectId(wxWorkMessageDTO.getObjectId(), qwUserByAppKey.getId());
+                            if(qwUserVideo == null){
+                                QwUserVideo userVideo=new QwUserVideo();
+                                userVideo.setSenderName(wxWorkMessageDTO.getSender_name());
+                                userVideo.setAppKey(qwUserByAppKey.getAppKey());
+                                userVideo.setNickName(wxWorkMessageDTO.getNickname());
+                                userVideo.setObjectId(wxWorkMessageDTO.getObjectId());
+                                userVideo.setCoverUrl(wxWorkMessageDTO.getCover_url());
+                                userVideo.setThumbUrl(wxWorkMessageDTO.getThumb_url());
+                                userVideo.setAvatar(wxWorkMessageDTO.getAvatar());
+                                userVideo.setDesc(wxWorkMessageDTO.getDesc());
+                                userVideo.setUrl(wxWorkMessageDTO.getUrl());
+                                userVideo.setExtras(wxWorkMessageDTO.getExtras());
+                                userVideo.setObjectNonceId(wxWorkMessageDTO.getObjectNonceId());
+                                userVideo.setQwUserId(qwUserByAppKey.getId());
+                                userVideo.setCompanyUserId(qwUserByAppKey.getCompanyUserId());
+                                userVideo.setCompanyId(qwUserByAppKey.getCompanyId());
+                                qwUserVideoService.insertQwUserVideo(userVideo);
+                                log.info("存储完成:userVideo={}",userVideo);
+                            }
+                        }
+                    }
+                    //语音通话
+                    if (wxWorkMessageDTO.getMsgtype()==40){
+                        if (wxWorkMessageDTO.getRecordtype()==null){
+                            break;
+                        }
+                        Long receiver = wxWorkMessageDTO.getReceiver();
+                        Long extId=null;
+                        long totalSeconds=0L;
+                        if (2000000000000000L-receiver>0){
+                            extId = wxWorkMessageDTO.getSender();
+                            log.info("id:{}, 客户发起", id);
+                        }else {
+                            log.info("id:{}, 销售发起", id);
+                            extId = wxWorkMessageDTO.getReceiver();
+                        }
+                        Integer recordType = wxWorkMessageDTO.getRecordtype();
+                        if (recordType==5){
+                            String recordwording = wxWorkMessageDTO.getRecordwording();
+                            //String recordwording = "通话时长01:20:07";
+                            if (recordwording != null && !recordwording.isEmpty()){
+                                // 同时匹配 "HH:mm:ss" 和 "mm:ss" 格式
+                                Pattern pattern = Pattern.compile("(\\d+):(\\d+)(?::(\\d+))?");
+                                Matcher matcher = pattern.matcher(recordwording);
+                                if (matcher.find()) {
+                                    int hours = 0;
+                                    int minutes;
+                                    int seconds;
+
+                                    // 如果有小时部分(匹配到3个组)
+                                    if (matcher.group(3) != null) {
+                                        hours = Integer.parseInt(matcher.group(1));
+                                        minutes = Integer.parseInt(matcher.group(2));
+                                        seconds = Integer.parseInt(matcher.group(3));
+                                    }
+                                    else {// 只有分钟和秒(匹配到2个组)
+                                        minutes = Integer.parseInt(matcher.group(1));
+                                        seconds = Integer.parseInt(matcher.group(2));
+                                    }
+                                    totalSeconds = hours * 3600L + minutes * 60L + seconds;
+                                    log.info("id:{}, 总通话秒数: " + totalSeconds, id);
+                                }
+                            }
+                        } else if (recordType==2){
+                            log.info("id:{}, 通话挂断", id);
+                        }else if (recordType==3){
+                            log.info("通话未接听");
+                        }else {
+                            break;
+                        }
 
+                        qwUserVoiceLogService.addQuUserVoiceByIpadCallback(id,extId,recordType,totalSeconds,wxWorkMsgResp.getUuid());
+                    }
 
+                    break;
 
+            }
+        });
 
         return map;
     }
@@ -551,7 +561,8 @@ public class QwMsgController {
 
 
     void qwUserStatus(String uid, Integer status, String msg){
-        Long id = redisCache.getCacheObject("qrCode:uuid:"+uid);
+        Object idOld = redisForObject.opsForValue().get("qrCode:uuid:" + uid);
+        Long id = (Long) idOld;
         QwUser qwUser = new QwUser();
         qwUser.setId(id);
         qwUser.setRemark(msg);

+ 11 - 2
fs-qw-api/pom.xml

@@ -105,12 +105,12 @@
 
     <build>
         <plugins>
-            <plugin>
+            <!--<plugin>
                 <groupId>org.springframework.boot</groupId>
                 <artifactId>spring-boot-maven-plugin</artifactId>
                 <version>2.1.1.RELEASE</version>
                 <configuration>
-                    <fork>true</fork> <!-- 如果没有该配置,devtools不会生效 -->
+                    <fork>true</fork> &lt;!&ndash; 如果没有该配置,devtools不会生效 &ndash;&gt;
                 </configuration>
                 <executions>
                     <execution>
@@ -119,6 +119,15 @@
                         </goals>
                     </execution>
                 </executions>
+            </plugin>-->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>${java.version}</source>
+                    <target>${java.version}</target>
+                    <encoding>${project.build.sourceEncoding}</encoding>
+                </configuration>
             </plugin>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>

+ 5 - 1
fs-service/pom.xml

@@ -302,7 +302,11 @@
             <artifactId>spring-retry</artifactId>
             <version>1.3.1</version>
         </dependency>
-
+        <!-- 阿里数据库连接池 -->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>druid-spring-boot-starter</artifactId>
+        </dependency>
 
     </dependencies>
 

+ 14 - 0
fs-service/src/main/java/com/fs/fastGpt/domain/FastGptChatConversation.java

@@ -0,0 +1,14 @@
+package com.fs.fastGpt.domain;
+
+import com.alibaba.fastjson.JSONObject;
+import lombok.Data;
+
+@Data
+public class FastGptChatConversation {
+    private JSONObject userInfo;
+    private JSONObject aiInfo;
+    private JSONObject history;
+    private String isRepository;
+    private String userContent;
+    private String aiContent;
+}

+ 1 - 1
fs-service/src/main/java/com/fs/fastGpt/service/AiHookService.java

@@ -10,7 +10,7 @@ public interface AiHookService {
     R AiRemind();
 
     /** ai回复**/
-    R qwHookNotifyAiReply(Long qwUserID, Long sender,String count,String uid,Integer type);
+    R qwHookNotifyAiReply(Long qwUserID, Long sender,String count,String uid,Integer type,Long tenantId);
 
     /** 转人工 **/
     void artificial(QwHookVO vo);

+ 95 - 88
fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java

@@ -62,10 +62,10 @@ import com.fs.qwHookApi.vo.QwHookMsgVO;
 import com.fs.qwHookApi.vo.QwHookVO;
 import com.fs.sop.domain.QwSopLogs;
 import com.fs.sop.mapper.QwSopLogsMapper;
-import com.fs.utils.SensitiveDataUtils;
 import com.fs.voice.utils.StringUtil;
 import com.fs.wxwork.dto.*;
 import com.fs.wxwork.service.WxWorkService;
+import com.google.gson.Gson;
 import com.vdurmont.emoji.EmojiParser;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.ObjectUtils;
@@ -73,11 +73,17 @@ import org.apache.commons.lang3.StringUtils;
 import org.jetbrains.annotations.Nullable;
 import org.json.JSONObject;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
+import javax.annotation.Resource;
+import javax.sql.DataSource;
 import java.lang.reflect.Field;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
 import java.sql.Time;
 import java.time.DayOfWeek;
 import java.time.LocalDate;
@@ -188,6 +194,14 @@ public class AiHookServiceImpl implements AiHookService {
     private ConfigUtil configUtil;
 
 
+    @Autowired
+    @Qualifier("redisTemplateForObject")
+    RedisTemplate<String, Object> redisForObject;
+
+    @Autowired
+    @Qualifier("redisTemplateForInteger")
+    RedisTemplate<String, Integer> redisForInteger;
+
     /** Ai半小时未回复提醒 **/
     /**
      *
@@ -322,7 +336,7 @@ public class AiHookServiceImpl implements AiHookService {
                                 for (String msg : countList) {
                                     sendAiMsg(msg,sender,uid,serverId);
                                     try {
-                                        Thread.sleep(10000);
+                                        Thread.sleep(500);
                                     } catch (InterruptedException e) {
 
                                     }
@@ -380,11 +394,11 @@ public class AiHookServiceImpl implements AiHookService {
         return R.error();
     }
 
-
+    @Autowired
+    private DataSource dataSource;
     /** Ai回复 **/
-    @Async
     @Override
-    public R qwHookNotifyAiReply(Long qwUserId, Long sender,String qwContent,String uid,Integer type) {
+    public R qwHookNotifyAiReply(Long qwUserId, Long sender,String qwContent,String uid,Integer type,Long tenantId) {
         if (qwContent==null||qwContent.isEmpty()){
             return R.ok();
         }
@@ -456,7 +470,7 @@ public class AiHookServiceImpl implements AiHookService {
             return R.ok();
         }
         // 添加脱敏逻辑
-        if(qwExternalContacts.getType()!=null&&qwExternalContacts.getType()==1){
+        if(qwExternalContacts.getType() == 1){
             FastGptChatSession fastGptChatSession= getFastGptSession(qwExternalContacts,user,dto);
             if(type == 104||type == 101){
                 String imageParse = aiImgUtil.getImageParse(qwContent,user,sender);
@@ -525,11 +539,11 @@ public class AiHookServiceImpl implements AiHookService {
                     jsonObject.put("type",type);
                     String objectString = jsonObject.toString();
 
-                    redisCache.hPut(DELAY_MSG, sessionId, objectString);
+                    redisForObject.opsForHash().put(DELAY_MSG, sessionId, objectString);
 
                     // 4. 确保主Key有8小时过期时间(只在首次设置时生效,避免重复刷新)
-                    if (!redisCache.hasKey(DELAY_MSG)) {
-                        redisCache.expire(DELAY_MSG, 8, TimeUnit.HOURS);
+                    if (Boolean.FALSE.equals(redisForObject.hasKey(DELAY_MSG))) {
+                        redisForObject.expire(DELAY_MSG, 8, TimeUnit.HOURS);
                     }
 
                     return R.ok();
@@ -543,44 +557,35 @@ public class AiHookServiceImpl implements AiHookService {
                 return R.ok();
             }
             //获取是用户是否发送消息
-            Integer reply = redisCache.getCacheObject("reply:" + fastGptChatSession.getSessionId());
+            Integer reply = (Integer) redisForObject.opsForValue().get("reply:" + fastGptChatSession.getSessionId());
             Integer replyI=1;
             //用户正在发送消息 不发
             if (reply!=null&&reply!=0){
                 //更新用户发送消息次数
-                redisCache.setCacheObject("reply:" + fastGptChatSession.getSessionId(),reply+1,5, TimeUnit.MINUTES);
+                redisForObject.opsForValue().set("reply:" + fastGptChatSession.getSessionId(),reply+1,5, TimeUnit.MINUTES);
                 //获取用户之前发送的消息
-                String msg = redisCache.getCacheObject("msg:" + fastGptChatSession.getSessionId());
+                String msg = (String) redisForObject.opsForValue().get("msg:" + fastGptChatSession.getSessionId());
                 if (!msg.isEmpty()){
                     //更新用户发送消息内容
-                    redisCache.setCacheObject("msg:" + fastGptChatSession.getSessionId(),msg+","+contentEmj,5,TimeUnit.MINUTES);
+                    redisForObject.opsForValue().set("msg:" + fastGptChatSession.getSessionId(),msg+","+contentEmj,5,TimeUnit.MINUTES);
                 }
                 //本次跳过
                 log.info("正在对话");
                 return R.ok();
             }
             //用户首次发送消息
-            redisCache.setCacheObject("reply:" + fastGptChatSession.getSessionId(),1,5,TimeUnit.MINUTES);
-            redisCache.setCacheObject("msg:" + fastGptChatSession.getSessionId(),contentEmj,5,TimeUnit.MINUTES);
+            redisForObject.opsForValue().set("reply:" + fastGptChatSession.getSessionId(),1,5,TimeUnit.MINUTES);
+            redisForObject.opsForValue().set("msg:" + fastGptChatSession.getSessionId(),contentEmj,5,TimeUnit.MINUTES);
             log.info("等待");
             R r= sendAiMsg(replyI,fastGptChatSession,role,user,qwExternalContacts.getId(),config.getAPPKey(),qwExternalContacts,sender);
             EventLogUtils.recordEventLog(sender,1L,1,user);
             EventLogUtils.recordEventLog(sender,1L,2,user);
             log.info("数据:{}", r);
             //完成对话 删除消息记录
-            redisCache.deleteObject("reply:" + fastGptChatSession.getSessionId());
-            redisCache.deleteObject("msg:" + fastGptChatSession.getSessionId());
+            redisForObject.delete("reply:" + fastGptChatSession.getSessionId());
+            redisForObject.delete("msg:" + fastGptChatSession.getSessionId());
             if(!r.get("code").equals(200)){
                 //判断消息是否需要重发的依据
-               /*redisCache.setCacheObject("retry:" + fastGptChatSession.getSessionId(),1,2,TimeUnit.MINUTES);
-                redisCache.setCacheObject("retryMsg:" + fastGptChatSession.getSessionId(),contentEmj,2,TimeUnit.MINUTES);
-                Integer retryCount = redisCache.getCacheObject("retry:" + fastGptChatSession.getSessionId());
-                if(retryCount < 3){
-                    r= retrySendAiMsg(retryCount,fastGptChatSession,role,user,qwExternalContacts.getId(),config.getAPPKey(),qwExternalContacts,sender);
-                }
-                redisCache.deleteObject("retry:" + fastGptChatSession.getSessionId());
-                redisCache.deleteObject("retryMsg:" + fastGptChatSession.getSessionId());*/
-
                 log.error("ai报错转人工:"+role.getRoleId()+":"+qwExternalContacts.getName());
                 notifyArtificial(fastGptChatSession.getSessionId(),user,qwExternalContacts.getName()," ai报错转人工",qwExternalContacts.getId(),sender);
                 return R.ok();
@@ -591,7 +596,20 @@ public class AiHookServiceImpl implements AiHookService {
                 return R.ok();
             }
             String contentKh = result.getChoices().get(0).getMessage().getContent();
-            String content = replace(result.getChoices().get(0).getMessage().getContent()).trim();
+            String finalContentKh = null;
+            JSONArray jsonArray = JSON.parseArray(contentKh);
+            for (int i = 0; i < jsonArray.size(); i++) {
+                com.alibaba.fastjson.JSONObject jsonObject = (com.alibaba.fastjson.JSONObject)jsonArray.get(i);
+                com.alibaba.fastjson.JSONObject text = (com.alibaba.fastjson.JSONObject) jsonObject.get("text");
+                String content = text.getString("content");
+                if(content != null && !content.isEmpty()){
+                    finalContentKh = content;
+                }
+            }
+            Gson gson = new Gson();
+            contentKh = finalContentKh;
+            FastGptChatConversation fastGptChatConversation = gson.fromJson(finalContentKh, FastGptChatConversation.class);
+            String content = fastGptChatConversation.getAiContent();
             //计算token
             List<ChatDetailTStreamFResult.ResponseNode> responseData = result.getResponseData();
             int token=0;
@@ -612,11 +630,11 @@ public class AiHookServiceImpl implements AiHookService {
                     return R.ok();
                 }
                 //ai回复文字长度大于500就转人工
-                if(content.length() > 500){
+                /*if(content.length() > 500){
                     log.error("回复长度异常:"+role.getRoleId()+":"+qwExternalContacts.getName());
                     notifyArtificial(fastGptChatSession.getSessionId(),user,qwExternalContacts.getName()," 回复长度异常",qwExternalContacts.getId(),sender);
                     return R.ok();
-                }
+                }*/
                 if (result.isLongText()){
                     //新增用户信息
                     addUserInfo(contentKh, qwExternalContacts.getId(),fastGptChatSession);
@@ -627,14 +645,7 @@ public class AiHookServiceImpl implements AiHookService {
                     }
 
                 }else {
-                    String sa = contentKh.replaceAll("】\n", "】").replaceAll("\n【", "【");
-                    String nr = replace(sa);
-                    String[] split = nr.split("\n");
-                    if (split.length>6){
-                        log.info("回复长度异常:"+role.getRoleId()+":"+qwExternalContacts.getName());
-                        notifyArtificial(fastGptChatSession.getSessionId(),user,qwExternalContacts.getName(),"回复长度异常",qwExternalContacts.getId(),sender);
-                        return R.ok();
-                    }
+
                     List<String> countList = countString(content);
                     //新增用户信息
                     addUserInfo(contentKh, qwExternalContacts.getId(),fastGptChatSession);
@@ -645,11 +656,11 @@ public class AiHookServiceImpl implements AiHookService {
                             sendAiMsg(msg,sender,uid,serverId);
                         }
                         try {
-                            Thread.sleep(10000);
+                            Thread.sleep(500);
                         } catch (InterruptedException e) {
 
                         }
-                        Integer replyH = redisCache.getCacheObject("reply:" + fastGptChatSession.getSessionId());
+                        Integer replyH = (Integer) redisForObject.opsForValue().get("reply:" + fastGptChatSession.getSessionId());
                         //用户正在发送消息 后面的消息不发了
                         if (replyH!=null&&replyH!=0){
                             return R.ok();
@@ -666,7 +677,6 @@ public class AiHookServiceImpl implements AiHookService {
                 notifyArtificial(fastGptChatSession.getSessionId(),user,qwExternalContacts.getName()," ai请求人工协助",qwExternalContacts.getId(),sender);
             }
         }
-
         return R.ok();
     }
 
@@ -678,7 +688,7 @@ public class AiHookServiceImpl implements AiHookService {
      * @return
      */
     private @Nullable R userIsReply(Long sender, String uid, QwUser user) {
-        Long qwExternalContactId = redisCache.getCacheObject(AI_REPLY + sender);
+        Long qwExternalContactId = (Long) redisForObject.opsForValue().get(AI_REPLY + sender);
         if(qwExternalContactId == null){
             WxWorkVid2UserIdDTO wxWorkVid2UserIdDTO = new WxWorkVid2UserIdDTO();
             wxWorkVid2UserIdDTO.setUser_id(Arrays.asList(sender));
@@ -696,8 +706,9 @@ public class AiHookServiceImpl implements AiHookService {
                 log.error("没有外部联系人" + "user:" + user);
                 return R.ok();
             }
-            List<String> oldCache = redisCache.getCacheObject(AI_REPLY_TAG + user.getCorpId());
-            if(oldCache != null && !oldCache.isEmpty()){
+            Object oldCacheObject = redisForObject.opsForValue().get(AI_REPLY_TAG + user.getCorpId());
+            if(oldCacheObject != null){
+                List<String> oldCache = JSON.parseArray(oldCacheObject.toString(), String.class);
                 QwExternalContact qwExternalContact = new QwExternalContact();
                 if(qwExternalContacts.getTagIds() != null && !qwExternalContacts.getTagIds().isEmpty() && !"[]".equals(qwExternalContacts.getTagIds())){
                     List<String> parsedTags = JSON.parseArray(qwExternalContacts.getTagIds(), String.class);
@@ -716,7 +727,7 @@ public class AiHookServiceImpl implements AiHookService {
                 qwExternalContactMapper.updateQwExternalContact(qwExternalContact);
             }
             qwExternalContactMapper.updateQwExternalContactIsRePlyById(qwExternalContacts.getId());
-            redisCache.setCacheObject(AI_REPLY + sender,qwExternalContacts.getId());
+            redisForObject.opsForValue().set(AI_REPLY + sender,qwExternalContacts.getId());
         }
         return R.ok();
     }
@@ -1472,12 +1483,12 @@ public class AiHookServiceImpl implements AiHookService {
     private R  sendAiMsg(Integer i,FastGptChatSession fastGptChatSession, FastGptRole role,QwUser user,Long qwExternalContactsId,String appKey,QwExternalContact qwExternalContacts,Long sender){
         //等待5秒
         try {
-            Thread.sleep(10000); // 5000 毫秒 = 5 秒
+            Thread.sleep(500); // 5000 毫秒 = 5 秒
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         //获取现在的次数
-        Integer reply = (Integer)redisCache.getCacheObject("reply:" + fastGptChatSession.getSessionId());
+        Integer reply = (Integer)redisForObject.opsForValue().get("reply:" + fastGptChatSession.getSessionId());
         if (reply!=i){
             //次数变动 重新等待5秒
             R r = sendAiMsg(reply, fastGptChatSession, role, user, qwExternalContactsId, appKey, qwExternalContacts,sender);
@@ -1495,8 +1506,8 @@ public class AiHookServiceImpl implements AiHookService {
             List<ChatParam.Message> messageList=new ArrayList<ChatParam.Message>();
             param.setMessages(messageList);
             //添加看客记录
-            addCourseWatchLog(qwExternalContactsId);
-            String msgC = (String)redisCache.getCacheObject("msg:" + fastGptChatSession.getSessionId());
+            //addCourseWatchLog(qwExternalContactsId);
+            String msgC = (String)redisForObject.opsForValue().get("msg:" + fastGptChatSession.getSessionId());
 
             if (("今正科技".equals(cloudHostProper.getCompanyName()))) {
                 //处理名称替换
@@ -1510,14 +1521,14 @@ public class AiHookServiceImpl implements AiHookService {
 
             //添加关键词
             addPromptWord(messageList,msgC,qwExternalContactsId,role.getReminderWords(), role.getContactInfo(),fastGptChatSession.getSessionId());
-            R r = chatService.initiatingTakeChat(param, aiHostProper.getAiApi(), appKey);
+            R r = chatService.initiatingTakeChat(param, "http://1.95.196.10:3000/api/", appKey);
             Object data1 = r.get("data");
             if(!(data1 instanceof KnowledgeBaseResult)){
                 ChatDetailTStreamFResult data = (ChatDetailTStreamFResult) r.get("data");
                 EventLogUtils.createEventTokenLog("发起对话",user,sender,data);
             }
 
-            Integer reply2 = (Integer)redisCache.getCacheObject("reply:" + fastGptChatSession.getSessionId());
+            Integer reply2 = (Integer)redisForObject.opsForValue().get("reply:" + fastGptChatSession.getSessionId());
             //次数变动 重新等待5秒
             if (reply2!=i){
                 System.out.println("等待");
@@ -1564,12 +1575,16 @@ public class AiHookServiceImpl implements AiHookService {
     /** 组装发送AI内容 **/
     private void addPromptWord(List<ChatParam.Message> messageList,String count,Long extId,String words,String countInfo,Long sessionId){
 
-        String  str="";
+        FastGptChatConversation conversation = new FastGptChatConversation();
+        conversation.setAiInfo(new com.alibaba.fastjson.JSONObject());
+        conversation.setUserInfo(new com.alibaba.fastjson.JSONObject());
+        conversation.setHistory(new com.alibaba.fastjson.JSONObject());
+
 
         // 这里获取后台的提示词进行匹配
         QwExternalContactInfo info = qwExternalContactInfoMapper.selectQwExternalContactInfoByExternalContactId(extId);
-        if(info==null){
-            info=new QwExternalContactInfo();
+        if(info == null){
+            info = new QwExternalContactInfo();
         }
 
         if (info.getCreateTime()!=null){
@@ -1591,43 +1606,37 @@ public class AiHookServiceImpl implements AiHookService {
             }
         }
 
-        if (info!=null){
-            str="【用户状态信息\n";
-            Field[] fields = info.getClass().getDeclaredFields();
-            for (Field field : fields) {
-                field.setAccessible(true);
-                Excel annotation = field.getAnnotation(Excel.class);
-                if (annotation != null) {
-                    String name = field.getName();
-                    String fieldName = annotation.name();
-                    String[] split = countInfo.split(",");
-                    for (String zName : split) {
-                        if (zName.equals(name)) {
-                            Object value = null;
-                            try {
-                                value = field.get(info);
-                            } catch (IllegalAccessException e) {
-                            }
-                            if (value != null) {
-                                str += fieldName + ": " + value.toString() + "\n";
-                            }else if("交流状态".equals(fieldName) || "学习到的章节".equals(fieldName)){
-                                str += fieldName + ":  \n";
-                            }
+        Field[] fields = info.getClass().getDeclaredFields();
+        for (Field field : fields) {
+            com.alibaba.fastjson.JSONObject userInfo = conversation.getUserInfo();
+            field.setAccessible(true);
+            Excel annotation = field.getAnnotation(Excel.class);
+            if (annotation != null) {
+                String name = field.getName();
+                String fieldName = annotation.name();
+                String[] split = countInfo.split(",");
+                for (String zName : split) {
+                    if (zName.equals(name)) {
+                        Object value = null;
+                        try {
+                            value = field.get(info);
+                        } catch (IllegalAccessException e) {
+                        }
+                        if (value != null) {
+                            userInfo.put(fieldName,value.toString());
+                        }else if("交流状态".equals(fieldName) || "学习到的章节".equals(fieldName)){
+                            userInfo.put(fieldName,"");
                         }
                     }
                 }
             }
         }
-        str+="】\n";
-        if (words!=null&&!"".equals(words)){
-            str+="【你的角色信息:以下内容为你的信息状态的补充而非用户信息,相当于放在角色任务里面,问到了需要知晓,但是如果无关的时候请无视此段内容 "+"\""+words+"\""+"】\n";
-        }
 
         List<FastGptChatMsg> msgs=fastGptChatMsgService.selectFastGptChatMsgByMsgSessionIdAndExtId(sessionId,extId);
         if (!msgs.isEmpty()){
+            com.alibaba.fastjson.JSONObject history = conversation.getHistory();
             Collections.reverse(msgs);
             msgs.remove(msgs.size() - 1);
-            str+="【历史聊天内容:\n";
             for (FastGptChatMsg msg : msgs) {
                 Integer sendType = msg.getSendType();
                 String content = msg.getContent();
@@ -1636,22 +1645,20 @@ public class AiHookServiceImpl implements AiHookService {
                         continue;
                     }
                 }
-
-                str +=(sendType==1?"用户:":"AI:")+content+"\n";
+                history.put(sendType==1?"user":"ai",content);
             }
-            str+="】\n";
         }
 
-        if (count!=null&&!"".equals(count)){
-            str+="【用户说的话内容(之前的内容仅仅为背景,你知道即可,以下才是用户真实说的话的内容)\n" +
-                    "\""+count+"\""+"\n" +
-                    "】";
+        if (count!=null&& !count.isEmpty()){
+            conversation.setUserContent(count);
         }
 
 
         ChatParam.Message message1=new ChatParam.Message();
         message1.setRole("user");
-        message1.setContent(str);
+        Gson gson = new Gson();
+        String jsonStr = gson.toJson(conversation);
+        message1.setContent(jsonStr);
         messageList.add(message1);
     }
     /** 组装表情 **/

+ 0 - 4
fs-service/src/main/java/com/fs/fastgptApi/service/Impl/ChatServiceImpl.java

@@ -47,10 +47,6 @@ public class ChatServiceImpl implements ChatService {
                 if(JSON.parseObject(choices.get(0).toString()).getJSONObject("message").getString("content").contains("FunctionCallBegin")){
                     jsonObject.put("artificial", true);
                 }
-                if(JSON.parseObject(choices.get(0).toString()).getJSONObject("message").getString("content").contains("【长对话】")){
-                    jsonObject.put("longText", true);
-                }
-
 
 
                 // 替换AI回复里面的所有的【】包括里面的字符串

+ 1 - 0
fs-service/src/main/java/com/fs/qw/param/QwLoginHookParam.java

@@ -6,4 +6,5 @@ import lombok.Data;
 public class QwLoginHookParam {
     private Long qwUserId;
     private String code;
+    private Long tenantId;
 }

+ 53 - 2
fs-service/src/main/java/com/fs/qw/service/impl/AsyncChatSopService.java

@@ -1,8 +1,12 @@
 package com.fs.qw.service.impl;
 
+import com.alibaba.fastjson.JSON;
 import com.fs.common.utils.PubFun;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.date.DateUtil;
+import com.fs.common.utils.spring.SpringUtils;
+import com.fs.core.config.TenantConfigContext;
+import com.fs.common.core.domain.model.TenantPrincipal;
 import com.fs.qw.domain.QwGroupChat;
 import com.fs.qw.service.IQwGroupChatService;
 import com.fs.qw.service.IQwUserService;
@@ -14,12 +18,18 @@ import com.fs.sop.domain.SopUserLogs;
 import com.fs.sop.mapper.QwSopMapper;
 import com.fs.sop.mapper.QwSopTempMapper;
 import com.fs.sop.mapper.SopUserLogsMapper;
+import com.fs.system.domain.SysConfig;
+import com.fs.system.mapper.SysConfigMapper;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
+import java.lang.reflect.Method;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
@@ -39,13 +49,42 @@ public class AsyncChatSopService {
     private final SopUserLogsMapper sopUserLogsMapper;
     private final IQwUserService qwUserService;
     private final IQwGroupChatService qwGroupChatService;
+    private final SysConfigMapper sysConfigMapper;
 
     /**
      * 立即执行SOP任务
      */
     @Async("scheduledExecutorService")
-    public void executeChatSopByIds(String[] ids) {
+    public void executeChatSopByIds(String[] ids, Long tenantId) {
+        boolean isSwitched = false;
         try {
+            // 1. 切换到租户数据源并加载配置
+            if (tenantId != null) {
+                try {
+                    Object manager = SpringUtils.getBean("tenantDataSourceManager");
+                    Method method = manager.getClass().getMethod("ensureSwitchByTenantId", Long.class);
+                    method.invoke(manager, tenantId);
+                    isSwitched = true;
+
+                    // 加载租户 projectConfig
+                    SysConfig cfg = sysConfigMapper.selectConfigByConfigKey("projectConfig");
+                    if (cfg != null && StringUtils.isNotBlank(cfg.getConfigValue())) {
+                        TenantConfigContext.set(JSON.parseObject(cfg.getConfigValue()));
+                    }
+
+                    // 设置租户到 SecurityContext
+                    SecurityContextHolder.getContext().setAuthentication(
+                            new UsernamePasswordAuthenticationToken(
+                                    new TenantPrincipal(tenantId),
+                                    null,
+                                    Collections.emptyList()
+                            )
+                    );
+                } catch (Exception e) {
+                    log.error("SOP异步任务切换租户数据源失败: tenantId={}", tenantId, e);
+                }
+            }
+
             List<ChatSopRuleTimeVO> ruleTimeVOList = qwSopMapper.executeSopChatByIds(ids);
             List<QwSopTemp> tempList = qwSopTempMapper.selectListByIds(PubFun.listToNewList(ruleTimeVOList, ChatSopRuleTimeVO::getTempId));
             Map<String, QwSopTemp> tempMap = PubFun.listToMapByGroupObject(tempList, QwSopTemp::getId);
@@ -64,8 +103,20 @@ public class AsyncChatSopService {
             });
             qwSopMapper.updateStatusQwSopById2(PubFun.listToNewList(ruleTimeVOList, ChatSopRuleTimeVO::getId));
         } catch (Exception e) {
-            e.printStackTrace();
             log.error("立即执行执行失败", e);
+        } finally {
+            // 2. 清理租户上下文
+            if (isSwitched) {
+                try {
+                    TenantConfigContext.clear();
+                    SecurityContextHolder.clearContext();
+                    Object manager = SpringUtils.getBean("tenantDataSourceManager");
+                    Method method = manager.getClass().getMethod("clear");
+                    method.invoke(manager);
+                } catch (Exception e) {
+                    log.error("SOP异步任务清理租户数据源失败", e);
+                }
+            }
         }
     }
 

+ 58 - 7
fs-service/src/main/java/com/fs/qw/service/impl/AsyncSopService.java

@@ -1,6 +1,11 @@
 package com.fs.qw.service.impl;
 
+import com.alibaba.fastjson.JSON;
 import com.fs.common.utils.PubFun;
+import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.spring.SpringUtils;
+import com.fs.core.config.TenantConfigContext;
+import com.fs.common.core.domain.model.TenantPrincipal;
 import com.fs.qw.domain.QwUser;
 import com.fs.qw.mapper.QwExternalContactMapper;
 import com.fs.qw.result.QwFilterSopCustomersResult;
@@ -16,15 +21,20 @@ import com.fs.sop.params.SopUserLogsArray;
 import com.fs.sop.params.SopUserLogsList;
 import com.fs.sop.service.IQwSopTempVoiceService;
 import com.fs.sop.service.ISopUserLogsService;
+import com.fs.system.domain.SysConfig;
+import com.fs.system.mapper.SysConfigMapper;
 import com.fs.voice.utils.StringUtil;
 import com.fs.wxUser.domain.CompanyWxUser;
 import com.fs.wxUser.mapper.CompanyWxUserMapper;
 import com.fs.wxUser.param.CompanyWxUserSopParam;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
+import java.lang.reflect.Method;
 import java.text.SimpleDateFormat;
 import java.util.*;
 import java.util.stream.Collectors;
@@ -43,20 +53,48 @@ public class AsyncSopService {
     private final QwExternalContactMapper qwExternalContactMapper;
     private final QwSopTempMapper qwSopTempMapper;
     private final IQwSopTempVoiceService qwSopTempVoiceService;
+    private final SysConfigMapper sysConfigMapper;
 
     /**
      * 立即执行SOP任务
      */
     @Async("scheduledExecutorService")
-    public void executeSopByIds(String[] ids) {
+    public void executeSopByIds(String[] ids, Long tenantId) {
+        boolean isSwitched = false;
+        try {
+            // 1. 切换到租户数据源并加载配置
+            if (tenantId != null) {
+                try {
+                    Object manager = SpringUtils.getBean("tenantDataSourceManager");
+                    Method method = manager.getClass().getMethod("ensureSwitchByTenantId", Long.class);
+                    method.invoke(manager, tenantId);
+                    isSwitched = true;
+
+                    // 加载租户 projectConfig
+                    SysConfig cfg = sysConfigMapper.selectConfigByConfigKey("projectConfig");
+                    if (cfg != null && StringUtils.isNotBlank(cfg.getConfigValue())) {
+                        TenantConfigContext.set(JSON.parseObject(cfg.getConfigValue()));
+                    }
+
+                    // 设置租户到 SecurityContext
+                    SecurityContextHolder.getContext().setAuthentication(
+                            new UsernamePasswordAuthenticationToken(
+                                    new TenantPrincipal(tenantId),
+                                    null,
+                                    Collections.emptyList()
+                            )
+                    );
+                } catch (Exception e) {
+                    log.error("SOP异步任务切换租户数据源失败: tenantId={}", tenantId, e);
+                }
+            }
 
-        List<QwSopRuleTimeVO> ruleTimeVOList = qwSopMapper.executeSopByIds(ids);
-        List<QwSopTemp> tempList = qwSopTempMapper.selectListByIds(PubFun.listToNewList(ruleTimeVOList, QwSopRuleTimeVO::getTempId));
-        Map<String, QwSopTemp> tempMap = PubFun.listToMapByGroupObject(tempList, QwSopTemp::getId);
-        CompanyWxUserSopParam wxUserSopParam=new CompanyWxUserSopParam();
-        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+            List<QwSopRuleTimeVO> ruleTimeVOList = qwSopMapper.executeSopByIds(ids);
+            List<QwSopTemp> tempList = qwSopTempMapper.selectListByIds(PubFun.listToNewList(ruleTimeVOList, QwSopRuleTimeVO::getTempId));
+            Map<String, QwSopTemp> tempMap = PubFun.listToMapByGroupObject(tempList, QwSopTemp::getId);
+            CompanyWxUserSopParam wxUserSopParam=new CompanyWxUserSopParam();
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
 
-        try {
             ruleTimeVOList.forEach(ruleTimeVO->{
                 QwSopTemp qwSopTemp = tempMap.get(ruleTimeVO.getTempId());
                 //如果当前模板停用了,则不运行了
@@ -291,6 +329,19 @@ public class AsyncSopService {
             });
         }catch (Exception e){
             log.error("立即执行执行失败",e);
+        } finally {
+            // 2. 清理租户上下文
+            if (isSwitched) {
+                try {
+                    TenantConfigContext.clear();
+                    SecurityContextHolder.clearContext();
+                    Object manager = SpringUtils.getBean("tenantDataSourceManager");
+                    Method method = manager.getClass().getMethod("clear");
+                    method.invoke(manager);
+                } catch (Exception e) {
+                    log.error("SOP异步任务清理租户数据源失败", e);
+                }
+            }
         }
 
     }

+ 115 - 27
fs-service/src/main/java/com/fs/qw/service/impl/AsyncSopTestService.java

@@ -3,6 +3,7 @@ package com.fs.qw.service.impl;
 import cn.hutool.core.util.StrUtil;
 import com.alibaba.fastjson.JSON;
 import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fs.common.core.domain.model.TenantPrincipal;
 import com.fs.common.utils.PubFun;
 import com.fs.course.domain.FsCourseSopAppLink;
 import com.fs.course.mapper.FsCourseSopAppLinkMapper;
@@ -30,12 +31,18 @@ import com.fs.voice.utils.StringUtil;
 import com.fs.wxUser.param.CompanyWxUserSopParam;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang.StringUtils;
-import org.apache.rocketmq.spring.core.RocketMQTemplate;
+import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.spring.SpringUtils;
+import com.fs.core.config.TenantConfigContext;
+import com.fs.system.domain.SysConfig;
+import com.fs.system.mapper.SysConfigMapper;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
-
+import org.apache.rocketmq.spring.core.RocketMQTemplate;
+import java.lang.reflect.Method;
 import java.text.SimpleDateFormat;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
@@ -62,13 +69,42 @@ public class AsyncSopTestService {
     private final SopUserLogsMapper sopUserLogsMapper;
     private final FsCourseSopAppLinkMapper fsCourseSopAppLinkMapper;
     private final uniPush2Service push2Service;
+    private final SysConfigMapper sysConfigMapper;
 
     /**
      * 立即执行SOP任务
      */
     @Async("scheduledExecutorService")
-    public void executeSopByIds(String[] ids) {
+    public void executeSopByIds(String[] ids, Long tenantId) {
+        boolean isSwitched = false;
         try {
+            // 1. 切换到租户数据源并加载配置
+            if (tenantId != null) {
+                try {
+                    Object manager = SpringUtils.getBean("tenantDataSourceManager");
+                    Method method = manager.getClass().getMethod("ensureSwitchByTenantId", Long.class);
+                    method.invoke(manager, tenantId);
+                    isSwitched = true;
+
+                    // 加载租户 projectConfig
+                    SysConfig cfg = sysConfigMapper.selectConfigByConfigKey("projectConfig");
+                    if (cfg != null && StringUtils.isNotBlank(cfg.getConfigValue())) {
+                        TenantConfigContext.set(JSON.parseObject(cfg.getConfigValue()));
+                    }
+
+                    // 设置租户到 SecurityContext,供 TenantKeyRedisSerializer 自动为 Redis Key 加 tenantid 前缀
+                    SecurityContextHolder.getContext().setAuthentication(
+                            new UsernamePasswordAuthenticationToken(
+                                    new TenantPrincipal(tenantId),
+                                    null,
+                                    Collections.emptyList()
+                            )
+                    );
+                } catch (Exception e) {
+                    log.error("SOP异步任务切换租户数据源失败: tenantId={}", tenantId, e);
+                }
+            }
+
             List<QwSopRuleTimeVO> ruleTimeVOList = qwSopMapper.executeSopByIds(ids);
             List<QwSopTemp> tempList = qwSopTempMapper.selectListByIds(PubFun.listToNewList(ruleTimeVOList, QwSopRuleTimeVO::getTempId));
             Map<String, QwSopTemp> tempMap = PubFun.listToMapByGroupObject(tempList, QwSopTemp::getId);
@@ -80,11 +116,23 @@ public class AsyncSopTestService {
                     updateQwSopStatus(ruleTimeVO.getId(), 0L);
                     return;
                 }
-                processRuleTimeVOInternal(ruleTimeVO, qwSopTemp,sdf);
+                processRuleTimeVOInternal(ruleTimeVO, qwSopTemp, sdf, tenantId);
             });
         } catch (Exception e) {
-            e.printStackTrace();
             log.error("立即执行执行失败", e);
+        } finally {
+            // 2. 清理租户上下文
+            if (isSwitched) {
+                try {
+                    TenantConfigContext.clear();
+                    SecurityContextHolder.clearContext();
+                    Object manager = SpringUtils.getBean("tenantDataSourceManager");
+                    Method method = manager.getClass().getMethod("clear");
+                    method.invoke(manager);
+                } catch (Exception e) {
+                    log.error("SOP异步任务清理租户数据源失败", e);
+                }
+            }
         }
     }
 
@@ -167,28 +215,68 @@ public class AsyncSopTestService {
     /**
      * 判断类型
      */
-    private void processRuleTimeVOInternal(QwSopRuleTimeVO ruleTimeVO, QwSopTemp qwSopTemp,SimpleDateFormat sdf) {
-        QwSop qwSop = createQwSop(ruleTimeVO, 2L);
-
-        switch (ruleTimeVO.getType()) {
-            case 1:
-                processWeChatSop(ruleTimeVO, qwSop,sdf);
-                break;
-            case 2:
-                processEnterpriseWeChatSop(ruleTimeVO, qwSop,sdf);
-                break;
-            default:
-                break;
-        }
+    private void processRuleTimeVOInternal(QwSopRuleTimeVO ruleTimeVO, QwSopTemp qwSopTemp,SimpleDateFormat sdf, Long tenantId) {
+        boolean isSwitched = false;
+        try {
+            if (tenantId != null) {
+                try {
+                    Object manager = SpringUtils.getBean("tenantDataSourceManager");
+                    Method method = manager.getClass().getMethod("ensureSwitchByTenantId", Long.class);
+                    method.invoke(manager, tenantId);
+                    isSwitched = true;
+
+                    // 加载租户 projectConfig
+                    SysConfig cfg = sysConfigMapper.selectConfigByConfigKey("projectConfig");
+                    if (cfg != null && StringUtils.isNotBlank(cfg.getConfigValue())) {
+                        TenantConfigContext.set(JSON.parseObject(cfg.getConfigValue()));
+                    }
+
+                    // 设置租户到 SecurityContext
+                    SecurityContextHolder.getContext().setAuthentication(
+                            new UsernamePasswordAuthenticationToken(
+                                    new TenantPrincipal(tenantId),
+                                    null,
+                                    Collections.emptyList()
+                            )
+                    );
+                } catch (Exception e) {
+                    log.error("processRuleTimeVOInternal 切换租户数据源失败: tenantId={}", tenantId, e);
+                }
+            }
 
-        qwSopMapper.updateQwSop(qwSop);
+            QwSop qwSop = createQwSop(ruleTimeVO, 2L);
+            switch (ruleTimeVO.getType()) {
+                case 1:
+                    processWeChatSop(ruleTimeVO, qwSop, sdf, tenantId);
+                    break;
+                case 2:
+                    processEnterpriseWeChatSop(ruleTimeVO, qwSop, sdf, tenantId);
+                    break;
+                default:
+                    break;
+            }
+
+            qwSopMapper.updateQwSop(qwSop);
+        } finally {
+            if (isSwitched) {
+                try {
+                    TenantConfigContext.clear();
+                    SecurityContextHolder.clearContext();
+                    Object manager = SpringUtils.getBean("tenantDataSourceManager");
+                    Method method = manager.getClass().getMethod("clear");
+                    method.invoke(manager);
+                } catch (Exception e) {
+                    log.error("processRuleTimeVOInternal 清理租户数据源失败", e);
+                }
+            }
+        }
     }
 
 
     /**
      * 个微
      */
-    private void processWeChatSop(QwSopRuleTimeVO ruleTimeVO, QwSop qwSop,SimpleDateFormat sdf) {
+    private void processWeChatSop(QwSopRuleTimeVO ruleTimeVO, QwSop qwSop,SimpleDateFormat sdf, Long tenantId) {
 //        CompanyWxUserSopParam wxUserSopParam = createCompanyWxUserSopParam(ruleTimeVO);
 //        List<CompanyWxUser> companyWxUserList = companyWxUserMapper.selectCompanyWxUserByCompanyUserId(wxUserSopParam);
 //
@@ -214,7 +302,7 @@ public class AsyncSopTestService {
     /**
      * 企微
      */
-    private void processEnterpriseWeChatSop(QwSopRuleTimeVO ruleTimeVO, QwSop qwSop,SimpleDateFormat sdf) {
+    private void processEnterpriseWeChatSop(QwSopRuleTimeVO ruleTimeVO, QwSop qwSop,SimpleDateFormat sdf, Long tenantId) {
         QwSopTagsParam qwSopTagsParam = createQwSopTagsParam(ruleTimeVO);
         List<QwFilterSopCustomersResult> qwFilterSopCustomersResults = qwSopMapper.selectFilterQwSopCustomers(qwSopTagsParam);
 
@@ -225,7 +313,7 @@ public class AsyncSopTestService {
         }
 
         //如果是新客对话类型的,则跳过
-        if (ruleTimeVO.getSendType()==4){
+        if (ruleTimeVO.getSendType() == 4) {
             return;
         }
 
@@ -245,8 +333,8 @@ public class AsyncSopTestService {
             rocketMQTemplate.syncSend("voice-generation", JSON.toJSONString(VoiceVo.builder().type(1).id(ruleTimeVO.getId()).build()));
 //            new Thread(() -> HttpUtils.sendGet("http://118.24.209.192:8009/qw/voice/synchronousSop", "sopId=" + ruleTimeVO.getId())).start();
 //            qwSopTempVoiceService.synchronous(ruleTimeVO.getId(), companyUserIds);
-        }catch (Exception e){
-            log.error("异步同步临时语音失败:",e);
+        } catch (Exception e) {
+            log.error("异步同步临时语音失败:", e);
         }
         groupedResults.forEach((userIdAndQwUserId, externalUserIds) -> {
             String[] keys = userIdAndQwUserId.split("\\|");
@@ -255,8 +343,8 @@ public class AsyncSopTestService {
             String companyUserId = keys[2].trim();
             String companyId = keys[3].trim();
 
-            SopUserLogs sopUserLogs = createSopUserLogs(ruleTimeVO, userId, qwUserId, companyUserId, companyId,sdf);
-            SopUserLogsList userLogsList = createSopUserLogsList(ruleTimeVO, userId, qwUserId, companyUserId, companyId,sdf);
+            SopUserLogs sopUserLogs = createSopUserLogs(ruleTimeVO, userId, qwUserId, companyUserId, companyId, sdf);
+            SopUserLogsList userLogsList = createSopUserLogsList(ruleTimeVO, userId, qwUserId, companyUserId, companyId, sdf);
 
             String unionSopId = sopUserLogsService.selectSopUserLogsByUnionSopId(sopUserLogs);
             if (!StringUtil.strIsNullOrEmpty(unionSopId)) {

+ 54 - 2
fs-service/src/main/java/com/fs/qw/service/impl/AsyncWxSopService.java

@@ -1,7 +1,12 @@
 package com.fs.qw.service.impl;
 
+import com.alibaba.fastjson.JSON;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.fs.common.utils.PubFun;
+import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.spring.SpringUtils;
+import com.fs.core.config.TenantConfigContext;
+import com.fs.common.core.domain.model.TenantPrincipal;
 import com.fs.qw.vo.WxSopRuleTimeVO;
 import com.fs.sop.domain.QwSop;
 import com.fs.sop.domain.QwSopTemp;
@@ -9,13 +14,19 @@ import com.fs.sop.domain.SopUserLogsWx;
 import com.fs.sop.mapper.QwSopMapper;
 import com.fs.sop.mapper.QwSopTempMapper;
 import com.fs.sop.service.ISopUserLogsWxService;
+import com.fs.system.domain.SysConfig;
+import com.fs.system.mapper.SysConfigMapper;
 import com.fs.wxUser.domain.CompanyWxUser;
 import com.fs.wxUser.mapper.CompanyWxUserMapper;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
+import java.lang.reflect.Method;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
@@ -29,13 +40,42 @@ public class AsyncWxSopService {
     private final QwSopTempMapper qwSopTempMapper;
     private final CompanyWxUserMapper companyWxUserMapper;
     private final ISopUserLogsWxService sopUserLogsWxService;
+    private final SysConfigMapper sysConfigMapper;
 
     /**
      * 立即执行SOP任务
      */
     @Async("scheduledExecutorService")
-    public void scheduledWxSopService(String[] ids) {
+    public void scheduledWxSopService(String[] ids, Long tenantId) {
+        boolean isSwitched = false;
         try {
+            // 1. 切换到租户数据源并加载配置
+            if (tenantId != null) {
+                try {
+                    Object manager = SpringUtils.getBean("tenantDataSourceManager");
+                    Method method = manager.getClass().getMethod("ensureSwitchByTenantId", Long.class);
+                    method.invoke(manager, tenantId);
+                    isSwitched = true;
+
+                    // 加载租户 projectConfig
+                    SysConfig cfg = sysConfigMapper.selectConfigByConfigKey("projectConfig");
+                    if (cfg != null && StringUtils.isNotBlank(cfg.getConfigValue())) {
+                        TenantConfigContext.set(JSON.parseObject(cfg.getConfigValue()));
+                    }
+
+                    // 设置租户到 SecurityContext
+                    SecurityContextHolder.getContext().setAuthentication(
+                            new UsernamePasswordAuthenticationToken(
+                                    new TenantPrincipal(tenantId),
+                                    null,
+                                    Collections.emptyList()
+                            )
+                    );
+                } catch (Exception e) {
+                    log.error("SOP异步任务切换租户数据源失败: tenantId={}", tenantId, e);
+                }
+            }
+
             List<WxSopRuleTimeVO> ruleTimeVOList = qwSopMapper.executeSopWxByIds(ids);
             List<QwSopTemp> tempList = qwSopTempMapper.selectListByIds(PubFun.listToNewList(ruleTimeVOList, WxSopRuleTimeVO::getTempId));
             Map<String, QwSopTemp> tempMap = PubFun.listToMapByGroupObject(tempList, QwSopTemp::getId);
@@ -53,8 +93,20 @@ public class AsyncWxSopService {
                 }
             });
         } catch (Exception e) {
-            e.printStackTrace();
             log.error("立即执行执行失败", e);
+        } finally {
+            // 2. 清理租户上下文
+            if (isSwitched) {
+                try {
+                    TenantConfigContext.clear();
+                    SecurityContextHolder.clearContext();
+                    Object manager = SpringUtils.getBean("tenantDataSourceManager");
+                    Method method = manager.getClass().getMethod("clear");
+                    method.invoke(manager);
+                } catch (Exception e) {
+                    log.error("SOP异步任务清理租户数据源失败", e);
+                }
+            }
         }
     }
 

+ 16 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java

@@ -81,8 +81,12 @@ import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import javax.sql.DataSource;
 import java.io.IOException;
 import java.math.BigDecimal;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
 import java.text.ParseException;
 import java.time.*;
 import java.time.format.DateTimeFormatter;
@@ -5642,6 +5646,9 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
         return R.ok();
     }
 
+    @Autowired
+    private DataSource dataSource;
+
     @Override
     public void qwExternalContactSyncAddAndDel() {
         List<QwUser> qwUser = qwUserMapper.getQwUserAllKey();
@@ -5650,6 +5657,15 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
 
 
         for (QwUser user : qwUser) {
+            try {
+                Connection conn = dataSource.getConnection();
+                DatabaseMetaData metaData = conn.getMetaData();
+                String dbName = conn.getCatalog();
+                String url = metaData.getURL();
+                int a = 1;
+            } catch (SQLException e) {
+                throw new RuntimeException(e);
+            }
             syncAddMyQwExternalContact(user.getId());
         }
         long endTime = System.nanoTime();

+ 13 - 6
fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceImpl.java

@@ -58,6 +58,8 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.slf4j.MDC;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.scheduling.annotation.EnableAsync;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -126,6 +128,10 @@ public class QwUserServiceImpl implements IQwUserService
     @Autowired
     IpadSendUtils ipadSendUtils;
 
+    @Autowired
+    @Qualifier("redisTemplate")
+    RedisTemplate<Object, Object> redisForObject;
+
     @Override
     public R getQwIpad(QwLoginHookParam loginParam) {
         QwUser qwUser = qwUserMapper.selectQwUserById(loginParam.getQwUserId());
@@ -1307,12 +1313,13 @@ public class QwUserServiceImpl implements IQwUserService
         qwUserMapper.updateQwUser(u);
         if (data.getIs_login().equals("true")) {
             updateIpadStatus(qwUser.getId(),1);
-            redisCache.setCacheObject("qrCode:uuid:"+data.getUuid(),loginParam.getQwUserId());
+            redisForObject.opsForValue().set("qrCode:uuid:"+data.getUuid(),loginParam.getQwUserId());
             return R.ok("登录成功");
         }
         WxWorkSetCallbackUrlDTO wxWorkSetCallbackUrlDTO = new WxWorkSetCallbackUrlDTO();
-        System.out.println("回调地址"+aiHostProper.getIpadUrl()+"/msg/callback/"+serverId);
-        wxWorkSetCallbackUrlDTO.setUrl(aiHostProper.getIpadUrl()+"/msg/callback/"+serverId);
+
+        System.out.println("回调地址"+"http://saasqwapimsg.ylrzcloud.com/msg/callback/"+serverId + "/"+loginParam.getTenantId());
+        wxWorkSetCallbackUrlDTO.setUrl("http://saasqwapimsg.ylrzcloud.com/msg/callback/"+serverId+ "/"+loginParam.getTenantId());
         wxWorkSetCallbackUrlDTO.setUuid(data.getUuid());
         wxWorkService.SetCallbackUrl(wxWorkSetCallbackUrlDTO,serverId);
 
@@ -1322,9 +1329,9 @@ public class QwUserServiceImpl implements IQwUserService
         WxWorkGetQrCodeRespDTO qrData = qrCode.getData();
 
 
-        redisCache.setCacheObject("qrCode:uuid:"+data.getUuid(),loginParam.getQwUserId());
-        redisCache.setCacheObject("qrCode:qwUserId:"+loginParam.getQwUserId(),qrData.getKey(),10, TimeUnit.MINUTES);
-        redisCache.setCacheObject("qrCodeUid:qwUserId:"+loginParam.getQwUserId(),data.getUuid(),10, TimeUnit.MINUTES);
+        redisForObject.opsForValue().set("qrCode:uuid:"+data.getUuid(),loginParam.getQwUserId());
+        redisForObject.opsForValue().set("qrCode:qwUserId:"+loginParam.getQwUserId(),qrData.getKey(),10, TimeUnit.MINUTES);
+        redisForObject.opsForValue().set("qrCodeUid:qwUserId:"+loginParam.getQwUserId(),data.getUuid(),10, TimeUnit.MINUTES);
         return R.ok().put("qrCode",qrData.getQrcode()).put("qrCode64",qrData.getQrcodeData());
     }
 

+ 2 - 2
fs-service/src/main/java/com/fs/qwApi/config/OpenQwConfig.java

@@ -1,6 +1,6 @@
 package com.fs.qwApi.config;
 
 public interface OpenQwConfig {
-    String baseApi ="http://127.0.0.1:8007/open/qwapi";
-    String api ="http://127.0.0.1:8007";
+    String baseApi ="http://saasqwapi.ylrzcloud.com/open/qwapi";
+    String api ="http://saasqwapi.ylrzcloud.com";
 }

+ 0 - 2
fs-service/src/main/java/com/fs/sop/mapper/QwSopMapper.java

@@ -168,7 +168,6 @@ public interface QwSopMapper extends BaseMapper<QwSop> {
             "</script>")
     List<QwSopListVO> selectSopListVO(@Param("map") QwSopParam qwSopParam);
 
-    @DataSource(DataSourceType.MASTER)
     @Select("SELECT cu.nick_name as userName , qec.* " +
             "FROM " +
             " qw_external_contact qec  left JOIN company_user cu on  qec.company_user_id=cu.user_id " +
@@ -246,7 +245,6 @@ public interface QwSopMapper extends BaseMapper<QwSop> {
 //            "</script>")
 //    public List<QwFilterSopCustomersResult> selectFilterSopCustomers(@Param("map") QwSopTagsParam qwSopTagsParam);
 
-    @DataSource(DataSourceType.MASTER)
     @Select("<script>" +
             "SELECT  qec.* , qu.company_user_id as cuCompanyUserId ,cu.company_id as cuCompanyId  " +
             "FROM " +

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

@@ -7,6 +7,7 @@ import com.fs.common.core.domain.R;
 import com.fs.common.enums.DataSourceType;
 import com.fs.common.exception.base.BaseException;
 import com.fs.common.utils.PubFun;
+import com.fs.common.utils.SecurityUtils;
 import com.fs.common.utils.StringUtils;
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.mapper.CompanyUserMapper;
@@ -790,8 +791,7 @@ public class QwSopServiceImpl implements IQwSopService
 
     @Override
     public R updateStatusQwSopByIds(String[] ids) {
-
-
+        Long tenantId = SecurityUtils.getTenantId();
         List<QwSop> qwSops = qwSopMapper.selectStatusQwSopById(ids);
         List<QwSop> qwSopList = qwSops.stream().filter(e -> e.getType() == 2 && e.getFilterMode() == 1).collect(Collectors.toList());
         List<QwSop> wxSopList = qwSops.stream().filter(e -> e.getType() == 1).collect(Collectors.toList());
@@ -812,7 +812,7 @@ public class QwSopServiceImpl implements IQwSopService
                     .map(QwSop::getId)
                     .collect(Collectors.toList());
 
-            asyncWxSopService.scheduledWxSopService(toBeSent);
+            asyncWxSopService.scheduledWxSopService(toBeSent, tenantId);
 
             if (toBeSent.length > 0) {
                 int i = qwSopMapper.updateStatusQwSopById(toBeSent);
@@ -839,7 +839,7 @@ public class QwSopServiceImpl implements IQwSopService
                     .collect(Collectors.toList());
 
             //异步执行
-            asyncSopTestService.executeSopByIds(toBeSent);
+            asyncSopTestService.executeSopByIds(toBeSent, tenantId);
 
             if (toBeSent.length > 0) {
                 int i = qwSopMapper.updateStatusQwSopById(toBeSent);
@@ -866,7 +866,7 @@ public class QwSopServiceImpl implements IQwSopService
                     .collect(Collectors.toList());
 
             //异步执行
-            asyncChatSopService.executeChatSopByIds(toBeSent);
+            asyncChatSopService.executeChatSopByIds(toBeSent, tenantId);
 
             if (toBeSent.length > 0) {
                 int i = qwSopMapper.updateStatusQwSopById(toBeSent);

+ 5 - 2
fs-service/src/main/java/com/fs/statis/service/impl/StatisticsServiceImpl.java

@@ -1113,9 +1113,12 @@ public class StatisticsServiceImpl implements IStatisticsService {
             watchCourseStatisticsDTOS = JSONObject.parseArray(redisData, WatchCourseStatisticsResultDTO.class);
         }
         String sendType;
-        if (param.getUserType() == 1) {
+        Integer userType = param.getUserType();
+        if (userType == null){
+            return new ArrayList<>();
+        } else if (userType == 1) {
             sendType = "1";
-        } else if (param.getUserType() == 2) {
+        } else if (userType == 2) {
             sendType = "2";
         } else {
             return new ArrayList<>();

+ 6 - 1
fs-service/src/main/java/com/fs/tenant/service/impl/TenantInfoServiceImpl.java

@@ -2,7 +2,10 @@ package com.fs.tenant.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.domain.entity.SysMenu;
 import com.fs.common.enums.DataSourceType;
+import com.fs.common.exception.CustomException;
 import com.fs.common.utils.DateUtils;
 import com.fs.tenant.domain.TenantInfo;
 import com.fs.tenant.mapper.TenantInfoMapper;
@@ -67,7 +70,7 @@ public class TenantInfoServiceImpl extends ServiceImpl<TenantInfoMapper, TenantI
             conn = TenantUtils.getConnection(tenantInfo.getDbIp(),tenantInfo.getDbPort(),"mysql",tenantInfo.getDbAccount(),tenantInfo.getDbPwd());
             // 1. 判断是否存在
             if (TenantUtils.databaseExists(conn, tenantInfo.getDbName())) {
-                throw new RuntimeException("数据库已存在");
+                throw new CustomException("数据库已存在");
             }
 
             // 2. 创建数据库
@@ -83,6 +86,8 @@ public class TenantInfoServiceImpl extends ServiceImpl<TenantInfoMapper, TenantI
             tenantAsyncService.asyncInit(tenantInfo);
             return result;
 
+        } catch (CustomException  e) {
+            throw e;
         } catch (Exception e) {
             try {
                 // 删除租户数据库

+ 4 - 2
fs-service/src/main/resources/application-dev.yml

@@ -7,13 +7,15 @@ spring:
     # redis 配置
     redis:
         # 地址
-        host: localhost
+#        host: localhost
+        host: 192.168.0.245
         # 端口,默认为6379
         port: 6379
         # 数据库索引
         database: 0
         # 密码
-        password:
+#        password:
+        password: Ylrztek250218!3@.
         # 连接超时时间
         timeout: 20s
         lettuce: