Sfoglia il codice sorgente

feat:会员批量发送课程消息、去掉用户的项目前缀

caoliqin 4 settimane fa
parent
commit
06fbfa04cd
23 ha cambiato i file con 880 aggiunte e 74 eliminazioni
  1. 1 1
      fs-doctor-app/src/main/java/com/fs/app/controller/DoctorController.java
  2. 2 2
      fs-doctor-app/src/main/java/com/fs/app/controller/DrugReportController.java
  3. 2 2
      fs-doctor-app/src/main/java/com/fs/app/controller/PrescribeController.java
  4. 50 0
      fs-service/src/main/java/com/fs/course/dto/BatchSendCourseDTO.java
  5. 1 0
      fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java
  6. 2 0
      fs-service/src/main/java/com/fs/course/mapper/FsUserCompanyUserMapper.java
  7. 1 1
      fs-service/src/main/java/com/fs/his/service/impl/FsDoctorServiceImpl.java
  8. 1 1
      fs-service/src/main/java/com/fs/his/service/impl/FsFollowServiceImpl.java
  9. 16 16
      fs-service/src/main/java/com/fs/his/service/impl/FsInquiryOrderMsgServiceImpl.java
  10. 4 4
      fs-service/src/main/java/com/fs/his/service/impl/FsInquiryOrderServiceImpl.java
  11. 10 10
      fs-service/src/main/java/com/fs/im/config/IMConfig.java
  12. 67 0
      fs-service/src/main/java/com/fs/im/domain/ImSendLog.java
  13. 52 0
      fs-service/src/main/java/com/fs/im/dto/OpenImBatchMsgDTO.java
  14. 31 0
      fs-service/src/main/java/com/fs/im/dto/OpenImBatchResponseDataDTO.java
  15. 70 0
      fs-service/src/main/java/com/fs/im/mapper/ImSendLogMapper.java
  16. 61 0
      fs-service/src/main/java/com/fs/im/service/IImSendLogService.java
  17. 19 1
      fs-service/src/main/java/com/fs/im/service/OpenIMService.java
  18. 92 0
      fs-service/src/main/java/com/fs/im/service/impl/ImSendLogServiceImpl.java
  19. 212 33
      fs-service/src/main/java/com/fs/im/service/impl/OpenIMServiceImpl.java
  20. 1 1
      fs-service/src/main/resources/application-druid-fby.yml
  21. 22 0
      fs-service/src/main/resources/mapper/course/FsUserCompanyUserMapper.xml
  22. 138 0
      fs-service/src/main/resources/mapper/im/ImSendLogMapper.xml
  23. 25 2
      fs-user-app/src/main/java/com/fs/app/controller/course/CourseFsUserController.java

+ 1 - 1
fs-doctor-app/src/main/java/com/fs/app/controller/DoctorController.java

