浏览代码

Merge branch 'master' of http://1.14.104.71:10880/root/ylrz_his_scrm_java

# Conflicts:
#	fs-service/src/main/resources/mapper/company/CompanyUserMapper.xml
#	fs-service/src/main/resources/mapper/store/FsUserCourseCountMapper.xml
caoliqin 1 周之前
父节点
当前提交
2f69ecbbd3
共有 100 个文件被更改,包括 3828 次插入289 次删除
  1. 2 1
      fs-admin/src/main/resources/application.yml
  2. 5 1
      fs-company-app/src/main/java/com/fs/app/param/LoginParam.java
  3. 1 2
      fs-company/src/main/java/com/fs/company/controller/course/FsCourseWatchLogController.java
  4. 1 1
      fs-company/src/main/java/com/fs/company/controller/fastGpt/FastGptChatSessionController.java
  5. 5 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwUserController.java
  6. 3 2
      fs-company/src/main/resources/application.yml
  7. 49 0
      fs-qw-api-msg/src/main/java/com/fs/app/config/QWConfigProperties.java
  8. 182 13
      fs-qw-api-msg/src/main/java/com/fs/app/controller/QwMsgController.java
  9. 1 1
      fs-qw-api-msg/src/main/java/com/fs/app/controller/imgTest.java
  10. 55 0
      fs-qw-api-msg/src/main/java/com/fs/app/controller/test.java
  11. 20 3
      fs-qw-api-msg/src/main/java/com/fs/framework/config/DataSourceConfig.java
  12. 1 1
      fs-qw-api-msg/src/main/java/com/fs/framework/config/MyBatisConfig.java
  13. 2 0
      fs-qw-api-msg/src/main/java/com/fs/framework/config/SecurityConfig.java
  14. 3 2
      fs-qw-api-msg/src/main/resources/application.yml
  15. 4 2
      fs-qw-api/src/main/resources/application.yml
  16. 1 1
      fs-qw-task/src/main/java/com/fs/app/taskService/impl/SopLogsTaskServiceImpl.java
  17. 45 0
      fs-service/src/main/java/com/fs/company/domain/CompanyCompanyFsuser.java
  18. 20 0
      fs-service/src/main/java/com/fs/company/domain/CompanyUser.java
  19. 68 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyCompanyFsuserMapper.java
  20. 2 0
      fs-service/src/main/java/com/fs/company/mapper/CompanyUserMapper.java
  21. 15 0
      fs-service/src/main/java/com/fs/company/vo/OptionVO.java
  22. 1 1
      fs-service/src/main/java/com/fs/core/config/WxMaConfiguration.java
  23. 2 2
      fs-service/src/main/java/com/fs/course/domain/FsCourseWatchLog.java
  24. 9 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserCourseMapper.java
  25. 1 1
      fs-service/src/main/java/com/fs/course/param/FsCourseLinkCreateParam.java
  26. 3 0
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseService.java
  27. 2 2
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseLinkServiceImpl.java
  28. 1 1
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java
  29. 10 0
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseServiceImpl.java
  30. 1 1
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseVideoServiceImpl.java
  31. 63 0
      fs-service/src/main/java/com/fs/course/vo/FsUserCourseVideoAppletVO.java
  32. 23 0
      fs-service/src/main/java/com/fs/fastGpt/domain/FastGptEventLog.java
  33. 23 0
      fs-service/src/main/java/com/fs/fastGpt/domain/FastGptEventTokenLog.java
  34. 12 0
      fs-service/src/main/java/com/fs/fastGpt/domain/FastGptKeyword.java
  35. 19 0
      fs-service/src/main/java/com/fs/fastGpt/domain/FastGptKeywordArtificial.java
  36. 55 0
      fs-service/src/main/java/com/fs/fastGpt/domain/FastGptKeywordSend.java
  37. 2 0
      fs-service/src/main/java/com/fs/fastGpt/domain/FastGptRole.java
  38. 77 0
      fs-service/src/main/java/com/fs/fastGpt/domain/FastgptChatArtificialWords.java
  39. 56 0
      fs-service/src/main/java/com/fs/fastGpt/domain/FastgptEventLogTotal.java
  40. 7 4
      fs-service/src/main/java/com/fs/fastGpt/mapper/FastGptChatMsgMapper.java
  41. 72 0
      fs-service/src/main/java/com/fs/fastGpt/mapper/FastGptKeywordSendMapper.java
  42. 1 1
      fs-service/src/main/java/com/fs/fastGpt/mapper/FastGptRoleMapper.java
  43. 4 0
      fs-service/src/main/java/com/fs/fastGpt/service/AiHookService.java
  44. 9 1
      fs-service/src/main/java/com/fs/fastGpt/service/IFastGptChatMsgService.java
  45. 72 0
      fs-service/src/main/java/com/fs/fastGpt/service/IFastGptKeywordSendService.java
  46. 497 196
      fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java
  47. 24 3
      fs-service/src/main/java/com/fs/fastGpt/service/impl/FastGptChatMsgServiceImpl.java
  48. 116 0
      fs-service/src/main/java/com/fs/fastGpt/service/impl/FastGptKeywordSendServiceImpl.java
  49. 78 0
      fs-service/src/main/java/com/fs/fastGpt/vo/FastGptChatSessionVo.java
  50. 31 0
      fs-service/src/main/java/com/fs/fastgptApi/param/DouBaoAiParam.java
  51. 118 20
      fs-service/src/main/java/com/fs/fastgptApi/util/AiImgUtil.java
  52. 28 0
      fs-service/src/main/java/com/fs/fastgptApi/util/EventLogQueue.java
  53. 154 0
      fs-service/src/main/java/com/fs/fastgptApi/util/EventLogUtils.java
  54. 33 0
      fs-service/src/main/java/com/fs/his/dto/PayloadDTO.java
  55. 2 0
      fs-service/src/main/java/com/fs/his/mapper/FsStoreOrderMapper.java
  56. 11 0
      fs-service/src/main/java/com/fs/his/param/PushFgOrderConfig.java
  57. 1 0
      fs-service/src/main/java/com/fs/his/service/IFsStoreOrderService.java
  58. 4 0
      fs-service/src/main/java/com/fs/his/service/impl/FsStoreOrderServiceImpl.java
  59. 51 0
      fs-service/src/main/java/com/fs/his/vo/FsComplaintOrderExportVO.java
  60. 13 0
      fs-service/src/main/java/com/fs/his/vo/FsStoreOrderAndUserVo.java
  61. 35 0
      fs-service/src/main/java/com/fs/his/vo/FsStoreOrderLogisticsFgVO.java
  62. 25 0
      fs-service/src/main/java/com/fs/his/vo/FsStoreOrderPushFgVO.java
  63. 29 0
      fs-service/src/main/java/com/fs/im/config/IMConfig.java
  64. 15 0
      fs-service/src/main/java/com/fs/im/dto/OpenImConversationDTO.java
  65. 11 0
      fs-service/src/main/java/com/fs/im/dto/OpenImEditConversationDTO.java
  66. 45 0
      fs-service/src/main/java/com/fs/im/dto/OpenImMsgDTO.java
  67. 13 0
      fs-service/src/main/java/com/fs/im/dto/OpenImResponseDTO.java
  68. 34 0
      fs-service/src/main/java/com/fs/im/service/OpenIMService.java
  69. 882 0
      fs-service/src/main/java/com/fs/im/service/impl/OpenIMServiceImpl.java
  70. 28 0
      fs-service/src/main/java/com/fs/im/vo/OpenImMsgCallBackVO.java
  71. 18 0
      fs-service/src/main/java/com/fs/im/vo/OpenImResponseDTOTest.java
  72. 12 0
      fs-service/src/main/java/com/fs/qw/domain/QwExternalContact.java
  73. 2 0
      fs-service/src/main/java/com/fs/qw/domain/QwExternalContactInfo.java
  74. 12 0
      fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java
  75. 5 0
      fs-service/src/main/java/com/fs/qw/param/QwAutoSopTimeParam.java
  76. 13 0
      fs-service/src/main/java/com/fs/qw/param/QwExternalContactUpdateNoteParam.java
  77. 12 0
      fs-service/src/main/java/com/fs/qw/param/RepeatParam.java
  78. 2 0
      fs-service/src/main/java/com/fs/qw/service/IQwExternalContactService.java
  79. 2 0
      fs-service/src/main/java/com/fs/qw/service/IQwUserService.java
  80. 5 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java
  81. 62 11
      fs-service/src/main/java/com/fs/qw/service/impl/QwUserServiceImpl.java
  82. 6 2
      fs-service/src/main/java/com/fs/qw/service/impl/QwUserVoiceLogServiceImpl.java
  83. 14 1
      fs-service/src/main/java/com/fs/qw/vo/QwUserVO.java
  84. 6 0
      fs-service/src/main/java/com/fs/sop/mapper/QwSopLogsMapper.java
  85. 2 2
      fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java
  86. 1 1
      fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsServiceImpl.java
  87. 13 0
      fs-service/src/main/java/com/fs/store/domain/FsUserCourseCount.java
  88. 2 2
      fs-service/src/main/java/com/fs/store/service/impl/FsUserCourseCountServiceImpl.java
  89. 3 0
      fs-service/src/main/java/com/fs/watch/mapper/WatchCompanyUserMapper.java
  90. 1 1
      fs-service/src/main/java/com/fs/wxwork/service/WxWorkServiceImpl.java
  91. 2 1
      fs-service/src/main/resources/application-config-druid-hzyy.yml
  92. 1 1
      fs-service/src/main/resources/application-config-druid-jzzx.yml
  93. 4 1
      fs-service/src/main/resources/application-druid-hdt.yml
  94. 168 0
      fs-service/src/main/resources/application-druid-hzyy-test.yml
  95. 18 0
      fs-service/src/main/resources/application-druid-hzyy.yml
  96. 3 3
      fs-service/src/main/resources/application-druid-jnmy.yml
  97. 18 0
      fs-service/src/main/resources/application-druid-jzzx.yml
  98. 103 0
      fs-service/src/main/resources/mapper/company/CompanyCompanyFsuserMapper.xml
  99. 27 0
      fs-service/src/main/resources/mapper/company/CompanyUserChangeApplyUserMapper.xml
  100. 3 0
      fs-service/src/main/resources/mapper/company/CompanyUserMapper.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
 

