Browse Source

增加直播课自动排课功能
增加同步融云用户状态变更功能
增加房间数据查询测试方法

hgw 3 years ago
parent
commit
0aae06b8dc
16 changed files with 576 additions and 56 deletions
  1. 9 0
      cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/constant/CourseConstant.java
  2. 31 0
      cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/constant/SysConfigConstant.java
  3. 65 0
      cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/dto/CheckLiveCourseTimeDto.java
  4. 12 33
      cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/dto/LiveCourseGroupDto.java
  5. 43 0
      cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/entity/CourseTimeEntity.java
  6. 1 4
      cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/entity/ImRoomMessage.java
  7. 69 0
      cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/entity/ImUserStateSync.java
  8. 9 2
      cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/service/CourseGroupService.java
  9. 48 0
      cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/service/CourseScheduleService.java
  10. 4 0
      cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/service/LiveRoomService.java
  11. 184 6
      cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/service/impl/CourseGroupServiceImpl.java
  12. 12 10
      cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/service/impl/CourseScheduleServiceImpl.java
  13. 50 0
      cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/service/impl/LiveRoomServiceImpl.java
  14. 3 1
      cooleshow-user/user-teacher/src/main/java/com/yonge/cooleshow/teacher/config/ResourceServerConfig.java
  15. 13 0
      cooleshow-user/user-teacher/src/main/java/com/yonge/cooleshow/teacher/controller/CourseGroupController.java
  16. 23 0
      cooleshow-user/user-teacher/src/main/java/com/yonge/cooleshow/teacher/controller/LiveRoomController.java

+ 9 - 0
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/constant/CourseConstant.java

@@ -0,0 +1,9 @@
+package com.yonge.cooleshow.biz.dal.constant;
+
+public interface CourseConstant {
+    /**
+     * 未正式写入数据库的排课时间临时数据
+     * <p>用途将未写入数据库的排课数据写入缓存,然后生成日历及陪练课买课时校验课时将缓存时间拿出来比对</p>
+     */
+    String LOCK_COURSE_TIME_INFO = "LOCK_COURSE_TIME_INFO";
+}

+ 31 - 0
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/constant/SysConfigConstant.java

@@ -66,4 +66,35 @@ public interface SysConfigConstant {
      */
     String COURSE_SETTLEMENT_TIME_DAY = "course_settlement_time_day";
 
+    /**
+     * 创建直播课可选时间设定
+     */
+    String LIVE_TIME_SETTING = "live_time_setting";
+
+    /**
+     * 学生购买陪练课时间段锁定时间
+     */
+    String STUDENT_BUY_PRACTICE_TIME_LOCK_MINUTE = "student_buy_practice_time_lock_minute";
+
+    /**
+     * 老师创建直播课选择课程时间段锁定的时间
+     */
+    String CREATE_LIVE_TIME_LOCK_MINUTE = "create_live_time_lock_minute";
+
+    /**
+     * 课程开始时间
+     */
+    String COURSE_START_SETTING = "course_start_setting";
+
+    /**
+     * 课程结束时间
+     */
+    String COURSE_END_SETTING = "course_end_setting";
+
+    /**
+     * 自动规划课程最大周数
+     */
+    String auto_planning_course_max_week = "auto_planning_course_max_week";
+
+
 }

+ 65 - 0
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/dto/CheckLiveCourseTimeDto.java

@@ -0,0 +1,65 @@
+package com.yonge.cooleshow.biz.dal.dto;
+
+import com.yonge.cooleshow.biz.dal.entity.CourseTimeEntity;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @author hgw
+ * Created by 2022-03-30
+ */
+@ApiModel(value = "创建直播课组验校时间接收类")
+public class CheckLiveCourseTimeDto implements Serializable {
+
+    @NotNull(message = "老师Id不能为空")
+    @ApiModelProperty(value = "老师Id")
+    private Long teacherId;
+
+    @NotNull(message = "是否需要自动排课不能为空")
+    @ApiModelProperty(value = "是否需要循环 0:不需要 1:需要")
+    private Integer loop;
+
+    @NotNull(message = "课程数不能为空")
+    @ApiModelProperty(value = "课程数")
+    private Integer courseNum;
+
+    @NotNull(message = "课程时间不能为空")
+    @ApiModelProperty(value = "课程时间接收类")
+    private List<CourseTimeEntity> timeList;
+
+    public Long getTeacherId() {
+        return teacherId;
+    }
+
+    public void setTeacherId(Long teacherId) {
+        this.teacherId = teacherId;
+    }
+
+    public Integer getLoop() {
+        return loop;
+    }
+
+    public void setLoop(Integer loop) {
+        this.loop = loop;
+    }
+
+    public Integer getCourseNum() {
+        return courseNum;
+    }
+
+    public void setCourseNum(Integer courseNum) {
+        this.courseNum = courseNum;
+    }
+
+    public List<CourseTimeEntity> getTimeList() {
+        return timeList;
+    }
+
+    public void setTimeList(List<CourseTimeEntity> timeList) {
+        this.timeList = timeList;
+    }
+}

