2 次代码提交 3920c9406e ... 16f975b1c1

作者 SHA1 备注 提交日期
  caoliqin 16f975b1c1 Merge branch 'openIm' of http://1.14.104.71:10880/root/ylrz_his_scrm_java into openIm 1 周之前
  caoliqin 2556faf628 feat:会员im发课消息接口、app看课记录接口 1 周之前
共有 26 个文件被更改,包括 1351 次插入159 次删除
  1. 37 3
      fs-admin/src/main/java/com/fs/his/task/Task.java
  2. 36 3
      fs-admin/src/main/java/com/fs/web/controller/common/CommonController.java
  3. 42 4
      fs-company-app/src/main/java/com/fs/app/controller/FsUserCourseVideoController.java
  4. 1 1
      fs-company/src/main/java/com/fs/user/FsUserAdminController.java
  5. 2 0
      fs-service/src/main/java/com/fs/course/domain/FsCourseWatchLog.java
  6. 7 2
      fs-service/src/main/java/com/fs/course/dto/BatchSendCourseAllDTO.java
  7. 33 11
      fs-service/src/main/java/com/fs/course/dto/BatchSendCourseDTO.java
  8. 31 0
      fs-service/src/main/java/com/fs/course/param/newfs/FsCourseWatchAppParam.java
  9. 2 0
      fs-service/src/main/java/com/fs/course/service/IFsUserCourseService.java
  10. 38 0
      fs-service/src/main/java/com/fs/course/service/impl/FsUserCourseServiceImpl.java
  11. 3 2
      fs-service/src/main/java/com/fs/his/dto/PayloadDTO.java
  12. 72 0
      fs-service/src/main/java/com/fs/im/domain/FsImMsgSendDetail.java
  13. 87 0
      fs-service/src/main/java/com/fs/im/domain/FsImMsgSendLog.java
  14. 5 4
      fs-service/src/main/java/com/fs/im/dto/OpenImBatchMsgDTO.java
  15. 61 0
      fs-service/src/main/java/com/fs/im/mapper/FsImMsgSendDetailMapper.java
  16. 61 0
      fs-service/src/main/java/com/fs/im/mapper/FsImMsgSendLogMapper.java
  17. 61 0
      fs-service/src/main/java/com/fs/im/service/IFsImMsgSendDetailService.java
  18. 61 0
      fs-service/src/main/java/com/fs/im/service/IFsImMsgSendLogService.java
  19. 12 3
      fs-service/src/main/java/com/fs/im/service/OpenIMService.java
  20. 93 0
      fs-service/src/main/java/com/fs/im/service/impl/FsImMsgSendDetailServiceImpl.java
  21. 93 0
      fs-service/src/main/java/com/fs/im/service/impl/FsImMsgSendLogServiceImpl.java
  22. 240 103
      fs-service/src/main/java/com/fs/im/service/impl/OpenIMServiceImpl.java
  23. 4 2
      fs-service/src/main/resources/mapper/course/FsCourseWatchLogMapper.xml
  24. 136 0
      fs-service/src/main/resources/mapper/im/FsImMsgSendDetailMapper.xml
  25. 133 0
      fs-service/src/main/resources/mapper/im/FsImMsgSendLogMapper.xml
  26. 0 21
      fs-user-app/src/main/java/com/fs/app/controller/course/CourseFsUserController.java

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