+ 5 - 1
fs-company-app/src/main/java/com/fs/app/param/LoginParam.java

@@ -1,5 +1,7 @@
 package com.fs.app.param;
 
+import com.fasterxml.jackson.annotation.JsonAlias;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
@@ -7,6 +9,7 @@ import javax.validation.constraints.NotBlank;
 
 
 @Data
+@JsonIgnoreProperties(ignoreUnknown = true)
 public class LoginParam {
     @NotBlank(message = "请填写帐号")
     private String account;
@@ -14,6 +17,7 @@ public class LoginParam {
     private String password;
 
     private String jpushId;
-
+    @JsonAlias({"appid", "appId"})
     private String appid;
+
 }

+ 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
+

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

@@ -911,7 +911,7 @@ public class SopLogsTaskServiceImpl implements SopLogsTaskService {
                             createParam.setCompanyUserId(Long.parseLong(companyUserId));
                             createParam.setCompanyId(Long.parseLong(companyId));
                             createParam.setChatId(logVo.getChatId());
-                            createParam.setQwUserId(Long.parseLong(qwUserId));
+                            createParam.setQwUserId(Long.valueOf(qwUserId));
                             createParam.setDays(setting.getExpiresDays());
                             R createLink = courseLinkService.createRoomLinkUrl(createParam);
                             if (createLink.get("code").equals(500)) {

+ 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,6 +302,8 @@ public interface CompanyUserMapper
      * **/
     void batchUpdateUserDept(@Param("companyUserList") List<CompanyUser> companyUserList);
 
+    CompanyUser selectCompanyUserByCompanyUserId(Long companyUserId);
+
     String selectCompanyUserNameByIds(@Param("companyUserIds")String companyUserIds);
 
     int updateAllowedAllRegister(@Param("status") boolean status, @Param("userIds")List<Long> userIds);

+ 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);

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

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

+ 9 - 0
fs-service/src/main/java/com/fs/course/mapper/FsUserCourseMapper.java

@@ -276,4 +276,13 @@ public interface FsUserCourseMapper
      * 查询当天用户-项目看课记录条数
      */
     Integer selectTodayCourseWatchLogCountByUserIdAndProjectId(@Param("userId") Long userId, @Param("projectId") Long projectId);
+
+    @Select("select course_id,course_name,description,img_url,second_img secondImg,views from fs_user_course where is_private = 0 order by sort,course_id")
+    List<FsUserCourseVideoAppletVO> selectFsUserCourseVideoApplet();
+
+    @Select("select video_id,title,course_id,video_url,SEC_TO_TIME(duration) as total_duration," +
+            "thumbnail videoImgUrl,description videoDescription,video_url videoUrl,question_bank_id questionBankId " +
+            " from fs_user_course_video where course_id = #{courseId}  order by course_sort,video_id")
+    List<FsUserCourseVideoAppletVO.FsUserCourseVideo> selectFsUserCourseVideoAppletByCourseId(@Param("courseId") Long courseId);
+
 }

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

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

+ 3 - 0
fs-service/src/main/java/com/fs/course/service/IFsUserCourseService.java

@@ -125,4 +125,7 @@ public interface IFsUserCourseService
 
     int copyFsUserCourse(Long courseId);
 
+    List<FsUserCourseVideoAppletVO> selectFsUserCourseVideoApplet();
+
+    List<FsUserCourseVideoAppletVO.FsUserCourseVideo> selectFsUserCourseVideoAppletByCourseId(Long courseId);
 }

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