+ 12 - 33
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/dto/CourseGroupDto.java → cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/dto/LiveCourseGroupDto.java

@@ -1,5 +1,6 @@
 package com.yonge.cooleshow.biz.dal.dto;
 
+import com.yonge.cooleshow.biz.dal.entity.CourseTimeEntity;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 
@@ -16,8 +17,8 @@ import java.util.List;
  * @author hgw
  * Created by 2022-03-21
  */
-@ApiModel(value = "课程组接收类")
-public class CourseGroupDto implements Serializable {
+@ApiModel(value = "创建直播课接收类")
+public class LiveCourseGroupDto implements Serializable {
 
     @ApiModelProperty(value = "主键")
     private Long id;
@@ -69,23 +70,16 @@ public class CourseGroupDto implements Serializable {
     @ApiModelProperty(value = "直播背景图")
     private String backgroundPic;
 
-    @NotNull(message = "课程数不能为空")
-    @Positive(message = "课程数必须大于0")
+    @NotNull(message = "最少成课人数不能为空")
+    @Positive(message = "最少成课人数必须大于0")
     @ApiModelProperty(value = "最少成课人数")
     private Integer mixStudentNum;
 
     @ApiModelProperty(value = "课时及教学计划")
-    private List<CourseInfoDto> courseInfo;
+    private List<CoursePlanDto> CoursePlanList;
 
     @ApiModel(value = "课程详情接收类")
-    static class CourseInfoDto implements Serializable{
-        @NotNull(message = "上课时间不能为空")
-        @ApiModelProperty(value = "上课时间")
-        private Date startTime;
-
-        @NotNull(message = "下课时间不能为空")
-        @ApiModelProperty(value = "下课时间")
-        private Date endTime;
+    static class CoursePlanDto extends CourseTimeEntity implements Serializable {
 
         @NotNull(message = "课堂编号不能为空")
         @ApiModelProperty(value = "课堂编号-第几堂课")
@@ -96,22 +90,6 @@ public class CourseGroupDto implements Serializable {
         @ApiModelProperty(value = "教学计划/最多200字")
         private String plan;
 
-        public Date getStartTime() {
-            return startTime;
-        }
-
-        public void setStartTime(Date startTime) {
-            this.startTime = startTime;
-        }
-
-        public Date getEndTime() {
-            return endTime;
-        }
-
-        public void setEndTime(Date endTime) {
-            this.endTime = endTime;
-        }
-
         public Integer getClassNum() {
             return classNum;
         }
@@ -233,11 +211,12 @@ public class CourseGroupDto implements Serializable {
         this.mixStudentNum = mixStudentNum;
     }
 
-    public List<CourseInfoDto> getCourseInfo() {
-        return courseInfo;
+    public List<CoursePlanDto> getCoursePlanList() {
+        return CoursePlanList;
     }
 
-    public void setCourseInfo(List<CourseInfoDto> courseInfo) {
-        this.courseInfo = courseInfo;
+    public void setCoursePlanList(List<CoursePlanDto> coursePlanList) {
+        CoursePlanList = coursePlanList;
     }
+
 }

+ 43 - 0
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/entity/CourseTimeEntity.java

@@ -0,0 +1,43 @@
+package com.yonge.cooleshow.biz.dal.entity;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * @author hgw
+ * Created by 2022-03-31
+ */
+@ApiModel(value = "课程时间类")
+public class CourseTimeEntity implements Serializable {
+
+    @NotNull(message = "上课时间不能为空")
+    @ApiModelProperty(value = "上课时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date startTime;
+
+    @NotNull(message = "下课时间不能为空")
+    @ApiModelProperty(value = "下课时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date endTime;
+
+    public Date getStartTime() {
+        return startTime;
+    }
+
+    public void setStartTime(Date startTime) {
+        this.startTime = startTime;
+    }
+
+    public Date getEndTime() {
+        return endTime;
+    }
+
+    public void setEndTime(Date endTime) {
+        this.endTime = endTime;
+    }
+}

+ 1 - 4
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/entity/ImRoomMessage.java

@@ -6,11 +6,8 @@ package com.yonge.cooleshow.biz.dal.entity;
  */
 public class ImRoomMessage extends BaseMessage {
 
-    //objectName 类型-将所有人强制踢出房间
-    public static final String FORCED_OFFLINE = "RC:ForcedOffline";
-
     //objectName 类型-观看者退出房间
-    public static final String LOOKER_LOGIN_OUT = "RC:LookerLoginOut";
+    public static final String RC_CHATROOM_LEAVE = "RC:Chatroom:Leave";
 
     /**
      * 消息类型

+ 69 - 0
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/entity/ImUserStateSync.java

@@ -0,0 +1,69 @@
+package com.yonge.cooleshow.biz.dal.entity;
+
+import io.swagger.annotations.ApiModelProperty;
+
+import java.io.Serializable;
+
+/**
+ * 监听融云用户状态变更实体类
+ *
+ * @author hgw
+ * Created by 2022-02-18
+ */
+public class ImUserStateSync implements Serializable {
+
+    @ApiModelProperty(value = "用户 Id")
+    private String userid;
+
+    @ApiModelProperty(value = "状态:0:online 上线、1:offline 离线、2:logout 登出 3:退出直播间")
+    private String status;
+
+    @ApiModelProperty(value = "操作系统:iOS、Android、Websocket、PC、MiniProgram(小程序),用户上线时同步")
+    private String os;
+
+    @ApiModelProperty(value = "发生时间")
+    private Long time;
+
+    @ApiModelProperty(value = "用户当前的 IP 地址及端口")
+    private String clientIp;
+
+    public String getUserid() {
+        return userid;
+    }
+
+    public void setUserid(String userid) {
+        this.userid = userid;
+    }
+
+    public String getStatus() {
+        return status;
+    }
+
+    public void setStatus(String status) {
+        this.status = status;
+    }
+
+    public String getOs() {
+        return os;
+    }
+
+    public void setOs(String os) {
+        this.os = os;
+    }
+
+    public Long getTime() {
+        return time;
+    }
+
+    public void setTime(Long time) {
+        this.time = time;
+    }
+
+    public String getClientIp() {
+        return clientIp;
+    }
+
+    public void setClientIp(String clientIp) {
+        this.clientIp = clientIp;
+    }
+}

+ 9 - 2
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/service/CourseGroupService.java

@@ -2,8 +2,11 @@ package com.yonge.cooleshow.biz.dal.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.yonge.cooleshow.biz.dal.dao.CourseGroupDao;
-import com.yonge.cooleshow.biz.dal.dto.CourseGroupDto;
+import com.yonge.cooleshow.biz.dal.dto.CheckLiveCourseTimeDto;
 import com.yonge.cooleshow.biz.dal.entity.CourseGroup;
+import com.yonge.cooleshow.biz.dal.entity.CourseTimeEntity;
+
+import java.util.List;
 
 /**
  * 课程组表(CourseGroup)表服务接口
@@ -15,6 +18,10 @@ public interface CourseGroupService extends IService<CourseGroup> {
 
     CourseGroupDao getDao();
 
-    void add(CourseGroupDto dto);
+    /**
+     * 创建直播课程组时将课时写到缓存当作锁定的时间
+     */
+    List<CourseTimeEntity> lockCourseToCache(CheckLiveCourseTimeDto dto);
+
 }
 

+ 48 - 0
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/service/CourseScheduleService.java

@@ -10,6 +10,7 @@ import javax.validation.Valid;
 import java.util.Date;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Function;
 
 /**
  * 老师课程表(CourseSchedule)表服务接口
@@ -34,6 +35,53 @@ public interface CourseScheduleService extends IService<CourseSchedule> {
     PageInfo<TeacherCourseVo> queryTeacherLiveCourse(Map<String, Object> param);
 
     /**
+     * 校验该学生大于当前时间并未开始的课程时间和传入时间段有没有交集
+     *
+     * @param studentId 学生id
+     * @param startTime 新增课程 开始时间
+     * @param endTime   新增课程 结束时间
+     * @return true 被占用 false 没有被占用
+     */
+    boolean checkStudentCourseTime(Long studentId, Date startTime, Date endTime);
+
+    /**
+     * 校验老师这个时间段有没有被占用-校验课时
+     *
+     * @param teacherId 老师id
+     * @param startTime 新增课程 开始时间
+     * @param endTime   新增课程 结束时间
+     * @return true 被占用 false 没有被占用
+     */
+    boolean checkTeacherCourseTime(Long teacherId, Date startTime, Date endTime);
+
+    /**
+     * 查询这个课程列表中时间是否有重叠
+     * <p>案例:1
+     * <p>已有9:00~ 10:00 课程
+     * <p>新增课程 09:10~ 09:40
+     * <p>返回 true
+     *
+     * <p>案例:2
+     * <p>已有9:00~ 10:00 课程
+     * <p>新增课程 08:45~ 09:40
+     * <p>返回 true
+     *
+     * <p>案例:3
+     * <p>已有9:00~ 10:00 课程
+     * <p>新增课程 10:00~ 10:40
+     * <p>返回 false
+     *
+     * @param list         时间列表
+     * @param startTimeFun 获取现课程开始时间
+     * @param endTimeFun   获取现课程结束时间
+     * @param startTime    新增课程开始时间
+     * @param endTime      新增课程结束时间
+     * @return true 被占用 false 没有被占用
+     */
+    <T> boolean checkCourseTime(List<T> list, Function<T, Date> startTimeFun, Function<T, Date> endTimeFun,
+                                Date startTime, Date endTime);
+
+    /**
      * 锁定课时
      *
      * @param id 课程id

+ 4 - 0
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/service/LiveRoomService.java

@@ -57,5 +57,9 @@ public interface LiveRoomService extends IService<LiveRoom> {
      */
     List<RoomUserInfoCache> queryRoomUserInfo(String roomUid);
 
+    /**
+     * 方便测试观察房间数据的方法
+     */
+    Map<String, Object> test(String roomUid);
 }
 

+ 184 - 6
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/service/impl/CourseGroupServiceImpl.java

@@ -1,17 +1,39 @@
 package com.yonge.cooleshow.biz.dal.service.impl;
 
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.yonge.cooleshow.auth.api.client.SysUserFeignService;
+import com.yonge.cooleshow.auth.api.entity.SysUser;
+import com.yonge.cooleshow.biz.dal.constant.CourseConstant;
+import com.yonge.cooleshow.biz.dal.constant.LiveRoomConstant;
 import com.yonge.cooleshow.biz.dal.dao.CourseGroupDao;
-import com.yonge.cooleshow.biz.dal.dto.CourseGroupDto;
+import com.yonge.cooleshow.biz.dal.dto.CheckLiveCourseTimeDto;
+import com.yonge.cooleshow.biz.dal.dto.LiveCourseGroupDto;
 import com.yonge.cooleshow.biz.dal.entity.CourseGroup;
+import com.yonge.cooleshow.biz.dal.entity.CourseTimeEntity;
 import com.yonge.cooleshow.biz.dal.service.CourseGroupService;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-
+import com.yonge.cooleshow.biz.dal.service.CourseScheduleService;
+import com.yonge.cooleshow.biz.dal.service.SysConfigService;
+import com.yonge.cooleshow.common.exception.BizException;
+import com.yonge.toolset.utils.date.DateUtil;
+import org.apache.commons.lang3.StringUtils;
+import org.redisson.api.RMap;
+import org.redisson.api.RedissonClient;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.util.Comparator;
+import java.util.Date;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
 /**
  * 课程组表(CourseGroup)表服务实现类
  *
@@ -23,6 +45,15 @@ public class CourseGroupServiceImpl extends ServiceImpl<CourseGroupDao, CourseGr
 
     private final static Logger log = LoggerFactory.getLogger(CourseGroupServiceImpl.class);
 
+    @Autowired
+    private SysUserFeignService sysUserFeignService;
+    @Autowired
+    private RedissonClient redissonClient;
+    @Autowired
+    private CourseScheduleService courseScheduleService;
+    @Autowired
+    private SysConfigService sysConfigService;
+
     @Override
     public CourseGroupDao getDao() {
         return this.baseMapper;
@@ -30,15 +61,162 @@ public class CourseGroupServiceImpl extends ServiceImpl<CourseGroupDao, CourseGr
 
     /**
      * 新增课程组
+     *
      * @param dto
      */
     @Transactional(rollbackFor = Exception.class)
-    @Override
-    public void add(CourseGroupDto dto){
+    public void add(LiveCourseGroupDto dto) {
+        //1.查询该老师没有用缓存的课时
+
+        //1.1 有缓存课时
+
+        //1.2 没有缓存课时
+
+    }
+
+    /**
+     * 创建直播课程组时将课时写到缓存当作锁定的时间
+     */
+    public List<CourseTimeEntity> lockCourseToCache(CheckLiveCourseTimeDto dto) {
+        //先自校验传入时间是否交集
+        List<CourseTimeEntity> timeList = dto.getTimeList();
+        if (timeList.size() > 1) {
+            for (int i = 0; i < timeList.size(); i++) {
+                if (i == timeList.size() - 1) {
+                    break;
+                }
+                CourseTimeEntity o1 = timeList.get(i);
+                List<CourseTimeEntity> newList = timeList.subList(i + 1, timeList.size());
+                boolean checkParamTime = courseScheduleService.checkCourseTime(newList, CourseTimeEntity::getStartTime, CourseTimeEntity::getEndTime, o1.getStartTime(), o1.getEndTime());
+                if (checkParamTime) {
+                    throw new BizException(DateUtil.dateToString(o1.getStartTime(), "yyyy年MM月dd号 HH点mm分") + "的课程时间重复!");
+                }
+            }
+        }
 
+        //再校验数据库中课程时间和传入时间是否有交集
+        timeList.forEach(o -> {
+            boolean checkDataTime = courseScheduleService.checkTeacherCourseTime(dto.getTeacherId(), o.getStartTime(), o.getEndTime());
+            if (checkDataTime) {
+                throw new BizException("预计安排在" + DateUtil.dateToString(o.getStartTime(), "yyyy年MM月dd号 HH点mm分") + "的课程已被学员选择!");
+            }
+        });
+        //先将当前验证通过课程时间锁住
+        String key = String.join(":", LiveRoomConstant.COOLESHOW, CourseConstant.LOCK_COURSE_TIME_INFO, dto.getTeacherId().toString());
+        RMap<Long, List<CourseTimeEntity>> map = redissonClient.getMap(key);
+        map.expire(1L, TimeUnit.DAYS);
+        map.fastPut(dto.getTeacherId(), timeList);
+
+        //需要自动补全课时
+        if (dto.getLoop() == 1) {
+            //获取总课程数量
+            Integer totalCourseNum = dto.getCourseNum();
+            //获取当前课程
+            int nowCourseNum = timeList.size();
+            //自动排课,获取排课后所有的课程时间
+            List<CourseTimeEntity> allCourseTime = teacherAutoPlanningLiveCourseTime(dto.getTeacherId(), totalCourseNum, nowCourseNum, timeList);
+            //替换掉原有的课时
+            dto.setTimeList(allCourseTime);
+            //将自动排课后的课时写入缓存覆盖原有的
+            map.fastPut(dto.getTeacherId(), allCourseTime);
+        }
+        return dto.getTimeList();
     }
 
+    /**
+     * 老师创建直播课自动排课
+     * <p>自动排课规则及场景:总5节课,填入2节,需要自动补3节
+     * <p>1.把前面2节课的时间循环+1周直到填满5节课为止
+     * <p>2.如果自动排课时的时间和未来课程时间有冲突则继续往后面延续一周
+     *
+     * @param totalCourseNum 总课程数量
+     * @param nowCourseNum   当前选择的课程数量
+     * @param paramTimeList  当前课程的时间段
+     * @return 自动排课后的全部课时
+     */
+    private List<CourseTimeEntity> teacherAutoPlanningLiveCourseTime(Long teacherId, int totalCourseNum, int nowCourseNum, List<CourseTimeEntity> paramTimeList) {
+        //获取总课程数量 - 获取当前选择的课程数量 = 要自动排课的课程数量
+        int diffCourse = totalCourseNum - nowCourseNum;
+        //获取课程时间,并按开始时间排序
+        List<CourseTimeEntity> sortCourseTime = paramTimeList.stream()
+                .sorted(Comparator.comparing(CourseTimeEntity::getStartTime))
+                .collect(Collectors.toList());
+        //获取最大排课周
+        String maxWeekStr = sysConfigService.findConfigValue("auto_planning_course_max_week");
+        int maxWeek = 26;//默认 26周
+        if (StringUtils.isBlank(maxWeekStr)) {
+            maxWeek = Integer.parseInt(maxWeekStr);
+        }
+        int index = 0;
+        //自动补全课时
+        for (int i = 0; i < diffCourse; i++) {
+            //需要增加的周数
+            AtomicInteger week = new AtomicInteger(1);
+            //是否要一直生成课程 true 一直增加周数并生成到不冲突的时间为止
+            AtomicBoolean flag = new AtomicBoolean(true);
+            //生成课程
+            while (flag.get()) {
+                if (index == nowCourseNum) {
+                    index = 0;
+                    //进入新的循环周数+1
+                    week.getAndIncrement();
+                }
+                CourseTimeEntity timeDto = sortCourseTime.get(index);
+                if (week.get() > maxWeek) {
+                    throw new BizException("系统自动排课时发现当前时间往后" + maxWeek + "周的课程时间已排满,请手动排课!");
+                }
+                Date autoStartDate = DateUtil.addWeeks(timeDto.getStartTime(), week.get());
+                Date autoEndDate = DateUtil.addWeeks(timeDto.getEndTime(), week.get());
+
+                //true  flag = true 并且 周数+1
+                Consumer<Boolean> con = (check) -> {
+                    //check = true ,有交集延续1周后继续生成时间
+                    if (check) {
+                        week.getAndIncrement();
+                        flag.set(true);
+                    } else {
+                        flag.set(false);
+                    }
+                };
+
+                boolean checkTime;
 
+                //若:传入时间是1号10点和8号10点,然后1号10点自动生成的课时是8号10点那么就和传入的8号10点冲突了,这种情况需要继续往后延续1周
+                checkTime = courseScheduleService.checkCourseTime(sortCourseTime, CourseTimeEntity::getStartTime, CourseTimeEntity::getEndTime, autoStartDate, autoEndDate);
+                con.accept(checkTime);
+                //如果和传入时间冲突则跳过
+                if (flag.get()) {
+                    continue;
+                }
 
+                //校验当前生成时间是否和未来的课程时间是否交集
+                checkTime = courseScheduleService.checkTeacherCourseTime(teacherId, autoStartDate, autoEndDate);
+                con.accept(checkTime);
+                //如果和未来时间冲突则跳过
+                if (flag.get()) {
+                    continue;
+                }
+
+                //将自动生成时间收入集合
+                CourseTimeEntity autoTimeDto = new CourseTimeEntity();
+                autoTimeDto.setStartTime(autoStartDate);
+                autoTimeDto.setEndTime(autoEndDate);
+                sortCourseTime.add(autoTimeDto);
+            }
+            index++;
+        }
+        return sortCourseTime;
+    }
+
+    private SysUser getSysUser(Long userId) {
+        return Optional.ofNullable(userId)
+                .map(sysUserFeignService::queryUserById)
+                .orElseThrow(() -> new BizException("用户不存在"));
+    }
+
+    private SysUser getSysUser() {
+        return Optional.ofNullable(sysUserFeignService.queryUserInfo())
+                .orElseThrow(() -> new BizException("用户不存在"));
+    }
 }
 

+ 12 - 10
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/service/impl/CourseScheduleServiceImpl.java

@@ -1,6 +1,5 @@
 package com.yonge.cooleshow.biz.dal.service.impl;
 
-import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -18,7 +17,6 @@ import com.yonge.cooleshow.common.exception.BizException;
 import com.yonge.cooleshow.common.page.PageInfo;
 import com.yonge.toolset.utils.date.DateUtil;
 import org.apache.commons.collections.CollectionUtils;
-import org.apache.commons.lang.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -33,6 +31,7 @@ import java.util.Date;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.function.Function;
 
 /**
  * 老师课程表(CourseSchedule)表服务实现类
@@ -100,7 +99,7 @@ public class CourseScheduleServiceImpl extends ServiceImpl<CourseScheduleDao, Co
         String ymd = DateUtil.format(startTime, DateUtil.DEFAULT_PATTERN);
         //查询大于当前时间并未开始的课程
         List<CourseSchedule> list = baseMapper.queryStudentCourse(studentId, ymd);
-        return checkCourseTime(list, startTime, endTime);
+        return checkCourseTime(list, CourseSchedule::getStartTime, CourseSchedule::getEndTime, startTime, endTime);
     }
 
     /**
@@ -120,7 +119,7 @@ public class CourseScheduleServiceImpl extends ServiceImpl<CourseScheduleDao, Co
         List<CourseSchedule> list = this.list(Wrappers.<CourseSchedule>lambdaQuery()
                 .eq(CourseSchedule::getTeacherId, teacherId)
                 .eq(CourseSchedule::getClassDate, ymd));
-        return checkCourseTime(list, startTime, endTime);
+        return checkCourseTime(list, CourseSchedule::getStartTime, CourseSchedule::getEndTime, startTime, endTime);
     }
 
     /**
@@ -140,12 +139,15 @@ public class CourseScheduleServiceImpl extends ServiceImpl<CourseScheduleDao, Co
      * <p>新增课程 10:00~ 10:40
      * <p>返回 false
      *
-     * @param list      课程列表
-     * @param startTime 新增课程 开始时间
-     * @param endTime   新增课程 结束时间
+     * @param list         时间列表
+     * @param startTimeFun 获取现课程开始时间
+     * @param endTimeFun   获取现课程结束时间
+     * @param startTime    新增课程开始时间
+     * @param endTime      新增课程结束时间
      * @return true 被占用 false 没有被占用
      */
-    public boolean checkCourseTime(List<CourseSchedule> list, Date startTime, Date endTime) {
+    public <T> boolean checkCourseTime(List<T> list, Function<T, Date> startTimeFun, Function<T, Date> endTimeFun,
+                                       Date startTime, Date endTime) {
         WrapperUtil.checkObj(startTime, "开始时间不能为空!");
         WrapperUtil.checkObj(endTime, "结束时间不能为空!");
         // 如果没有课程,直接返回false
@@ -154,8 +156,8 @@ public class CourseScheduleServiceImpl extends ServiceImpl<CourseScheduleDao, Co
         }
         boolean result;
         //匹配时间是否有交集
-        for (CourseSchedule course : list) {
-            result = WrapperUtil.inInterSection(course.getStartTime(), course.getEndTime(), startTime, endTime);
+        for (T t : list) {
+            result = WrapperUtil.inInterSection(startTimeFun.apply(t), endTimeFun.apply(t), startTime, endTime);
             if (result) {
                 return true;
             }

+ 50 - 0
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/service/impl/LiveRoomServiceImpl.java

@@ -1,11 +1,13 @@
 package com.yonge.cooleshow.biz.dal.service.impl;
 
+import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.yonge.cooleshow.auth.api.client.SysUserFeignService;
 import com.yonge.cooleshow.auth.api.entity.SysUser;
 import com.yonge.cooleshow.biz.dal.dao.LiveRoomDao;
+import com.yonge.cooleshow.biz.dal.dto.CheckLiveCourseTimeDto;
 import com.yonge.cooleshow.biz.dal.entity.*;
 import com.yonge.cooleshow.biz.dal.enums.CourseScheduleEnum;
 import com.yonge.cooleshow.biz.dal.enums.RoomTypeEnum;
@@ -483,5 +485,53 @@ public class LiveRoomServiceImpl extends ServiceImpl<LiveRoomDao, LiveRoom> impl
         return roomUserInfoCaches.size();
     }
 
+    /**
+     * 方便测试观察房间数据的方法
+     */
+    public Map<String, Object> test(String roomUid) {
+        //result
+        Map<String, Object> result = new HashMap<>();
+
+        //获取房间信息
+        RBucket<RoomInfoCache> speakerCache = redissonClient.getBucket(LIVE_ROOM_INFO.replace(ROOM_UID, roomUid));
+        if (speakerCache.isExists()) {
+            result.put("房间信息信息", speakerCache.get());
+        } else {
+            result.put("房间信息", "房间信息不存在");
+        }
+
+        //点赞数
+        Object like = redissonClient.getBucket(LIVE_ROOM_LIKE.replace(ROOM_UID, roomUid)).get();
+        if (Objects.isNull(like)) {
+            like = 0;
+        }
+        result.put("点赞数", like);
+
+        int totalLook = 0;
+        int look = 0;
+        List<RoomUserInfoCache> inRoomUserInfo;
+
+        //累计总观看的用户数量
+        List<RoomUserInfoCache> totalUserInfo = queryTotalRoomUserInfo(roomUid);
+        if (CollectionUtils.isNotEmpty(totalUserInfo)) {
+            //正在房间观看的用户数据
+            inRoomUserInfo = queryRoomUserInfo(totalUserInfo);
+            if (CollectionUtils.isNotEmpty(inRoomUserInfo)) {
+                look = inRoomUserInfo.size();
+                result.put("正在观看的人员信息", inRoomUserInfo);
+            } else {
+                result.put("正在观看的人员信息", "没有正在观看的人员数据");
+            }
+            totalLook = totalUserInfo.size();
+            result.put("总人员数据", totalUserInfo);
+        } else {
+            result.put("总人员数据", "没有人员数据");
+        }
+
+        result.put("总观看人数", totalLook);
+        result.put("实时观看数", look);
+        return result;
+    }
+
 }
 

+ 3 - 1
cooleshow-user/user-teacher/src/main/java/com/yonge/cooleshow/teacher/config/ResourceServerConfig.java

@@ -31,7 +31,9 @@ public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
 				.authenticationEntryPoint(baseAuthenticationEntryPoint)
 				.and()
 				.authorizeRequests()
-				.antMatchers("/v2/api-docs", "/code/*").permitAll().anyRequest().authenticated().and().httpBasic();
+				.antMatchers("/v2/api-docs", "/code/*",
+                        "/liveRoom/test","/liveRoom/syncUserStatus")
+                .permitAll().anyRequest().authenticated().and().httpBasic();
 	}
 
 	@Override

+ 13 - 0
cooleshow-user/user-teacher/src/main/java/com/yonge/cooleshow/teacher/controller/CourseGroupController.java

@@ -1,13 +1,20 @@
 package com.yonge.cooleshow.teacher.controller;
 
 
+import com.yonge.cooleshow.biz.dal.dto.CheckLiveCourseTimeDto;
+import com.yonge.cooleshow.biz.dal.entity.CourseTimeEntity;
 import com.yonge.cooleshow.biz.dal.service.CourseGroupService;
 import com.yonge.cooleshow.common.controller.BaseController;
+import com.yonge.cooleshow.common.entity.HttpResponseResult;
 import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
 import javax.annotation.Resource;
+import java.util.List;
 
 /**
  * 课程组表(CourseGroup)表控制层
@@ -25,5 +32,11 @@ public class CourseGroupController extends BaseController {
     @Resource
     private CourseGroupService courseGroupService;
 
+    @ApiOperation("创建直播课程组-锁定课程时间-将课时写到缓存当作锁定的时间")
+    @PostMapping("/lockCourseTime")
+    public HttpResponseResult<List<CourseTimeEntity>> lockCourseToCache(@RequestBody CheckLiveCourseTimeDto dto) {
+        return succeed(courseGroupService.lockCourseToCache(dto));
+    }
+
 }
 

+ 23 - 0
cooleshow-user/user-teacher/src/main/java/com/yonge/cooleshow/teacher/controller/LiveRoomController.java

@@ -1,6 +1,8 @@
 package com.yonge.cooleshow.teacher.controller;
 
 
+import com.alibaba.fastjson.JSONObject;
+import com.yonge.cooleshow.biz.dal.entity.ImUserStateSync;
 import com.yonge.cooleshow.biz.dal.entity.RoomInfoCache;
 import com.yonge.cooleshow.biz.dal.service.LiveRoomService;
 import com.yonge.cooleshow.biz.dal.vo.RoomVo;
@@ -10,9 +12,12 @@ import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiImplicitParam;
 import io.swagger.annotations.ApiImplicitParams;
 import io.swagger.annotations.ApiOperation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -25,6 +30,7 @@ import java.util.Map;
 @RestController
 @RequestMapping("/liveRoom")
 public class LiveRoomController extends BaseController {
+    private final static Logger log = LoggerFactory.getLogger(LiveRoomController.class);
     /**
      * 服务对象
      */
@@ -68,5 +74,22 @@ public class LiveRoomController extends BaseController {
     public void destroyExpiredLiveRoom() {
         liveRoomService.destroyExpiredLiveRoom();
     }
+
+    /**
+     * 同步融云用户状态变更
+     *
+     * @param userState
+     */
+    @PostMapping(value = "/syncUserStatus")
+    public void statusImUser(@RequestBody List<ImUserStateSync> userState) {
+        log.info("statusImUser >>>>> : {}", JSONObject.toJSONString(userState));
+    }
+
+    @ApiOperation("方便测试观察房间数据的方法")
+    @GetMapping("/test")
+    public Object destroyExpiredLiveRoom(@RequestParam("roomUid") String roomUid) {
+        return liveRoomService.test(roomUid);
+    }
+
 }