Explorar o código

Merge remote-tracking branch 'origin/feature/0529-live' into feature/0529-live

shangke %!s(int64=2) %!d(string=hai) anos
pai
achega
718cfbe81f

+ 212 - 155
.idea/httpRequests/http-requests-log.http

@@ -1,3 +1,215 @@
+GET http://127.0.0.1:9002/teacherCourseSchedule/findCourseAttendanceDetailHeadInfo?courseScheduleId=2002
+authorization: bearer 97fd8e85-bb3c-4967-8a07-47c44957f94a
+Content-Type: application/json
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
+
+<> 2023-06-08T162615.200.json
+
+###
+
+POST http://127.0.0.1:9002/teacherCourseSchedule/liveCoursePage
+authorization: bearer fe63240c-4f7d-4b67-90f8-7d51eaafdfb7
+Content-Type: application/json
+Content-Length: 152
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
+
+{
+  "page": 1,
+  "rows": 10,
+//  "startTime": "2023-06-06 10:14:57",
+//  "endTime": "2023-06-08 10:14:57",
+//  "search": "",
+//  "status": "NOT_START"
+}
+
+<> 2023-06-08T162147.200.json
+
+###
+
+POST http://127.0.0.1:9002/teacherAttendance/getLiveCurrentCourseStudents
+authorization: bearer 97fd8e85-bb3c-4967-8a07-47c44957f94a
+Content-Type: application/json
+Content-Length: 70
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
+
+{
+  "courseScheduleId": 2001,
+  "status": "PURCHASE",
+  "search": ""
+}
+
+<> 2023-06-08T162015.200.json
+
+###
+
+POST http://127.0.0.1:9002/teacherAttendance/getLiveCurrentCourseStudents
+authorization: bearer 97fd8e85-bb3c-4967-8a07-47c44957f94a
+Content-Type: application/json
+Content-Length: 67
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
+
+{
+  "courseScheduleId": 2001,
+  "status": "SHARE",
+  "search": ""
+}
+
+<> 2023-06-08T161954.200.json
+
+###
+
+GET http://127.0.0.1:8005/task/closeLiveCourseRoom
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
+
+<> 2023-06-08T161900.200.json
+
+###
+
+GET http://127.0.0.1:8005/task/closeLiveCourseRoom
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
+
+<> 2023-06-08T161723.200.json
+
+###
+
+GET http://127.0.0.1:8005/task/closeLiveCourseRoom
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
+
+<> 2023-06-08T161420.200.json
+
+###
+
+POST http://127.0.0.1:9002/teacherCourseSchedule/liveCoursePage
+authorization: bearer 1238347b-3b06-444e-9d35-d122639121ad
+Content-Type: application/json
+Content-Length: 152
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
+
+{
+  "page": 1,
+  "rows": 10,
+//  "startTime": "2023-06-06 10:14:57",
+//  "endTime": "2023-06-08 10:14:57",
+//  "search": "",
+//  "status": "NOT_START"
+}
+
+<> 2023-06-08T155851.200.json
+
+###
+
+POST http://127.0.0.1:9002/teacherCourseSchedule/liveCoursePage
+authorization: bearer 1238347b-3b06-444e-9d35-d122639121ad
+Content-Type: application/json
+Content-Length: 152
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
+
+{
+  "page": 1,
+  "rows": 10,
+//  "startTime": "2023-06-06 10:14:57",
+//  "endTime": "2023-06-08 10:14:57",
+//  "search": "",
+//  "status": "NOT_START"
+}
+
+<> 2023-06-08T155845.200.json
+
+###
+
+POST http://127.0.0.1:9002/teacherAttendance/getLiveCurrentCourseStudents
+authorization: bearer 97fd8e85-bb3c-4967-8a07-47c44957f94a
+Content-Type: application/json
+Content-Length: 70
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
+
+{
+  "courseScheduleId": 2001,
+//  "status": "NORMAL",
+  "search": ""
+}
+
+<> 2023-06-08T155815.200.json
+
+###
+
+POST http://127.0.0.1:9002/teacherAttendance/getLiveCurrentCourseStudents
+authorization: bearer 97fd8e85-bb3c-4967-8a07-47c44957f94a
+Content-Type: application/json
+Content-Length: 70
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
+
+{
+  "courseScheduleId": 2001,
+//  "status": "NORMAL",
+  "search": ""
+}
+
+<> 2023-06-08T155623.200.json
+
+###
+
+POST http://127.0.0.1:9002/teacherAttendance/getLiveCurrentCourseStudents
+authorization: bearer 97fd8e85-bb3c-4967-8a07-47c44957f94a
+Content-Type: application/json
+Content-Length: 70
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
+
+{
+  "courseScheduleId": 2001,
+//  "status": "NORMAL",
+  "search": ""
+}
+
+<> 2023-06-08T155544.200.json
+
+###
+
+POST http://127.0.0.1:9002/teacherCourseSchedule/liveCoursePage
+authorization: bearer 1238347b-3b06-444e-9d35-d122639121ad
+Content-Type: application/json
+Content-Length: 152
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
+
+{
+  "page": 1,
+  "rows": 10,
+//  "startTime": "2023-06-06 10:14:57",
+//  "endTime": "2023-06-08 10:14:57",
+//  "search": "",
+//  "status": "NOT_START"
+}
+
+<> 2023-06-08T155515.200.json
+
+###
+
 POST http://127.0.0.1:9002/teacherAttendance/getLiveCurrentCourseStudents
 POST http://127.0.0.1:9002/teacherAttendance/getLiveCurrentCourseStudents
 authorization: bearer 97fd8e85-bb3c-4967-8a07-47c44957f94a
 authorization: bearer 97fd8e85-bb3c-4967-8a07-47c44957f94a
 Content-Type: application/json
 Content-Type: application/json
