Переглянути джерело

企微聊天-sop发送消息保存
企微聊天-撤回消息处理
企微聊天-聊天会话表和聊天记录表优化
fastjson升级2.0.60

Long 1 тиждень тому
батько
коміт
b522b0a658
26 змінених файлів з 632 додано та 162 видалено
  1. 117 1
      fs-ipad-task/src/main/java/com/fs/app/service/IpadSendServer.java
  2. 96 93
      fs-qw-api-msg/src/main/java/com/fs/app/controller/QwMsgController.java
  3. 3 0
      fs-service/src/main/java/com/fs/erp/dto/wdt/ErpWdtApiResponse.java
  4. 3 0
      fs-service/src/main/java/com/fs/erp/dto/wdt/ErpWdtBaseResponseDTO.java
  5. 3 0
      fs-service/src/main/java/com/fs/erp/dto/wdt/ErpWdtStockRespDTO.java
  6. 3 0
      fs-service/src/main/java/com/fs/erp/dto/wdt/ErpWdtTradeQueryResponse.java
  7. 2 7
      fs-service/src/main/java/com/fs/erp/service/impl/WdtErpGoodsServiceImpl.java
  8. 5 17
      fs-service/src/main/java/com/fs/erp/service/impl/WdtErpOrderServiceImpl.java
  9. 13 1
      fs-service/src/main/java/com/fs/fastGpt/service/AiHookService.java
  10. 256 14
      fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java
  11. 1 0
      fs-service/src/main/java/com/fs/ipad/vo/BaseVo.java
  12. 5 0
      fs-service/src/main/java/com/fs/qw/domain/QwMsg.java
  13. 9 0
      fs-service/src/main/java/com/fs/qw/domain/QwSession.java
  14. 1 0
      fs-service/src/main/java/com/fs/qw/enums/MsgType.java
  15. 5 2
      fs-service/src/main/java/com/fs/qw/mapper/QwMsgMapper.java
  16. 3 1
      fs-service/src/main/java/com/fs/qw/param/QwMsgSendParam.java
  17. 58 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwMsgServiceImpl.java
  18. 0 1
      fs-service/src/main/java/com/fs/qw/vo/QwMessageListVO.java
  19. 1 0
      fs-service/src/main/java/com/fs/wxwork/dto/WxWorkMessageDTO.java
  20. 6 1
      fs-service/src/main/java/com/fs/wxwork/dto/WxWorkSendAppMsgRespDTO.java
  21. 5 2
      fs-service/src/main/java/com/fs/wxwork/dto/WxWorkSendTextMsgRespDTO.java
  22. 2 6
      fs-service/src/main/resources/mapper/qw/QwExternalContactMapper.xml
  23. 2 6
      fs-service/src/main/resources/mapper/qw/QwGroupChatMapper.xml
  24. 11 1
      fs-service/src/main/resources/mapper/qw/QwMsgMapper.xml
  25. 21 8
      fs-service/src/main/resources/mapper/qw/QwSessionMapper.xml
  26. 1 1
      pom.xml

+ 117 - 1
fs-ipad-task/src/main/java/com/fs/app/service/IpadSendServer.java

@@ -6,11 +6,14 @@ import com.fs.common.core.redis.RedisCache;
 import com.fs.common.exception.base.BaseException;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.date.DateUtil;
+import com.fs.common.utils.http.HttpUtils;
 import com.fs.company.domain.CompanyMiniapp;
 import com.fs.company.service.ICompanyMiniappService;
+import com.fs.config.ai.AiHostProper;
 import com.fs.course.domain.FsCoursePlaySourceConfig;
 import com.fs.course.domain.FsCourseWatchLog;
 import com.fs.course.service.IFsCourseWatchLogService;
+import com.fs.fastGpt.service.AiHookService;
 import com.fs.ipad.IpadSendUtils;
 import com.fs.ipad.vo.*;
 import com.fs.qw.domain.QwUser;
@@ -19,19 +22,21 @@ import com.fs.qw.mapper.QwExternalContactMapper;
 import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.service.IQwUserService;
 import com.fs.qw.service.IQwUserVideoService;
+import com.fs.qw.vo.QwMessageListVO;
 import com.fs.qw.vo.QwSopCourseFinishTempSetting;
-import com.fs.qwApi.param.QwExternalContactHParam;
 import com.fs.sop.domain.QwSopLogs;
 import com.fs.sop.service.IQwSopLogsService;
 import com.fs.sop.service.impl.QwSopLogsServiceImpl;
 import com.fs.wxwork.dto.*;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.json.JSONObject;
 import org.springframework.stereotype.Service;
 
 import java.time.LocalDateTime;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.*;
 
 @Slf4j
 @Service