@@ -1103,7 +1103,7 @@ public class Task {
      * 定时任务-im会员定时发课,每一分钟执行一次
      */
     public void sendOpenImCourse(){
-        String redisKey = "openIm:batchSendMsg";
+        String redisKey = "openIm:batchSendMsg:sendCourse";
         Map<String, BatchSendCourseAllDTO> cacheMap = redisCache.getCacheMap(redisKey);
         if(cacheMap == null || cacheMap.isEmpty()){
             logger.info("=====================会员IM定时发课,不存在对应的redisKey==================");
@@ -1111,7 +1111,7 @@ public class Task {
         }
         List<Map.Entry<String, BatchSendCourseAllDTO>> toSendMap = cacheMap.entrySet().parallelStream().filter((v) -> {
             String[] split = v.getKey().split(":");
-            long timestamp = Long.parseLong(split[3]);
+            long timestamp = Long.parseLong(split[2]);
             return timestamp < System.currentTimeMillis();
         }).collect(Collectors.toList());
 
@@ -1122,7 +1122,7 @@ public class Task {
         for (Map.Entry<String, BatchSendCourseAllDTO> entry : toSendMap) {
             //执行发送消息任务
             BatchSendCourseAllDTO batchSendCourseAllDTO = entry.getValue();
-            openIMService.batchSendMsgTask(batchSendCourseAllDTO.getBatchSendCourseDTO(), batchSendCourseAllDTO.getOpenImBatchMsgDTO(), batchSendCourseAllDTO.getProject(), 1);
+            openIMService.batchSendCourseTask(batchSendCourseAllDTO.getBatchSendCourseDTO(), batchSendCourseAllDTO.getOpenImBatchMsgDTO(), batchSendCourseAllDTO.getProject(), batchSendCourseAllDTO.getImMsgSendDetailList());
 
             // 执行结束,删除
             this.redisTemplate.<String, BatchSendCourseAllDTO>opsForHash().delete(redisKey, entry.getKey());
@@ -1130,4 +1130,38 @@ public class Task {
         }
 
     }
+
+
+    /**
+     * 定时任务-im 会员催课,每一分钟执行一次
+     */
+    public void urgeOpenImCourse(){
+        String redisKey = "openIm:batchSendMsg:urgeCourse";
+        Map<String, BatchSendCourseAllDTO> cacheMap = redisCache.getCacheMap(redisKey);
+        if(cacheMap == null || cacheMap.isEmpty()){
+            logger.info("===================== 会员-IM发消息催课,不存在对应的redisKey==================");
+            return;
+        }
+        List<Map.Entry<String, BatchSendCourseAllDTO>> toSendMap = cacheMap.entrySet().parallelStream().filter((v) -> {
+            String[] split = v.getKey().split(":");
+            long timestamp = Long.parseLong(split[2]);
+            return timestamp < System.currentTimeMillis();
+        }).collect(Collectors.toList());
+
+        if(toSendMap.isEmpty()){
+            logger.info("===================== 会员-IM发消息催课,不存在可发送的消息==================");
+            return;
+        }
+        for (Map.Entry<String, BatchSendCourseAllDTO> entry : toSendMap) {
+            //执行发送消息任务
+            BatchSendCourseAllDTO batchSendCourseAllDTO = entry.getValue();
+            openIMService.batchUrgeCourseTask(batchSendCourseAllDTO.getOpenImBatchMsgDTO(), batchSendCourseAllDTO.getImMsgSendDetailList());
+
+            // 执行结束,删除
+            this.redisTemplate.<String, BatchSendCourseAllDTO>opsForHash().delete(redisKey, entry.getKey());
+
+        }
+    }
+
+
 }

+ 36 - 3
fs-admin/src/main/java/com/fs/web/controller/common/CommonController.java

@@ -242,8 +242,8 @@ public class CommonController
     /**
      * 测试接口
      */
-    @PostMapping("/common/im/testTask")
-    public void testIMTask(){
+    @PostMapping("/common/im/testTask/course")
+    public void testIMCourseTask(){
         String redisKey = "openIm:batchSendMsg";
         Map<String, BatchSendCourseAllDTO> cacheMap = redisCache.getCacheMap(redisKey);
         if(cacheMap == null || cacheMap.isEmpty()){
@@ -263,7 +263,40 @@ public class CommonController
         for (Map.Entry<String, BatchSendCourseAllDTO> entry : toSendMap) {
             //执行发送消息任务
             BatchSendCourseAllDTO batchSendCourseAllDTO = entry.getValue();
-            openIMService.batchSendMsgTask(batchSendCourseAllDTO.getBatchSendCourseDTO(), batchSendCourseAllDTO.getOpenImBatchMsgDTO(), batchSendCourseAllDTO.getProject(), 1);
+            openIMService.batchSendCourseTask(batchSendCourseAllDTO.getBatchSendCourseDTO(), batchSendCourseAllDTO.getOpenImBatchMsgDTO(), batchSendCourseAllDTO.getProject(), batchSendCourseAllDTO.getImMsgSendDetailList());
+
+            // 执行结束,删除
+            this.redisTemplate.<String, BatchSendCourseAllDTO>opsForHash().delete(redisKey, entry.getKey());
+
+        }
+
+    }
+
+    /**
+     * 测试接口
+     */
+    @PostMapping("/common/im/testTask/urge")
+    public void testIMUrgeTask(){
+        String redisKey = "openIm:batchSendMsg:urgeCourse";
+        Map<String, BatchSendCourseAllDTO> cacheMap = redisCache.getCacheMap(redisKey);
+        if(cacheMap == null || cacheMap.isEmpty()){
+            logger.info("===================== 会员-IM发消息催课,不存在对应的redisKey==================");
+            return;
+        }
+        List<Map.Entry<String, BatchSendCourseAllDTO>> toSendMap = cacheMap.entrySet().parallelStream().filter((v) -> {
+            String[] split = v.getKey().split(":");
+            long timestamp = Long.parseLong(split[3]);
+            return timestamp < System.currentTimeMillis();
+        }).collect(Collectors.toList());
+
+        if(toSendMap.isEmpty()){
+            logger.info("===================== 会员-IM发消息催课,不存在可发送的消息==================");
+            return;
+        }
+        for (Map.Entry<String, BatchSendCourseAllDTO> entry : toSendMap) {
+            //执行发送消息任务
+            BatchSendCourseAllDTO batchSendCourseAllDTO = entry.getValue();
+            openIMService.batchUrgeCourseTask(batchSendCourseAllDTO.getOpenImBatchMsgDTO(), batchSendCourseAllDTO.getImMsgSendDetailList());
 
             // 执行结束,删除
             this.redisTemplate.<String, BatchSendCourseAllDTO>opsForHash().delete(redisKey, entry.getKey());

+ 42 - 4
fs-company-app/src/main/java/com/fs/app/controller/FsUserCourseVideoController.java

@@ -1,5 +1,6 @@
 package com.fs.app.controller;
 
+import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fs.app.annotation.Login;
 import com.fs.app.config.ImageStorageConfig;
 import com.fs.common.core.domain.R;
@@ -8,20 +9,23 @@ import com.fs.common.utils.StringUtils;
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.service.ICompanyUserService;
 import com.fs.course.domain.FsUserCoursePeriod;
+import com.fs.course.dto.BatchSendCourseDTO;
 import com.fs.course.param.FsCourseLinkCreateParam;
+import com.fs.course.param.FsCourseWatchLogListParam;
 import com.fs.course.param.FsWatchCourseTimeParam;
 import com.fs.course.param.newfs.FsCourseSortLinkParam;
+import com.fs.course.param.newfs.FsCourseWatchAppParam;
 import com.fs.course.param.newfs.FsUserCourseListParam;
 import com.fs.course.param.newfs.UserCourseVideoPageParam;
-import com.fs.course.service.IFsCourseLinkService;
-import com.fs.course.service.IFsUserCoursePeriodService;
-import com.fs.course.service.IFsUserCourseService;
-import com.fs.course.service.IFsUserCourseVideoService;
+import com.fs.course.service.*;
+import com.fs.course.vo.FsCourseWatchLogListVO;
 import com.fs.course.vo.FsUserCourseParticipationRecordVO;
 import com.fs.course.vo.newfs.FsUserCourseListVO;
 import com.fs.course.vo.newfs.FsUserCourseVideoDetailsVO;
 import com.fs.course.vo.newfs.FsUserCourseVideoPageListVO;
 import com.fs.course.vo.newfs.FsUserVideoListVO;
+import com.fs.im.dto.OpenImResponseDTO;
+import com.fs.im.service.OpenIMService;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
 import io.swagger.annotations.Api;
@@ -62,6 +66,12 @@ public class FsUserCourseVideoController extends AppBaseController {
     @Autowired
     private ICompanyUserService companyUserService;
 
+    @Autowired
+    private IFsCourseWatchLogService fsCourseWatchLogService;
+
+    @Autowired
+    private OpenIMService openIMService;
+
     @Login
     @GetMapping("/pageList")
     @ApiOperation("课程分页列表")
@@ -244,4 +254,32 @@ public class FsUserCourseVideoController extends AppBaseController {
         return ResponseResult.ok(courseLinkService.getGotoWxAppLink(linkStr,appid));
     }
 
+    @ApiOperation("会员批量发送课程消息")
+    @PostMapping("/batchSendCourse")
+    public OpenImResponseDTO batchSendCourse(@RequestBody BatchSendCourseDTO batchSendCourseDTO) throws JsonProcessingException {
+        // 生成看课短链
+        FsCourseLinkCreateParam fsCourseLinkCreateParam = new FsCourseLinkCreateParam();
+        BeanUtils.copyProperties(batchSendCourseDTO, fsCourseLinkCreateParam);
+        R courseSortLink = fsUserCourseService.createAppCourseSortLink(fsCourseLinkCreateParam);
+        String url = courseSortLink.get("url").toString();
+        batchSendCourseDTO.setUrl(url);
+
+        return openIMService.batchSendCourse(batchSendCourseDTO);
+    }
+
+    @Login
+    @ApiOperation("app-看课记录")
+    @GetMapping("/courseWatchLog")
+    public ResponseResult<PageInfo<FsCourseWatchLogListVO>> list(FsCourseWatchAppParam fsCourseWatchAppParam)
+    {
+        FsCourseWatchLogListParam param = new FsCourseWatchLogListParam();
+        BeanUtils.copyProperties(fsCourseWatchAppParam, param);
+        param.setCompanyUserId(Long.parseLong(getUserId()));
+        param.setCompanyId(getCompanyId());
+//        startPage();
+        PageHelper.startPage(fsCourseWatchAppParam.getPageNum (), fsCourseWatchAppParam.getPageSize());
+        List<FsCourseWatchLogListVO> list = fsCourseWatchLogService.selectFsCourseWatchLogListVO(param);
+        PageInfo<FsCourseWatchLogListVO> pageInfo = new PageInfo<>(list);
+        return ResponseResult.ok(pageInfo);
+    }
 }

+ 1 - 1
fs-company/src/main/java/com/fs/user/FsUserAdminController.java

@@ -164,7 +164,7 @@ public class FsUserAdminController extends BaseController {
         // 生成看课短链
         FsCourseLinkCreateParam fsCourseLinkCreateParam = new FsCourseLinkCreateParam();
         BeanUtils.copyProperties(batchSendCourseDTO, fsCourseLinkCreateParam);
-        R courseSortLink = fsUserCourseService.createCourseSortLink(fsCourseLinkCreateParam);
+        R courseSortLink = fsUserCourseService.createAppCourseSortLink(fsCourseLinkCreateParam);
         String url = courseSortLink.get("url").toString();
         batchSendCourseDTO.setUrl(url);
 

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

@@ -88,5 +88,7 @@ public class FsCourseWatchLog extends BaseEntity
     /** 营期id */
     private Long periodId;
 
+    /** im发送消息详情id */
+    private Long imMsgSendDetailId;
 
 }

+ 7 - 2
fs-service/src/main/java/com/fs/course/dto/BatchSendCourseAllDTO.java

@@ -1,11 +1,12 @@
 package com.fs.course.dto;
 
-import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fs.im.domain.FsImMsgSendDetail;
 import com.fs.im.dto.OpenImBatchMsgDTO;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 import lombok.experimental.Accessors;
 
+import java.io.Serializable;
 import java.util.Date;
 import java.util.List;
 
@@ -14,7 +15,7 @@ import java.util.List;
  */
 @Data
 @Accessors(chain = true)
-public class BatchSendCourseAllDTO {
+public class BatchSendCourseAllDTO implements Serializable {
 
     @ApiModelProperty(value = "前端点击发送时的入参")
     private BatchSendCourseDTO batchSendCourseDTO;
@@ -25,4 +26,8 @@ public class BatchSendCourseAllDTO {
     @ApiModelProperty(value = "课程所属项目")
     private Long project;
 
+    @ApiModelProperty(value = "消息发送记录详情")
+    private List<FsImMsgSendDetail> imMsgSendDetailList;
+
+
 }

+ 33 - 11
fs-service/src/main/java/com/fs/course/dto/BatchSendCourseDTO.java

@@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonFormat;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
+import java.io.Serializable;
 import java.util.Date;
 import java.util.List;
 
@@ -11,46 +12,67 @@ import java.util.List;
  * 批量发课-入参
  */
 @Data
-public class BatchSendCourseDTO {
+public class BatchSendCourseDTO implements Serializable {
 
     @ApiModelProperty(value = "用户ids")
     private List<Long> userIds;
 
-    @ApiModelProperty(value = "销售id")
+    @ApiModelProperty(value = "销售id,生成短链需要", required = true)
     private Long companyUserId;
 
-    @ApiModelProperty(value = "公司id")
+    @ApiModelProperty(value = "公司id,生成短链需要", required = true)
     private Long companyId;
 
-    @ApiModelProperty(value = "营期id")
+    @ApiModelProperty(value = "营期id,生成短链需要", required = true)
     private Long periodId;
 
-    @ApiModelProperty(value = "课程id")
+    @ApiModelProperty(value = "课程id,生成短链需要", required = true)
     private Long courseId;
 
-    @ApiModelProperty(value = "视频id")
+    @ApiModelProperty(value = "课程名称", required = true)
+    private String courseName;
+
+    @ApiModelProperty(value = "视频id,生成短链需要", required = true)
     private Long videoId;
 
-    @ApiModelProperty(value = "标签ids")
+    @ApiModelProperty(value = "视频名称", required = true)
+    private String videoName;
+
+    @ApiModelProperty(value = "标签ids,如果没有companyUserId或者userIds则必传")
     private List<Long> tagIds;
 
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     @ApiModelProperty(value = "发送消息时间,定时发送需要传")
     private Date sendTime;
 
-    @ApiModelProperty(value = "发送标题")
+    @ApiModelProperty(value = "发送类型,1-定时;2-实时", required = true)
+    private Integer sendType;
+
+    @ApiModelProperty(value = "发送方式,1-app;2-销售后台", required = true)
+    private Integer sendMode;
+
+    @ApiModelProperty(value = "发课内容", required = true)
     private String title;
 
     @ApiModelProperty(value = "链接有效时长(分钟)")
     private Integer effectiveDuration;
 
-    @ApiModelProperty(value = "营期课程id")
+    @ApiModelProperty(value = "营期课程id,生成短链需要", required = true)
     private Long id;
 
-    @ApiModelProperty(value = "看课短链,不用传")
+    /* 看课短链 */
     private String url;
 
-    @ApiModelProperty(value = "项目id")
+    @ApiModelProperty(value = "项目id,生成短链需要", required = true)
     private Long projectId;
 
+    @ApiModelProperty(value = "是否催课", required = true)
+    private Boolean isUrgeCourse;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @ApiModelProperty(value = "催课时间")
+    private Date urgeTime;
+
+    @ApiModelProperty(value = "催课内容")
+    private String urgeContent;
 }

+ 31 - 0
fs-service/src/main/java/com/fs/course/param/newfs/FsCourseWatchAppParam.java

@@ -0,0 +1,31 @@
+package com.fs.course.param.newfs;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+@ApiModel(description = "app-看课记录入参")
+public class FsCourseWatchAppParam implements Serializable {
+
+    @ApiModelProperty(value = "页码,默认为1", required = true)
+    private Integer pageNum = 1;
+
+    @ApiModelProperty(value = "页大小,默认为10", required = true)
+    private Integer pageSize = 10;
+
+    @ApiModelProperty(value = "类型,1-看课中,2-完课,3-待看课,4-看课中断", required = true)
+    private Integer logType;
+
+    @ApiModelProperty(value = "发送方式:1-个微,2-企微", required = true)
+    private Integer sendType;
+
+    @ApiModelProperty(value = "发送方式:1-个微,2-企微", required = true)
+    private Long companyId;
+
+    @ApiModelProperty(value = "发送方式:1-个微,2-企微", required = true)
+    private Long companyUserId;
+
+}

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

@@ -128,4 +128,6 @@ public interface IFsUserCourseService
     List<FsUserCourseVideoAppletVO> selectFsUserCourseVideoApplet();
 
     List<FsUserCourseVideoAppletVO.FsUserCourseVideo> selectFsUserCourseVideoAppletByCourseId(Long courseId);
+
+    R createAppCourseSortLink(FsCourseLinkCreateParam fsCourseLinkCreateParam);
 }

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

@@ -130,6 +130,10 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
     public static final String shortLink = "/courseH5/pages/course/learning?s=";
 
     private static final String userRealLink = "/pages/user/users/becomeVIP?";
+
+    private static final String appRealLink = "/#/pages_course/videovip?course=";
+    public static final String appShortLink = "/#/pages_course/videovip?s=";
+
     /**
      * 查询课程
      *
@@ -708,6 +712,40 @@ public class FsUserCourseServiceImpl implements IFsUserCourseService
         return fsUserCourseMapper.selectFsUserCourseVideoAppletByCourseId(courseId);
     }
 
+    @Override
+    public R createAppCourseSortLink(FsCourseLinkCreateParam param) {
+        String json = configService.selectConfigByKey("course.config");
+        CourseConfig config = JSONUtil.toBean(json, CourseConfig.class);
+        //短链参数
+        String random = generateRandomString();
+
+        //新增链接表信息
+        FsCourseLink link = new FsCourseLink();
+        BeanUtils.copyProperties(param, link);
+        link.setLinkType(0);
+        link.setIsRoom(0);
+        link.setLink(random);
+
+        FsCourseRealLink courseMap = new FsCourseRealLink();
+        BeanUtils.copyProperties(link, courseMap);
+        courseMap.setProjectId(param.getProjectId());
+        String courseJson = JSON.toJSONString(courseMap);
+        link.setRealLink(appRealLink + courseJson);
+
+        link.setCreateTime(new Date());
+
+        //获取过期时间
+        Calendar calendar = getExpireDay(param, config, link.getCreateTime());
+        link.setUpdateTime(calendar.getTime());
+        int i = fsCourseLinkMapper.insertFsCourseLink(link);
+        if (i > 0){
+            String domainName = getDomainName(param.getCompanyUserId(), config);
+            String sortLink = domainName + appShortLink + link.getLink();
+            return R.ok().put("url", sortLink).put("link", random);
+        }
+        return R.error("生成链接失败!");
+    }
+
 
     private Graphics2D initializeGraphics(BufferedImage combined) {
         Graphics2D graphics = combined.createGraphics();

+ 3 - 2
fs-service/src/main/java/com/fs/his/dto/PayloadDTO.java

@@ -2,16 +2,17 @@ package com.fs.his.dto;
 
 import lombok.Data;
 
+import java.io.Serializable;
 import java.util.Date;
 
 @Data
-public class PayloadDTO {
+public class PayloadDTO implements Serializable {
     private String data;
     private Extension extension;
     private String description;
 
     @Data
-    public static class Extension{
+    public static class Extension implements Serializable{
         private String title;
         private String patientName;
         private String sex;

+ 72 - 0
fs-service/src/main/java/com/fs/im/domain/FsImMsgSendDetail.java

@@ -0,0 +1,72 @@
+package com.fs.im.domain;
+
+import java.util.Date;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+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;
+import lombok.experimental.Accessors;
+
+/**
+ * openim消息记录详情对象 fs_im_msg_send_detail
+ *
+ * @author fs
+ * @date 2025-08-21
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Accessors(chain = true)
+public class FsImMsgSendDetail extends BaseEntity{
+
+    /** 记录详情id */
+    @TableId(type = IdType.AUTO)
+    private Long logDetailId;
+
+    /** 发课记录主表id */
+    @Excel(name = "发课记录主表id")
+    private Long logId;
+
+    /** 销售id(发送人id) */
+    @Excel(name = "销售id", readConverterExp = "发送人id")
+    private Long companyUserId;
+
+    /** 用户id(接收人id) */
+    @Excel(name = "用户id", readConverterExp = "接收人id")
+    private Long userId;
+
+    /** 预计发送时间 */
+    @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 sendStatus;
+
+    /** 执行入参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;
+
+
+}

+ 87 - 0
fs-service/src/main/java/com/fs/im/domain/FsImMsgSendLog.java

@@ -0,0 +1,87 @@
+package com.fs.im.domain;
+
+import java.util.Date;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+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;
+import lombok.experimental.Accessors;
+
+/**
+ * openim消息记录主对象 fs_im_msg_send_log
+ *
+ * @author fs
+ * @date 2025-08-21
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Accessors(chain = true)
+public class FsImMsgSendLog extends BaseEntity{
+
+    /** 发送记录id */
+    @TableId(type = IdType.AUTO)
+    private Long logId;
+
+    /** 销售id(发送人id) */
+    @Excel(name = "销售id", readConverterExp = "发送人id")
+    private Long companyUserId;
+
+    /** 公司id */
+    @Excel(name = "公司id")
+    private Long companyId;
+
+    /** 课程id */
+    @Excel(name = "课程id")
+    private Long courseId;
+
+    /** 课程名称 */
+    @Excel(name = "课程名称")
+    private String courseName;
+
+    /** 视频id */
+    @Excel(name = "视频id")
+    private Long videoId;
+
+    /** 视频标题 */
+    @Excel(name = "视频标题")
+    private String videoName;
+
+    /** 营期id */
+    @Excel(name = "营期id")
+    private Long periodId;
+
+    /** 发送内容 */
+    @Excel(name = "发送内容")
+    private String sendTitle;
+
+    /** 预计发送时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "预计发送时间", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date planSendTime;
+
+    /** 发送类型,1-定时;2-实时 */
+    @Excel(name = "发送类型,1-定时;2-实时")
+    private Integer sendType;
+
+    /** 发送方式,1-app;2-销售后台 */
+    @Excel(name = "发送方式,1-app;2-销售后台")
+    private Integer sendMode;
+
+    /** 发送状态,1-已发送;2-待发送 */
+    @Excel(name = "发送状态,1-已发送;2-待发送")
+    private Integer sendStatus;
+
+    /** 是否催课,1-是;0-否 */
+    @Excel(name = "是否催课,1-是;0-否")
+    private Integer isUrgeCourse;
+
+    /** 消息类型,1-发课;2-催课 */
+    @Excel(name = "消息类型,1-发课;2-催课")
+    private Integer msgType;
+
+
+}

+ 5 - 4
fs-service/src/main/java/com/fs/im/dto/OpenImBatchMsgDTO.java

@@ -4,6 +4,7 @@ import com.fs.his.dto.PayloadDTO;
 import lombok.Data;
 import lombok.experimental.Accessors;
 
+import java.io.Serializable;
 import java.util.List;
 
 /**
@@ -11,7 +12,7 @@ import java.util.List;
  */
 @Data
 @Accessors(chain = true)
-public class OpenImBatchMsgDTO {
+public class OpenImBatchMsgDTO implements Serializable {
 
     private String sendID;
     private List<String> recvIDs;
@@ -30,18 +31,18 @@ public class OpenImBatchMsgDTO {
 
 
     @Data
-    public static class Content {
+    public static class Content implements Serializable {
         private String content;
         private String data; //自定义消息
         private String description;
         private String extension;
     }
     @Data
-    public static class ImData{
+    public static class ImData implements Serializable {
         private PayloadDTO payload;
     }
     @Data
-    public static class OfflinePushInfo {
+    public static class OfflinePushInfo implements Serializable {
         private String title;
         private String desc;
         private String ex;

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

@@ -0,0 +1,61 @@
+package com.fs.im.mapper;
+
+import java.util.List;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.im.domain.FsImMsgSendDetail;
+
+/**
+ * openim消息记录详情Mapper接口
+ * 
+ * @author fs
+ * @date 2025-08-21
+ */
+public interface FsImMsgSendDetailMapper extends BaseMapper<FsImMsgSendDetail>{
+    /**
+     * 查询openim消息记录详情
+     * 
+     * @param logDetailId openim消息记录详情主键
+     * @return openim消息记录详情
+     */
+    FsImMsgSendDetail selectFsImMsgSendDetailByLogDetailId(Long logDetailId);
+
+    /**
+     * 查询openim消息记录详情列表
+     * 
+     * @param fsImMsgSendDetail openim消息记录详情
+     * @return openim消息记录详情集合
+     */
+    List<FsImMsgSendDetail> selectFsImMsgSendDetailList(FsImMsgSendDetail fsImMsgSendDetail);
+
+    /**
+     * 新增openim消息记录详情
+     * 
+     * @param fsImMsgSendDetail openim消息记录详情
+     * @return 结果
+     */
+    int insertFsImMsgSendDetail(FsImMsgSendDetail fsImMsgSendDetail);
+
+    /**
+     * 修改openim消息记录详情
+     * 
+     * @param fsImMsgSendDetail openim消息记录详情
+     * @return 结果
+     */
+    int updateFsImMsgSendDetail(FsImMsgSendDetail fsImMsgSendDetail);
+
+    /**
+     * 删除openim消息记录详情
+     * 
+     * @param logDetailId openim消息记录详情主键
+     * @return 结果
+     */
+    int deleteFsImMsgSendDetailByLogDetailId(Long logDetailId);
+
+    /**
+     * 批量删除openim消息记录详情
+     * 
+     * @param logDetailIds 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteFsImMsgSendDetailByLogDetailIds(Long[] logDetailIds);
+}

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

@@ -0,0 +1,61 @@
+package com.fs.im.mapper;
+
+import java.util.List;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.fs.im.domain.FsImMsgSendLog;
+
+/**
+ * openim消息记录主Mapper接口
+ * 
+ * @author fs
+ * @date 2025-08-21
+ */
+public interface FsImMsgSendLogMapper extends BaseMapper<FsImMsgSendLog>{
+    /**
+     * 查询openim消息记录主
+     * 
+     * @param logId openim消息记录主主键
+     * @return openim消息记录主
+     */
+    FsImMsgSendLog selectFsImMsgSendLogByLogId(Long logId);
+
+    /**
+     * 查询openim消息记录主列表
+     * 
+     * @param fsImMsgSendLog openim消息记录主
+     * @return openim消息记录主集合
+     */
+    List<FsImMsgSendLog> selectFsImMsgSendLogList(FsImMsgSendLog fsImMsgSendLog);
+
+    /**
+     * 新增openim消息记录主
+     * 
+     * @param fsImMsgSendLog openim消息记录主
+     * @return 结果
+     */
+    int insertFsImMsgSendLog(FsImMsgSendLog fsImMsgSendLog);
+
+    /**
+     * 修改openim消息记录主
+     * 
+     * @param fsImMsgSendLog openim消息记录主
+     * @return 结果
+     */
+    int updateFsImMsgSendLog(FsImMsgSendLog fsImMsgSendLog);
+
+    /**
+     * 删除openim消息记录主
+     * 
+     * @param logId openim消息记录主主键
+     * @return 结果
+     */
+    int deleteFsImMsgSendLogByLogId(Long logId);
+
+    /**
+     * 批量删除openim消息记录主
+     * 
+     * @param logIds 需要删除的数据主键集合
+     * @return 结果
+     */
+    int deleteFsImMsgSendLogByLogIds(Long[] logIds);
+}

+ 61 - 0
fs-service/src/main/java/com/fs/im/service/IFsImMsgSendDetailService.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.FsImMsgSendDetail;
+
+/**
+ * openim消息记录详情Service接口
+ * 
+ * @author fs
+ * @date 2025-08-21
+ */
+public interface IFsImMsgSendDetailService extends IService<FsImMsgSendDetail>{
+    /**
+     * 查询openim消息记录详情
+     * 
+     * @param logDetailId openim消息记录详情主键
+     * @return openim消息记录详情
+     */
+    FsImMsgSendDetail selectFsImMsgSendDetailByLogDetailId(Long logDetailId);
+
+    /**
+     * 查询openim消息记录详情列表
+     * 
+     * @param fsImMsgSendDetail openim消息记录详情
+     * @return openim消息记录详情集合
+     */
+    List<FsImMsgSendDetail> selectFsImMsgSendDetailList(FsImMsgSendDetail fsImMsgSendDetail);
+
+    /**
+     * 新增openim消息记录详情
+     * 
+     * @param fsImMsgSendDetail openim消息记录详情
+     * @return 结果
+     */
+    int insertFsImMsgSendDetail(FsImMsgSendDetail fsImMsgSendDetail);
+
+    /**
+     * 修改openim消息记录详情
+     * 
+     * @param fsImMsgSendDetail openim消息记录详情
+     * @return 结果
+     */
+    int updateFsImMsgSendDetail(FsImMsgSendDetail fsImMsgSendDetail);
+
+    /**
+     * 批量删除openim消息记录详情
+     * 
+     * @param logDetailIds 需要删除的openim消息记录详情主键集合
+     * @return 结果
+     */
+    int deleteFsImMsgSendDetailByLogDetailIds(Long[] logDetailIds);
+
+    /**
+     * 删除openim消息记录详情信息
+     * 
+     * @param logDetailId openim消息记录详情主键
+     * @return 结果
+     */
+    int deleteFsImMsgSendDetailByLogDetailId(Long logDetailId);
+}

+ 61 - 0
fs-service/src/main/java/com/fs/im/service/IFsImMsgSendLogService.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.FsImMsgSendLog;
+
+/**
+ * openim消息记录主Service接口
+ * 
+ * @author fs
+ * @date 2025-08-21
+ */
+public interface IFsImMsgSendLogService extends IService<FsImMsgSendLog>{
+    /**
+     * 查询openim消息记录主
+     * 
+     * @param logId openim消息记录主主键
+     * @return openim消息记录主
+     */
+    FsImMsgSendLog selectFsImMsgSendLogByLogId(Long logId);
+
+    /**
+     * 查询openim消息记录主列表
+     * 
+     * @param fsImMsgSendLog openim消息记录主
+     * @return openim消息记录主集合
+     */
+    List<FsImMsgSendLog> selectFsImMsgSendLogList(FsImMsgSendLog fsImMsgSendLog);
+
+    /**
+     * 新增openim消息记录主
+     * 
+     * @param fsImMsgSendLog openim消息记录主
+     * @return 结果
+     */
+    int insertFsImMsgSendLog(FsImMsgSendLog fsImMsgSendLog);
+
+    /**
+     * 修改openim消息记录主
+     * 
+     * @param fsImMsgSendLog openim消息记录主
+     * @return 结果
+     */
+    int updateFsImMsgSendLog(FsImMsgSendLog fsImMsgSendLog);
+
+    /**
+     * 批量删除openim消息记录主
+     * 
+     * @param logIds 需要删除的openim消息记录主主键集合
+     * @return 结果
+     */
+    int deleteFsImMsgSendLogByLogIds(Long[] logIds);
+
+    /**
+     * 删除openim消息记录主信息
+     * 
+     * @param logId openim消息记录主主键
+     * @return 结果
+     */
+    int deleteFsImMsgSendLogByLogId(Long logId);
+}

+ 12 - 3
fs-service/src/main/java/com/fs/im/service/OpenIMService.java

@@ -4,6 +4,7 @@ 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.domain.FsImMsgSendDetail;
 import com.fs.im.dto.OpenImBatchMsgDTO;
 import com.fs.im.dto.OpenImEditConversationDTO;
 import com.fs.im.dto.OpenImMsgDTO;
@@ -52,13 +53,21 @@ public interface OpenIMService {
     OpenImResponseDTO batchSendCourse(BatchSendCourseDTO batchSendCourseDTO) throws JsonProcessingException;
 
     /**
-     * 会批量发课-任务
+     * 会批量发课-任务
      * @param batchSendCourseDTO
      * @param openImBatchMsgDTO
      * @param project
-     * @param sendType
+     * @param imMsgSendDetailList
      * @return
      */
-    OpenImResponseDTO batchSendMsgTask(BatchSendCourseDTO batchSendCourseDTO, OpenImBatchMsgDTO openImBatchMsgDTO, Long project, Integer sendType);
+    OpenImResponseDTO batchSendCourseTask(BatchSendCourseDTO batchSendCourseDTO, OpenImBatchMsgDTO openImBatchMsgDTO, Long project, List<FsImMsgSendDetail> imMsgSendDetailList);
+
+    /**
+     * 会员批量催课-定时任务
+     * @param openImBatchMsgDTO
+     * @param imMsgSendDetailList
+     * @return
+     */
+    OpenImResponseDTO batchUrgeCourseTask(OpenImBatchMsgDTO openImBatchMsgDTO, List<FsImMsgSendDetail> imMsgSendDetailList);
 
 }

+ 93 - 0
fs-service/src/main/java/com/fs/im/service/impl/FsImMsgSendDetailServiceImpl.java

@@ -0,0 +1,93 @@
+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.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.fs.im.mapper.FsImMsgSendDetailMapper;
+import com.fs.im.domain.FsImMsgSendDetail;
+import com.fs.im.service.IFsImMsgSendDetailService;
+
+/**
+ * openim消息记录详情Service业务层处理
+ * 
+ * @author fs
+ * @date 2025-08-21
+ */
+@Service
+public class FsImMsgSendDetailServiceImpl extends ServiceImpl<FsImMsgSendDetailMapper, FsImMsgSendDetail> implements IFsImMsgSendDetailService {
+
+    /**
+     * 查询openim消息记录详情
+     * 
+     * @param logDetailId openim消息记录详情主键
+     * @return openim消息记录详情
+     */
+    @Override
+    public FsImMsgSendDetail selectFsImMsgSendDetailByLogDetailId(Long logDetailId)
+    {
+        return baseMapper.selectFsImMsgSendDetailByLogDetailId(logDetailId);
+    }
+
+    /**
+     * 查询openim消息记录详情列表
+     * 
+     * @param fsImMsgSendDetail openim消息记录详情
+     * @return openim消息记录详情
+     */
+    @Override
+    public List<FsImMsgSendDetail> selectFsImMsgSendDetailList(FsImMsgSendDetail fsImMsgSendDetail)
+    {
+        return baseMapper.selectFsImMsgSendDetailList(fsImMsgSendDetail);
+    }
+
+    /**
+     * 新增openim消息记录详情
+     * 
+     * @param fsImMsgSendDetail openim消息记录详情
+     * @return 结果
+     */
+    @Override
+    public int insertFsImMsgSendDetail(FsImMsgSendDetail fsImMsgSendDetail)
+    {
+        fsImMsgSendDetail.setCreateTime(DateUtils.getNowDate());
+        return baseMapper.insertFsImMsgSendDetail(fsImMsgSendDetail);
+    }
+
+    /**
+     * 修改openim消息记录详情
+     * 
+     * @param fsImMsgSendDetail openim消息记录详情
+     * @return 结果
+     */
+    @Override
+    public int updateFsImMsgSendDetail(FsImMsgSendDetail fsImMsgSendDetail)
+    {
+        return baseMapper.updateFsImMsgSendDetail(fsImMsgSendDetail);
+    }
+
+    /**
+     * 批量删除openim消息记录详情
+     * 
+     * @param logDetailIds 需要删除的openim消息记录详情主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsImMsgSendDetailByLogDetailIds(Long[] logDetailIds)
+    {
+        return baseMapper.deleteFsImMsgSendDetailByLogDetailIds(logDetailIds);
+    }
+
+    /**
+     * 删除openim消息记录详情信息
+     * 
+     * @param logDetailId openim消息记录详情主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsImMsgSendDetailByLogDetailId(Long logDetailId)
+    {
+        return baseMapper.deleteFsImMsgSendDetailByLogDetailId(logDetailId);
+    }
+}

+ 93 - 0
fs-service/src/main/java/com/fs/im/service/impl/FsImMsgSendLogServiceImpl.java

@@ -0,0 +1,93 @@
+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.FsImMsgSendLogMapper;
+import com.fs.im.domain.FsImMsgSendLog;
+import com.fs.im.service.IFsImMsgSendLogService;
+
+/**
+ * openim消息记录主Service业务层处理
+ *
+ * @author fs
+ * @date 2025-08-21
+ */
+@Service
+public class FsImMsgSendLogServiceImpl extends ServiceImpl<FsImMsgSendLogMapper, FsImMsgSendLog> implements IFsImMsgSendLogService {
+
+    /**
+     * 查询openim消息记录主
+     *
+     * @param logId openim消息记录主主键
+     * @return openim消息记录主
+     */
+    @Override
+    public FsImMsgSendLog selectFsImMsgSendLogByLogId(Long logId)
+    {
+        return baseMapper.selectFsImMsgSendLogByLogId(logId);
+    }
+
+    /**
+     * 查询openim消息记录主列表
+     *
+     * @param fsImMsgSendLog openim消息记录主
+     * @return openim消息记录主
+     */
+    @Override
+    public List<FsImMsgSendLog> selectFsImMsgSendLogList(FsImMsgSendLog fsImMsgSendLog)
+    {
+        return baseMapper.selectFsImMsgSendLogList(fsImMsgSendLog);
+    }
+
+    /**
+     * 新增openim消息记录主
+     *
+     * @param fsImMsgSendLog openim消息记录主
+     * @return 结果
+     */
+    @Override
+    public int insertFsImMsgSendLog(FsImMsgSendLog fsImMsgSendLog)
+    {
+        fsImMsgSendLog.setCreateTime(DateUtils.getNowDate());
+        return baseMapper.insertFsImMsgSendLog(fsImMsgSendLog);
+    }
+
+    /**
+     * 修改openim消息记录主
+     *
+     * @param fsImMsgSendLog openim消息记录主
+     * @return 结果
+     */
+    @Override
+    public int updateFsImMsgSendLog(FsImMsgSendLog fsImMsgSendLog)
+    {
+        fsImMsgSendLog.setUpdateTime(DateUtils.getNowDate());
+        return baseMapper.updateFsImMsgSendLog(fsImMsgSendLog);
+    }
+
+    /**
+     * 批量删除openim消息记录主
+     *
+     * @param logIds 需要删除的openim消息记录主主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsImMsgSendLogByLogIds(Long[] logIds)
+    {
+        return baseMapper.deleteFsImMsgSendLogByLogIds(logIds);
+    }
+
+    /**
+     * 删除openim消息记录主信息
+     *
+     * @param logId openim消息记录主主键
+     * @return 结果
+     */
+    @Override
+    public int deleteFsImMsgSendLogByLogId(Long logId)
+    {
+        return baseMapper.deleteFsImMsgSendLogByLogId(logId);
+    }
+}

+ 240 - 103
fs-service/src/main/java/com/fs/im/service/impl/OpenIMServiceImpl.java

@@ -32,8 +32,12 @@ 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.domain.FsImMsgSendDetail;
+import com.fs.im.domain.FsImMsgSendLog;
 import com.fs.im.domain.ImSendLog;
 import com.fs.im.dto.*;
+import com.fs.im.mapper.FsImMsgSendDetailMapper;
+import com.fs.im.mapper.FsImMsgSendLogMapper;
 import com.fs.im.mapper.ImSendLogMapper;
 import com.fs.im.service.OpenIMService;
 import com.fs.im.vo.OpenImMsgCallBackVO;
@@ -87,6 +91,13 @@ public class OpenIMServiceImpl implements OpenIMService {
     private FsCourseWatchLogMapper courseWatchLogMapper;
     @Autowired
     private ImSendLogMapper imSendLogMapper;
+    @Autowired
+    private FsImMsgSendLogMapper fsImMsgSendLogMapper;
+    @Autowired
+    private FsImMsgSendDetailMapper fsImMsgSendDetailMapper;
+    @Autowired
+    private FsImMsgSendDetailServiceImpl fsImMsgSendDetailServiceImpl;
+
 
 //    @Value("${openIM.prefix}")
 //    private String openImPrefix;
@@ -1071,12 +1082,7 @@ public class OpenIMServiceImpl implements OpenIMService {
         objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); // 忽略null字段
 
         //获取需要发送的人
-        List<String> userIds;
-        if(!batchSendCourseDTO.getUserIds().isEmpty()){
-            userIds = batchSendCourseDTO.getUserIds().stream().map(v -> "U" + v).collect(Collectors.toList());
-        } else {
-            userIds = this.getRecvIds(batchSendCourseDTO);
-        }
+        List<String> userIds = this.getRecvIds(batchSendCourseDTO);
 
         //注册和添加好友
         for (String userId : userIds) {
@@ -1084,14 +1090,85 @@ public class OpenIMServiceImpl implements OpenIMService {
             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());
         Long project = fsUserCourse != null ? fsUserCourse.getProject() : null;
-        extension.setCourseUrl(fsUserCourse != null ? fsUserCourse.getImgUrl() : null);
+        long planSendTimeStamp;
+        if(batchSendCourseDTO.getSendType() == 1 && batchSendCourseDTO.getSendTime() != null && batchSendCourseDTO.getSendTime().compareTo(new Date()) > 0){
+            planSendTimeStamp = batchSendCourseDTO.getSendTime().getTime();
+        } else {
+            planSendTimeStamp = System.currentTimeMillis();
+        }
+
+        OpenImBatchMsgDTO openImBatchMsgDTO = makeOpenImBatchMsgDTO(batchSendCourseDTO, fsUserCourse, objectMapper, userIds, planSendTimeStamp, "发课");
+
+        int sendType;
+        if(batchSendCourseDTO.getSendType() == 1 && batchSendCourseDTO.getSendTime() != null && batchSendCourseDTO.getSendTime().compareTo(new Date()) > 0) {
+            sendType = 1; //定时
+        } else {
+            sendType = 2; //实时
+        }
+
+        // 创建消息发送记录
+        List<FsImMsgSendDetail> imMsgSendDetailList = createImMsgSendLog("发课",batchSendCourseDTO, planSendTimeStamp, sendType, userIds);
+
+        OpenImResponseDTO openImResponseDTO = new OpenImResponseDTO();
+        if(sendType == 1) {
+            // 定时发送
+//            for (String userId : userIds) {
+                // 缓存定时发课消息
+                String redisKey = "openIm:batchSendMsg:sendCourse";
+                Map<String, Object> redisMap = new HashMap<>();
+                BatchSendCourseAllDTO batchSendCourseAllDTO = new BatchSendCourseAllDTO();
+                batchSendCourseAllDTO.setBatchSendCourseDTO(batchSendCourseDTO).setOpenImBatchMsgDTO(openImBatchMsgDTO).setProject(project)
+                        .setImMsgSendDetailList(imMsgSendDetailList);
+                redisMap.put(batchSendCourseDTO.getCourseId()+":"+batchSendCourseDTO.getVideoId()+":"+batchSendCourseDTO.getSendTime().getTime()
+                        , batchSendCourseAllDTO);
+                redisCache.setCacheMap(redisKey, redisMap);
+//            }
+            openImResponseDTO.setErrCode(0);
+            openImResponseDTO.setErrMsg("计划发送创建成功,待消息发送");
+        } else {
+            // 实时发送
+            openImResponseDTO = this.batchSendCourseTask(batchSendCourseDTO, openImBatchMsgDTO, project, imMsgSendDetailList);
+            openImResponseDTO.setErrMsg("实时发送成功");
+        }
+
+        //是否催课
+        if(batchSendCourseDTO.getIsUrgeCourse()){
+            // 组装催课消息数据
+            OpenImBatchMsgDTO openImBatchUrgeCourse = makeOpenImBatchMsgDTO(batchSendCourseDTO, fsUserCourse, objectMapper, userIds, planSendTimeStamp, "催课");
+
+            //缓存定时催课消息
+            List<FsImMsgSendDetail> imMsgSendDetailUrgeList = createImMsgSendLog("催课", batchSendCourseDTO, planSendTimeStamp, sendType, userIds);
+//            for (String userId : userIds) {
+                String redisKey = "openIm:batchSendMsg:urgeCourse";
+                Map<String, Object> redisMap = new HashMap<>();
+                BatchSendCourseAllDTO batchSendCourseAllDTO = new BatchSendCourseAllDTO();
+                batchSendCourseAllDTO.setOpenImBatchMsgDTO(openImBatchUrgeCourse)
+                        .setImMsgSendDetailList(imMsgSendDetailUrgeList);
+                redisMap.put(batchSendCourseDTO.getCourseId()+":"+batchSendCourseDTO.getVideoId()+":"+batchSendCourseDTO.getUrgeTime().getTime()
+                        , batchSendCourseAllDTO);
+                redisCache.setCacheMap(redisKey, redisMap);
+//            }
+        }
+        return openImResponseDTO;
+    }
+
+    private OpenImBatchMsgDTO makeOpenImBatchMsgDTO(BatchSendCourseDTO batchSendCourseDTO, FsUserCourse fsUserCourse, ObjectMapper objectMapper, List<String> userIds, long planSendTimeStamp, String logType) throws JsonProcessingException {
+        PayloadDTO.Extension extension = new PayloadDTO.Extension();
+        OpenImBatchMsgDTO openImBatchMsgDTO = new OpenImBatchMsgDTO();
+        if("发课".equals(logType)){
+            extension.setTitle(batchSendCourseDTO.getTitle());
+            extension.setAppRealLink(batchSendCourseDTO.getUrl());
+            extension.setCourseUrl(fsUserCourse != null ? fsUserCourse.getImgUrl() : null);
+            extension.setSendTime(new Date(planSendTimeStamp));
+            openImBatchMsgDTO.setSendTime(planSendTimeStamp);
+        } else {
+            extension.setTitle(batchSendCourseDTO.getUrgeContent());
+            extension.setSendTime(batchSendCourseDTO.getUrgeTime());
+            openImBatchMsgDTO.setSendTime(batchSendCourseDTO.getUrgeTime().getTime());
+        }
 
         PayloadDTO payload = new PayloadDTO();
         payload.setData("course");
@@ -1111,7 +1188,7 @@ public class OpenIMServiceImpl implements OpenIMService {
         offlinePushInfo.setIOSPushSound("");
 
         // 设置发送的消息
-        OpenImBatchMsgDTO openImBatchMsgDTO = new OpenImBatchMsgDTO();
+
         openImBatchMsgDTO.setSendID("C" + batchSendCourseDTO.getCompanyUserId());
         openImBatchMsgDTO.setRecvIDs(userIds);
         openImBatchMsgDTO.setContent(content);
@@ -1119,125 +1196,122 @@ public class OpenIMServiceImpl implements OpenIMService {
         openImBatchMsgDTO.setSessionType(1);
         openImBatchMsgDTO.setIsOnlineOnly(false);
         openImBatchMsgDTO.setNotOfflinePush(false);
-        long planSendTimeStamp;
-        if(batchSendCourseDTO.getSendTime() != null && batchSendCourseDTO.getSendTime().compareTo(new Date()) > 0){
-            planSendTimeStamp = batchSendCourseDTO.getSendTime().getTime();
-        } else {
-            planSendTimeStamp = System.currentTimeMillis();
-        }
-        openImBatchMsgDTO.setSendTime(planSendTimeStamp);
         openImBatchMsgDTO.setOfflinePushInfo(offlinePushInfo);
         openImBatchMsgDTO.setIsSendAll(false);
-
-        OpenImResponseDTO openImResponseDTO = new OpenImResponseDTO();
-        // 保存发送时间到缓存中
-        if(batchSendCourseDTO.getSendTime() != null && batchSendCourseDTO.getSendTime().compareTo(new Date()) > 0) {
-            // 定时发送
-            for (String userId : userIds) {
-                String redisKey = "openIm:batchSendMsg";
-                Map<String, Object> redisMap = new HashMap<>();
-                BatchSendCourseAllDTO batchSendCourseAllDTO = new BatchSendCourseAllDTO();
-                batchSendCourseAllDTO.setBatchSendCourseDTO(batchSendCourseDTO).setOpenImBatchMsgDTO(openImBatchMsgDTO).setProject(project);
-                redisMap.put(batchSendCourseDTO.getCourseId()+":"+batchSendCourseDTO.getVideoId()+":"+userId+":"+batchSendCourseDTO.getSendTime().getTime()
-                        , batchSendCourseAllDTO);
-                redisCache.setCacheMap(redisKey, redisMap);
-            }
-            openImResponseDTO.setErrCode(0);
-            openImResponseDTO.setErrMsg("计划发送创建成功,待消息发送");
-        } else {
-            // 实时发送
-            openImResponseDTO = this.batchSendMsgTask(batchSendCourseDTO, openImBatchMsgDTO, project, 2);
-            openImResponseDTO.setErrMsg("实时发送成功");
-        }
-        return openImResponseDTO;
+        return openImBatchMsgDTO;
     }
 
     @Override
     @Transactional
-    public OpenImResponseDTO batchSendMsgTask(BatchSendCourseDTO batchSendCourseDTO, OpenImBatchMsgDTO openImBatchMsgDTO, Long project, Integer sendType) {
-
+    public OpenImResponseDTO batchSendCourseTask(BatchSendCourseDTO batchSendCourseDTO, OpenImBatchMsgDTO openImBatchMsgDTO, Long project, List<FsImMsgSendDetail> imMsgSendDetailList) {
          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);
+            OpenImBatchResponseDataDTO openImBatchResponseDataDTO = JSON.parseObject(JSON.toJSONString(openImResponseDTO.getData()), OpenImBatchResponseDataDTO.class);
             List<OpenImBatchResponseDataDTO.Results> results = openImBatchResponseDataDTO.getResults();
 
-            // 生成发送记录
-            this.batchInsertSendLogs(openImBatchMsgDTO, openImResponseDTO, openImBatchResponseDataDTO, sendType);
+            // 发送成功,生成看课记录
+            this.batchInsertWatchLogs(batchSendCourseDTO, results, project, imMsgSendDetailList);
+        }
 
-            // 生成看课记录
-            this.batchInsertWatchLogs(batchSendCourseDTO, results, project);
+        // 修改发送记录
+        this.updateImMsgSendLog(openImBatchMsgDTO, imMsgSendDetailList, openImResponseDTO, "发课");
 
-        } else {
-            // 生成发送记录
-            this.batchInsertSendLogs(openImBatchMsgDTO, openImResponseDTO, null, sendType);
-            log.error("发送消息失败,结果:{}", openImResponseDTO);
-            throw new ServiceException("发送消息失败");
-        }
+        return openImResponseDTO;
+    }
+
+    @Override
+    @Transactional
+    public OpenImResponseDTO batchUrgeCourseTask(OpenImBatchMsgDTO openImBatchMsgDTO, List<FsImMsgSendDetail> imMsgSendDetailList) {
+        log.info("批量催课消息: \n{}", JSON.toJSONString(openImBatchMsgDTO));
+        OpenImResponseDTO openImResponseDTO = openIMBatchSendMsg(openImBatchMsgDTO);
 
+        // 修改发送记录
+        this.updateImMsgSendLog(openImBatchMsgDTO, imMsgSendDetailList, openImResponseDTO, "催课");
         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("没有消息接收人");
+        List<String> userIds;
+        if(!batchSendCourseDTO.getUserIds().isEmpty()) {
+            userIds = batchSendCourseDTO.getUserIds().stream().map(v -> "U" + v).collect(Collectors.toList());
+        } else{
+            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("没有消息接收人");
+            }
+            userIds = fsUserCompanyUsers.stream().map(v -> "U" + v.getUserId()).collect(Collectors.toList());
         }
-        return fsUserCompanyUsers.stream().map(v -> "U" + v.getUserId()).collect(Collectors.toList());
+        return userIds;
     }
 
-    private void batchInsertSendLogs(OpenImBatchMsgDTO openImBatchMsgDTO, OpenImResponseDTO openImResponseDTO, OpenImBatchResponseDataDTO openImBatchResponseDataDTO, Integer sendType) {
-        List<ImSendLog> list = new LinkedList<>();
-        if(openImResponseDTO.getErrCode() != 0){
-            ImSendLog imSendLog = createImsendLog(openImBatchMsgDTO, openImBatchMsgDTO.getSendTime());
-            imSendLog.setStatus(1);
-            imSendLog.setResultMessage(JSON.toJSONString(openImResponseDTO.getErrMsg()));
-            imSendLog.setExceptionInfo(JSON.toJSONString(openImResponseDTO.getErrDlt()));
-            list.add(imSendLog);
-        } else {
-            if(openImBatchResponseDataDTO.getFailedUserIDs() != null) {
-                for (String failedUserID : openImBatchResponseDataDTO.getFailedUserIDs()) {
-                    ImSendLog imSendLog = createImsendLog(openImBatchMsgDTO, openImBatchMsgDTO.getSendTime());
-                    imSendLog.setRecvId(failedUserID);
-                    imSendLog.setStatus(1);
-                    imSendLog.setResultMessage(JSON.toJSONString(openImBatchResponseDataDTO));
-                    list.add(imSendLog);
-                }
+
+    /**
+     * 创建消息发送记录
+     */
+    private List<FsImMsgSendDetail> createImMsgSendLog(String logType, BatchSendCourseDTO batchSendCourseDTO, long planSendTimeStamp, int sendType, List<String> userIds) {
+        // 记录发送消息记录主表
+        FsImMsgSendLog fsImMsgSendLog = new FsImMsgSendLog();
+        BeanUtils.copyProperties(batchSendCourseDTO, fsImMsgSendLog);
+        fsImMsgSendLog.setCreateBy(batchSendCourseDTO.getCompanyUserId().toString());
+        fsImMsgSendLog.setCreateTime(new Date());
+        if("发课".equals(logType)){
+            // 发课
+            fsImMsgSendLog.setPlanSendTime(new Date(planSendTimeStamp));
+            fsImMsgSendLog.setSendTitle(batchSendCourseDTO.getTitle()).setMsgType(1);
+            if(sendType == 1){
+                fsImMsgSendLog.setSendStatus(2);
+            } else {
+                fsImMsgSendLog.setSendStatus(1);
             }
-            for (OpenImBatchResponseDataDTO.Results result : openImBatchResponseDataDTO.getResults()) {
-                ImSendLog imSendLog = createImsendLog(openImBatchMsgDTO, openImBatchMsgDTO.getSendTime());
-                imSendLog.setRecvId(result.getRecvID());
-                imSendLog.setActualSendTime(new Date(result.getSendTime()));
-                imSendLog.setSendType(sendType);
-                imSendLog.setStatus(0);
-                imSendLog.setResultMessage(JSON.toJSONString(openImBatchResponseDataDTO));
-                list.add(imSendLog);
+        } else {
+            //催课
+            fsImMsgSendLog.setPlanSendTime(batchSendCourseDTO.getUrgeTime());
+            fsImMsgSendLog.setSendTitle(batchSendCourseDTO.getUrgeContent()).setMsgType(2).setIsUrgeCourse(1);
+            if(batchSendCourseDTO.getUrgeTime() != null && batchSendCourseDTO.getUrgeTime().compareTo(new Date()) > 0){
+                fsImMsgSendLog.setSendStatus(2);
+            } else{
+                fsImMsgSendLog.setSendStatus(1);
             }
         }
+        fsImMsgSendLogMapper.insert(fsImMsgSendLog);
 
-        imSendLogMapper.insertImSendLogBatch(list);
-    }
-
-    private static ImSendLog createImsendLog(OpenImBatchMsgDTO openImBatchMsgDTO, long planSendTimeStamp) {
-        ImSendLog imSendLog = new ImSendLog();
-        imSendLog.setSendId(openImBatchMsgDTO.getSendID());
-        imSendLog.setSendTitle(openImBatchMsgDTO.getOfflinePushInfo() != null ? openImBatchMsgDTO.getOfflinePushInfo().getTitle() : null);
-        imSendLog.setPlanSendTime(new Date(planSendTimeStamp));
-        imSendLog.setParamJson(JSON.toJSONString(openImBatchMsgDTO));
-
-        return imSendLog;
+        // 记录发送消息记录详情
+        List<FsImMsgSendDetail> list = new LinkedList<>();
+        for (String userId : userIds) {
+            FsImMsgSendDetail fsImMsgSendDetail = new FsImMsgSendDetail();
+            BeanUtils.copyProperties(batchSendCourseDTO, fsImMsgSendDetail);
+            fsImMsgSendDetail.setLogId(fsImMsgSendLog.getLogId())
+                    .setCreateTime(new Date());
+            Long id = Long.parseLong(userId.replaceFirst("^" + "U", ""));
+            fsImMsgSendDetail.setUserId(id);
+            if("发课".equals(logType)) {
+                fsImMsgSendDetail.setPlanSendTime(new Date(planSendTimeStamp));
+                if (sendType == 1) {
+                    fsImMsgSendDetail.setSendStatus(2);
+                } else {
+                    fsImMsgSendDetail.setSendStatus(1);
+                }
+            } else {
+                fsImMsgSendDetail.setPlanSendTime(batchSendCourseDTO.getUrgeTime());
+                fsImMsgSendDetail.setSendStatus(2);
+            }
+            list.add(fsImMsgSendDetail);
+        }
+        fsImMsgSendDetailServiceImpl.saveBatch(list);
+        return list;
     }
 
-    public void batchInsertWatchLogs(BatchSendCourseDTO batchSendCourseDTO, List<OpenImBatchResponseDataDTO.Results> results, Long project) {
+    public void batchInsertWatchLogs(BatchSendCourseDTO batchSendCourseDTO, List<OpenImBatchResponseDataDTO.Results> results, Long project, List<FsImMsgSendDetail> imMsgSendDetailList) {
+        Map<Long, FsImMsgSendDetail> map = imMsgSendDetailList.stream().collect(Collectors.toMap(FsImMsgSendDetail::getUserId, v -> v));
         List<FsCourseWatchLog> watchLogsInsertList = new LinkedList<>();
         for (OpenImBatchResponseDataDTO.Results result : results) {
             FsCourseWatchLog fsCourseWatchLog = new FsCourseWatchLog();
@@ -1246,15 +1320,78 @@ public class OpenIMServiceImpl implements OpenIMService {
             fsCourseWatchLog.setUserId(Long.parseLong(userId));
             fsCourseWatchLog.setSendType(1);
             fsCourseWatchLog.setDuration(0L);
-            fsCourseWatchLog.setCreateTime(new Date());
+            fsCourseWatchLog.setCreateTime(new Date(result.getSendTime()));
             fsCourseWatchLog.setLogType(3);
             fsCourseWatchLog.setProject(project);
+            fsCourseWatchLog.setImMsgSendDetailId(map.get(Long.parseLong(userId)).getLogDetailId());
             watchLogsInsertList.add(fsCourseWatchLog);
         }
         courseWatchLogMapper.insertFsCourseWatchLogBatch(watchLogsInsertList);
         log.info("批量插入FsCourseWatchLog表完成,共插入 {} 条记录", watchLogsInsertList.size());
     }
 
+
+    private void updateImMsgSendLog(OpenImBatchMsgDTO openImBatchMsgDTO, List<FsImMsgSendDetail> imMsgSendDetailList, OpenImResponseDTO openImResponseDTO, String logType) {
+        List<FsImMsgSendDetail> updateList = new ArrayList<>();
+        if(openImResponseDTO.getErrCode() != 0){
+            // 所有都发送失败
+            List<FsImMsgSendDetail> allFailedList = imMsgSendDetailList.stream().map(v -> {
+                v.setSendStatus(1).setParamJson(JSON.toJSONString(openImBatchMsgDTO))
+                        .setStatus(1)
+                        .setResultMessage(JSON.toJSONString(openImResponseDTO))
+                        .setExceptionInfo(openImResponseDTO.getErrDlt())
+                        .setUpdateTime(new Date());
+                return v;
+            }).collect(Collectors.toList());
+            updateList.addAll(allFailedList);
+
+            log.error("发送消息失败,结果:{}", openImResponseDTO);
+            throw new ServiceException("发送消息失败");
+        } else {
+            OpenImBatchResponseDataDTO openImBatchResponseDataDTO = JSON.parseObject(JSON.toJSONString(openImResponseDTO.getData()), OpenImBatchResponseDataDTO.class);
+            if(openImBatchResponseDataDTO != null && openImBatchResponseDataDTO.getFailedUserIDs() != null) {
+                //发送失败的
+                String[] failedUserIds = openImBatchResponseDataDTO.getFailedUserIDs();
+                List<String> failedUserIdList = Arrays.asList(failedUserIds);
+                List<FsImMsgSendDetail> failedList = imMsgSendDetailList.stream().filter(v -> failedUserIdList.contains("U" + v.getUserId())).map(v -> {
+                    v.setSendStatus(1).setParamJson(JSON.toJSONString(openImBatchMsgDTO))
+                            .setStatus(1)
+                            .setResultMessage(JSON.toJSONString(openImBatchResponseDataDTO))
+                            .setExceptionInfo(JSON.toJSONString(openImResponseDTO.getErrDlt()))
+                            .setUpdateTime(new Date());
+                    return v;
+                }).collect(Collectors.toList());
+                updateList.addAll(failedList);
+            }
+
+            // 发送成功的
+            if(openImBatchResponseDataDTO != null){
+                List<OpenImBatchResponseDataDTO.Results> results = openImBatchResponseDataDTO.getResults();
+                Map<String, OpenImBatchResponseDataDTO.Results> resultsMap = results.stream().collect(Collectors.toMap(OpenImBatchResponseDataDTO.Results::getRecvID, v -> v));
+                List<String> actualRecvIdList = openImBatchResponseDataDTO.getResults().stream().map(OpenImBatchResponseDataDTO.Results::getRecvID).collect(Collectors.toList());
+                List<FsImMsgSendDetail> successList = imMsgSendDetailList.stream().filter(v -> actualRecvIdList.contains("U" + v.getUserId())).map(v -> {
+                    v.setActualSendTime(new Date(resultsMap.get("U" + v.getUserId()).getSendTime()))
+                            .setSendStatus(1).setParamJson(JSON.toJSONString(openImBatchMsgDTO))
+                            .setStatus(0)
+                            .setResultMessage(JSON.toJSONString(openImBatchResponseDataDTO))
+                            .setUpdateTime(new Date());
+                    return v;
+                }).collect(Collectors.toList());
+                updateList.addAll(successList);
+            }
+        }
+        fsImMsgSendDetailServiceImpl.updateBatchById(updateList);
+
+        // 更新记录主表
+        if("发课".equals(logType)) {
+            FsImMsgSendLog fsImMsgSendLog = new FsImMsgSendLog();
+            fsImMsgSendLog.setLogId(imMsgSendDetailList.get(0).getLogId());
+            fsImMsgSendLog.setSendStatus(1);
+            fsImMsgSendLog.setUpdateTime(new Date());
+            fsImMsgSendLogMapper.updateById(fsImMsgSendLog);
+        }
+    }
+
     /**
      * 修改好友信息
      * @param ownerUserID

+ 4 - 2
fs-service/src/main/resources/mapper/course/FsCourseWatchLogMapper.xml

@@ -317,7 +317,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         sop_id,
         camp_period_time,
         project,
-        period_id
+        period_id,
+        im_msg_send_detail_id
         )
         VALUES
         <foreach collection="watchLogs" item="log" separator=",">
@@ -338,7 +339,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             #{log.sopId},
             #{log.campPeriodTime},
             #{log.project},
-            #{log.periodId}
+            #{log.periodId},
+            #{log.imMsgSendDetailId}
             )
         </foreach>
         ON DUPLICATE KEY UPDATE

+ 136 - 0
fs-service/src/main/resources/mapper/im/FsImMsgSendDetailMapper.xml

@@ -0,0 +1,136 @@
+<?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.FsImMsgSendDetailMapper">
+
+    <resultMap type="FsImMsgSendDetail" id="FsImMsgSendDetailResult">
+        <result property="logDetailId"    column="log_detail_id"    />
+        <result property="logId"    column="log_id"    />
+        <result property="companyUserId"    column="company_user_id"    />
+        <result property="userId"    column="user_id"    />
+        <result property="planSendTime"    column="plan_send_time"    />
+        <result property="actualSendTime"    column="actual_send_time"    />
+        <result property="sendStatus"    column="send_status"    />
+        <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="selectFsImMsgSendDetailVo">
+        select log_detail_id, log_id, company_user_id, user_id, plan_send_time, actual_send_time, send_status, param_json, status, result_message, exception_info, create_time from fs_im_msg_send_detail
+    </sql>
+
+    <select id="selectFsImMsgSendDetailList" parameterType="FsImMsgSendDetail" resultMap="FsImMsgSendDetailResult">
+        <include refid="selectFsImMsgSendDetailVo"/>
+        <where>
+            <if test="logId != null "> and log_id = #{logId}</if>
+            <if test="companyUserId != null "> and company_user_id = #{companyUserId}</if>
+            <if test="userId != null "> and user_id = #{userId}</if>
+            <if test="planSendTime != null "> and plan_send_time = #{planSendTime}</if>
+            <if test="actualSendTime != null "> and actual_send_time = #{actualSendTime}</if>
+            <if test="sendStatus != null "> and send_status = #{sendStatus}</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="selectFsImMsgSendDetailByLogDetailId" parameterType="Long" resultMap="FsImMsgSendDetailResult">
+        <include refid="selectFsImMsgSendDetailVo"/>
+        where log_detail_id = #{logDetailId}
+    </select>
+
+    <insert id="insertFsImMsgSendDetail" parameterType="FsImMsgSendDetail" useGeneratedKeys="true" keyProperty="logDetailId">
+        insert into fs_im_msg_send_detail
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="logId != null">log_id,</if>
+            <if test="companyUserId != null">company_user_id,</if>
+            <if test="userId != null">user_id,</if>
+            <if test="planSendTime != null">plan_send_time,</if>
+            <if test="actualSendTime != null">actual_send_time,</if>
+            <if test="sendStatus != null">send_status,</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="logId != null">#{logId},</if>
+            <if test="companyUserId != null">#{companyUserId},</if>
+            <if test="userId != null">#{userId},</if>
+            <if test="planSendTime != null">#{planSendTime},</if>
+            <if test="actualSendTime != null">#{actualSendTime},</if>
+            <if test="sendStatus != null">#{sendStatus},</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="updateFsImMsgSendDetail" parameterType="FsImMsgSendDetail">
+        update fs_im_msg_send_detail
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="logId != null">log_id = #{logId},</if>
+            <if test="companyUserId != null">company_user_id = #{companyUserId},</if>
+            <if test="userId != null">user_id = #{userId},</if>
+            <if test="planSendTime != null">plan_send_time = #{planSendTime},</if>
+            <if test="actualSendTime != null">actual_send_time = #{actualSendTime},</if>
+            <if test="sendStatus != null">send_status = #{sendStatus},</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_detail_id = #{logDetailId}
+    </update>
+
+    <delete id="deleteFsImMsgSendDetailByLogDetailId" parameterType="Long">
+        delete from fs_im_msg_send_detail where log_detail_id = #{logDetailId}
+    </delete>
+
+    <delete id="deleteFsImMsgSendDetailByLogDetailIds" parameterType="String">
+        delete from fs_im_msg_send_detail where log_detail_id in
+        <foreach item="logDetailId" collection="array" open="(" separator="," close=")">
+            #{logDetailId}
+        </foreach>
+    </delete>
+
+<!--    <insert id="insertFsImMsgSendDetailBatch" parameterType="java.util.List" useGeneratedKeys="true" keyProperty="logDetailId">-->
+<!--        insert into fs_im_msg_send_detail (-->
+<!--            log_id,-->
+<!--            company_user_id,-->
+<!--            user_id,-->
+<!--            plan_send_time,-->
+<!--            actual_send_time,-->
+<!--            send_status,-->
+<!--            param_json,-->
+<!--            status,-->
+<!--            result_message,-->
+<!--            exception_info,-->
+<!--            create_time-->
+<!--        )-->
+<!--        VALUES-->
+<!--        <foreach collection="sendDetails" item="log" separator=",">-->
+<!--            #{logId},-->
+<!--            #{companyUserId},-->
+<!--            #{userId},-->
+<!--            #{planSendTime},-->
+<!--            #{actualSendTime},-->
+<!--            #{sendStatus},-->
+<!--            #{paramJson},-->
+<!--            #{status},-->
+<!--            #{resultMessage},-->
+<!--            #{exceptionInfo},-->
+<!--            #{createTime},-->
+<!--        </foreach>-->
+<!--    </insert>-->
+
+</mapper>

+ 133 - 0
fs-service/src/main/resources/mapper/im/FsImMsgSendLogMapper.xml

@@ -0,0 +1,133 @@
+<?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.FsImMsgSendLogMapper">
+    
+    <resultMap type="FsImMsgSendLog" id="FsImMsgSendLogResult">
+        <result property="logId"    column="log_id"    />
+        <result property="companyUserId"    column="company_user_id"    />
+        <result property="companyId"    column="company_id"    />
+        <result property="courseId"    column="course_id"    />
+        <result property="courseName"    column="course_name"    />
+        <result property="videoId"    column="video_id"    />
+        <result property="videoName"    column="video_name"    />
+        <result property="periodId"    column="period_id"    />
+        <result property="sendTitle"    column="send_title"    />
+        <result property="planSendTime"    column="plan_send_time"    />
+        <result property="sendType"    column="send_type"    />
+        <result property="sendMode"    column="send_mode"    />
+        <result property="sendStatus"    column="send_status"    />
+        <result property="isUrgeCourse"    column="is_urge_course"    />
+        <result property="msgType"    column="msg_type"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="createBy"    column="create_by"    />
+        <result property="updateTime"    column="update_time"    />
+    </resultMap>
+
+    <sql id="selectFsImMsgSendLogVo">
+        select log_id, company_user_id, company_id, course_id, course_name, video_id, video_name, period_id, send_title, plan_send_time, send_type, send_mode, send_status, is_urge_course, msg_type, create_time, create_by, update_time from fs_im_msg_send_log
+    </sql>
+
+    <select id="selectFsImMsgSendLogList" parameterType="FsImMsgSendLog" resultMap="FsImMsgSendLogResult">
+        <include refid="selectFsImMsgSendLogVo"/>
+        <where>  
+            <if test="companyUserId != null "> and company_user_id = #{companyUserId}</if>
+            <if test="companyId != null "> and company_id = #{companyId}</if>
+            <if test="courseId != null "> and course_id = #{courseId}</if>
+            <if test="courseName != null  and courseName != ''"> and course_name like concat('%', #{courseName}, '%')</if>
+            <if test="videoId != null "> and video_id = #{videoId}</if>
+            <if test="videoName != null  and videoName != ''"> and video_name like concat('%', #{videoName}, '%')</if>
+            <if test="periodId != null "> and period_id = #{periodId}</if>
+            <if test="sendTitle != null  and sendTitle != ''"> and send_title = #{sendTitle}</if>
+            <if test="planSendTime != null "> and plan_send_time = #{planSendTime}</if>
+            <if test="sendType != null "> and send_type = #{sendType}</if>
+            <if test="sendMode != null "> and send_mode = #{sendMode}</if>
+            <if test="sendStatus != null "> and send_status = #{sendStatus}</if>
+            <if test="isUrgeCourse != null "> and is_urge_course = #{isUrgeCourse}</if>
+            <if test="msgType != null "> and msg_type = #{msgType}</if>
+        </where>
+    </select>
+    
+    <select id="selectFsImMsgSendLogByLogId" parameterType="Long" resultMap="FsImMsgSendLogResult">
+        <include refid="selectFsImMsgSendLogVo"/>
+        where log_id = #{logId}
+    </select>
+        
+    <insert id="insertFsImMsgSendLog" parameterType="FsImMsgSendLog" useGeneratedKeys="true" keyProperty="logId">
+        insert into fs_im_msg_send_log
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="companyUserId != null">company_user_id,</if>
+            <if test="companyId != null">company_id,</if>
+            <if test="courseId != null">course_id,</if>
+            <if test="courseName != null">course_name,</if>
+            <if test="videoId != null">video_id,</if>
+            <if test="videoName != null">video_name,</if>
+            <if test="periodId != null">period_id,</if>
+            <if test="sendTitle != null">send_title,</if>
+            <if test="planSendTime != null">plan_send_time,</if>
+            <if test="sendType != null">send_type,</if>
+            <if test="sendMode != null">send_mode,</if>
+            <if test="sendStatus != null">send_status,</if>
+            <if test="isUrgeCourse != null">is_urge_course,</if>
+            <if test="msgType != null">msg_type,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="createBy != null">create_by,</if>
+            <if test="updateTime != null">update_time,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="companyUserId != null">#{companyUserId},</if>
+            <if test="companyId != null">#{companyId},</if>
+            <if test="courseId != null">#{courseId},</if>
+            <if test="courseName != null">#{courseName},</if>
+            <if test="videoId != null">#{videoId},</if>
+            <if test="videoName != null">#{videoName},</if>
+            <if test="periodId != null">#{periodId},</if>
+            <if test="sendTitle != null">#{sendTitle},</if>
+            <if test="planSendTime != null">#{planSendTime},</if>
+            <if test="sendType != null">#{sendType},</if>
+            <if test="sendMode != null">#{sendMode},</if>
+            <if test="sendStatus != null">#{sendStatus},</if>
+            <if test="isUrgeCourse != null">#{isUrgeCourse},</if>
+            <if test="msgType != null">#{msgType},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="createBy != null">#{createBy},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+         </trim>
+    </insert>
+
+    <update id="updateFsImMsgSendLog" parameterType="FsImMsgSendLog">
+        update fs_im_msg_send_log
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="companyUserId != null">company_user_id = #{companyUserId},</if>
+            <if test="companyId != null">company_id = #{companyId},</if>
+            <if test="courseId != null">course_id = #{courseId},</if>
+            <if test="courseName != null">course_name = #{courseName},</if>
+            <if test="videoId != null">video_id = #{videoId},</if>
+            <if test="videoName != null">video_name = #{videoName},</if>
+            <if test="periodId != null">period_id = #{periodId},</if>
+            <if test="sendTitle != null">send_title = #{sendTitle},</if>
+            <if test="planSendTime != null">plan_send_time = #{planSendTime},</if>
+            <if test="sendType != null">send_type = #{sendType},</if>
+            <if test="sendMode != null">send_mode = #{sendMode},</if>
+            <if test="sendStatus != null">send_status = #{sendStatus},</if>
+            <if test="isUrgeCourse != null">is_urge_course = #{isUrgeCourse},</if>
+            <if test="msgType != null">msg_type = #{msgType},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="createBy != null">create_by = #{createBy},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+        </trim>
+        where log_id = #{logId}
+    </update>
+
+    <delete id="deleteFsImMsgSendLogByLogId" parameterType="Long">
+        delete from fs_im_msg_send_log where log_id = #{logId}
+    </delete>
+
+    <delete id="deleteFsImMsgSendLogByLogIds" parameterType="String">
+        delete from fs_im_msg_send_log where log_id in 
+        <foreach item="logId" collection="array" open="(" separator="," close=")">
+            #{logId}
+        </foreach>
+    </delete>
+</mapper>

+ 0 - 21
fs-user-app/src/main/java/com/fs/app/controller/course/CourseFsUserController.java

@@ -53,13 +53,6 @@ public class CourseFsUserController extends AppBaseController {
     @Autowired
     private IFsCourseQuestionBankService questionBankService;
 
-    @Autowired
-    private OpenIMService openIMService;
-
-    @Autowired
-    private IFsUserCourseService fsUserCourseService;
-
-
 
     @Login
     @ApiOperation("判断是否添加客服(是否关联销售)")
@@ -140,18 +133,4 @@ 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);
-    }
-
-
 }