@@ -596,158 +808,3 @@ Accept-Encoding: br,deflate,gzip,x-gzip
 
 
 ###
 ###
 
 
-GET http://127.0.0.1:8005/schoolActivity/detail/16
-Connection: Keep-Alive
-User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
-Accept-Encoding: br,deflate,gzip,x-gzip
-
-<> 2023-06-02T182849.200.json
-
-###
-
-GET http://127.0.0.1:8005/schoolActivity/detail/16
-Connection: Keep-Alive
-User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
-Accept-Encoding: br,deflate,gzip,x-gzip
-
-<> 2023-06-02T182844.200.json
-
-###
-
-GET http://127.0.0.1:8005/vipGroupManage/liveGroupDetail/96
-Authorization: bearer b897da27-5a37-47df-8e76-54d80a61a55d
-Connection: Keep-Alive
-User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
-Accept-Encoding: br,deflate,gzip,x-gzip
-
-<> 2023-06-02T163244.200.json
-
-###
-
-GET http://127.0.0.1:8005/open/sysConfig/queryByParamName?paramName=open_coop_id
-Connection: Keep-Alive
-User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
-Accept-Encoding: br,deflate,gzip,x-gzip
-
-<> 2023-06-02T093159.200.json
-
-###
-
-POST http://127.0.0.1:8005/open/school/staffSave
-Content-Type: application/json
-coopId: 19
-tenantId: 1
-Content-Length: 127
-Connection: Keep-Alive
-User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
-Accept-Encoding: br,deflate,gzip,x-gzip
-
-{
-  "schoolId": 23,
-  "userType": "ORCHESTRA_LEADER",
-  "username": "小石",
-  "mobile": "15599993302",
-  "smsCode" : 666666
-}
-
-<> 2023-06-01T212120.200.json
-
-###
-
-POST http://127.0.0.1:8005/open/school/staffSave
-Content-Type: application/json
-coopId: 19
-tenantId: 1
-Content-Length: 130
-Connection: Keep-Alive
-User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
-Accept-Encoding: br,deflate,gzip,x-gzip
-
-{
-  "schoolId": 23,
-  "userType": "ORCHESTRA_LEADER",
-  "username": "测试113",
-  "mobile": "19900990155",
-  "smsCode" : 666666
-}
-
-<> 2023-06-01T205245.200.json
-
-###
-
-POST http://127.0.0.1:8005/schoolStaff/remove?id=1856
-Authorization: bearer 81d0c352-fcc8-4812-87f5-0f7a68d10451
-tenantId: 1
-Content-Length: 0
-Connection: Keep-Alive
-User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
-Accept-Encoding: br,deflate,gzip,x-gzip
-
-<> 2023-06-01T204922.200.json
-
-###
-
-POST http://127.0.0.1:8005/schoolStaff/remove?id=1040
-Authorization: bearer 81d0c352-fcc8-4812-87f5-0f7a68d10451
-tenantId: 1
-Content-Length: 0
-Connection: Keep-Alive
-User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
-Accept-Encoding: br,deflate,gzip,x-gzip
-
-<> 2023-06-01T204556.200.json
-
-###
-
-POST http://127.0.0.1:8005/schoolStaff/remove?id=1040
-Authorization: bearer 3f804d4e-b7f0-41ad-8dbf-119a0c54becc
-tenantId: 1
-Content-Length: 0
-Connection: Keep-Alive
-User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
-Accept-Encoding: br,deflate,gzip,x-gzip
-
-<> 2023-06-01T204521.200.json
-
-###
-
-GET http://127.0.0.1:9001/studentOrder/setSuccessStatus
-Authorization: bearer 81d0c352-fcc8-4812-87f5-0f7a68d10451
-Connection: Keep-Alive
-User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
-Accept-Encoding: br,deflate,gzip,x-gzip
-
-<> 2023-06-01T203947.200.json
-
-###
-
-GET http://127.0.0.1:9001/studentOrder/setSuccessStatus
-Authorization: bearer 81d0c352-fcc8-4812-87f5-0f7a68d10451
-Connection: Keep-Alive
-User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
-Accept-Encoding: br,deflate,gzip,x-gzip
-
-<> 2023-06-01T203823.200.json
-
-###
-
-GET http://127.0.0.1:9001/studentOrder/setSuccessStatus
-Authorization: bearer 81d0c352-fcc8-4812-87f5-0f7a68d10451
-Connection: Keep-Alive
-User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
-Accept-Encoding: br,deflate,gzip,x-gzip
-
-<> 2023-06-01T202754.200.json
-
-###
-
-GET http://127.0.0.1:9001/studentOrder/setSuccessStatus
-Authorization: bearer 81d0c352-fcc8-4812-87f5-0f7a68d10451
-Connection: Keep-Alive
-User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
-Accept-Encoding: br,deflate,gzip,x-gzip
-
-<> 2023-06-01T202246.200.json
-
-###
-

+ 15 - 0
mec-biz/src/main/java/com/ym/mec/biz/dal/dao/CourseScheduleDao.java

