فهرست منبع

1.弘珍医药和九州在线提交ai对话功能

jzp 1 هفته پیش
والد
کامیت
bb6ceb4878
100فایلهای تغییر یافته به همراه4026 افزوده شده و 313 حذف شده
  1. 2 1
      fs-admin/src/main/resources/application.yml
  2. 1 2
      fs-company/src/main/java/com/fs/company/controller/course/FsCourseWatchLogController.java
  3. 1 1
      fs-company/src/main/java/com/fs/company/controller/fastGpt/FastGptChatSessionController.java
  4. 5 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwUserController.java
  5. 3 2
      fs-company/src/main/resources/application.yml
  6. 49 0
      fs-qw-api-msg/src/main/java/com/fs/app/config/QWConfigProperties.java
  7. 182 13
      fs-qw-api-msg/src/main/java/com/fs/app/controller/QwMsgController.java
  8. 1 1
      fs-qw-api-msg/src/main/java/com/fs/app/controller/imgTest.java
  9. 55 0
      fs-qw-api-msg/src/main/java/com/fs/app/controller/test.java
  10. 20 3
      fs-qw-api-msg/src/main/java/com/fs/framework/config/DataSourceConfig.java
  11. 1 1
      fs-qw-api-msg/src/main/java/com/fs/framework/config/MyBatisConfig.java
  12. 2 0
      fs-qw-api-msg/src/main/java/com/fs/framework/config/SecurityConfig.java
  13. 3 2
      fs-qw-api-msg/src/main/resources/application.yml
  14. 4 2
      fs-qw-api/src/main/resources/application.yml
  15. 45 0
      fs-service/src/main/java/com/fs/company/domain/CompanyCompanyFsuser.java
  16. 20 0
      fs-service/src/main/java/com/fs/company/domain/CompanyUser.java
  17. 68 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyCompanyFsuserMapper.java
  18. 2 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyUserMapper.java
  19. 15 0
      fs-service/src/main/java/com/fs/company/vo/OptionVO.java
  20. 1 1
      fs-service/src/main/java/com/fs/core/config/WxMaConfiguration.java
  21. 1 1
      fs-service/src/main/java/com/fs/course/domain/FsCourseLink.java
  22. 3 3
      fs-service/src/main/java/com/fs/course/domain/FsCourseWatchLog.java
  23. 2 2
      fs-service/src/main/java/com/fs/course/param/FsCourseLinkCreateParam.java
  24. 3 3
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseLinkServiceImpl.java
  25. 3 3
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java
  26. 3 3
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  27. 23 0
      fs-service/src/main/java/com/fs/fastGpt/domain/FastGptEventLog.java
  28. 23 0
      fs-service/src/main/java/com/fs/fastGpt/domain/FastGptEventTokenLog.java
  29. 12 0
      fs-service/src/main/java/com/fs/fastGpt/domain/FastGptKeyword.java
  30. 19 0
      fs-service/src/main/java/com/fs/fastGpt/domain/FastGptKeywordArtificial.java
  31. 55 0
      fs-service/src/main/java/com/fs/fastGpt/domain/FastGptKeywordSend.java
  32. 2 0
      fs-service/src/main/java/com/fs/fastGpt/domain/FastGptRole.java
  33. 77 0
      fs-service/src/main/java/com/fs/fastGpt/domain/FastgptChatArtificialWords.java
  34. 56 0
      fs-service/src/main/java/com/fs/fastGpt/domain/FastgptEventLogTotal.java
  35. 7 4
      fs-service/src/main/java/com/fs/fastGpt/mapper/FastGptChatMsgMapper.java
  36. 72 0
      fs-service/src/main/java/com/fs/fastGpt/mapper/FastGptKeywordSendMapper.java
  37. 1 1
      fs-service/src/main/java/com/fs/fastGpt/mapper/FastGptRoleMapper.java
  38. 4 0
      fs-service/src/main/java/com/fs/fastGpt/service/AiHookService.java
  39. 9 1
      fs-service/src/main/java/com/fs/fastGpt/service/IFastGptChatMsgService.java
  40. 72 0
      fs-service/src/main/java/com/fs/fastGpt/service/IFastGptKeywordSendService.java
  41. 497 197
      fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java
  42. 4 4
      fs-service/src/main/java/com/fs/fastGpt/service/impl/AiNewServiceImpl.java
  43. 4 4
      fs-service/src/main/java/com/fs/fastGpt/service/impl/AiServiceImpl.java
  44. 24 3
      fs-service/src/main/java/com/fs/fastGpt/service/impl/FastGptChatMsgServiceImpl.java
  45. 116 0
      fs-service/src/main/java/com/fs/fastGpt/service/impl/FastGptKeywordSendServiceImpl.java
  46. 78 0
      fs-service/src/main/java/com/fs/fastGpt/vo/FastGptChatSessionVo.java
  47. 31 0
      fs-service/src/main/java/com/fs/fastgptApi/param/DouBaoAiParam.java
  48. 118 20
      fs-service/src/main/java/com/fs/fastgptApi/util/AiImgUtil.java
  49. 28 0
      fs-service/src/main/java/com/fs/fastgptApi/util/EventLogQueue.java
  50. 154 0
      fs-service/src/main/java/com/fs/fastgptApi/util/EventLogUtils.java
  51. 33 0
      fs-service/src/main/java/com/fs/his/dto/PayloadDTO.java
  52. 2 0
      fs-service/src/main/java/com/fs/his/mapper/FsStoreOrderMapper.java
  53. 11 0
      fs-service/src/main/java/com/fs/his/param/PushFgOrderConfig.java
  54. 1 0
      fs-service/src/main/java/com/fs/his/service/IFsStoreOrderService.java
  55. 4 0
      fs-service/src/main/java/com/fs/his/service/impl/FsStoreOrderServiceImpl.java
  56. 51 0
      fs-service/src/main/java/com/fs/his/vo/FsComplaintOrderExportVO.java
  57. 13 0
      fs-service/src/main/java/com/fs/his/vo/FsStoreOrderAndUserVo.java
  58. 35 0
      fs-service/src/main/java/com/fs/his/vo/FsStoreOrderLogisticsFgVO.java
  59. 25 0
      fs-service/src/main/java/com/fs/his/vo/FsStoreOrderPushFgVO.java
  60. 29 0
      fs-service/src/main/java/com/fs/im/config/IMConfig.java
  61. 15 0
      fs-service/src/main/java/com/fs/im/dto/OpenImConversationDTO.java
  62. 11 0
      fs-service/src/main/java/com/fs/im/dto/OpenImEditConversationDTO.java
  63. 45 0
      fs-service/src/main/java/com/fs/im/dto/OpenImMsgDTO.java
  64. 13 0
      fs-service/src/main/java/com/fs/im/dto/OpenImResponseDTO.java
  65. 34 0
      fs-service/src/main/java/com/fs/im/service/OpenIMService.java
  66. 882 0
      fs-service/src/main/java/com/fs/im/service/impl/OpenIMServiceImpl.java
  67. 28 0
      fs-service/src/main/java/com/fs/im/vo/OpenImMsgCallBackVO.java
  68. 18 0
      fs-service/src/main/java/com/fs/im/vo/OpenImResponseDTOTest.java
  69. 12 0
      fs-service/src/main/java/com/fs/qw/domain/QwExternalContact.java
  70. 2 0
      fs-service/src/main/java/com/fs/qw/domain/QwExternalContactInfo.java
  71. 12 0
      fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java
  72. 5 0
      fs-service/src/main/java/com/fs/qw/param/QwAutoSopTimeParam.java
  73. 13 0
      fs-service/src/main/java/com/fs/qw/param/QwExternalContactUpdateNoteParam.java
  74. 12 0
      fs-service/src/main/java/com/fs/qw/param/RepeatParam.java
  75. 2 0
      fs-service/src/main/java/com/fs/qw/service/IQwExternalContactService.java
  76. 2 0
      fs-service/src/main/java/com/fs/qw/service/IQwUserService.java
  77. 2 2
      fs-service/src/main/java/com/fs/qw/service/impl/QwContactWayServiceImpl.java
  78. 8 3
      fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java
  79. 1 1
      fs-service/src/main/java/com/fs/qw/service/impl/QwGroupMsgServiceImpl.java
  80. 62 11
      fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceImpl.java
  81. 6 2
      fs-service/src/main/java/com/fs/qw/service/impl/QwUserVoiceLogServiceImpl.java
  82. 14 1
      fs-service/src/main/java/com/fs/qw/vo/QwUserVO.java
  83. 6 0
      fs-service/src/main/java/com/fs/sop/mapper/QwSopLogsMapper.java
  84. 4 4
      fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java
  85. 2 2
      fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsServiceImpl.java
  86. 3 0
      fs-service/src/main/java/com/fs/watch/mapper/WatchCompanyUserMapper.java
  87. 1 1
      fs-service/src/main/java/com/fs/wxwork/service/WxWorkServiceImpl.java
  88. 2 1
      fs-service/src/main/resources/application-config-druid-hzyy.yml
  89. 1 1
      fs-service/src/main/resources/application-config-druid-jzzx.yml
  90. 168 0
      fs-service/src/main/resources/application-druid-hzyy-test.yml
  91. 18 0
      fs-service/src/main/resources/application-druid-hzyy.yml
  92. 18 0
      fs-service/src/main/resources/application-druid-jzzx.yml
  93. 103 0
      fs-service/src/main/resources/mapper/company/CompanyCompanyFsuserMapper.xml
  94. 3 0
      fs-service/src/main/resources/mapper/company/CompanyUserMapper.xml
  95. 139 4
      fs-service/src/main/resources/mapper/fastGpt/FastGptChatMsgMapper.xml
  96. 149 0
      fs-service/src/main/resources/mapper/fastGpt/FastGptKeywordSendMapper.xml
  97. 5 1
      fs-service/src/main/resources/mapper/fastGpt/FastGptRoleMapper.xml
  98. 5 0
      fs-service/src/main/resources/mapper/his/FsStoreOrderMapper.xml
  99. 4 1
      fs-service/src/main/resources/mapper/qw/QwExternalContactMapper.xml
  100. 11 0
      fs-service/src/main/resources/mapper/sop/QwSopLogsMapper.xml

+ 2 - 1
fs-admin/src/main/resources/application.yml

@@ -4,9 +4,10 @@ server:
 # Spring配置
 spring:
   profiles:
-    active: druid-myhk-test
+#    active: druid-myhk-test
 #    active: druid-hdt
 #    active: druid-yzt
 #    active: druid-sxjz
 #    active: druid-sft
+    active: druid-hzyy-test
 

+ 1 - 2
fs-company/src/main/java/com/fs/company/controller/course/FsCourseWatchLogController.java