@@ -310,7 +310,7 @@ public class FsCourseLinkServiceImpl implements IFsCourseLinkService
 
         FsCourseLink link = new FsCourseLink();
         link.setCompanyId(param.getCompanyId());
-        link.setQwUserId(param.getQwUserId());
+        link.setQwUserId(Long.valueOf(param.getQwUserId()));
         link.setCompanyUserId(param.getCompanyUserId());
         link.setVideoId(param.getVideoId());
         link.setCorpId(param.getCorpId());
@@ -531,7 +531,7 @@ public class FsCourseLinkServiceImpl implements IFsCourseLinkService
 
         FsCourseLink link = new FsCourseLink();
         link.setCompanyId(param.getCompanyId());
-        link.setQwUserId(param.getQwUserId());
+        link.setQwUserId(Long.valueOf(param.getQwUserId()));
         link.setCompanyUserId(param.getCompanyUserId());
         link.setVideoId(param.getVideoId());
         link.setCorpId(param.getCorpId());

+ 1 - 1
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();

+ 10 - 0
fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseServiceImpl.java

@@ -670,6 +670,16 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
         return 0;
     }
 
+    @Override
+    public List<FsUserCourseVideoAppletVO> selectFsUserCourseVideoApplet() {
+        return fsUserCourseMapper.selectFsUserCourseVideoApplet();
+    }
+
+    @Override
+    public List<FsUserCourseVideoAppletVO.FsUserCourseVideo> selectFsUserCourseVideoAppletByCourseId(Long courseId) {
+        return fsUserCourseMapper.selectFsUserCourseVideoAppletByCourseId(courseId);
+    }
+
 
     private Graphics2D initializeGraphics(BufferedImage combined) {
         Graphics2D graphics = combined.createGraphics();

+ 1 - 1
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(Long.valueOf(param.getQwUserId()));
         log.setCreateTime(new Date());
         log.setLogType(3);
         logger.info("zyp \n【群聊生成看课记录】:{}",param);

+ 63 - 0
fs-service/src/main/java/com/fs/course/vo/FsUserCourseVideoAppletVO.java

@@ -0,0 +1,63 @@
+package com.fs.course.vo;
+
+import com.fs.common.core.domain.BaseEntity;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 课堂视频对象 fs_user_video
+ *
+ * @author fs
+ * @date 2024-05-15
+ */
+@Data
+public class FsUserCourseVideoAppletVO extends BaseEntity
+{
+    private Long courseId;
+
+    /** 课程名称 */
+    private String courseName;
+
+    /** 课程描述 */
+    private String description;
+
+    /** 课程封面 */
+    private String imgUrl;
+
+    /** 小封面 */
+    private String secondImg;
+
+    /** 总播放量 */
+    private Long views;
+
+    /** 视频总数 */
+    private Long videoTotal;
+
+    private List<FsUserCourseVideo> fsUserCourseVideoList;
+
+    @Data
+    public static class FsUserCourseVideo{
+        /** ID */
+        private Long videoId;
+
+        /** 视频标题 */
+        private String title;
+
+        /** 视频封面 */
+        private String videoImgUrl;
+
+        /** 视频地址 */
+        private String videoUrl;
+
+        /** 视频描述 */
+        private String videoDescription;
+
+        /** 总播放时长 */
+        private String totalDuration;
+
+        private String questionBankId;
+    }
+
+
+}

+ 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 - 196
fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java


+ 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);
 }

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