@@ -2041,6 +2041,21 @@ public interface CourseScheduleDao extends BaseDAO<Long, CourseSchedule> {
     void updateRemindStatus(@Param("ids") List<Long> ids);
     void updateRemindStatus(@Param("ids") List<Long> ids);
 
 
     /**
     /**
+     * 获取已经关闭的直播课
+     *
+     * @return
+     */
+    List<CourseSchedule> getCloseLiveCourseRoom(@Param("studentRemindTime") Integer studentRemindTime, @Param("closeTime") Integer closeTime);
+
+    /**
+     * 更新通知状态
+     *
+     * @param courseScheduleId 课程ID
+     * @param liveRemind 通知状态
+     */
+    void updateLiveRemind(@Param("courseScheduleId") Long courseScheduleId, @Param("liveRemind") int liveRemind);
+
+    /**
      * 获取下一次连堂课
      * 获取下一次连堂课
      *
      *
      * @param classGroupId 班级编号
      * @param classGroupId 班级编号

+ 2 - 0
mec-biz/src/main/java/com/ym/mec/biz/dal/dao/StudentAttendanceDao.java

@@ -393,4 +393,6 @@ public interface StudentAttendanceDao extends BaseDAO<Long, StudentAttendance> {
     */
     */
     SchoolIndexStatWrapper.StudentAttendance statCoopAttendance(@Param("musicGroupIds") List<String> musicGroupIds,
     SchoolIndexStatWrapper.StudentAttendance statCoopAttendance(@Param("musicGroupIds") List<String> musicGroupIds,
                                                                       @Param("queryDto") SchoolIndexStatWrapper.QueryDto queryDto);
                                                                       @Param("queryDto") SchoolIndexStatWrapper.QueryDto queryDto);
+
+    Integer getTruantStudentNum(Long courseScheduleId);
 }
 }

+ 12 - 0
mec-biz/src/main/java/com/ym/mec/biz/dal/dto/CourseAttendanceDetailHeadInfoDto.java

@@ -46,6 +46,18 @@ public class CourseAttendanceDetailHeadInfoDto {
     @ApiModelProperty(value = "请假人数")
     @ApiModelProperty(value = "请假人数")
     private Integer leaveStudentNum;
     private Integer leaveStudentNum;
 
 
+
+    @ApiModelProperty(value = "旷课人数")
+    private Integer truantStudentNum;
+
+    public Integer getTruantStudentNum() {
+        return truantStudentNum;
+    }
+
+    public void setTruantStudentNum(Integer truantStudentNum) {
+        this.truantStudentNum = truantStudentNum;
+    }
+
     public Integer getTotalStudentNum() {
     public Integer getTotalStudentNum() {
         return totalStudentNum;
         return totalStudentNum;
     }
     }

+ 2 - 1
mec-biz/src/main/java/com/ym/mec/biz/dal/wrapper/LiveGroupWrapper.java

@@ -3,6 +3,7 @@ package com.ym.mec.biz.dal.wrapper;
 import com.microsvc.toolkit.common.response.paging.QueryInfo;
 import com.microsvc.toolkit.common.response.paging.QueryInfo;
 import com.ym.mec.biz.dal.entity.CourseSchedule;
 import com.ym.mec.biz.dal.entity.CourseSchedule;
 import com.ym.mec.biz.dal.enums.CourseStatusEnum;
 import com.ym.mec.biz.dal.enums.CourseStatusEnum;
+import com.ym.mec.biz.dal.enums.JoinCourseType;
 import com.ym.mec.biz.dal.enums.StudentAttendanceStatusEnum;
 import com.ym.mec.biz.dal.enums.StudentAttendanceStatusEnum;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import io.swagger.annotations.ApiModelProperty;
@@ -126,7 +127,7 @@ public class LiveGroupWrapper {
         private Long courseScheduleId;
         private Long courseScheduleId;
 
 
         @ApiModelProperty(value = "学生在学状态")
         @ApiModelProperty(value = "学生在学状态")
-        private StudentAttendanceStatusEnum status;
+        private JoinCourseType status;
 
 
         @ApiModelProperty(value = "学生姓名")
         @ApiModelProperty(value = "学生姓名")
         private String search;
         private String search;

+ 13 - 2
mec-biz/src/main/java/com/ym/mec/biz/service/ImLiveBroadcastRoomService.java

@@ -56,9 +56,9 @@ public interface ImLiveBroadcastRoomService extends IService<ImLiveBroadcastRoom
      *
      *
      * @param roomUid 直播间Uid
      * @param roomUid 直播间Uid
      */
      */
-    void roomDestroy(String roomUid);
+    boolean roomDestroy(String roomUid);
 
 
-    void roomDestroy(Integer id);
+    boolean roomDestroy(Integer id);
 
 
     void delete(Integer id);
     void delete(Integer id);
 
 
@@ -77,6 +77,8 @@ public interface ImLiveBroadcastRoomService extends IService<ImLiveBroadcastRoom
 
 
     void destroyExpiredLiveRoom();
     void destroyExpiredLiveRoom();
 
 
+    void sendForcedOffline(ImLiveBroadcastRoom room);
+
     void syncLike(String roomUid, Integer likeNum);
     void syncLike(String roomUid, Integer likeNum);
 
 
     void opsRoom(List<ImUserState> userState);
     void opsRoom(List<ImUserState> userState);
@@ -208,6 +210,8 @@ public interface ImLiveBroadcastRoomService extends IService<ImLiveBroadcastRoom
      */
      */
     void destroyLiveRoom();
     void destroyLiveRoom();
 
 
+    boolean tryDestroyLiveRoom(ImLiveBroadcastRoom imLiveBroadcastRoom);
+
     /**
     /**
      * 更新直播推流时间
      * 更新直播推流时间
      * @param event TencentData.CallbackStreamStateEvent
      * @param event TencentData.CallbackStreamStateEvent
@@ -238,5 +242,12 @@ public interface ImLiveBroadcastRoomService extends IService<ImLiveBroadcastRoom
     TencentWrapper.LiveStreamState roomLiveStreamStatus(String roomUid);
     TencentWrapper.LiveStreamState roomLiveStreamStatus(String roomUid);
 
 
     void batchUpdateLiveRoomTitle(List<String> list, String roomTitle, String liveRemark);
     void batchUpdateLiveRoomTitle(List<String> list, String roomTitle, String liveRemark);
+
+    /**
+     * 获取直播间信息
+     * @param liveRoomId 直播间id
+     * @return
+     */
+    ImLiveBroadcastRoom getByRoomUid(String liveRoomId);
 }
 }
 
 