@@ -226,7 +226,6 @@ public class FsCourseWatchLogController extends BaseController
     {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
         param.setCompanyId( loginUser.getCompany().getCompanyId());
-        param.setCompanyUserId( loginUser.getUser().getUserId());
         List<FsCourseWatchLogListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogListVO(param);
         ExcelUtil<FsCourseWatchLogListVO> util = new ExcelUtil<FsCourseWatchLogListVO>(FsCourseWatchLogListVO.class);
         return util.exportExcel(list, "短链课程看课记录数据");
@@ -241,7 +240,7 @@ public class FsCourseWatchLogController extends BaseController
     public AjaxResult myExport(FsCourseWatchLogListParam param)
     {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-//        param.setCompanyId( loginUser.getCompany().getCompanyId());
+        param.setCompanyId( loginUser.getCompany().getCompanyId());
         param.setCompanyUserId( loginUser.getUser().getUserId());
         List<FsCourseWatchLogListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogListVO(param);
         ExcelUtil<FsCourseWatchLogListVO> util = new ExcelUtil<FsCourseWatchLogListVO>(FsCourseWatchLogListVO.class);

+ 1 - 1
fs-company/src/main/java/com/fs/company/controller/fastGpt/FastGptChatSessionController.java

@@ -79,7 +79,7 @@ public class FastGptChatSessionController extends BaseController
     {
         FastGptChatSessionCVO sessionCVO = fastGptChatSessionService.selectFastGptChatSessionCVOBySessionId(sessionId);
 
-        List<FastGptChatMsgCVO> list = fastGptChatMsgService.selectFastGptChatMsgCVOBySessionId(sessionId);
+        List<FastGptChatMsgCVO> list = fastGptChatMsgService.selectFastGptChatMsgCVOBySessionId(sessionId,sessionCVO.getUserId());
 
         return R.ok().put("data",sessionCVO).put("list",list);
     }

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

@@ -191,6 +191,11 @@ public class QwUserController extends BaseController
     public R TwoCodeStatus(@RequestBody QwLoginHookParam loginParam){
         return qwUserService.getTwoCodeStatus(loginParam);
     }
+
+    @PostMapping("/getQwIpadStatus")
+    public R getQwIpadStatus(@RequestBody QwLoginHookParam loginParam){
+        return qwUserService.getLoginQwIpadStatus(loginParam);
+    }
     /**
     * 直接授权key
     */

+ 3 - 2
fs-company/src/main/resources/application.yml

@@ -4,10 +4,11 @@ server:
 spring:
   profiles:
 #    active: druid-fcky-test
-    active: dev
-#    active: druid-jzzx
+#    active: dev
+#    active: druid-jzzx-test
 #    active: druid-hdt
 #    active: druid-sxjz
 #    active: druid-yzt
 #    active: druid-myhk
 #    active: druid-sft
+    active: druid-hzyy

+ 49 - 0
fs-qw-api-msg/src/main/java/com/fs/app/config/QWConfigProperties.java

@@ -0,0 +1,49 @@
+package com.fs.app.config;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+@Component
+public class QWConfigProperties {
+
+    @Value("${custom.token}")
+    private String token;
+
+    @Value("${custom.encoding-aes-key}")
+    private String encodingAesKey;
+
+    @Value("${custom.corp-id}")
+    private String corpId;
+    @Value("${custom.secret}")
+    private String secret;
+
+    @Value("${custom.private-key-path}")
+    private String privateKeyPath;
+
+    @Value("${custom.webhook-url}")
+    private String webhookUrl;
+
+    public String getToken() {
+        return token;
+    }
+
+    public String getEncodingAesKey() {
+        return encodingAesKey;
+    }
+
+    public String getCorpId() {
+        return corpId;
+    }
+
+    public String getSecret() {
+        return secret;
+    }
+
+    public String getPrivateKeyPath() {
+        return privateKeyPath;
+    }
+
+    public String getWebhookUrl() {
+        return webhookUrl;
+    }
+}

+ 182 - 13
fs-qw-api-msg/src/main/java/com/fs/app/controller/QwMsgController.java

@@ -1,12 +1,22 @@
 package com.fs.app.controller;
 
 import com.alibaba.fastjson.JSON;
+import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.uuid.IdUtils;
 import com.fs.fastGpt.service.AiHookService;
+import com.fs.his.domain.FsStoreOrder;
+import com.fs.his.service.IFsStoreOrderService;
+import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.domain.QwUser;
+import com.fs.qw.mapper.QwExternalContactMapper;
 import com.fs.qw.mapper.QwUserMapper;
+import com.fs.qw.service.IQwExternalContactService;
 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.voice.utils.StringUtil;
 import com.fs.wxwork.dto.*;
 import com.fs.wxwork.service.WxWorkService;
 import io.swagger.annotations.Api;
@@ -15,8 +25,7 @@ import org.json.JSONObject;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
-import java.util.HashMap;
-import java.util.Map;
+import java.util.*;
 import java.util.concurrent.TimeUnit;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -37,6 +46,70 @@ public class QwMsgController {
     WxWorkService wxWorkService;
     @Autowired
     IQwUserVoiceLogService qwUserVoiceLogService;
+    @Autowired
+    IFsStoreOrderService fsStoreOrderService;
+    @Autowired
+    IQwExternalContactService externalContactService;
+    @Autowired
+    QwExternalContactMapper qwExternalContactMapper;
+    @Autowired
+    SopUserLogsInfoMapper sopUserLogsInfoMapper;
+    @Autowired
+    QwSopLogsMapper qwSopLogsMapper;
+
+    @GetMapping("/sendExpressInfo/{orderId}")
+    public R sendExpressInfo(@PathVariable Long orderId){
+        FsStoreOrder order = fsStoreOrderService.selectFsStoreOrderByOrderId(orderId);
+        if(order != null && order.getUserId() != null){
+            List<QwExternalContact> qwExternalContact = externalContactService.selectQwExternalContactByFsUserId(order.getUserId());
+            if(qwExternalContact != null && !qwExternalContact.isEmpty()){
+                for (QwExternalContact externalContact : qwExternalContact) {
+                    Long qwUserId = externalContact.getQwUserId();
+                    if(qwUserId != null ){
+                        QwUser qwUser = qwUserMapper.selectQwUserById(qwUserId);
+                        if(qwUser != null && qwUser.getUid() != null && qwUser.getServerId() != null && qwUser.getServerStatus() == 1 && qwUser.getIpadStatus() == 1){
+                            WxWorkUserId2VidDTO wxWorkUserId2VidDTO = new WxWorkUserId2VidDTO();
+                            wxWorkUserId2VidDTO.setOpenid(Collections.singletonList(externalContact.getExternalUserId()));
+                            wxWorkUserId2VidDTO.setUuid(qwUser.getUid());
+                            WxWorkResponseDTO<List<WxWorkVid2UserIdRespDTO>> WxWorkVid2UserIdRespDTO = wxWorkService.UserId2Vid(wxWorkUserId2VidDTO,qwUser.getServerId());
+                            List<WxWorkVid2UserIdRespDTO> data = WxWorkVid2UserIdRespDTO.getData();
+                            StringBuilder sBuilder = new StringBuilder();
+                            if(data != null && !data.isEmpty()){
+                                Long sendId = data.get(0).getUser_id();
+                                switch (order.getStatus())
+                                {
+                                    case -1:
+                                    case -2:
+                                    case 1:
+                                        break;
+                                    case 2:
+                                        sBuilder.append("您好,您购买的").append(order.getPackageName()).append("正在准备发货,请耐心等待;\n").append("\uD83C\uDF39\uD83C\uDF39\uD83C\uDF39");
+                                        break;
+                                    case 3:
+                                    case 4:
+                                    case 5:
+                                        break;
+                                }
+                                if(!"".contentEquals(sBuilder)){
+                                    //2.发送模板中的文字内容
+                                    String content = sBuilder.toString();
+                                    WxWorkSendTextMsgDTO wxWorkSendTextMsgDTO = new WxWorkSendTextMsgDTO();
+                                    wxWorkSendTextMsgDTO.setSend_userid(sendId);
+                                    wxWorkSendTextMsgDTO.setUuid(qwUser.getUid());
+                                    wxWorkSendTextMsgDTO.setContent(content);
+                                    wxWorkSendTextMsgDTO.setIsRoom(false);
+                                    wxWorkService.SendTextMsg(wxWorkSendTextMsgDTO,qwUser.getServerId());
+                                }
+                                return R.ok();
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
 
     @PostMapping("/callback/{serverId}")
     @ResponseBody
@@ -80,7 +153,7 @@ public class QwMsgController {
                     WxWorkGetQrCodeDTO wxWorkGetQrCodeDTO = new WxWorkGetQrCodeDTO();
                     wxWorkGetQrCodeDTO.setUuid(wxWorkMsgResp.getUuid());
                     wxWorkService.LoginOut(wxWorkGetQrCodeDTO,serverId);
-                    System.out.println("调用退出登录");
+                    log.info("调用退出登录");
                     break;
                 }
                 if (!qu.getQwUserId().equals(jsonObject.get("acctid"))){
@@ -89,7 +162,7 @@ public class QwMsgController {
                     WxWorkGetQrCodeDTO wxWorkGetQrCodeDTO = new WxWorkGetQrCodeDTO();
                     wxWorkGetQrCodeDTO.setUuid(wxWorkMsgResp.getUuid());
                     wxWorkService.LoginOut(wxWorkGetQrCodeDTO,serverId);
-                    System.out.println("调用退出登录");
+                    log.info("调用退出登录");
                     break;
                 }
                 QwUser qwUser = new QwUser();
@@ -101,39 +174,45 @@ public class QwMsgController {
                 redisCache.setCacheObject("qrCodeUid:"+wxWorkMsgResp.getUuid(),104001,10, TimeUnit.MINUTES);
                 break;
             case 100006:
-                System.out.println("企业切换");
+
                 break;
             case 100004:
                 System.out.println("需要验证二维码消息");
                 redisCache.setCacheObject("qrCodeUid:"+wxWorkMsgResp.getUuid(),100004,10, TimeUnit.MINUTES);
                 break;
             case 100012:
-                System.out.println("需要二次验证");
+                log.info("需要二次验证:"+wxWorkMsgResp.getJson());
+
                 redisCache.setCacheObject("qrCodeUid:"+wxWorkMsgResp.getUuid(),100012,10, TimeUnit.MINUTES);
 
                 break;
             case 100005:
-                System.out.println("手机端结束登录");
+
                 log.info("手机端结束登录:"+wxWorkMsgResp.getJson());
                 qwUserStatus(wxWorkMsgResp.getUuid(),0);
                 break;
             case 100008:
-                System.out.println("当前账号在其他设备登录");
-                log.info("当前账号在其他设备登录:"+wxWorkMsgResp.getJson());
-                qwUserStatus(wxWorkMsgResp.getUuid(),0);
+                QwUser vidUser = qwUserMapper.selectQwUserById(id);
+                if (vidUser.getUid().equals(wxWorkMsgResp.getUuid())){
+                    log.info("当前账号在其他设备登录:"+wxWorkMsgResp.getJson());
+                    qwUserStatus(wxWorkMsgResp.getUuid(),0);
+                }
+                log.info("当前账号重新登录:"+wxWorkMsgResp.getJson());
                 break;
             case 100007:
-                System.out.println("异常断开");
                 log.info("异常断开:"+wxWorkMsgResp.getJson());
                 qwUserStatus(wxWorkMsgResp.getUuid(),0);
                 break;
             case 100009:
-                System.out.println("二次验证");
                 log.info("二次验证:"+wxWorkMsgResp.getJson());
                 qwUserStatus(wxWorkMsgResp.getUuid(),0);
                 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;
@@ -155,6 +234,15 @@ public class QwMsgController {
                         ste.setUuid(wxWorkMsgResp.getUuid());
                         WxWorkResponseDTO<WxwSpeechToTextEntityRespDTO> dto = wxWorkService.SpeechToTextEntity(ste, serverId);
                         System.out.println(dto);
+                        if(dto.getErrcode() != 0){
+                            try {
+                                TimeUnit.SECONDS.sleep(1); // 阻塞1秒
+                            } catch (InterruptedException e) {
+                                Thread.currentThread().interrupt(); // 处理中断异常
+                                System.out.println("第一次语音转换失败");
+                            }
+                            dto = wxWorkService.SpeechToTextEntity(ste, serverId);
+                        }
                         WxwSpeechToTextEntityRespDTO data = dto.getData();
                         content = data.getText();
                         System.out.println("语音消息"+content);
@@ -239,6 +327,87 @@ public class QwMsgController {
         return map;
     }
 
+    /**
+     * 处理被拉黑的用户
+     * @param qwUserId
+     * @param wxWorkMessageDTO
+     * @param uuid
+     * @param status
+     */
+    private void handleSopBlockOrDel(Long qwUserId,WxWorkMessageDTO wxWorkMessageDTO,String uuid, Integer status){
+        QwUser user = qwUserMapper.selectQwUserById(qwUserId);
+        //查询接收人
+        if(user==null){
+            System.out.println("查询接收人为空");
+        }
+        if(user.getFastGptRoleId()==null){
+            System.out.println("未绑定角色");
+        }
+        Long serverId = user.getServerId();
+        System.out.println("服务器id"+serverId);
+        if (serverId == null) {
+            System.out.println("服务id为空");
+        }
+
+        WxWorkVid2UserIdDTO wxWorkVid2UserIdDTO = new WxWorkVid2UserIdDTO();
+        wxWorkVid2UserIdDTO.setUser_id(Arrays.asList(wxWorkMessageDTO.getReceiver()));
+        wxWorkVid2UserIdDTO.setUuid(uuid);
+        //下面的方法是返回当前对象
+        WxWorkResponseDTO<List<WxWorkVid2UserIdRespDTO>> WxWorkVid2UserIdRespDTO = wxWorkService.Vid2UserId(wxWorkVid2UserIdDTO,serverId);
+        List<WxWorkVid2UserIdRespDTO> data = WxWorkVid2UserIdRespDTO.getData();
+        if (data==null|| data.isEmpty()){
+
+            System.out.println("未获取到extId"+wxWorkVid2UserIdDTO);
+        }
+        com.fs.wxwork.dto.WxWorkVid2UserIdRespDTO dto = data.get(0);
+
+        QwExternalContact qwExternalContacts = qwExternalContactMapper.selectQwExternalContactByExternalUserIdAndQwUserId(dto.getOpenid(), user.getCorpId(),user.getQwUserId());
+        if (qwExternalContacts==null){
+            System.out.println("没有外部联系人");
+        }
+
+        //处理拉黑的
+        String appKey = user.getAppKey();
+        //客户编号
+        String receiverOpenid = qwExternalContacts.getExternalUserId();
+
+        if (StringUtil.strIsNullOrEmpty(appKey) || StringUtil.strIsNullOrEmpty(receiverOpenid)){
+            log.error("删除或拉黑-处理营期失败数据不对:"+appKey+"|"+receiverOpenid);
+            return;
+        }
+
+        try {
+            QwUser qwUser = qwUserMapper.selectQwUserByAppKey(appKey);
+            if (qwUser!=null){
+
+                //修改客户状态
+                QwExternalContact contact=new QwExternalContact();
+                contact.setStatus(status);
+                contact.setUserId(qwUser.getQwUserId());
+                contact.setCorpId(qwUser.getCorpId());
+                contact.setExternalUserId(receiverOpenid);
+                qwExternalContactMapper.updateQwExternalContactByUseridBlock(contact);
+
+
+                log.info("删除或拉黑-处理营期-"+qwUser.getQwUserId()+"|"+qwUser.getCorpId()+"|"+receiverOpenid);
+
+                //删除营期
+                sopUserLogsInfoMapper.deleteByQwUserIdAndCorpIdToContactId(qwUser.getQwUserId(),qwUser.getCorpId(),receiverOpenid);
+
+                //删除待发送记录
+                GetQwSopLogsByJsApiParam apiParam=new GetQwSopLogsByJsApiParam();
+                apiParam.setQwUserId(qwUser.getQwUserId());
+                apiParam.setCorpId(qwUser.getCorpId());
+                apiParam.setExternalUserId(receiverOpenid);
+                qwSopLogsMapper.deleteQwSopLogsByJsApi(apiParam);
+            }
+
+        }catch (Exception e){
+            log.error("删除或拉黑-处理营期失败:"+appKey+"|"+receiverOpenid+"|"+e.getMessage());
+        }
+
+    }
+
     /**
      * 处理图片消息
      * @param serverId          服务器ID

+ 1 - 1
fs-qw-api-msg/src/main/java/com/fs/app/controller/imgTest.java

@@ -18,7 +18,7 @@ public class imgTest {
             String apiKey = "208d3549-8dc9-4ef6-b3fa-5aa358f1ab20";
             String requestBody = String.format(
                     "{" +
-                            "\"model\": \"doubao-vision-lite-32k-241015\"," +
+                            "\"model\": \"doubao-1-5-thinking-vision-pro-250428\"," +
                             "\"messages\": [{" +
                             "\"role\": \"user\"," +
                             "\"content\": [" +

+ 55 - 0
fs-qw-api-msg/src/main/java/com/fs/app/controller/test.java

@@ -0,0 +1,55 @@
+package com.fs.app.controller;
+
+import com.alibaba.fastjson.JSON;
+import com.fs.common.annotation.Excel;
+import com.fs.fastGpt.domain.FastGptChatSession;
+import com.fs.fastgptApi.util.AiImgUtil;
+import com.fs.qw.domain.QwExternalContact;
+import com.fs.qw.domain.QwExternalContactInfo;
+import com.fs.wxwork.dto.WxwSilkVoceDTO;
+import com.fs.wxwork.utils.WxWorkHttpUtil;
+
+import javax.validation.constraints.Null;
+import java.lang.reflect.Field;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class test {
+    public static void main(String[] args) {
+
+        AiImgUtil aiImgUtil = new AiImgUtil();
+        String imageParse = aiImgUtil.getImageParse("https://cos.his.cdwjyyh.com/fs/20250606/098d6e12acf747548372ad61bdade4e3.jpg");
+        System.out.println(imageParse);
+    }
+
+    private static String imgEmoticon(String imageParse) {
+        Pattern img = Pattern.compile("【表情包:(.*?)】", Pattern.DOTALL);
+        Matcher imgMatcher = img.matcher(imageParse);
+        while  (imgMatcher.find()) {
+            String imgTag = imgMatcher.group(1).trim();
+            if(imgTag!=null&&!imgTag.equals("")){
+                return imgTag;
+            }
+
+        }
+        return null;
+    }
+
+
+    void voice(){
+        String  url="http://162.14.193.126:8009/app/common/voice?voice="+"你好"+"&id="+2020;
+        String json = WxWorkHttpUtil.get(url);
+        System.out.println(json);
+        WxwSilkVoceDTO wxwSilkVoceDTO = JSON.parseObject(json, WxwSilkVoceDTO.class);
+    }
+
+
+    void img(){
+        AiImgUtil aiImgUtil = new AiImgUtil();
+        String imageParse = aiImgUtil.getImageParse("https://cos.his.cdwjyyh.com/fs/20250604/e8458ed36e534135b50e7459e67b19af.jpg");
+        System.out.println(imageParse);
+    }
+
+}

+ 20 - 3
fs-qw-api-msg/src/main/java/com/fs/framework/config/DataSourceConfig.java

@@ -21,12 +21,16 @@ import java.util.Map;
 
 @Configuration
 public class DataSourceConfig {
-
     @Bean
     @ConfigurationProperties(prefix = "spring.datasource.sop.druid.master")
     public DataSource sopDataSource() {
         return new DruidDataSource();
     }
+    @Bean
+    @ConfigurationProperties(prefix = "spring.datasource.clickhouse")
+    public DataSource clickhouseDataSource() {
+        return new DruidDataSource();
+    }
 
     @Bean
     @ConfigurationProperties(prefix = "spring.datasource.mysql.druid.master")
@@ -34,13 +38,26 @@ public class DataSourceConfig {
         return new DruidDataSource();
     }
 
+    @Bean
+    @ConfigurationProperties(prefix = "spring.datasource.mysql.druid.slave")
+    public DataSource slaveDataSource() {
+        return new DruidDataSource();
+    }
+
 
 
     @Bean
     @Primary
-    public DynamicDataSource dataSource(@Qualifier("masterDataSource") DataSource masterDataSource, @Qualifier("sopDataSource") DataSource sopDataSource) {
+    public DynamicDataSource dataSource(@Qualifier("clickhouseDataSource") DataSource clickhouseDataSource,
+                                        @Qualifier("masterDataSource") DataSource masterDataSource,
+                                        @Qualifier("sopDataSource") DataSource sopDataSource,
+                                        @Qualifier("slaveDataSource") DataSource slaveDataSource) {
         Map<Object, Object> targetDataSources = new HashMap<>();
+        targetDataSources.put(DataSourceType.MASTER, masterDataSource);
+
+        targetDataSources.put(DataSourceType.SLAVE, slaveDataSource);
         targetDataSources.put(DataSourceType.SOP.name(), sopDataSource);
+        targetDataSources.put(DataSourceType.CLICKHOUSE.name(), clickhouseDataSource); // Ensure matching key
         return new DynamicDataSource(masterDataSource, targetDataSources);
     }
 
@@ -49,7 +66,7 @@ public class DataSourceConfig {
      */
     @SuppressWarnings({ "rawtypes", "unchecked" })
     @Bean
-    @ConditionalOnProperty(name = "spring.datasource.mysql.druid.statViewServlet.enabled", havingValue = "true")
+    @ConditionalOnProperty(name = "spring.datasource.druid.statViewServlet.enabled", havingValue = "true")
     public FilterRegistrationBean removeDruidFilterRegistrationBean(DruidStatProperties properties)
     {
         // 获取web监控页面的参数

+ 1 - 1
fs-qw-api-msg/src/main/java/com/fs/framework/config/MyBatisConfig.java

@@ -26,7 +26,7 @@ import java.util.List;
 
 /**
  * Mybatis支持*匹配扫描包
- *
+ * 
 
  */
 @Configuration

+ 2 - 0
fs-qw-api-msg/src/main/java/com/fs/framework/config/SecurityConfig.java

@@ -110,6 +110,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
                 ).permitAll()
 
                 .antMatchers("/**").anonymous()
+                .antMatchers("**/errorLogUpload").anonymous()
                 .antMatchers("/msg/**").anonymous()
                 .antMatchers("/msg/**/**").anonymous()
                 .antMatchers("/msg").anonymous()
@@ -125,6 +126,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
                 .antMatchers("/*/api-docs").anonymous()
                 .antMatchers("/druid/**").anonymous()
                 .antMatchers("/qw/data/**").anonymous()
+                .antMatchers("/qw/test/**").anonymous()
                 // 除上面外的所有请求全部需要鉴权认证
                 .anyRequest().authenticated()
                 .and()

+ 3 - 2
fs-qw-api-msg/src/main/resources/application.yml

@@ -1,9 +1,10 @@
 server:
-  port: 8006
+  port: 8667
 # Spring配置
 spring:
   profiles:
-    active: dev
+#    active: dev
 #    active: druid-jzzx
 #    active: druid-hdt
 #    active: druid-sxjz
+    active: druid-hzyy-test

+ 4 - 2
fs-qw-api/src/main/resources/application.yml

@@ -1,11 +1,13 @@
 server:
   # 服务器的HTTP端口,默认为8080
-  port: 8006
+  port: 8007
 
 # Spring配置
 spring:
   profiles:
-    active: dev
+#    active: dev
 #    active: druid-hdt
 #    active: druid-sft
 #    active: druid-myhk
+    active: config-druid-hzyy
+

+ 45 - 0
fs-service/src/main/java/com/fs/company/domain/CompanyCompanyFsuser.java

@@ -0,0 +1,45 @@
+package com.fs.company.domain;
+
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 销售绑定用户(一个用户绑定唯一一个销售)对象 company_company_fsuser
+ *
+ * @author fs
+ * @date 2025-04-09
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class CompanyCompanyFsuser extends BaseEntity{
+
+    /** $column.columnComment */
+    private Long id;
+
+    /** 公司id */
+    @Excel(name = "公司id")
+    private Long companyId;
+
+    /** 销售id */
+    @Excel(name = "销售id")
+    private Long companyUserId;
+
+    /** 会员id */
+    @Excel(name = "会员id")
+    private Long userId;
+
+    @Excel(name = "企微外部联系人id")
+    private Long qwContactId;
+
+
+    /** 状态 0:禁用 1:正常 */
+    @Excel(name = "状态 0:禁用 1:正常")
+    private Integer status;
+
+    /** 绑定类型 0:链接二维码 1:看课 */
+    private Integer bindType;
+
+
+}

+ 20 - 0
fs-service/src/main/java/com/fs/company/domain/CompanyUser.java

@@ -142,6 +142,19 @@ public class CompanyUser extends BaseEntity
 
     private String addressId;
 
+    //绑定二维码
+    private String bindCode;
+
+    private String imNickName;
+
+    public String getImNickName() {
+        return imNickName;
+    }
+
+    public void setImNickName(String imNickName) {
+        this.imNickName = imNickName;
+    }
+
     /** 看课域名 */
     private String domain;
 
@@ -526,4 +539,11 @@ public class CompanyUser extends BaseEntity
     public void setPosts(List<CompanyPost> posts) {
         this.posts = posts;
     }
+    public String getBindCode() {
+        return bindCode;
+    }
+
+    public void setBindCode(String bindCode) {
+        this.bindCode = bindCode;
+    }
 }

+ 68 - 0
fs-service/src/main/java/com/fs/company/mapper/CompanyCompanyFsuserMapper.java

@@ -0,0 +1,68 @@
+package com.fs.company.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.company.domain.CompanyCompanyFsuser;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * 销售绑定用户Mapper接口
+ *
+ * @author fs
+ * @date 2025-04-09
+ */
+public interface CompanyCompanyFsuserMapper extends BaseMapper<CompanyCompanyFsuser>{
+    /**
+     * 查询销售绑定用户
+     *
+     * @param id 销售绑定用户主键
+     * @return 销售绑定用户
+     */
+    CompanyCompanyFsuser selectCompanyCompanyUserById(Long id);
+
+    /**
+     * 查询销售绑定用户列表
+     *
+     * @param companyCompanyUser 销售绑定用户
+     * @return 销售绑定用户集合
+     */
+    List<CompanyCompanyFsuser> selectCompanyCompanyUserList(CompanyCompanyFsuser companyCompanyUser);
+
+    /**
+     * 新增销售绑定用户
+     *
+     * @param companyCompanyUser 销售绑定用户
+     * @return 结果
+     */
+    int insertCompanyCompanyUser(CompanyCompanyFsuser companyCompanyUser);
+
+    /**
+     * 修改销售绑定用户
+     *
+     * @param companyCompanyUser 销售绑定用户
+     * @return 结果
+     */
+    int updateCompanyCompanyUser(CompanyCompanyFsuser companyCompanyUser);
+
+    /**
+     * 删除销售绑定用户
+     *
+     * @param id 销售绑定用户主键
+     * @return 结果
+     */
+    int deleteCompanyCompanyUserById(Long id);
+
+    /**
+     * 批量删除销售绑定用户
+     *
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteCompanyCompanyUserByIds(Long[] ids);
+
+    CompanyCompanyFsuser getInfoByUserId(@Param("userId") String userId);
+
+    List<CompanyCompanyFsuser> selectNoHistoryApp(@Param("limit") Integer limit);
+
+}

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

@@ -302,5 +302,7 @@ public interface CompanyUserMapper
      * **/
     void batchUpdateUserDept(@Param("companyUserList") List<CompanyUser> companyUserList);
 
+    CompanyUser selectCompanyUserByCompanyUserId(Long companyUserId);
+
     String selectCompanyUserNameByIds(@Param("companyUserIds")String companyUserIds);
 }

+ 15 - 0
fs-service/src/main/java/com/fs/company/vo/OptionVO.java

@@ -0,0 +1,15 @@
+package com.fs.company.vo;
+
+import lombok.Data;
+
+@Data
+public class OptionVO {
+    /**
+     * 选项名称
+     */
+    private String label;
+    /**
+     * 选项值
+     */
+    private Long value;
+}

+ 1 - 1
fs-service/src/main/java/com/fs/core/config/WxMaConfiguration.java

@@ -65,7 +65,7 @@ public class WxMaConfiguration {
                 if (appid.equals(courseMaConfig.getAppid())) {
                     continue;
                 }
-                if (courseMaConfig.getType().equals("1")){
+                if (courseMaConfig.getType() != null && courseMaConfig.getType().equals("1")){
                     WxMaConfig.Config wxMaConfig = new WxMaConfig.Config();
                     BeanUtils.copyProperties(courseMaConfig, wxMaConfig);
                     c.add(wxMaConfig);

+ 1 - 1
fs-service/src/main/java/com/fs/course/domain/FsCourseLink.java

@@ -37,7 +37,7 @@ public class FsCourseLink extends BaseEntity
 
     /** 企微userId */
     @Excel(name = "企微userId")
-    private Long qwUserId;
+    private String qwUserId;
 
     /** 课节id */
     @Excel(name = "课节id")

+ 3 - 3
fs-service/src/main/java/com/fs/course/domain/FsCourseWatchLog.java

@@ -47,9 +47,9 @@ public class FsCourseWatchLog extends BaseEntity
     @Excel(name = "播放时长")
     private Long duration;
 
-    /** 分享人企微userId */
-    @Excel(name = "分享人企微userId")
-    private Long qwUserId;
+    /** 分享人企微userId 主键 */
+    @Excel(name = "分享人企微userId 主键")
+    private String qwUserId;
 
     /** 销售id */
     @Excel(name = "销售id")

+ 2 - 2
fs-service/src/main/java/com/fs/course/param/FsCourseLinkCreateParam.java

@@ -12,8 +12,8 @@ public class FsCourseLinkCreateParam {
 
     private Integer days;
 
-//    private Long qwUserIdLong;
-    private Long qwUserId;
+    private Long qwUserIdLong;
+    private String qwUserId;
 
     private String corpId;
 

+ 3 - 3
fs-service/src/main/java/com/fs/course/service/impl/FsCourseLinkServiceImpl.java

@@ -370,7 +370,7 @@ public class FsCourseLinkServiceImpl implements IFsCourseLinkService
     }
 
 
-    public FsCourseLink createFsCourseLink(String corpId, Date sendTime,Long courseId,Long videoId, Long qwUserId,
+    public FsCourseLink createFsCourseLink(String corpId, Date sendTime,Long courseId,Long videoId, String qwUserId,
                                            Long companyUserId, Long companyId,Long externalId,Integer type){
         // 手动创建 FsCourseLink 对象,避免使用 BeanUtils.copyProperties
         FsCourseLink link = new FsCourseLink();
@@ -627,7 +627,7 @@ public class FsCourseLinkServiceImpl implements IFsCourseLinkService
         createParam.setCorpId(param.getCorpId());
         createParam.setCompanyUserId(qwUser.getCompanyUserId());
         createParam.setCompanyId(qwUser.getCompanyId());
-        createParam.setQwUserId(qwUser.getId());
+        createParam.setQwUserId(String.valueOf(qwUser.getId()));
         createParam.setChatId(param.getChatId());
         String linkUrl;
         R createLink = createRoomLinkUrl(createParam);
@@ -679,7 +679,7 @@ public class FsCourseLinkServiceImpl implements IFsCourseLinkService
             watchLog.setVideoId(videoId);
             watchLog.setQwExternalContactId(externalId);
             watchLog.setSendType(2);
-            watchLog.setQwUserId(qwUserId);
+            watchLog.setQwUserId(String.valueOf(qwUserId));
             watchLog.setDuration(0L);
             watchLog.setCourseId(courseId);
             watchLog.setCompanyUserId(companyUserId);

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

@@ -514,7 +514,7 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
     @Override
     public void testFinishMsg() {
         FsCourseWatchLog finishLog = fsCourseWatchLogMapper.selectFsCourseWatchLogByLogId(341170L);
-        QwUser qwUser = qwUserMapper.selectQwUserById(finishLog.getQwUserId());
+        QwUser qwUser = qwUserMapper.selectQwUserById(Long.valueOf(finishLog.getQwUserId()));
         QwExternalContact externalContact = qwExternalContactMapper.selectQwExternalContactById(finishLog.getQwExternalContactId());
         FsCourseFinishTemp finishTemp = fsCourseFinishTempMapper.selectFsCourseFinishTempByCompanyUserId(finishLog.getCompanyUserId(),finishLog.getVideoId());
         QwSopCourseFinishTempSetting setting = new QwSopCourseFinishTempSetting();
@@ -625,7 +625,7 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
 
             FsCourseWatchLog watchLog = new FsCourseWatchLog();
             watchLog.setVideoId(videoId);
-            watchLog.setQwUserId(qwUserId);
+            watchLog.setQwUserId(String.valueOf(qwUserId));
             watchLog.setQwExternalContactId(externalId);
             watchLog.setDuration(duration);
 
@@ -693,7 +693,7 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
             Duration duration = Duration.between(lastHeartbeatTime, now);
 
             watchLog.setVideoId(videoId);
-            watchLog.setQwUserId(qwUserId);
+            watchLog.setQwUserId(String.valueOf(qwUserId));
             watchLog.setQwExternalContactId(externalId);
             // 如果超过一分钟没有心跳,标记为“观看中断”
             if (duration.getSeconds() >= 60) {

+ 3 - 3
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java

@@ -553,7 +553,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
         log.setUserId(param.getUserId());
         log.setVideoId(param.getVideoId());
         log.setDuration(0L);
-        log.setQwUserId(Long.parseLong(param.getQwUserId()));
+        log.setQwUserId(param.getQwUserId());
         log.setCreateTime(new Date());
         log.setLogType(3);
         logger.info("zyp \n【群聊生成看课记录】:{}",param);
@@ -1862,7 +1862,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
             watchLog.setVideoId(videoId);
             watchLog.setQwExternalContactId(externalId);
             watchLog.setSendType(2);
-            watchLog.setQwUserId(qwUser.getId());
+            watchLog.setQwUserId(String.valueOf(qwUser.getId()));
             watchLog.setDuration(0L);
             watchLog.setCourseId(courseId);
             watchLog.setCompanyUserId(qwUser.getCompanyUserId());
@@ -1892,7 +1892,7 @@ public class FsUserCourseVideoServiceImpl implements IFsUserCourseVideoService
 
         FsCourseLink link = new FsCourseLink();
         link.setCompanyId(qwUser.getCompanyId());
-        link.setQwUserId(qwUser.getId());
+        link.setQwUserId(String.valueOf(qwUser.getId()));
         link.setCompanyUserId(qwUser.getCompanyUserId());
         link.setVideoId(videoId);
         link.setCorpId(qwUser.getCorpId());

+ 23 - 0
fs-service/src/main/java/com/fs/fastGpt/domain/FastGptEventLog.java

@@ -0,0 +1,23 @@
+package com.fs.fastGpt.domain;
+
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+public class FastGptEventLog {
+    private Long id;
+    private Long senderId;
+    private Long roleId;
+    private String eventName;
+    private Long count;
+    /**
+     * 事件类型(1互动 1总对话 3转人工 4AI无法回复转人工 5AI回复不合适转人工 6完课回复 7物流事件 8图片回复 9自定义事件回复 10用户未回复AI再次提醒)
+     */
+    private Integer type;
+    private Long companyId;
+    private Long companyUserId;
+    private Long qwUserId;
+    private Date createTime;
+
+}

+ 23 - 0
fs-service/src/main/java/com/fs/fastGpt/domain/FastGptEventTokenLog.java

@@ -0,0 +1,23 @@
+package com.fs.fastGpt.domain;
+
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+public class FastGptEventTokenLog extends BaseEntity {
+
+    private Long id;
+    private Long senderId;
+    private Long roleId;
+    private String eventName;
+    private Integer eventType;
+    private Long tokenCount;
+    private Integer tokenType;
+    private Long companyId;
+    private Long companyUserId;
+    private Long qwUserId;
+    private String statTime;
+    private Date createTime;
+}

+ 12 - 0
fs-service/src/main/java/com/fs/fastGpt/domain/FastGptKeyword.java

@@ -0,0 +1,12 @@
+package com.fs.fastGpt.domain;
+
+import lombok.Data;
+
+@Data
+public class FastGptKeyword {
+    private Long id;
+    //关键字
+    private String keyword;
+    //关键字类型
+    private Integer keywordType;
+}

+ 19 - 0
fs-service/src/main/java/com/fs/fastGpt/domain/FastGptKeywordArtificial.java

@@ -0,0 +1,19 @@
+package com.fs.fastGpt.domain;
+
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+public class FastGptKeywordArtificial extends BaseEntity {
+
+    private Long id;
+    private String userId;
+    private Long companyId;
+    private Long companyUserId;
+    private Long keywordSendId;
+    private String keywordSendContent;
+    private Date createTime;
+
+}

+ 55 - 0
fs-service/src/main/java/com/fs/fastGpt/domain/FastGptKeywordSend.java

@@ -0,0 +1,55 @@
+package com.fs.fastGpt.domain;
+
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * Ai关键字表(根据关键字发送文本和图片)对象 fastgpt_keyword_send
+ *
+ * @author fs
+ * @date 2025-05-12
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FastGptKeywordSend extends BaseEntity{
+
+    /** $column.columnComment */
+    private Long id;
+
+    /** 营销关键字 */
+    @Excel(name = "营销关键字")
+    private String keyword;
+
+    @Excel(name = "营销关键字类型 0客服端  1总后台")
+    private Long keywordType;
+
+    /** 发送文字内容 */
+    @Excel(name = "发送文字内容")
+    private String content;
+
+    /** 内容类型 */
+    @Excel(name = "内容类型")
+    private Integer contentType;
+
+    /** 图片访问地址 */
+    @Excel(name = "图片访问地址")
+    private String imgUrl;
+
+    /** 状态 */
+    @Excel(name = "状态")
+    private Integer status;
+
+    /** Ai角色id*/
+    @Excel(name = "Ai角色id")
+    private String roleIds;
+
+    private Long cropId;
+
+    private Long companyId;
+
+    private Long companyUserId;
+
+
+}

+ 2 - 0
fs-service/src/main/java/com/fs/fastGpt/domain/FastGptRole.java

@@ -61,4 +61,6 @@ public class FastGptRole extends BaseEntity
     private String bindCorpId;
 
     private String contactInfo;
+
+    private String channelType;
 }

+ 77 - 0
fs-service/src/main/java/com/fs/fastGpt/domain/FastgptChatArtificialWords.java

@@ -0,0 +1,77 @@
+package com.fs.fastGpt.domain;
+
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 转人工提示词对象 fastgpt_chat_artificial_words
+ *
+ * @author fs
+ * @date 2025-05-07
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FastgptChatArtificialWords extends BaseEntity{
+
+    /** id */
+    private Long id;
+
+    /** 类型 1报错 2关键词 */
+    @Excel(name = "类型 1报错 2关键词")
+    private Long type;
+
+    /** 文本 */
+    @Excel(name = "文本")
+    private String content;
+
+    /** 状态 */
+    @Excel(name = "状态")
+    private Long status;
+
+    /** 排序 */
+    @Excel(name = "排序")
+    private Long sort;
+
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public Long getType() {
+        return type;
+    }
+
+    public void setType(Long type) {
+        this.type = type;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public Long getStatus() {
+        return status;
+    }
+
+    public void setStatus(Long status) {
+        this.status = status;
+    }
+
+    public Long getSort() {
+        return sort;
+    }
+
+    public void setSort(Long sort) {
+        this.sort = sort;
+    }
+}

+ 56 - 0
fs-service/src/main/java/com/fs/fastGpt/domain/FastgptEventLogTotal.java

@@ -0,0 +1,56 @@
+package com.fs.fastGpt.domain;
+
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.List;
+
+/**
+ * ai事件埋点统计对象 fastgpt_event_log_total
+ *
+ * @author fs
+ * @date 2025-06-26
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class FastgptEventLogTotal extends BaseEntity{
+
+    /** $column.columnComment */
+    private Long id;
+
+    /** 角色id */
+    @Excel(name = "角色id")
+    private Long roleId;
+
+    /** 数量 */
+    @Excel(name = "数量")
+    private Long count;
+
+    /** 日志类型 */
+    @Excel(name = "日志类型")
+    private Integer type;
+
+    /** 公司id */
+    @Excel(name = "公司id")
+    private Long companyId;
+
+    /** 公司用户id */
+    @Excel(name = "公司用户id")
+    private Long companyUserId;
+
+    /** 企微用户id */
+    @Excel(name = "企微用户id")
+    private Long qwUserId;
+
+    @Excel(name = "日志生成时间")
+    private String statTime;
+
+    private Long senderCount;
+
+    private String qwUserIds;
+
+    private List<String> userIds;
+
+}

+ 7 - 4
fs-service/src/main/java/com/fs/fastGpt/mapper/FastGptChatMsgMapper.java

@@ -6,6 +6,8 @@ import com.fs.common.annotation.DataSource;
 import com.fs.common.enums.DataSourceType;
 import com.fs.fastGpt.domain.FastGptChatMsg;
 import com.fs.fastGpt.domain.FastGptChatSession;
+import com.fs.fastGpt.domain.FastGptEventLog;
+import com.fs.fastGpt.domain.FastGptEventTokenLog;
 import com.fs.fastGpt.param.FastGptChatMsgListCParam;
 import com.fs.fastGpt.vo.FastGptChatMsgCVO;
 import com.fs.fastGpt.vo.FastGptChatMsgListCVO;
@@ -20,7 +22,6 @@ import org.springframework.stereotype.Repository;
  * @date 2024-10-10
  */
 @Repository
-@DataSource(DataSourceType.CLICKHOUSE)
 public interface FastGptChatMsgMapper
 {
     /**
@@ -47,7 +48,6 @@ public interface FastGptChatMsgMapper
      * @param fastGptChatMsg 聊天记录
      * @return 结果
      */
-    @DataSource(DataSourceType.CLICKHOUSE)
     public int insertFastGptChatMsg(FastGptChatMsg fastGptChatMsg);
 
     /**
@@ -74,8 +74,7 @@ public interface FastGptChatMsgMapper
      */
     public int deleteFastGptChatMsgByMsgIds(Long[] msgIds);
 
-    @Select("select m.*  from fastgpt_chat_msg m  where m.session_id=#{sessionId} and msg_type=2 order by m.msg_id desc")
-    public List<FastGptChatMsgCVO> selectFastGptChatMsgCVOBySessionId(@Param("sessionId")Long sessionId);
+    public List<FastGptChatMsgCVO> selectFastGptChatMsgCVOBySessionId(@Param("sessionId")Long sessionId,@Param("userId") String userId);
 
 
     @Select({"<script> " +
@@ -114,5 +113,9 @@ public interface FastGptChatMsgMapper
     @Select("select * from fastgpt_chat_msg where  session_id =#{sessionId} and msg_type=1 ORDER BY msg_id DESC  limit 20 ")
     List<FastGptChatMsg> selectFastGptChatMsgByMsgSessionId(Long sessionId);
 
+    void insertFastGptEventLog(FastGptEventLog fastGptEventLog);
 
+    void insertFastGptEventTokenLog(FastGptEventTokenLog fastGptEventTokenLog);
+
+    List<FastGptChatMsg> selectFastGptChatMsgByMsgSessionIdAndExtId(@Param("sessionId") Long sessionId,@Param("extId") String extId);
 }

+ 72 - 0
fs-service/src/main/java/com/fs/fastGpt/mapper/FastGptKeywordSendMapper.java

@@ -0,0 +1,72 @@
+package com.fs.fastGpt.mapper;
+
+import java.util.List;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.fastGpt.domain.FastGptKeyword;
+import com.fs.fastGpt.domain.FastGptKeywordArtificial;
+import com.fs.fastGpt.domain.FastGptKeywordSend;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * Ai关键字表(根据关键字发送文本和图片)Mapper接口
+ * 
+ * @author fs
+ * @date 2025-05-12
+ */
+public interface FastGptKeywordSendMapper extends BaseMapper<FastGptKeywordSend>{
+    /**
+     * 查询Ai关键字表(根据关键字发送文本和图片)
+     * 
+     * @param id Ai关键字表(根据关键字发送文本和图片)主键
+     * @return Ai关键字表(根据关键字发送文本和图片)
+     */
+    FastGptKeywordSend selectFastGptKeywordSendById(@Param("id") Long id,@Param("keywordType") Long keywordType);
+
+    /**
+     * 查询Ai关键字表(根据关键字发送文本和图片)列表
+     * 
+     * @param fastGptKeywordSend Ai关键字表(根据关键字发送文本和图片)
+     * @return Ai关键字表(根据关键字发送文本和图片)集合
+     */
+    List<FastGptKeywordSend> selectFastGptKeywordSendList(FastGptKeywordSend fastGptKeywordSend);
+
+    /**
+     * 新增Ai关键字表(根据关键字发送文本和图片)
+     * 
+     * @param fastGptKeywordSend Ai关键字表(根据关键字发送文本和图片)
+     * @return 结果
+     */
+    int insertFastGptKeywordSend(FastGptKeywordSend fastGptKeywordSend);
+
+    /**
+     * 修改Ai关键字表(根据关键字发送文本和图片)
+     * 
+     * @param fastGptKeywordSend Ai关键字表(根据关键字发送文本和图片)
+     * @return 结果
+     */
+    int updateFastGptKeywordSend(FastGptKeywordSend fastGptKeywordSend);
+
+    /**
+     * 删除Ai关键字表(根据关键字发送文本和图片)
+     * 
+     * @param id Ai关键字表(根据关键字发送文本和图片)主键
+     * @return 结果
+     */
+    int deleteFastGptKeywordSendById(Long id);
+
+    /**
+     * 批量删除Ai关键字表(根据关键字发送文本和图片)
+     * 
+     * @param ids 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteFastGptKeywordSendByIds(Long[] ids);
+
+    List<FastGptKeywordSend> selectFastGptKeywordSendByRoleId(@Param("roleIds") Long roleId);
+
+    List<FastGptKeywordArtificial> selectFastGptKeywordArtificial(FastGptKeywordArtificial keywordArtificial);
+
+    int insertFastGptKeywordArtificial(FastGptKeywordArtificial keywordArtificial);
+
+    List<FastGptKeyword> selectFastGptKeywordList(int type);
+}

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

@@ -86,6 +86,6 @@ public interface FastGptRoleMapper
 
     @Select("select id dictValue,name dictLabel from fastgpt_role_type ")
     List<OptionsVO> selectFastGptRoleType();
-    @Select("select r.role_id, r.role_name,t.contact_info,r.company_id, r.create_time, r.update_time, r.role_type, r.mode_config_json, r.mode, r.kf_id, r.kf_url, r.avatar, r.kf_media_id,r.reminder_words, r.bind_corp_id from fastgpt_role r LEFT JOIN fastgpt_role_type t on t.id =r.role_type where role_id = #{roleId}")
+    @Select("select r.role_id, r.role_name,t.contact_info,r.company_id, r.create_time, r.update_time, r.role_type, r.mode_config_json, r.mode, r.kf_id, r.kf_url, r.avatar, r.kf_media_id,r.reminder_words, r.bind_corp_id,r.channel_type from fastgpt_role r LEFT JOIN fastgpt_role_type t on t.id =r.role_type where role_id = #{roleId}")
     FastGptRole selectFastGptRoleTypeByRoleId(Long roleId);
 }

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

@@ -1,6 +1,7 @@
 package com.fs.fastGpt.service;
 
 import com.fs.common.core.domain.R;
+import com.fs.im.vo.OpenImMsgCallBackVO;
 import com.fs.qwHookApi.vo.QwHookVO;
 import com.fs.wxwork.dto.WxWorkResponseDTO;
 
@@ -14,6 +15,9 @@ public interface AiHookService {
     /** 转人工 **/
     void artificial(QwHookVO vo);
 
+    /** ai自动回复 **/
+    R AiReply(OpenImMsgCallBackVO openImMsgDTO, Long companyId);
+
     R qwHookNotifyAddMsg(Long qwUserID, Long sender,String count,String uid);
 
     void expireAiMsg();

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

@@ -2,6 +2,8 @@ package com.fs.fastGpt.service;
 
 import com.fs.fastGpt.domain.FastGptChatMsg;
 import com.fs.fastGpt.domain.FastGptChatSession;
+import com.fs.fastGpt.domain.FastGptEventLog;
+import com.fs.fastGpt.domain.FastGptEventTokenLog;
 import com.fs.fastGpt.param.FastGptChatMsgListCParam;
 import com.fs.fastGpt.vo.FastGptChatMsgCVO;
 import com.fs.fastGpt.vo.FastGptChatMsgListCVO;
@@ -64,7 +66,7 @@ public interface IFastGptChatMsgService
      */
     public int deleteFastGptChatMsgByMsgId(Long msgId);
 
-    List<FastGptChatMsgCVO> selectFastGptChatMsgCVOBySessionId(Long sessionId);
+    List<FastGptChatMsgCVO> selectFastGptChatMsgCVOBySessionId(Long sessionId,String userId);
 
     List<FastGptChatMsgListCVO> selectFastGptChatMsgListCVO(FastGptChatMsgListCParam param);
 
@@ -74,4 +76,10 @@ public interface IFastGptChatMsgService
 
 
     List<FastGptChatMsg> selectFastGptChatMsgByMsgSessionId(Long sessionId);
+
+    void insertFastGptEventLog(FastGptEventLog fastGptEventLog);
+
+    void insertFastGptEventTokenLog(FastGptEventTokenLog fastGptEventTokenLog);
+
+    List<FastGptChatMsg> selectFastGptChatMsgByMsgSessionIdAndExtId(Long sessionId, Long extId);
 }

+ 72 - 0
fs-service/src/main/java/com/fs/fastGpt/service/IFastGptKeywordSendService.java

@@ -0,0 +1,72 @@
+package com.fs.fastGpt.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.fastGpt.domain.FastGptKeyword;
+import com.fs.fastGpt.domain.FastGptKeywordArtificial;
+import com.fs.fastGpt.domain.FastGptKeywordSend;
+
+import java.util.List;
+
+/**
+ * Ai关键字表(根据关键字发送文本和图片)Service接口
+ * 
+ * @author fs
+ * @date 2025-05-12
+ */
+public interface IFastGptKeywordSendService extends IService<FastGptKeywordSend>{
+    /**
+     * 查询Ai关键字表(根据关键字发送文本和图片)
+     * 
+     * @param id Ai关键字表(根据关键字发送文本和图片)主键
+     * @return Ai关键字表(根据关键字发送文本和图片)
+     */
+    FastGptKeywordSend selectFastGptKeywordSendById(Long id,Long keywordType);
+
+    /**
+     * 查询Ai关键字表(根据关键字发送文本和图片)列表
+     * 
+     * @param fastGptKeywordSend Ai关键字表(根据关键字发送文本和图片)
+     * @return Ai关键字表(根据关键字发送文本和图片)集合
+     */
+    List<FastGptKeywordSend> selectFastGptKeywordSendList(FastGptKeywordSend fastGptKeywordSend);
+
+    /**
+     * 新增Ai关键字表(根据关键字发送文本和图片)
+     * 
+     * @param fastGptKeywordSend Ai关键字表(根据关键字发送文本和图片)
+     * @return 结果
+     */
+    int insertFastGptKeywordSend(FastGptKeywordSend fastGptKeywordSend);
+
+    /**
+     * 修改Ai关键字表(根据关键字发送文本和图片)
+     * 
+     * @param fastGptKeywordSend Ai关键字表(根据关键字发送文本和图片)
+     * @return 结果
+     */
+    int updateFastGptKeywordSend(FastGptKeywordSend fastGptKeywordSend);
+
+    /**
+     * 批量删除Ai关键字表(根据关键字发送文本和图片)
+     * 
+     * @param ids 需要删除的Ai关键字表(根据关键字发送文本和图片)主键集合
+     * @return 结果
+     */
+    int deleteFastGptKeywordSendByIds(Long[] ids);
+
+    /**
+     * 删除Ai关键字表(根据关键字发送文本和图片)信息
+     * 
+     * @param id Ai关键字表(根据关键字发送文本和图片)主键
+     * @return 结果
+     */
+    int deleteFastGptKeywordSendById(Long id);
+
+    List<FastGptKeywordSend> selectFastGptKeywordSendByRoleId(Long roleId);
+
+    List<FastGptKeywordArtificial> selectFastGptKeywordArtificial(FastGptKeywordArtificial keywordArtificial);
+
+    int insertFastGptKeywordArtificial(FastGptKeywordArtificial keywordArtificial);
+
+    List<FastGptKeyword> selectFastGptKeywordList(int type);
+}

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 497 - 197
fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java


+ 4 - 4
fs-service/src/main/java/com/fs/fastGpt/service/impl/AiNewServiceImpl.java

@@ -538,7 +538,7 @@ public class AiNewServiceImpl implements AiNewService {
             if (fsCourseWatchLogVO!=null){
                 FsCourseLinkCreateParam param = new FsCourseLinkCreateParam();
                 param.setVideoId(fsCourseWatchLogVO.getVideoId());
-                param.setQwUserId(user.getId());
+                param.setQwUserId(String.valueOf(user.getId()));
                 param.setDays(1);
                 param.setCorpId(user.getCorpId());
                 param.setCourseId(fsCourseWatchLogVO.getCourseId());
@@ -569,7 +569,7 @@ public class AiNewServiceImpl implements AiNewService {
                     if (fsUserCourseVideo==null){
                         FsCourseLinkCreateParam param = new FsCourseLinkCreateParam();
                         param.setVideoId(fsUserCourseVideo.getVideoId());
-                        param.setQwUserId(user.getId());
+                        param.setQwUserId(String.valueOf(user.getId()));
                         param.setDays(1);
                         param.setCorpId(user.getCorpId());
                         param.setCourseId(fsUserCourseVideo.getCourseId());
@@ -1056,7 +1056,7 @@ public class AiNewServiceImpl implements AiNewService {
             if (fsCourseWatchLogVO!=null){
                 FsCourseLinkCreateParam param = new FsCourseLinkCreateParam();
                 param.setVideoId(fsCourseWatchLogVO.getVideoId());
-                param.setQwUserId(user.getId());
+                param.setQwUserId(String.valueOf(user.getId()));
                 param.setDays(1);
                 param.setCorpId(user.getCorpId());
                 param.setCourseId(fsCourseWatchLogVO.getCourseId());
@@ -1087,7 +1087,7 @@ public class AiNewServiceImpl implements AiNewService {
                     System.out.println("课程:"+fsUserCourseVideo);
                     FsCourseLinkCreateParam param = new FsCourseLinkCreateParam();
                     param.setVideoId(fsUserCourseVideo.getVideoId());
-                    param.setQwUserId(user.getId());
+                    param.setQwUserId(String.valueOf(user.getId()));
                     param.setDays(1);
                     param.setCorpId(user.getCorpId());
                     param.setCourseId(fsUserCourseVideo.getCourseId());

+ 4 - 4
fs-service/src/main/java/com/fs/fastGpt/service/impl/AiServiceImpl.java

@@ -516,7 +516,7 @@ public class AiServiceImpl implements AiService {
             if (fsCourseWatchLogVO!=null){
                 FsCourseLinkCreateParam param = new FsCourseLinkCreateParam();
                 param.setVideoId(fsCourseWatchLogVO.getVideoId());
-                param.setQwUserId(user.getId());
+                param.setQwUserId(String.valueOf(user.getId()));
                 param.setDays(1);
                 param.setCorpId(user.getCorpId());
                 param.setCourseId(fsCourseWatchLogVO.getCourseId());
@@ -547,7 +547,7 @@ public class AiServiceImpl implements AiService {
                     if (fsUserCourseVideo==null){
                         FsCourseLinkCreateParam param = new FsCourseLinkCreateParam();
                         param.setVideoId(fsUserCourseVideo.getVideoId());
-                        param.setQwUserId(user.getId());
+                        param.setQwUserId(String.valueOf(user.getId()));
                         param.setDays(1);
                         param.setCorpId(user.getCorpId());
                         param.setCourseId(fsUserCourseVideo.getCourseId());
@@ -1034,7 +1034,7 @@ public class AiServiceImpl implements AiService {
             if (fsCourseWatchLogVO!=null){
                 FsCourseLinkCreateParam param = new FsCourseLinkCreateParam();
                 param.setVideoId(fsCourseWatchLogVO.getVideoId());
-                param.setQwUserId(user.getId());
+                param.setQwUserId(String.valueOf(user.getId()));
                 param.setDays(1);
                 param.setCorpId(user.getCorpId());
                 param.setCourseId(fsCourseWatchLogVO.getCourseId());
@@ -1065,7 +1065,7 @@ public class AiServiceImpl implements AiService {
                     System.out.println("课程:"+fsUserCourseVideo);
                     FsCourseLinkCreateParam param = new FsCourseLinkCreateParam();
                     param.setVideoId(fsUserCourseVideo.getVideoId());
-                    param.setQwUserId(user.getId());
+                    param.setQwUserId(String.valueOf(user.getId()));
                     param.setDays(1);
                     param.setCorpId(user.getCorpId());
                     param.setCourseId(fsUserCourseVideo.getCourseId());

+ 24 - 3
fs-service/src/main/java/com/fs/fastGpt/service/impl/FastGptChatMsgServiceImpl.java

@@ -4,8 +4,12 @@ import java.util.Collections;
 import java.util.List;
 
 import cn.hutool.core.lang.Snowflake;
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.common.utils.DateUtils;
 import com.fs.fastGpt.domain.FastGptChatSession;
+import com.fs.fastGpt.domain.FastGptEventLog;
+import com.fs.fastGpt.domain.FastGptEventTokenLog;
 import com.fs.fastGpt.param.FastGptChatMsgListCParam;
 import com.fs.fastGpt.vo.FastGptChatMsgCVO;
 import com.fs.fastGpt.vo.FastGptChatMsgListCVO;
@@ -61,7 +65,7 @@ public class FastGptChatMsgServiceImpl implements IFastGptChatMsgService
     public int insertFastGptChatMsg(FastGptChatMsg fastGptChatMsg)
     {
         fastGptChatMsg.setCreateTime(DateUtils.getNowDate());
-        fastGptChatMsg.setMsgId((new Snowflake(1, 1)).nextId());
+        //fastGptChatMsg.setMsgId((new Snowflake(1, 1)).nextId());
         return fastGptChatMsgMapper.insertFastGptChatMsg(fastGptChatMsg);
     }
 
@@ -102,8 +106,8 @@ public class FastGptChatMsgServiceImpl implements IFastGptChatMsgService
     }
 
     @Override
-    public List<FastGptChatMsgCVO> selectFastGptChatMsgCVOBySessionId(Long sessionId) {
-        return fastGptChatMsgMapper.selectFastGptChatMsgCVOBySessionId(sessionId);
+    public List<FastGptChatMsgCVO> selectFastGptChatMsgCVOBySessionId(Long sessionId,String userId) {
+        return fastGptChatMsgMapper.selectFastGptChatMsgCVOBySessionId(sessionId,userId);
     }
 
     @Override
@@ -128,4 +132,21 @@ public class FastGptChatMsgServiceImpl implements IFastGptChatMsgService
     public List<FastGptChatMsg> selectFastGptChatMsgByMsgSessionId(Long sessionId) {
         return fastGptChatMsgMapper.selectFastGptChatMsgByMsgSessionId(sessionId);
     }
+
+    @Override
+    @DataSource(DataSourceType.CLICKHOUSE)
+    public void insertFastGptEventLog(FastGptEventLog fastGptEventLog) {
+        fastGptChatMsgMapper.insertFastGptEventLog(fastGptEventLog);
+    }
+
+    @Override
+    @DataSource(DataSourceType.CLICKHOUSE)
+    public void insertFastGptEventTokenLog(FastGptEventTokenLog fastGptEventTokenLog) {
+        fastGptChatMsgMapper.insertFastGptEventTokenLog(fastGptEventTokenLog);
+    }
+
+    @Override
+    public List<FastGptChatMsg> selectFastGptChatMsgByMsgSessionIdAndExtId(Long sessionId, Long extId) {
+        return fastGptChatMsgMapper.selectFastGptChatMsgByMsgSessionIdAndExtId(sessionId,String.valueOf(extId));
+    }
 }

+ 116 - 0
fs-service/src/main/java/com/fs/fastGpt/service/impl/FastGptKeywordSendServiceImpl.java

@@ -0,0 +1,116 @@
+package com.fs.fastGpt.service.impl;
+
+import java.util.List;
+
+import com.fs.common.BeanCopyUtils;
+import com.fs.common.utils.DateUtils;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.fs.fastGpt.domain.FastGptKeyword;
+import com.fs.fastGpt.domain.FastGptKeywordArtificial;
+import org.springframework.stereotype.Service;
+import com.fs.fastGpt.mapper.FastGptKeywordSendMapper;
+import com.fs.fastGpt.domain.FastGptKeywordSend;
+import com.fs.fastGpt.service.IFastGptKeywordSendService;
+
+/**
+ * Ai关键字表(根据关键字发送文本和图片)Service业务层处理
+ * 
+ * @author fs
+ * @date 2025-05-12
+ */
+@Service
+public class FastGptKeywordSendServiceImpl extends ServiceImpl<FastGptKeywordSendMapper, FastGptKeywordSend> implements IFastGptKeywordSendService {
+
+    /**
+     * 查询Ai关键字表(根据关键字发送文本和图片)
+     * 
+     * @param id Ai关键字表(根据关键字发送文本和图片)主键
+     * @return Ai关键字表(根据关键字发送文本和图片)
+     */
+    @Override
+    public FastGptKeywordSend selectFastGptKeywordSendById(Long id,Long keywordType)
+    {
+        return baseMapper.selectFastGptKeywordSendById(id,keywordType);
+    }
+
+    /**
+     * 查询Ai关键字表(根据关键字发送文本和图片)列表
+     * 
+     * @param fastGptKeywordSend Ai关键字表(根据关键字发送文本和图片)
+     * @return Ai关键字表(根据关键字发送文本和图片)
+     */
+    @Override
+    public List<FastGptKeywordSend> selectFastGptKeywordSendList(FastGptKeywordSend fastGptKeywordSend)
+    {
+        return baseMapper.selectFastGptKeywordSendList(fastGptKeywordSend);
+    }
+
+    /**
+     * 新增Ai关键字表(根据关键字发送文本和图片)
+     * 
+     * @param fastGptKeywordSend Ai关键字表(根据关键字发送文本和图片)
+     * @return 结果
+     */
+    @Override
+    public int insertFastGptKeywordSend(FastGptKeywordSend fastGptKeywordSend)
+    {
+        fastGptKeywordSend.setCreateTime(DateUtils.getNowDate());
+        return baseMapper.insertFastGptKeywordSend(fastGptKeywordSend);
+    }
+
+    /**
+     * 修改Ai关键字表(根据关键字发送文本和图片)
+     * 
+     * @param fastGptKeywordSend Ai关键字表(根据关键字发送文本和图片)
+     * @return 结果
+     */
+    @Override
+    public int updateFastGptKeywordSend(FastGptKeywordSend fastGptKeywordSend)
+    {
+        return baseMapper.updateFastGptKeywordSend(fastGptKeywordSend);
+    }
+
+    /**
+     * 批量删除Ai关键字表(根据关键字发送文本和图片)
+     * 
+     * @param ids 需要删除的Ai关键字表(根据关键字发送文本和图片)主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFastGptKeywordSendByIds(Long[] ids)
+    {
+        return baseMapper.deleteFastGptKeywordSendByIds(ids);
+    }
+
+    /**
+     * 删除Ai关键字表(根据关键字发送文本和图片)信息
+     * 
+     * @param id Ai关键字表(根据关键字发送文本和图片)主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFastGptKeywordSendById(Long id)
+    {
+        return baseMapper.deleteFastGptKeywordSendById(id);
+    }
+
+    @Override
+    public List<FastGptKeywordSend> selectFastGptKeywordSendByRoleId(Long roleId) {
+        return baseMapper.selectFastGptKeywordSendByRoleId(roleId);
+    }
+
+    @Override
+    public List<FastGptKeywordArtificial> selectFastGptKeywordArtificial(FastGptKeywordArtificial keywordArtificial) {
+        return baseMapper.selectFastGptKeywordArtificial(keywordArtificial);
+    }
+
+    @Override
+    public int insertFastGptKeywordArtificial(FastGptKeywordArtificial keywordArtificial) {
+        return baseMapper.insertFastGptKeywordArtificial(keywordArtificial);
+    }
+
+    @Override
+    public List<FastGptKeyword> selectFastGptKeywordList(int type) {
+        return baseMapper.selectFastGptKeywordList(type);
+    }
+}

+ 78 - 0
fs-service/src/main/java/com/fs/fastGpt/vo/FastGptChatSessionVo.java

@@ -0,0 +1,78 @@
+package com.fs.fastGpt.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 对话关系对象 fastgpt_chat_session
+ *
+ * @author fs
+ * @date 2024-10-10
+ */
+@Data
+public class FastGptChatSessionVo extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 会话ID */
+    private Long sessionId;
+
+    /** 聊天id */
+    @Excel(name = "聊天id")
+    private String chatId;
+
+    /** 客户ID uid */
+    @Excel(name = "客户ID uid")
+    private String userId;
+
+    /** 客服ID 应用id? */
+    @Excel(name = "客服ID 应用id?")
+    private String kfId;
+
+    /** 状态 1会话中 2已结束 */
+    @Excel(name = "状态 1会话中 2已结束")
+    private Integer status;
+
+    /** 公司ID */
+    @Excel(name = "公司ID")
+    private Long companyId;
+
+    /** 是否查看 */
+    @Excel(name = "是否查看")
+    private Long isLook;
+
+    /** 用户类型 1微信用户 2小程序用户 3销售用户 */
+    @Excel(name = "用户类型 1微信用户 2小程序用户 3销售用户")
+    private Integer userType;
+
+    /** 客户昵称 */
+    @Excel(name = "客户昵称")
+    private String nickName;
+
+    /** 头像 */
+    @Excel(name = "头像")
+    private String avatar;
+
+    private Integer isArtificial;
+
+    private Date remindTime;
+
+    private Integer remindStatus;
+
+    private Integer remindCount;
+
+    private Long qwExtId;
+    private Long qwUserId;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date lastTime;
+
+    private Integer isReply;
+
+    private Integer overTime;
+
+}

+ 31 - 0
fs-service/src/main/java/com/fs/fastgptApi/param/DouBaoAiParam.java

@@ -0,0 +1,31 @@
+package com.fs.fastgptApi.param;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class DouBaoAiParam {
+    private String model;
+    private List<Message> messages;
+    private Integer temperature=1;
+    private double top_p=0.7;
+    private Integer max_tokens=4096;
+    @Data
+    public static class Message {
+        private String role;
+        private List<Content> content;
+    }
+
+    @Data
+    public static class Content {
+        private String type;
+        private imageUrl image_url;
+        private String text;
+    }
+
+    @Data
+    public static class imageUrl {
+        private String url;
+    }
+}

+ 118 - 20
fs-service/src/main/java/com/fs/fastgptApi/util/AiImgUtil.java

@@ -1,7 +1,9 @@
 package com.fs.fastgptApi.util;
 
 import com.alibaba.fastjson.JSON;
+import com.fs.fastgptApi.param.DouBaoAiParam;
 import com.fs.fastgptApi.result.AiImgResult;
+import com.fs.qw.domain.QwUser;
 import org.springframework.stereotype.Service;
 
 import java.io.BufferedReader;
@@ -11,6 +13,8 @@ import java.io.OutputStream;
 import java.net.HttpURLConnection;
 import java.net.URL;
 import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
 
 @Service
 public class AiImgUtil {
@@ -18,31 +22,125 @@ public class AiImgUtil {
 
     public String getImageParse(String imageUrl) {
         try {
-            String requestBody = String.format(
-                    "{" +
-                            "\"model\": \"doubao-vision-lite-32k-241015\"," +
-                            "\"messages\": [{" +
-                            "\"role\": \"user\"," +
-                            "\"content\": [" +
-                            "{" +
-                            "\"type\": \"image_url\"," +
-                            "\"image_url\": {\"url\": \"" + imageUrl + "\"}" +
-                            "}," +
-                            "{" +
-                            "\"type\": \"text\"," +
-                            "\"text\": \"解析一下这张图片\"" +
-                            "}" +
-                            "]" +
-                            "}]" +
-                            "}"
-            );
 
+
+            DouBaoAiParam.Content imageContent = new DouBaoAiParam.Content();
+            imageContent.setType("image_url");
+            imageContent.setImage_url(new DouBaoAiParam.imageUrl());
+            imageContent.getImage_url().setUrl(imageUrl);
+
+
+            DouBaoAiParam.Content textContent = new DouBaoAiParam.Content();
+            textContent.setType("text");
+            textContent.setText(
+                    "识别图片内容 \n" +
+                            "情况一:图片为表情包的时候或是明确意义图片的时候,单独提取出表情包的含义为图片,并输出:【表情包:XXX】XXX为表情表达的内容,例如这个表情包是很开心的感谢,那么XXX就是谢谢。在【】外不进行其他的解释直接结束 \n" +
+                            "情况二:图片是舌头的时候,根据他的舌苔进行简单的分析,直接输出 \n" +
+                            "情况三:图片是其他的时候,正常提取图片内容,如果是身体异常部位要进行简单分析,直接输出,如果是卡通图片,需要在结尾输出【这是卡通图片】这几个字");
+
+
+            List<DouBaoAiParam.Content> contents = new ArrayList<>();
+            contents.add(imageContent);
+            contents.add(textContent);
+
+
+            DouBaoAiParam.Message message = new DouBaoAiParam.Message();
+            message.setRole("user");
+            message.setContent(contents);
+
+            // Add message to list
+            List<DouBaoAiParam.Message> messages = new ArrayList<>();
+
+            DouBaoAiParam.Content textContent2 = new DouBaoAiParam.Content();
+            textContent2.setType("text");
+            textContent2.setText(
+                    "识别图片内容 情况一:图片为表情包的时候或是明确意义图片的时候,单独提取出表情包的含义为图片,并输出:【表情包:XXX】XXX为表情表达的内容,例如这个表情包是很开心的感谢,那么XXX就是谢谢。在【】外不进行其他的解释直接结束 情况二:图片是舌头的时候,根据他的舌苔进行简单的分析,直接输出 情况三:图片是其他的时候,正常提取图片内容,如果是身体异常部位要进行简单分析,直接输出");
+
+
+            List<DouBaoAiParam.Content> contents2 = new ArrayList<>();
+            contents2.add(textContent2);
+            DouBaoAiParam.Message message2 = new DouBaoAiParam.Message();
+            message2.setRole("system");
+            message2.setContent(contents2);
+            // messages.add(message2);
+            messages.add(message);
+            // Create request
+            DouBaoAiParam request = new DouBaoAiParam();
+            request.setModel("doubao-1-5-thinking-vision-pro-250428");
+            request.setMessages(messages);
+
+            String jsonString = JSON.toJSONString(request);
+            System.out.println(jsonString);
+            // 发送请求
+            String response = sendAiImgHttpRequest(jsonString);
+            System.out.println("API响应: " + response);
+            AiImgResult aiImgResult = JSON.parseObject(response, AiImgResult.class);
+            String content = aiImgResult.getChoices().get(0).getMessage().getContent();
+
+            return content;
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    public String getImageParse(String imageUrl, QwUser qwUser,Long sender) {
+        try {
+
+
+            DouBaoAiParam.Content imageContent = new DouBaoAiParam.Content();
+            imageContent.setType("image_url");
+            imageContent.setImage_url(new DouBaoAiParam.imageUrl());
+            imageContent.getImage_url().setUrl(imageUrl);
+
+
+            DouBaoAiParam.Content textContent = new DouBaoAiParam.Content();
+            textContent.setType("text");
+            textContent.setText(
+                    "识别图片内容 \n" +
+                            "情况一:图片为表情包的时候或是明确意义图片的时候,单独提取出表情包的含义为图片,并输出:【表情包:XXX】XXX为表情表达的内容,例如这个表情包是很开心的感谢,那么XXX就是谢谢。在【】外不进行其他的解释直接结束 \n" +
+                            "情况二:图片是舌头的时候,根据他的舌苔进行简单的分析,直接输出 \n" +
+                            "情况三:图片是其他的时候,正常提取图片内容,如果是身体异常部位要进行简单分析,直接输出,如果是卡通图片,需要在结尾输出【这是卡通图片】这几个字");
+
+
+            List<DouBaoAiParam.Content> contents = new ArrayList<>();
+            contents.add(imageContent);
+            contents.add(textContent);
+
+
+            DouBaoAiParam.Message message = new DouBaoAiParam.Message();
+            message.setRole("user");
+            message.setContent(contents);
+
+            // Add message to list
+            List<DouBaoAiParam.Message> messages = new ArrayList<>();
+
+            DouBaoAiParam.Content textContent2 = new DouBaoAiParam.Content();
+            textContent2.setType("text");
+            textContent2.setText(
+                    "识别图片内容 情况一:图片为表情包的时候或是明确意义图片的时候,单独提取出表情包的含义为图片,并输出:【表情包:XXX】XXX为表情表达的内容,例如这个表情包是很开心的感谢,那么XXX就是谢谢。在【】外不进行其他的解释直接结束 情况二:图片是舌头的时候,根据他的舌苔进行简单的分析,直接输出 情况三:图片是其他的时候,正常提取图片内容,如果是身体异常部位要进行简单分析,直接输出");
+
+
+            List<DouBaoAiParam.Content> contents2 = new ArrayList<>();
+            contents2.add(textContent2);
+            DouBaoAiParam.Message message2 = new DouBaoAiParam.Message();
+            message2.setRole("system");
+            message2.setContent(contents2);
+            // messages.add(message2);
+            messages.add(message);
+            // Create request
+            DouBaoAiParam request = new DouBaoAiParam();
+            request.setModel("doubao-1-5-thinking-vision-pro-250428");
+            request.setMessages(messages);
+
+            String jsonString = JSON.toJSONString(request);
+            System.out.println(jsonString);
             // 发送请求
-            String response = sendAiImgHttpRequest(requestBody);
+            String response = sendAiImgHttpRequest(jsonString);
             System.out.println("API响应: " + response);
             AiImgResult aiImgResult = JSON.parseObject(response, AiImgResult.class);
+            EventLogUtils.createEventTokenLog("读取图片",qwUser,sender,aiImgResult);
+            EventLogUtils.recordEventLog(sender,1L,8, qwUser);
             String content = aiImgResult.getChoices().get(0).getMessage().getContent();
-            System.out.println(content);
             return content;
         } catch (Exception e) {
             return null;

+ 28 - 0
fs-service/src/main/java/com/fs/fastgptApi/util/EventLogQueue.java

@@ -0,0 +1,28 @@
+package com.fs.fastgptApi.util;
+
+import com.fs.fastGpt.domain.FastGptEventLog;
+import com.fs.fastGpt.domain.FastGptEventTokenLog;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+public class EventLogQueue {
+    private static final BlockingQueue<FastGptEventLog> eventLogQueue = new LinkedBlockingQueue<>(10000);
+    private static final BlockingQueue<FastGptEventTokenLog> eventTokenLogQueue = new LinkedBlockingQueue<>(10000);
+
+    public static void addEventLog(FastGptEventLog log) {
+        eventLogQueue.offer(log);
+    }
+
+    public static void addEventTokenLog(FastGptEventTokenLog log) {
+        eventTokenLogQueue.offer(log);
+    }
+
+    public static FastGptEventLog pollEventLog() {
+        return eventLogQueue.poll();
+    }
+
+    public static FastGptEventTokenLog pollEventTokenLog() {
+        return eventTokenLogQueue.poll();
+    }
+}

+ 154 - 0
fs-service/src/main/java/com/fs/fastgptApi/util/EventLogUtils.java

@@ -0,0 +1,154 @@
+package com.fs.fastgptApi.util;
+
+import com.fs.common.core.domain.entity.SysDictData;
+import com.fs.fastGpt.domain.FastGptEventLog;
+import com.fs.fastGpt.domain.FastGptEventTokenLog;
+import com.fs.fastGpt.service.IFastGptChatMsgService;
+import com.fs.fastgptApi.result.AiImgResult;
+import com.fs.fastgptApi.result.ChatDetailFStreamFResult;
+import com.fs.fastgptApi.result.ChatDetailTStreamFResult;
+import com.fs.qw.domain.QwUser;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import java.util.Date;
+import java.util.List;
+
+import static com.fs.common.utils.DictUtils.getDictCache;
+
+@Component
+@Slf4j
+public class EventLogUtils {
+
+    private static IFastGptChatMsgService fastGptChatMsgService;
+
+    @Autowired
+    public void setFastGptChatMsgService(IFastGptChatMsgService service) {
+        EventLogUtils.fastGptChatMsgService = service;
+    }
+
+    @Scheduled(fixedDelay = 200)
+    public void consumeLogs() {
+        FastGptEventLog eventLog;
+        while ((eventLog = EventLogQueue.pollEventLog()) != null) {
+            try {
+                fastGptChatMsgService.insertFastGptEventLog(eventLog);
+            } catch (Exception e) {
+                // 记录失败日志,可加入重试机制
+                log.error("Ai事件日志写入失败:" + eventLog, e);
+            }
+        }
+
+        FastGptEventTokenLog tokenLog;
+        while ((tokenLog = EventLogQueue.pollEventTokenLog()) != null) {
+            try {
+                fastGptChatMsgService.insertFastGptEventTokenLog(tokenLog);
+            } catch (Exception e) {
+                log.error("Ai的token事件日志写入失败:" + eventLog, e);
+            }
+        }
+    }
+
+    /**
+     *  记录ai事件日志
+     * @param count     触发数量
+     * @param type      事件类型(1互动 1总对话 3转人工 4AI无法回复转人工 5AI回复不合适转人工 6完课回复 7物流事件 8图片回复 9自定义事件回复 10用户未回复AI再次提醒)
+     * @param user      企微用户
+     */
+    public static void recordEventLog(Long senderId, Long count, Integer type, QwUser user) {
+        List<SysDictData> dictCache = getDictCache("sys_fastgpt_event_log_type");
+        String content = "未知事件";
+        if(dictCache != null){
+            for (SysDictData sysDictData : dictCache) {
+                if (type.toString().equals(sysDictData.getDictValue())){
+                    content = sysDictData.getDictLabel();
+                }
+            }
+        }
+        FastGptEventLog fastGptEventLog = new FastGptEventLog();
+        fastGptEventLog.setEventName(content);
+        fastGptEventLog.setSenderId(senderId);
+        fastGptEventLog.setRoleId(user.getFastGptRoleId());
+        fastGptEventLog.setCount(count);
+        fastGptEventLog.setType(type);
+        fastGptEventLog.setCompanyId(user.getCompanyId());
+        fastGptEventLog.setCompanyUserId(user.getCompanyUserId());
+        fastGptEventLog.setQwUserId(user.getId());
+        fastGptEventLog.setCreateTime(new Date());
+
+
+        //EventLogQueue.addEventLog(fastGptEventLog); // 入队
+        //fastGptChatMsgService.insertFastGptEventLog(fastGptEventLog);
+    }
+
+    /**
+     * 记录ai事件token消耗日志
+     * @param content       事件名称
+     * @param eventType     事件类型(1文字 2图片 3...)
+     * @param tokenCount    token数量
+     * @param tokenType     token类型(1输入 2输出)
+     * @param user
+     */
+    public static void recordEventTokenLog(String content,Long senderId,Integer eventType, Long tokenCount, Integer tokenType, QwUser user) {
+        FastGptEventTokenLog fastGptEventTokenLog = new FastGptEventTokenLog();
+        fastGptEventTokenLog.setEventName(content);
+        fastGptEventTokenLog.setSenderId(senderId);
+        fastGptEventTokenLog.setRoleId(user.getFastGptRoleId());
+        fastGptEventTokenLog.setEventType(eventType);
+        fastGptEventTokenLog.setTokenCount(tokenCount);
+        fastGptEventTokenLog.setTokenType(tokenType);
+        fastGptEventTokenLog.setCompanyId(user.getCompanyId());
+        fastGptEventTokenLog.setCompanyUserId(user.getCompanyUserId());
+        fastGptEventTokenLog.setQwUserId(user.getId());
+        fastGptEventTokenLog.setCreateTime(new Date());
+
+        //EventLogQueue.addEventTokenLog(fastGptEventTokenLog); // 入队
+        //fastGptChatMsgService.insertFastGptEventTokenLog(fastGptEventTokenLog);
+    }
+
+    public static void createEventTokenLog(String content,QwUser user,Long senderId,ChatDetailTStreamFResult result) {
+
+        //计算token
+        List<ChatDetailTStreamFResult.ResponseNode> responseData = result.getResponseData();
+        Long totalTokens = 0L;
+        for (ChatDetailTStreamFResult.ResponseNode responseDatum : responseData) {
+            int tokens = responseDatum.getTokens();
+            totalTokens+=tokens;
+        }
+        recordEventTokenLog(content + "消耗token",senderId,2,totalTokens,0, user);
+
+        Long promptTokens = (long) result.getUsage().getPrompt_tokens();
+        recordEventTokenLog(content + "输入token",senderId,2,promptTokens,1, user);
+
+        Long completionTokens = (long) result.getUsage().getCompletion_tokens();
+        recordEventTokenLog(content + "输出token",senderId,2,completionTokens,2, user);
+    }
+
+
+    public static void createEventTokenLog(String content,QwUser user,Long senderId,ChatDetailFStreamFResult result) {
+
+        Long totalTokens = (long) result.getUsage().getTotalTokens();
+        recordEventTokenLog(content + "消耗token",senderId,2,totalTokens,0, user);
+
+        Long promptTokens = (long) result.getUsage().getPromptTokens();
+        recordEventTokenLog(content + "输入token",senderId,2,promptTokens,1, user);
+
+        Long completionTokens = (long) result.getUsage().getCompletionTokens();
+        recordEventTokenLog(content + "输出token",senderId,2,completionTokens,2, user);
+    }
+
+    public static void createEventTokenLog(String content,QwUser user,Long senderId,AiImgResult result) {
+
+        Long totalTokens = (long) result.getUsage().getTotal_tokens();
+        recordEventTokenLog(content + "消耗token",senderId,2,totalTokens,0, user);
+
+        Long promptTokens = (long) result.getUsage().getPrompt_tokens();
+        recordEventTokenLog(content + "输入token",senderId,2,promptTokens,1, user);
+
+        Long completionTokens = (long) result.getUsage().getCompletion_tokens();
+        recordEventTokenLog(content + "输出token",senderId,2,completionTokens,2, user);
+    }
+
+}

+ 33 - 0
fs-service/src/main/java/com/fs/his/dto/PayloadDTO.java

@@ -0,0 +1,33 @@
+package com.fs.his.dto;
+
+import lombok.Data;
+
+import java.util.Date;
+
+@Data
+public class PayloadDTO {
+    private String data;
+    private Extension extension;
+    private String description;
+
+    @Data
+    public static class Extension{
+        private String title;
+        private String patientName;
+        private String sex;
+        private String mobile;
+        private String duration;
+        private String isVisit;
+        private String diagnose;
+        private String prescribeId;
+        private String description;
+        private String followId;
+        private Integer status;
+        private Date sendTime;
+        private String courseUrl;
+        private String appRealLink;
+        private String writeStatus;
+
+    }
+
+}

+ 2 - 0
fs-service/src/main/java/com/fs/his/mapper/FsStoreOrderMapper.java

@@ -1099,4 +1099,6 @@ public interface FsStoreOrderMapper
     List<FsStoreOrder> selectShippedOrder();
 
     List<FsStoreOrderListVO> selectFsStoreOrderListVOByErpAccount(@Param("maps") FsStoreOrderParam fsStoreOrder);
+
+    List<FsStoreOrder> selectFsStoreOrderByFsUserId(@Param("fsUserId") Long fsUserId);
 }

+ 11 - 0
fs-service/src/main/java/com/fs/his/param/PushFgOrderConfig.java

@@ -0,0 +1,11 @@
+package com.fs.his.param;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class PushFgOrderConfig {
+
+    private List<String> companyIds;
+}

+ 1 - 0
fs-service/src/main/java/com/fs/his/service/IFsStoreOrderService.java

@@ -247,4 +247,5 @@ public interface IFsStoreOrderService
     R receiveWaybillPush(String body);
 
     List<FsStoreOrderListVO> selectFsStoreOrderListVOByErpAccount(FsStoreOrderParam fsStoreOrder);
+    List<FsStoreOrder> selectFsStoreOrderByFsUserId(Long fsUserId);
 }

+ 4 - 0
fs-service/src/main/java/com/fs/his/service/impl/FsStoreOrderServiceImpl.java

@@ -3402,5 +3402,9 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService
         return null;
     }
 
+    @Override
+    public List<FsStoreOrder> selectFsStoreOrderByFsUserId(Long fsUserId) {
+        return fsStoreOrderMapper.selectFsStoreOrderByFsUserId(fsUserId);
+    }
 
 }

+ 51 - 0
fs-service/src/main/java/com/fs/his/vo/FsComplaintOrderExportVO.java

@@ -0,0 +1,51 @@
+package com.fs.his.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * @author MixLiu
+ * @date 2025/7/2 下午5:13)
+ */
+
+@Data
+public class FsComplaintOrderExportVO implements Serializable  {
+
+    private static final long serialVersionUID = 1L;
+
+    private Long OrderId;
+
+    @Excel(name = "订单号")
+    private String orderCode;
+
+    @Excel(name = "公司")
+    private String companyName;
+
+    @Excel(name = "业务员")
+    private String companyUserNickName;
+
+    /** 用户姓名 */
+    @Excel(name = "收货人")
+    private String userName;
+
+    /** 支付时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "支付时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date payTime;
+
+    /** 投诉时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "投诉时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date complaintTime;
+
+    @Excel(name = "异议内容")
+    private String objectionContent;
+
+    @Excel(name = "处理结果")
+    private String handleResult;
+
+}

+ 13 - 0
fs-service/src/main/java/com/fs/his/vo/FsStoreOrderAndUserVo.java

@@ -0,0 +1,13 @@
+package com.fs.his.vo;
+
+import lombok.Data;
+
+@Data
+public class FsStoreOrderAndUserVo {
+    /** 订单ID */
+    private Float type; //订单类型
+    private Long orderId;
+    private Long userId;
+    private String jpushId; //推送id
+
+}

+ 35 - 0
fs-service/src/main/java/com/fs/his/vo/FsStoreOrderLogisticsFgVO.java

@@ -0,0 +1,35 @@
+package com.fs.his.vo;
+
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+
+@Data
+public class FsStoreOrderLogisticsFgVO {
+
+    private String scorpId; //企微主体 ID
+    private String acctid; //企微账号 ID
+    private String externalUserId; //企微客户 ID
+    private Integer status; //当前物流状态
+    /** 快递公司编号 */
+    @Excel(name = "快递公司编号")
+    private String deliveryCode;
+    /**
+    * 快递公司名称
+    */
+    private String deliveryName;
+
+    private String packageName;
+
+    /** 收货人电话 */
+    @Excel(name = "收货人电话")
+    private String userPhone;
+
+    /** 订单号 */
+    @Excel(name = "订单号")
+    private String orderCode;
+
+    /** 快递单号 */
+    @Excel(name = "快递单号")
+    private String deliverySn;
+
+}

+ 25 - 0
fs-service/src/main/java/com/fs/his/vo/FsStoreOrderPushFgVO.java

@@ -0,0 +1,25 @@
+package com.fs.his.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+@Data
+public class FsStoreOrderPushFgVO {
+
+    private String corpId;//企微主体 ID
+    private String userId;//企微账号 ID
+    private String externalUserId;//企微客户  ID
+    private String orderCode;//	订单号
+
+    private String packageName;//	商品名称(套餐包名称)
+    private Integer status;//	订单状态 1待付款 2已付款 3处理中 4已发货 5已完成 6已取消 7退款中 8已退款
+
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private String createTime;//下单时间 格式为 yyyy-MM-dd HH:mm:ss
+
+    private Long payPrice;//	订单金额(分)(处方定价)
+    private Integer totalNum;//	商品数量
+    private Integer packageSubType; //套餐包类型
+
+}

+ 29 - 0
fs-service/src/main/java/com/fs/im/config/IMConfig.java

@@ -0,0 +1,29 @@
+package com.fs.im.config;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+@Component
+//@ConfigurationProperties(prefix = "openIM")
+public class IMConfig {
+    @Value("${openIM.secret}")
+    private String secret;
+    @Value("${openIM.userID}")
+    private String userID;
+
+    public String getSecret() {
+        return secret;
+    }
+
+    public void setSecret(String secret) {
+        this.secret = secret;
+    }
+
+    public String getUserID() {
+        return userID;
+    }
+
+    public void setUserID(String userID) {
+        this.userID = userID;
+    }
+}

+ 15 - 0
fs-service/src/main/java/com/fs/im/dto/OpenImConversationDTO.java

@@ -0,0 +1,15 @@
+package com.fs.im.dto;
+
+import lombok.Data;
+
+@Data
+public class OpenImConversationDTO {
+             private String conversationID;
+             private Integer conversationType;
+             private String userID;
+             private String groupID;
+             private String recvMsgOpt;
+             private String attachedInfo;
+             private String ex;
+
+}

+ 11 - 0
fs-service/src/main/java/com/fs/im/dto/OpenImEditConversationDTO.java

@@ -0,0 +1,11 @@
+package com.fs.im.dto;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class OpenImEditConversationDTO {
+    private OpenImConversationDTO conversation;
+    private List<String> userIDs;
+}

+ 45 - 0
fs-service/src/main/java/com/fs/im/dto/OpenImMsgDTO.java

@@ -0,0 +1,45 @@
+package com.fs.im.dto;
+
+import com.fs.his.dto.PayloadDTO;
+import lombok.Data;
+
+@Data
+public class OpenImMsgDTO {
+
+    private String sendID;
+    private String recvID;
+    private String groupID;
+    private String senderNickname;
+    private String senderFaceURL;
+    private int senderPlatformID;
+    private Content content;
+    private int contentType;
+    private int sessionType;
+    private boolean isOnlineOnly;
+    private boolean notOfflinePush;
+    private long sendTime;
+    private OfflinePushInfo offlinePushInfo;
+    private String ex;
+
+
+    @Data
+    public static class Content {
+        private String content;
+        private String data;
+        private String description;
+        private String extension;
+    }
+    @Data
+    public static class ImData{
+        private PayloadDTO payload;
+    }
+    @Data
+    public static class OfflinePushInfo {
+        private String title;
+        private String desc;
+        private String ex;
+        private String iOSPushSound;
+        private boolean iOSBadgeCount;
+
+    }
+}

+ 13 - 0
fs-service/src/main/java/com/fs/im/dto/OpenImResponseDTO.java

@@ -0,0 +1,13 @@
+package com.fs.im.dto;
+
+import lombok.Data;
+
+import java.util.Map;
+
+@Data
+public class OpenImResponseDTO {
+    private Integer errCode;
+    private String errMsg;
+    private String errDlt;
+    private Map<String, Object> data;
+}

+ 34 - 0
fs-service/src/main/java/com/fs/im/service/OpenIMService.java

@@ -0,0 +1,34 @@
+package com.fs.im.service;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fs.common.core.domain.R;
+import com.fs.company.domain.CompanyUser;
+import com.fs.im.dto.OpenImEditConversationDTO;
+import com.fs.im.dto.OpenImMsgDTO;
+import com.fs.im.dto.OpenImResponseDTO;
+import com.fs.im.vo.OpenImMsgCallBackVO;
+
+import java.util.List;
+
+public interface OpenIMService {
+    String getAdminToken();
+
+    OpenImResponseDTO openIMSendMsg(OpenImMsgDTO openImMsgDTO);
+
+    OpenImResponseDTO AiAutoReply(OpenImMsgCallBackVO messageInfo) throws JsonProcessingException;
+
+    OpenImResponseDTO sendUtil(String sendID, String recvID,Integer contentType,String payloadData,String diagnose,String title,String followId,String orderId,String ex) throws JsonProcessingException;
+
+    OpenImResponseDTO sendUtilUserToDoctor(String sendID, String recvID,Integer contentType,String payloadData,String diagnose,String title,String followId,String orderId,String ex) throws JsonProcessingException;
+
+    OpenImResponseDTO editConversation(OpenImEditConversationDTO openImEditConversationDTO) throws JsonProcessingException, InterruptedException;
+
+    OpenImResponseDTO importFriend(String ownerUserID,List<String> friendUserIDs);
+
+    OpenImResponseDTO isFriend(String userID1, String userID2);
+    R accountCheck(String userId, String type);
+    void checkAndImportFriend(Long companyUserId,String fsUserId);
+    OpenImResponseDTO sendCourse(Long userId,Long companyUserId,String url,String title,String linkImageUrl,String cropId) throws JsonProcessingException;
+    void checkAndImportFriendByDianBo(Long companyUserId,String fsUserId,String cropId);
+    OpenImResponseDTO updateUserInfo(CompanyUser companyUser);
+}

+ 882 - 0
fs-service/src/main/java/com/fs/im/service/impl/OpenIMServiceImpl.java

@@ -0,0 +1,882 @@
+package com.fs.im.service.impl;
+
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.json.JSONUtil;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fs.common.core.domain.R;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.common.utils.StringUtils;
+import com.fs.company.domain.Company;
+import com.fs.company.domain.CompanyUser;
+import com.fs.company.mapper.CompanyMapper;
+import com.fs.company.mapper.CompanyUserMapper;
+import com.fs.fastGpt.service.AiHookService;
+import com.fs.his.domain.FsDoctor;
+import com.fs.his.domain.FsFollow;
+import com.fs.his.domain.FsUser;
+import com.fs.his.dto.PayloadDTO;
+import com.fs.his.mapper.FsDoctorMapper;
+import com.fs.his.mapper.FsFollowMapper;
+import com.fs.his.mapper.FsUserMapper;
+import com.fs.im.config.IMConfig;
+import com.fs.im.dto.OpenImConversationDTO;
+import com.fs.im.dto.OpenImEditConversationDTO;
+import com.fs.im.dto.OpenImMsgDTO;
+import com.fs.im.dto.OpenImResponseDTO;
+import com.fs.im.service.OpenIMService;
+import com.fs.im.vo.OpenImMsgCallBackVO;
+import com.fs.im.vo.OpenImResponseDTOTest;
+import com.fs.qw.mapper.QwExternalContactMapper;
+import com.github.pagehelper.util.StringUtil;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.json.JSONArray;
+import org.json.JSONObject;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+@Service
+@Slf4j
+public class OpenIMServiceImpl implements OpenIMService {
+    @Autowired
+    IMConfig imConfig;
+    @Autowired
+    private RedisCache redisCache;
+    @Autowired
+    private FsUserMapper fsUserMapper;
+    @Autowired
+    private FsDoctorMapper fsDoctorMapper;
+    @Autowired
+    private CompanyUserMapper companyUserMapper;
+    @Autowired
+    private CompanyMapper companyMapper;
+    @Autowired
+    private FsFollowMapper fsFollowMapper;
+    @Autowired
+    private QwExternalContactMapper qwExternalContactMapper;
+    @Autowired
+    @Lazy
+    private AiHookService aiHookService;
+    /*@Autowired
+    private IFsUserService fsUserService;*/
+    @Override
+    public String getAdminToken() {
+        Object cachedTokenObj = redisCache.getCacheObject("openImAdminToken:" + imConfig.getUserID());
+        if (cachedTokenObj != null) {
+            return cachedTokenObj.toString();
+        }
+        JSONObject requestBody = new JSONObject();
+        requestBody.put("secret", imConfig.getSecret());  // 预设的管理员密钥
+        requestBody.put("userID", imConfig.getUserID());   // 管理员 userID
+        String adminToken = null;
+        // 发起 HTTP POST 请求,获取管理员 token
+        try {
+            String response = HttpRequest.post("https://web.im.cdwjyyh.com/api/auth/get_admin_token")
+                    .header("operationID", String.valueOf(System.currentTimeMillis()))
+                    .body(requestBody.toString())
+                    .execute()
+                    .body();
+
+            JSONObject jsonResponse = new JSONObject(response);
+            if (jsonResponse.getInt("errCode") == 0) {
+                JSONObject data = jsonResponse.getJSONObject("data");
+                adminToken = data.getString("token");
+                redisCache.setCacheObject("openImAdminToken:" + imConfig.getUserID(), adminToken,
+                        data.getInt("expireTimeSeconds"), TimeUnit.SECONDS);
+                return adminToken;
+            }
+        } catch (Exception e) {
+            // 可以记录日志
+            log.error("获取管理员 token 失败", e);
+        }
+        return null;
+    }
+
+    @Override
+    public OpenImResponseDTO AiAutoReply(OpenImMsgCallBackVO openImMsgDTO) throws JsonProcessingException {
+        try {
+            String sendID = openImMsgDTO.getSendID();
+            // 如果发送人不是用户 直接返回 不做自动回复
+            if (!sendID.startsWith("U")) {
+                return null;
+            }
+            String recvType = "2"; // 接收信息人类型 2销售,3医生 默认销售
+            OpenImMsgDTO.OfflinePushInfo offlinePushInfo = new OpenImMsgDTO.OfflinePushInfo();
+            // 初始化ObjectMapper对象,用于JSON序列化和反序列化
+            ObjectMapper objectMapper = new ObjectMapper();
+            // 初始化消息DTO对象
+            OpenImMsgDTO replyMsg = new OpenImMsgDTO();
+            String recvID = openImMsgDTO.getRecvID();
+            Long companyId = 0L;
+            if (recvID.startsWith("D")) {
+                // 因为主要是销售想接入ai ,所以医生不回复,只回复销售 下面暂时保留
+                return null;
+//                FsDoctor fsDoctor = fsDoctorMapper.selectFsDoctorByDoctorId(Long.parseLong(recvID.replace("D", "")));
+//                if (null != fsDoctor && StringUtils.isNotEmpty(fsDoctor.getAvatar())) {
+//                    offlinePushInfo.setTitle(fsDoctor.getDoctorName());
+//                    replyMsg.setSenderFaceURL(fsDoctor.getAvatar());
+//                }
+            } else if (recvID.startsWith("C")) {
+                CompanyUser company = companyUserMapper.selectCompanyUserByUserId(Long.parseLong(recvID.replace("C", "")));
+                companyId = company.getCompanyId();
+                if (null != company && StringUtils.isNotEmpty(company.getAvatar())) {
+                    offlinePushInfo.setTitle(company.getImNickName());
+                    replyMsg.setSenderFaceURL(company.getAvatar());
+                }
+                recvType = "2";
+            }
+            // 对接收者ID进行账户校验
+            accountCheck(recvID.toString(), recvType);
+            if (companyId == 0L) {
+                return null;
+            }
+            // ai接口智能回复
+            R r = aiHookService.AiReply(openImMsgDTO, companyId);
+            if (r.get("code").equals(500)) {
+                return null;
+            }
+            String aiReplyContent = r.get("content").toString();
+
+            // 设置消息的发送者ID、接收者ID、内容类型等基础信息
+            // 将发送者和接收者交换一下
+            replyMsg.setSendID(recvID);
+            replyMsg.setRecvID(sendID);
+            replyMsg.setContentType(101);
+            replyMsg.setSenderPlatformID(5);
+            replyMsg.setSessionType(1);
+
+            // 初始化消息内容对象
+            OpenImMsgDTO.Content content = new OpenImMsgDTO.Content();
+            // 初始化负载数据对象,并设置数据内容
+            PayloadDTO payload = new PayloadDTO();
+            payload.setData(aiReplyContent);
+
+            PayloadDTO.Extension extension = new PayloadDTO.Extension();
+            extension.setTitle("快速回复"); // 可选标题
+            payload.setExtension(extension);
+            OpenImMsgDTO.ImData imData = new OpenImMsgDTO.ImData();
+            imData.setPayload(payload);
+            String imJson = objectMapper.writeValueAsString(imData);
+            content.setData(imJson);
+            content.setContent(aiReplyContent);
+            replyMsg.setContent(content);
+            offlinePushInfo.setDesc("快速回复");
+            offlinePushInfo.setIOSBadgeCount(true);
+            offlinePushInfo.setIOSPushSound("");
+            replyMsg.setOfflinePushInfo(offlinePushInfo);
+            // 发送消息
+            OpenImResponseDTO response = openIMSendMsg(replyMsg);
+            if (response.getErrCode() == 0) {
+                Thread.sleep(1000);
+                OpenImEditConversationDTO openImEditConversationDTO = new OpenImEditConversationDTO();
+                ArrayList<String> userIDs = new ArrayList<>();
+                userIDs.add(recvID);
+                openImEditConversationDTO.setUserIDs(userIDs);
+                OpenImConversationDTO openImConversationDTO = new OpenImConversationDTO();
+                openImConversationDTO.setConversationID("si_" + recvID + "_" + sendID);
+                openImConversationDTO.setConversationType(1);
+                openImConversationDTO.setUserID(sendID);
+                openImEditConversationDTO.setConversation(openImConversationDTO);
+                OpenImResponseDTO openImResponseDTO1 = editConversation(openImEditConversationDTO);
+                log.info("修改回话返回参数:{}", openImResponseDTO1);
+            }
+            return null;
+        } catch (Exception e) {
+            log.error("openImMsgCallBack,自动销售回复消息,发送消息失败:", e);
+            e.printStackTrace();
+            return null;
+        }
+
+    }
+
+    //批量修改销售名称,由昵称-公司名,改为昵称
+    public static void main(String[] args) {
+        String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySUQiOiJpbUFkbWluIiwiUGxhdGZvcm1JRCI6MTAsImV4cCI6MTc1OTkxMDgwNywiaWF0IjoxNzUyMTM0ODAyfQ.y0akpb-TnOBqJewPUD13tnUeR1iF41A3CcgaXXsjyKE";
+        long time = System.currentTimeMillis();
+        int pageSize = 5000;
+        int pageNumber = 1;
+
+        while (true) {
+            // 构建分页查询请求体
+            JSONObject requestPage = new JSONObject();
+            JSONObject pagination = new JSONObject();
+            pagination.put("pageNumber", pageNumber);
+            pagination.put("showNumber", pageSize);
+            requestPage.put("pagination", pagination);
+
+            String result = HttpRequest.post("https://web.im.cdwjyyh.com/api/user/get_all_users_uid")
+                    .header("operationID", String.valueOf(time))
+                    .header("token", token)
+                    .body(requestPage.toString())
+                    .execute()
+                    .body();
+
+            OpenImResponseDTO responseDTO = JSONUtil.toBean(result, OpenImResponseDTO.class);
+            List<String> userIDs = (List<String>) responseDTO.getData().get("userIDs");
+
+            if (CollectionUtil.isEmpty(userIDs)) {
+                System.out.println("数据为空,处理结束");
+                break;
+            }
+
+            // 过滤出以C开头的用户ID
+            List<String> userIds = userIDs.stream()
+                    .filter(uid -> uid.startsWith("C"))
+                    .collect(Collectors.toList());
+
+            if (CollectionUtil.isNotEmpty(userIds)) {
+                Map<String, Object> paramMap = new HashMap<>();
+                paramMap.put("userIDs", userIds);
+
+                String jsonBody = JSONUtil.toJsonStr(paramMap);
+                String result1 = HttpRequest.post("https://web.im.cdwjyyh.com/api/user/get_users_info")
+                        .header("operationID", String.valueOf(time))
+                        .header("token", token)
+                        .body(jsonBody)
+                        .execute()
+                        .body();
+
+                OpenImResponseDTOTest responseDTO1 = JSONUtil.toBean(result1, OpenImResponseDTOTest.class);
+                List<UserInfo> users = responseDTO1.getData().getUsersInfo();
+
+                for (UserInfo user : users) {
+                    if (StringUtil.isNotEmpty(user.getNickname()) && user.getNickname().contains("-")) {
+                        UpdateUserInfo updateUserInfo = new UpdateUserInfo();
+                        updateUserInfo.setUserID(user.getUserID());
+                        updateUserInfo.setNickname(user.getNickname().split("-")[0]);
+                        updateUserInfo.setFaceURL(Optional.ofNullable(user.getFaceURL()).orElse(""));
+                        updateUserInfo.setEx(Optional.ofNullable(user.getEx()).orElse(""));
+
+                        Map<String, Object> bodyMap = new HashMap<>();
+                        bodyMap.put("userInfo", updateUserInfo);
+
+                        String jsonBody1 = JSONUtil.toJsonStr(bodyMap);
+                        String result2 = HttpRequest.post("https://web.im.cdwjyyh.com/api/user/update_user_info_ex")
+                                .header("operationID", String.valueOf(System.currentTimeMillis()))
+                                .header("token", token)
+                                .body(jsonBody1)
+                                .execute()
+                                .body();
+
+                        OpenImResponseDTO responseDTO2 = JSONUtil.toBean(result2, OpenImResponseDTO.class);
+                        if (responseDTO2.getErrCode() != 0) {
+                            System.out.println("更新失败 userID=" + user.getUserID() + ",错误信息:" + responseDTO2.getErrMsg());
+                        }
+                    }
+                }
+            }
+
+            System.out.println("已处理第 " + pageNumber + " 页,共处理用户数:" + userIDs.size());
+            if (userIDs.size() < pageSize) {
+                System.out.println("已是最后一页,处理完毕!");
+                break;
+            }
+            pageNumber++; // 下一页
+        }
+    }
+
+    /**
+     * 修改im用户信息
+     * @param companyUser
+     * @return
+     */
+    @Override
+    public OpenImResponseDTO updateUserInfo(CompanyUser companyUser){
+        String adminToken = getAdminToken();
+        long time = System.currentTimeMillis();
+        OpenImResponseDTO responseDTO = null;
+        Map<String, Object> paramMap = new HashMap<>();
+        ArrayList<String> userIDs = new ArrayList<>();
+        userIDs.add("C"+companyUser.getUserId());
+        paramMap.put("userIDs", userIDs);
+
+        String jsonBody = JSONUtil.toJsonStr(paramMap);
+        String result1 = HttpRequest.post("https://web.im.cdwjyyh.com/api/user/get_users_info")
+                .header("operationID", String.valueOf(time))
+                .header("token", adminToken)
+                .body(jsonBody)
+                .execute()
+                .body();
+        UpdateUserInfo updateUserInfo = new UpdateUserInfo();
+        OpenImResponseDTOTest responseDTO1 = JSONUtil.toBean(result1, OpenImResponseDTOTest.class);
+        List<UserInfo> users = responseDTO1.getData().getUsersInfo();
+        if (users.size()<=0){
+            return null;
+        }
+        for (UserInfo user : users) {
+            updateUserInfo.setUserID(user.getUserID());
+            updateUserInfo.setNickname(StringUtils.isNotEmpty(companyUser.getImNickName())?companyUser.getImNickName():companyUser.getNickName());
+            updateUserInfo.setFaceURL(Optional.ofNullable(user.getFaceURL()).orElse(""));
+            updateUserInfo.setEx(Optional.ofNullable(user.getEx()).orElse(""));
+
+            Map<String, Object> bodyMap = new HashMap<>();
+            bodyMap.put("userInfo", updateUserInfo);
+            String jsonBody1 = JSONUtil.toJsonStr(bodyMap);
+            String result2 = HttpRequest.post("https://web.im.cdwjyyh.com/api/user/update_user_info_ex")
+                    .header("operationID", String.valueOf(System.currentTimeMillis()))
+                    .header("token", adminToken)
+                    .body(jsonBody1)
+                    .execute()
+                    .body();
+            responseDTO= JSONUtil.toBean(result2,OpenImResponseDTO.class);
+        }
+
+        return responseDTO;
+    }
+    @Data
+    public static class UpdateUserInfo {
+        private String userID;
+        private String nickname;
+        private String faceURL = "";
+        private String ex = "";
+    }
+
+    @Data
+    public class UserInfo {
+        private String userID;
+        private String nickname;
+        private String faceURL;
+        private String ex;
+        private Long createTime;
+        private Integer appMangerLevel;
+        private Integer globalRecvMsgOpt;
+    }
+
+
+    @Override
+    public OpenImResponseDTO openIMSendMsg(OpenImMsgDTO openImMsgDTO) {
+        log.info("进入发消息的方法");
+        String adminToken = getAdminToken();
+        JSONObject jsonObject = new JSONObject(openImMsgDTO);
+        log.info("发送消息的请求体:\n{}", jsonObject.toString());
+        long time = new Date().getTime();
+        String result = HttpRequest.post("https://web.im.cdwjyyh.com/api/msg/send_msg")
+                .header("operationID", time + "")
+                .header("token",adminToken)
+                .body(jsonObject.toString())
+                .execute()
+                .body();
+        OpenImResponseDTO responseDTO= JSONUtil.toBean(result,OpenImResponseDTO.class);
+        log.info("发送消息返回内容:\n{}", responseDTO);
+        return responseDTO;
+    }
+    @Override
+    public OpenImResponseDTO sendCourse(Long userId,Long companyUserId,String url,String title,String linkImageUrl,String cropId) throws JsonProcessingException {
+        ObjectMapper objectMapper = new ObjectMapper();
+        //userId = 61l;
+        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); // 忽略null字段
+        checkAndImportFriendByDianBo(companyUserId,userId.toString(),cropId);
+        OpenImMsgDTO.Content content = new OpenImMsgDTO.Content();
+        OpenImMsgDTO.ImData imData = new OpenImMsgDTO.ImData();
+        PayloadDTO payload = new PayloadDTO();
+        PayloadDTO.Extension extension = new PayloadDTO.Extension();
+        payload.setData("course");
+        extension.setTitle(title);
+        extension.setAppRealLink(url);
+        extension.setSendTime(new Date());
+        extension.setCourseUrl(linkImageUrl);
+        payload.setExtension(extension);
+        imData.setPayload(payload);
+        String imJson = objectMapper.writeValueAsString(imData);
+        content.setData(imJson);
+
+        OpenImMsgDTO.OfflinePushInfo offlinePushInfo = new OpenImMsgDTO.OfflinePushInfo();
+        offlinePushInfo.setDesc(title);
+        offlinePushInfo.setTitle("芳华未来");
+        offlinePushInfo.setIOSBadgeCount(true);
+        offlinePushInfo.setIOSPushSound("");
+
+        OpenImMsgDTO openImMsgDTO = new OpenImMsgDTO();
+        openImMsgDTO.setOfflinePushInfo(offlinePushInfo);
+        openImMsgDTO.setContent(content);
+
+
+
+        openImMsgDTO.setSendID("C"+companyUserId);
+        openImMsgDTO.setRecvID("U"+userId);
+        openImMsgDTO.setContentType(110);
+        openImMsgDTO.setSessionType(1);
+        // 输出格式化JSON日志
+        log.info("课程消息:\n{}", objectMapper.writeValueAsString(openImMsgDTO));
+        OpenImResponseDTO openImResponseDTO = openIMSendMsg(openImMsgDTO);
+        openImMsgDTO = null;
+        content = null;
+        return openImResponseDTO;
+    }
+    @Override
+    public OpenImResponseDTO sendUtil(String sendID, String recvID, Integer contentType, String payloadData, String diagnose,String title,String followId,String orderId,String ex) throws JsonProcessingException {
+        try {
+            OpenImMsgDTO.OfflinePushInfo offlinePushInfo = new OpenImMsgDTO.OfflinePushInfo();
+            ObjectMapper objectMapper = new ObjectMapper();
+            OpenImMsgDTO openImMsgDTO = new OpenImMsgDTO();
+            if (sendID.startsWith("D")){
+                FsDoctor fsDoctor = fsDoctorMapper.selectFsDoctorByDoctorId(Long.parseLong(sendID.replace("D","")));
+                //FsUser fsUser = fsUserService.selectFsUserByUserId(sendID);
+                if (null!=fsDoctor&&StringUtils.isNotEmpty(fsDoctor.getAvatar())){
+                    offlinePushInfo.setTitle(fsDoctor.getDoctorName());
+                    openImMsgDTO.setSenderFaceURL(fsDoctor.getAvatar());
+                }
+            }else if (sendID.startsWith("C")){
+                CompanyUser company = companyUserMapper.selectCompanyUserByUserId(Long.parseLong(sendID.replace("C", "")));
+                if (null!=company&&StringUtils.isNotEmpty(company.getAvatar())){
+                    offlinePushInfo.setTitle(company.getImNickName());
+                    openImMsgDTO.setSenderFaceURL(company.getAvatar());
+                }
+            }
+            accountCheck(recvID.toString(),"1");
+            openImMsgDTO.setSendID(sendID);
+            openImMsgDTO.setRecvID(recvID);
+            openImMsgDTO.setContentType(contentType);
+            openImMsgDTO.setSenderPlatformID(5);
+            openImMsgDTO.setSessionType(1);
+            if (StringUtils.isNotEmpty(ex)){
+                openImMsgDTO.setEx(ex);
+            }
+            OpenImMsgDTO.Content content = new OpenImMsgDTO.Content();
+            //content.setContent(ext);
+            PayloadDTO payload = new PayloadDTO();
+            payload.setData(payloadData);
+            PayloadDTO.Extension extension = new PayloadDTO.Extension();
+            //extension.setDiagnose(diagnose);
+            if (StringUtils.isNotEmpty(title)){
+                extension.setTitle(title);
+            }
+            if (StringUtils.isNotEmpty(diagnose)){
+                extension.setDiagnose(diagnose);
+            }
+            if (StringUtils.isNotEmpty(followId)){
+                //随访医生与患者加好友
+                ArrayList<String> userIds = new ArrayList<>();
+                userIds.add(recvID);
+                importFriend(sendID,userIds);
+                extension.setFollowId(followId);
+            }
+            if (StringUtils.isNotEmpty(orderId)){
+                payload.setDescription(orderId);
+            }
+
+            payload.setExtension(extension);
+            OpenImMsgDTO.ImData imData = new OpenImMsgDTO.ImData();
+
+            imData.setPayload(payload);
+
+            String imJson = objectMapper.writeValueAsString(imData);
+            content.setData(imJson);
+            openImMsgDTO.setContent(content);
+            if (contentType == 101){
+                content.setContent(title);
+            }
+            //cn.hutool.json.JSONObject jsonObject = new cn.hutool.json.JSONObject(openImMsgDTO);
+
+            //openImMsgDTO.setEx(payload);
+
+            offlinePushInfo.setDesc(title);
+
+            offlinePushInfo.setIOSBadgeCount(true);
+            offlinePushInfo.setIOSPushSound("");
+            openImMsgDTO.setOfflinePushInfo(offlinePushInfo);
+            OpenImResponseDTO openImResponseDTO = openIMSendMsg(openImMsgDTO);
+            if (openImResponseDTO.getErrCode()==0){
+                Thread.sleep(1000 );
+                OpenImEditConversationDTO openImEditConversationDTO = new OpenImEditConversationDTO();
+                ArrayList<String> userIDs = new ArrayList<>();
+                userIDs.add(sendID);
+                openImEditConversationDTO.setUserIDs(userIDs);
+                OpenImConversationDTO openImConversationDTO = new OpenImConversationDTO();
+                openImConversationDTO.setConversationID("si_"+sendID+"_"+recvID);
+                openImConversationDTO.setConversationType(1);
+                openImConversationDTO.setUserID(recvID);
+                openImConversationDTO.setEx(ex);
+                log.info("更新到会话的ex的值:{}",ex);
+                openImEditConversationDTO.setConversation(openImConversationDTO);
+
+                OpenImResponseDTO openImResponseDTO1 = editConversation(openImEditConversationDTO);
+                log.info("修改回话返回参数:{}",openImResponseDTO1);
+
+            }
+            return openImResponseDTO;
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+    public OpenImResponseDTO sendUtilUserToDoctor(String sendID, String recvID, Integer contentType, String payloadData, String diagnose,String title,String followId,String orderId,String ex) throws JsonProcessingException {
+        try {
+            ObjectMapper objectMapper = new ObjectMapper();
+            OpenImMsgDTO openImMsgDTO = new OpenImMsgDTO();
+            if (sendID.startsWith("U")){
+                //FsDoctor fsDoctor = fsDoctorMapper.selectFsDoctorByDoctorId(Long.parseLong(sendID.replace("D","")));
+                FsUser fsUser = fsUserMapper.selectFsUserByUserId(Long.parseLong(sendID.replace("U","")));
+                if (null!=fsUser&&StringUtils.isNotEmpty(fsUser.getAvatar())){
+                    openImMsgDTO.setSenderFaceURL(fsUser.getAvatar());
+                }
+            }
+            accountCheck(sendID.toString(),"1");
+            openImMsgDTO.setSendID(sendID);
+            openImMsgDTO.setRecvID(recvID);
+            openImMsgDTO.setContentType(contentType);
+            openImMsgDTO.setSenderPlatformID(5);
+            openImMsgDTO.setSessionType(1);
+            if (StringUtils.isNotEmpty(ex)){
+                openImMsgDTO.setEx(ex);
+            }
+            OpenImMsgDTO.Content content = new OpenImMsgDTO.Content();
+            //content.setContent(ext);
+            PayloadDTO payload = new PayloadDTO();
+            payload.setData(payloadData);
+            PayloadDTO.Extension extension = new PayloadDTO.Extension();
+            extension.setDiagnose(diagnose);
+            if (StringUtils.isNotEmpty(title)){
+                extension.setTitle(title);
+            }
+            if (StringUtils.isNotEmpty(diagnose)){
+                extension.setDiagnose(diagnose);
+            }
+            if (StringUtils.isNotEmpty(followId)){
+                FsFollow follow=fsFollowMapper.selectFsFollowByFollowId(Long.parseLong(followId));
+
+                //随访医生与患者加好友
+                ArrayList<String> userIds = new ArrayList<>();
+                userIds.add(recvID);
+                importFriend(sendID,userIds);
+                extension.setFollowId(followId);
+                extension.setWriteStatus(follow.getWriteStatus().toString());
+            }
+            if (StringUtils.isNotEmpty(orderId)){
+                payload.setDescription(orderId);
+            }
+            payload.setExtension(extension);
+            OpenImMsgDTO.ImData imData = new OpenImMsgDTO.ImData();
+
+            imData.setPayload(payload);
+
+            String imJson = objectMapper.writeValueAsString(imData);
+            content.setData(imJson);
+            openImMsgDTO.setContent(content);
+
+            //cn.hutool.json.JSONObject jsonObject = new cn.hutool.json.JSONObject(openImMsgDTO);
+
+            //openImMsgDTO.setEx(payload);
+            OpenImMsgDTO.OfflinePushInfo offlinePushInfo = new OpenImMsgDTO.OfflinePushInfo();
+            offlinePushInfo.setDesc(title);
+            offlinePushInfo.setTitle("芳华未来");
+            offlinePushInfo.setIOSBadgeCount(true);
+            offlinePushInfo.setIOSPushSound("");
+            openImMsgDTO.setOfflinePushInfo(offlinePushInfo);
+            OpenImResponseDTO openImResponseDTO = openIMSendMsg(openImMsgDTO);
+            if (openImResponseDTO.getErrCode()==0){
+                Thread.sleep(1000 );
+                OpenImEditConversationDTO openImEditConversationDTO = new OpenImEditConversationDTO();
+                ArrayList<String> userIDs = new ArrayList<>();
+                userIDs.add(sendID);
+                openImEditConversationDTO.setUserIDs(userIDs);
+                OpenImConversationDTO openImConversationDTO = new OpenImConversationDTO();
+                openImConversationDTO.setConversationID("si_"+sendID+"_"+recvID);
+                openImConversationDTO.setConversationType(1);
+                openImConversationDTO.setUserID(recvID);
+                openImConversationDTO.setEx(ex);
+                log.info("更新到会话的ex的值:{}",ex);
+                openImEditConversationDTO.setConversation(openImConversationDTO);
+
+                OpenImResponseDTO openImResponseDTO1 = editConversation(openImEditConversationDTO);
+                log.info("修改回话返回参数:{}",openImResponseDTO1);
+
+            }
+            return openImResponseDTO;
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+    /**
+     * 修改会话
+     * @return
+     */
+    public OpenImResponseDTO editConversation(OpenImEditConversationDTO dto) throws JsonProcessingException, InterruptedException {
+        String adminToken = getAdminToken();
+        ObjectMapper objectMapper = new ObjectMapper();
+        String requestBody = objectMapper.writeValueAsString(dto);
+
+        int retryCount = 0;
+        int maxRetries = 3;
+        OpenImResponseDTO responseDTO = null;
+
+        while (retryCount < maxRetries) {
+            try {
+                JSONObject jsonObject = new JSONObject(dto);
+                String body = HttpRequest.post("https://web.im.cdwjyyh.com/api/conversation/set_conversations")
+                        .header("operationID", String.valueOf(System.currentTimeMillis()))
+                        .header("token", adminToken)
+                        .body(jsonObject.toString())
+                        .execute()
+                        .body();
+                responseDTO = JSONUtil.toBean(body, OpenImResponseDTO.class);
+
+                if (responseDTO.getErrCode() == 0) {
+                    break; // 成功则退出重试
+                } else if (responseDTO.getErrCode() == 1501) {
+                    adminToken = getAdminToken(); // 刷新token
+                }
+            } catch (Exception e) {
+                log.error("修改会话失败,重试次数: {}", retryCount, e);
+            }
+            retryCount++;
+            if (retryCount < maxRetries) {
+                Thread.sleep(1000 * retryCount); // 指数退避
+            }
+        }
+        return responseDTO;
+    }
+    /**
+     * 检查两个用户是否存在好友关系
+     */
+    @Override
+    public OpenImResponseDTO isFriend(String userID1, String userID2) {
+        String adminToken = getAdminToken();
+        JSONObject jsonObject = new JSONObject();
+        jsonObject.put("userID1",userID1);
+        jsonObject.put("userID2",userID2);
+        String body = HttpRequest.post("https://web.im.cdwjyyh.com/api/friend/is_friend")
+                .header("operationID", String.valueOf(System.currentTimeMillis()))
+                .header("token", adminToken)
+                .body(jsonObject.toString())
+                .execute()
+                .body();
+        OpenImResponseDTO responseDTO= JSONUtil.toBean(body,OpenImResponseDTO.class);
+        return responseDTO;
+    }
+    /**
+     * 添加好友
+     */
+    @Override
+    public OpenImResponseDTO importFriend(String ownerUserID, List<String> friendUserIDs) {
+        //先检查用户是否存在好友关系
+        List<String> newFriendIds = new ArrayList<>();
+        for (String friendUserID : friendUserIDs) {
+            OpenImResponseDTO friend = isFriend(ownerUserID,friendUserID);
+            if (friend.getErrCode()==0){
+                Object data = friend.getData();
+                cn.hutool.json.JSONObject jsonObject = JSONUtil.parseObj(data);
+                Boolean inUser1Friends = (Boolean)jsonObject.get("inUser1Friends");
+                Boolean inUser2Friends = (Boolean)jsonObject.get("inUser2Friends");
+                //如果不存在好友关系
+                if (!inUser1Friends&&!inUser2Friends){
+                    newFriendIds.add(friendUserID);
+                    //friendUserIDs.remove(friendUserID);
+                }
+            }
+        }
+        if (newFriendIds.size()<=0){
+            return null;
+        }
+        String adminToken = getAdminToken();
+        JSONObject jsonObject = new JSONObject();
+        jsonObject.put("ownerUserID",ownerUserID);
+        jsonObject.put("friendUserIDs",newFriendIds);
+        String body = HttpRequest.post("https://web.im.cdwjyyh.com/api/friend/import_friend")
+                .header("operationID", String.valueOf(System.currentTimeMillis()))
+                .header("token", adminToken)
+                .body(jsonObject.toString())
+                .execute()
+                .body();
+        OpenImResponseDTO responseDTO= JSONUtil.toBean(body,OpenImResponseDTO.class);
+        return responseDTO;
+    }
+
+    /**
+     * 通用注册im
+     * @param userId
+     * @param type 1用户,2销售,3医生
+     * @return
+     */@Override
+    public R accountCheck(String userId,String type){
+        String adminToken = getAdminToken();
+        JSONObject requestBody = new JSONObject();
+        // 解析响应
+        if (StringUtil.isNotEmpty(adminToken)) {
+            //查询用户是否注册
+            /*switch (type){
+                case "1":
+                    userId = "U"+userId;
+                    break;
+                case "2":
+                    userId = userId;
+                    break;
+                case "3":
+                    userId = userId;
+                    break;
+            }*/
+            ArrayList<String> userIds = new ArrayList<>();
+            requestBody = new JSONObject();
+            userIds.add(userId);
+            requestBody.put("checkUserIDs", userIds);
+            String body = HttpRequest.post("https://web.im.cdwjyyh.com/api/user/account_check")
+                    .header("operationID", String.valueOf(System.currentTimeMillis()))
+                    .header("token", adminToken)
+                    .body(requestBody.toString())
+                    .execute()
+                    .body();
+            JSONObject jsonObject = new JSONObject(body);
+            JSONArray results = jsonObject.getJSONObject("data").getJSONArray("results");
+            if (results != null && results.length() > 0) {
+                JSONObject resultObj = results.getJSONObject(0);
+                int accountStatus = resultObj.getInt("accountStatus");
+                //未注册自动注册
+                if (accountStatus==0){
+                    HashMap<String, String> map = new HashMap<>();
+                    ArrayList<Object> users = new ArrayList<>();
+                    String s = "";
+                    switch (type){
+                        case "2":
+                            s = userId.replaceFirst("^C", "");
+                            CompanyUser companyUser = companyUserMapper.selectCompanyUserByCompanyUserId(Long.parseLong(s));
+                            if (null==companyUser){
+                                return R.error("用户不存在");
+                            }
+                            Company company = companyMapper.selectCompanyById(companyUser.getCompanyId());
+                            map.put("userID",userId);
+                            map.put("nickname",companyUser.getImNickName());
+                            map.put("faceURL",companyUser.getAvatar());
+                            break;
+                        case "1":
+                            s = userId.replaceFirst("^U", "");
+                            FsUser fsUser = fsUserMapper.selectFsUserByUserId(Long.parseLong(s));
+                            if (null==fsUser){
+                                return R.error("用户不存在");
+                            }
+                            map.put("userID",userId);
+                            map.put("nickname",StringUtils.isEmpty(fsUser.getNickName())?"微信用户":fsUser.getNickName());
+                            map.put("faceURL",fsUser.getAvatar());
+                            break;
+                        case "3":
+                            s = userId.replaceFirst("^D", "");
+                            FsDoctor fsDoctor = fsDoctorMapper.selectFsDoctorByDoctorId(Long.parseLong(s));
+                            if (null==fsDoctor){
+                                return R.error("用户不存在");
+                            }
+                            map.put("userID",userId);
+                            map.put("nickname",fsDoctor.getDoctorName());
+                            map.put("faceURL",fsDoctor.getAvatar());
+                            break;
+                    }
+
+                    users.add(map);
+                    requestBody = new JSONObject();
+                    userIds.add(userId);
+                    requestBody.put("users", users);
+                    HttpRequest.post("https://web.im.cdwjyyh.com/api/user/user_register")
+                            .header("operationID", String.valueOf(System.currentTimeMillis()))
+                            .header("token", adminToken).body(requestBody.toString()).execute().body();
+                }
+            } else {
+                return R.error("返回结果为空");
+            }
+           /* HashMap<String, String> tokenMap = new HashMap<>();
+            tokenMap.put("platformID","1");
+            tokenMap.put("userID",userId);*/
+            requestBody = new JSONObject();
+            requestBody.put("platformID",5);
+            requestBody.put("userID",userId);
+            String body1 = HttpRequest.post("https://web.im.cdwjyyh.com/api/auth/get_user_token")
+                    .header("operationID", String.valueOf(System.currentTimeMillis()))
+                    .header("token", adminToken)
+                    .body(requestBody.toString()).execute().body();
+            JSONObject userJson = new JSONObject(body1);
+            Integer errCode = (Integer)userJson.get("errCode");
+            if (errCode==0){
+                JSONObject userData = userJson.getJSONObject("data");
+                String userToken = userData.getString("token");
+                return R.ok().put("token", userToken);
+            }
+            return R.error("注册失败");
+        } else {
+            return R.error("获取管理员token失败");
+        }
+    }
+    @Async
+    @Override
+    public void checkAndImportFriend(Long companyUserId,String fsUserId) {
+        try {
+            // 注册账号
+            accountCheck("C" + companyUserId, "2");
+            accountCheck("U"+fsUserId, "1");
+
+            // 导入好友关系
+            ArrayList<String> userIds = new ArrayList<>();
+            userIds.add("U" + fsUserId);
+            importFriend("C" + companyUserId, userIds);
+        } catch (Exception e) {
+            log.error("异步执行IM注册/添加好友失败:", e);
+        }
+    }
+
+    @Async
+    @Override
+    public void checkAndImportFriendByDianBo(Long companyUserId,String fsUserId,String cropId) {
+        try {
+            // 注册账号
+            accountCheck("C" + companyUserId, "2");
+            accountCheck("U"+fsUserId, "1");
+
+            // 导入好友关系
+            ArrayList<String> userIds = new ArrayList<>();
+            userIds.add("U" + fsUserId);
+            importFriend("C" + companyUserId, userIds);
+            updateFriendByDianBo("C" + companyUserId, userIds,cropId);
+        } catch (Exception e) {
+            log.error("异步执行IM注册/添加好友失败:", e);
+        }
+    }
+
+    /**
+     * 修改好友信息
+     * @param ownerUserID
+     * @param friendUserIDs
+     * @return
+     */
+    public OpenImResponseDTO updateFriendByDianBo(String ownerUserID, List<String> friendUserIDs,String cropId) {
+        //先检查用户是否存在好友关系
+        //List<String> newFriendIds = new ArrayList<>();
+        for (String friendUserID : friendUserIDs) {
+            OpenImResponseDTO friend = isFriend(ownerUserID,friendUserID);
+            if (friend.getErrCode()==0){
+                Object data = friend.getData();
+                cn.hutool.json.JSONObject jsonObject = JSONUtil.parseObj(data);
+                Boolean inUser1Friends = (Boolean)jsonObject.get("inUser1Friends");
+                Boolean inUser2Friends = (Boolean)jsonObject.get("inUser2Friends");
+                //如果不存在好友关系,则不执行im修改好友信息
+                if (!inUser1Friends&&!inUser2Friends){
+                    return null;
+                    //friendUserIDs.remove(friendUserID);
+                }
+            }
+        }
+        List<String> remark = qwExternalContactMapper.selectRemarkByCompanyUserAndFsUser(friendUserIDs.get(0).replaceFirst("^U", ""), ownerUserID.replaceFirst("^C", ""),cropId);
+        if (CollectionUtils.isEmpty(remark)||remark.size()<=0){
+            return null;
+        }
+        String adminToken = getAdminToken();
+        JSONObject jsonObject = new JSONObject();
+        jsonObject.put("ownerUserID",ownerUserID);
+        jsonObject.put("friendUserIDs",friendUserIDs);
+        jsonObject.put("remark",remark.get(0));
+        String body = HttpRequest.post("https://web.im.cdwjyyh.com/api/friend/update_friends")
+                .header("operationID", String.valueOf(System.currentTimeMillis()))
+                .header("token", adminToken)
+                .body(jsonObject.toString())
+                .execute()
+                .body();
+        OpenImResponseDTO responseDTO= JSONUtil.toBean(body,OpenImResponseDTO.class);
+        return responseDTO;
+    }
+}

+ 28 - 0
fs-service/src/main/java/com/fs/im/vo/OpenImMsgCallBackVO.java

@@ -0,0 +1,28 @@
+package com.fs.im.vo;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class OpenImMsgCallBackVO {
+    private String sendID;
+    private String callbackCommand;
+    private String serverMsgID;
+    private String clientMsgID;
+    private String operationID;
+    private Integer senderPlatformID;
+    private String senderNickname;
+    private Integer sessionType;
+    private Integer msgFrom;
+    private Integer contentType;
+    private Integer status;
+    private Long sendTime;
+    private Long createTime;
+    private String content;
+    private Integer seq;
+    private List<String> atUserList;
+    private String faceURL;
+    private String ex;
+    private String recvID;
+}

+ 18 - 0
fs-service/src/main/java/com/fs/im/vo/OpenImResponseDTOTest.java

@@ -0,0 +1,18 @@
+package com.fs.im.vo;
+
+import com.fs.im.service.impl.OpenIMServiceImpl;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+    public class OpenImResponseDTOTest {
+        private Integer errCode;
+        private String errMsg;
+        private String errDlt;
+        private UserData data;
+        @Data
+        public static class UserData {
+            private List<OpenIMServiceImpl.UserInfo> usersInfo;
+        }
+    }

+ 12 - 0
fs-service/src/main/java/com/fs/qw/domain/QwExternalContact.java

@@ -1,5 +1,6 @@
 package com.fs.qw.domain;
 
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fs.common.annotation.Excel;
 import com.fs.common.core.domain.BaseEntity;
@@ -34,6 +35,8 @@ public class QwExternalContact extends BaseEntity
     @Excel(name = "名称")
     private String name;
 
+    private String remark;
+
     /** 头像 */
     @Excel(name = "头像")
     private String avatar;
@@ -91,6 +94,11 @@ public class QwExternalContact extends BaseEntity
     private Integer status;
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date createTime;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @TableField(exist = false)
+    private Date lastCreateTime;
+
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date transferTime;
     private Integer transferNum;
@@ -115,6 +123,9 @@ public class QwExternalContact extends BaseEntity
 
     private Integer level;
 
+    @TableField(exist = false)
+    private Integer isDaysNotStudy;
+
     private Integer levelType;
 
     @JsonFormat(pattern = "yyyy-MM-dd")
@@ -133,4 +144,5 @@ public class QwExternalContact extends BaseEntity
     private Integer userRepeat;
     // 是否已购0 否 1程序内下单 2程序外下单
     private Integer payOrder;
+
 }

+ 2 - 0
fs-service/src/main/java/com/fs/qw/domain/QwExternalContactInfo.java

@@ -145,4 +145,6 @@ public class QwExternalContactInfo extends BaseEntity
     private String productTalk;
     @Excel(name = "疾病交流")
     private String diseaseTalk;
+    @Excel(name = "渠道类型")
+    private String channelType;
 }

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

@@ -1,6 +1,7 @@
 package com.fs.qw.mapper;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.fastGpt.domain.FastgptChatArtificialWords;
 import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.param.*;
 import com.fs.qw.param.newparam.ExternalContactPageListParam;
@@ -412,4 +413,15 @@ public interface QwExternalContactMapper extends BaseMapper<QwExternalContact> {
 
     List<QwExternalContactComplaintVO> selectFsUserCourseComplaintRecordByUserId(Long userId);
 
+    @Select("select content from fastgpt_chat_artificial_words")
+    List<FastgptChatArtificialWords> selectChatGptChatArtificialWords();
+
+    @Select({"<script> " +
+            "SELECT qe.remark FROM qw_external_contact qe " +
+            "where qe.company_user_id = #{companyUserId} and qe.fs_user_id = #{userId}" +
+            "<if test = \"cropId!=null and cropId!=''\">  and qe.corp_id = #{cropId} </if>" +
+            "</script>"})
+    List<String> selectRemarkByCompanyUserAndFsUser(@Param("userId")String userId,@Param("companyUserId") String companyUserId,@Param("cropId") String cropId);
+
+    List<QwExternalContact> selectQwExternalContactByFsUserId(@Param("userId") Long userId);
 }

+ 5 - 0
fs-service/src/main/java/com/fs/qw/param/QwAutoSopTimeParam.java

@@ -18,4 +18,9 @@ public class QwAutoSopTimeParam {
     private String autoEndTime;
 
     private Integer autoSopSend;
+
+    /**
+     * 日期
+     */
+    private String autoDateTime;
 }

+ 13 - 0
fs-service/src/main/java/com/fs/qw/param/QwExternalContactUpdateNoteParam.java

@@ -0,0 +1,13 @@
+package com.fs.qw.param;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class QwExternalContactUpdateNoteParam {
+    private List<Long> userIds;
+    private String notes;
+    private int type;
+    private int nameType;
+}

+ 12 - 0
fs-service/src/main/java/com/fs/qw/param/RepeatParam.java

@@ -0,0 +1,12 @@
+package com.fs.qw.param;
+
+import lombok.Data;
+
+@Data
+public class RepeatParam {
+
+    private String externalUserId;
+    private String corpId;
+    private String userId;
+
+}

+ 2 - 0
fs-service/src/main/java/com/fs/qw/service/IQwExternalContactService.java

@@ -235,4 +235,6 @@ public interface IQwExternalContactService extends IService<QwExternalContact> {
      */
     int updateExternalContactTag(TagGroupUpdateParam param);
 
+    List<QwExternalContact> selectQwExternalContactByFsUserId(Long userId);
+
 }

+ 2 - 0
fs-service/src/main/java/com/fs/qw/service/IQwUserService.java

@@ -187,4 +187,6 @@ public interface IQwUserService
     List<UserVOs> getQwUserList(Long userId, String qwUserId);
 
     R updateSendType(UpdateSendTypeVo vo);
+
+    R getLoginQwIpadStatus(QwLoginHookParam loginParam);
 }

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

@@ -445,7 +445,7 @@ public class QwContactWayServiceImpl implements IQwContactWayService
                 try {
                     FsCourseLinkCreateParam param = new FsCourseLinkCreateParam();
                     param.setVideoId(Long.valueOf(att.getLink().getVideoId()));
-                    param.setQwUserId(qwUser.getId());
+                    param.setQwUserId(String.valueOf(qwUser.getId()));
                     param.setDays(att.getLink().getExpiresDays());
                     param.setCorpId(corpId);
                     param.setCourseId(Long.valueOf(att.getLink().getCourseId()));
@@ -485,7 +485,7 @@ public class QwContactWayServiceImpl implements IQwContactWayService
         watchLog.setVideoId(videoId != null ? videoId.longValue() : null);
         watchLog.setQwExternalContactId(externalId != null ? Long.valueOf(externalId) : null);
         watchLog.setSendType(2);
-        watchLog.setQwUserId(Long.valueOf(qwUserId));
+        watchLog.setQwUserId(String.valueOf(Long.valueOf(qwUserId)));
         watchLog.setSopId(null);
         watchLog.setCourseId(courseId != null ? courseId.longValue() : null);
         watchLog.setCompanyUserId(companyUserId != null ? Long.valueOf(companyUserId) : null);

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

@@ -3600,7 +3600,7 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
                 try {
                     FsCourseLinkCreateParam param = new FsCourseLinkCreateParam();
                     param.setVideoId(Long.valueOf(att.getLink().getVideoId()));
-                    param.setQwUserId(qwUser.getId());
+                    param.setQwUserId(String.valueOf(qwUser.getId()));
                     param.setDays(att.getLink().getExpiresDays());
                     param.setCorpId(corpId);
                     param.setCourseId(Long.valueOf(att.getLink().getCourseId()));
@@ -3646,7 +3646,7 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
 
                                 FsCourseLinkCreateParam param = new FsCourseLinkCreateParam();
                                 param.setVideoId(Long.valueOf(att.getMiniprogram().getVideoId()));
-                                param.setQwUserId(qwUser.getId());
+                                param.setQwUserId(String.valueOf(qwUser.getId()));
                                 param.setDays(att.getMiniprogram().getExpiresDays());
                                 param.setCorpId(corpId);
                                 param.setCourseId(Long.valueOf(att.getMiniprogram().getCourseId()));
@@ -3700,7 +3700,7 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
         watchLog.setVideoId(videoId != null ? videoId.longValue() : null);
         watchLog.setQwExternalContactId(externalId != null ? Long.valueOf(externalId) : null);
         watchLog.setSendType(2);
-        watchLog.setQwUserId(Long.valueOf(qwUserId));
+        watchLog.setQwUserId(qwUserId);
         watchLog.setSopId(null);
         watchLog.setCourseId(courseId != null ? courseId.longValue() : null);
         watchLog.setCompanyUserId(companyUserId != null ? Long.valueOf(companyUserId) : null);
@@ -5602,4 +5602,9 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
             logger.info("企微凌晨同步完成"+corpId);
         }
     }
+
+    @Override
+    public List<QwExternalContact> selectQwExternalContactByFsUserId(Long userId) {
+        return qwExternalContactMapper.selectQwExternalContactByFsUserId(userId);
+    }
 }

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

@@ -716,7 +716,7 @@ public class QwGroupMsgServiceImpl implements IQwGroupMsgService
                                        QwUser qwUser, Long externalId, Integer isRoom){
         FsCourseLink link = new FsCourseLink();
         link.setCompanyId(qwUser.getCompanyId());
-        link.setQwUserId(qwUser.getId());
+        link.setQwUserId(String.valueOf(qwUser.getId()));
         link.setCompanyUserId(qwUser.getCompanyUserId());
         link.setVideoId(videoId);
         link.setCorpId(qwUser.getCorpId());

+ 62 - 11
fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceImpl.java

@@ -20,6 +20,7 @@ import com.fs.config.ai.AiHostProper;
 import com.fs.config.cloud.CloudHostProper;
 import com.fs.his.config.FsSysConfig;
 import com.fs.his.utils.ConfigUtil;
+import com.fs.ipad.IpadSendUtils;
 import com.fs.qw.domain.*;
 import com.fs.qw.dto.QwUserByToolDTO;
 import com.fs.qw.dto.QwUserKeyDTO;
@@ -107,6 +108,9 @@ public class QwUserServiceImpl implements IQwUserService
     @Autowired
     IQwExternalContactService externalContactService;
 
+    @Autowired
+    IpadSendUtils ipadSendUtils;
+
     @Override
     public R getQwIpad(QwLoginHookParam loginParam) {
         QwUser qwUser = qwUserMapper.selectQwUserById(loginParam.getQwUserId());
@@ -1148,16 +1152,16 @@ public class QwUserServiceImpl implements IQwUserService
             return R.error("请先获取ai主机");
         }
 
-        if (qwUser.getUid()!=null){
-            WxWorkGetQrCodeDTO wxWorkGetQrCodeDTO = new WxWorkGetQrCodeDTO();
-            wxWorkGetQrCodeDTO.setUuid(qwUser.getUid());
-            WxWorkResponseDTO wxWorkResponseDTO = wxWorkService.automaticLogin(wxWorkGetQrCodeDTO,qwUser.getServerId());
-            if (wxWorkResponseDTO.getErrcode()==0){
-                updateIpadStatus(qwUser.getId(),1);
-                redisCache.setCacheObject("qrCode:uuid:"+qwUser.getUid(),loginParam.getQwUserId());
-                return R.ok("登录成功");
-            }
-        }
+//        if (qwUser.getUid()!=null){
+//            WxWorkGetQrCodeDTO wxWorkGetQrCodeDTO = new WxWorkGetQrCodeDTO();
+//            wxWorkGetQrCodeDTO.setUuid(qwUser.getUid());
+//            WxWorkResponseDTO wxWorkResponseDTO = wxWorkService.automaticLogin(wxWorkGetQrCodeDTO,qwUser.getServerId());
+//            if (wxWorkResponseDTO.getErrcode()==0){
+//                updateIpadStatus(qwUser.getId(),1);
+//                redisCache.setCacheObject("qrCode:uuid:"+qwUser.getUid(),loginParam.getQwUserId());
+//                return R.ok("登录成功");
+//            }
+//        }
         //自动登录失败
         WxWorkInitDTO wxWorkInitDTO = new WxWorkInitDTO();
         if (qwUser.getVid()!=null){
@@ -1181,7 +1185,7 @@ public class QwUserServiceImpl implements IQwUserService
             return R.ok("登录成功");
         }
         WxWorkSetCallbackUrlDTO wxWorkSetCallbackUrlDTO = new WxWorkSetCallbackUrlDTO();
-//        System.out.println("回调地址"+aiHostProper.getIpadUrl()+"/msg/callback/"+serverId);
+        System.out.println("回调地址"+aiHostProper.getIpadUrl()+"/msg/callback/"+serverId);
         wxWorkSetCallbackUrlDTO.setUrl(aiHostProper.getIpadUrl()+"/msg/callback/"+serverId);
         wxWorkSetCallbackUrlDTO.setUuid(data.getUuid());
         wxWorkService.SetCallbackUrl(wxWorkSetCallbackUrlDTO,serverId);
@@ -1398,6 +1402,53 @@ public class QwUserServiceImpl implements IQwUserService
         return R.ok();
     }
 
+    @Override
+    public R getLoginQwIpadStatus(QwLoginHookParam loginParam) {
+        QwUser qwUser = qwUserMapper.selectQwUserById(loginParam.getQwUserId());
+        if (qwUser.getUid()==null){
+            return R.error("请先绑定主机");
+        }
+        if (qwUser.getServerId()==null){
+            return R.error("请先获取ai主机");
+        }
+        // 更新信息使用的类
+        QwUser updateQwUser = new QwUser();
+        try {
+            // 调用ipad接口判断是否真实登录
+            WxLoginResp login = ipadSendUtils.isLogin(qwUser.getUid(), qwUser.getServerId());
+            if (login.getLoginType() == 0) {
+                updateQwUser.setId(qwUser.getId());
+                updateQwUser.setRemark("AI未初始化");
+                updateQwUser.setIpadStatus(0);
+                qwUserMapper.updateById(updateQwUser);
+            }
+            if (login.getLoginType() == 1) {
+                updateQwUser.setId(qwUser.getId());
+                updateQwUser.setRemark("未登录");
+                updateQwUser.setIpadStatus(0);
+                qwUserMapper.updateById(updateQwUser);
+            }
+            if (login.getUser_info().isLogin()) {
+                updateQwUser.setId(qwUser.getId());
+                updateQwUser.setRemark("已登录");
+                updateQwUser.setIpadStatus(1);
+                qwUserMapper.updateById(updateQwUser);
+            }
+            if (!login.getUser_info().isLogin()) {
+                updateQwUser.setId(qwUser.getId());
+                updateQwUser.setRemark("登录状态异常");
+                updateQwUser.setIpadStatus(0);
+                qwUserMapper.updateById(updateQwUser);
+            }
+        } catch (Exception e) {
+            updateQwUser.setId(qwUser.getId());
+            updateQwUser.setRemark("登录状态异常");
+            updateQwUser.setIpadStatus(0);
+            qwUserMapper.updateById(updateQwUser);
+        }
+        return null;
+    }
+
 
     /**
      * 构建查询条件

+ 6 - 2
fs-service/src/main/java/com/fs/qw/service/impl/QwUserVoiceLogServiceImpl.java

@@ -17,6 +17,8 @@ import com.fs.wxwork.dto.WxWorkResponseDTO;
 import com.fs.wxwork.dto.WxWorkVid2UserIdDTO;
 import com.fs.wxwork.dto.WxWorkVid2UserIdRespDTO;
 import com.fs.wxwork.service.WxWorkService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
@@ -34,6 +36,7 @@ import java.util.stream.Collectors;
  */
 @Service
 public class QwUserVoiceLogServiceImpl extends ServiceImpl<QwUserVoiceLogMapper, QwUserVoiceLog> implements IQwUserVoiceLogService {
+    private static final Logger log = LoggerFactory.getLogger(QwUserVoiceLogServiceImpl.class);
     @Autowired
     private QwUserMapper qwUserMapper;
 
@@ -158,6 +161,7 @@ public class QwUserVoiceLogServiceImpl extends ServiceImpl<QwUserVoiceLogMapper,
             String extId = getExtId(extUserId, uuid, sendUser.getServerId());
             QwExternalContact qwExternalContacts = qwExternalContactMapper.selectQwExternalContactByExternalUserIdAndQwUserId(extId, sendUser.getCorpId(),sendUser.getQwUserId());
             if (qwExternalContacts==null){
+                log.error("未找到外部联系人,qwUserId:" + id);
                 return ;
             }
 
@@ -211,8 +215,8 @@ public class QwUserVoiceLogServiceImpl extends ServiceImpl<QwUserVoiceLogMapper,
         wxWorkVid2UserIdDTO.setUuid(uid);
         WxWorkResponseDTO<List<WxWorkVid2UserIdRespDTO>> WxWorkVid2UserIdRespDTO = wxWorkService.Vid2UserId(wxWorkVid2UserIdDTO,serverId);
         List<WxWorkVid2UserIdRespDTO> data = WxWorkVid2UserIdRespDTO.getData();
-        if (data==null&& data.isEmpty()){
-            System.out.println("未获取到extId");
+        if (data==null || data.isEmpty()){
+            System.out.println("未获取到extId,uid:" + uid);
             return "";
         }
         com.fs.wxwork.dto.WxWorkVid2UserIdRespDTO dto = data.get(0);

+ 14 - 1
fs-service/src/main/java/com/fs/qw/vo/QwUserVO.java

@@ -1,9 +1,15 @@
 package com.fs.qw.vo;
 
 import com.fs.common.annotation.Excel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
 import lombok.Data;
+import lombok.NoArgsConstructor;
 
 @Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
 public class QwUserVO {
     private Long id;
 
@@ -40,6 +46,7 @@ public class QwUserVO {
     private String nickName;
     private String userName;
     private String welcomeText;
+
     private Integer isSendMsg;
     /**
     * 活码id
@@ -74,10 +81,16 @@ public class QwUserVO {
     private Integer ipadStatus;
     private Long serverId;
     private Integer serverStatus;
-    private Integer sendMsgType;
 
+    /**
+     * 云主机地址(不包含拼接内容)
+     */
+    private String loginCodeUrlOriginal;
+    private Integer sendMsgType;
+    private String companyName;
     /**
      * 是否自动发课 00、禁用,01、启用
      */
     private String isAuto;
+
 }

+ 6 - 0
fs-service/src/main/java/com/fs/sop/mapper/QwSopLogsMapper.java

@@ -323,4 +323,10 @@ public interface QwSopLogsMapper extends BaseMapper<QwSopLogs> {
 
     @DataSource(DataSourceType.SopREAD)
     List<QwSopLogs> selectByQwUserId(@Param("id") Long id);
+
+    @Select("select * from qw_sop_logs where send_type=8 and send_status=3 and  create_time <= DATE_SUB(NOW(), INTERVAL 2 HOUR) ")
+    List<QwSopLogs> selectExpireAiMsg();
+
+    @DataSource(DataSourceType.SOP)
+    void batchUpdateQwSopLogsById(@Param("data") List<QwSopLogs> logs);
 }

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

@@ -549,7 +549,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                                     createParam.setCompanyUserId(Long.parseLong(companyUserId));
                                     createParam.setCompanyId(Long.parseLong(companyId));
                                     createParam.setChatId(groupUser.getChatId());
-                                    createParam.setQwUserId(qwUser.getId());
+                                    createParam.setQwUserId(String.valueOf(qwUser.getId()));
                                     createParam.setDays(st.getExpiresDays());
                                     R createLink = courseLinkService.createRoomLinkUrl(createParam);
                                     if (createLink.get("code").equals(500)) {
@@ -646,7 +646,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                                     createParam.setCompanyUserId(qwUser.getCompanyUserId());
                                     createParam.setCompanyId(qwUser.getCompanyId());
                                     createParam.setChatId(groupChat.getChatId());
-                                    createParam.setQwUserId(qwUser.getId());
+                                    createParam.setQwUserId(String.valueOf(qwUser.getId()));
                                     createParam.setDays(st.getExpiresDays());
                                     R createLink = courseLinkService.createRoomLinkUrl(createParam);
                                     if (createLink.get("code").equals(500)) {
@@ -1230,7 +1230,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
             watchLog.setVideoId(videoId != null ? videoId.longValue() : null);
             watchLog.setQwExternalContactId(externalId);
             watchLog.setSendType(2);
-            watchLog.setQwUserId(Long.parseLong(qwUserId));
+            watchLog.setQwUserId(qwUserId);
             watchLog.setSopId(sopId);
             watchLog.setDuration(0L);
             watchLog.setCourseId(courseId != null ? courseId.longValue() : null);
@@ -1369,7 +1369,7 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
         // 手动创建 FsCourseLink 对象,避免使用 BeanUtils.copyProperties
         FsCourseLink link = new FsCourseLink();
         link.setCompanyId(Long.parseLong(companyId));
-        link.setQwUserId(Long.parseLong(qwUserId));
+        link.setQwUserId(qwUserId);
         link.setCompanyUserId(Long.parseLong(companyUserId));
         link.setVideoId(videoId.longValue());
         link.setCorpId(corpId);

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

@@ -550,7 +550,7 @@ public class SopUserLogsServiceImpl  implements ISopUserLogsService {
                         if (item.getIsBindUrl() != null && item.getIsBindUrl().equals("1")) {
                             FsCourseLinkCreateParam param = new FsCourseLinkCreateParam();
                             param.setVideoId((long) videoId);
-                            param.setQwUserId(Long.valueOf(qwUserId));
+                            param.setQwUserId(qwUserId);
 //                            param.setDays(item.getExpiresDays());
                             param.setCorpId(log.getCorpId());
                             param.setCourseId(Long.valueOf(courseId));
@@ -578,7 +578,7 @@ public class SopUserLogsServiceImpl  implements ISopUserLogsService {
                                 watchLog.setVideoId(Long.valueOf(videoId));
                                 watchLog.setQwExternalContactId(contactId.getExternalId());
                                 watchLog.setSendType(2);
-                                watchLog.setQwUserId(Long.parseLong(qwUserId));
+                                watchLog.setQwUserId(qwUserId);
                                 watchLog.setCourseId(Long.valueOf(courseId));
                                 watchLog.setCompanyUserId(Long.valueOf(companyUserId));
                                 watchLog.setCompanyId(Long.valueOf(companyId));

+ 3 - 0
fs-service/src/main/java/com/fs/watch/mapper/WatchCompanyUserMapper.java

@@ -65,4 +65,7 @@ public interface WatchCompanyUserMapper extends BaseMapper<WatchCompanyUser>{
     void insertBatchWatchUserCompany(@Param("list") ArrayList<WatchCompanyUser> cuList);
 
     void deleteBatch(@Param("deviceId")Long deviceId, @Param("list")List<String> subCompanyUserList);
+
+    int updateByCompanyUserId(@Param("newCompanyUserId")Long newCompanyUserId,@Param("oldCompanyUserId")Long oldCompanyUserId);
+
 }

+ 1 - 1
fs-service/src/main/java/com/fs/wxwork/service/WxWorkServiceImpl.java

@@ -36,7 +36,7 @@ public class WxWorkServiceImpl implements WxWorkService {
         System.out.println("serverId:" + serverId);
         QwIpadServer qwIpadServer = qwIpadServerService.selectQwIpadServerById(serverId);
         if (qwIpadServer == null||qwIpadServer.getUrl()==null) {
-            throw new CustomException("获取到服务地址与端口");
+            throw new CustomException("获取到服务地址与端口");
         }
         redisCache.setCacheObject("serverId:" + serverId,qwIpadServer.getUrl(),2, TimeUnit.HOURS);
         return qwIpadServer.getUrl();

+ 2 - 1
fs-service/src/main/resources/application-config-druid-hzyy.yml

@@ -87,7 +87,8 @@ cloud_host:
 headerImg:
   imgUrl: https://hzyy.obs.cn-north-4.myhuaweicloud.com/fs/20250616/1750067609692.png
 ipad:
-  ipadUrl: http://ipad.cdwjyyh.com
+  ipadUrl: http://139.159.133.223:8667
+  aiApi: http://154.8.194.176:3000/api
 wx_miniapp_temp:
   pay_order_temp_id:
   inquiry_temp_id:

+ 1 - 1
fs-service/src/main/resources/application-config-druid-jzzx.yml

@@ -87,7 +87,7 @@ headerImg:
   imgUrl: https://jiuzhouzaixian.obs.cn-southwest-2.myhuaweicloud.com/fs/20250623/1750665141214.png
 ipad:
   ipadUrl: http://ipad.jiuzhouzaixian.com
-  aiApi:
+  aiApi: http://154.8.194.176:3000/api
 wx_miniapp_temp:
   pay_order_temp_id: VXEvKaGNPFuJmhWK9O_QPrTZxe9umDCukq-maI8Vdek
   inquiry_temp_id: 9POPYeqhI48LOPvq-Rfoklze7H-9SlunJKh10Qt4_2I

+ 168 - 0
fs-service/src/main/resources/application-druid-hzyy-test.yml

@@ -0,0 +1,168 @@
+# 数据源配置
+spring:
+    profiles:
+        include: config-druid-hzyy,common
+    # redis 配置
+    redis:
+        # 地址
+        host: 127.0.0.1
+        # 端口,默认为6379
+        port: 6379
+        # 数据库索引
+        database: 0
+        # 密码
+        password:
+        # 连接超时时间
+        timeout: 20s
+        lettuce:
+            pool:
+                # 连接池中的最小空闲连接
+                min-idle: 0
+                # 连接池中的最大空闲连接
+                max-idle: 8
+                # 连接池的最大数据库连接数
+                max-active: 8
+                # #连接池最大阻塞等待时间(使用负值表示没有限制)
+                max-wait: -1ms
+    datasource:
+        #        clickhouse:
+        #            type: com.alibaba.druid.pool.DruidDataSource
+        #            driverClassName: com.clickhouse.jdbc.ClickHouseDriver
+        #            url: jdbc:clickhouse://cc-2vc8zzo26w0l7m2l6.public.clickhouse.ads.aliyuncs.com/sop?compress=0&use_server_time_zone=true&use_client_time_zone=false&timezone=Asia/Shanghai
+        #            username: rt_2024
+        #            password: Yzx_19860213
+        #            initialSize: 10
+        #            maxActive: 100
+        #            minIdle: 10
+        #            maxWait: 6000
+        mysql:
+            type: com.alibaba.druid.pool.DruidDataSource
+            driverClassName: com.mysql.cj.jdbc.Driver
+            druid:
+                # 主库数据源
+                master:
+                    url: jdbc:mysql://139.159.133.223:2345/fs_his?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    username: root
+                    password: Ylrztek250218!3@.
+                # 从库数据源
+                slave:
+                    # 从数据源开关/默认关闭
+                    enabled: false
+                    url:
+                    username:
+                    password:
+                # 初始连接数
+                initialSize: 5
+                # 最小连接池数量
+                minIdle: 10
+                # 最大连接池数量
+                maxActive: 20
+                # 配置获取连接等待超时的时间
+                maxWait: 60000
+                # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+                timeBetweenEvictionRunsMillis: 60000
+                # 配置一个连接在池中最小生存的时间,单位是毫秒
+                minEvictableIdleTimeMillis: 300000
+                # 配置一个连接在池中最大生存的时间,单位是毫秒
+                maxEvictableIdleTimeMillis: 900000
+                # 配置检测连接是否有效
+                validationQuery: SELECT 1 FROM DUAL
+                testWhileIdle: true
+                testOnBorrow: false
+                testOnReturn: false
+                webStatFilter:
+                    enabled: true
+                statViewServlet:
+                    enabled: true
+                    # 设置白名单,不填则允许所有访问
+                    allow:
+                    url-pattern: /druid/*
+                    # 控制台管理用户名和密码
+                    login-username: fs
+                    login-password: 123456
+                filter:
+                    stat:
+                        enabled: true
+                        # 慢SQL记录
+                        log-slow-sql: true
+                        slow-sql-millis: 1000
+                        merge-sql: true
+                    wall:
+                        config:
+                            multi-statement-allow: true
+        sop:
+            type: com.alibaba.druid.pool.DruidDataSource
+            driverClassName: com.mysql.cj.jdbc.Driver
+            druid:
+                # 主库数据源
+                master:
+                    url: jdbc:mysql://139.159.133.223:2345/sop?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+                    username: root
+                    password: Ylrztek250218!3@.
+                # 初始连接数
+                initialSize: 5
+                # 最小连接池数量
+                minIdle: 10
+                # 最大连接池数量
+                maxActive: 20
+                # 配置获取连接等待超时的时间
+                maxWait: 60000
+                # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+                timeBetweenEvictionRunsMillis: 60000
+                # 配置一个连接在池中最小生存的时间,单位是毫秒
+                minEvictableIdleTimeMillis: 300000
+                # 配置一个连接在池中最大生存的时间,单位是毫秒
+                maxEvictableIdleTimeMillis: 900000
+                # 配置检测连接是否有效
+                validationQuery: SELECT 1 FROM DUAL
+                testWhileIdle: true
+                testOnBorrow: false
+                testOnReturn: false
+                webStatFilter:
+                    enabled: true
+                statViewServlet:
+                    enabled: true
+                    # 设置白名单,不填则允许所有访问
+                    allow:
+                    url-pattern: /druid/*
+                    # 控制台管理用户名和密码
+                    login-username: fs
+                    login-password: 123456
+                filter:
+                    stat:
+                        enabled: true
+                        # 慢SQL记录
+                        log-slow-sql: true
+                        slow-sql-millis: 1000
+                        merge-sql: true
+                    wall:
+                        config:
+                            multi-statement-allow: true
+rocketmq:
+    name-server: rmq-1243b25nj.rocketmq.gz.public.tencenttdmq.com:8080 # RocketMQ NameServer 地址
+    producer:
+        group: my-producer-group
+        access-key: ak1243b25nj17d4b2dc1a03 # 替换为实际的 accessKey
+        secret-key: sk08a7ea1f9f4b0237 # 替换为实际的 secretKey
+    consumer:
+        group: test-group
+        access-key: ak1243b25nj17d4b2dc1a03 # 替换为实际的 accessKey
+        secret-key: sk08a7ea1f9f4b0237 # 替换为实际的 secretKey
+custom:
+    token: "1o62d3YxvdHd4LEUiltnu7sK"
+    encoding-aes-key: "UJfTQ5qKTKlegjkXtp1YuzJzxeHlUKvq5GyFbERN1iU"
+    corp-id: "ww51717e2b71d5e2d3"
+    secret: "6ODAmw-8W4t6h9mdzHh2Z4Apwj8mnsyRnjEDZOHdA7k"
+    private-key-path: "privatekey.pem"
+    webhook-url: "https://your-server.com/wecom/archive"
+# token配置
+token:
+    # 令牌自定义标识
+    header: Authorization
+    # 令牌密钥
+    secret: abcdefghijklmnopqrstuvwxyz
+    # 令牌有效期(默认30分钟)
+    expireTime: 180
+openIM:
+    secret: openIM123
+    userID: imAdmin

+ 18 - 0
fs-service/src/main/resources/application-druid-hzyy.yml

@@ -148,3 +148,21 @@ rocketmq:
         group: test-group
         access-key: ak1243b25nj17d4b2dc1a03 # 替换为实际的 accessKey
         secret-key: sk08a7ea1f9f4b0237 # 替换为实际的 secretKey
+custom:
+    token: "1o62d3YxvdHd4LEUiltnu7sK"
+    encoding-aes-key: "UJfTQ5qKTKlegjkXtp1YuzJzxeHlUKvq5GyFbERN1iU"
+    corp-id: "ww51717e2b71d5e2d3"
+    secret: "6ODAmw-8W4t6h9mdzHh2Z4Apwj8mnsyRnjEDZOHdA7k"
+    private-key-path: "privatekey.pem"
+    webhook-url: "https://your-server.com/wecom/archive"
+# token配置
+token:
+    # 令牌自定义标识
+    header: Authorization
+    # 令牌密钥
+    secret: abcdefghijklmnopqrstuvwxyz
+    # 令牌有效期(默认30分钟)
+    expireTime: 180
+openIM:
+    secret: openIM123
+    userID: imAdmin

+ 18 - 0
fs-service/src/main/resources/application-druid-jzzx.yml

@@ -148,3 +148,21 @@ rocketmq:
         group: test-group
         access-key: ak1243b25nj17d4b2dc1a03 # 替换为实际的 accessKey
         secret-key: sk08a7ea1f9f4b0237 # 替换为实际的 secretKey
+custom:
+    token: "1o62d3YxvdHd4LEUiltnu7sK"
+    encoding-aes-key: "UJfTQ5qKTKlegjkXtp1YuzJzxeHlUKvq5GyFbERN1iU"
+    corp-id: "ww51717e2b71d5e2d3"
+    secret: "6ODAmw-8W4t6h9mdzHh2Z4Apwj8mnsyRnjEDZOHdA7k"
+    private-key-path: "privatekey.pem"
+    webhook-url: "https://your-server.com/wecom/archive"
+# token配置
+token:
+    # 令牌自定义标识
+    header: Authorization
+    # 令牌密钥
+    secret: abcdefghijklmnopqrstuvwxyz
+    # 令牌有效期(默认30分钟)
+    expireTime: 180
+openIM:
+    secret: openIM123
+    userID: imAdmin

+ 103 - 0
fs-service/src/main/resources/mapper/company/CompanyCompanyFsuserMapper.xml

@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.company.mapper.CompanyCompanyFsuserMapper">
+
+    <resultMap type="CompanyCompanyFsuser" id="CompanyCompanyFsuserResult">
+        <result property="id"    column="id"    />
+        <result property="companyId"    column="company_id"    />
+        <result property="companyUserId"    column="company_user_id"    />
+        <result property="userId"    column="user_id"    />
+        <result property="qwContactId"    column="qw_contact_id"    />
+        <result property="bindType"    column="bind_type"    />
+        <result property="status"    column="status"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="updateTime"    column="update_time"    />
+    </resultMap>
+
+    <sql id="selectCompanyCompanyFsuserVo">
+        select id, company_id, company_user_id, user_id,qw_contact_id,bind_type, status, create_time, update_time from company_company_fsuser
+    </sql>
+
+    <select id="selectCompanyCompanyUserList" parameterType="CompanyCompanyFsuser" resultMap="CompanyCompanyFsuserResult">
+        <include refid="selectCompanyCompanyFsuserVo"/>
+        <where>
+            <if test="companyId != null "> and company_id = #{companyId}</if>
+            <if test="companyUserId != null "> and company_user_id = #{companyUserId}</if>
+            <if test="userId != null "> and user_id = #{userId}</if>
+            <if test="qwContactId != null "> and qw_contact_id = #{qwContactId}</if>
+            <if test="bindType != null "> and bind_type = #{bindType}</if>
+            <if test="status != null "> and status = #{status}</if>
+        </where>
+    </select>
+
+    <select id="selectCompanyCompanyUserById" parameterType="Long" resultMap="CompanyCompanyFsuserResult">
+        <include refid="selectCompanyCompanyFsuserVo"/>
+        where id = #{id}
+    </select>
+    <select id="getInfoByUserId" resultType="com.fs.company.domain.CompanyCompanyFsuser">
+        <include refid="selectCompanyCompanyFsuserVo"/>
+        where status = 1
+        and user_id = #{userId}
+    </select>
+    <select id="selectNoHistoryApp" resultType="com.fs.company.domain.CompanyCompanyFsuser">
+        select ccf.*
+        from company_company_fsuser ccf
+                 left join fs_user f on  ccf.user_id = f.user_id
+        where ccf.status = 1 AND ccf.bind_type = 1
+        AND DATE_ADD(ccf.create_time, INTERVAL #{limit} DAY) &lt; NOW()  -- 筛选创建时间加上5天后小于等于当前时间的数据
+        AND f.history_app is null
+        AND ccf.user_id is not null
+    </select>
+
+    <insert id="insertCompanyCompanyUser" parameterType="CompanyCompanyFsuser" useGeneratedKeys="true" keyProperty="id">
+        insert into company_company_fsuser
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="companyId != null">company_id,</if>
+            <if test="companyUserId != null">company_user_id,</if>
+            <if test="userId != null">user_id,</if>
+            <if test="qwContactId != null">qw_contact_id,</if>
+            <if test="bindType != null">bind_type,</if>
+            <if test="status != null">status,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateTime != null">update_time,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="companyId != null">#{companyId},</if>
+            <if test="companyUserId != null">#{companyUserId},</if>
+            <if test="userId != null">#{userId},</if>
+            <if test="qwContactId != null">#{qwContactId},</if>
+            <if test="bindType != null">#{bindType},</if>
+            <if test="status != null">#{status},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+         </trim>
+    </insert>
+
+    <update id="updateCompanyCompanyUser" parameterType="CompanyCompanyFsuser">
+        update company_company_fsuser
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="companyId != null">company_id = #{companyId},</if>
+            <if test="companyUserId != null">company_user_id = #{companyUserId},</if>
+            <if test="userId != null">user_id = #{userId},</if>
+            <if test="qwContactId != null">qw_contact_id = #{qwContactId},</if>
+            <if test="bindType != null">bind_type = #{bindType},</if>
+            <if test="status != null">status = #{status},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteCompanyCompanyUserById" parameterType="Long">
+        delete from company_company_fsuser where id = #{id}
+    </delete>
+
+    <delete id="deleteCompanyCompanyUserByIds" parameterType="String">
+        delete from company_company_fsuser where id in
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>

+ 3 - 0
fs-service/src/main/resources/mapper/company/CompanyUserMapper.xml

@@ -529,4 +529,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             #{item.userId}
         </foreach>
     </update>
+    <select id="selectCompanyUserByCompanyUserId" resultMap="CompanyUserResult">
+        select  * from company_user where user_id = #{companyUserId}
+    </select>
 </mapper>

+ 139 - 4
fs-service/src/main/resources/mapper/fastGpt/FastGptChatMsgMapper.xml

@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8" ?>
 <!DOCTYPE mapper
-PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
-"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="com.fs.fastGpt.mapper.FastGptChatMsgMapper">
 
     <resultMap type="FastGptChatMsg" id="FastGptChatMsgResult">
@@ -54,6 +54,89 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <include refid="selectFastGptChatMsgVo"/>
         where msg_id = #{msgId}
     </select>
+    <select id="selectFastGptChatMsgUserList" resultType="com.fs.fastGpt.domain.FastGptChatMsg">
+        SELECT session_id AS sessionId, content, msg_type AS msgType, send_type AS sendType
+        FROM (
+        SELECT
+        session_id,
+        content,
+        msg_type,
+        send_type,
+        @row_number := IF(@prev_session_id = session_id AND @prev_send_type = send_type, @row_number + 1, 1) AS rn,
+        @prev_session_id := session_id,
+        @prev_send_type := send_type
+        FROM fastgpt_chat_msg
+        CROSS JOIN (SELECT @row_number := 0, @prev_session_id := NULL, @prev_send_type := NULL) AS vars
+        WHERE msg_type = 1
+        <if test="list != null and !list.isEmpty()">
+            AND session_id IN
+            <foreach collection="list" item="item" open="(" separator="," close=")">
+                #{item}
+            </foreach>
+        </if>
+        ORDER BY session_id, send_type, create_time DESC
+        ) AS tmp
+        WHERE rn = 1;
+
+
+        <!--SELECT session_id sessionId,content,msg_type msgType,send_type sendType FROM (
+                      SELECT *, ROW_NUMBER() OVER (
+                            PARTITION BY session_id, send_type
+                            ORDER BY create_time DESC
+                        ) AS rn
+                      FROM fastgpt_chat_msg
+                      WHERE msg_type = 1
+                        <if test="list != null and !list.isEmpty()">
+                            AND session_id IN &lt;!&ndash; 关键修改点 &ndash;&gt;
+                            <foreach collection="list" item="item" open="(" separator="," close=")">
+                                #{item}
+                            </foreach>
+                        </if>
+                          ) tmp
+                WHERE rn = 1;-->
+    </select>
+    <select id="selectFastGptChatMsgByMsgSessionId" resultType="com.fs.fastGpt.domain.FastGptChatMsg">
+        select * from (SELECT * FROM fastgpt_chat_msg
+                       WHERE session_id = #{sessionId}
+                         AND msg_type = 1
+                         AND send_type IN (1, 2)
+                       ORDER BY send_type, create_time DESC
+                           LIMIT 5 BY send_type)
+        ORDER BY create_time DESC
+    </select>
+
+    <select id="selectFastGptChatMsgByMsgSessionIdAndExtId" resultMap="FastGptChatMsgResult">
+        <![CDATA[SELECT * FROM (
+                       SELECT
+                           *,
+                           ROW_NUMBER() OVER (PARTITION BY send_type ORDER BY create_time DESC) AS row_num
+                       FROM fastgpt_chat_msg
+                       WHERE session_id = #{sessionId}
+                         AND ext_id = #{extId}
+                         AND msg_type = 1
+                         AND send_type IN (1, 2)
+                   ) AS temp
+                 WHERE row_num <= 5
+                 ORDER BY create_time DESC]]>
+
+        <!--select * from (SELECT * FROM fastgpt_chat_msg
+                       WHERE session_id = #{sessionId}
+                         AND ext_id = #{extId}
+                         AND msg_type = 1
+                         AND send_type IN (1, 2)
+                       ORDER BY send_type, create_time DESC
+                           LIMIT 5 BY send_type)
+        ORDER BY create_time DESC-->
+    </select>
+
+    <select id="selectFastGptChatMsgCVOBySessionId" resultType="com.fs.fastGpt.vo.FastGptChatMsgCVO">
+        select m.*  from fastgpt_chat_msg m
+        where m.session_id=#{sessionId}
+        <if test="userId != null">
+            and m.user_id = #{userId}
+        </if>
+        and msg_type=2 order by m.msg_id desc
+    </select>
 
     <insert id="insertFastGptChatMsg" parameterType="FastGptChatMsg" useGeneratedKeys="false">
         insert into fastgpt_chat_msg
@@ -77,7 +160,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="promptTokens != null">prompt_tokens,</if>
             <if test="completionTokens != null">completion_tokens,</if>
             <if test="totalTokens != null">total_tokens,</if>
-         </trim>
+        </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="msgId != null">#{msgId},</if>
             <if test="sessionId != null">#{sessionId},</if>
@@ -98,7 +181,59 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="promptTokens != null">#{promptTokens},</if>
             <if test="completionTokens != null">#{completionTokens},</if>
             <if test="totalTokens != null">#{totalTokens},</if>
-         </trim>
+        </trim>
+    </insert>
+    <insert id="insertFastGptEventLog" useGeneratedKeys="false">
+        insert into fastgpt_event_log
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="eventName != null">event_name,</if>
+            <if test="senderId != null">sender_id,</if>
+            <if test="roleId != null">role_id,</if>
+            <if test="count != null">`count`,</if>
+            <if test="type != null">`type`,</if>
+            <if test="companyId != null">company_id,</if>
+            <if test="companyUserId != null">company_user_id,</if>
+            <if test="qwUserId != null">qw_user_id,</if>
+            <if test="createTime != null">create_time,</if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="eventName != null">#{eventName},</if>
+            <if test="senderId != null">#{senderId},</if>
+            <if test="roleId != null">#{roleId},</if>
+            <if test="count != null">#{count},</if>
+            <if test="type != null">#{type},</if>
+            <if test="companyId != null">#{companyId},</if>
+            <if test="companyUserId != null">#{companyUserId},</if>
+            <if test="qwUserId != null">#{qwUserId},</if>
+            <if test="createTime != null">#{createTime},</if>
+        </trim>
+    </insert>
+    <insert id="insertFastGptEventTokenLog" useGeneratedKeys="false">
+        insert into fastgpt_event_token_log
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="senderId != null">sender_id,</if>
+            <if test="roleId != null">role_id,</if>
+            <if test="eventName != null">event_name,</if>
+            <if test="eventType != null">event_type,</if>
+            <if test="tokenCount != null">token_count,</if>
+            <if test="tokenType != null">token_type,</if>
+            <if test="companyId != null">company_id,</if>
+            <if test="companyUserId != null">company_user_id,</if>
+            <if test="qwUserId != null">qw_user_id,</if>
+            <if test="createTime != null">create_time,</if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="senderId != null">#{senderId},</if>
+            <if test="roleId != null">#{roleId},</if>
+            <if test="eventName != null">#{eventName},</if>
+            <if test="eventType != null">#{eventType},</if>
+            <if test="tokenCount != null">#{tokenCount},</if>
+            <if test="tokenType != null">#{tokenType},</if>
+            <if test="companyId != null">#{companyId},</if>
+            <if test="companyUserId != null">#{companyUserId},</if>
+            <if test="qwUserId != null">#{qwUserId},</if>
+            <if test="createTime != null">#{createTime},</if>
+        </trim>
     </insert>
 
     <update id="updateFastGptChatMsg" parameterType="FastGptChatMsg">

+ 149 - 0
fs-service/src/main/resources/mapper/fastGpt/FastGptKeywordSendMapper.xml

@@ -0,0 +1,149 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.fs.fastGpt.mapper.FastGptKeywordSendMapper">
+    
+    <resultMap type="FastGptKeywordSend" id="FastGptKeywordSendResult">
+        <result property="id"    column="id"    />
+        <result property="keyword"    column="keyword"    />
+        <result property="keywordType"    column="keyword_type"    />
+        <result property="content"    column="content"    />
+        <result property="contentType"    column="content_type"    />
+        <result property="imgUrl"    column="img_url"    />
+        <result property="status"    column="status"    />
+        <result property="roleIds"    column="role_ids"    />
+        <result property="cropId"    column="crop_id"    />
+        <result property="companyId"    column="company_id"    />
+        <result property="companyUserId"    column="company_user_id"    />
+        <result property="createTime"    column="create_time"    />
+    </resultMap>
+
+    <sql id="selectFastGptKeywordSendVo">
+        select id, keyword,keyword_type,content, content_type, img_url, status, role_ids,crop_id,
+               company_id,company_user_id,create_time from fastgpt_keyword_send
+    </sql>
+
+    <select id="selectFastGptKeywordSendList" parameterType="FastGptKeywordSend" resultMap="FastGptKeywordSendResult">
+        <include refid="selectFastGptKeywordSendVo"/>
+        <where>
+            <if test="keyword != null  and keyword != ''"> and keyword = #{keyword}</if>
+            <if test="keywordType != null"> and keyword_type = #{keywordType}</if>
+            <if test="content != null  and content != ''"> and content like concat('%',#{content},'%')</if>
+            <if test="contentType != null "> and content_type = #{contentType}</if>
+            <if test="imgUrl != null  and imgUrl != ''"> and img_url = #{imgUrl}</if>
+            <if test="status != null "> and status = #{status}</if>
+            <if test="roleIds != null  and roleIds != ''"> and FIND_IN_SET(#{roleIds}, role_ids) > 0</if>
+            <if test="cropId != null "> and crop_id = #{cropId}</if>
+            <if test="companyId != null "> and company_id = #{companyId}</if>
+            <if test="companyUserId != null "> and company_user_id = #{companyUserId}</if>
+        </where>
+         order by create_time desc
+    </select>
+    
+    <select id="selectFastGptKeywordSendById" parameterType="Long" resultMap="FastGptKeywordSendResult">
+        <include refid="selectFastGptKeywordSendVo"/>
+        <where>
+            <if test="keywordType != null"> and keyword_type = #{keywordType}</if>
+             <if test="id != null">and id = #{id}</if>
+        </where>
+    </select>
+
+    <select id="selectFastGptKeywordSendByRoleId" resultType="com.fs.fastGpt.domain.FastGptKeywordSend">
+        <include refid="selectFastGptKeywordSendVo"/>
+        where status = 0 and (role_ids is null <if test="roleIds != null">or FIND_IN_SET(#{roleIds}, role_ids) > 0</if>)
+    </select>
+
+    <select id="selectFastGptKeywordArtificial" resultType="com.fs.fastGpt.domain.FastGptKeywordArtificial">
+        select user_id userId,company_id companyId,company_user_id companyUserId,keyword_send_id keywordSendId,
+               keyword_send_content keywordSendContent,create_time createTime from fastgpt_keyword_artificial
+        <where>
+            <if test="userId != null">and user_id = #{userId}</if>
+            <if test="keywordSendContent != null "> and keyword_send_content = #{keywordSendContent}</if>
+            <if test="companyId != null "> and company_id = #{companyId}</if>
+            <if test="companyUserId != null "> and company_user_id = #{companyUserId}</if>
+            <if test="createTime != null">and date_format(create_time,'%Y-%m-%d') = date_format(#{createTime},'%Y-%m-%d')</if>
+        </where>
+    </select>
+    <select id="selectFastGptKeywordList" resultType="com.fs.fastGpt.domain.FastGptKeyword">
+        select id,keyword,keyword_type from fastgpt_keyword
+        where keyword_type = #{keywordType}
+    </select>
+
+    <insert id="insertFastGptKeywordSend" parameterType="FastGptKeywordSend" useGeneratedKeys="true" keyProperty="id">
+        insert into fastgpt_keyword_send
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="keyword != null">keyword,</if>
+            <if test="keywordType != null">keyword_type,</if>
+            <if test="content != null">content,</if>
+            <if test="contentType != null">content_type,</if>
+            <if test="imgUrl != null">img_url,</if>
+            <if test="status != null">status,</if>
+            <if test="roleIds != null">role_ids,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="cropId != null "> crop_id,</if>
+            <if test="companyId != null ">company_id,</if>
+            <if test="companyUserId != null ">company_user_id,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="keyword != null">#{keyword},</if>
+            <if test="keywordType != null">#{keywordType},</if>
+            <if test="content != null">#{content},</if>
+            <if test="contentType != null">#{contentType},</if>
+            <if test="imgUrl != null">#{imgUrl},</if>
+            <if test="status != null">#{status},</if>
+            <if test="roleIds != null">#{roleIds},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="cropId != null "> #{cropId},</if>
+            <if test="companyId != null ">#{companyId},</if>
+            <if test="companyUserId != null "> #{companyUserId},</if>
+         </trim>
+    </insert>
+    <insert id="insertFastGptKeywordArtificial"  parameterType="fastGptKeywordArtificial" useGeneratedKeys="true" keyProperty="id">
+        insert into fastgpt_keyword_artificial
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="userId != null">user_id,</if>
+            <if test="companyId != null ">company_id,</if>
+            <if test="companyUserId != null ">company_user_id,</if>
+            <if test="keywordSendId != null "> keyword_send_Id,</if>
+            <if test="keywordSendContent != null "> keyword_send_content,</if>
+            <if test="createTime != null">create_time,</if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="userId != null">#{userId},</if>
+            <if test="companyId != null ">#{companyId},</if>
+            <if test="companyUserId != null ">#{companyUserId},</if>
+            <if test="keywordSendId != null "> #{keywordSendId},</if>
+            <if test="keywordSendContent != null "> #{keywordSendContent},</if>
+            <if test="createTime != null">#{createTime},</if>
+        </trim>
+    </insert>
+
+    <update id="updateFastGptKeywordSend" parameterType="FastGptKeywordSend">
+        update fastgpt_keyword_send
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="keyword != null">keyword = #{keyword},</if>
+            <if test="content != null">content = #{content},</if>
+            <if test="contentType != null">content_type = #{contentType},</if>
+            <if test="imgUrl != null">img_url = #{imgUrl},</if>
+            <if test="status != null">status = #{status},</if>
+            <if test="roleIds != null">role_ids = #{roleIds},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="cropId != null ">crop_id = #{cropId},</if>
+            <if test="companyId != null ">company_id = #{companyId},</if>
+            <if test="companyUserId != null ">company_user_id = #{companyUserId},</if>
+        </trim>
+        where id = #{id}
+    </update>
+
+    <delete id="deleteFastGptKeywordSendById" parameterType="Long">
+        delete from fastgpt_keyword_send where id = #{id}
+    </delete>
+
+    <delete id="deleteFastGptKeywordSendByIds" parameterType="String">
+        delete from fastgpt_keyword_send where id in 
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>

+ 5 - 1
fs-service/src/main/resources/mapper/fastGpt/FastGptRoleMapper.xml

@@ -20,10 +20,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="reminderWords"    column="reminder_words"    />
         <result property="bindCorpId"    column="bind_corp_id"    />
         <result property="contactInfo"    column="contact_info"    />
+        <result property="channelType"    column="channel_type"    />
     </resultMap>
 
     <sql id="selectFastGptRoleVo">
-        select role_id, role_name,contact_info,company_id, create_time, update_time, role_type, mode_config_json, mode, kf_id, kf_url, avatar, kf_media_id,reminder_words, bind_corp_id from fastgpt_role
+        select role_id, role_name,contact_info,company_id, create_time, update_time, role_type, mode_config_json, mode, kf_id, kf_url, avatar, kf_media_id,reminder_words, bind_corp_id,channel_type from fastgpt_role
     </sql>
 
     <select id="selectFastGptRoleList" parameterType="FastGptRole" resultMap="FastGptRoleResult">
@@ -65,6 +66,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="reminderWords != null">reminder_words,</if>
             <if test="bindCorpId != null">bind_corp_id,</if>
             <if test="contactInfo != null">contact_info,</if>
+            <if test="channelType != null">channel_type,</if>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="roleName != null">#{roleName},</if>
@@ -81,6 +83,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="reminderWords != null">#{reminderWords},</if>
             <if test="bindCorpId != null">#{bindCorpId},</if>
             <if test="contactInfo != null">#{contactInfo},</if>
+            <if test="channelType != null">#{channelType},</if>
          </trim>
     </insert>
 
@@ -101,6 +104,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="reminderWords != null">reminder_words = #{reminderWords},</if>
             <if test="bindCorpId != null">bind_corp_id = #{bindCorpId},</if>
             <if test="contactInfo != null">contact_info = #{contactInfo},</if>
+            <if test="channelType != null">channel_type = #{channelType},</if>
         </trim>
         where role_id = #{roleId}
     </update>

+ 5 - 0
fs-service/src/main/resources/mapper/his/FsStoreOrderMapper.xml

@@ -1126,4 +1126,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </if>
         ${maps.params.dataScope}
     </select>
+    <select id="selectFsStoreOrderByFsUserId" resultMap="FsStoreOrderResult">
+        <include refid="selectFsStoreOrderVo"/>
+        where user_id = #{fsUserId} and create_time >= DATE_SUB(NOW(), INTERVAL 30 DAY)
+        order by create_time desc limit 3
+    </select>
 </mapper>

+ 4 - 1
fs-service/src/main/resources/mapper/qw/QwExternalContactMapper.xml

@@ -583,7 +583,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             #{userId}
         </foreach>
     </select>
-
+    <select id="selectQwExternalContactByFsUserId" resultType="com.fs.qw.domain.QwExternalContact">
+        <include refid="selectQwExternalContactVo"/>
+        where fs_user_id = #{userId}
+    </select>
     <select id="selectFsUserCourseComplaintRecordByUserId" resultType="com.fs.qw.vo.QwExternalContactComplaintVO">
         select qec.id,qec.qw_user_id,qec.status,qec.user_id,qec.name,qec.avatar,qec.remark,qec.description,qec.fs_user_id,
                qu.qw_user_name,cu.user_name ,cu.nick_name

+ 11 - 0
fs-service/src/main/resources/mapper/sop/QwSopLogsMapper.xml

@@ -814,6 +814,17 @@
         order by ql.sort DESC ,ql.send_time asc
     </select>
 
+    <update id="batchUpdateQwSopLogsById" parameterType="java.util.List" useGeneratedKeys="false">
+        UPDATE  qw_sop_logs
+        SET
+        send_status = 5,
+        remark = 'ai回复超时过期'
+        WHERE id IN
+        <foreach collection="data" item="log" open="(" separator="," close=")">
+            #{log.id}
+        </foreach>
+    </update>
+
     <select id="selectByQwUserId" resultType="com.fs.sop.domain.QwSopLogs">
         select ql.*,
         qs.name,

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است