@@ -133,7 +133,7 @@ public class DoctorController extends  AppBaseController {
                 int accountStatus = resultObj.getInt("accountStatus");
                 //未注册自动注册
                 if (accountStatus==0){
-                    String s = userId.replaceFirst("^"+IMConfig.PREFIX+"D", "");
+                    String s = userId.replaceFirst("^"+"D", "");
                     FsDoctor fsDoctor = doctorService.selectFsDoctorByDoctorId(Long.parseLong(s));
                     if (null==fsDoctor){
                         return R.error("用户不存在");

+ 2 - 2
fs-doctor-app/src/main/java/com/fs/app/controller/DrugReportController.java

@@ -112,7 +112,7 @@ public class DrugReportController extends AppBaseController {
             //imService.sendMsg(msgDTO);
             ObjectMapper objectMapper = new ObjectMapper();
             String ex = objectMapper.writeValueAsString(customDTO);
-            openIMService.sendUtil(IMConfig.PREFIX+"D"+follow.getDoctorId(),IMConfig.PREFIX+"U"+follow.getUserId(),110,"drugReport","","","",report.getReportId().toString(),ex);
+            openIMService.sendUtil("D"+follow.getDoctorId(),"U"+follow.getUserId(),110,"drugReport","","","",report.getReportId().toString(),ex);
 
             fsDrugReportCountService.addReportCountByUserIdAndDoctorId(follow.getUserId(),follow.getDoctorId());
 
@@ -147,7 +147,7 @@ public class DrugReportController extends AppBaseController {
         //imService.sendMsg(msgDTO);
         ObjectMapper objectMapper = new ObjectMapper();
         String ex = objectMapper.writeValueAsString(customDTO);
-        openIMService.sendUtil(IMConfig.PREFIX+"D"+follow.getDoctorId(),IMConfig.PREFIX+"U"+follow.getUserId(),110,"finishDrugReport","","您的用药咨询报告已出具,本次咨询结束","","",ex);
+        openIMService.sendUtil("D"+follow.getDoctorId(),"U"+follow.getUserId(),110,"finishDrugReport","","您的用药咨询报告已出具,本次咨询结束","","",ex);
 
         redisTemplate.delete("DrugReport:doctorId:" + follow.getDoctorId() + ":userId:" + follow.getUserId());
         return R.ok();

+ 2 - 2
fs-doctor-app/src/main/java/com/fs/app/controller/PrescribeController.java

@@ -333,8 +333,8 @@ public class PrescribeController extends  AppBaseController {
 //        openIMService.checkAndImportFriendByDianBo(Long.parseLong(map.get("sendId")),map.get("userId"),qwExternalContact.getCorpId());
         //OpenImResponseDTO openImResponseDTO = openIMService.sendCourse(Long.parseLong(map.get("userId")), Long.parseLong(map.get("sendId")), "/pages/courseAnswer/index?link=1932017457275338752", "《五仙传医2.0》","https://cos.his.cdwjyyh.com/fs/20241108/a8ed49ae9a264c7483cec5bdcbcf6060.png");
         log.info("请求地址{}",IMConfig.URL);
-        log.info("前缀{}",IMConfig.PREFIX);
-        //OpenImResponseDTO openImResponseDTO = openIMService.sendUtil(IMConfig.PREFIX+"D" +map.get("sendId"), IMConfig.PREFIX+"U"+map.get("userId").toString(), 110, map.get("payloadDAata").toString(), "", map.get("title").toString(), "", "3135749",ex);
+//        log.info("前缀{}",IMConfig.PREFIX);
+        //OpenImResponseDTO openImResponseDTO = openIMService.sendUtil("D" +map.get("sendId"), "U"+map.get("userId").toString(), 110, map.get("payloadDAata").toString(), "", map.get("title").toString(), "", "3135749",ex);
         return R.ok().put("data",null);
     }
 }

+ 50 - 0
fs-service/src/main/java/com/fs/course/dto/BatchSendCourseDTO.java

@@ -0,0 +1,50 @@
+package com.fs.course.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 批量发课-入参
+ */
+@Data
+public class BatchSendCourseDTO {
+
+    @ApiModelProperty(value = "销售id")
+    private Long companyUserId;
+
+    @ApiModelProperty(value = "公司id")
+    private Long companyId;
+
+    @ApiModelProperty(value = "营期id")
+    private Long periodId;
+
+    @ApiModelProperty(value = "课程id")
+    private Long courseId;
+
+    @ApiModelProperty(value = "视频id")
+    private Long videoId;
+
+    @ApiModelProperty(value = "标签ids")
+    private List<Long> tagIds;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @ApiModelProperty(value = "发送消息时间,定时发送需要传")
+    private Date sendTime;
+
+    @ApiModelProperty(value = "发送标题")
+    private String title;
+
+    @ApiModelProperty(value = "链接有效时长(分钟)")
+    private Integer effectiveDuration;
+
+    @ApiModelProperty(value = "营期课程id")
+    private Long id;
+
+    @ApiModelProperty(value = "看课短链,不用传")
+    private String url;
+
+}

+ 1 - 0
fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java

@@ -6,6 +6,7 @@ import com.fs.course.domain.FsUserCourseVideo;
 import com.fs.course.dto.WatchLogDTO;
 import com.fs.course.param.*;
 import com.fs.course.vo.*;
+import com.fs.im.dto.OpenImBatchResponseDataDTO;
 import com.fs.qw.domain.QwExternalContact;
 import com.fs.sop.vo.QwRatingVO;
 import org.apache.ibatis.annotations.Param;

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

@@ -93,4 +93,6 @@ public interface FsUserCompanyUserMapper extends BaseMapper<FsUserCompanyUser>{
     int batchUpdateStatus(@Param("ids") List<Long> ids, @Param("status") int status);
 
     List<Long> selectFsUserCompanyUserListByMap(@Param("param") Map<String, Object> param);
+
+    List<FsUserCompanyUser> selectFsUserCompanyUserByIds(@Param("param") Map<String, Object> param);
 }

+ 1 - 1
fs-service/src/main/java/com/fs/his/service/impl/FsDoctorServiceImpl.java

@@ -419,7 +419,7 @@ public class FsDoctorServiceImpl implements IFsDoctorService
                         //imService.sendMsg(msgDTO);
                         ObjectMapper objectMapper = new ObjectMapper();
                         String ex = objectMapper.writeValueAsString(customDTO3);
-                        openIMService.sendUtil(IMConfig.PREFIX+"D"+fsFollow.getDoctorId(),IMConfig.PREFIX+"U"+fsFollow.getUserId(),110,"follow","","",followId,"",ex);
+                        openIMService.sendUtil("D"+fsFollow.getDoctorId(),"U"+fsFollow.getUserId(),110,"follow","","",followId,"",ex);
                         /*OpenImMsgDTO openImMsgDTO = new OpenImMsgDTO();
                         openImMsgDTO.setSendID("D"+fsFollow.getDoctorId().toString());
                         openImMsgDTO.setRecvID("U"+fsFollow.getUserId().toString());

+ 1 - 1
fs-service/src/main/java/com/fs/his/service/impl/FsFollowServiceImpl.java

@@ -191,7 +191,7 @@ public class FsFollowServiceImpl implements IFsFollowService
             //MsgResponseDTO msgResponseDTO = imService.sendMsg(msgDTO);
             ObjectMapper objectMapper = new ObjectMapper();
             String ex = objectMapper.writeValueAsString(customDTO);
-            OpenImResponseDTO follow = openIMService.sendUtil(IMConfig.PREFIX+"D"+fsFollow.getDoctorId(), IMConfig.PREFIX+"U"+fsFollow.getUserId(), 110, "follow", "", "", followId, "",ex);
+            OpenImResponseDTO follow = openIMService.sendUtil("D"+fsFollow.getDoctorId(), "U"+fsFollow.getUserId(), 110, "follow", "", "", followId, "",ex);
             /*OpenImMsgDTO openImMsgDTO = new OpenImMsgDTO();
             openImMsgDTO.setSendID("D"+fsFollow.getDoctorId().toString());
             openImMsgDTO.setRecvID("U"+fsFollow.getUserId().toString());

+ 16 - 16
fs-service/src/main/java/com/fs/his/service/impl/FsInquiryOrderMsgServiceImpl.java

@@ -348,20 +348,20 @@ public class FsInquiryOrderMsgServiceImpl implements IFsInquiryOrderMsgService
             String companyUserId="";
             String msgType="2";
             //用户发送消息
-            if (send.contains(IMConfig.PREFIX+"U")){
+            if (send.contains("U")){
                 msgType="1";
-                userId=send.replace(IMConfig.PREFIX+"U","");
-                if (to.contains(IMConfig.PREFIX+"D")){
-                    doctorId=to.replace(IMConfig.PREFIX+"D","");
+                userId=send.replace("U","");
+                if (to.contains("D")){
+                    doctorId=to.replace("D","");
                     fsFollowReportService.addReport(userId,doctorId);
-                }else if (to.contains(IMConfig.PREFIX+"C")){
-                    companyUserId=to.replace(IMConfig.PREFIX+"C","");
+                }else if (to.contains("C")){
+                    companyUserId=to.replace("C","");
                 }
                 //医生发送消息
-            }else if (send.contains(IMConfig.PREFIX+"D")){
-                doctorId=send.replace(IMConfig.PREFIX+"D","");
-                if (to.contains(IMConfig.PREFIX+"U")){
-                    userId=to.replace(IMConfig.PREFIX+"U","");
+            }else if (send.contains("D")){
+                doctorId=send.replace("D","");
+                if (to.contains("U")){
+                    userId=to.replace("U","");
                     TemplateBean templateBean = TemplateBean.builder()
                             .title("您收到咨询回复")
                             .remark("您的咨询已回复")
@@ -404,15 +404,15 @@ public class FsInquiryOrderMsgServiceImpl implements IFsInquiryOrderMsgService
                         }
                     }
                 }else {
-                    companyUserId = to.replace(IMConfig.PREFIX+"C","");
+                    companyUserId = to.replace("C","");
                 }
                 //销售发送消息
-            }else if (send.contains(IMConfig.PREFIX+"C")){
-                companyUserId=send.replace(IMConfig.PREFIX+"C","");
-                if (to.contains(IMConfig.PREFIX+"U")){
-                    userId=to.replace(IMConfig.PREFIX+"U","");
+            }else if (send.contains("C")){
+                companyUserId=send.replace("C","");
+                if (to.contains("U")){
+                    userId=to.replace("U","");
                 }else {
-                    doctorId = to.replace(IMConfig.PREFIX+"D","");
+                    doctorId = to.replace("D","");
                 }
             }
             Long orderId = fsInquiryOrderMsgMapper.selectFsInquiryOrderMsgOrderId(doctorId, userId);

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

@@ -938,7 +938,7 @@ public class FsInquiryOrderServiceImpl implements IFsInquiryOrderService
         //imService.sendMsg(msgDTO);
         ObjectMapper objectMapper = new ObjectMapper();
         String ex = objectMapper.writeValueAsString(customDTO);
-        openIMService.sendUtil(IMConfig.PREFIX+"D"+param.getDoctorId(),IMConfig.PREFIX+"U"+order.getUserId(),110,"startInquiry","",doctor.getDoctorName()+doc+"为您服务","","","");
+        openIMService.sendUtil("D"+param.getDoctorId(),"U"+order.getUserId(),110,"startInquiry","",doctor.getDoctorName()+doc+"为您服务","","","");
         /*OpenImMsgDTO openImMsgDTO = new OpenImMsgDTO();
         openImMsgDTO.setSendID("D"+param.getDoctorId().toString());
         openImMsgDTO.setRecvID("U"+order.getUserId().toString());
@@ -971,7 +971,7 @@ public class FsInquiryOrderServiceImpl implements IFsInquiryOrderService
 
             ObjectMapper objectMapper1 = new ObjectMapper();
             String ex1 = objectMapper.writeValueAsString(customDTO1);
-            openIMService.sendUtil(IMConfig.PREFIX+"D"+param.getDoctorId(),IMConfig.PREFIX+"U"+order.getUserId(),110,"startInquiry","","您好,我是芸医汇互联网医院执业药师,请问您有什么问题想咨询?","","",ex1);
+            openIMService.sendUtil("D"+param.getDoctorId(),"U"+order.getUserId(),110,"startInquiry","","您好,我是芸医汇互联网医院执业药师,请问您有什么问题想咨询?","","",ex1);
             /*OpenImMsgDTO openImMsgDTO1 = new OpenImMsgDTO();
             openImMsgDTO1.setSendID("D"+param.getDoctorId().toString());
             openImMsgDTO1.setRecvID("U"+order.getUserId().toString());
@@ -1102,7 +1102,7 @@ public class FsInquiryOrderServiceImpl implements IFsInquiryOrderService
         //imService.sendMsg(msgDTO);
         ObjectMapper objectMapper = new ObjectMapper();
         String ex = objectMapper.writeValueAsString(customDTO);
-        openIMService.sendUtil(IMConfig.PREFIX+"D"+order.getDoctorId(),IMConfig.PREFIX+"U"+order.getUserId(),110,"startInquiry","",doctor.getDoctorName()+doc+"为您服务","","",ex);
+        openIMService.sendUtil("D"+order.getDoctorId(),"U"+order.getUserId(),110,"startInquiry","",doctor.getDoctorName()+doc+"为您服务","","",ex);
         /*OpenImMsgDTO openImMsgDTO = new OpenImMsgDTO();
         openImMsgDTO.setSendID("D"+order.getDoctorId().toString());
         openImMsgDTO.setRecvID("U"+order.getUserId().toString());
@@ -1135,7 +1135,7 @@ public class FsInquiryOrderServiceImpl implements IFsInquiryOrderService
 
             ObjectMapper objectMapper1 = new ObjectMapper();
             String ex1 = objectMapper.writeValueAsString(customDTO1);
-            openIMService.sendUtil(IMConfig.PREFIX+"D"+param.getDoctorId(),IMConfig.PREFIX+"U"+order.getUserId(),110,"startInquiry","","您好,我是芸医汇互联网医院执业药师,请问您有什么问题想咨询?","","",ex1);
+            openIMService.sendUtil("D"+param.getDoctorId(),"U"+order.getUserId(),110,"startInquiry","","您好,我是芸医汇互联网医院执业药师,请问您有什么问题想咨询?","","",ex1);
            /* OpenImMsgDTO openImMsgDTO1 = new OpenImMsgDTO();
             openImMsgDTO1.setSendID("D"+order.getDoctorId().toString());
             openImMsgDTO1.setRecvID("U"+order.getUserId().toString());

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

@@ -14,15 +14,15 @@ public class IMConfig {
     private String userID;
     @Value("${openIM.url}")
     private String url;
-    @Value("${openIM.prefix}")
-    private String prefix;
+//    @Value("${openIM.prefix}")
+//    private String prefix;
     // 静态常量
     public static String PREFIX;
     public static String URL;
 
     @PostConstruct
     public void init() {
-        PREFIX = this.prefix;
+//        PREFIX = this.prefix;
         URL = this.url;
     }
 
@@ -50,11 +50,11 @@ public class IMConfig {
         this.url = url;
     }
 
-    public String getPrefix() {
-        return prefix;
-    }
-
-    public void setPrefix(String scrm) {
-        this.prefix = scrm;
-    }
+//    public String getPrefix() {
+//        return prefix;
+//    }
+//
+//    public void setPrefix(String scrm) {
+//        this.prefix = scrm;
+//    }
 }

+ 67 - 0
fs-service/src/main/java/com/fs/im/domain/ImSendLog.java

@@ -0,0 +1,67 @@
+package com.fs.im.domain;
+
+import java.util.Date;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.fs.common.annotation.Excel;
+import lombok.Data;
+import com.fs.common.core.domain.BaseEntity;
+import lombok.EqualsAndHashCode;
+
+/**
+ * openim消息发送记录对象 im_send_log
+ *
+ * @author fs
+ * @date 2025-08-12
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class ImSendLog extends BaseEntity{
+
+    /** 发送记录id */
+    private Long logId;
+
+    /** 发送者id */
+    @Excel(name = "发送者id")
+    private String sendId;
+
+    /** 接收者id */
+    @Excel(name = "接收者id")
+    private String recvId;
+
+    /** 发送的标题 */
+    @Excel(name = "发送的标题")
+    private String sendTitle;
+
+    /** 预计发送时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "预计发送时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date planSendTime;
+
+    /** 实际发送时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "实际发送时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date actualSendTime;
+
+    /** 发送类型,1-定时;2-实时 */
+    @Excel(name = "发送类型,1-定时;2-实时")
+    private Integer sendType;
+
+    /** 执行入参json */
+    @Excel(name = "执行入参json")
+    private String paramJson;
+
+    /** 执行状态,0-正常;1-失败 */
+    @Excel(name = "执行状态,0-正常;1-失败")
+    private Integer status;
+
+    /** 执行结果 */
+    @Excel(name = "执行结果")
+    private String resultMessage;
+
+    /** 异常信息 */
+    @Excel(name = "异常信息")
+    private String exceptionInfo;
+
+
+}

+ 52 - 0
fs-service/src/main/java/com/fs/im/dto/OpenImBatchMsgDTO.java

@@ -0,0 +1,52 @@
+package com.fs.im.dto;
+
+import com.fs.his.dto.PayloadDTO;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.util.List;
+
+/**
+ * 批量发送消息-请求参数
+ */
+@Data
+@Accessors(chain = true)
+public class OpenImBatchMsgDTO {
+
+    private String sendID;
+    private List<String> recvIDs;
+    private String senderNickname;
+    private String senderFaceURL;
+    private Integer senderPlatformID;
+    private Content content;
+    private Integer contentType;
+    private Integer sessionType;
+    private Boolean isOnlineOnly;
+    private Boolean notOfflinePush;
+    private Long sendTime;
+    private OfflinePushInfo offlinePushInfo;
+    private String ex;
+    private Boolean isSendAll; //是否发送给全部人
+
+
+    @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;
+
+    }
+}

+ 31 - 0
fs-service/src/main/java/com/fs/im/dto/OpenImBatchResponseDataDTO.java

@@ -0,0 +1,31 @@
+package com.fs.im.dto;
+
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.util.List;
+
+/**
+ * 批量发送消息-相应参数的data参数
+ */
+@Data
+@Accessors(chain = true)
+public class OpenImBatchResponseDataDTO {
+    /**
+     * 结果列表
+     */
+    private List<Results> results;
+    /**
+     * 向其发送消息失败的用户ID列表
+     */
+    private String[] failedUserIDs;
+
+    @Data
+    public static class Results {
+        private String serverMsgID;//服务器消息ID,预留字段
+        private String clientMsgID;//客户端消息ID,此ID为消息唯一ID
+        private Long sendTime;
+        private String recvID;
+    }
+
+}

+ 70 - 0
fs-service/src/main/java/com/fs/im/mapper/ImSendLogMapper.java

@@ -0,0 +1,70 @@
+package com.fs.im.mapper;
+
+import java.util.List;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.im.domain.ImSendLog;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * openim消息发送记录Mapper接口
+ *
+ * @author fs
+ * @date 2025-08-12
+ */
+public interface ImSendLogMapper extends BaseMapper<ImSendLog>{
+    /**
+     * 查询openim消息发送记录
+     *
+     * @param logId openim消息发送记录主键
+     * @return openim消息发送记录
+     */
+    ImSendLog selectImSendLogByLogId(Long logId);
+
+    /**
+     * 查询openim消息发送记录列表
+     *
+     * @param imSendLog openim消息发送记录
+     * @return openim消息发送记录集合
+     */
+    List<ImSendLog> selectImSendLogList(ImSendLog imSendLog);
+
+    /**
+     * 新增openim消息发送记录
+     *
+     * @param imSendLog openim消息发送记录
+     * @return 结果
+     */
+    int insertImSendLog(ImSendLog imSendLog);
+
+    /**
+     * 修改openim消息发送记录
+     *
+     * @param imSendLog openim消息发送记录
+     * @return 结果
+     */
+    int updateImSendLog(ImSendLog imSendLog);
+
+    /**
+     * 删除openim消息发送记录
+     *
+     * @param logId openim消息发送记录主键
+     * @return 结果
+     */
+    int deleteImSendLogByLogId(Long logId);
+
+    /**
+     * 批量删除openim消息发送记录
+     *
+     * @param logIds 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteImSendLogByLogIds(Long[] logIds);
+
+    /**
+     * 批量新增
+     * @param sendLogs
+     * @return
+     */
+    int insertImSendLogBatch(@Param("sendLogs") List<ImSendLog> sendLogs);
+
+}

+ 61 - 0
fs-service/src/main/java/com/fs/im/service/IImSendLogService.java

@@ -0,0 +1,61 @@
+package com.fs.im.service;
+
+import java.util.List;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.fs.im.domain.ImSendLog;
+
+/**
+ * openim消息发送记录Service接口
+ * 
+ * @author fs
+ * @date 2025-08-12
+ */
+public interface IImSendLogService extends IService<ImSendLog>{
+    /**
+     * 查询openim消息发送记录
+     * 
+     * @param logId openim消息发送记录主键
+     * @return openim消息发送记录
+     */
+    ImSendLog selectImSendLogByLogId(Long logId);
+
+    /**
+     * 查询openim消息发送记录列表
+     * 
+     * @param imSendLog openim消息发送记录
+     * @return openim消息发送记录集合
+     */
+    List<ImSendLog> selectImSendLogList(ImSendLog imSendLog);
+
+    /**
+     * 新增openim消息发送记录
+     * 
+     * @param imSendLog openim消息发送记录
+     * @return 结果
+     */
+    int insertImSendLog(ImSendLog imSendLog);
+
+    /**
+     * 修改openim消息发送记录
+     * 
+     * @param imSendLog openim消息发送记录
+     * @return 结果
+     */
+    int updateImSendLog(ImSendLog imSendLog);
+
+    /**
+     * 批量删除openim消息发送记录
+     * 
+     * @param logIds 需要删除的openim消息发送记录主键集合
+     * @return 结果
+     */
+    int deleteImSendLogByLogIds(Long[] logIds);
+
+    /**
+     * 删除openim消息发送记录信息
+     * 
+     * @param logId openim消息发送记录主键
+     * @return 结果
+     */
+    int deleteImSendLogByLogId(Long logId);
+}

+ 19 - 1
fs-service/src/main/java/com/fs/im/service/OpenIMService.java

@@ -3,6 +3,8 @@ 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.course.dto.BatchSendCourseDTO;
+import com.fs.im.dto.OpenImBatchMsgDTO;
 import com.fs.im.dto.OpenImEditConversationDTO;
 import com.fs.im.dto.OpenImMsgDTO;
 import com.fs.im.dto.OpenImResponseDTO;
@@ -29,8 +31,24 @@ public interface OpenIMService {
     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);
+    void checkAndImportFriendByDianBo(Long companyUserId,String fsUserId,String cropId, boolean isUpdate);
     OpenImResponseDTO updateUserInfo(CompanyUser companyUser);
 
     OpenImResponseDTO sendPackageUtil(String sendID, String recvID, Integer contentType, String payloadData,String packageName,String packageId);
+
+    /**
+     * 批量发送消息
+     * @param openImBatchMsgDTO 入参
+     * @return
+     */
+    OpenImResponseDTO openIMBatchSendMsg(OpenImBatchMsgDTO openImBatchMsgDTO);
+
+    /**
+     * 会员批量发课
+     * @param batchSendCourseDTO
+     * @return
+     * @throws JsonProcessingException
+     */
+    OpenImResponseDTO batchSendCourse(BatchSendCourseDTO batchSendCourseDTO) throws JsonProcessingException;
+
 }

+ 92 - 0
fs-service/src/main/java/com/fs/im/service/impl/ImSendLogServiceImpl.java

@@ -0,0 +1,92 @@
+package com.fs.im.service.impl;
+
+import java.util.List;
+import com.fs.common.utils.DateUtils;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+import com.fs.im.mapper.ImSendLogMapper;
+import com.fs.im.domain.ImSendLog;
+import com.fs.im.service.IImSendLogService;
+
+/**
+ * openim消息发送记录Service业务层处理
+ *
+ * @author fs
+ * @date 2025-08-12
+ */
+@Service
+public class ImSendLogServiceImpl extends ServiceImpl<ImSendLogMapper, ImSendLog> implements IImSendLogService {
+
+    /**
+     * 查询openim消息发送记录
+     *
+     * @param logId openim消息发送记录主键
+     * @return openim消息发送记录
+     */
+    @Override
+    public ImSendLog selectImSendLogByLogId(Long logId)
+    {
+        return baseMapper.selectImSendLogByLogId(logId);
+    }
+
+    /**
+     * 查询openim消息发送记录列表
+     *
+     * @param imSendLog openim消息发送记录
+     * @return openim消息发送记录
+     */
+    @Override
+    public List<ImSendLog> selectImSendLogList(ImSendLog imSendLog)
+    {
+        return baseMapper.selectImSendLogList(imSendLog);
+    }
+
+    /**
+     * 新增openim消息发送记录
+     *
+     * @param imSendLog openim消息发送记录
+     * @return 结果
+     */
+    @Override
+    public int insertImSendLog(ImSendLog imSendLog)
+    {
+        imSendLog.setCreateTime(DateUtils.getNowDate());
+        return baseMapper.insertImSendLog(imSendLog);
+    }
+
+    /**
+     * 修改openim消息发送记录
+     *
+     * @param imSendLog openim消息发送记录
+     * @return 结果
+     */
+    @Override
+    public int updateImSendLog(ImSendLog imSendLog)
+    {
+        return baseMapper.updateImSendLog(imSendLog);
+    }
+
+    /**
+     * 批量删除openim消息发送记录
+     *
+     * @param logIds 需要删除的openim消息发送记录主键
+     * @return 结果
+     */
+    @Override
+    public int deleteImSendLogByLogIds(Long[] logIds)
+    {
+        return baseMapper.deleteImSendLogByLogIds(logIds);
+    }
+
+    /**
+     * 删除openim消息发送记录信息
+     *
+     * @param logId openim消息发送记录主键
+     * @return 结果
+     */
+    @Override
+    public int deleteImSendLogByLogId(Long logId)
+    {
+        return baseMapper.deleteImSendLogByLogId(logId);
+    }
+}

+ 212 - 33
fs-service/src/main/java/com/fs/im/service/impl/OpenIMServiceImpl.java

@@ -15,6 +15,13 @@ 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.course.domain.FsCourseWatchLog;
+import com.fs.course.domain.FsUserCompanyUser;
+import com.fs.course.domain.FsUserCourse;
+import com.fs.course.dto.BatchSendCourseDTO;
+import com.fs.course.mapper.FsCourseWatchLogMapper;
+import com.fs.course.mapper.FsUserCompanyUserMapper;
+import com.fs.course.mapper.FsUserCourseMapper;
 import com.fs.fastGpt.service.AiHookService;
 import com.fs.his.domain.FsDoctor;
 import com.fs.his.domain.FsFollow;
@@ -24,10 +31,9 @@ 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.domain.ImSendLog;
+import com.fs.im.dto.*;
+import com.fs.im.mapper.ImSendLogMapper;
 import com.fs.im.service.OpenIMService;
 import com.fs.im.vo.OpenImMsgCallBackVO;
 import com.fs.im.vo.OpenImResponseDTOTest;
@@ -35,15 +41,17 @@ import com.fs.qw.mapper.QwExternalContactMapper;
 import com.github.pagehelper.util.StringUtil;
 import lombok.Data;
 import lombok.extern.slf4j.Slf4j;
+import org.jetbrains.annotations.NotNull;
 import org.json.JSONArray;
 import org.json.JSONObject;
+import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.CollectionUtils;
-
 import java.util.*;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
@@ -70,9 +78,17 @@ public class OpenIMServiceImpl implements OpenIMService {
     @Autowired
     @Lazy
     private AiHookService aiHookService;
+    @Autowired
+    private FsUserCourseMapper fsUserCourseMapper;
+    @Autowired
+    private FsUserCompanyUserMapper fsUserCompanyUserMapper;
+    @Autowired
+    private FsCourseWatchLogMapper courseWatchLogMapper;
+    @Autowired
+    private ImSendLogMapper imSendLogMapper;
 
-    @Value("${openIM.prefix}")
-    private String openImPrefix;
+//    @Value("${openIM.prefix}")
+//    private String openImPrefix;
     /*@Autowired
     private IFsUserService fsUserService;*/
     @Override
@@ -338,7 +354,7 @@ public class OpenIMServiceImpl implements OpenIMService {
 
             // 过滤出以C开头的用户ID
             List<String> userIds = userIDs.stream()
-                    .filter(uid -> uid.startsWith(IMConfig.PREFIX+"C"))
+                    .filter(uid -> uid.startsWith("C"))
                     .collect(Collectors.toList());
 
             if (CollectionUtil.isNotEmpty(userIds)) {
@@ -404,7 +420,7 @@ public class OpenIMServiceImpl implements OpenIMService {
         OpenImResponseDTO responseDTO = null;
         Map<String, Object> paramMap = new HashMap<>();
         ArrayList<String> userIDs = new ArrayList<>();
-        userIDs.add(IMConfig.PREFIX+"C"+companyUser.getUserId());
+        userIDs.add("C"+companyUser.getUserId());
         paramMap.put("userIDs", userIDs);
 
         String jsonBody = JSONUtil.toJsonStr(paramMap);
@@ -487,7 +503,7 @@ public class OpenIMServiceImpl implements OpenIMService {
         ObjectMapper objectMapper = new ObjectMapper();
         //userId = 61l;
         objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); // 忽略null字段
-        checkAndImportFriendByDianBo(companyUserId,userId.toString(),cropId);
+        checkAndImportFriendByDianBo(companyUserId,userId.toString(),cropId,true);
         OpenImMsgDTO.Content content = new OpenImMsgDTO.Content();
         OpenImMsgDTO.ImData imData = new OpenImMsgDTO.ImData();
         PayloadDTO payload = new PayloadDTO();
@@ -513,8 +529,8 @@ public class OpenIMServiceImpl implements OpenIMService {
         openImMsgDTO.setOfflinePushInfo(offlinePushInfo);
         openImMsgDTO.setContent(content);
 
-        openImMsgDTO.setSendID(IMConfig.PREFIX+"C"+companyUserId);
-        openImMsgDTO.setRecvID(IMConfig.PREFIX+"U"+userId);
+        openImMsgDTO.setSendID("C"+companyUserId);
+        openImMsgDTO.setRecvID("U"+userId);
         openImMsgDTO.setContentType(110);
         openImMsgDTO.setSessionType(1);
         // 输出格式化JSON日志
@@ -530,8 +546,8 @@ public class OpenIMServiceImpl implements OpenIMService {
             OpenImMsgDTO.OfflinePushInfo offlinePushInfo = new OpenImMsgDTO.OfflinePushInfo();
             ObjectMapper objectMapper = new ObjectMapper();
             OpenImMsgDTO openImMsgDTO = new OpenImMsgDTO();
-            if (sendID.startsWith(IMConfig.PREFIX+"C")){
-                CompanyUser company = companyUserMapper.selectCompanyUserByUserId(Long.parseLong(sendID.replace(IMConfig.PREFIX+"C", "")));
+            if (sendID.startsWith("C")){
+                CompanyUser company = companyUserMapper.selectCompanyUserByUserId(Long.parseLong(sendID.replace("C", "")));
                 if (null!=company){
                     offlinePushInfo.setTitle(company.getNickName());
                     openImMsgDTO.setSenderFaceURL(company.getAvatar());
@@ -583,21 +599,22 @@ public class OpenIMServiceImpl implements OpenIMService {
             return null;
         }
     }
+
     @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(IMConfig.PREFIX+"D")){
-                FsDoctor fsDoctor = fsDoctorMapper.selectFsDoctorByDoctorId(Long.parseLong(sendID.replace(IMConfig.PREFIX+"D","")));
+            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(IMConfig.PREFIX+"C")){
-                CompanyUser company = companyUserMapper.selectCompanyUserByUserId(Long.parseLong(sendID.replace(IMConfig.PREFIX+"C", "")));
+            }else if (sendID.startsWith("C")){
+                CompanyUser company = companyUserMapper.selectCompanyUserByUserId(Long.parseLong(sendID.replace("C", "")));
                 if (null!=company&&StringUtils.isNotEmpty(company.getAvatar())){
                     offlinePushInfo.setTitle(company.getNickName());
                     openImMsgDTO.setSenderFaceURL(company.getAvatar());
@@ -684,9 +701,9 @@ public class OpenIMServiceImpl implements OpenIMService {
         try {
             ObjectMapper objectMapper = new ObjectMapper();
             OpenImMsgDTO openImMsgDTO = new OpenImMsgDTO();
-            if (sendID.startsWith(IMConfig.PREFIX+"U")){
+            if (sendID.startsWith("U")){
                 //FsDoctor fsDoctor = fsDoctorMapper.selectFsDoctorByDoctorId(Long.parseLong(sendID.replace("D","")));
-                FsUser fsUser = fsUserMapper.selectFsUserByUserId(Long.parseLong(sendID.replace(IMConfig.PREFIX+"U","")));
+                FsUser fsUser = fsUserMapper.selectFsUserByUserId(Long.parseLong(sendID.replace("U","")));
                 if (null!=fsUser&&StringUtils.isNotEmpty(fsUser.getAvatar())){
                     openImMsgDTO.setSenderFaceURL(fsUser.getAvatar());
                 }
@@ -908,7 +925,7 @@ public class OpenIMServiceImpl implements OpenIMService {
                     String s = "";
                     switch (type){
                         case "2":
-                            s = userId.replaceFirst("^"+openImPrefix+"C", "");
+                            s = userId.replaceFirst("^"+"C", "");
                             CompanyUser companyUser = companyUserMapper.selectCompanyUserByCompanyUserId(Long.parseLong(s));
                             if (null==companyUser){
 //                                return R.error("用户不存在");
@@ -922,7 +939,7 @@ public class OpenIMServiceImpl implements OpenIMService {
                             map.put("faceURL",companyUser.getAvatar());
                             break;
                         case "1":
-                            s = userId.replaceFirst("^"+openImPrefix+"U", "");
+                            s = userId.replaceFirst("^"+"U", "");
                             FsUser fsUser = fsUserMapper.selectFsUserByUserId(Long.parseLong(s));
                             if (null==fsUser){
 //                                return R.error("用户不存在");
@@ -934,7 +951,7 @@ public class OpenIMServiceImpl implements OpenIMService {
                             map.put("faceURL",fsUser.getAvatar());
                             break;
                         case "3":
-                            s = userId.replaceFirst("^"+openImPrefix+"D", "");
+                            s = userId.replaceFirst("^"+"D", "");
                             FsDoctor fsDoctor = fsDoctorMapper.selectFsDoctorByDoctorId(Long.parseLong(s));
                             if (null==fsDoctor){
 //                                return R.error("用户不存在");
@@ -991,13 +1008,13 @@ public class OpenIMServiceImpl implements OpenIMService {
     public void checkAndImportFriend(Long companyUserId,String fsUserId) {
 //        try {
             // 注册账号
-            accountCheck(IMConfig.PREFIX + "C" + companyUserId, "2");
-            accountCheck(IMConfig.PREFIX + "U" + fsUserId, "1");
+            accountCheck( "C" + companyUserId, "2");
+            accountCheck( "U" + fsUserId, "1");
 
             // 导入好友关系
             ArrayList<String> userIds = new ArrayList<>();
-            userIds.add(IMConfig.PREFIX+"U" + fsUserId);
-            importFriend(IMConfig.PREFIX+"C" + companyUserId, userIds);
+            userIds.add("U" + fsUserId);
+            importFriend("C" + companyUserId, userIds);
 //        } catch (Exception e) {
 //            log.error("异步执行IM注册/添加好友失败:", e);
 //        }
@@ -1005,22 +1022,184 @@ public class OpenIMServiceImpl implements OpenIMService {
 
     @Async
     @Override
-    public void checkAndImportFriendByDianBo(Long companyUserId,String fsUserId,String cropId) {
+    public void checkAndImportFriendByDianBo(Long companyUserId,String fsUserId,String cropId, boolean isUpdate) {
         try {
             // 注册账号
-            accountCheck(IMConfig.PREFIX+"C" + companyUserId, "2");
-            accountCheck(IMConfig.PREFIX+"U"+fsUserId, "1");
+            accountCheck("C" + companyUserId, "2");
+            accountCheck("U"+fsUserId, "1");
 
             // 导入好友关系
             ArrayList<String> userIds = new ArrayList<>();
-            userIds.add(IMConfig.PREFIX+"U" + fsUserId);
-            importFriend(IMConfig.PREFIX+"C" + companyUserId, userIds);
-            updateFriendByDianBo(IMConfig.PREFIX+"C" + companyUserId, userIds,cropId);
+            userIds.add("U" + fsUserId);
+            importFriend("C" + companyUserId, userIds);
+            if(isUpdate){
+                updateFriendByDianBo("C" + companyUserId, userIds,cropId);
+            }
         } catch (Exception e) {
             log.error("异步执行IM注册/添加好友失败:", e);
         }
     }
 
+
+    @Override
+    @Transactional
+    public OpenImResponseDTO openIMBatchSendMsg(OpenImBatchMsgDTO openImBatchMsgDTO) {
+        log.info("========================== 批量发送消息 ==========================");
+        String adminToken = getAdminToken();
+        JSONObject jsonObject = new JSONObject(openImBatchMsgDTO);
+        String url = IMConfig.URL+"/msg/batch_send_msg";
+        log.info("请求url: {},\n请求参数:{}", url, jsonObject);
+        long timestamp = System.currentTimeMillis();
+        log.info("请求header,operationID:{},token:{}", timestamp, adminToken);
+
+        String result = HttpRequest.post(url)
+                .header("operationID", timestamp + "")
+                .header("token",adminToken)
+                .body(jsonObject.toString())
+                .execute()
+                .body();
+        log.info("批量发送消息返回内容:\n{}", result);
+        OpenImResponseDTO responseDTO= JSONUtil.toBean(result, OpenImResponseDTO.class);
+        return responseDTO;
+    }
+
+    @Override
+    @Transactional
+    public OpenImResponseDTO batchSendCourse(BatchSendCourseDTO batchSendCourseDTO) throws JsonProcessingException {
+        ObjectMapper objectMapper = new ObjectMapper();
+        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); // 忽略null字段
+
+        //查询需要发送的人
+        List<String> userIds = this.getRecvIds(batchSendCourseDTO);
+        //注册和添加好友
+        for (String userId : userIds) {
+            String uId = userId.substring(1);
+            checkAndImportFriendByDianBo(batchSendCourseDTO.getCompanyUserId(), uId,null,false);
+        }
+
+        //组装消息数据
+        PayloadDTO.Extension extension = new PayloadDTO.Extension();
+        extension.setTitle(batchSendCourseDTO.getTitle());
+        extension.setAppRealLink(batchSendCourseDTO.getUrl());
+        extension.setSendTime(new Date());
+        FsUserCourse fsUserCourse = fsUserCourseMapper.selectFsUserCourseByCourseId(batchSendCourseDTO.getCourseId());
+        extension.setCourseUrl(fsUserCourse != null ? fsUserCourse.getImgUrl() : null);
+
+        PayloadDTO payload = new PayloadDTO();
+        payload.setData("course");
+        payload.setExtension(extension);
+
+        OpenImBatchMsgDTO.ImData imData = new OpenImBatchMsgDTO.ImData();
+        OpenImBatchMsgDTO.Content content = new OpenImBatchMsgDTO.Content();
+        imData.setPayload(payload);
+        String imJson = objectMapper.writeValueAsString(imData);
+        content.setData(imJson);
+
+        OpenImBatchMsgDTO.OfflinePushInfo offlinePushInfo = new OpenImBatchMsgDTO.OfflinePushInfo();
+        offlinePushInfo.setDesc(batchSendCourseDTO.getTitle());
+        CompanyUser companyUser = companyUserMapper.selectCompanyUserById(batchSendCourseDTO.getCompanyUserId());
+        offlinePushInfo.setTitle(companyUser != null ? companyUser.getNickName(): null);
+        offlinePushInfo.setIOSBadgeCount(true);
+        offlinePushInfo.setIOSPushSound("");
+
+        // 设置发送的消息
+        OpenImBatchMsgDTO openImBatchMsgDTO = new OpenImBatchMsgDTO();
+        openImBatchMsgDTO.setSendID("C" + batchSendCourseDTO.getCompanyUserId());
+        openImBatchMsgDTO.setRecvIDs(userIds);
+        openImBatchMsgDTO.setContent(content);
+        openImBatchMsgDTO.setContentType(110);
+        openImBatchMsgDTO.setSessionType(1);
+        openImBatchMsgDTO.setIsOnlineOnly(false);
+        openImBatchMsgDTO.setNotOfflinePush(false);
+        long planSendTimeStamp;
+        if(batchSendCourseDTO.getSendTime() != null){
+            planSendTimeStamp = batchSendCourseDTO.getSendTime().getTime();
+        } else {
+            planSendTimeStamp = System.currentTimeMillis();
+        }
+        openImBatchMsgDTO.setSendTime(planSendTimeStamp);
+        openImBatchMsgDTO.setOfflinePushInfo(offlinePushInfo);
+        openImBatchMsgDTO.setIsSendAll(false);
+
+        log.info("批量发送课程消息: \n{}", JSON.toJSONString(openImBatchMsgDTO));
+        OpenImResponseDTO openImResponseDTO = openIMBatchSendMsg(openImBatchMsgDTO);
+//        openImBatchMsgDTO = null;
+//        content = null;
+
+        //获取发送消息结果,成功后再生成看课记录和发送记录
+        if(openImResponseDTO.getErrCode() == 0 && openImResponseDTO.getData() != null){
+            Object data = openImResponseDTO.getData();
+            OpenImBatchResponseDataDTO openImBatchResponseDataDTO = JSON.parseObject(JSON.toJSONString(data), OpenImBatchResponseDataDTO.class);
+            List<OpenImBatchResponseDataDTO.Results> results = openImBatchResponseDataDTO.getResults();
+
+            // 生成看课记录
+            this.batchInsertWatchLogs(batchSendCourseDTO, results, fsUserCourse);
+
+            // 生成发送记录
+            this.batchInsertSendLogs(openImBatchMsgDTO, results, planSendTimeStamp);
+
+        } else {
+            log.error("发送消息失败,结果:{}", openImResponseDTO);
+            throw new ServiceException("发送消息失败");
+        }
+
+        return openImResponseDTO;
+    }
+
+    private List<String> getRecvIds(BatchSendCourseDTO batchSendCourseDTO) {
+        Map<String,Object> param = new HashMap<>();
+        param.put("tagIds", batchSendCourseDTO.getTagIds());
+        param.put("companyUserId", batchSendCourseDTO.getCompanyUserId());
+        List<FsUserCompanyUser> fsUserCompanyUsers = fsUserCompanyUserMapper.selectFsUserCompanyUserByIds(param);
+        if(fsUserCompanyUsers.isEmpty()){
+            log.error("没有消息接收人,参数:{}", batchSendCourseDTO);
+            throw new ServiceException("没有消息接收人");
+        }
+        return fsUserCompanyUsers.stream().map(v -> "U" + v.getUserId()).collect(Collectors.toList());
+    }
+
+    private void batchInsertSendLogs(OpenImBatchMsgDTO openImBatchMsgDTO, List<OpenImBatchResponseDataDTO.Results> results, long planSendTimeStamp) {
+        List<ImSendLog> list = new LinkedList<>();
+        for (OpenImBatchResponseDataDTO.Results result : results) {
+            ImSendLog imSendLog = new ImSendLog();
+            imSendLog.setSendId(openImBatchMsgDTO.getSendID());
+            imSendLog.setRecvId(result.getRecvID());
+            imSendLog.setSendTitle(openImBatchMsgDTO.getOfflinePushInfo() != null ? openImBatchMsgDTO.getOfflinePushInfo().getTitle() : null);
+            imSendLog.setPlanSendTime(new Date(planSendTimeStamp));
+            imSendLog.setActualSendTime(new Date(result.getSendTime()));
+            if(planSendTimeStamp == result.getSendTime()){
+                imSendLog.setSendType(2);
+            } else {
+                imSendLog.setSendType(1);
+            }
+            imSendLog.setParamJson(JSON.toJSONString(openImBatchMsgDTO));
+            imSendLog.setStatus(0);
+            imSendLog.setResultMessage(JSON.toJSONString(results));
+//            imSendLog.setExceptionInfo();
+            list.add(imSendLog);
+        }
+        imSendLogMapper.insertImSendLogBatch(list);
+    }
+
+    @Transactional
+    public void batchInsertWatchLogs(BatchSendCourseDTO batchSendCourseDTO, List<OpenImBatchResponseDataDTO.Results> results, FsUserCourse fsUserCourse) {
+        List<FsCourseWatchLog> watchLogsInsertList = new LinkedList<>();
+        for (OpenImBatchResponseDataDTO.Results result : results) {
+            FsCourseWatchLog fsCourseWatchLog = new FsCourseWatchLog();
+            BeanUtils.copyProperties(batchSendCourseDTO, fsCourseWatchLog);
+            String userId = result.getRecvID().replaceFirst("^"+"U", "");
+            fsCourseWatchLog.setUserId(Long.parseLong(userId));
+            fsCourseWatchLog.setSendType(1);
+            fsCourseWatchLog.setDuration(0L);
+            fsCourseWatchLog.setCreateTime(new Date());
+            fsCourseWatchLog.setLogType(3);
+            fsCourseWatchLog.setProject(fsUserCourse != null ? fsUserCourse.getProject() : null);
+            watchLogsInsertList.add(fsCourseWatchLog);
+        }
+        courseWatchLogMapper.insertFsCourseWatchLogBatch(watchLogsInsertList);
+        log.info("批量插入FsCourseWatchLog表完成,共插入 {} 条记录", watchLogsInsertList.size());
+    }
+
     /**
      * 修改好友信息
      * @param ownerUserID

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

@@ -167,4 +167,4 @@ openIM:
     secret: openIM123
     userID: imAdmin
     url: https://web.im.fbylive.com/api
-    prefix: fby
+#    prefix: fby

+ 22 - 0
fs-service/src/main/resources/mapper/course/FsUserCompanyUserMapper.xml

@@ -171,5 +171,27 @@
         group by fucu.project_id
     </select>
 
+    <select id="selectFsUserCompanyUserByIds" resultType="FsUserCompanyUser">
+        SELECT
+            *
+        FROM
+            fs_user_company_user
+        <where>
+            <if test="param.tagIds != null and param.tagIds.size() > 0">
+            id IN (
+            SELECT DISTINCT
+            up.user_company_user_id
+            FROM fs_user_project_tag up
+            WHERE up.tag_id IN
+            <foreach collection="param.tagIds" item="tagId" separator="," open="(" close=")">
+                #{tagId}
+            </foreach>
+            )
+            </if>
+            <if test="param.companyUserId != null">
+                and fs_user_company_user.company_user_id = #{param.companyUserId}
+            </if>
+        </where>
+    </select>
 
 </mapper>

+ 138 - 0
fs-service/src/main/resources/mapper/im/ImSendLogMapper.xml

@@ -0,0 +1,138 @@
+<?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.im.mapper.ImSendLogMapper">
+
+    <resultMap type="ImSendLog" id="ImSendLogResult">
+        <result property="logId"    column="log_id"    />
+        <result property="sendId"    column="send_id"    />
+        <result property="recvId"    column="recv_id"    />
+        <result property="sendTitle"    column="send_title"    />
+        <result property="planSendTime"    column="plan_send_time"    />
+        <result property="actualSendTime"    column="actual_send_time"    />
+        <result property="sendType"    column="send_type"    />
+        <result property="paramJson"    column="param_json"    />
+        <result property="status"    column="status"    />
+        <result property="resultMessage"    column="result_message"    />
+        <result property="exceptionInfo"    column="exception_info"    />
+        <result property="createTime"    column="create_time"    />
+    </resultMap>
+
+    <sql id="selectImSendLogVo">
+        select log_id, send_id, recv_id, send_title, plan_send_time, actual_send_time, send_type, param_json, status, result_message, exception_info, create_time from im_send_log
+    </sql>
+
+    <select id="selectImSendLogList" parameterType="ImSendLog" resultMap="ImSendLogResult">
+        <include refid="selectImSendLogVo"/>
+        <where>
+            <if test="sendId != null  and sendId != ''"> and send_id = #{sendId}</if>
+            <if test="recvId != null  and recvId != ''"> and recv_id = #{recvId}</if>
+            <if test="sendTitle != null  and sendTitle != ''"> and send_title = #{sendTitle}</if>
+            <if test="planSendTime != null "> and plan_send_time = #{planSendTime}</if>
+            <if test="actualSendTime != null "> and actual_send_time = #{actualSendTime}</if>
+            <if test="sendType != null "> and send_type = #{sendType}</if>
+            <if test="paramJson != null  and paramJson != ''"> and param_json = #{paramJson}</if>
+            <if test="status != null "> and status = #{status}</if>
+            <if test="resultMessage != null  and resultMessage != ''"> and result_message = #{resultMessage}</if>
+            <if test="exceptionInfo != null  and exceptionInfo != ''"> and exception_info = #{exceptionInfo}</if>
+        </where>
+    </select>
+
+    <select id="selectImSendLogByLogId" parameterType="Long" resultMap="ImSendLogResult">
+        <include refid="selectImSendLogVo"/>
+        where log_id = #{logId}
+    </select>
+
+    <insert id="insertImSendLog" parameterType="ImSendLog" useGeneratedKeys="true" keyProperty="logId">
+        insert into im_send_log
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="sendId != null">send_id,</if>
+            <if test="recvId != null">recv_id,</if>
+            <if test="sendTitle != null">send_title,</if>
+            <if test="planSendTime != null">plan_send_time,</if>
+            <if test="actualSendTime != null">actual_send_time,</if>
+            <if test="sendType != null">send_type,</if>
+            <if test="paramJson != null">param_json,</if>
+            <if test="status != null">status,</if>
+            <if test="resultMessage != null">result_message,</if>
+            <if test="exceptionInfo != null">exception_info,</if>
+            <if test="createTime != null">create_time,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="sendId != null">#{sendId},</if>
+            <if test="recvId != null">#{recvId},</if>
+            <if test="sendTitle != null">#{sendTitle},</if>
+            <if test="planSendTime != null">#{planSendTime},</if>
+            <if test="actualSendTime != null">#{actualSendTime},</if>
+            <if test="sendType != null">#{sendType},</if>
+            <if test="paramJson != null">#{paramJson},</if>
+            <if test="status != null">#{status},</if>
+            <if test="resultMessage != null">#{resultMessage},</if>
+            <if test="exceptionInfo != null">#{exceptionInfo},</if>
+            <if test="createTime != null">#{createTime},</if>
+         </trim>
+    </insert>
+
+    <update id="updateImSendLog" parameterType="ImSendLog">
+        update im_send_log
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="sendId != null">send_id = #{sendId},</if>
+            <if test="recvId != null">recv_id = #{recvId},</if>
+            <if test="sendTitle != null">send_title = #{sendTitle},</if>
+            <if test="planSendTime != null">plan_send_time = #{planSendTime},</if>
+            <if test="actualSendTime != null">actual_send_time = #{actualSendTime},</if>
+            <if test="sendType != null">send_type = #{sendType},</if>
+            <if test="paramJson != null">param_json = #{paramJson},</if>
+            <if test="status != null">status = #{status},</if>
+            <if test="resultMessage != null">result_message = #{resultMessage},</if>
+            <if test="exceptionInfo != null">exception_info = #{exceptionInfo},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+        </trim>
+        where log_id = #{logId}
+    </update>
+
+    <delete id="deleteImSendLogByLogId" parameterType="Long">
+        delete from im_send_log where log_id = #{logId}
+    </delete>
+
+    <delete id="deleteImSendLogByLogIds" parameterType="String">
+        delete from im_send_log where log_id in
+        <foreach item="logId" collection="array" open="(" separator="," close=")">
+            #{logId}
+        </foreach>
+    </delete>
+
+    <insert id="insertImSendLogBatch" parameterType="java.util.List" useGeneratedKeys="true" keyProperty="logId">
+        insert into im_send_log (
+            send_id,
+            recv_id,
+            send_title,
+            plan_send_time,
+            actual_send_time,
+            send_type,
+            param_json,
+            status,
+            result_message,
+            exception_info,
+            create_time
+    )
+        VALUES
+        <foreach collection="sendLogs" item="log" separator=",">
+            (
+            #{log.sendId},
+            #{log.recvId},
+            #{log.sendTitle},
+            #{log.planSendTime},
+            #{log.actualSendTime},
+            #{log.sendType},
+            #{log.paramJson},
+            #{log.status},
+            #{log.resultMessage},
+            #{log.exceptionInfo},
+            #{log.createTime}
+            )
+        </foreach>
+    </insert>
+
+</mapper>

+ 25 - 2
fs-user-app/src/main/java/com/fs/app/controller/course/CourseFsUserController.java

@@ -2,12 +2,15 @@ package com.fs.app.controller.course;
 
 
 
+import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fs.app.annotation.UserOperationLog;
 import com.fs.app.controller.AppBaseController;
 import com.fs.common.annotation.RepeatSubmit;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.ResponseResult;
 import com.fs.app.annotation.Login;
+import com.fs.course.dto.BatchSendCourseDTO;
+import com.fs.course.param.FsCourseLinkCreateParam;
 import com.fs.course.param.FsCourseQuestionAnswerUParam;
 import com.fs.course.param.FsCourseSendRewardUParam;
 import com.fs.course.param.FsUserCourseVideoFinishUParam;
@@ -18,12 +21,13 @@ import com.fs.course.service.*;
 import com.fs.course.vo.FsUserCourseVideoH5VO;
 import com.fs.course.vo.newfs.FsUserCourseVideoLinkDetailsVO;
 import com.fs.his.enums.FsUserOperationEnum;
-import com.fs.his.service.IFsUserService;
-import com.fs.system.service.ISysConfigService;
+import com.fs.im.dto.OpenImResponseDTO;
+import com.fs.im.service.OpenIMService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
@@ -48,6 +52,12 @@ public class CourseFsUserController extends AppBaseController {
     @Autowired
     private IFsCourseQuestionBankService questionBankService;
 
+    @Autowired
+    private OpenIMService openIMService;
+
+    @Autowired
+    private IFsUserCourseService fsUserCourseService;
+
 
 
     @Login
@@ -129,5 +139,18 @@ public class CourseFsUserController extends AppBaseController {
         logger.error("zyp \n【h5看课中途报错】:{}",msg);
     }
 
+    @ApiOperation("会员批量发送课程消息")
+    @PostMapping("/batchSendCourse")
+    public OpenImResponseDTO batchSendCourse(@RequestBody BatchSendCourseDTO batchSendCourseDTO) throws JsonProcessingException {
+        // 生成看课短链
+        FsCourseLinkCreateParam fsCourseLinkCreateParam = new FsCourseLinkCreateParam();
+        BeanUtils.copyProperties(batchSendCourseDTO, fsCourseLinkCreateParam);
+        R courseSortLink = fsUserCourseService.createCourseSortLink(fsCourseLinkCreateParam);
+        String url = courseSortLink.get("url").toString();
+        batchSendCourseDTO.setUrl(url);
+
+        return openIMService.batchSendCourse(batchSendCourseDTO);
+    }
+
 
 }