+ 4 - 0
mec-biz/src/main/java/com/ym/mec/biz/service/SysConfigService.java

@@ -419,6 +419,10 @@ public interface SysConfigService extends BaseService<Long, SysConfig> {
 
 
     // 微信公众号secret
     // 微信公众号secret
     String SCHOOL_IM_GROUP_IMG = "school_im_group_img";
     String SCHOOL_IM_GROUP_IMG = "school_im_group_img";
+    // 直播课通知学生退出直播间时间
+    String liveClassStudentRemindTime = "live_class_student_remind_time";
+    // 关闭直播间时间
+    String closeClassStudentRemindTime = "close_class_student_remind_time";
 
 
     static void checkActivityDate(String startTimeStr, String endTimeStr) {
     static void checkActivityDate(String startTimeStr, String endTimeStr) {
         if(StringUtils.isEmpty(startTimeStr) || StringUtils.isEmpty(startTimeStr)){
         if(StringUtils.isEmpty(startTimeStr) || StringUtils.isEmpty(startTimeStr)){

+ 1 - 0
mec-biz/src/main/java/com/ym/mec/biz/service/impl/CourseScheduleServiceImpl.java

@@ -3916,6 +3916,7 @@ public class CourseScheduleServiceImpl extends BaseServiceImpl<Long, CourseSched
         }
         }
         CourseAttendanceDetailHeadInfoDto courseAttendanceDetailHeadInfoDto = courseScheduleDao.findByCourse(courseScheduleId);
         CourseAttendanceDetailHeadInfoDto courseAttendanceDetailHeadInfoDto = courseScheduleDao.findByCourse(courseScheduleId);
         courseAttendanceDetailHeadInfoDto.setLatestAttendanceTime(studentAttendanceDao.findLatestAttendanceDate(courseScheduleId));
         courseAttendanceDetailHeadInfoDto.setLatestAttendanceTime(studentAttendanceDao.findLatestAttendanceDate(courseScheduleId));
+        courseAttendanceDetailHeadInfoDto.setTruantStudentNum(studentAttendanceDao.getTruantStudentNum(courseScheduleId));
         return courseAttendanceDetailHeadInfoDto;
         return courseAttendanceDetailHeadInfoDto;
     }
     }
 
 

+ 95 - 61
mec-biz/src/main/java/com/ym/mec/biz/service/impl/ImLiveBroadcastRoomServiceImpl.java

@@ -620,13 +620,13 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
         });
         });
     }
     }
 
 
-    private void destroyExpiredLiveRoom(Date now, ImLiveBroadcastRoom room, int expiredMinute) throws Exception {
+    private boolean destroyExpiredLiveRoom(Date now, ImLiveBroadcastRoom room, int expiredMinute) throws Exception {
         log.error("roomDestroy destroyExpiredLiveRoom >>>> now {} roomInfo : {} expiredMinute:{}", now, JSONObject.toJSONString(room), expiredMinute);
         log.error("roomDestroy destroyExpiredLiveRoom >>>> now {} roomInfo : {} expiredMinute:{}", now, JSONObject.toJSONString(room), expiredMinute);
         //过期时间 = LiveStartTime + expiredMinute
         //过期时间 = LiveStartTime + expiredMinute
         Date expiredTime = DateUtil.addMinutes(room.getLiveStartTime(), expiredMinute);
         Date expiredTime = DateUtil.addMinutes(room.getLiveStartTime(), expiredMinute);
         //当前时间 小于 过期时间 则不销毁
         //当前时间 小于 过期时间 则不销毁
         if (now.getTime() <= expiredTime.getTime()) {
         if (now.getTime() <= expiredTime.getTime()) {
-            return;
+            return false;
         }
         }
         //获取直播间主讲人信息
         //获取直播间主讲人信息
         RBucket<RoomSpeakerInfo> speakerCache = getRoomSpeakerInfoCache(room.getRoomUid(), room.getSpeakerId().toString());
         RBucket<RoomSpeakerInfo> speakerCache = getRoomSpeakerInfoCache(room.getRoomUid(), room.getSpeakerId().toString());
@@ -637,7 +637,7 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
             if (Objects.nonNull(liveRoomUser) && liveRoomUser.getExist()  &&
             if (Objects.nonNull(liveRoomUser) && liveRoomUser.getExist()  &&
                 "1".equals(liveRoomUser.getStatus()) && StringUtils.equals(liveRoomUser.getStatus(), "1")) {
                 "1".equals(liveRoomUser.getStatus()) && StringUtils.equals(liveRoomUser.getStatus(), "1")) {
                 log.info("roomDestroy destroyExpiredLiveRoom  is online >>>> roomId:{} speakerId:{}", room.getId(), speakerInfo.getSpeakerId());
                 log.info("roomDestroy destroyExpiredLiveRoom  is online >>>> roomId:{} speakerId:{}", room.getId(), speakerInfo.getSpeakerId());
-                return;
+                return false;
             }
             }
             //校验房间心跳是否过期没续租
             //校验房间心跳是否过期没续租
             RBucket<Date> lastRoomHeartbeatCache = redissonClient.getBucket(LIVE_ROOM_SPEAKER_HEART_BEAT.replace(ROOM_UID, room.getRoomUid()));
             RBucket<Date> lastRoomHeartbeatCache = redissonClient.getBucket(LIVE_ROOM_SPEAKER_HEART_BEAT.replace(ROOM_UID, room.getRoomUid()));
@@ -649,14 +649,13 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
                 //当前时间 小于 房间心跳过期时间 则不销毁
                 //当前时间 小于 房间心跳过期时间 则不销毁
                 if (now.getTime() <= lastRoomDateExpired.getTime()) {
                 if (now.getTime() <= lastRoomDateExpired.getTime()) {
                     log.info("roomDestroy destroyExpiredLiveRoom  last room heartbeat  >>>> roomId:{} speakerId:{}", room.getId(), speakerInfo.getSpeakerId());
                     log.info("roomDestroy destroyExpiredLiveRoom  last room heartbeat  >>>> roomId:{} speakerId:{}", room.getId(), speakerInfo.getSpeakerId());
-                    return;
+                    return false;
                 }
                 }
             }
             }
             //1.主播没有进入房间,则直接销毁房间
             //1.主播没有进入房间,则直接销毁房间
             if (Objects.isNull(speakerInfo.getJoinRoomTime())) {
             if (Objects.isNull(speakerInfo.getJoinRoomTime())) {
                 log.info("roomDestroy not joinRoom >>>> cache : {}", JSONObject.toJSONString(test(room.getRoomUid())));
                 log.info("roomDestroy not joinRoom >>>> cache : {}", JSONObject.toJSONString(test(room.getRoomUid())));
-                roomDestroy(room);
-                return;
+                return roomDestroy(room);
             }
             }
 
 
             //2.已知主播已进入了房间 就判断是否退出过房间
             //2.已知主播已进入了房间 就判断是否退出过房间