@@ -5602,4 +5602,9 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
             logger.info("企微凌晨同步完成"+corpId);
         }
     }
+
+    @Override
+    public List<QwExternalContact> selectQwExternalContactByFsUserId(Long userId) {
+        return qwExternalContactMapper.selectQwExternalContactByFsUserId(userId);
+    }
 }

+ 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);
 }

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

@@ -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(Long.valueOf(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(Long.valueOf(qwUserId));
         link.setCompanyUserId(Long.parseLong(companyUserId));
         link.setVideoId(videoId.longValue());
         link.setCorpId(corpId);

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

@@ -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(Long.valueOf(qwUserId));
                                 watchLog.setCourseId(Long.valueOf(courseId));
                                 watchLog.setCompanyUserId(Long.valueOf(companyUserId));
                                 watchLog.setCompanyId(Long.valueOf(companyId));

+ 13 - 0
fs-service/src/main/java/com/fs/store/domain/FsUserCourseCount.java

@@ -58,6 +58,19 @@ public class FsUserCourseCount extends BaseEntity
     @Excel(name = "用户状态,1-正常;2-停止;3-未看")
     private Long status;
 
+    /**
+     * 项目id
+     */
+    private Long projectId;
+
+    public Long getProjectId() {
+        return projectId;
+    }
+
+    public void setProjectId(Long projectId) {
+        this.projectId = projectId;
+    }
+
     /** 停课天数(如果状态是停止) */
     @Excel(name = "停课天数", readConverterExp = "如=果状态是停止")
     private Long stopWatchDays;

+ 2 - 2
fs-service/src/main/java/com/fs/store/service/impl/FsUserCourseCountServiceImpl.java

@@ -121,10 +121,10 @@ public class FsUserCourseCountServiceImpl implements IFsUserCourseCountService
         // 查询用户-每天的最新的看课状态,和最后的心跳时间
         List<FsUserCourseCount> userStatusAndLastWatchDate = fsUserCourseCountMapper.getUserStatusAndLastWatchDate();
         Map<String, FsUserCourseCount> map = userStatusAndLastWatchDate.stream()
-                .collect(Collectors.toMap(k -> String.format("%s-%s", k.getUserId(), k.getLastDate()), v -> v));
+                .collect(Collectors.toMap(k -> String.format("%s-%s-%s", k.getUserId(),k.getProjectId(), k.getLastDate()), v -> v));
 
         for (FsUserCourseCount data : countResult) {
-            String key = String.format("%s-%s",data.getUserId(), data.getLastDate());
+            String key = String.format("%s-%s-%s", data.getUserId(),data.getProjectId(), data.getLastDate());
             FsUserCourseCount fsUserCourseCount = map.get(key);
             if(fsUserCourseCount != null){
                 data.setLastWatchDate(fsUserCourseCount.getLastWatchDate());

+ 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

+ 4 - 1
fs-service/src/main/resources/application-druid-hdt.yml

@@ -149,4 +149,7 @@ rocketmq:
     consumer:
         group: common-group
         access-key: default
-        secret-key: aZS4z!88dNndKTfhITzTpTxRrVUShtH3
+        secret-key: aZS4z!88dNndKTfhITzTpTxRrVUShtH3
+openIM:
+    secret: openIM123
+    userID: imAdmin

+ 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

+ 3 - 3
fs-service/src/main/resources/application-druid-jnmy.yml

@@ -148,6 +148,6 @@ rocketmq:
         group: test-group
         access-key: ak1243b25nj17d4b2dc1a03 # 替换为实际的 accessKey
         secret-key: sk08a7ea1f9f4b0237 # 替换为实际的 secretKey
-ipad:
-    ipadUrl:
-    aiApi:
+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>

+ 27 - 0
fs-service/src/main/resources/mapper/company/CompanyUserChangeApplyUserMapper.xml

@@ -0,0 +1,27 @@
+<?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.CompanyUserChangeApplyUserMapper">
+
+    <!-- 查询申请记录关联用户 -->
+    <select id="getApplyUsers" resultType="com.fs.company.vo.CompanyUserChangeApplyUserVO">
+        select
+            fu.user_id,
+            fu.nickname as userName,
+            fu.avatar,
+            cucau.project_id
+        from company_user_change_apply_user cucau
+        inner join fs_user fu on fu.user_id = cucau.user_id
+        where cucau.apply_id = #{applyId}
+    </select>
+
+    <!-- 修改申请记录关联用户销售 -->
+    <update id="changeUser">
+        update fs_user_company_user fu
+        inner join company_user_change_apply_user cucau on cucau.user_id = fu.user_id  and cucau.project_id = fu.project_id
+        inner join company_user_change_apply cuca on cuca.id = cucau.apply_id
+        set fu.company_user_id = cuca.`to`
+        where cuca.id = #{applyId}
+    </update>
+</mapper>

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

@@ -528,6 +528,9 @@ 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>
 
     <update id="updateAllowedAllRegister" parameterType="Long">
         update company_user

部分文件因为文件数量过多而无法显示