@@ -47,6 +52,23 @@ public class IpadSendServer {
     private final IQwUserVideoService qwUserVideoService;
     private final RedisCache redisCache;
     private final ICompanyMiniappService companyMiniappService;
+    private final AiHookService aiHookService;
+    private final AiHostProper aiHostProper;
+    private final ExecutorService executor = new ThreadPoolExecutor(
+            8, 32, 60L, TimeUnit.SECONDS,
+            new LinkedBlockingQueue<>(1000),
+            new ThreadFactory() {
+                private final ThreadFactory defaultFactory = Executors.defaultThreadFactory();
+                private int count = 1;
+                @Override
+                public Thread newThread(Runnable r) {
+                    Thread thread = defaultFactory.newThread(r);
+                    thread.setName("sop-im-exec-" + count++);
+                    return thread;
+                }
+            },
+            new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
+    );
 
     private void sendMiniProgram(BaseVo vo, QwSopCourseFinishTempSetting.Setting content, Map<String, FsCoursePlaySourceConfig> miniMap, Long companyId) {
         String appid = content.getMiniprogramAppid();
@@ -83,6 +105,35 @@ public class IpadSendServer {
             content.setSendStatus(2);
             content.setSendRemarks("发送失败:" + resp.getErrmsg());
         }
+
+        try {
+            WxWorkSendAppMsgRespDTO data = resp.getData();
+
+            JSONObject json = new JSONObject();
+            json.put("appid", courseMaConfig.getAppid());
+            json.put("appName", courseMaConfig.getName());
+            json.put("weappIconUrl", courseMaConfig.getImg());
+            json.put("desc", content.getMiniprogramTitle());
+            json.put("pagepath", content.getMiniprogramPage());
+            json.put("title",courseMaConfig.getName());
+            json.put("thumbnail", content.getMiniprogramPicUrl());
+
+            // 保存聊天消息
+            QwMessageListVO message = aiHookService.saveQwMsg(vo.getQwUserId(), vo.getExId(), json.toString(), 2, vo.isRoom(), data.getMsg_id(), data.getApp_info());
+            if (message == null) {
+                return;
+            }
+            // 发送webSocket
+            executor.execute(() -> {
+                try {
+                    HttpUtils.doPost(aiHostProper.getIpadUrl() + "/msg/sendQwImMsg", JSON.toJSONString(message));
+                } catch (Exception e) {
+                    log.error("转发sop发送消息失败 err: {}", e.getMessage(), e);
+                }
+            });
+        } catch (Exception e) {
+            log.error("保存sop发送消息失败 err: {}", e.getMessage(), e);
+        }
     }
 
     private void sendFile(BaseVo vo, QwSopCourseFinishTempSetting.Setting content) {
@@ -107,6 +158,26 @@ public class IpadSendServer {
             content.setSendStatus(2);
             content.setSendRemarks("发送失败:" + resp.getErrmsg());
         }
+
+        try {
+            WxWorkSendTextMsgRespDTO data = resp.getData();
+
+            // 保存聊天消息
+            QwMessageListVO message = aiHookService.saveQwMsg(vo.getQwUserId(), vo.getExId(), content.getValue(), 1, vo.isRoom(), data.getMsg_id(), data.getApp_info());
+            if (message == null) {
+                return;
+            }
+            // 发送webSocket
+            executor.execute(() -> {
+                try {
+                    HttpUtils.doPost(aiHostProper.getIpadUrl() + "/msg/sendQwImMsg", JSON.toJSONString(message));
+                } catch (Exception e) {
+                    log.error("转发sop发送消息失败 err: {}", e.getMessage(), e);
+                }
+            });
+        } catch (Exception e) {
+            log.error("保存sop发送消息失败 err: {}", e.getMessage(), e);
+        }
     }
 
     public void sendImg(BaseVo vo, QwSopCourseFinishTempSetting.Setting content) {
@@ -118,6 +189,26 @@ public class IpadSendServer {
             content.setSendStatus(2);
             content.setSendRemarks("发送失败:" + resp.getErrmsg());
         }
+
+        try {
+            WxwSendCDNImgMsgRespDTO data = resp.getData();
+
+            // 保存聊天消息
+            QwMessageListVO message = aiHookService.saveQwMsg(vo.getQwUserId(), vo.getExId(), content.getImgUrl(), 2, vo.isRoom(), data.getMsg_id(), data.getApp_info());
+            if (message == null) {
+                return;
+            }
+            // 发送webSocket
+            executor.execute(() -> {
+                try {
+                    HttpUtils.doPost(aiHostProper.getIpadUrl() + "/msg/sendQwImMsg", JSON.toJSONString(message));
+                } catch (Exception e) {
+                    log.error("转发sop发送消息失败 err: {}", e.getMessage(), e);
+                }
+            });
+        } catch (Exception e) {
+            log.error("保存sop发送消息失败 err: {}", e.getMessage(), e);
+        }
     }
 
     public void sendLink(BaseVo vo, QwSopCourseFinishTempSetting.Setting content) {
@@ -189,6 +280,30 @@ public class IpadSendServer {
             content.setSendStatus(2);
             content.setSendRemarks("发送失败:" + resp.getErrmsg());
         }
+
+        try {
+            WxwSendCDNVoiceMsgRespDTO data = resp.getData();
+
+            // 保存聊天消息
+            JSONObject json = new JSONObject();
+            json.put("url", content.getVoiceUrl());
+            json.put("content", content.getValue());
+
+            QwMessageListVO message = aiHookService.saveQwMsg(vo.getQwUserId(), vo.getExId(), json.toString(), 4, vo.isRoom(), data.getMsg_id(), data.getApp_info());
+            if (message == null) {
+                return;
+            }
+            // 发送webSocket
+            executor.execute(() -> {
+                try {
+                    HttpUtils.doPost(aiHostProper.getIpadUrl() + "/msg/sendQwImMsg", JSON.toJSONString(message));
+                } catch (Exception e) {
+                    log.error("转发sop发送消息失败 err: {}", e.getMessage(), e);
+                }
+            });
+        } catch (Exception e) {
+            log.error("保存sop发送消息失败 err: {}", e.getMessage(), e);
+        }
     }
 
     public boolean isSend(QwUser qwUser, BaseVo parentVo) {
@@ -341,6 +456,7 @@ public class IpadSendServer {
         vo.setServerId(qwUser.getServerId());
         vo.setCorpCode(parentVo.getCorpCode());
         vo.setCorpId(parentVo.getCorpId());
+        vo.setQwUserId(qwUser.getId());
         try {
             content.setSendStatus(1);
             switch (content.getContentType()) {

+ 96 - 93
fs-qw-api-msg/src/main/java/com/fs/app/controller/QwMsgController.java

@@ -37,9 +37,11 @@ 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.web.bind.annotation.*;
 
 import java.util.*;
+import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -82,6 +84,9 @@ public class QwMsgController {
     private IFsExpressService expressService;
     @Autowired
     private IFsStoreOrderService storeOrderService;
+    @Autowired
+    @Qualifier("threadPoolTaskExecutor")
+    private Executor executor;
 
     @GetMapping("/sendExpressInfo/{orderId}")
     public R sendExpressInfo(@PathVariable Long orderId){
@@ -310,29 +315,21 @@ public class QwMsgController {
                 if (wxWorkMessageDTO.getReferid()!=0){
                     break;
                 }
+
+                try {
+                    executor.execute(() -> processImMessage(wxWorkMsgResp, wxWorkMessageDTO, id, serverId));
+                } catch (Exception e) {
+                    log.error("接收处理群消息失败 info: {} err: {}", json, e.getMessage(), e);
+                }
+
                 if (wxWorkMessageDTO.getIs_room()!=0){
-                    try {
-                        // 接收处理群消息
-                        processRoomMessage(wxWorkMsgResp, wxWorkMessageDTO, id, serverId);
-                    } catch (Exception e) {
-                        log.error("接收处理群消息失败 info: {} err: {}", json, e.getMessage(), e);
-                    }
                     break;
                 }
 
                 Long receiver = wxWorkMessageDTO.getReceiver();
                 Long sender = wxWorkMessageDTO.getSender();
 
-                // 1客户 2销售
-                int sendType = 2;
-
-                // 消息发送者用户ID
-                Long userId = receiver;
-                if (2000000000000000L - receiver > 0){
-                    sendType = 1;
-                    userId = sender;
-                }
-                if (wxWorkMessageDTO.getMsgtype()==2||wxWorkMessageDTO.getMsgtype()==0||wxWorkMessageDTO.getMsgtype()==16||wxWorkMessageDTO.getMsgtype() == 101||wxWorkMessageDTO.getMsgtype() == 104){
+                if (wxWorkMessageDTO.getMsgtype()==2||wxWorkMessageDTO.getMsgtype()==0||wxWorkMessageDTO.getMsgtype()==16||wxWorkMessageDTO.getMsgtype() == 101||wxWorkMessageDTO.getMsgtype() == 104||wxWorkMessageDTO.getMsgtype()==141){
 
                     String content = wxWorkMessageDTO.getContent();
                     log.info("id:{}, 接收人:"+wxWorkMessageDTO.getReceiver(), id);
@@ -458,30 +455,6 @@ public class QwMsgController {
                     qwUserVoiceLogService.addQuUserVoiceByIpadCallback(id,extId,recordType,totalSeconds,wxWorkMsgResp.getUuid());
                 }
 
-                try {
-                    // 处理文本消息
-                    if (wxWorkMessageDTO.getMsgtype() == 2 || wxWorkMessageDTO.getMsgtype() == 0) {
-                        processTextMessage(id, sender, receiver, serverId, wxWorkMessageDTO.getContent(), wxWorkMsgResp, false, null, null);
-                    }
-                    // 语音消息
-                    if (wxWorkMessageDTO.getMsgtype() == 16) {
-                        processVoiceMessage(id, sender, receiver, serverId, wxWorkMessageDTO.getContent(), wxWorkMessageDTO, wxWorkMsgResp, false, null, null);
-                    }
-                    // 图片消息
-                    if (wxWorkMessageDTO.getMsgtype() == 101){
-                        processImageMessage(id, sender, receiver, serverId, wxWorkMessageDTO, wxWorkMsgResp, false, null, null);
-                    }
-                    // gif 表情消息
-                    if (wxWorkMessageDTO.getMsgtype() == 104){
-                        processEmotionDynamicMessage(id, sender, receiver, serverId, wxWorkMessageDTO, wxWorkMsgResp, false, null, null);
-                    }
-                    // 小程序消息
-                    if (wxWorkMessageDTO.getMsgtype() == 78) {
-                        processMiniAppMessage(id, sender, receiver, serverId, wxWorkMessageDTO, wxWorkMsgResp, false, null, null);
-                    }
-                } catch (Exception e) {
-                    log.error("接收处理个微消息失败 info: {} err: {}", json, e.getMessage(), e);
-                }
 
                 break;
 
@@ -493,6 +466,62 @@ public class QwMsgController {
         return map;
     }
 
+    /**
+     * Im消息存档回显
+     */
+    private void processImMessage(WxWorkMsgResp wxWorkMsgResp, WxWorkMessageDTO wxWorkMessageDTO, Long qwUserId, Long serverId) {
+        log.debug("接收到消息 msg: {}", JSON.toJSONString(wxWorkMessageDTO));
+        Long receiver = wxWorkMessageDTO.getReceiver();
+        Long sender = wxWorkMessageDTO.getSender();
+        boolean isRoom = wxWorkMessageDTO.getIs_room() != 0;
+        String chatId = null;
+        String chatAvatar = null;
+        if (isRoom) {
+            WxWorkRoomId2ChatIdDTO roomId2ChatIdDTO = new WxWorkRoomId2ChatIdDTO();
+            roomId2ChatIdDTO.setUuid(wxWorkMsgResp.getUuid());
+            roomId2ChatIdDTO.setRoom_id(wxWorkMessageDTO.getRoom_conversation_id());
+            WxWorkResponseDTO<WxWorkChatId2RoomIdResp> roomId2ChatIdResp = wxWorkService.roomId2ChatId(roomId2ChatIdDTO, serverId);
+            if (roomId2ChatIdResp.getErrcode() != 0) {
+                log.warn("接收群消息  rooId2ChatId失败: {}", roomId2ChatIdResp.getErrmsg());
+                return;
+            }
+            chatId = roomId2ChatIdResp.getData().getChatid();
+            WxRoomHeaderDTO roomHeaderDTO = new WxRoomHeaderDTO();
+            roomHeaderDTO.setRoomid(roomId2ChatIdResp.getData().getRoom_id());
+            roomHeaderDTO.setUuid(wxWorkMsgResp.getUuid());
+            WxWorkResponseDTO<WxRoomHeaderResp> roomHeaderResp = wxWorkService.wxRoomHeader(roomHeaderDTO, serverId);
+            if (roomHeaderResp.getErrcode() == 0) {
+                chatAvatar = roomHeaderResp.getData().getImage_url();
+            }
+        }
+
+        // 处理文本消息
+        if (wxWorkMessageDTO.getMsgtype() == 2 || wxWorkMessageDTO.getMsgtype() == 0) {
+            processTextMessage(qwUserId, sender, receiver, serverId, wxWorkMessageDTO, wxWorkMsgResp, isRoom, chatId, chatAvatar);
+        }
+        // 语音消息
+        if (wxWorkMessageDTO.getMsgtype() == 16) {
+            processVoiceMessage(qwUserId, sender, receiver, serverId, wxWorkMessageDTO, wxWorkMsgResp, isRoom, chatId, chatAvatar);
+        }
+        // 图片消息
+        if (wxWorkMessageDTO.getMsgtype() == 101){
+            processImageMessage(qwUserId, sender, receiver, serverId, wxWorkMessageDTO, wxWorkMsgResp, isRoom, chatId, chatAvatar);
+        }
+        // gif 表情消息
+        if (wxWorkMessageDTO.getMsgtype() == 104){
+            processEmotionDynamicMessage(qwUserId, sender, receiver, serverId, wxWorkMessageDTO, wxWorkMsgResp, isRoom, chatId, chatAvatar);
+        }
+        // 小程序消息
+        if (wxWorkMessageDTO.getMsgtype() == 78) {
+            processMiniAppMessage(qwUserId, sender, receiver, serverId, wxWorkMessageDTO, wxWorkMsgResp, isRoom, chatId, chatAvatar);
+        }
+
+        // 撤回消息
+        if (wxWorkMessageDTO.getMsgtype() == 2063) {
+            processCancelMessage(qwUserId, sender, receiver, serverId, wxWorkMessageDTO, wxWorkMsgResp, isRoom, chatId, chatAvatar);
+        }
+    }
+
     /**
      * 处理被拉黑的用户
      * @param qwUserId
@@ -613,15 +642,15 @@ public class QwMsgController {
      * @param senderVid         消息发送者ID
      * @param receiverVid       消息接收者ID
      * @param serverId          服务器ID
-     * @param content           消息内容
+     * @param wxWorkMessageDTO  消息内容
      * @param wxWorkMsgResp     回调信息对象
      * @param isRoom            是否群聊
      * @param chatId            会话ID(群聊才有)
      * @param chatAvatar        群头像(群聊才有)
      */
-    private void processTextMessage(Long qwUserId, Long senderVid, Long receiverVid, Long serverId, String content, WxWorkMsgResp wxWorkMsgResp, boolean isRoom, String chatId, String chatAvatar) {
+    private void processTextMessage(Long qwUserId, Long senderVid, Long receiverVid, Long serverId, WxWorkMessageDTO wxWorkMessageDTO, WxWorkMsgResp wxWorkMsgResp, boolean isRoom, String chatId, String chatAvatar) {
         // 保存聊天消息
-        QwMessageListVO message = aiHookService.saveQwMsg(qwUserId, senderVid, receiverVid, serverId, content, wxWorkMsgResp.getUuid(), wxWorkMsgResp.getJson(), 1, isRoom, chatId, chatAvatar);
+        QwMessageListVO message = aiHookService.saveQwMsg(qwUserId, senderVid, receiverVid, serverId, wxWorkMessageDTO.getContent(), wxWorkMsgResp.getUuid(), wxWorkMsgResp.getJson(), 1, isRoom, chatId, chatAvatar, wxWorkMessageDTO.getMsg_id(), wxWorkMessageDTO.getApp_info());
         QwImSocket.broadcast(message);
     }
 
@@ -632,13 +661,12 @@ public class QwMsgController {
      * @param receiverVid       消息接收者ID
      * @param serverId          服务器ID
      * @param wxWorkMessageDTO  消息DTO
-     * @param content           翻译后的内容
      * @param wxWorkMsgResp     回调信息对象
      * @param isRoom            是否群聊
      * @param chatId            会话ID(群聊才有)
      * @param chatAvatar        群头像(群聊才有)
      */
-    private void processVoiceMessage(Long qwUserId, Long senderVid, Long receiverVid, Long serverId, String content, WxWorkMessageDTO wxWorkMessageDTO, WxWorkMsgResp wxWorkMsgResp, boolean isRoom, String chatId, String chatAvatar) {
+    private void processVoiceMessage(Long qwUserId, Long senderVid, Long receiverVid, Long serverId, WxWorkMessageDTO wxWorkMessageDTO, WxWorkMsgResp wxWorkMsgResp, boolean isRoom, String chatId, String chatAvatar) {
         String voiceFileName = IdUtils.fastSimpleUUID() + ".silk";
         WxWorkResponseDTO<String> fileUrlResp =
                 aiHookService.getFileUrl(wxWorkMsgResp.getUuid(), wxWorkMessageDTO.getVoice_id(), wxWorkMessageDTO.getAes_key(), 5, voiceFileName, wxWorkMessageDTO.getVoice_size(), serverId);
@@ -655,6 +683,7 @@ public class QwMsgController {
         }
 
         // 转换内容为空时再尝试一次
+        String content = wxWorkMessageDTO.getContent();
         if (StringUtils.isBlank(content)) {
             WxwSpeechToTextEntityDTO ste = new WxwSpeechToTextEntityDTO();
             ste.setMsgid(wxWorkMessageDTO.getMsg_id());
@@ -670,7 +699,7 @@ public class QwMsgController {
         json.put("content", content);
 
         // 保存聊天消息
-        QwMessageListVO message = aiHookService.saveQwMsg(qwUserId, senderVid, receiverVid, serverId, json.toString(), wxWorkMsgResp.getUuid(), wxWorkMsgResp.getJson(), 4, isRoom, chatId, chatAvatar);
+        QwMessageListVO message = aiHookService.saveQwMsg(qwUserId, senderVid, receiverVid, serverId, json.toString(), wxWorkMsgResp.getUuid(), wxWorkMsgResp.getJson(), 4, isRoom, chatId, chatAvatar, wxWorkMessageDTO.getMsg_id(), wxWorkMessageDTO.getApp_info());
         QwImSocket.broadcast(message);
     }
 
@@ -697,7 +726,7 @@ public class QwMsgController {
 
         String content = fileUrlResp.getData();
         // 保存聊天消息
-        QwMessageListVO message = aiHookService.saveQwMsg(qwUserId, senderVid, receiverVid, serverId, content, wxWorkMsgResp.getUuid(), wxWorkMsgResp.getJson(), 2, isRoom, chatId, chatAvatar);
+        QwMessageListVO message = aiHookService.saveQwMsg(qwUserId, senderVid, receiverVid, serverId, content, wxWorkMsgResp.getUuid(), wxWorkMsgResp.getJson(), 2, isRoom, chatId, chatAvatar, wxWorkMessageDTO.getMsg_id(), wxWorkMessageDTO.getApp_info());
         QwImSocket.broadcast(message);
     }
 
@@ -716,7 +745,7 @@ public class QwMsgController {
     private void processEmotionDynamicMessage(Long qwUserId, Long senderVid, Long receiverVid, Long serverId, WxWorkMessageDTO wxWorkMessageDTO, WxWorkMsgResp wxWorkMsgResp, boolean isRoom, String chatId, String chatAvatar) {
         String content = wxWorkMessageDTO.getUrl();
         // 保存聊天消息
-        QwMessageListVO message = aiHookService.saveQwMsg(qwUserId, senderVid, receiverVid, serverId, content, wxWorkMsgResp.getUuid(), wxWorkMsgResp.getJson(), 3, isRoom, chatId, chatAvatar);
+        QwMessageListVO message = aiHookService.saveQwMsg(qwUserId, senderVid, receiverVid, serverId, content, wxWorkMsgResp.getUuid(), wxWorkMsgResp.getJson(), 3, isRoom, chatId, chatAvatar, wxWorkMessageDTO.getMsg_id(), wxWorkMessageDTO.getApp_info());
         QwImSocket.broadcast(message);
     }
 
@@ -751,57 +780,31 @@ public class QwMsgController {
         json.put("thumbnail", fileUrlResp.getData());
 
         // 保存聊天消息
-        QwMessageListVO message = aiHookService.saveQwMsg(qwUserId, senderVid, receiverVid, serverId, json.toString(), wxWorkMsgResp.getUuid(), wxWorkMsgResp.getJson(), 5, isRoom, chatId, chatAvatar);
+        QwMessageListVO message = aiHookService.saveQwMsg(qwUserId, senderVid, receiverVid, serverId, json.toString(), wxWorkMsgResp.getUuid(), wxWorkMsgResp.getJson(), 5, isRoom, chatId, chatAvatar, wxWorkMessageDTO.getMsg_id(), wxWorkMessageDTO.getApp_info());
         QwImSocket.broadcast(message);
     }
 
     /**
-     * 处理群消息
+     * 小程序消息处理
+     * @param qwUserId          企微用户ID
+     * @param senderVid         消息发送者ID
+     * @param receiverVid       消息接收者ID
+     * @param serverId          服务器ID
+     * @param wxWorkMessageDTO  消息DTO
+     * @param wxWorkMsgResp     回调信息对象
+     * @param isRoom            是否群聊
+     * @param chatId            会话ID(群聊才有)
+     * @param chatAvatar        群头像(群聊才有)
      */
-    private void processRoomMessage(WxWorkMsgResp wxWorkMsgResp, WxWorkMessageDTO wxWorkMessageDTO, Long qwUserId, Long serverId) {
-        log.debug("接收群消息 msg: {}", JSON.toJSONString(wxWorkMessageDTO));
-        Long receiver = wxWorkMessageDTO.getReceiver();
-        Long sender = wxWorkMessageDTO.getSender();
-
-        WxWorkRoomId2ChatIdDTO roomId2ChatIdDTO = new WxWorkRoomId2ChatIdDTO();
-        roomId2ChatIdDTO.setUuid(wxWorkMsgResp.getUuid());
-        roomId2ChatIdDTO.setRoom_id(wxWorkMessageDTO.getRoom_conversation_id());
-        WxWorkResponseDTO<WxWorkChatId2RoomIdResp> roomId2ChatIdResp = wxWorkService.roomId2ChatId(roomId2ChatIdDTO, serverId);
-        if (roomId2ChatIdResp.getErrcode() != 0) {
-            log.warn("接收群消息  rooId2ChatId失败: {}", roomId2ChatIdResp.getErrmsg());
-            return;
-        }
-        String chatId = roomId2ChatIdResp.getData().getChatid();
-
-        String chatAvatar = "";
-        WxRoomHeaderDTO roomHeaderDTO = new WxRoomHeaderDTO();
-        roomHeaderDTO.setRoomid(roomId2ChatIdResp.getData().getRoom_id());
-        roomHeaderDTO.setUuid(wxWorkMsgResp.getUuid());
-        WxWorkResponseDTO<WxRoomHeaderResp> roomHeaderResp = wxWorkService.wxRoomHeader(roomHeaderDTO, serverId);
-        if (roomHeaderResp.getErrcode() == 0) {
-            chatAvatar = roomHeaderResp.getData().getImage_url();
-        }
+    private void processCancelMessage(Long qwUserId, Long senderVid, Long receiverVid, Long serverId, WxWorkMessageDTO wxWorkMessageDTO, WxWorkMsgResp wxWorkMsgResp, boolean isRoom, String chatId, String chatAvatar) {
+        // 修改聊天消息
+        QwMessageListVO message = aiHookService.updateQwMsg(qwUserId, senderVid, receiverVid, serverId, wxWorkMsgResp.getUuid(), isRoom, chatId, wxWorkMessageDTO.getRevoke_ref_appinfo());
+        QwImSocket.broadcast(message);
+    }
 
-        // 处理文本消息
-        if (wxWorkMessageDTO.getMsgtype() == 2 || wxWorkMessageDTO.getMsgtype() == 0) {
-            processTextMessage(qwUserId, sender, receiver, serverId, wxWorkMessageDTO.getContent(), wxWorkMsgResp, true, chatId, chatAvatar);
-        }
-        // 语音消息
-        if (wxWorkMessageDTO.getMsgtype() == 16) {
-            processVoiceMessage(qwUserId, sender, receiver, serverId, wxWorkMessageDTO.getContent(), wxWorkMessageDTO, wxWorkMsgResp, true, chatId, chatAvatar);
-        }
-        // 图片消息
-        if (wxWorkMessageDTO.getMsgtype() == 101){
-            processImageMessage(qwUserId, sender, receiver, serverId, wxWorkMessageDTO, wxWorkMsgResp, true, chatId, chatAvatar);
-        }
-        // gif 表情消息
-        if (wxWorkMessageDTO.getMsgtype() == 104){
-            processEmotionDynamicMessage(qwUserId, sender, receiver, serverId, wxWorkMessageDTO, wxWorkMsgResp, true, chatId, chatAvatar);
-        }
-        // 小程序消息
-        if (wxWorkMessageDTO.getMsgtype() == 78) {
-            processMiniAppMessage(qwUserId, sender, receiver, serverId, wxWorkMessageDTO, wxWorkMsgResp, true, chatId, chatAvatar);
-        }
+    @PostMapping("/sendQwImMsg")
+    public void sendQwImMsg(@RequestBody QwMessageListVO message) {
+        QwImSocket.broadcast(message);
     }
 
 }

+ 3 - 0
fs-service/src/main/java/com/fs/erp/dto/wdt/ErpWdtApiResponse.java

@@ -1,5 +1,7 @@
 package com.fs.erp.dto.wdt;
 
+import com.alibaba.fastjson.PropertyNamingStrategy;
+import com.alibaba.fastjson.annotation.JSONType;
 import lombok.Data;
 import org.apache.commons.lang3.StringEscapeUtils;
 
@@ -9,6 +11,7 @@ import org.apache.commons.lang3.StringEscapeUtils;
  * @date 2025-02-27
  */
 @Data
+@JSONType(naming = PropertyNamingStrategy.CamelCase)
 public class ErpWdtApiResponse {
 
     /**

+ 3 - 0
fs-service/src/main/java/com/fs/erp/dto/wdt/ErpWdtBaseResponseDTO.java

@@ -1,5 +1,7 @@
 package com.fs.erp.dto.wdt;
 
+import com.alibaba.fastjson.PropertyNamingStrategy;
+import com.alibaba.fastjson.annotation.JSONType;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import lombok.AllArgsConstructor;
 import lombok.Data;
@@ -13,6 +15,7 @@ import java.util.List;
 @Data
 @NoArgsConstructor
 @AllArgsConstructor
+@JSONType(naming = PropertyNamingStrategy.CamelCase)
 public class ErpWdtBaseResponseDTO {
 
     /**

+ 3 - 0
fs-service/src/main/java/com/fs/erp/dto/wdt/ErpWdtStockRespDTO.java

@@ -1,5 +1,7 @@
 package com.fs.erp.dto.wdt;
 
+import com.alibaba.fastjson.PropertyNamingStrategy;
+import com.alibaba.fastjson.annotation.JSONType;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import lombok.Data;
 
@@ -7,6 +9,7 @@ import javax.validation.constraints.NotNull;
 import java.io.Serializable;
 import java.util.List;
 
+@JSONType(naming = PropertyNamingStrategy.CamelCase)
 @Data
 public class ErpWdtStockRespDTO implements Serializable {
     /**

+ 3 - 0
fs-service/src/main/java/com/fs/erp/dto/wdt/ErpWdtTradeQueryResponse.java

@@ -1,5 +1,7 @@
 package com.fs.erp.dto.wdt;
 
+import com.alibaba.fastjson.PropertyNamingStrategy;
+import com.alibaba.fastjson.annotation.JSONType;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
@@ -21,6 +23,7 @@ import java.util.List;
 @Builder
 @NoArgsConstructor
 @AllArgsConstructor
+@JSONType(naming = PropertyNamingStrategy.CamelCase)
 public class ErpWdtTradeQueryResponse {
 
     /**

+ 2 - 7
fs-service/src/main/java/com/fs/erp/service/impl/WdtErpGoodsServiceImpl.java

@@ -47,10 +47,7 @@ public class WdtErpGoodsServiceImpl implements IErpGoodsService {
         Asserts.notNull(param.getCode(),"barcode不能为空!");
         try {
             String response = client.execute("goods_query.php", map);
-            ParserConfig config = new ParserConfig();
-            config.propertyNamingStrategy = PropertyNamingStrategy.CamelCase;
-            ErpWdtBaseResponseDTO erpWdtBaseResponseDTO =
-                    JSON.parseObject(response, ErpWdtBaseResponseDTO.class, config);
+            ErpWdtBaseResponseDTO erpWdtBaseResponseDTO = JSON.parseObject(response, ErpWdtBaseResponseDTO.class);
             if(ObjectUtils.equals(0, erpWdtBaseResponseDTO.getCode())){
                 List<ErpGoods> list = new ArrayList<>();
 
@@ -100,9 +97,7 @@ public class WdtErpGoodsServiceImpl implements IErpGoodsService {
         map.put("spec_no",barcode);
         try {
             String response = client.execute("stock_query.php", map);
-            ParserConfig config = new ParserConfig();
-            config.propertyNamingStrategy = PropertyNamingStrategy.CamelCase;
-            ErpWdtStockRespDTO erpWdtStockRespDTO = JSON.parseObject(response, ErpWdtStockRespDTO.class,config);
+            ErpWdtStockRespDTO erpWdtStockRespDTO = JSON.parseObject(response, ErpWdtStockRespDTO.class);
             List<ErpGoodsStock> list = new ArrayList<>();
             if(ObjectUtils.equals(0,erpWdtStockRespDTO.getCode())){
                 List<ErpWdtStockDTO> stocks = erpWdtStockRespDTO.getStocks();

+ 5 - 17
fs-service/src/main/java/com/fs/erp/service/impl/WdtErpOrderServiceImpl.java

@@ -263,9 +263,7 @@ public class WdtErpOrderServiceImpl implements IErpOrderService {
 
         try {
             String response = client.execute("trade_push.php", map);
-            ParserConfig config = new ParserConfig();
-            config.propertyNamingStrategy = PropertyNamingStrategy.CamelCase;
-            ErpWdtApiResponse erpWdtApiResponse = JSON.parseObject(response, ErpWdtApiResponse.class,config);
+            ErpWdtApiResponse erpWdtApiResponse = JSON.parseObject(response, ErpWdtApiResponse.class);
             if(ObjectUtil.equal(0,erpWdtApiResponse.getCode())){
                 log.info("订单推送成功: {}", response);
                 ErpOrderResponse erpOrderResponse = new ErpOrderResponse();
@@ -438,9 +436,7 @@ public class WdtErpOrderServiceImpl implements IErpOrderService {
 
         try {
             String response = client.execute("trade_push.php", map);
-            ParserConfig config = new ParserConfig();
-            config.propertyNamingStrategy = PropertyNamingStrategy.CamelCase;
-            ErpWdtApiResponse erpWdtApiResponse = JSON.parseObject(response, ErpWdtApiResponse.class,config);
+            ErpWdtApiResponse erpWdtApiResponse = JSON.parseObject(response, ErpWdtApiResponse.class);
             if(ObjectUtil.equal(0,erpWdtApiResponse.getCode())){
                 log.info("订单推送成功: {}", response);
                 ErpOrderResponse erpOrderResponse = new ErpOrderResponse();
@@ -772,10 +768,8 @@ public class WdtErpOrderServiceImpl implements IErpOrderService {
 
         try {
             String response = client.execute("trade_push.php", map);
-            ParserConfig config = new ParserConfig();
-            config.propertyNamingStrategy = PropertyNamingStrategy.CamelCase;
              log.info("Erp推送传参: {}", map);
-            ErpWdtApiResponse erpWdtApiResponse = JSON.parseObject(response, ErpWdtApiResponse.class,config);
+            ErpWdtApiResponse erpWdtApiResponse = JSON.parseObject(response, ErpWdtApiResponse.class);
             if(ObjectUtil.equal(0,erpWdtApiResponse.getCode())){
                 log.info("订单推送成功: {}", response);
                 ErpOrderResponse erpOrderResponse = new ErpOrderResponse();
@@ -811,8 +805,6 @@ public class WdtErpOrderServiceImpl implements IErpOrderService {
         map.put("src_tid",param.getCode());
         try {
             String execute = client.execute("sales_trade_query.php", map);
-            ParserConfig config = new ParserConfig();
-            config.propertyNamingStrategy = PropertyNamingStrategy.CamelCase;
             ErpWdtTradeQueryResponse tradeQueryResponseDTO = JSON.parseObject(execute, ErpWdtTradeQueryResponse.class);
             if(ObjectUtil.equal(0,tradeQueryResponseDTO.getCode())){
 //                if (tradeQueryResponseDTO.getTrades().isEmpty()){
@@ -901,9 +893,7 @@ public class WdtErpOrderServiceImpl implements IErpOrderService {
         map.put("api_refund_list",convertToSnakeCase(erpWdtApiRefunds));
         try {
             String execute = client.execute("sales_refund_push.php", map);
-            ParserConfig config = new ParserConfig();
-            config.propertyNamingStrategy = PropertyNamingStrategy.CamelCase;
-            ErpWdtApiResponse erpWdtApiResponse = JSON.parseObject(execute, ErpWdtApiResponse.class,config);
+            ErpWdtApiResponse erpWdtApiResponse = JSON.parseObject(execute, ErpWdtApiResponse.class);
             if(ObjectUtil.equal(0, erpWdtApiResponse.getCode())){
                 log.info("退款单更新成功: {}", erpWdtApiResponse);
                 return new BaseResponse();
@@ -1073,9 +1063,7 @@ public class WdtErpOrderServiceImpl implements IErpOrderService {
 
         try {
             String response = client.execute("trade_push.php", map);
-            ParserConfig config = new ParserConfig();
-            config.propertyNamingStrategy = PropertyNamingStrategy.CamelCase;
-            ErpWdtApiResponse erpWdtApiResponse = JSON.parseObject(response, ErpWdtApiResponse.class,config);
+            ErpWdtApiResponse erpWdtApiResponse = JSON.parseObject(response, ErpWdtApiResponse.class);
             if(ObjectUtil.equal(0,erpWdtApiResponse.getCode())){
                 log.info("订单推送成功: {}", response);
                 ErpOrderResponse erpOrderResponse = new ErpOrderResponse();

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

@@ -54,6 +54,18 @@ public interface AiHookService {
      * @param isRoom        是否群聊
      * @param chatId        会话ID(群聊才有)
      * @param chatAvatar    群头像(群聊才有)
+     * @param qwMsgId       企微消息ID
+     * @param qwAppInfo       企微appInfo
      */
-    QwMessageListVO saveQwMsg(Long qwUserId, Long senderVid, Long receiverVid, Long serverId, String content, String uuid, String json, int msgType, boolean isRoom, String chatId, String chatAvatar);
+    QwMessageListVO saveQwMsg(Long qwUserId, Long senderVid, Long receiverVid, Long serverId, String content, String uuid, String json, int msgType, boolean isRoom, String chatId, String chatAvatar, Long qwMsgId, String qwAppInfo);
+
+    /**
+     * 保存企微聊天信息
+     */
+    QwMessageListVO saveQwMsg(Long qwUserId, String exId, String content, int msgType, boolean isRoom, Long qwMsgId, String qwAppInfo);
+
+    /**
+     * 修改消息
+     */
+    QwMessageListVO updateQwMsg(Long qwUserId, Long senderVid, Long receiverVid, Long serverId, String uuid, boolean isRoom, String chatId, String qwAppInfo);
 }

+ 256 - 14
fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java

@@ -3,10 +3,12 @@ package com.fs.fastGpt.service.impl;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.fs.common.annotation.Excel;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.PinYinUtil;
+import com.fs.common.utils.http.HttpUtils;
 import com.fs.company.domain.CompanyConfig;
 import com.fs.company.mapper.CompanyConfigMapper;
 import com.fs.config.ai.AiHostProper;
@@ -65,14 +67,13 @@ import com.vdurmont.emoji.EmojiParser;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.jetbrains.annotations.Nullable;
+import org.json.JSONObject;
 import org.redisson.api.RLock;
 import org.redisson.api.RedissonClient;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Propagation;
-import org.springframework.transaction.annotation.Transactional;
 
 import java.lang.reflect.Field;
 import java.time.DayOfWeek;
@@ -80,7 +81,7 @@ import java.time.LocalDate;
 import java.time.LocalTime;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.*;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
@@ -180,6 +181,21 @@ public class AiHookServiceImpl implements AiHookService {
     private static final String AI_REPLY = "AI_REPLY:";
     private static final String AI_REPLY_TAG = "AI_REPLY_TAG:";
 
+    private final ExecutorService executor = new ThreadPoolExecutor(
+            8, 32, 60L, TimeUnit.SECONDS,
+            new LinkedBlockingQueue<>(1000),
+            new ThreadFactory() {
+                private final ThreadFactory defaultFactory = Executors.defaultThreadFactory();
+                private int count = 1;
+                @Override
+                public Thread newThread(Runnable r) {
+                    Thread thread = defaultFactory.newThread(r);
+                    thread.setName("ai-im-exec-" + count++);
+                    return thread;
+                }
+            },
+            new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
+    );
 
     /** Ai半小时未回复提醒 **/
     /**
@@ -299,7 +315,7 @@ public class AiHookServiceImpl implements AiHookService {
                             if (result.isLongText()){
                                 //新增用户信息
                                 addUserInfo(contentKh, qwExternalContacts.getId(),fastGptChatSession);
-                                sendAiMsg(content,sender,uid,serverId);
+                                sendAiMsg(content,sender,uid,serverId, user.getId(), qwExternalContacts.getExternalUserId());
 
                             }else {
                                 String sa = contentKh.replaceAll("】\n", "】").replaceAll("\n【", "【");
@@ -313,7 +329,7 @@ public class AiHookServiceImpl implements AiHookService {
                                 //新增用户信息
                                 addUserInfo(contentKh, qwExternalContacts.getId(),fastGptChatSession);
                                 for (String msg : countList) {
-                                    sendAiMsg(msg,sender,uid,serverId);
+                                    sendAiMsg(msg,sender,uid,serverId, user.getId(), qwExternalContacts.getExternalUserId());
                                     try {
                                         Thread.sleep(10000);
                                     } catch (InterruptedException e) {
@@ -575,9 +591,9 @@ public class AiHookServiceImpl implements AiHookService {
                     //新增用户信息
                     addUserInfo(contentKh, qwExternalContacts.getId(),fastGptChatSession);
                     if (type==16){
-                        sendAiVoiceMsg(content,sender,uid,serverId,user);
+                        sendAiVoiceMsg(content,sender,uid,serverId,user, qwExternalContacts.getExternalUserId());
                     }else {
-                        sendAiMsg(content,sender,uid,serverId);
+                        sendAiMsg(content,sender,uid,serverId, user.getId(), qwExternalContacts.getExternalUserId());
                     }
 
                 }else {
@@ -594,9 +610,9 @@ public class AiHookServiceImpl implements AiHookService {
                     addUserInfo(contentKh, qwExternalContacts.getId(),fastGptChatSession);
                     for (String msg : countList) {
                         if (type==16){
-                            sendAiVoiceMsg(msg,sender,uid,serverId,user);
+                            sendAiVoiceMsg(msg,sender,uid,serverId,user, qwExternalContacts.getExternalUserId());
                         }else {
-                            sendAiMsg(msg,sender,uid,serverId);
+                            sendAiMsg(msg,sender,uid,serverId, user.getId(), qwExternalContacts.getExternalUserId());
                         }
                         try {
                             Thread.sleep(10000);
@@ -961,7 +977,7 @@ public class AiHookServiceImpl implements AiHookService {
         return maskedContent;
     }
 
-    private void sendAiVoiceMsg(String content, Long sendId , String uuid,Long serverId,QwUser user) {
+    private void sendAiVoiceMsg(String content, Long sendId , String uuid,Long serverId,QwUser user, String extUserId) {
         if (content == null || content.trim().isEmpty()){
             System.out.println("输出为空格");
             return;
@@ -1000,11 +1016,37 @@ public class AiHookServiceImpl implements AiHookService {
         WxWorkResponseDTO<WxwSendCDNVoiceMsgRespDTO> wxwSendCDNVoiceMsgRespDTOWxWorkResponseDTO = wxWorkService.SendCDNVoiceMsg(wxwSendCDNVoiceMsgDTO, serverId);
         System.out.println(wxwSendCDNVoiceMsgRespDTOWxWorkResponseDTO);
 
+        if (wxwSendCDNVoiceMsgRespDTOWxWorkResponseDTO.getErrcode() == 0) {
+            try {
+                WxwSendCDNVoiceMsgRespDTO dtoData = wxwSendCDNVoiceMsgRespDTOWxWorkResponseDTO.getData();
+
+                JSONObject json = new JSONObject();
+                json.put("url", data.getUrl());
+                json.put("content", content);
+
+                // 保存聊天消息
+                QwMessageListVO message = this.saveQwMsg(user.getId(), extUserId, json.toString(), 4, false, dtoData.getMsg_id(), dtoData.getApp_info());
+                if (message == null) {
+                    return;
+                }
+                // 发送webSocket
+                executor.execute(() -> {
+                    try {
+                        HttpUtils.doPost(aiHostProper.getIpadUrl() + "/msg/sendQwImMsg", JSON.toJSONString(message));
+                    } catch (Exception e) {
+                        log.error("转发ai回复消息失败 err: {}", e.getMessage(), e);
+                    }
+                });
+            } catch (Exception e) {
+                log.error("保存ai回复消息失败 err: {}", e.getMessage(), e);
+            }
+        }
+
     }
 
     @Autowired
     QwSopLogsMapper qwSopLogsMapper;
-    private void sendAiMsg(String content, Long sendId , String uuid,Long serverId) {
+    private void sendAiMsg(String content, Long sendId , String uuid,Long serverId, Long qwUserId, String extUserId) {
         if (content == null || content.trim().isEmpty()){
             System.out.println("输出为空格");
             return;
@@ -1017,6 +1059,27 @@ public class AiHookServiceImpl implements AiHookService {
         WxWorkResponseDTO<WxWorkSendTextMsgRespDTO> wxWorkSendTextMsgRespDTOWxWorkResponseDTO = wxWorkService.SendTextMsg(wxWorkSendTextMsgDTO,serverId);
         WxWorkSendTextMsgRespDTO data = wxWorkSendTextMsgRespDTOWxWorkResponseDTO.getData();
 
+        if (wxWorkSendTextMsgRespDTOWxWorkResponseDTO.getErrcode() == 0) {
+            try {
+                WxWorkSendTextMsgRespDTO dtoData = wxWorkSendTextMsgRespDTOWxWorkResponseDTO.getData();
+
+                // 保存聊天消息
+                QwMessageListVO message = this.saveQwMsg(qwUserId, extUserId, content, 1, false, dtoData.getMsg_id(), dtoData.getApp_info());
+                if (message == null) {
+                    return;
+                }
+                // 发送webSocket
+                executor.execute(() -> {
+                    try {
+                        HttpUtils.doPost(aiHostProper.getIpadUrl() + "/msg/sendQwImMsg", JSON.toJSONString(message));
+                    } catch (Exception e) {
+                        log.error("转发ai回复消息失败 err: {}", e.getMessage(), e);
+                    }
+                 });
+             } catch (Exception e) {
+                log.error("保存ai回复消息失败 err: {}", e.getMessage(), e);
+            }
+        }
 
     }
     /**
@@ -2145,9 +2208,11 @@ public class AiHookServiceImpl implements AiHookService {
      * @param isRoom        是否群聊
      * @param chatId        会话ID(群聊才有)
      * @param chatAvatar    群头像(群聊才有)
+     * @param qwMsgId       企微消息ID
+     * @param qwAppInfo       企微appInfo
      */
     @Override
-    public QwMessageListVO saveQwMsg(Long qwUserId, Long senderVid, Long receiverVid, Long serverId, String content, String uuid, String json, int msgType, boolean isRoom, String chatId, String chatAvatar) {
+    public QwMessageListVO saveQwMsg(Long qwUserId, Long senderVid, Long receiverVid, Long serverId, String content, String uuid, String json, int msgType, boolean isRoom, String chatId, String chatAvatar, Long qwMsgId, String qwAppInfo) {
         // 查询企微用户
         QwUser qwUser = qwUserService.selectQwUserById(qwUserId);
         if (Objects.isNull(qwUser)) {
@@ -2214,9 +2279,16 @@ public class AiHookServiceImpl implements AiHookService {
         }
         qwMsg.setAvatar(sender.getAvatar());
         qwMsg.setNickName(sender.getUserName());
+        qwMsg.setQwMsgId(qwMsgId);
+        qwMsg.setQwAppInfo(qwAppInfo);
         qwMsgMapper.insertQwMsg(qwMsg);
         log.debug("保存企微聊天记录 msgId: {}", qwMsg.getMsgId());
 
+        qwSession.setLastMsgId(qwMsg.getMsgId());
+        qwSession.setLastSendTime(qwMsg.getCreateTime().getTime());
+        qwSession.setLastContent(qwMsg.getContent());
+        qwSessionMapper.updateQwSession(qwSession);
+
         // 组装返回消息结构
         QwMessageListVO listVO = new QwMessageListVO();
         QWFromUser qwFromUser = new QWFromUser();
@@ -2243,12 +2315,90 @@ public class AiHookServiceImpl implements AiHookService {
         return listVO;
     }
 
+    /**
+     * 保存企微聊天信息
+     */
+    @Override
+    public QwMessageListVO saveQwMsg(Long qwUserId, String exId, String content, int msgType, boolean isRoom, Long qwMsgId, String qwAppInfo) {
+        QwUser qwUser = qwUserService.selectQwUserById(qwUserId);
+        if (Objects.isNull(qwUser)) {
+            log.warn("企微用户不存在 qwUserId: {}", qwUserId);
+            return null;
+        }
+
+        // 查询会话
+        QwSession qwSession;
+        if (isRoom) {
+            qwSession = qwSessionMapper.selectQwSessionByChatIdAndQwUserId(exId, qwUser.getId());
+        } else {
+            QwExternalContact externalContact = qwExternalContactMapper.selectQwExternalContactByExternalUserIdAndQwUserId(exId, qwUser.getCorpId(), qwUser.getQwUserId());
+            if (Objects.isNull(externalContact)) {
+                log.warn("企微外部联系人不存在 extId: {}", exId);
+                return null;
+            }
+            qwSession = qwSessionMapper.selectQwSessionByExtIdAndQwUserId(externalContact.getId(), qwUser.getId());
+        }
+
+        if (qwSession == null) {
+            log.warn("获取session失败 qwUserId: {}, extId: {}", qwUserId, exId);
+            return null;
+        }
+
+        // 保存聊天消息
+        QwMsg qwMsg = new QwMsg();
+        qwMsg.setContent(content);
+        qwMsg.setSessionId(qwSession.getSessionId());
+        qwMsg.setSendType(2);
+        qwMsg.setCompanyId(qwUser.getCompanyId());
+        qwMsg.setCompanyUserId(qwUser.getCompanyUserId());
+        qwMsg.setMsgType(msgType);
+        qwMsg.setMsgJson(content);
+        qwMsg.setStatus(0);
+        qwMsg.setQwUserId(qwSession.getQwUserId());
+        qwMsg.setCreateTime(new Date());
+        qwMsg.setAvatar(qwUser.getAvatar());
+        qwMsg.setNickName(qwUser.getQwUserName());
+        qwMsg.setQwMsgId(qwMsgId);
+        qwMsg.setQwAppInfo(qwAppInfo);
+        qwMsgMapper.insertQwMsg(qwMsg);
+        log.debug("保存企微聊天记录 msgId: {}", qwMsg.getMsgId());
+
+        qwSession.setLastMsgId(qwMsg.getMsgId());
+        qwSession.setLastSendTime(qwMsg.getCreateTime().getTime());
+        qwSession.setLastContent(qwMsg.getContent());
+        qwSessionMapper.updateQwSession(qwSession);
+
+        // 组装返回消息结构
+        QwMessageListVO listVO = new QwMessageListVO();
+        QWFromUser qwFromUser = new QWFromUser();
+        qwFromUser.setId(qwUser.getId());
+        qwFromUser.setAvatar(qwUser.getAvatar());
+        qwFromUser.setDisplayName(qwUser.getQwUserName());
+
+        listVO.setCompanyUserId(qwUser.getCompanyUserId());
+        String type = "text";
+        MsgType messageType = MsgType.getMsgType(msgType);
+        if (Objects.nonNull(messageType)){
+            type = messageType.getValue();
+        }
+
+        listVO.setType(type);
+        listVO.setStatus("succeed");
+        listVO.setExtId(qwMsg.getQwExtId());
+        listVO.setFromUser(qwFromUser);
+        listVO.setSendTime(qwMsg.getCreateTime().getTime());
+        listVO.setId(qwMsg.getMsgId().toString());
+        listVO.setContent(qwMsg.getContent());
+        listVO.setToContactId(String.valueOf(qwSession.getSessionId()));
+        listVO.setAppKey(qwUser.getAppKey());
+        return listVO;
+    }
+
     /**
      * 获取单聊session
      */
     private QwSession getQwSession(QwImUserDTO extUser, QwUser qwUser, Long extWxId) {
         QwSession qwSession;
-        String chatId;
         qwSession = qwSessionMapper.selectQwSessionByExtIdAndQwUserId(extUser.getUserId(), qwUser.getId());
         String firstLetter = PinYinUtil.getFirstLetter(extUser.getUserName());
         if (qwSession == null) {
@@ -2264,7 +2414,7 @@ public class AiHookServiceImpl implements AiHookService {
                 qwSession = qwSessionMapper.selectQwSessionByExtIdAndQwUserId(extUser.getUserId(), qwUser.getId());
                 if (qwSession == null) {
                     qwSession = new QwSession();
-                    chatId = UUID.randomUUID().toString();
+                    String chatId = UUID.randomUUID().toString();
                     qwSession.setChatId(chatId);
                     qwSession.setCorpId(qwUser.getCorpId());
                     qwSession.setQwExtWxId(String.valueOf(extWxId));
@@ -2404,4 +2554,96 @@ public class AiHookServiceImpl implements AiHookService {
         }
     }
 
+
+    /**
+     * 修改消息
+     */
+    @Override
+    public QwMessageListVO updateQwMsg(Long qwUserId, Long senderVid, Long receiverVid, Long serverId, String uuid, boolean isRoom, String chatId, String qwAppInfo) {
+        QwUser qwUser = qwUserService.selectQwUserById(qwUserId);
+        if (Objects.isNull(qwUser)) {
+            log.warn("企微用户不存在 qwUserId: {}", qwUserId);
+            return null;
+        }
+
+        // 获取发送人
+        QwImUserDTO sender = getUserByVid(senderVid, uuid, serverId, qwUser.getCorpId(), qwUser.getQwUserId());
+        if (Objects.isNull(sender)) {
+            log.warn("sender用户不存在 senderVid: {}", senderVid);
+            return null;
+        }
+
+        // 获取接收者
+        QwImUserDTO receiver = getUserByVid(receiverVid, uuid, serverId, qwUser.getCorpId(), qwUser.getQwUserId());
+
+        // 查询会话
+        QwSession qwSession = null;
+        if (isRoom) {
+            QwGroupChat qwGroupChat = qwGroupChatMapper.selectQwGroupChatByChatId(chatId);
+            if (Objects.isNull(qwGroupChat)){
+                log.warn("群聊不存在 serverId: {}, corpId: {}, qwUserId: {}, chatId: {}", qwUser.getServerId(), qwUser.getCorpId(), qwUser.getQwUserId(), chatId);
+                return null;
+            }
+
+            qwSession = qwSessionMapper.selectQwSessionByChatIdAndQwUserId(chatId, qwUser.getId());
+        } else {
+            QwImUserDTO extUser = null;
+
+            // 获取外部联系人
+            if (sender.getUserType() == 1) {
+                extUser = sender;
+            }
+            if (receiver != null && receiver.getUserType() == 1) {
+                extUser = receiver;
+            }
+            if (extUser != null) {
+                qwSession = qwSessionMapper.selectQwSessionByExtIdAndQwUserId(extUser.getUserId(), qwUser.getId());
+            }
+        }
+
+        if (qwSession == null) {
+            log.warn("获取session失败 senderVid: {}, receiverVid: {}", senderVid, receiverVid);
+            return null;
+        }
+
+        QwMsg qwMsg = qwMsgMapper.selectQwMsgBySessionIdAndQwAppInfo(qwSession.getSessionId(), qwAppInfo);
+        if (qwMsg == null) {
+            log.warn("消息不存在 sessionId: {}, appInfo: {}", qwSession.getSessionId(), qwAppInfo);
+            return null;
+        }
+
+        qwMsg.setMsgType(6);
+        qwMsgMapper.updateQwMsg(qwMsg);
+
+        qwSession.setLastMsgId(qwMsg.getMsgId());
+        qwSession.setLastSendTime(qwMsg.getCreateTime().getTime());
+        qwSession.setLastContent("");
+        qwSessionMapper.updateQwSession(qwSession);
+
+        // 组装返回消息结构
+        QwMessageListVO listVO = new QwMessageListVO();
+        QWFromUser qwFromUser = new QWFromUser();
+        qwFromUser.setId(sender.getUserId());
+        qwFromUser.setAvatar(sender.getAvatar());
+        qwFromUser.setDisplayName(sender.getUserName());
+
+        listVO.setCompanyUserId(qwUser.getCompanyUserId());
+        String type = "text";
+        MsgType messageType = MsgType.getMsgType(qwMsg.getMsgType());
+        if (Objects.nonNull(messageType)){
+            type = messageType.getValue();
+        }
+
+        listVO.setType(type);
+        listVO.setStatus("succeed");
+        listVO.setExtId(qwMsg.getQwExtId());
+        listVO.setFromUser(qwFromUser);
+        listVO.setSendTime(qwMsg.getCreateTime().getTime());
+        listVO.setId(qwMsg.getMsgId().toString());
+        listVO.setContent(qwMsg.getContent());
+        listVO.setToContactId(String.valueOf(qwSession.getSessionId()));
+        listVO.setAppKey(qwUser.getAppKey());
+        return listVO;
+    }
+
 }

+ 1 - 0
fs-service/src/main/java/com/fs/ipad/vo/BaseVo.java

@@ -15,6 +15,7 @@ public class BaseVo{
     private String corpId;
     private String corpCode;
     private boolean isRoom;
+    private Long qwUserId;
 
 
     public void setBase(BaseVo vo){

+ 5 - 0
fs-service/src/main/java/com/fs/qw/domain/QwMsg.java

@@ -75,6 +75,11 @@ public class QwMsg extends BaseEntity implements Serializable
     @Excel(name = "昵称")
     private String avatar;
 
+    /** 企微消息ID **/
+    private Long qwMsgId;
+
+    /** 企微消息info **/
+    private String qwAppInfo;
 
 
 }

+ 9 - 0
fs-service/src/main/java/com/fs/qw/domain/QwSession.java

@@ -71,4 +71,13 @@ public class QwSession extends BaseEntity implements Serializable
     /** 首字母 **/
     private String firstLetter;
 
+    /** 最后一条消息id **/
+    private Long lastMsgId;
+
+    /** 最后一条消息发送时间 **/
+    private Long lastSendTime;
+
+    /** 最后一条消息内容 **/
+    private String lastContent;
+
 }

+ 1 - 0
fs-service/src/main/java/com/fs/qw/enums/MsgType.java

@@ -11,6 +11,7 @@ public enum MsgType {
     EMOTION_DYNAMIC(3, "emotionDynamic"),
     VOICE(4, "voice"),
     MINI_PROGRAM(5, "miniprogram"),
+    CANCEL(6, "cancel"),
     ;
 
     private final Integer code;

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

@@ -63,6 +63,9 @@ public interface QwMsgMapper  extends BaseMapper<QwMsg>
      */
     public int deleteQwMsgByMsgIds(Long[] msgIds);
 
-    @Select("select * from qw_msg where session_id = #{sessionId} order by msg_id desc ")
-    List<QwMsg> selectQwMsgListBySession(@Param("sessionId") String conversationId);
+    /**
+     * 根据会话ID和QwAppInfo查询消息
+     */
+    @Select("select * from qw_msg where session_id = #{sessionId} and qw_app_info = #{qwAppInfo}")
+    QwMsg selectQwMsgBySessionIdAndQwAppInfo(@Param("sessionId") Long sessionId, @Param("qwAppInfo") String qwAppInfo);
 }

+ 3 - 1
fs-service/src/main/java/com/fs/qw/param/QwMsgSendParam.java

@@ -9,11 +9,13 @@ public class QwMsgSendParam implements Serializable {
     private Long sessionId;
     private String content;//内容
     private String appKey;
-    // 消息类型 1文本 2图片 3动态表情 4语音 5小程序
+    // 消息类型 1文本 2图片 3动态表情 4语音 5小程序 6撤销
     private Integer msgType;
     // 小程序标题
     private String title;
     // 小程序图片
     private String image;
+    // 消息ID
+    private Long msgId;
 
 }

+ 58 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwMsgServiceImpl.java

@@ -331,6 +331,8 @@ public class QwMsgServiceImpl extends ServiceImpl<QwMsgMapper, QwMsg> implements
 
         String msgJson;
 
+        Long qwMsgId = null;
+        String qwAppInfo = null;
         MsgType msgType = MsgType.getMsgType(param.getMsgType());
         // 发送消息  文本
         if (MsgType.TEXT == msgType) {
@@ -345,6 +347,9 @@ public class QwMsgServiceImpl extends ServiceImpl<QwMsgMapper, QwMsg> implements
                 return R.error(msgRespDTOWxWorkResponseDTO.getErrmsg());
             }
             msgJson = JSONObject.toJSONString(textMsgDTO);
+            WxWorkSendTextMsgRespDTO data = msgRespDTOWxWorkResponseDTO.getData();
+            qwMsgId = data.getMsg_id();
+            qwAppInfo = data.getApp_info();
         }
 
         // 图片
@@ -372,6 +377,9 @@ public class QwMsgServiceImpl extends ServiceImpl<QwMsgMapper, QwMsg> implements
                 return R.error(imgMsgResp.getErrmsg());
             }
             msgJson = JSONObject.toJSONString(imgMsgDTO);
+            WxwSendCDNImgMsgRespDTO imgData = imgMsgResp.getData();
+            qwMsgId = imgData.getMsg_id();
+            qwAppInfo = imgData.getApp_info();
         }
         // 小程序
         else if (MsgType.MINI_PROGRAM == msgType) {
@@ -441,6 +449,49 @@ public class QwMsgServiceImpl extends ServiceImpl<QwMsgMapper, QwMsg> implements
 
             msgJson = json.toJSONString();
             param.setContent(msgJson);
+
+            WxWorkSendAppMsgRespDTO appData = appMsgResp.getData();
+            qwMsgId = appData.getMsg_id();
+            qwAppInfo = appData.getApp_info();
+        }
+        // 消息撤销
+        else if (MsgType.CANCEL == msgType) {
+            QwMsg qwMsg = qwMsgMapper.selectQwMsgByMsgId(param.getMsgId());
+            if (qwMsg == null || qwMsg.getQwMsgId() == null) {
+                return R.error("消息不存在");
+            }
+
+            if (!Objects.equals(qwSession.getSessionId(), param.getSessionId())) {
+                return R.error("会话错误,操作不允许");
+            }
+
+            WxwRevokeMsgDTO params = new WxwRevokeMsgDTO();
+            params.setUuid(uuid);
+            params.setMsgid(qwMsg.getQwMsgId().intValue());
+            params.setRoomid(isRoom ? sendUserId : 0);
+            WxWorkResponseDTO wxWorkResponseDTO = wxWorkService.RevokeMsg(params, serverId);
+            if (wxWorkResponseDTO.getErrcode() != 0) {
+                return R.error(wxWorkResponseDTO.getErrmsg());
+            }
+
+            qwMsg.setMsgType(6);
+            qwMsgMapper.updateQwMsg(qwMsg);
+
+            // 组装返回消息结构
+            QwMessageListVO listVO = new QwMessageListVO();
+            QWFromUser qwFromUser = new QWFromUser();
+            qwFromUser.setId(Long.parseLong(qwMsg.getQwUserId()));
+            qwFromUser.setDisplayName(qwUser.getQwUserName());
+            qwFromUser.setAvatar(qwUser.getAvatar());
+            listVO.setType(msgType.getValue());
+            listVO.setStatus("succeed");
+            listVO.setFromUser(qwFromUser);
+            listVO.setSendTime(qwMsg.getCreateTime().getTime());
+            listVO.setId(qwMsg.getMsgId().toString());
+            listVO.setContent(qwMsg.getContent());
+            listVO.setToContactId(String.valueOf(param.getSessionId()));
+            listVO.setAppKey(qwUser.getAppKey());
+            return R.ok().put("data", listVO);
         } else {
             return R.error("暂不支持的消息类型");
         }
@@ -457,6 +508,8 @@ public class QwMsgServiceImpl extends ServiceImpl<QwMsgMapper, QwMsg> implements
         qwMsg.setStatus(0);
         qwMsg.setQwUserId(qwSession.getQwUserId());
         qwMsg.setCreateTime(new Date());
+        qwMsg.setQwMsgId(qwMsgId);
+        qwMsg.setQwAppInfo(qwAppInfo);
 
         if (Objects.isNull(qwExternalContact)) {
             qwMsg.setAvatar(qwUser.getAvatar());
@@ -468,6 +521,11 @@ public class QwMsgServiceImpl extends ServiceImpl<QwMsgMapper, QwMsg> implements
         }
         qwMsgMapper.insertQwMsg(qwMsg);
 
+        qwSession.setLastMsgId(qwMsg.getMsgId());
+        qwSession.setLastSendTime(qwMsg.getCreateTime().getTime());
+        qwSession.setLastContent(qwMsg.getContent());
+        qwSessionMapper.updateQwSession(qwSession);
+
         // 组装返回消息结构
         QwMessageListVO listVO = new QwMessageListVO();
         QWFromUser qwFromUser = new QWFromUser();

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

@@ -21,7 +21,6 @@ public class QwMessageListVO {
     private String toContactId;
     private QWFromUser fromUser;
     private String appKey;
-    @JSONField(serialize = false)
     private Long companyUserId;
     private String extId;
 

+ 1 - 0
fs-service/src/main/java/com/fs/wxwork/dto/WxWorkMessageDTO.java

@@ -21,6 +21,7 @@ public class WxWorkMessageDTO {
     private String recordwording;    //通话时长
     private Integer recordtype;         //通话5
     private Long room_conversation_id; // 群ChatID
+    private String revoke_ref_appinfo; //撤回消息的appinfo
 
     // 图片
     private String file_id;

+ 6 - 1
fs-service/src/main/java/com/fs/wxwork/dto/WxWorkSendAppMsgRespDTO.java

@@ -35,7 +35,7 @@ public class WxWorkSendAppMsgRespDTO {
     /**
      * 应用信息, 来源等附加信息
      */
-    private String appInfo;
+    private String app_info;
     /**
      * 小程序 App ID
      */
@@ -64,4 +64,9 @@ public class WxWorkSendAppMsgRespDTO {
      */
     private Boolean isRoom;
 
+    /**
+     * 消息ID
+     */
+    private Long msg_id;
+
 }

+ 5 - 2
fs-service/src/main/java/com/fs/wxwork/dto/WxWorkSendTextMsgRespDTO.java

@@ -1,7 +1,10 @@
 package com.fs.wxwork.dto;
 
+import lombok.Data;
+
 import java.util.List;
 
+@Data
 public class WxWorkSendTextMsgRespDTO {
     /**
      * 接收者用户 ID
@@ -22,7 +25,7 @@ public class WxWorkSendTextMsgRespDTO {
     /**
      * 应用信息(来源等)
      */
-    private String appInfo;
+    private String app_info;
     /**
      * 是否为群组消息
      * <p>
@@ -37,7 +40,7 @@ public class WxWorkSendTextMsgRespDTO {
     /**
      * 消息 ID (客户端生成)
      */
-    private Long msgId;
+    private Long msg_id;
     /**
      * 消息 ID (服务器生成)
      */

+ 2 - 6
fs-service/src/main/resources/mapper/qw/QwExternalContactMapper.xml

@@ -802,15 +802,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             ifnull(qec.avatar, qs.avatar)                       as avatar,
             ifnull(nullif(qs.first_letter, ''), '未定义')        as `index`,
             0                                                   as unread,
-            unix_timestamp(qm.create_time) * 1000               as lastSendTime,
-            qm.content                                          as lastContent,
+            qs.last_send_time                                   as lastSendTime,
+            qs.last_content                                     as lastContent,
             false                                               as isGroup
         from qw_external_contact qec
         left join qw_session qs on qec.id = qs.qw_ext_id and qs.is_room = 0
-        left join (
-            select session_id, max(msg_id) as max_msg_id from qw_msg group by session_id
-        ) latest_msg on latest_msg.session_id = qs.session_id
-        left join qw_msg qm on qm.msg_id = latest_msg.max_msg_id
         where qec.qw_user_id = #{qwUserId}
         <if test="name != null and name != ''">
             and qec.name like concat('%', #{name}, '%')

+ 2 - 6
fs-service/src/main/resources/mapper/qw/QwGroupChatMapper.xml

@@ -226,16 +226,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             qs.avatar                                           as avatar,
             ifnull(nullif(qs.first_letter, ''), '未定义')        as `index`,
             0                                                   as unread,
-            unix_timestamp(qm.create_time) * 1000               as lastSendTime,
-            qm.content                                          as lastContent,
+            qs.last_send_time                                   as lastSendTime,
+            qs.last_content                                     as lastContent,
             true                                                as isGroup
         from qw_group_chat qgc
         inner join qw_user qu on qu.qw_user_id = qgc.owner
         left join qw_session qs on qgc.chat_id = qs.chat_id and qs.is_room = 1
-        left join (
-            select session_id, max(msg_id) as max_msg_id from qw_msg group by session_id
-        ) latest_msg on latest_msg.session_id = qs.session_id
-        left join qw_msg qm on qm.msg_id = latest_msg.max_msg_id
         where qu.id = #{qwUserId}
         <if test="name != null and name != ''">
             and qgc.name like concat('%', #{name}, '%')

+ 11 - 1
fs-service/src/main/resources/mapper/qw/QwMsgMapper.xml

@@ -20,10 +20,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="status"    column="status"    />
         <result property="nickName"    column="nick_name"    />
         <result property="avatar"    column="avatar"    />
+        <result property="qwMsgId"    column="qw_msg_id"    />
+        <result property="qwAppInfo"    column="qw_app_info"    />
     </resultMap>
 
     <sql id="selectQwMsgVo">
-        select msg_id, session_id, qw_ext_id, qw_user_id, content, msg_type, send_type, company_id, role_id, company_user_id, create_time, msg_json, status, nick_name, avatar from qw_msg
+        select msg_id, session_id, qw_ext_id, qw_user_id, content, msg_type, send_type, company_id, role_id, company_user_id, create_time, msg_json, status, nick_name, avatar, qw_msg_id, qw_app_info from qw_msg
     </sql>
 
     <select id="selectQwMsgList" parameterType="QwMsg" resultMap="QwMsgResult">
@@ -42,6 +44,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="status != null "> and status = #{status}</if>
             <if test="nickName != null  and nickName != ''"> and nick_name like concat( #{nickName}, '%')</if>
             <if test="avatar != null  and avatar != ''"> and avatar = #{avatar}</if>
+            <if test="qwMsgId != null"> and qw_msg_id = #{qwMsgId}</if>
+            <if test="qwAppInfo != null  and qwAppInfo != ''"> and qw_app_info = #{qwAppInfo}</if>
         </where>
     </select>
 
@@ -67,6 +71,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="status != null">status,</if>
             <if test="nickName != null">nick_name,</if>
             <if test="avatar != null">avatar,</if>
+            <if test="qwMsgId != null">qw_msg_id,</if>
+            <if test="qwAppInfo != null">qw_app_info,</if>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="sessionId != null">#{sessionId},</if>
@@ -83,6 +89,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="status != null">#{status},</if>
             <if test="nickName != null">#{nickName},</if>
             <if test="avatar != null">#{avatar},</if>
+            <if test="qwMsgId != null">#{qwMsgId},</if>
+            <if test="qwAppInfo != null">#{qwAppInfo},</if>
          </trim>
     </insert>
 
@@ -103,6 +111,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="status != null">status = #{status},</if>
             <if test="nickName != null">nick_name = #{nickName},</if>
             <if test="avatar != null">avatar = #{avatar},</if>
+            <if test="qwMsgId != null">qw_msg_id = #{qwMsgId},</if>
+            <if test="qwAppInfo != null">qw_app_info = #{qwAppInfo},</if>
         </trim>
         where msg_id = #{msgId}
     </update>

+ 21 - 8
fs-service/src/main/resources/mapper/qw/QwSessionMapper.xml

@@ -21,10 +21,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="companyUserId"    column="company_user_id"    />
         <result property="isRoom"    column="is_room"    />
         <result property="firstLetter"    column="first_letter"    />
+        <result property="lastMsgId"    column="last_msg_id"    />
+        <result property="lastSendTime"    column="last_send_time"    />
+        <result property="lastContent"    column="last_content"    />
     </resultMap>
 
     <sql id="selectQwSessionVo">
-        select session_id,qw_ext_wx_id, chat_id, qw_ext_id, corp_id, qw_user_id, create_time, update_time, status, company_id, user_type, nick_name, avatar, company_user_id, is_room, first_letter from qw_session
+        select session_id,qw_ext_wx_id, chat_id, qw_ext_id, corp_id, qw_user_id, create_time, update_time, status,
+               company_id, user_type, nick_name, avatar, company_user_id, is_room, first_letter, last_msg_id, last_send_time,last_content  from qw_session
     </sql>
 
     <select id="selectQwSessionList" parameterType="QwSession" resultMap="QwSessionResult">
@@ -42,6 +46,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="companyUserId != null "> and company_user_id = #{companyUserId}</if>
             <if test="isRoom != null "> and is_room = #{isRoom}</if>
             <if test="firstLetter != null "> and first_letter = #{firstLetter}</if>
+            <if test="lastMsgId != null "> and last_msg_id = #{lastMsgId}</if>
+            <if test="lastSendTime != null "> and last_send_time = #{lastSendTime}</if>
+            <if test="lastContent != null "> and last_content = #{lastContent}</if>
         </where>
     </select>
 
@@ -61,25 +68,22 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             s.is_room                                               as isGroup,
             (u.qw_repeat = 1 OR u.user_repeat = 1)                  as isRepeat,
             false                                                   as isPend,
-            qm.msg_id                                               as msgId,
+            s.last_msg_id                                           as msgId,
             case qm.msg_type
                 when 1 then 'text'
                 when 2 then 'image'
                 when 3 then 'emotiondynamic'
                 when 4 then 'voice'
                 when 5 then 'miniprogram'
+                when 6 then 'cancel'
                 else 'text'
             end                                                     as type,
-            qm.content                                              as lastContent,
-            unix_timestamp(qm.create_time) * 1000                   as lastSendTime,
+            s.last_content                                          as lastContent,
+            s.last_send_time                                        as lastSendTime,
             0                                                       as unread
         from qw_session s
         left join qw_external_contact ec on s.qw_ext_id = ec.id
         left join fs_user u on ec.fs_user_id = u.user_id
-        left join (
-            select session_id, max(msg_id) as max_msg_id from qw_msg group by session_id
-        ) latest_msg on latest_msg.session_id = s.session_id
-        left join qw_msg qm on qm.msg_id = latest_msg.max_msg_id
         where s.qw_user_id = #{params.qwUserId}
         <if test="params.removeBlack != null and params.removeBlack">
             and ec.comment_status = 0
@@ -108,6 +112,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="companyUserId != null">company_user_id,</if>
             <if test="isRoom != null">is_room,</if>
             <if test="firstLetter != null">first_letter,</if>
+            <if test="lastMsgId != null">last_msg_id,</if>
+            <if test="lastSendTime != null">last_send_time,</if>
+            <if test="lastContent != null">last_content,</if>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="chatId != null">#{chatId},</if>
@@ -125,6 +132,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="companyUserId != null">#{companyUserId},</if>
             <if test="isRoom != null">#{isRoom},</if>
             <if test="firstLetter != null">#{firstLetter},</if>
+            <if test="lastMsgId != null">#{lastMsgId},</if>
+            <if test="lastSendTime != null">#{lastSendTime},</if>
+            <if test="lastContent != null">#{lastContent},</if>
          </trim>
     </insert>
 
@@ -146,6 +156,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="companyUserId != null">company_user_id = #{companyUserId},</if>
             <if test="isRoom != null">is_room = #{isRoom},</if>
             <if test="firstLetter != null">first_letter = #{firstLetter},</if>
+            <if test="lastMsgId != null">last_msg_id = #{lastMsgId},</if>
+            <if test="lastSendTime != null">last_send_time = #{lastSendTime},</if>
+            <if test="lastContent != null">last_content = #{lastContent},</if>
         </trim>
         where session_id = #{sessionId}
     </update>

+ 1 - 1
pom.xml

@@ -21,7 +21,7 @@
 		<kaptcha.version>2.3.2</kaptcha.version>
 		<mybatis-spring-boot.version>2.1.4</mybatis-spring-boot.version>
         <pagehelper.boot.version>1.3.1</pagehelper.boot.version>
-        <fastjson.version>1.2.76</fastjson.version>
+        <fastjson.version>2.0.60</fastjson.version>
         <oshi.version>5.8.0</oshi.version>
         <jna.version>5.8.0</jna.version>
         <commons.io.version>2.11.0</commons.io.version>