@@ -668,11 +667,12 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
                     //现在时间 大于等于 最终过期时间 则证明退出后至少30分钟没进入过房间
                     //现在时间 大于等于 最终过期时间 则证明退出后至少30分钟没进入过房间
                     if (now.getTime() >= exitExpiredTime.getTime()) {
                     if (now.getTime() >= exitExpiredTime.getTime()) {
                         log.info("roomDestroy exitExpiredTime >>>> cache : {}", JSONObject.toJSONString(test(room.getRoomUid())));
                         log.info("roomDestroy exitExpiredTime >>>> cache : {}", JSONObject.toJSONString(test(room.getRoomUid())));
-                        roomDestroy(room);
+                        return roomDestroy(room);
                     }
                     }
                 }
                 }
             }
             }
         }
         }
+        return false;
     }
     }
 
 
     /**
     /**
@@ -682,10 +682,10 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
      * @param roomUid 直播间Uid
      * @param roomUid 直播间Uid
      */
      */
     @Override
     @Override
-    public void roomDestroy(String roomUid) {
+    public boolean roomDestroy(String roomUid) {
         ImLiveBroadcastRoom room = this.getOne(Wrappers.<ImLiveBroadcastRoom>lambdaQuery()
         ImLiveBroadcastRoom room = this.getOne(Wrappers.<ImLiveBroadcastRoom>lambdaQuery()
                 .eq(ImLiveBroadcastRoom::getRoomUid, roomUid));
                 .eq(ImLiveBroadcastRoom::getRoomUid, roomUid));
-        opsRoomDestroy(room);
+        return opsRoomDestroy(room);
     }
     }
 
 
     /**
     /**
@@ -695,14 +695,14 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
      * @param id 直播房间表id
      * @param id 直播房间表id
      */
      */
     @Override
     @Override
-    public void roomDestroy(Integer id) {
+    public boolean roomDestroy(Integer id) {
         ImLiveBroadcastRoom room = this.getById(id);
         ImLiveBroadcastRoom room = this.getById(id);
-        opsRoomDestroy(room);
+        return opsRoomDestroy(room);
     }
     }
 
 
-    private void opsRoomDestroy(ImLiveBroadcastRoom room) {
+    private boolean opsRoomDestroy(ImLiveBroadcastRoom room) {
         if (Objects.isNull(room)) {
         if (Objects.isNull(room)) {
-            return;
+            return false;
         }
         }
         if (room.getLiveState() == 0) {
         if (room.getLiveState() == 0) {
             throw new BizException("直播未开始");
             throw new BizException("直播未开始");
@@ -711,14 +711,47 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
             throw new BizException("直播已经结束,请刷新数据!");
             throw new BizException("直播已经结束,请刷新数据!");
         }
         }
         //销毁房间
         //销毁房间
-        roomDestroy(room);
+        return roomDestroy(room);
+    }
+
+    @Override
+    public void sendForcedOffline(ImLiveBroadcastRoom room) {
+
+        LivePluginService pluginService = livePluginContext.getPluginService(room.getServiceProvider());
+
+        LiveRoomMessage message = new LiveRoomMessage();
+        message.setIsIncludeSender(1);
+        message.setFromUserId(room.getSpeakerId().toString());
+        message.setToChatRoomId(room.getRoomUid());
+        message.setObjectName(ImRoomMessage.FORCED_OFFLINE);
+
+        SysUser sysUser = sysUserFeignService.queryUserInfo();
+        if (Objects.nonNull(sysUser)) {
+
+            // 发送用户信息
+            LiveRoomMessage.MessageUser messageUser = LiveRoomMessage.MessageUser.builder()
+                    .sendUserId(String.valueOf(room.getSpeakerId()))
+                    .sendUserName(sysUser.getUsername())
+                    .avatarUrl(sysUser.getAvatar())
+                    .build();
+
+            message.setContent(LiveRoomMessage.MessageContent.builder().sendUserInfo(messageUser).build());
+        }
+
+        try {
+            pluginService.sendChatRoomMessage(message);
+        } catch (Exception e) {
+
+            log.error("sendForcedOffline error", e);
+        }
+        log.info("roomDestroy>>>> FORCED_OFFLINE {}", JSONObject.toJSONString(message));
     }
     }
 
 
