liujc hai 1 ano
pai
achega
e85ac5b361

+ 13 - 0
cooleshow-user/user-admin/src/main/java/com/yonge/cooleshow/admin/controller/open/ImController.java

@@ -200,10 +200,23 @@ public class ImController extends BaseController {
         // 断流事件通知
         if (event.getEventType() == 0) {
             imUserState.setStatus("3");
+
+            // 更新推流时长
+            if (StringUtils.isNotBlank(event.getPushDuration()) && event.getPushDuration().matches("\\d+")) {
+                // 更新直播推流时长
+                liveRoomService.updateLiveRoomPushStreamTime(event);
+            }
+
+            // 自动关闭录制
+            liveRoomService.closeLive(getRoomUid(event.getStreamId()), getSpeakerId(event.getStreamId()),event.getSequence());
+            // 同步点赞数
+            liveRoomService.syncLikeCount(getRoomUid(event.getStreamId()));
         }
 
         // 推流事件通知
         if (event.getEventType() == 1) {
+            // 自动开启录制
+            liveRoomService.startLive(getRoomUid(event.getStreamId()), getSpeakerId(event.getStreamId()),event.getSequence());
 
             imUserState.setStatus("0");
         }

+ 2 - 0
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/constant/LiveRoomConstant.java

@@ -39,5 +39,7 @@ public interface LiveRoomConstant {
     String TEACHER_TEMP_LIVE_ROOM = String.join(":", COOLESHOW, "TEACHER_TEMP_LIVE_ROOM");
     //主讲人信息
     String LIVE_SPEAKER_INFO = String.join(":", "IM:LIVE_SPEAKER_INFO", ROOM_UID, USER_ID);
+    //计算人员观看时长锁
+    public static final String LIVE_LOOK_LOCK = String.join(":", "IM:LIVE_LOOK_LOCK", ROOM_UID);
 
 }

+ 2 - 2
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/entity/ImLiveBroadcastRoomMember.java

@@ -24,7 +24,7 @@ public class ImLiveBroadcastRoomMember implements Serializable {
 
     @ApiModelProperty("主键") 
 	    @TableId(value = "id_", type = IdType.AUTO)
-        private Integer id;
+        private Long id;
 
     @ApiModelProperty("房间编号") 
 	@TableField(value = "room_uid_")
@@ -32,7 +32,7 @@ public class ImLiveBroadcastRoomMember implements Serializable {
 
     @ApiModelProperty("进入房间的人员id/学生id") 
 	@TableField(value = "user_id_")
-    private Integer userId;
+    private Long userId;
 
     @ApiModelProperty("进入房间时间") 
 	@TableField(value = "join_time_")

+ 5 - 0
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/entity/LiveRoom.java

@@ -88,6 +88,11 @@ public class LiveRoom implements Serializable {
     @ApiModelProperty(value = "预热模版")
     private String preTemplate;
 
+
+    @TableField("live_total_time_")
+    @ApiModelProperty(value = "直播时长")
+    private Integer liveTotalTime;
+
     @TableField("os_")
     @ApiModelProperty(value = "播出端-  pc网页端 移动端mobile")
     private String os;

+ 13 - 1
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/service/LiveRoomService.java

@@ -99,7 +99,7 @@ public interface LiveRoomService extends IService<LiveRoom> {
      * @param roomUid 房间uid
      * @param userId  观看者id
      */
-    RoomInfoCache joinRoom(String roomUid, Long userId);
+    void joinRoom(String roomUid, Long userId);
 
     /**
      * 发送消息
@@ -219,5 +219,17 @@ public interface LiveRoomService extends IService<LiveRoom> {
      * @param whetherMicStatus 连麦状态
      */
     void userWhetherMic(String roomUid, Long userId, Integer whetherMicStatus);
+
+    void joinRoom(String roomUid, Long userId, Boolean microphoneFlag);
+
+    /**
+     * 更新直播推流时间
+     * @param event TencentData.CallbackStreamStateEvent
+     */
+    void updateLiveRoomPushStreamTime(TencentData.CallbackStreamStateEvent event);
+
+    void closeLive(String roomUid, Integer speakerId, String sequence);
+
+    void startLive(String roomUid, Integer speakerId, String sequence);
 }
 

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

@@ -9,6 +9,7 @@ import java.text.MessageFormat;
 import java.time.LocalDate;
 import java.time.ZoneId;
 import java.util.*;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.function.BiFunction;
 import java.util.function.BiPredicate;
@@ -34,6 +35,7 @@ import com.yonge.cooleshow.biz.dal.service.*;
 import com.yonge.cooleshow.biz.dal.wrapper.liveroom.ImLiveBroadcastRoomMemberWrapper;
 import com.yonge.cooleshow.biz.dal.wrapper.liveroom.LiveRoomWrapper;
 import com.yonge.cooleshow.common.enums.EGroupDefinedDataType;
+import com.yonge.toolset.payment.util.DistributedLock;
 import lombok.Data;
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
@@ -126,6 +128,8 @@ public class LiveRoomServiceImpl extends ServiceImpl<LiveRoomDao, LiveRoom> impl
 
     @Autowired
     private ImLiveBroadcastRoomMemberMapper imLiveBroadcastRoomMemberMapper;
+    @Autowired
+    private ImLiveBroadcastRoomMemberService imLiveBroadcastRoomMemberService;
 
     @Autowired
     private ImGroupService imGroupService;
@@ -788,6 +792,7 @@ public class LiveRoomServiceImpl extends ServiceImpl<LiveRoomDao, LiveRoom> impl
             if (StringUtils.isBlank(user.getStatus())) {
                 return;
             }
+            Date now = new Date();
             String userIdStr = user.getUserid();
             //获取当前用户所在房间的uid
             RBucket<String> userRoom = redissonClient.getBucket(LIVE_USER_ROOM.replace(USER_ID, userIdStr));
@@ -796,7 +801,6 @@ public class LiveRoomServiceImpl extends ServiceImpl<LiveRoomDao, LiveRoom> impl
             }
             String roomUid = Optional.ofNullable(user.getRoomUid()).orElse(userRoom.get());
 
-            Date now = new Date();
             //获取当前用户状态变更的时间
             long userStateTime = Optional.ofNullable(user.getTime()).orElse(now.getTime());
             RBucket<Long> userStateTimeCache = redissonClient.getBucket(LIVE_USER_LAST_TIME.replace(USER_ID, userIdStr));
@@ -868,6 +872,23 @@ public class LiveRoomServiceImpl extends ServiceImpl<LiveRoomDao, LiveRoom> impl
                     .eq(StudentAttendance::getStudentId, userId)
                     .eq(StudentAttendance::getCourseScheduleId, schedule.getId())
                     .set(StudentAttendance::getSignOutTime, now));
+
+            // 更新用户离线状态
+            ImLiveBroadcastRoomMember roomMember = new ImLiveBroadcastRoomMember();
+            roomMember.setOnlineStatus(0);
+
+            // 用户离开直播间
+            if (user.getStatus().equals("3")) {
+                // 直播间用户离开状态
+                roomMember.setLiveRoomStatus(0);
+
+            }
+            // 更新用户在线状态为离线
+            imLiveBroadcastRoomMemberService.lambdaUpdate()
+                    .eq(ImLiveBroadcastRoomMember::getRoomUid, roomVo.getRoomUid())
+                    .eq(ImLiveBroadcastRoomMember::getUserId, userId)
+                    .update(roomMember);
+
             //向直播间发送当前在线人数消息
             this.sendOnlineUserCount(roomUid, userId, onlineUserInfo.size());
             log.info("opsRoom>>>> looker userInfo: {}", userJsonStr);
@@ -1019,50 +1040,9 @@ public class LiveRoomServiceImpl extends ServiceImpl<LiveRoomDao, LiveRoom> impl
      * @param roomUid 房间uid
      * @param userId  用户id 观看者id
      */
-    public RoomInfoCache joinRoom(String roomUid, Long userId) {
-        Optional.ofNullable(roomUid).orElseThrow(() -> new BizException("房间编号不能为空!"));
-        Optional.ofNullable(userId).orElseThrow(() -> new BizException("人员编号不能为空!"));
-        //获取进入房间人员信息
-        SysUser sysUser = this.getSysUser(userId);
-        //校验信息
-        RoomInfoCache roomInfo = this.checkStudentRoom(roomUid, sysUser);
-        Date now = new Date();
-        //房间累计用户信息-指只要进入到该房间的用户都要记录
-        RMap<Long, String> roomTotalUser = this.getTotalUserCache(roomUid);
-        //判断是否第一次进房间
-        RoomUserInfoCache userInfo;
-        liveBroadcastRoomMemberService.saveRecord(roomUid,userId, InOrOutEnum.IN, YesOrNoEnum.YES);
-        if (roomTotalUser.containsKey(userId)) {
-            //多次进入更新动态进入时间
-            userInfo = JSONObject.toJavaObject(JSONObject.parseObject(roomTotalUser.get(userId)), RoomUserInfoCache.class);
-        } else {
-            //第一次进该房间 写入用户首次进入时间
-            userInfo = new RoomUserInfoCache();
-            userInfo.setUserId(sysUser.getId());
-            userInfo.setUserName(sysUser.getRealName());
-            userInfo.setFirstJoinTime(now);
-        }
-
+    public void joinRoom(String roomUid, Long userId) {
 
-        CourseSchedule schedule = getCourseScheduleByRoomUid(roomUid);
-        //查询学生是否有进入过,没有则写学生考勤表的进入时间
-        this.setStudentAttendance(userId, schedule.getCourseGroupId(), schedule.getId());
-        userInfo.setDynamicJoinTime(now);
-        //用户json信息
-        String userJsonStr = JSONObject.toJSONString(userInfo);
-        //写入人员缓存
-        roomTotalUser.fastPut(userId, userJsonStr);
-        //写入在线人员缓存
-        RMap<Long, String> onlineUserCache = this.getOnlineUserCache(roomUid);
-        onlineUserCache.fastPut(userId, userJsonStr);
-        log.info("joinRoom>>>> userInfo: {}", userJsonStr);
-        //向直播间发送当前在线人数消息
-        this.sendOnlineUserCount(roomUid, userId, onlineUserCache.size());
-        log.info("join sendOnlineUserCount>>>> param is null   roomUid: {}  fromUserId:{}  count:{}", roomUid, userId, onlineUserCache.size());
-        log.info("joinRoom>>>> userInfo: {}", userJsonStr);
-        //记录当前用户对应的房间uid
-        redissonClient.getBucket(LIVE_USER_ROOM.replace(USER_ID, userId.toString())).set(roomUid, 2L, TimeUnit.DAYS);
-        return roomInfo;
+        joinRoom(roomUid, userId, true);
     }
 
     //校验学生与房间的关系
@@ -1711,13 +1691,20 @@ public class LiveRoomServiceImpl extends ServiceImpl<LiveRoomDao, LiveRoom> impl
         if (room == null) {
             throw new BizException("直播间不存在");
         }
+
+        room.setId(room.getId());
+
+
+        room.setBanStatus(liveRoom.getBanStatus());
         // 设置直播群组自定义数据
         if (liveRoom.getSpeakerStatus() != null) {
+            room.setSpeakerStatus(liveRoom.getSpeakerStatus());
             setGroupDefinedData(room, EGroupDefinedDataType.ANCHOR_STATUS,
                     liveRoom.getSpeakerStatus() == 0 ? EAnchorStatus.OFFLINE.getCode() : EAnchorStatus.ONLINE.getCode());
         }
 
         if (liveRoom.getPushStatus() != null) {
+            room.setPushStatus(liveRoom.getPushStatus());
             // 设置推流状态
             setGroupDefinedData(room, EGroupDefinedDataType.LIVE_STATUS, liveRoom.getPushStatus() == 1 ?
                     EOnOffStatus.ON.getCode() : EOnOffStatus.OFF.getCode());
@@ -1725,6 +1712,7 @@ public class LiveRoomServiceImpl extends ServiceImpl<LiveRoomDao, LiveRoom> impl
 
 
         if (liveRoom.getBanStatus() != null) {
+            room.setBanStatus(liveRoom.getBanStatus());
             setGroupDefinedData(room, EGroupDefinedDataType.GLOBAL_BAN, liveRoom.getBanStatus() == 1 ?
                     EOnOffStatus.ON.getCode() : EOnOffStatus.OFF.getCode());
         }
@@ -1740,7 +1728,8 @@ public class LiveRoomServiceImpl extends ServiceImpl<LiveRoomDao, LiveRoom> impl
             setGroupDefinedData(room, EGroupDefinedDataType.ANCHOR_MIC,
                     liveRoom.getMicStatus() == 1 ? EOnOffStatus.ON.getCode() : EOnOffStatus.OFF.getCode());
         }
-        return true;
+
+        return this.updateById(room);
     }
 
     /**
@@ -1986,6 +1975,384 @@ public class LiveRoomServiceImpl extends ServiceImpl<LiveRoomDao, LiveRoom> impl
         imLiveBroadcastRoomMemberMapper.userWhetherMic(roomUid, userId, whetherMicStatus);
     }
 
+    @Override
+    public void joinRoom(String roomUid, Long userId, Boolean microphoneFlag) {
+        Optional.ofNullable(roomUid).orElseThrow(() -> new BizException("房间编号不能为空!"));
+        Optional.ofNullable(userId).orElseThrow(() -> new BizException("人员编号不能为空!"));
+
+        LiveRoomWrapper.LiveRoomVo liveRoomVo = queryRoomInfo(roomUid);
+
+        //记录用户当前房间uid
+        redissonClient.getBucket(LIVE_USER_ROOM.replace(USER_ID, userId.toString())).set(roomUid, 12L, TimeUnit.HOURS);
+
+        //判断是否第一次进房间
+        ImLiveBroadcastRoomMemberWrapper.RoomUserInfoVo userInfo;
+
+        //房间累计用户信息-指只要进入到该房间的用户都要记录
+        RMap<Long, String> roomTotalUser = getTotalUserCache(roomUid);
+        if (roomTotalUser.containsKey(userId)) {
+            //多次进入更新动态进入时间
+            userInfo = JSONObject.toJavaObject(JSONObject.parseObject(roomTotalUser.get(userId)), ImLiveBroadcastRoomMemberWrapper.RoomUserInfoVo.class);
+
+            ImLiveBroadcastRoomMember liveRoomMember = imLiveBroadcastRoomMemberService.lambdaQuery()
+                    .eq(ImLiveBroadcastRoomMember::getRoomUid, liveRoomVo.getRoomUid())
+                    .eq(ImLiveBroadcastRoomMember::getUserId, userInfo.getUserId())
+                    .list().stream().findFirst().orElse(null);
+
+            if (Objects.isNull(liveRoomMember)) {
+
+                // 初次进入房间
+                getLiveRoomUserInfo(userId, liveRoomVo, microphoneFlag);
+            } else {
+                // 更新直播间用户信息
+                ImLiveBroadcastRoomMember roomMember = new ImLiveBroadcastRoomMember();
+                roomMember.setId(liveRoomMember.getId());
+                roomMember.setOnlineStatus(1);
+                roomMember.setLiveRoomStatus(1);
+                roomMember.setMicrophoneFlag(microphoneFlag);
+
+                imLiveBroadcastRoomMemberService.updateById(roomMember);
+            }
+
+        } else {
+            //第一次进该房间 写入用户首次进入时间
+            userInfo = getLiveRoomUserInfo(userId, liveRoomVo, microphoneFlag);
+        }
+
+        //查询主讲人信息
+        RBucket<RoomSpeakerInfo> speakerCache = getRoomSpeakerInfoCache(roomUid, liveRoomVo.getSpeakerId().toString());
+        if (speakerCache.isExists()) {
+            //如果用户进来时主讲人已经开启直播则修改学生观看时间
+            Integer state = speakerCache.get().getState();
+            if (Objects.nonNull(state) && state == 0 && liveRoomVo.getPushStatus() == 1) {
+                userInfo.setDynamicLookTime(DateTime.now().toDate());
+            }
+        }
+        CourseSchedule schedule = getCourseScheduleByRoomUid(roomUid);
+        //查询学生是否有进入过,没有则写学生考勤表的进入时间
+        this.setStudentAttendance(userId, schedule.getCourseGroupId(), schedule.getId());
+        //用户json信息
+        String userJsonStr = JSONObject.toJSONString(userInfo);
+        //写入人员缓存
+        roomTotalUser.fastPut(userId, userJsonStr);
+        //写入在线人员缓存
+        RMap<Long, String> onlineUserCache = this.getOnlineUserCache(roomUid);
+        onlineUserCache.fastPut(userId, userJsonStr);
+        log.info("joinRoom>>>> userInfo: {}", userJsonStr);
+        //向直播间发送当前在线人数消息
+        this.sendOnlineUserCount(roomUid, userId, onlineUserCache.size());
+        log.info("join sendOnlineUserCount>>>> param is null   roomUid: {}  fromUserId:{}  count:{}", roomUid, userId, onlineUserCache.size());
+        log.info("joinRoom>>>> userInfo: {}", userJsonStr);
+        //记录当前用户对应的房间uid
+        redissonClient.getBucket(LIVE_USER_ROOM.replace(USER_ID, userId.toString())).set(roomUid, 2L, TimeUnit.DAYS);
+    }
+
+    /**
+     * 更新直播推流时间
+     *
+     * @param event TencentData.CallbackStreamStateEvent
+     */
+    @Override
+    public void updateLiveRoomPushStreamTime(TencentData.CallbackStreamStateEvent event) {
+
+        // 直播间ROOM_UID
+        String roomId = event.getStreamId().split("_")[0];
+
+        LiveRoom room = getByRoomUid(roomId);
+        if (Objects.isNull(room)) {
+            log.warn("updateLiveRoomPushStreamTime 无效的直播间ID, roomId:{}", roomId);
+            return;
+        }
+
+        // 更新直播时长
+        LiveRoom entity = new LiveRoom();
+        // 主键ID
+        entity.setId(room.getId());
+        // 直播累积播放时长
+        int liveTotalTime = Optional.ofNullable(room.getLiveTotalTime()).orElse(0) + Integer.parseInt(event.getPushDuration());
+        // 累积播放时长
+        entity.setLiveTotalTime(liveTotalTime);
+
+        updateById(entity);
+    }
+
+    @Override
+    public void closeLive(String roomUid, Integer userId, String sequence) {
+        //查询房间主播信息
+        RBucket<RoomSpeakerInfo> speakerCache = getRoomSpeakerInfoCache(roomUid, userId.toString());
+        if (!speakerCache.isExists()) {
+            return;
+        }
+        RoomSpeakerInfo roomSpeakerInfo = speakerCache.get();
+
+        //关闭直播
+        if (sequence.equals(roomSpeakerInfo.getSequence())) {
+            setPushStatus(roomSpeakerInfo.getRoomUid(), 0);
+        }
+
+
+        speakerCache.set(roomSpeakerInfo);
+
+        Date now = new Date();
+        roomSpeakerInfo.setEndLiveTime(now);
+        roomSpeakerInfo.setState(1);
+        //如果开播时间和本次操作结束播放时间小于1分钟则不计算观看时间
+        int liveMinutes = getLookMillisecond(roomSpeakerInfo.getStartLiveTime(), null);
+        if (liveMinutes > 1) {
+            //写入本次直播时长
+            roomSpeakerInfo.setTotalLiveTime(getLookMillisecond(roomSpeakerInfo.getStartLiveTime(), roomSpeakerInfo.getTotalLiveTime()));
+            //关闭直播后异步执行计算房间人员观看时长
+            CompletableFuture.runAsync(() -> this.asyncOpsLiveLookTime(roomSpeakerInfo.getRoomUid(), 2, now));
+        }
+        //计算完后将开始直播时间设置为空,待下次开启后再计算
+        roomSpeakerInfo.setStartLiveTime(null);
+        log.info("closeLive>>>> roomSpeakerInfo: {}", JSONObject.toJSONString(roomSpeakerInfo));
+    }
+
+
+    @Override
+    public void startLive(String roomUid, Integer userId, String sequence) {
+        //查询房间信息
+        RBucket<RoomSpeakerInfo> speakerCache = getRoomSpeakerInfoCache(roomUid, userId.toString());
+        if (!speakerCache.isExists()) {
+            return;
+        }
+        RoomSpeakerInfo roomSpeakerInfo = speakerCache.get();
+        //已是直播状态则直接返回
+        if (roomSpeakerInfo.getState() ==  0) {
+            return;
+        }
+
+
+        // 设置推流状态房间信息
+        setPushStatus(roomSpeakerInfo.getRoomUid(), 1);
+
+        //开始录制视频
+        try {
+            // 查询房间信息
+            LiveRoom imLiveBroadcastRoomVo = getByRoomUid(roomUid);
+            if (Objects.isNull(imLiveBroadcastRoomVo)) {
+                log.info("opsRoom>>>> startRecord error roomUid: {}", roomUid);
+                return;
+            }
+            LivePluginService pluginService = livePluginContext.getPluginService(imLiveBroadcastRoomVo.getServiceProvider());
+            RTCRoom rtcRoom = pluginService.rtcRoomInfo(roomSpeakerInfo.getRoomUid());
+            if (rtcRoom == null) {
+                log.info("startRecord>>>> rtcRoom is null");
+                return;
+            }
+
+            if (imLiveBroadcastRoomVo.getServiceProvider().equals(TencentCloudLivePlugin.PLUGIN_NAME) && roomSpeakerInfo.getVideoTemplate() == 0) {
+                // 直播开始时间
+                DateTime dateTime = new DateTime(imLiveBroadcastRoomVo.getLiveStartTime());
+
+                RTCRoom.RecordResp recordResp = pluginService.rtcRoomRecordStart(RTCRequest.RecordStart.builder()
+                        .startTime(dateTime.getMillis() )
+                        .endTime(dateTime.plusDays(1).getMillis() )
+                        .streamName(MessageFormat.format("{0}_{1}", imLiveBroadcastRoomVo.getRoomUid(), String.valueOf(imLiveBroadcastRoomVo.getSpeakerId())))
+                        .build());
+                roomSpeakerInfo.setVideoTemplate(1);
+
+                log.info("startLive 直播录录开启: recordResp={}", JSON.toJSONString(recordResp));
+            }
+
+
+        } catch (Exception e) {
+            log.error("startRecord error: {}", e.getMessage());
+        }
+        Date now = new Date();
+        //开始直播
+        roomSpeakerInfo.setState(0);
+        roomSpeakerInfo.setSequence(sequence);
+        roomSpeakerInfo.setStartLiveTime(now);
+        speakerCache.set(roomSpeakerInfo);
+        log.info("startLive>>>> roomSpeakerInfo: {}", JSONObject.toJSONString(roomSpeakerInfo));
+        //主播开启直播,查询所有在直播间的用户并写入观看时间
+        CompletableFuture.runAsync(() -> this.asyncOpsLiveLookTime(roomUid, 1, now));
+    }
+
+
+    /**
+     * 计算观看时长差-毫秒数
+     *
+     * @param startDT    开始时间
+     * @param nowMinutes 现在观看时长
+     */
+    private int getLookMillisecond(Date startDT, Integer nowMinutes) {
+        return getLookMillisecond(startDT, new Date(), nowMinutes);
+    }
+    /**
+     * 计算观看时长差-毫秒数
+     *
+     * @param startDT    开始时间
+     * @param endDT      结束时间
+     * @param nowMinutes 现在观看时长
+     */
+    private int getLookMillisecond(Date startDT, Date endDT, Integer nowMinutes) {
+        if (Objects.isNull(nowMinutes)) {
+            nowMinutes = 0;
+        }
+        if (Objects.isNull(startDT)) {
+            return nowMinutes;
+        }
+        if (startDT.getTime() > endDT.getTime()) {
+            return nowMinutes;
+        }
+        //课程结束时间-课程开始时间
+        long durationTime = endDT.getTime() - startDT.getTime();
+        //相差多少分钟
+        int minutesBetween = new Long(durationTime).intValue();
+        minutesBetween += nowMinutes;
+        return Math.max(minutesBetween, 0);
+    }
+
+
+    /**
+     * 打开/关闭直播后-异步计算房间人员观看时长
+     *
+     * @param roomUid 房间uid
+     * @param type    type 1:开始直播-开始录像     2:关闭直播关闭录像
+     */
+    private void asyncOpsLiveLookTime(String roomUid, Integer type, Date now) {
+        //加锁-避免快速点击开启直播和关闭直后异步执行后直播数据错误
+        boolean b = DistributedLock.of(redissonClient).runIfLockCanGet(LIVE_LOOK_LOCK.replace(ROOM_UID, roomUid), () -> {
+            //查询所有在直播间的用户并计算观看时长
+            RMap<Long, String> roomTotalUser = getTotalUserCache(roomUid);
+            if (!roomTotalUser.isExists()) {
+                return;
+            }
+            //查询在线人员列表
+            RMap<Long, String> onlineUserInfo = getOnlineUserCache(roomUid);
+            boolean onlineUserInfoExists = onlineUserInfo.isExists();
+            roomTotalUser.forEach((id, userInfoStr) -> {
+                ImLiveBroadcastRoomMemberWrapper.RoomUserInfoVo userInfo = JSONObject.toJavaObject(JSONObject.parseObject(userInfoStr), ImLiveBroadcastRoomMemberWrapper.RoomUserInfoVo.class);
+                //获取当前用户是否在房间状态 true在 false不在
+                if (onlineUserInfoExists && onlineUserInfo.containsKey(id)) {
+                    if (type.equals(1)) {
+                        //开启直播后对当前在房间的用户写入观看时间
+                        userInfo.setDynamicLookTime(now);
+                    } else if (type.equals(2)) {
+                        userInfo.setTotalViewTime(getLookMinutes(userInfo.getDynamicLookTime(), now, userInfo.getTotalViewTime()));
+                        userInfo.setDynamicLookTime(null);
+                    } else {
+                        return;
+                    }
+                    roomTotalUser.fastPut(id, JSONObject.toJSONString(userInfo));
+                }
+            });
+        }, 2, TimeUnit.MINUTES);
+        if (!b) {
+            try {
+                Thread.sleep(200L);
+            } catch (InterruptedException e) {
+                log.error("asyncOpsLiveLookTime error: {}", e.getMessage());
+            }
+            this.asyncOpsLiveLookTime(roomUid, type, now);
+        }
+    }
+
+
+    /**
+     * 计算观看时长差-分钟数不满一分钟为0
+     *
+     * @param startDT    开始时间
+     * @param endDT      结束时间
+     * @param nowMinutes 现在观看时长
+     */
+    private int getLookMinutes(Date startDT, Date endDT, Integer nowMinutes) {
+        if (Objects.isNull(nowMinutes)) {
+            nowMinutes = 0;
+        }
+        if (Objects.isNull(startDT)) {
+            return nowMinutes;
+        }
+        if (startDT.getTime() > endDT.getTime()) {
+            return nowMinutes;
+        }
+        //课程结束时间-课程开始时间
+        long durationTime = endDT.getTime() - startDT.getTime();
+        //相差多少分钟
+        int minutesBetween = new Long(durationTime / 1000 / 60).intValue();
+        minutesBetween += nowMinutes;
+        return Math.max(minutesBetween, 0);
+    }
+
+    /**
+     * 设置推流状态房间信息
+     * @param roomUid 房间uid
+     * @param status 0:关闭直播 1:开启直播
+     */
+    private void setPushStatus(String roomUid, Integer status) {
+        if (status == null) {
+            return;
+        }
+        LiveRoom liveRoom = getByRoomUid(roomUid);
+        if (liveRoom == null) {
+            log.warn("setPushStatus liveRoom is null");
+            return;
+        }
+
+        LiveRoom imLiveBroadcastRoom = new LiveRoom();
+        imLiveBroadcastRoom.setId(liveRoom.getId());
+        imLiveBroadcastRoom.setPushStatus(status);
+        this.updateById(imLiveBroadcastRoom);
+        setGroupDefinedData(liveRoom,EGroupDefinedDataType.LIVE_STATUS,status == 1?
+                EOnOffStatus.ON.getCode():EOnOffStatus.OFF.getCode());
+
+    }
+
+    private ImLiveBroadcastRoomMemberWrapper.RoomUserInfoVo getLiveRoomUserInfo(Long userId, LiveRoomWrapper.LiveRoomVo imLiveBroadcastRoomVo, Boolean microphoneFlag) {
+
+        ImLiveBroadcastRoomMemberWrapper.RoomUserInfoVo userInfo = getUserInfo(userId);
+        userInfo.setFirstJoinTime(DateTime.now().toDate());
+        userInfo.setTotalViewTime(0);
+
+        // 记录直播间用户信息
+        ImLiveBroadcastRoomMember roomMember = new ImLiveBroadcastRoomMember();
+        roomMember.setRoomUid(imLiveBroadcastRoomVo.getRoomUid());
+        roomMember.setUserId(userInfo.getUserId());
+        roomMember.setJoinTime(DateTime.now().toDate());
+        roomMember.setTotalTime(0);
+        roomMember.setOnlineStatus(1);
+        roomMember.setBanStatus(0);
+        roomMember.setLiveRoomStatus(1);
+        roomMember.setMicrophoneFlag(microphoneFlag);
+
+        ImLiveBroadcastRoomMember liveRoomMember = imLiveBroadcastRoomMemberService.lambdaQuery()
+                .eq(ImLiveBroadcastRoomMember::getRoomUid, imLiveBroadcastRoomVo.getRoomUid())
+                .eq(ImLiveBroadcastRoomMember::getUserId, userInfo.getUserId())
+                .list().stream().findFirst().orElse(null);
+        if (Objects.nonNull(liveRoomMember)) {
+            return userInfo;
+        }
+
+
+        // 保存直播间用户信息
+        imLiveBroadcastRoomMemberService.save(roomMember);
+
+        return userInfo;
+    }
+
+    private ImLiveBroadcastRoomMemberWrapper.RoomUserInfoVo getUserInfo(Long userId) {
+        ImLiveBroadcastRoomMemberWrapper.RoomUserInfoVo userInfo = new ImLiveBroadcastRoomMemberWrapper.RoomUserInfoVo();
+        userInfo.setUserId(userId);
+        SysUser sysUser = sysUserFeignService.queryUserById(userId);
+        String name = userId.toString();
+        if (Objects.nonNull(sysUser)) {
+            name = sysUser.getPhone();
+            if (sysUser.getUserType().contains("STUDENT")) {
+                name = sysUser.getUsername();
+            } else {
+                if (StringUtils.isNotBlank(sysUser.getRealName())) {
+                    name = sysUser.getRealName();
+                }
+            }
+        }
+        userInfo.setUserName(name);
+        return userInfo;
+    }
+
+
     /**
      * 主讲人信息
      */
@@ -2020,8 +2387,6 @@ public class LiveRoomServiceImpl extends ServiceImpl<LiveRoomDao, LiveRoom> impl
         private Integer totalLiveTime = 0;
         //是否录像 0允许 1不允许
         private Integer whetherVideo;
-        //机构
-        private Integer tenantId;
         //播出端-  pc网页端 移动端mobile
         private String os = "pc";
 

+ 18 - 0
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/wrapper/liveroom/ImLiveBroadcastRoomMemberWrapper.java

@@ -6,6 +6,7 @@ import com.microsvc.toolkit.common.response.paging.QueryInfo;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 
+import java.io.Serializable;
 import java.util.Date;
 import java.util.Optional;
 
@@ -118,4 +119,21 @@ public class ImLiveBroadcastRoomMemberWrapper {
     }
 
 
+    @Data
+    public static class RoomUserInfoVo extends LiveRoomWrapper.BaseRoomUserVo implements Serializable {
+
+        //累计观看时长
+        private Integer totalViewTime;
+
+        //首次进入时间
+        @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+        private Date firstJoinTime;
+
+        //动态观看直播时间  主播每次开启直播后更新
+        @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+        private Date dynamicLookTime;
+
+        @ApiModelProperty("用户头象")
+        private String avatar;
+    }
 }

+ 1 - 1
cooleshow-user/user-biz/src/main/java/com/yonge/cooleshow/biz/dal/wrapper/liveroom/LiveRoomWrapper.java

@@ -231,7 +231,7 @@ public class LiveRoomWrapper implements Serializable {
     @Data
     public static class BaseRoomUserVo implements Serializable {
 
-        private Integer userId;
+        private Long userId;
 
         private String userName;
     }

+ 71 - 0
cooleshow-user/user-student/src/main/java/com/yonge/cooleshow/student/controller/ImLiveBroadcastRoomController.java

@@ -0,0 +1,71 @@
+package com.yonge.cooleshow.student.controller;
+
+import com.yonge.cooleshow.biz.dal.entity.ImUserStateSync;
+import com.yonge.cooleshow.biz.dal.service.LiveRoomService;
+import com.yonge.cooleshow.biz.dal.wrapper.liveroom.LiveRoomWrapper;
+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 io.swagger.annotations.ApiParam;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * 直播房间管理表(ImLiveBroadcastRoom)表控制层
+ *
+ * @author hgw
+ * @since 2022-02-17 20:52:04
+ */
+@Api(tags = "直播房间管理表")
+@RestController
+@RequestMapping("/imLiveBroadcastRoom")
+public class ImLiveBroadcastRoomController extends BaseController {
+    /**
+     * 服务对象
+     */
+    @Resource
+    private LiveRoomService liveRoomService;
+
+    @ApiOperation("学生端-查询房间信息")
+    @GetMapping("/queryRoomInfo")
+    public HttpResponseResult<LiveRoomWrapper.LiveRoomVo> queryRoomInfo(@ApiParam(value = "房间uid", required = true) String roomUid) {
+        return succeed(liveRoomService.queryRoomInfo(roomUid));
+    }
+
+
+    @ApiOperation("查询房间信息并校验房间是否合规")
+    @GetMapping("/queryRoom")
+    public HttpResponseResult<LiveRoomWrapper.LiveRoomVo> queryRoomAndCheck(@ApiParam(value = "房间uid", required = true) String roomUid,
+                                                                       @ApiParam(value = "用户id", required = true) Long userId) {
+        return succeed(liveRoomService.queryRoomAndCheck(roomUid, userId, 1));
+    }
+
+    @PostMapping("/quitRoom")
+    public HttpResponseResult<Object> quitRoom(@RequestBody List<ImUserStateSync> userState) {
+        liveRoomService.opsRoom(userState);
+        return succeed();
+    }
+
+    @ApiOperation("学生-进入房间")
+    @GetMapping("/joinRoom")
+    public HttpResponseResult<Object> joinRoom(String roomUid, Long userId,Boolean microphoneFlag) {
+        if (microphoneFlag == null) {
+            microphoneFlag = true;
+        }
+        liveRoomService.joinRoom(roomUid, userId,microphoneFlag);
+        return succeed();
+    }
+
+    @ApiOperation("设置连麦状态")
+    @PutMapping("/userWhetherMic")
+    public HttpResponseResult<Object> userWhetherMic(@ApiParam(value = "房间uid", required = true) String roomUid,
+                                                     @ApiParam(value = "用户id", required = true) Long userId,
+                                                     @ApiParam(value = "连麦状态 0:未申请1:申请连麦中2:连麦中", required = true) Integer whetherMicStatus) {
+        liveRoomService.userWhetherMic(roomUid,userId,whetherMicStatus);
+        return succeed();
+    }
+}
+

+ 4 - 8
cooleshow-user/user-student/src/main/java/com/yonge/cooleshow/student/controller/StudentLiveRoomController.java

@@ -44,12 +44,7 @@ public class StudentLiveRoomController extends BaseController {
      */
     @Resource
     private LiveRoomService liveRoomService;
-    
-    @Autowired
-    private SysUserFeignService sysUserFeignService;
-    
-    @Autowired
-    private StudentService studentService;
+
 
     /**
      * 校验房间信息,及个人信息
@@ -63,8 +58,9 @@ public class StudentLiveRoomController extends BaseController {
 
     @ApiOperation("观看者-进入房间")
     @GetMapping(value = "/joinRoom")
-    public HttpResponseResult<RoomInfoCache> joinRoom(String roomUid, Long userId) {
-        return succeed(liveRoomService.joinRoom(roomUid, userId));
+    public HttpResponseResult<Object> joinRoom(String roomUid, Long userId) {
+        liveRoomService.joinRoom(roomUid, userId);
+        return succeed();
     }
 
     /**

+ 0 - 2
cooleshow-user/user-teacher/src/main/java/com/yonge/cooleshow/teacher/controller/TeacherImLiveBroadcastRoomController.java

@@ -35,8 +35,6 @@ public class TeacherImLiveBroadcastRoomController extends BaseController {
      */
     @Resource
     private LiveRoomService liveRoomService;
-    @Autowired
-    private LiveBroadcastRoomMemberService liveBroadcastRoomMemberService;
 
     @Autowired
     private ImLiveBroadcastRoomMemberService imLiveBroadcastRoomMemberService;

+ 0 - 6
cooleshow-user/user-teacher/src/main/java/com/yonge/cooleshow/teacher/controller/TeacherLiveRoomController.java

@@ -78,12 +78,6 @@ public class TeacherLiveRoomController extends BaseController {
         return succeed();
     }
 
-    @ApiOperation("主讲人-进入房间")
-    @GetMapping(value = "/speakerJoinRoom")
-    public HttpResponseResult<RoomInfoCache> speakerJoinRoom(String roomUid) {
-        return succeed(liveRoomService.speakerJoinRoom(roomUid));
-    }
-
 
     @ApiOperation("设置是否允许连麦")
     @GetMapping("/whetherMic")