-    public void roomDestroy(ImLiveBroadcastRoom room) {
+    public boolean roomDestroy(ImLiveBroadcastRoom room) {
         //10秒内同一个房间不能重复销毁-防重复销毁
         //10秒内同一个房间不能重复销毁-防重复销毁
         RBucket<Object> bucket = redissonClient.getBucket("IM:ROOMDESTROY:" + room.getRoomUid());
         RBucket<Object> bucket = redissonClient.getBucket("IM:ROOMDESTROY:" + room.getRoomUid());
         if (!bucket.trySet(1, 10, TimeUnit.SECONDS)) {
         if (!bucket.trySet(1, 10, TimeUnit.SECONDS)) {
-            return;
+            return false;
         }
         }
         log.error("roomDestroy>>>> room : {}", JSONObject.toJSONString(room));
         log.error("roomDestroy>>>> room : {}", JSONObject.toJSONString(room));
         String roomUid = room.getRoomUid();
         String roomUid = room.getRoomUid();
@@ -755,28 +788,9 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
 
 
         //向聊天室发自定义消息踢出所有人
         //向聊天室发自定义消息踢出所有人
         try {
         try {
-            LiveRoomMessage message = new LiveRoomMessage();
-            message.setIsIncludeSender(1);
-            message.setFromUserId(speakerId.toString());
-            message.setToChatRoomId(roomUid);
-            message.setObjectName(ImRoomMessage.FORCED_OFFLINE);
-
-            SysUser sysUser = sysUserFeignService.queryUserInfo();
-            if (Objects.nonNull(sysUser)) {
-
-                // 发送用户信息
-                LiveRoomMessage.MessageUser messageUser = LiveRoomMessage.MessageUser.builder()
-                        .sendUserId(String.valueOf(speakerId))
-                        .sendUserName(sysUser.getUsername())
-                        .avatarUrl(sysUser.getAvatar())
-                        .build();
-
-                message.setContent(LiveRoomMessage.MessageContent.builder().sendUserInfo(messageUser).build());
-            }
-
             LivePluginService pluginService = livePluginContext.getPluginService(room.getServiceProvider());
             LivePluginService pluginService = livePluginContext.getPluginService(room.getServiceProvider());
-            pluginService.sendChatRoomMessage(message);
-            log.info("roomDestroy>>>> FORCED_OFFLINE {}", JSONObject.toJSONString(message));
+
+            sendForcedOffline(room);
 
 
             //销毁直播间
             //销毁直播间
             pluginService.chatRoomDestroy(roomUid);
             pluginService.chatRoomDestroy(roomUid);
@@ -806,10 +820,12 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
             }
             }
 
 
             //            imFeignService.destroyLiveRoom(roomUid);
             //            imFeignService.destroyLiveRoom(roomUid);
-            log.info("roomDestroy>>>> destroyLiveRoom {}", JSONObject.toJSONString(message));
+//            log.info("roomDestroy>>>> destroyLiveRoom {}", JSONObject.toJSONString(message));
         } catch (Exception e) {
         } catch (Exception e) {
             log.error("roomDestroy>>>> errorMsg{}", e.getMessage(), e.getCause());
             log.error("roomDestroy>>>> errorMsg{}", e.getMessage(), e.getCause());
+            return false;
         }
         }
+        return true;
     }
     }
 
 
     private String getStreamId(String roomUid, Integer speakerId) {
     private String getStreamId(String roomUid, Integer speakerId) {
@@ -2905,34 +2921,41 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
         }
         }
         for (ImLiveBroadcastRoom imLiveBroadcastRoom : list) {
         for (ImLiveBroadcastRoom imLiveBroadcastRoom : list) {
 
 
-            LivePluginService pluginService = livePluginContext.getPluginService(
-                imLiveBroadcastRoom.getServiceProvider());
-            try {
-                if (pluginService == null) {
-                    log.error("查询直播间流失败,未找到对应的插件");
-                    continue;
+            tryDestroyLiveRoom(imLiveBroadcastRoom);
+
+        }
+    }
+
+    @Override
+    public boolean tryDestroyLiveRoom(ImLiveBroadcastRoom imLiveBroadcastRoom) {
+        LivePluginService pluginService = livePluginContext.getPluginService(
+            imLiveBroadcastRoom.getServiceProvider());
+        try {
+            if (pluginService == null) {
+                log.error("查询直播间流失败,未找到对应的插件");
+                return false;
+            }
+            if (pluginService.pluginName().equals(TencentCloudLivePlugin.PLUGIN_NAME)) {
+                TencentWrapper.LiveStreamState liveStreamState = pluginService.liveStreamState(
+                    getStreamId(imLiveBroadcastRoom.getRoomUid(), imLiveBroadcastRoom.getSpeakerId()));
+                if (liveStreamState == null) {
+                    log.error("查询直播间流失败,返回结果为空");
+                    return false;
                 }
                 }
-                if (pluginService.pluginName().equals(TencentCloudLivePlugin.PLUGIN_NAME)) {
-                    TencentWrapper.LiveStreamState liveStreamState = pluginService.liveStreamState(
-                        getStreamId(imLiveBroadcastRoom.getRoomUid(), imLiveBroadcastRoom.getSpeakerId()));
-                    if (liveStreamState == null) {
-                        log.error("查询直播间流失败,返回结果为空");
-                        continue;
-                    }
-                    log.info("查询直播间流状态:{},roomUid:{}", JSON.toJSONString(liveStreamState), imLiveBroadcastRoom.getRoomUid());
-                    if (!"active".equals(liveStreamState.getStreamState())) {
-                        roomDestroy(imLiveBroadcastRoom.getRoomUid());
-                    }
-                } else if (pluginService.pluginName().equals(RongCloudLivePlugin.PLUGIN_NAME)) {
-                    // 融云走原有逻辑
-                    destroyExpiredLiveRoom(new Date(), imLiveBroadcastRoom, 0);
+                log.info("查询直播间流状态:{},roomUid:{}", JSON.toJSONString(liveStreamState), imLiveBroadcastRoom.getRoomUid());
+                if (!"active".equals(liveStreamState.getStreamState())) {
+                    return roomDestroy(imLiveBroadcastRoom.getRoomUid());
                 }
                 }
-            } catch (Exception e) {
-
-                log.error("查询直播间流失败", e);
+            } else if (pluginService.pluginName().equals(RongCloudLivePlugin.PLUGIN_NAME)) {
+                // 融云走原有逻辑
+                return destroyExpiredLiveRoom(new Date(), imLiveBroadcastRoom, 0);
             }
             }
+        } catch (Exception e) {
 
 
+            log.error("查询直播间流失败", e);
+            return false;
         }
         }
+        return true;
     }
     }
 
 
     /**
     /**
@@ -3076,6 +3099,17 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
                 .update();
                 .update();
     }
     }
 
 
+    @Override
+    public ImLiveBroadcastRoom getByRoomUid(String liveRoomId) {
+        List<ImLiveBroadcastRoom> list = this.lambdaQuery()
+                .eq(ImLiveBroadcastRoom::getRoomUid, liveRoomId)
+                .list();
+        if (CollectionUtils.isEmpty(list)) {
+            return null;
+        }
+        return list.get(0);
+    }
+
 
 
     /**
     /**
      * 查询直播间所有用户信息
      * 查询直播间所有用户信息

+ 53 - 0
mec-biz/src/main/java/com/ym/mec/biz/service/impl/VipGroupServiceImpl.java

@@ -33,6 +33,7 @@ import com.ym.mec.util.collection.MapUtil;
 import com.ym.mec.util.date.DateConvertor;
 import com.ym.mec.util.date.DateConvertor;
 import com.ym.mec.util.date.DateUtil;
 import com.ym.mec.util.date.DateUtil;
 import com.ym.mec.util.string.MessageFormatter;
 import com.ym.mec.util.string.MessageFormatter;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.collections.MapUtils;
 import org.apache.commons.collections.MapUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.Logger;
@@ -60,6 +61,7 @@ import static com.ym.mec.biz.dal.enums.SysUserRoleEnum.ORGAN_MANAGER;
 import static java.math.BigDecimal.ROUND_DOWN;
 import static java.math.BigDecimal.ROUND_DOWN;
 import static java.math.BigDecimal.ZERO;
 import static java.math.BigDecimal.ZERO;
 
 
+@Slf4j
 @Service
 @Service
 public class VipGroupServiceImpl extends BaseServiceImpl<Long, VipGroup> implements VipGroupService {
 public class VipGroupServiceImpl extends BaseServiceImpl<Long, VipGroup> implements VipGroupService {
 
 
@@ -348,6 +350,10 @@ public class VipGroupServiceImpl extends BaseServiceImpl<Long, VipGroup> impleme
 
 
         vipGroupApplyBaseInfoDto.setName(vipGroup.getLiveBroadcastRoom().getRoomTitle());
         vipGroupApplyBaseInfoDto.setName(vipGroup.getLiveBroadcastRoom().getRoomTitle());
         vipGroupApplyBaseInfoDto.setSubjectId(Integer.parseInt(vipGroup.getLiveBroadcastRoom().getSubjectId()));
         vipGroupApplyBaseInfoDto.setSubjectId(Integer.parseInt(vipGroup.getLiveBroadcastRoom().getSubjectId()));
+        if (CourseSchedule.CourseScheduleType.LIVE.getCode().equals(groupType)) {
+            vipGroupApplyBaseInfoDto.setTotalPrice(vipGroupApplyBaseInfoDto.getOnlineClassesUnitPrice().multiply(new BigDecimal(vipGroupApplyBaseInfoDto.getOnlineClassesNum())));
+            vipGroupApplyBaseInfoDto.setOriginalTotalPrice(vipGroupApplyBaseInfoDto.getOfflineClassesUnitPrice().multiply(new BigDecimal(vipGroupApplyBaseInfoDto.getOnlineClassesNum())));
+        }
 
 
         vipGroupDao.update(vipGroupApplyBaseInfoDto);
         vipGroupDao.update(vipGroupApplyBaseInfoDto);
 
 
@@ -4997,11 +5003,58 @@ public class VipGroupServiceImpl extends BaseServiceImpl<Long, VipGroup> impleme
     @Override
     @Override
     public void closeLiveCourseRoom() {
     public void closeLiveCourseRoom() {
 
 
+        Integer studentRemindTime = Integer.parseInt(sysConfigService.findByParamName(SysConfigService.liveClassStudentRemindTime).getParanValue());
+
+        Integer closeTime = Integer.parseInt(sysConfigService.findByParamName(SysConfigService.closeClassStudentRemindTime).getParanValue());
         // 查询非连堂课  且  已经结束的课程
         // 查询非连堂课  且  已经结束的课程
+        List<CourseSchedule> scheduleList = courseScheduleDao.getCloseLiveCourseRoom(studentRemindTime, null);
 
 
         // 通知学生退出直播间
         // 通知学生退出直播间
 
 
+        scheduleList.forEach(data -> {
+            ImLiveBroadcastRoom imLiveBroadcastRoomVo = imLiveBroadcastRoomService.getByRoomUid(data.getLiveRoomId());
+            if (!Objects.isNull(imLiveBroadcastRoomVo) ) {
+                if(imLiveBroadcastRoomVo.getLiveState() == 1) {
+
+                    try {
+                        imLiveBroadcastRoomService.sendForcedOffline(imLiveBroadcastRoomVo);
+
+                        courseScheduleDao.updateLiveRemind(data.getId(), 1);
+                    } catch (Exception e) {
+                        log.error("发送直播间退出消息失败", e);
+                    }
+                } else if (imLiveBroadcastRoomVo.getLiveState() == 2) {
+                    // 直播未开始 设置通知状态
+                    courseScheduleDao.updateLiveRemind(data.getId(), 2);
+                }
+            }
+        });
+
         // 没有直播流 关闭直播间
         // 没有直播流 关闭直播间
+        scheduleList = courseScheduleDao.getCloseLiveCourseRoom(null, closeTime);
+
+        // 检测直播间是否有直播流
+        scheduleList.forEach(data -> {
+            ImLiveBroadcastRoom imLiveBroadcastRoomVo = imLiveBroadcastRoomService.getByRoomUid(data.getLiveRoomId());
+            if (!Objects.isNull(imLiveBroadcastRoomVo) ) {
+                if(imLiveBroadcastRoomVo.getLiveState() == 1) {
+                    // 关闭直播间
+                    try {
+                        boolean b = imLiveBroadcastRoomService.tryDestroyLiveRoom(imLiveBroadcastRoomVo);
+                        if (b) {
+                            // 直播已关闭 设置通知状态
+                            courseScheduleDao.updateLiveRemind(data.getId(), 2);
+                        }
+                    } catch (Exception e) {
+                        log.error("关闭直播间失败", e);
+                    }
+                } else if (imLiveBroadcastRoomVo.getLiveState() == 2) {
+                    // 直播已关闭 设置通知状态
+                    courseScheduleDao.updateLiveRemind(data.getId(), 2);
+                }
+            }
+
+        });
 
 
     }
     }
 }
 }

+ 2 - 5
mec-biz/src/main/resources/config/mybatis/ClassGroupStudentMapperMapper.xml

@@ -737,6 +737,7 @@
         cssp.be_merged_,
         cssp.be_merged_,
         st.current_grade_num_,
         st.current_grade_num_,
         st.current_class_,
         st.current_class_,
+        cssp.join_course_type_ as joinCourseType,
         IF(sa.status_ IS NULL,'TRUANT',sa.status_) status_
         IF(sa.status_ IS NULL,'TRUANT',sa.status_) status_
         FROM
         FROM
         course_schedule_student_payment cssp
         course_schedule_student_payment cssp
@@ -749,11 +750,7 @@
             and (su.username_ like CONCAT('%',#{param.search},'%') )
             and (su.username_ like CONCAT('%',#{param.search},'%') )
         </if>
         </if>
         <if test="param.status != null">
         <if test="param.status != null">
-            and (
-            <if test="param.status.name == 'TRUANT'">
-                sa.status_ is null or
-            </if>
-            sa.status_ = #{param.status})
+           and cssp.join_course_type_ = #{param.status,typeHandler=com.ym.mec.common.dal.CustomEnumTypeHandler}
         </if>
         </if>
         AND su.id_ IS NOT NULL
         AND su.id_ IS NOT NULL
     </select>
     </select>

+ 17 - 1
mec-biz/src/main/resources/config/mybatis/CourseScheduleMapper.xml

@@ -187,7 +187,6 @@
         cs.service_provider_,
         cs.service_provider_,
         cs.mute_all_,
         cs.mute_all_,
         cs.live_room_id_,
         cs.live_room_id_,
-        cs.live_room_id_,
         cs.continuous_course_
         cs.continuous_course_
     </sql>
     </sql>
 
 
@@ -4362,6 +4361,23 @@
         </foreach>
         </foreach>
     </update>
     </update>
 
 
+    <select id="getCloseLiveCourseRoom" resultMap="CourseSchedule">
+        select * from course_schedule
+        where status_ = 'OVER' and type_ = 'LIVE'  and  continuous_course_ = 0
+        <if test="studentRemindTime != null" >
+            and CONCAT(class_date_,' ',end_class_time_) &lt;= date_format(date_add(now(),interval -#{studentRemindTime} minute),'%Y-%m-%d %H:%i:%s')
+            and live_remind_ = 0
+        </if>
+        <if test="closeTime != null" >
+            and CONCAT(class_date_,' ',end_class_time_) &lt;= date_format(date_add(now(),interval -#{closeTime} minute),'%Y-%m-%d %H:%i:%s')
+            and live_remind_ = 1
+        </if>
+    </select>
+
+    <update id="updateLiveRemind">
+        update course_schedule set live_remind_ = #{liveRemind} where id_ = #{courseScheduleId}
+    </update>
+
     <select id="getTeacherContinuousCourse" resultMap="CourseSchedule">
     <select id="getTeacherContinuousCourse" resultMap="CourseSchedule">
         SELECT <include refid="resultSql"/> FROM course_schedule cs
         SELECT <include refid="resultSql"/> FROM course_schedule cs
         WHERE cs.class_group_id_ = #{classGroupId}
         WHERE cs.class_group_id_ = #{classGroupId}

+ 6 - 0
mec-biz/src/main/resources/config/mybatis/StudentAttendanceMapper.xml

@@ -745,4 +745,10 @@
         </foreach>
         </foreach>
         AND cs.class_date_ BETWEEN #{queryDto.startTime} AND #{queryDto.endTime}
         AND cs.class_date_ BETWEEN #{queryDto.startTime} AND #{queryDto.endTime}
     </select>
     </select>
+
+    <select id="getTruantStudentNum" resultType="java.lang.Integer">
+        select COUNT(cssp.user_id_) from course_schedule_student_payment cssp
+        left join student_attendance sa on sa.course_schedule_id_ = cssp.course_schedule_id_ and sa.user_id_ = cssp.user_id_
+        where cssp.course_schedule_id_ = #{courseId} and (sa.status_ = 'TRUANT' or sa.id_ is null)
+    </select>
 </mapper>
 </mapper>