package com.yonge.cooleshow.biz.dal.service.impl;

import static com.yonge.cooleshow.biz.dal.constant.LiveRoomConstant.*;
import static com.yonge.cooleshow.common.constant.SysConfigConstant.DESTROY_EXPIRED_LIVE_ROOM_MINUTE;
import static com.yonge.cooleshow.common.constant.SysConfigConstant.PRE_CREATE_LIVE_ROOM_MINUTE;

import java.io.Serializable;
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;
import java.util.function.Function;
import java.util.stream.Collectors;

import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.microsvc.toolkit.middleware.live.LivePluginContext;
import com.microsvc.toolkit.middleware.live.LivePluginService;
import com.microsvc.toolkit.middleware.live.impl.RongCloudLivePlugin;
import com.microsvc.toolkit.middleware.live.impl.TencentCloudLivePlugin;
import com.microsvc.toolkit.middleware.live.message.*;
import com.yonge.cooleshow.api.feign.dto.RoomConfig;
import com.yonge.cooleshow.biz.dal.dto.LiveRoomStatus;
import com.yonge.cooleshow.biz.dal.dto.TencentData;
import com.yonge.cooleshow.biz.dal.entity.*;
import com.yonge.cooleshow.biz.dal.enums.live.EAnchorStatus;
import com.yonge.cooleshow.biz.dal.enums.live.EOnOffStatus;
import com.yonge.cooleshow.biz.dal.mapper.ImLiveBroadcastRoomMemberMapper;
import com.yonge.cooleshow.biz.dal.mapper.SysUserMapper;
import com.yonge.cooleshow.biz.dal.redisson.RedissonMessageService;
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.constant.SysConfigConstant;
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;
import org.joda.time.DateTime;
import org.redisson.api.RBucket;
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.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
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.enums.ClientEnum;
import com.yonge.cooleshow.biz.dal.enums.CourseScheduleEnum;
import com.yonge.cooleshow.biz.dal.enums.InOrOutEnum;
import com.yonge.cooleshow.biz.dal.enums.MessageTypeEnum;
import com.yonge.cooleshow.biz.dal.enums.OrderStatusEnum;
import com.yonge.cooleshow.biz.dal.enums.RoomTypeEnum;
import com.yonge.cooleshow.biz.dal.enums.TeacherSalaryEnum;
import com.yonge.cooleshow.biz.dal.support.IMHelper;
import com.yonge.cooleshow.biz.dal.support.WrapperUtil;
import com.yonge.cooleshow.biz.dal.vo.TeacherLivingInfoVo;
import com.yonge.cooleshow.biz.dal.vo.UserOrderVo;
import com.yonge.cooleshow.common.enums.YesOrNoEnum;
import com.yonge.toolset.base.exception.BizException;
import com.yonge.toolset.base.page.PageInfo;
import com.yonge.toolset.mybatis.support.PageUtil;
import com.yonge.toolset.thirdparty.message.MessageSenderPluginContext;
import com.yonge.toolset.utils.date.DateUtil;

/**
 * 直播房间与课程的关系表表(LiveRoom)表服务实现类
 *
 * @author hgw
 * @since 2022-03-18 15:41:17
 */
@Service("liveRoomService")
public class LiveRoomServiceImpl extends ServiceImpl<LiveRoomDao, LiveRoom> implements LiveRoomService {
    private final static Logger log = LoggerFactory.getLogger(LiveRoomServiceImpl.class);

    @Autowired
    private SysUserFeignService sysUserFeignService;
    @Autowired
    private IMHelper imHelper;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private CourseGroupService courseGroupService;
    @Autowired
    private CourseScheduleService courseScheduleService;
    @Autowired
    private SysConfigService sysConfigService;
    @Autowired
    private UserOrderService userOrderService;
    @Autowired
    private CourseScheduleTeacherSalaryService courseScheduleTeacherSalaryService;
    @Autowired
    private CourseScheduleStudentPaymentService courseScheduleStudentPaymentService;
    @Autowired
    private StudentAttendanceService studentAttendanceService;
    @Autowired
    private TeacherAttendanceService teacherAttendanceService;
    @Autowired
    private SysMessageService sysMessageService;

    @Autowired
    private LiveBroadcastRoomMemberService liveBroadcastRoomMemberService;
    @Autowired
    private SysUserMapper sysUserMapper;
    @Autowired
    private LivePluginContext livePluginContext;

    @Autowired
    private LiveRoomService liveRoomService;

    @Autowired
    private LiveRoomVideoService liveRoomVideoService;

    @Autowired
    private LiveBroadcastRoomDataService liveBroadcastRoomDataService;

    @Autowired
    private ImLiveBroadcastRoomMemberMapper imLiveBroadcastRoomMemberMapper;
    @Autowired
    private ImLiveBroadcastRoomMemberService imLiveBroadcastRoomMemberService;

    @Autowired
    private ImGroupService imGroupService;

    @Autowired
    private RedissonMessageService redissonMessageService;

    /**
     * 生成房间UID
     *
     * <p> userId：主讲人id
     * <p> en ：房间类型
     */
    private final BiFunction<Long, RoomTypeEnum, String> GenRoomUid = (userId, en) -> String
            .join("-", en.equals(RoomTypeEnum.TEMP)?RoomTypeEnum.LIVE.getCode() +"-"+en.getCode():en.getCode(), userId.toString(), new Date().getTime() + "");

    @Override
    public LiveRoomDao getDao() {
        return this.baseMapper;
    }

    /**
     * 分页查询当前用户关联的房间信息
     *
     * @param param 参数
     *              <p>roomType 房间类型  LIVE-直播课  TEMP-临时直播间
     *              <p>liveState 直播状态 0未开始 1已开始 2已结束
     *              <p>page 页数
     *              <p>rows 每页数量
     */
    @Override
    public PageInfo<LiveRoom> queryPageRoom(Map<String, Object> param) {
        String roomType = WrapperUtil.toStr(param, "roomType");
        Integer liveState = WrapperUtil.toInt(param, "liveState");
        Long userId = this.getSysUser().getId();
        Page<LiveRoom> pageInfo = PageUtil.getPageInfo(param);
        pageInfo.setDesc("id_");
        IPage<LiveRoom> page = this.page(pageInfo, Wrappers.<LiveRoom>lambdaQuery()
                .eq(WrapperUtil.StrPredicate.test(roomType), LiveRoom::getType, roomType)
                .eq(WrapperUtil.ObjPredicate.test(liveState), LiveRoom::getLiveState, liveState)
                .in(LiveRoom::getRoomState, 0,2)
                .eq(LiveRoom::getSpeakerId, userId)
        );
        return PageUtil.pageInfo(page);
    }

    /**
     * 校验房间信息，及个人信息
     *
     * @param roomUid 房间uid
     */
    @Override
    public RoomInfoCache studentCheckRoomInfo(String roomUid) {
        return this.checkStudentRoom(roomUid, this.getSysUser());
    }

    /**
     * 主讲人查询并校验房间信息
     *
     * @param roomUid 房间uid
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public LiveRoomWrapper.LiveRoomVo speakerCheckRoomInfo(String roomUid) {

        SysUser sysUser = getSysUser();
        Long userId = sysUser.getId();
        LiveRoomWrapper.LiveRoomVo liveRoom = queryRoomInfo(roomUid);
        LiveRoom room = getByRoomUid(roomUid);
        if (Objects.isNull(liveRoom)) {
            log.error("teacherCheckRoomInfo>>>live not start  roomUid: {} userId:{}", roomUid, userId);
            throw new BizException("直播还未开始!");
        }
        if (liveRoom.getLiveState().equals(2)) {
            throw new BizException("直播已结束");
        }
        //校验房间是否存在
        RBucket<RoomInfoCache> roomInfoCache = getLiveRoomInfo(roomUid);
        if (!roomInfoCache.isExists()) {
            log.error("teacherCheckRoomInfo>>>live not start  roomUid: {} userId:{}", roomUid, userId);
            throw new BizException("直播还未开始!");
        }
        Date now = new Date();
        //校验进入房间的是否是该直播间的主讲人
        if (!liveRoom.getSpeakerId().equals(userId)) {
            log.error("teacherCheckRoomInfo>>>not speaker  roomUid: {} userId:{}", roomUid, userId);
            throw new BizException("您不是该直播间的主讲人!");
        }
        speakCache(room, userId);
        return liveRoom;
    }

    private void speakCache(LiveRoom room, Long userId) {
        //获取直播间信息
        RBucket<RoomSpeakerInfo> speakerCache = getRoomSpeakerInfoCache(room.getRoomUid(), userId.toString());
        if (!speakerCache.isExists()) {

            //没有主讲人信息则生成一个
            createSpeakerInfo(this.getById(room.getId()), userId);

            // 查询主播缓存信息
            speakerCache = getRoomSpeakerInfoCache(room.getRoomUid(), userId.toString());
        }
        RoomSpeakerInfo speakerInfo = speakerCache.get();
        speakerInfo.setJoinRoomTime(new Date());
        speakerCache.set(speakerInfo);

        //记录用户当前房间uid
        redissonClient.getBucket(LIVE_USER_ROOM.replace(USER_ID, userId.toString())).set(room.getRoomUid(), 12L, TimeUnit.HOURS);

        // 更新直播间主播状态
        lambdaUpdate()
                .eq(LiveRoom::getRoomUid, room.getRoomUid())
                .eq(LiveRoom::getSpeakerId, userId)
                .set(LiveRoom::getSpeakerStatus, 1)
                .update();

        // 设置直播群组自定义数据
        setGroupDefinedData(room,EGroupDefinedDataType.ANCHOR_STATUS,EAnchorStatus.ONLINE.getCode());
    }

    /**
     * 获取房间主讲人消息
     *
     * @param roomUid 房间uid
     * @param userId  用户id
     */
    private RBucket<RoomSpeakerInfo> getRoomSpeakerInfoCache(String roomUid, String userId) {
        return redissonClient.getBucket(LIVE_SPEAKER_INFO.replace(USER_ID, userId).replace(ROOM_UID, roomUid));
    }


    //生成主讲人信息
    private void createSpeakerInfo(LiveRoom room, Long userId) {

        SysUser sysUser = sysUserFeignService.queryUserById(userId);

        Date now = new Date();
        RoomSpeakerInfo speakerInfo = new RoomSpeakerInfo();
        speakerInfo.setSpeakerId(sysUser.getId());
        speakerInfo.setSpeakerName(sysUser.getRealName());
        speakerInfo.setCreateRoomTime(now);
        speakerInfo.setRoomUid(room.getRoomUid());
        speakerInfo.setTotalLiveTime(0);

        //可以录制视频
        speakerInfo.setWhetherVideo(0);
        // 设置模板
        speakerInfo.setVideoTemplate(1);

        speakerInfo.setOs(room.getOs());
        //写入主讲人信息
        getRoomSpeakerInfoCache(room.getRoomUid(), room.getSpeakerId().toString()).set(speakerInfo);

        //生成0点赞
        redissonClient.getBucket(LIVE_ROOM_LIKE.replace(ROOM_UID, room.getRoomUid())).set(0);

        //修改房间状态为开始
        LiveRoom liveRoomUpdate = new LiveRoom();
        liveRoomUpdate.setId(room.getId());
        liveRoomUpdate.setLiveState(1);
        liveRoomUpdate.setUpdatedTime(now);
        liveRoomUpdate.setUpdatedBy(-2L);
        liveRoomUpdate.setSpeakerStatus(1);
        baseMapper.updateById(liveRoomUpdate);
    }
    /**
     * 定时任务创建直播间
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void createCourseLiveRoom() {
        //查询房间提前创建的时间
        String preCreateRoomMinute = sysConfigService.findConfigValue(PRE_CREATE_LIVE_ROOM_MINUTE);
        if (StringUtils.isEmpty(preCreateRoomMinute)) {
            log.info("createCourseLiveRoom>>>> 未查询到配置：{}", PRE_CREATE_LIVE_ROOM_MINUTE);
            return;
        }
        //查询课时表生成直播间
        List<CourseSchedule> courseScheduleList = courseScheduleService.list(Wrappers.<CourseSchedule>lambdaQuery()
                .eq(CourseSchedule::getType, CourseScheduleEnum.LIVE.getCode())
                .in(CourseSchedule::getStatus, CourseScheduleEnum.NOT_START.getCode(), CourseScheduleEnum.ING.getCode())
                .eq(CourseSchedule::getLock, 0)
                .eq(CourseSchedule::getClassDate, LocalDate.now().toString()));
        if (CollectionUtils.isEmpty(courseScheduleList)) {
            return;
        }
        //获取课程组名称
        Map<Long, String> titleMap = new HashMap<>();
        //获取课程组介绍
        Map<Long, String> remarkMap = new HashMap<>();
        courseScheduleList.stream()
                .map(CourseSchedule::getCourseGroupId)
                .distinct()
                .map(courseGroupService::getById)
                .filter(Objects::nonNull)
                .forEach(courseGroup -> {
                    titleMap.put(courseGroup.getId(), courseGroup.getName());
                    remarkMap.put(courseGroup.getId(), courseGroup.getCourseIntroduce());
                });
        //生成课程对应的房间
        RoomTypeEnum en = RoomTypeEnum.LIVE;
        Date now = new Date();
        courseScheduleList.forEach(c -> {
            try {
                DistributedLock.of(redissonClient).runIfLockCanGet("LIVE_COURSE_CREATE:"+c.getId(), () ->
                    liveRoomService.updateLiveCourseSchedule(preCreateRoomMinute, titleMap, remarkMap, en, now, c));
            } catch (Exception e) {
                log.error("createCourseLiveRoom>>>生成直播间失败:{}", c, e);
            }
        });
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateLiveCourseSchedule(String preCreateRoomMinute, Map<Long, String> titleMap, Map<Long, String> remarkMap, RoomTypeEnum en, Date now, CourseSchedule c) {
        //课程开始时间-提前创建时间 = 创建房间时间
        Date createRoomTime = DateUtil.addMinutes(c.getStartTime(), -Integer.parseInt(preCreateRoomMinute));
        if (now.getTime() < createRoomTime.getTime()) {
            return;
        }
        //避免重复创建直播间

        // 如果是同时在管乐迷和酷乐秀直播的老师

        LiveRoom room = new LiveRoom();
        String teacherIds = sysConfigService.findConfigValue(SysConfigConstant.LIVE_TEACHER_IDS);
        if (StringUtils.isNotBlank(c.getRoomUid())) {
            if ((StringUtils.isBlank(teacherIds) || !Arrays.asList(teacherIds.split(",")).contains(c.getTeacherId().toString()))) {
                log.info("createCourseLiveRoom>>>roomUid:{} 已存在", c.getRoomUid());
                return;
            } else {
                LiveRoom liveRoom = getByRoomUid(c.getRoomUid());
                if (liveRoom != null) {
                    log.info("createCourseLiveRoom>>>roomUid:{} 已存在", c.getRoomUid());
                   return;
                } else {
                    // 设置直播间不显示购物车
                    RoomConfig roomConfig = new RoomConfig();
                    // 1:是隐藏， 和管乐迷一样
                    roomConfig.setWhether_view_shop_cart(1);
                    room.setRoomConfig(JSON.toJSONString(roomConfig));

                }
            }
        }
        if (StringUtils.isBlank(c.getRoomUid())) {
            room.setRoomUid(GenRoomUid.apply(c.getTeacherId(), en));
        } else {
            room.setRoomUid(c.getRoomUid());
        }
        if (StringUtils.isNotBlank(teacherIds) && Arrays.asList(teacherIds.split(",")).contains(c.getTeacherId().toString())) {
            room.setOs("pc");
        }
        room.setRoomTitle(titleMap.get(c.getCourseGroupId()));
        room.setLiveRemark(remarkMap.get(c.getCourseGroupId()));
        room.setSpeakerId(c.getTeacherId());
        room.setLiveStartTime(c.getStartTime());
        room.setLiveEndTime(c.getEndTime());
        room.setLiveState(1);
        room.setRoomState(0);
        room.setType(en.getCode());
        room.setCreatedBy(-2L);
        room.setCreatedTime(now);
        this.save(room);
        // 更新课程房间信息
        c.setRoomUid(room.getRoomUid());
        courseScheduleService.updateById(c);
        //去融云创建房间及创建房间缓存信息
        this.createLiveRoomInfo(room);
        //开课提醒
        this.pushLiveCreateRoom(room);

        if (StringUtils.isNotBlank(teacherIds) && Arrays.asList(teacherIds.split(",")).contains(room.getSpeakerId().toString())) {
            speakCache(room, room.getSpeakerId());
        }
    }

    /**
     * 开课提醒
     */
    private void pushLiveCreateRoom(LiveRoom room) {
        log.info("pushLiveCreateRoom JIGUANG start param : {}", JSONObject.toJSONString(room));
        try {
            //查询老师信息
            SysUser teacherInfo = this.getSysUser(room.getSpeakerId());
            //极光-消息推送
            Map<Long, String> teacherMap = new HashMap<>();
            teacherMap.put(teacherInfo.getId(), teacherInfo.getPhone());
            sysMessageService.batchSendMessage(MessageSenderPluginContext.MessageSender.JIGUANG, MessageTypeEnum.LIVE_REMINDER,
                    teacherMap, null, 0, null, ClientEnum.TEACHER.getCode(),
                    teacherInfo.getUsername(), room.getRoomTitle());
            log.info("pushLiveCreateRoom JIGUANG ok ");
        } catch (Exception e) {
            log.error("pushLiveCreateRoom JIGUANG error param : {}", JSONObject.toJSONString(room));
            log.error("pushLiveCreateRoom JIGUANG error", e.getCause());
        }
    }

    /**
     * 创建临时房间-直播间
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String createTempLiveRoom(Map<String, Object> param) {
        //查询主讲人信息
        SysUser sysUser = this.getSysUser();
        Long teacherId = sysUser.getId();
        List<LiveRoom> liveRoomList = this.list(Wrappers.<LiveRoom>lambdaQuery()
                .eq(LiveRoom::getSpeakerId, teacherId)
                .eq(LiveRoom::getType, RoomTypeEnum.TEMP.getCode())
                .eq(LiveRoom::getLiveState, 1)
                .eq(LiveRoom::getRoomState, 0)
        );
        if (CollectionUtils.isNotEmpty(liveRoomList)) {
            throw new BizException("当前有直播间未结束，无需创建直播！");
        }
        String roomTitle = WrapperUtil.toStr(param, "roomTitle", "房间标题不能为空!");
        String liveRemark = WrapperUtil.toStr(param, "liveRemark", "直播间描述不能为空!");
        Integer liveTime = WrapperUtil.toInt(param, "liveTime", "请选择直播时长!");
        String coverPic = WrapperUtil.toStr(param, "coverPic", "直播封面图不能为空!");
        if (roomTitle.length() > 20) {
            throw new BizException("直播标题不能超出20个字");
        }
        Date liveStartTime = new Date();
        Date liveEndTime = DateUtil.addMinutes(liveStartTime, liveTime);
        //当前时间有课程则不能开启直播
        boolean check = courseScheduleService.checkTeacherCourseTime(teacherId, liveStartTime, liveEndTime);
        if (check) {
            //如果当前时间和课程时间有交集则不能开启临时直播
            throw new BizException("当前选择的时间段有课无法开启直播");
        }
        RoomTypeEnum en = RoomTypeEnum.TEMP;
        String roomUid = GenRoomUid.apply(teacherId, en);
        LiveRoom room = new LiveRoom();
        room.setRoomUid(roomUid);
        room.setRoomTitle(roomTitle);
        room.setLiveRemark(liveRemark);
        room.setSpeakerId(teacherId);
        room.setLiveStartTime(liveStartTime);
        room.setLiveEndTime(liveEndTime);
        room.setLiveState(1);
        room.setRoomState(0);
        room.setCreatedBy(teacherId);
        room.setCreatedTime(liveStartTime);
        room.setType(en.getCode());
        room.setCoverPic(coverPic);
        this.save(room);
        log.info("createTempLiveRoom>>>>>>room:{}", room.getRoomUid());
        //去融云创建房间及创建房间缓存信息
        this.createLiveRoomInfo(room, sysUser);
        //写入临时直播间列表
        RMap<Long, String> map = redissonClient.getMap(TEACHER_TEMP_LIVE_ROOM);
        map.put(teacherId, roomUid);
        return roomUid;
    }

    /**
     * 去融云创建房间及创建房间缓存信息-直播间
     */
    private void createLiveRoomInfo(LiveRoom room) {
        //查询主讲人信息
        SysUser sysUser = this.getSysUser(room.getSpeakerId());
        this.createLiveRoomInfo(room, sysUser);
    }

    private void createLiveRoomInfo(LiveRoom room, SysUser sysUser) {
        try {
            //生成主讲人信息
            this.createRoomInfoCache(room, sysUser);
            //去融云创建房间

            //去融云创建房间
            LivePluginService pluginService = livePluginContext.getPluginService(room.getServiceProvider());

            String imUserId = imGroupService.getImUserId(sysUser.getId(), ClientEnum.TEACHER);
            // 注册主播用户信息到三方平台
            pluginService.register(imUserId, sysUser.getUsername(), sysUser.getAvatar());
            // 创建直播间IM群
            pluginService.chatRoomCreate(room.getRoomUid(), room.getRoomTitle(),imUserId);
            // 腾讯云直播，提前生成录制规则
            if (room.getServiceProvider().equals(TencentCloudLivePlugin.PLUGIN_NAME)) {

                DateTime now = DateTime.now();

                RTCRequest.RecordStart recordStart = RTCRequest.RecordStart.builder()
                        .streamName(getStreamId(room.getRoomUid(),room.getSpeakerId()))
                        .extra("")
                        .startTime(now.toDateTime().getMillis())
                        .endTime(now.plusDays(1).toDateTime().getMillis())
                        .build();

                if (StringUtils.isBlank(room.getVideoRecord())) {

                    // 生成录制任务
                    log.info("createLiveRoom>>>>>> recordStart:{}", recordStart.jsonString());
                    try {

                        RTCRoom.RecordResp resp = pluginService.rtcRoomRecordStart(recordStart);
                        // 设置录制任务Id
                        LiveRoom update = new LiveRoom();
                        update.setId(room.getId());
                        if (StringUtils.isBlank(room.getVideoRecord())) {
                            update.setVideoRecord(resp.getRecordId());
                        } else {
                            update.setVideoRecord(room.getVideoRecord() + "," + resp.getRecordId());
                        }
                        updateById(update);
                    }catch (Exception e) {
                        log.error("createLiveRoom>>>>>> recordStart error:{}", e.getMessage());
                    }

                }
                // 设置直播间房间属性默认值
                List<TencentWrapper.ChatRoomGroupCounter> chatRoomGroupDefinedData = pluginService.getChatRoomGroupDefinedData(room.getRoomUid());
                if (CollectionUtils.isEmpty(chatRoomGroupDefinedData)) {
                    setDefaultRoomDefinedInfo(room);
                }
            }
        } catch (Exception e) {
            throw new BizException("创建直播间失败!", e);
        }
    }


    private void setDefaultRoomDefinedInfo(LiveRoom roomVo) {
        List<TencentRequest.ChatRoomGroupDefinedData> appDefinedData = new ArrayList<>();
        Arrays.stream(EGroupDefinedDataType.values()).forEach(key -> {
            switch (key) {
                case ANCHOR_STATUS:
                    appDefinedData.add(TencentRequest.ChatRoomGroupDefinedData.builder()
                            .key(key.getCode())
                            .value("OFFLINE")
                            .build());
                    break;
                case GLOBAL_BAN:
                    appDefinedData.add(TencentRequest.ChatRoomGroupDefinedData.builder()
                            .key(key.getCode())
                            .value("OFF")
                            .build());
                    break;
                case LIKES:
                    appDefinedData.add(TencentRequest.ChatRoomGroupDefinedData.builder()
                            .key(key.getCode())
                            .value("0")
                            .build());
                    break;

                case MEMBER_ONLINE:
                    appDefinedData.add(TencentRequest.ChatRoomGroupDefinedData.builder()
                            .key(key.getCode())
                            .value("0")
                            .build());
                    break;
                case MEMBER_TOTAL:
                    appDefinedData.add(TencentRequest.ChatRoomGroupDefinedData.builder()
                            .key(key.getCode())
                            .value("0")
                            .build());
                    break;
                case LIVE_STATUS:
                    appDefinedData.add(TencentRequest.ChatRoomGroupDefinedData.builder()
                            .key(key.getCode())
                            .value("OFF")
                            .build());
                    break;
                case ANCHOR_CAMERA:
                    appDefinedData.add(TencentRequest.ChatRoomGroupDefinedData.builder()
                            .key(key.getCode())
                            .value("ON")
                            .build());
                    break;
            }
        });
        try {
            livePluginContext.getPluginService(roomVo.getServiceProvider())
                    .chatRoomGroupDefinedData(TencentRequest.ChatRoomGroup.builder()
                            .groupId(roomVo.getRoomUid())
                            .appDefinedData(appDefinedData)
                            .build());
        } catch (Exception e) {
            log.error("设置直播群配置失败", e);
        }
    }

    //生成主讲人信息
    private void createRoomInfoCache(LiveRoom room, SysUser sysUser) {
        RoomInfoCache roomCache = new RoomInfoCache();
        roomCache.setSpeakerId(sysUser.getId());
        roomCache.setSpeakerName(sysUser.getUsername());
        roomCache.setSpeakerPic(sysUser.getAvatar());
        roomCache.setSpeakerState(1);
        roomCache.setRoomUid(room.getRoomUid());
        roomCache.setRoomTitle(room.getRoomTitle());
        roomCache.setLiveRemark(room.getLiveRemark());
        roomCache.setLiveStartTime(room.getLiveStartTime());
        roomCache.setLiveEndTime(room.getLiveEndTime());
        roomCache.setCreateRoomTime(new Date());
        String configValue = sysConfigService.findConfigValue(PRE_CREATE_LIVE_ROOM_MINUTE);
        String expireCloseMinute = sysConfigService.findConfigValue(DESTROY_EXPIRED_LIVE_ROOM_MINUTE);
        roomCache.setExpiredMinute(Integer.parseInt(configValue));
        roomCache.setExpiredCloseMinute(Integer.parseInt(expireCloseMinute));
        roomCache.setRoomType(room.getType());
//        roomCache.setCourseId(room.getCourseId());
//        roomCache.setCourseGroupId(room.getCourseGroupId());

        //写入房间信息
        getLiveRoomInfo(room.getRoomUid()).set(roomCache, 2L, TimeUnit.DAYS);
        //生成0点赞
        redissonClient.getBucket(LIVE_ROOM_LIKE.replace(ROOM_UID, room.getRoomUid()))
                .set(0, 2L, TimeUnit.DAYS);
        //生成房间人员列表数据
        redissonClient.getMap(LIVE_ROOM_TOTAL_USER_LIST.replace(ROOM_UID, room.getRoomUid()))
                .expire(2L, TimeUnit.DAYS);
    }

    /**
     * 创建房间-聊天室
     *
     * @param roomId   房间Uid
     * @param roomName 房间名称
     */
    private void createLiveRoom(String roomId, String roomName) {
        IMApiResultInfo resultInfo;
        try {
            resultInfo = imHelper.createChatRoom(roomId, roomName);
        } catch (Exception e) {
            throw new BizException("创建聊天室失败!" + e.getMessage());
        }
        if (!resultInfo.isSuccess()) {
            throw new BizException("创建聊天室失败!" + resultInfo.getErrorMessage());
        }
        log.info("create chatRoom success: {}", roomId);
    }

    /**
     * 定时任务-清理过期的房间
     */
    @Override
    public void destroyExpiredLiveRoom() {
        //查询房间过期时间
        String expiredMinuteStr = sysConfigService.findConfigValue(DESTROY_EXPIRED_LIVE_ROOM_MINUTE);
        if (StringUtils.isEmpty(expiredMinuteStr)) {
            log.info("roomDestroy>>>> 未查询到配置：{}", DESTROY_EXPIRED_LIVE_ROOM_MINUTE);
            return;
        }
        Date now = new Date();
        //查询已经开始并且没有删除及销毁的直播间
        List<LiveRoom> list = this.list(Wrappers.<LiveRoom>lambdaQuery()
                .eq(LiveRoom::getRoomState, 0)
                .eq(LiveRoom::getLiveState, 1)
                .le(LiveRoom::getLiveEndTime, now));
        if (CollectionUtils.isEmpty(list)) {
            return;
        }
        list.forEach(room -> {
            //直播间过期时间
            Date expiredDate = room.getLiveEndTime();
            if (room.getType().equals(RoomTypeEnum.LIVE.getCode())) {
                //如果是直播课，那么结束时间 = 直播间结束时间 + 设置的过期分钟数
                expiredDate = DateUtil.addMinutes(room.getLiveEndTime(), Integer.parseInt(expiredMinuteStr));
            }
            //当前时间 大于 直播间过期时间
            if (now.getTime() >= expiredDate.getTime()) {
                liveRoomService.destroyLiveRoom(room.getRoomUid());
            }
        });
    }

    /**
     * 关闭直播间
     *
     * @param roomUId 房间uid
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void destroyLiveRoom(String roomUId) {
        LiveRoom liveRoom = this.getOne(Wrappers.<LiveRoom>lambdaQuery().eq(LiveRoom::getRoomUid, roomUId));
        this.destroyLiveRoom(liveRoom);
    }


    private CourseSchedule getCourseScheduleByRoomUid(String roomUid) {
        CourseSchedule one = courseScheduleService.lambdaQuery()
                .eq(CourseSchedule::getRoomUid, roomUid)
                .last("limit 1")
                .one();
        if (Objects.isNull(one)) {
            one = new CourseSchedule();
            one.setId(-1L);
            one.setCourseGroupId(-1L);
        }

        return one;
    }
    /**
     * 关闭直播间
     *
     * @param room 房间信息
     */
    private void destroyLiveRoom(LiveRoom room) {
        if (Objects.isNull(room)) {
            return;
        }

        //10秒内同一个房间不能重复销毁-防重复销毁
        RBucket<Object> bucket = redissonClient.getBucket("IM:ROOMDESTROY:" + room.getRoomUid());
        if (!bucket.trySet(1, 10, TimeUnit.SECONDS)) {
            return;
        }

        liveRoomService.closeRoom(room);

        //获取所有直播间缓存数据并写入数据库后并清理缓存
        CompletableFuture.runAsync(() -> insertAndCleanLiveData(room.getRoomUid(), room.getSpeakerId()));
        log.info("roomDestroy>>>> insertAndCleanLiveData {}", JSONObject.toJSONString(room));

        String speakerIdStr = room.getSpeakerId().toString();
        String roomUid = room.getRoomUid();
        try {

            //关闭直播间发送消息
            ImRoomMessage message = new ImRoomMessage();
            message.setFromUserId(speakerIdStr);
            message.setToChatroomId(roomUid);
            message.setClientType(ClientEnum.TEACHER.getCode());
            message.setObjectName(ImRoomMessage.FORCED_OFFLINE);
            message.setContent(speakerIdStr);
            try {
                this.publishRoomMessage(message);
                log.info("destroyLiveRoom>>>> FORCED_OFFLINE : {}", speakerIdStr);
            } catch (Exception e) {
                log.error("destroyLiveRoom>>>>  FORCED_OFFLINE {}", e.getMessage());
            }

            LivePluginService pluginService = livePluginContext.getPluginService(room.getServiceProvider());

            //销毁直播间
            pluginService.chatRoomDestroy(roomUid);

            TencentWrapper.LiveStreamState liveStreamState = pluginService.liveStreamState(
                    getStreamId(room.getRoomUid(), room.getSpeakerId()));
            if (liveStreamState == null) {
                log.error("查询直播间流失败,返回结果为空");
            } else if ("active".equals(liveStreamState.getStreamState())) {
                pluginService.liveStreamStop(getStreamId(room.getRoomUid(), room.getSpeakerId()));
            }

            // 录制任务Id
            if (room.getServiceProvider().equals(TencentCloudLivePlugin.PLUGIN_NAME)) {

                List<String> collect = liveRoomService.lambdaQuery()
                        .eq(LiveRoom::getRoomUid, roomUid).list().stream()
                        .map(LiveRoom::getVideoRecord)
                        .filter(StringUtils::isNotEmpty)
                        .distinct().collect(Collectors.toList());

                for (String taskId : collect) {
                    // 删除录制任务
                    pluginService.rtcRoomRecordStop(taskId);
                }
            }
        } catch (Exception e) {
            log.error("roomDestroy>>>> errorMsg{}", e.getMessage(), e.getCause());
        }
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void closeRoom(LiveRoom room) {
        CourseSchedule schedule = getCourseScheduleByRoomUid(room.getRoomUid());
        //直播课
        if (room.getType().equals(RoomTypeEnum.LIVE.getCode())) {
            //查询老师分润表
            List<CourseScheduleTeacherSalary> salaryList = courseScheduleTeacherSalaryService.list(Wrappers.<CourseScheduleTeacherSalary>lambdaQuery()
                    .eq(CourseScheduleTeacherSalary::getCourseScheduleId,schedule.getId())
            );
            if (!CollectionUtils.isEmpty(salaryList)) {
                for (CourseScheduleTeacherSalary salary : salaryList) {//修改教师课酬状态-已结算
                    salary.setStatus(TeacherSalaryEnum.WAIT.getCode());
                    courseScheduleTeacherSalaryService.updateById(salary);
                }
            }
        }else {
            //删除该临时直播间列表
            RMap<Long, String> map = redissonClient.getMap(TEACHER_TEMP_LIVE_ROOM);
            map.remove(room.getSpeakerId());
        }
        //修改房间状态
        room.setLiveState(2);
        room.setRoomState(2);
        this.updateById(room);


        Date now = new Date();
        //给老师签退
        teacherAttendanceService.update(Wrappers.<TeacherAttendance>lambdaUpdate()
                .eq(TeacherAttendance::getTeacherId, room.getSpeakerId())
                .eq(TeacherAttendance::getCourseScheduleId, schedule.getId())
                .set(TeacherAttendance::getSignOutTime, now));
        //获取在线人员信息
        RMap<Long, String> onlineUserCache = this.getOnlineUserCache(room.getRoomUid());
        onlineUserCache.forEach((id, s) -> {
            //观看者签退
            studentAttendanceService.update(Wrappers.<StudentAttendance>lambdaUpdate()
                    .eq(StudentAttendance::getStudentId, id)
                    .eq(StudentAttendance::getCourseScheduleId,schedule.getId())
                    .set(StudentAttendance::getSignOutTime, now));
        });
    }

    /**
     * <p>主讲人处理进入和退出房间数据
     * <p>观看者只处理退出房间数据
     *
     * @param userState 用户状态数据
     */
    public void opsRoom(List<ImUserStateSync> userState) {
        if (CollectionUtils.isEmpty(userState)) {
            return;
        }
        userState.forEach(user -> {
            log.info("opsRoom>>>> {}", JSONObject.toJSONString(user));
            if (StringUtils.isBlank(user.getStatus())) {
                return;
            }
            Date now = new Date();
            String userIdStr = imGroupService.analysisImUserId(user.getUserid());
            //获取当前用户所在房间的uid
            RBucket<String> userRoom = redissonClient.getBucket(LIVE_USER_ROOM.replace(USER_ID, userIdStr));
            if (!userRoom.isExists()) {
                return;
            }
            String roomUid = Optional.ofNullable(user.getRoomUid()).orElse(userRoom.get());

            //获取当前用户状态变更的时间
            long userStateTime = Optional.ofNullable(user.getTime()).orElse(now.getTime());
            RBucket<Long> userStateTimeCache = redissonClient.getBucket(LIVE_USER_LAST_TIME.replace(USER_ID, userIdStr));
            if (userStateTimeCache.isExists()) {
                //缓存的时间比当前传入时间大则放弃这条数据
                long cacheTime = userStateTimeCache.get();
                log.info(" cacheTime: {}, userStateTime: {}", cacheTime, userStateTime);
                if (cacheTime > userStateTime) {
                    log.info("opsRoom>>>> cacheTime > userStateTime, cacheTime: {}, userStateTime: {}", cacheTime, userStateTime);
                    return;
                }
            }
            //将最新的时间写入缓存
            userStateTimeCache.set(userStateTime, 60L, TimeUnit.MINUTES);

            // 查询房间信息
            LiveRoom roomVo = getByRoomUid(roomUid);
            if (Objects.isNull(roomVo)) {
                log.info("opsRoom>>>> roomVo is null, userState: {}", JSON.toJSONString(userState));
                return;
            }

            // 腾讯云直播不需要处理融云im的消息
            if (roomVo.getServiceProvider().equals(TencentCloudLivePlugin.PLUGIN_NAME) && StringUtils.isNotBlank(user.getClientIp())) {
                return;
            }

            // 查询userId是不是主讲人 ,如果是主讲人则返回
            if (roomVo.getSpeakerId().toString().equals(userIdStr)) {
                this.opsSpeaker(roomUid, user, now, userIdStr);
                return;
            }
            //这里开始只处理观看者的数据，观看者只接受退出消息 status=0 是进入房间
            if (user.getStatus().equals("0")) {

                // 更新用户在线状态
                imLiveBroadcastRoomMemberService.lambdaUpdate()
                        .eq(ImLiveBroadcastRoomMember::getRoomUid, roomVo.getRoomUid())
                        .eq(ImLiveBroadcastRoomMember::getUserId, user.getUserid())
                        .set(ImLiveBroadcastRoomMember::getOnlineStatus, 1)
                        .update();


                // 用户进入直播间，发送统计数据
                // sendLiveRoomStatMessage(userid, roomVo);

                // 缓存JoinRoom用户信息到redis
                RBucket<Object> bucket = redissonClient.getBucket(RedissonMessageService.LIVE_ROOM_MEMBER + roomUid);
                if (!bucket.isExists()) {
                    bucket.set(0, 30, TimeUnit.MINUTES);
                    // 发布删除缓存消息
                    redissonMessageService.publish(RedissonMessageService.TOPIC_MESSAGE, roomUid);
                }

                log.debug("opsRoom>>>> user enter room, userState: {}", JSON.toJSONString(userState));
                return;
            }

            //用户id
            Long userId = Long.valueOf(userIdStr);
            // 更新用户离线状态
            ImLiveBroadcastRoomMember roomMember = new ImLiveBroadcastRoomMember();
            roomMember.setOnlineStatus(0);

            // 用户离开直播间
            if (user.getStatus().equals("3")) {
                // 直播间用户离开状态
                roomMember.setLiveRoomStatus(0);

            }
            // 更新用户在线状态为离线
            ImLiveBroadcastRoomMember one = imLiveBroadcastRoomMemberService.lambdaQuery()
                    .eq(ImLiveBroadcastRoomMember::getRoomUid, roomVo.getRoomUid())
                    .eq(ImLiveBroadcastRoomMember::getUserId, userId)
                    .last("limit 1")
                    .one();
            if (one != null) {
                roomMember.setId(one.getId());
                imLiveBroadcastRoomMemberService.updateById(roomMember);
            }
            liveBroadcastRoomMemberService.saveRecord(roomUid, userId, InOrOutEnum.OUT, YesOrNoEnum.NO);

            //从房间累计用户信息中查询该用户的信息
            RMap<Long, String> roomTotalUser = this.getTotalUserCache(roomUid);
            //该房间未查询到用户数据则不处理
            if (!roomTotalUser.isExists()) {
                return;
            }
            if (!roomTotalUser.containsKey(userId)) {
                return;
            }
            String userJsonStr = roomTotalUser.get(userId);
            log.info("opsRoom>>>> userJsonStr  {}", userJsonStr);
            //查询用户数据
            ImLiveBroadcastRoomMemberWrapper.RoomUserInfoVo userInfo = JSONObject.toJavaObject(JSONObject.parseObject(userJsonStr), ImLiveBroadcastRoomMemberWrapper.RoomUserInfoVo.class);
            //如果有动态观看时间则证明主播开播过，需要计算当前用户观看时长
            if (Objects.nonNull(userInfo.getDynamicLookTime())) {
                userInfo.setTotalViewTime(this.getLookMinutes(userInfo.getDynamicLookTime(), userInfo.getTotalViewTime()));
                userInfo.setDynamicLookTime(null);
            }
            roomTotalUser.fastPut(userId, JSONObject.toJSONString(userInfo));
            //查询在线人员列表
            RMap<Long, String> onlineUserInfo = this.getOnlineUserCache(roomUid);
            if (!onlineUserInfo.isExists()) {
                return;
            }
            CourseSchedule schedule = getCourseScheduleByRoomUid(roomUid);
            //从在线人员列表删除该人员
            onlineUserInfo.fastRemove(userId);
            //学员退出 写学生考勤表
            studentAttendanceService.update(Wrappers.<StudentAttendance>lambdaUpdate()
                    .eq(StudentAttendance::getStudentId, userId)
                    .eq(StudentAttendance::getCourseScheduleId, schedule.getId())
                    .set(StudentAttendance::getSignOutTime, now));

            //向直播间发送当前在线人数消息
            this.sendOnlineUserCount(roomUid, roomVo.getSpeakerId(), onlineUserInfo.size());
            log.info("opsRoom>>>> looker userInfo: {}", userJsonStr);
            //用户离开直播间发送退出房间消息给主讲人
            ImRoomMessage message = new ImRoomMessage();
            message.setFromUserId(userId.toString());
            message.setToChatroomId(roomUid);
            message.setClientType(ClientEnum.STUDENT.getCode());
            message.setObjectName(ImRoomMessage.LOOKER_LOGIN_OUT);
            message.setContent(userId);
            try {
                this.publishRoomMessage(message);
                log.info("opsRoom>>>> looker RC_CHATROOM_LEAVE : {}", userJsonStr);
            } catch (Exception e) {
                log.error("opsRoom>>>>  looker error RC_CHATROOM_LEAVE {}", e.getMessage());
                log.error("opsRoom>>>>  looker error sendMessage {} : RC_CHATROOM_LEAVE : {}", message, userJsonStr);
            }
        });

    }


    /**
     * 计算观看时长差-分钟数不满一分钟为0
     *
     * @param startDT    开始时间
     * @param nowMinutes 现在观看时长
     */
    private int getLookMinutes(Date startDT, Integer nowMinutes) {
        return getLookMinutes(startDT, new Date(), nowMinutes);
    }

    private LiveRoom getByRoomUid(String roomUid) {
        return this.lambdaQuery()
                .eq(LiveRoom::getRoomUid, roomUid)
                .last("limit 1")
                .one()  ;
    }

    /**
     * 主讲人
     */
    private void opsSpeaker(String roomUid, ImUserStateSync user, Date now, String userIdStr) {

        LiveRoom liveRoom = getByRoomUid(roomUid);
        RBucket<RoomSpeakerInfo> speakerCache = getRoomSpeakerInfoCache(roomUid, userIdStr);
        if (!speakerCache.isExists()) {
            log.info("isSpeaker>>>> 主播用户缓存不存在, roomId: {}, userId={}", roomUid, userIdStr);
            return;
        }
        // 当前用户为主播
        RoomSpeakerInfo speakerInfo = speakerCache.get();

        CourseSchedule schedule = getCourseScheduleByRoomUid(roomUid);
        //进退房间写老师考勤表
        TeacherAttendance teacherAttendance = teacherAttendanceService.getOne(Wrappers.<TeacherAttendance>lambdaQuery()
                .eq(TeacherAttendance::getTeacherId, userIdStr)
                .eq(TeacherAttendance::getCourseScheduleId, schedule.getId()));

        //最后一次进入房间的clientIp
        RBucket<String> lastClientIp = redissonClient.getBucket(LIVE_USER_LAST_CLIENT_IP.replace(ROOM_UID, roomUid).replace(USER_ID, userIdStr));
        //主讲人进入房间
        if (user.getStatus().equals("0")) {

            speakerInfo.setJoinRoomTime(now);
            log.info("isSpeaker>>>> join speakerCache {}", JSONObject.toJSONString(speakerInfo));
            speakerCache.set(speakerInfo);
            //将本次进入房间的clientIp添加到主讲人最后一次clientIp缓存中
            if (StringUtils.isNotBlank(user.getClientIp())) {
                lastClientIp.set(user.getClientIp());
            }
            //查询老师是否有进入过房间，没有则写老师考勤表的进入时间
            if (Objects.isNull(teacherAttendance)) {
                this.setTeacherAttendance(Long.parseLong(userIdStr), schedule.getCourseGroupId(), schedule.getId());
            }
            // 设置直播群组自定义数据
            setGroupDefinedData(liveRoom,EGroupDefinedDataType.ANCHOR_STATUS,"ONLINE");
            return;
        }
        //退出房间 - 校验本次退出直播间的clientIp 是不是上次进入房间的clientIp
        if (StringUtils.isNotBlank(user.getClientIp())) {
            if (lastClientIp.isExists()) {
                //如果是上次进入房间的clientIp和本次退出房间的clientIp不相同，则直接忽略
                if (!user.getClientIp().equals(lastClientIp.get())) {
                    return;
                }
            }
        }
        //如果退出时间大于进入时间就无需再次退出-直接返回
        if (compareDate.test(speakerInfo.getExitRoomTime(), speakerInfo.getJoinRoomTime())) {
            return;
        }


        if (Objects.isNull(teacherAttendance)) {
            teacherAttendance = new TeacherAttendance();
            teacherAttendance.setTeacherId(Long.parseLong(userIdStr));
            teacherAttendance.setCourseGroupType(CourseScheduleEnum.LIVE.getCode());
            teacherAttendance.setCourseGroupId(schedule.getCourseGroupId());
            teacherAttendance.setCourseScheduleId(schedule.getId());
            teacherAttendance.setSignInTime(now);
            teacherAttendance.setSignOutTime(now);
            teacherAttendance.setCreateTime(now);
            teacherAttendanceService.save(teacherAttendance);
        } else {
            //修改老师考勤表
            teacherAttendance.setSignOutTime(now);
            teacherAttendance.setUpdateTime(now);
            teacherAttendanceService.updateById(teacherAttendance);
        }

        //主讲人退出房间关闭录像
        closeLive(roomUid,speakerInfo.getSpeakerId(),null);


        // 设置直播群组自定义数据
        setGroupDefinedData(liveRoom,EGroupDefinedDataType.ANCHOR_STATUS,"OFFLINE");

    }

    private void setGroupDefinedData(LiveRoom roomVo, EGroupDefinedDataType type, String value) {
        List<TencentRequest.ChatRoomGroupDefinedData> appDefinedData = new ArrayList<>();
        appDefinedData.add(TencentRequest.ChatRoomGroupDefinedData.builder()
                .key(type.getCode())
                .value(value)
                .build());

//        appDefinedData.add(TencentRequest.ChatRoomGroupDefinedData.builder()
//                .key(EGroupDefinedDataType.UPDATE_INFO.getCode())
//                .value(type.getCode())
//                .build());
        try {
            livePluginContext.getPluginService(roomVo.getServiceProvider())
                    .chatRoomGroupDefinedData(TencentRequest.ChatRoomGroup.builder()
                            .groupId(roomVo.getRoomUid())
                            .appDefinedData(appDefinedData)
                            .build());
            log.info("设置直播群配置成功 roomUid:{}, data {}",roomVo.getRoomUid(), JSONObject.toJSONString(appDefinedData));
        } catch (Exception e) {
            log.error("设置直播群配置失败", e);
        }
    }

    /**
     * 对比时间,2个时间一个为空返回false,否则对比时间大小
     * <p>- date1 时间1
     * <p>- date2 时间2
     */
    private final BiPredicate<Date, Date> compareDate = (date1, date2) -> {
        if (Objects.nonNull(date1) && Objects.nonNull(date2)) {
            return date1.getTime() > date2.getTime();
        } else {
            return false;
        }
    };

    /**
     * 同步点赞数量
     *
     * @param roomUid 房间uid
     * @param likeNum 点赞数
     */
    @Override
    public void syncLike(String roomUid, Integer likeNum) {
        redissonClient.getBucket(LIVE_ROOM_LIKE.replace(ROOM_UID, roomUid)).set(likeNum);
        //增加房间心跳
        redissonClient.getBucket(LIVE_ROOM_HEART_BEAT.replace(ROOM_UID, roomUid)).set(new Date());
    }


    @Override
    public void setGroupMemberDefinedData(LiveRoomWrapper.LiveRoomVo  roomVo, Integer onlineUser, Integer totalUser) {
        List<TencentRequest.ChatRoomGroupDefinedData> appDefinedData = new ArrayList<>();
        appDefinedData.add(TencentRequest.ChatRoomGroupDefinedData.builder()
                .key(EGroupDefinedDataType.MEMBER_ONLINE.getCode())
                .value(onlineUser.toString())
                .build());
        appDefinedData.add(TencentRequest.ChatRoomGroupDefinedData.builder()
                .key(EGroupDefinedDataType.MEMBER_TOTAL.getCode())
                .value(totalUser.toString())
                .build());

//        appDefinedData.add(TencentRequest.ChatRoomGroupDefinedData.builder()
//                .key(EGroupDefinedDataType.UPDATE_INFO.getCode())
//                .value(StringUtils.join(EGroupDefinedDataType.MEMBER_ONLINE.getCode(),",",EGroupDefinedDataType.MEMBER_TOTAL.getCode()))
//                .build());
        try {
            livePluginContext.getPluginService(roomVo.getServiceProvider())
                    .chatRoomGroupDefinedData(TencentRequest.ChatRoomGroup.builder()
                            .groupId(roomVo.getRoomUid())
                            .appDefinedData(appDefinedData)
                            .build());
            log.info("设置直播群观看人数 roomUid:{}, data {}",roomVo.getRoomUid(), JSONObject.toJSONString(appDefinedData));
        } catch (Exception e) {
            log.error("设置直播群观看人数失败", e);
            throw new BizException("设置直播群观看人数失败");
        }
    }


    /**
     * 观看者-进入房间
     *
     * @param roomUid 房间uid
     * @param userId  用户id 观看者id
     */
    public void joinRoom(String roomUid, Long userId) {

        joinRoom(roomUid, userId, true);
    }

    //校验学生与房间的关系
    private RoomInfoCache checkStudentRoom(String roomUid, SysUser sysUser) {
        //校验房间是否存在
        RBucket<RoomInfoCache> roomInfoCache = this.getLiveRoomInfo(roomUid);
        if (!roomInfoCache.isExists()) {
            throw new BizException("直播还未开始或已结束!");
        }
        RoomInfoCache roomInfo = roomInfoCache.get();

        CourseSchedule schedule = getCourseScheduleByRoomUid(roomUid);
        //校验观看者是否买过课,如果是非临时直播间，则校验是否已经买过课
        if (!roomInfo.getRoomType().equals(RoomTypeEnum.TEMP.getCode())) {
            // 查询该学员成功购买课程
            CourseScheduleStudentPayment studentPayment = courseScheduleStudentPaymentService.getOne(Wrappers.<CourseScheduleStudentPayment>lambdaQuery()
                    .eq(CourseScheduleStudentPayment::getUserId, sysUser.getId())
                    .eq(CourseScheduleStudentPayment::getCourseId, schedule.getId())
                    .eq(CourseScheduleStudentPayment::getCourseGroupId, schedule.getCourseGroupId())
                    .eq(CourseScheduleStudentPayment::getCourseType, CourseScheduleEnum.LIVE.getCode()));
            if (Objects.nonNull(studentPayment)) {
                UserOrderVo detail = userOrderService.detail(studentPayment.getOrderNo(), studentPayment.getUserId());
                if (Objects.isNull(detail)) {
                    throw new BizException("您还未购买该课程.");
                }
                //不等于已支付的都是异常
                if (detail.getStatus().equals(OrderStatusEnum.CLOSE) || detail.getStatus().equals(OrderStatusEnum.FAIL)) {
                    throw new BizException("您还未购买该课程。");
                }
                if (detail.getStatus().equals(OrderStatusEnum.WAIT_PAY) || detail.getStatus().equals(OrderStatusEnum.PAYING)) {
                    throw new BizException("该课程的订单" + detail.getStatus().getMsg() + "，请先支付完成后再上课。");
                }
            } else {
                throw new BizException("您还未购买该课程!");
            }
        }
        //刷新当前房间的点赞及观看人数信息
        roomInfo.setLikeNum(getLike(roomUid));
        roomInfo.setLookNum(getNum.apply(this::getOnlineUserCache, roomUid));
        return roomInfo;
    }

    /**
     * 查询学生是否有进入过，没有则写学生考勤表的进入时间
     *
     * @param studentId     学生id
     * @param courseGroupId 课程组id
     * @param courseId      课程id
     */
    private void setStudentAttendance(Long studentId, Long courseGroupId, Long courseId) {
        //写学生第一次进入时间
        StudentAttendance studentAttendance = studentAttendanceService.getOne(Wrappers.<StudentAttendance>lambdaQuery()
                .eq(StudentAttendance::getStudentId, studentId)
                .eq(StudentAttendance::getCourseScheduleId, courseId));
        if (Objects.isNull(studentAttendance)) {
            Date now = new Date();
            studentAttendance = new StudentAttendance();
            studentAttendance.setCourseGroupType(CourseScheduleEnum.LIVE.getCode());
            studentAttendance.setCourseGroupId(courseGroupId);
            studentAttendance.setCourseScheduleId(courseId);
            studentAttendance.setStudentId(studentId);
            studentAttendance.setSignInTime(now);
            studentAttendance.setCreateTime(now);
            studentAttendanceService.save(studentAttendance);
            log.info("SignInTime >>>  user {} courseId {}", studentId, courseId);
        }
    }

    /**
     * 查询老师是否有进入过，没有则写老师考勤表的进入时间
     *
     * @param teacherId     老师id
     * @param courseGroupId 课程组id
     * @param courseId      课程id
     */
    private void setTeacherAttendance(Long teacherId, Long courseGroupId, Long courseId) {
        TeacherAttendance teacherAttendance = teacherAttendanceService.getOne(Wrappers.<TeacherAttendance>lambdaQuery()
                .eq(TeacherAttendance::getTeacherId, teacherId)
                .eq(TeacherAttendance::getCourseScheduleId, courseId));
        if (Objects.isNull(teacherAttendance)) {
            Date now = new Date();
            teacherAttendance = new TeacherAttendance();
            teacherAttendance.setTeacherId(teacherId);
            teacherAttendance.setCourseGroupType(CourseScheduleEnum.LIVE.getCode());
            teacherAttendance.setCourseGroupId(courseGroupId);
            teacherAttendance.setCourseScheduleId(courseId);
            teacherAttendance.setSignInTime(now);
            teacherAttendance.setCreateTime(now);
            teacherAttendanceService.save(teacherAttendance);
        }
    }

    /**
     * 向直播间发送当前在线人数消息
     *
     * @param roomUid    房间uid
     * @param fromUserId 发送人id
     * @param count      人数
     */
    private void sendOnlineUserCount(String roomUid, Long fromUserId, Integer count) {
        //校验传入参数，房间uid和发送人id不能为空
        if (!WrapperUtil.checkObj(roomUid, fromUserId, count)) {
            log.info(" sendOnlineUserCount>>>> param is null   roomUid: {}  fromUserId:{}  count:{}", roomUid, fromUserId, count);
            return;
        }
        // 缓存JoinRoom用户信息到redis
        RBucket<Object> bucket = redissonClient.getBucket(RedissonMessageService.LIVE_ROOM_MEMBER + roomUid);
        if (!bucket.isExists()) {
            bucket.set(0, 30, TimeUnit.MINUTES);
            // 发布删除缓存消息
            redissonMessageService.publish(RedissonMessageService.TOPIC_MESSAGE, roomUid);
        }
        ImRoomMessage message = new ImRoomMessage();
        message.setObjectName(ImRoomMessage.MEMBER_COUNT);
        message.setClientType(ClientEnum.TEACHER.getCode());
        message.setToChatroomId(roomUid);
        HashMap<String, Integer> sendMap = new HashMap<>();
        sendMap.put("count", count);
        message.setFromUserId(fromUserId.toString());
        message.setContent(sendMap);
        //发送消息
        try {
//            this.publishRoomMessage(message);
            log.info("sendOnlineUserCount>>>> message: {}", JSONObject.toJSONString(message));
        } catch (Exception e) {
            log.error("sendOnlineUserCount>>>> error {}", e.getMessage());
            log.error("sendOnlineUserCount>>>> sendMessage {} :", JSONObject.toJSONString(message));
        }
    }

    /**
     * 发送消息
     *
     * @param message
     */
    @Override
    public void publishRoomMessage(ImRoomMessage message) {
        String msgStr = JSONObject.toJSONString(message);
        log.info("publishRoomMessage message : {}", msgStr);
        // 消息发送用户
        LiveRoomMessage.MessageUser messageUser = null;
        SysUser sysUser = sysUserFeignService.queryUserById(Long.parseLong(message.getFromUserId()));
        if (Objects.nonNull(sysUser)) {
            // 发送用户信息
            messageUser = LiveRoomMessage.MessageUser
                    .builder()
                    .sendUserId(imGroupService.getImUserId(message.getFromUserId(), message.getClientType()))
                    .sendUserName(sysUser.getUsername())
                    .avatarUrl(sysUser.getAvatar())
                    .build();
        }

        LiveRoomMessage.MessageContent messageContent = getMessageContent(message);
        messageContent.setSendUserInfo(messageUser);

        LiveRoomMessage build = LiveRoomMessage.builder()
                .isIncludeSender(1)
                .objectName(message.getObjectName())
                .fromUserId(imGroupService.getImUserId(message.getFromUserId(), message.getClientType()))
                .toChatRoomId(message.getToChatroomId())
                .content(messageContent)
                .build();
        LiveRoom liveRoom = getByRoomUid(message.getToChatroomId());
        try {

            LivePluginService pluginService = livePluginContext.getPluginService(liveRoom.getServiceProvider());
            pluginService.sendChatRoomMessage(build);
            log.info("sendLiveRoomMessage>>>> looker {} : roomId={}, userId={}, content={}",message.getObjectName(), message.getToChatroomId(),
                message.getFromUserId(), messageContent);
        } catch (Exception e) {
            log.error("sendLiveRoomMessage>>>>  looker error {} {}", message.getObjectName(),e.getMessage());
            log.error("sendLiveRoomMessage>>>>  looker error sendMessage {} : {} : : roomId={}, userId={}",message.getObjectName(), message, message.getToChatroomId(), message.getFromUserId());
        }
    }

    private LiveRoomMessage.MessageContent getMessageContent(ImRoomMessage message) {

        switch (message.getObjectName()) {

            case LiveRoomMessage.LOOKER_LOGIN_OUT:
                return LiveRoomMessage.MessageContent.builder()
                        .targetId( imGroupService.getImUserId(message.getFromUserId(),message.getClientType()))
                        .build();
            case LiveRoomMessage.MEMBER_COUNT:
                LiveRoomWrapper.LiveRoomVo liveRoomVo = queryRoomInfo(message.getToChatroomId());
                return LiveRoomMessage.MessageContent.builder()
                        .count(liveRoomVo.getLookNum().longValue())
                        .onlineUsers(liveRoomVo.getLookNum().longValue())
                        .viewers(liveRoomVo.getTotalLookNum().longValue())
                        .likes(liveRoomVo.getLikeNum().longValue())
                        .build();
            case LiveRoomMessage.FORCED_OFFLINE:
            case LiveRoomMessage.WELCOME:
            default:
                return LiveRoomMessage.MessageContent.builder()
                        .build();
        }
    }

    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("用户不存在"));
    }

    /**
     * 查询在观看直播的用户信息
     *
     * @param roomUid 直播间uid
     */
    private List<RoomUserInfoCache> getTotalUserInfo(String roomUid) {
        RMap<Long, String> roomTotalUser = getTotalUserCache(roomUid);
        return roomTotalUser.values().parallelStream()
                .map(a -> JSONObject.toJavaObject(JSONObject.parseObject(a), RoomUserInfoCache.class))
                .collect(Collectors.toList());
    }

    /**
     * 获取直播间所有用户信息缓存
     *
     * @param roomUid 直播间uid
     * @return RMap key:userId value:RoomUserInfoCache -> jsonToString
     */
    private RMap<Long, String> getTotalUserCache(String roomUid) {
        return redissonClient.getMap(LIVE_ROOM_TOTAL_USER_LIST.replace(ROOM_UID, roomUid));
    }

    /**
     * 查询在观看直播的用户信息
     *
     * @param roomUid 直播间uid
     */
    private List<RoomUserInfoCache> getOnlineUserInfo(String roomUid) {
        RMap<Long, String> roomTotalUser = getOnlineUserCache(roomUid);
        return roomTotalUser.values().parallelStream()
                .map(a -> JSONObject.toJavaObject(JSONObject.parseObject(a), RoomUserInfoCache.class))
                .collect(Collectors.toList());
    }

    /**
     * 获取直播间在线用户信息缓存
     *
     * @param roomUid 直播间uid
     * @return RMap key:userId value:BaseRoomUserVo -> jsonToString
     */
    private RMap<Long, String> getOnlineUserCache(String roomUid) {
        return redissonClient.getMap(LIVE_ROOM_ONLINE_USER_LIST.replace(ROOM_UID, roomUid));
    }

    /**
     * 获取房间点赞数
     *
     * @param roomUid 房间UId
     * @return 点赞数
     */
    private int getLike(String roomUid) {
        //点赞数
        Object like = redissonClient.getBucket(LIVE_ROOM_LIKE.replace(ROOM_UID, roomUid)).get();
        if (Objects.isNull(like)) {
            return 0;
        }
        return (int) like;
    }

    /**
     * 获取房间缓存中的用户数量/观看人数
     *
     * <p> func：查询用户数量/观看人数的方法
     * <p> roomUid ：房间uid
     * <p> return ：用户数量/观看人数
     */
    private final BiFunction<Function<String, RMap<Long, ?>>, String, Integer> getNum = (func, roomUid) -> Optional.of(roomUid)
            .map(func)
            .filter(RMap::isExists)
            .map(RMap::size)
            .orElse(0);

    /**
     * 获取房间信息
     *
     * @param roomUid 房间编号
     */
    private RBucket<RoomInfoCache> getLiveRoomInfo(String roomUid) {
        return redissonClient.getBucket(LIVE_ROOM_INFO.replace(ROOM_UID, roomUid));
    }

    /**
     * 方便测试观察房间数据的方法
     */
    public Map<String, Object> test(String roomUid) {
        //result
        Map<String, Object> result = new HashMap<>();
        //获取房间信息
        RBucket<RoomInfoCache> speakerCache = this.getLiveRoomInfo(roomUid);
        if (speakerCache.isExists()) {
            result.put("房间信息信息", speakerCache.get());
        } else {
            result.put("房间信息", "房间信息不存在");
        }

        //校验房间心跳是否过期没续租
        RBucket<Date> lastRoomHeartbeatCache = redissonClient.getBucket(LIVE_ROOM_HEART_BEAT.replace(ROOM_UID, roomUid));
        if (lastRoomHeartbeatCache.isExists()) {
            result.put("房间心跳", DateUtil.dateToString(lastRoomHeartbeatCache.get(), DateUtil.EXPANDED_DATE_TIME_FORMAT));
        } 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> totalUserInfo = this.getTotalUserInfo(roomUid);
        if (CollectionUtils.isNotEmpty(totalUserInfo)) {
            totalLook = totalUserInfo.size();
            result.put("总人员数据", totalUserInfo);
        } else {
            result.put("总人员数据", "没有人员数据");
        }
        result.put("总观看人数", totalLook);
        //正在房间观看的用户数据
        List<RoomUserInfoCache> onlineUserInfo = this.getOnlineUserInfo(roomUid);
        if (CollectionUtils.isNotEmpty(onlineUserInfo)) {
            look = onlineUserInfo.size();
            result.put("正在观看的人员信息", onlineUserInfo);
        } else {
            result.put("正在观看的人员信息", "没有正在观看的人员数据");
        }
        result.put("实时观看数", look);
        return result;
    }

    @Override
    public LiveRoom getLiveInRoomBySpeakerId(Long speakerId) {
        return baseMapper.getLiveInRoomBySpeakerId(speakerId);
    }

    @Override
    public List<TeacherLivingInfoVo> queryTeacherLivingList() {
        return baseMapper.queryTeacherLivingList();
    }
    @Override
    public void whetherMic(String roomUid, Integer whetherMic) {
        //校验房间是否存在
        RBucket<RoomInfoCache> roomInfoCache = getLiveRoomInfo(roomUid);
        if (!roomInfoCache.isExists()) {
            log.error("teacherCheckRoomInfo>>>live not start  roomUid: {} ", roomUid);
            throw new BizException("直播还未开始!");
        }
        RoomInfoCache roomInfo = roomInfoCache.get();
        roomInfo.setWhether_mic(whetherMic);
        roomInfoCache.set(roomInfo);
    }

    /**
     * 直播间详情信息
     *
     * @param liveRoomId 直播间编号
     * @param userId 分享用户ID
     * @return LiveRoomWrapper
     */
    @Override
    public LiveRoomWrapper findLiveRoomDetailInfoByRoomId(String liveRoomId, Long userId) {

        // 直播间信息
        LiveRoom liveRoom = getBaseMapper().selectOne(Wrappers.<LiveRoom>lambdaQuery()
                .eq(LiveRoom::getRoomUid, liveRoomId));
        if (Objects.isNull(liveRoom)) {
            return null;
        }

        LiveRoomWrapper roomWrapper = LiveRoomWrapper.from(JSON.toJSONString(liveRoom));

        // 直播老师信息
        com.yonge.cooleshow.biz.dal.entity.SysUser teacher = sysUserMapper.selectById(roomWrapper.getSpeakerId());
        if (Objects.nonNull(teacher)) {

            roomWrapper.setTeacher(JSON.parseObject(JSON.toJSONString(teacher), LiveRoomWrapper.UserInfo.class));
        }

        // 分享同学信息
        if (Optional.ofNullable(userId).orElse(0L) > 0) {

            com.yonge.cooleshow.biz.dal.entity.SysUser student = sysUserMapper.selectById(userId);
            roomWrapper.setStudent(JSON.parseObject(JSON.toJSONString(student), LiveRoomWrapper.UserInfo.class));
        }

        return roomWrapper;
    }


    /**
     * 生成直播录制信息
     *
     * @param event TencentData.CallbackSteamRecordEvent
     */
    @Override
    public void createLiveRoomVideoRecord(TencentData.CallbackSteamRecordEvent event) {

        // 直播间ROOM_UID
        String roomId = event.getStreamId().split("_")[0];

        // 录制开始时间
        long startTime =  (event.getEndTime() - event.getDuration()) * 1000L;
        //保存切片
        LiveRoomVideo imLiveRoomVideo = initImLiveRoomVideo(roomId, event.getTaskId(), DateTime.now().toDate());
        imLiveRoomVideo.setStartTime(new DateTime(startTime).toDate());
        imLiveRoomVideo.setEndTime(new DateTime(event.getEndTime() * 1000L).toDate());
        imLiveRoomVideo.setUrl(event.getVideoUrl());
        imLiveRoomVideo.setType(4);

        // 回放记录已存在，直接忽略
        LiveRoomVideo video = liveRoomVideoService.lambdaQuery()
                .eq(LiveRoomVideo::getRoomUid, imLiveRoomVideo.getRoomUid())
                .eq(LiveRoomVideo::getRecordId, imLiveRoomVideo.getRecordId())
                .last("LIMIT 1")
                .one();
        if (Objects.isNull(video)) {

            LiveRoom room = this.getByRoomUid(imLiveRoomVideo.getRoomUid());

            if (org.apache.commons.lang.StringUtils.isBlank(room.getVideoRecord()) || Arrays.stream(room.getVideoRecord().split(","))
                    .anyMatch(x -> x.equals(imLiveRoomVideo.getRecordId()))) {
                liveRoomVideoService.save(imLiveRoomVideo);
            }

        }

    }
    private LiveRoomVideo initImLiveRoomVideo(String roomId, String recordId, Date now) {
        LiveRoomVideo video = new LiveRoomVideo();
        video.setRoomUid(roomId);
        video.setRecordId(recordId);
        video.setStartTime(now);
        video.setType(0);
        video.setCreatedTime(now);
        return video;
    }


    /**
     * 直播群成员在线状态回调处理
     *
     */
    @Override
    @Transactional
    public void callbackOnMemberStateChange(TencentData.CallbackOnMemberStateChange callbackOnMemberStateChange) {

        if (callbackOnMemberStateChange == null) {
            return;
        }

        int onlineStatus = callbackOnMemberStateChange.getEventType().equals("Online")?1:0;

        // 用户id
        if (CollectionUtils.isEmpty(callbackOnMemberStateChange.getMemberList())) {
            return;
        }
        List<Long> userIds = callbackOnMemberStateChange.getMemberList()
                .stream()
                .map(o -> Long.parseLong(o.getMemberAccount()))
                .collect(Collectors.toList());

        List<ImLiveBroadcastRoomMember> imLiveBroadcastRoomMembers = imLiveBroadcastRoomMemberMapper.queryMember(userIds,
                callbackOnMemberStateChange.getGroupId());

        if (CollectionUtils.isEmpty(imLiveBroadcastRoomMembers)) {
            return;
        }

        // 根据用户id分组
        Map<Long, ImLiveBroadcastRoomMember> userMap = imLiveBroadcastRoomMembers.stream()
                .collect(Collectors.toMap(ImLiveBroadcastRoomMember::getUserId,o ->o, (k1, k2) -> k1));
        List<ImUserStateSync> imUserStates = new ArrayList<>();
        for (Long userId : userIds) {

            Integer liveRoomStatus = userMap.getOrDefault(userId, new ImLiveBroadcastRoomMember())
                    .getLiveRoomStatus();
            ImUserStateSync imUserState = new ImUserStateSync();
            imUserState.setUserid(userId.toString());
            imUserState.setStatus(onlineStatus == 1 && liveRoomStatus == 1?"0":"1");
            imUserState.setOs(callbackOnMemberStateChange.getOptPlatform());
            imUserState.setRoomUid(callbackOnMemberStateChange.getGroupId());
            imUserStates.add(imUserState);
        }
        imLiveBroadcastRoomMemberMapper.updateOnlineStatus(userIds, callbackOnMemberStateChange.getGroupId(),onlineStatus);
        CompletableFuture.runAsync(() -> {
            opsRoom(imUserStates);
        });
    }

    @Override
    @Transactional
    public void callbackAfterMemberExit(TencentData.CallbackAfterMemberExit callbackAfterMemberExit) {
        if (callbackAfterMemberExit == null) {
            return;
        }
        Integer liveRoomStatus = 0;
        // 用户id
        if (CollectionUtils.isEmpty(callbackAfterMemberExit.getExitMemberList())) {
            return;
        }
        List<Long> userIds = callbackAfterMemberExit.getExitMemberList()
                .stream()
                .map(o -> Long.parseLong(o.getMemberAccount()))
                .collect(Collectors.toList());


        List<ImUserStateSync> imUserStates = new ArrayList<>();
        for (Long userId : userIds) {
            ImUserStateSync imUserState = new ImUserStateSync();
            imUserState.setUserid(userId.toString());
            imUserState.setStatus("3");
            imUserState.setOs(callbackAfterMemberExit.getOptPlatform());
            imUserState.setTime(callbackAfterMemberExit.getEventTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
            imUserState.setRoomUid(callbackAfterMemberExit.getGroupId());
            imUserStates.add(imUserState);
        }
        imLiveBroadcastRoomMemberMapper.updateLiveRoomStatus(userIds, callbackAfterMemberExit.getGroupId(),liveRoomStatus);
        CompletableFuture.runAsync(() -> {
            opsRoom(imUserStates);
        });
    }

    @Override
    @Transactional
    public void callbackAfterNewMemberJoin(TencentData.CallbackAfterNewMemberJoin callbackAfterNewMemberJoin) {
        if (callbackAfterNewMemberJoin == null) {
            return;
        }
        Integer liveRoomStatus = 1;

        // 用户id
        if (CollectionUtils.isEmpty(callbackAfterNewMemberJoin.getNewMemberList())) {
            return;
        }
        List<Long> userIds = callbackAfterNewMemberJoin.getNewMemberList()
                .stream()
                .map(o -> Long.parseLong(o.getMemberAccount()))
                .collect(Collectors.toList());


        List<ImLiveBroadcastRoomMember> imLiveBroadcastRoomMembers = imLiveBroadcastRoomMemberMapper.queryMember(userIds,
                callbackAfterNewMemberJoin.getGroupId());

        if (CollectionUtils.isEmpty(imLiveBroadcastRoomMembers)) {
            return;
        }

        // 根据用户id分组
        Map<Long, ImLiveBroadcastRoomMember> userMap = imLiveBroadcastRoomMembers.stream()
                .collect(Collectors.toMap(ImLiveBroadcastRoomMember::getUserId,o ->o, (k1, k2) -> k1));

        List<ImUserStateSync> imUserStates = new ArrayList<>();
        for (Long userId : userIds) {
            ImUserStateSync imUserState = new ImUserStateSync();
            imUserState.setUserid(userId.toString());
            imUserState.setStatus("0");
            imUserState.setOs(callbackAfterNewMemberJoin.getOptPlatform());
            imUserState.setTime(callbackAfterNewMemberJoin.getEventTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
            imUserState.setRoomUid(callbackAfterNewMemberJoin.getGroupId());
            imUserStates.add(imUserState);
        }
        imLiveBroadcastRoomMemberMapper.updateLiveRoomStatus(userIds, callbackAfterNewMemberJoin.getGroupId(),liveRoomStatus);
        CompletableFuture.runAsync(() -> {
            opsRoom(imUserStates);
        });
    }

    // 定时任务凌晨2点，关闭腾讯直播间， 融云直播间自动关闭，不做处理
    @Override
    public void destroyLiveRoom() {
        List<LiveRoom> liveRooms = this.lambdaQuery()
                .eq(LiveRoom::getLiveState, 2)
                .ne(LiveRoom::getRoomState,2)
                .list();
        for (LiveRoom liveRoom : liveRooms) {
            liveRoomService.tryDestroyLiveRoom(liveRoom);
        }
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean tryDestroyLiveRoom(LiveRoom liveRoom) {
        LivePluginService pluginService = livePluginContext.getPluginService(
                liveRoom.getServiceProvider());
        try {
            if (pluginService == null) {
                log.error("查询直播间流失败,未找到对应的插件");
                return false;
            }
            if (pluginService.pluginName().equals(TencentCloudLivePlugin.PLUGIN_NAME)) {
                TencentWrapper.LiveStreamState liveStreamState = pluginService.liveStreamState(
                        getStreamId(liveRoom.getRoomUid(), liveRoom.getSpeakerId()));
                if (liveStreamState == null) {
                    log.error("查询直播间流失败,返回结果为空");
                    return false;
                }
                log.info("查询直播间流状态:{},roomUid:{}", JSON.toJSONString(liveStreamState), liveRoom.getRoomUid());
                if (!"active".equals(liveStreamState.getStreamState())) {
                    destroyLiveRoom(liveRoom);
                }

            } else if (pluginService.pluginName().equals(RongCloudLivePlugin.PLUGIN_NAME)) {
                // 融云走原有逻辑 融云自动销毁
                // 销毁状态改为2
                LiveRoom liveRoomUpdate = new LiveRoom();
                liveRoomUpdate.setId(liveRoom.getId());
                liveRoomUpdate.setRoomState(2);
                liveRoomService.updateById(liveRoomUpdate);
            }
        } catch (Exception e) {

            log.error("查询直播间流失败", e);
            return false;
        }
        return true;
    }

    private String getStreamId(String roomUid, Long speakerId) {
        return roomUid + "_" + imGroupService.getImUserId(speakerId,ClientEnum.TEACHER);
    }


    public boolean roomDestroy(LiveRoom room) {
        //10秒内同一个房间不能重复销毁-防重复销毁
        RBucket<Object> bucket = redissonClient.getBucket("IM:ROOMDESTROY:" + room.getRoomUid());
        if (!bucket.trySet(1, 10, TimeUnit.SECONDS)) {
            return false;
        }
        log.error("roomDestroy>>>> room : {}", JSONObject.toJSONString(room));
        String roomUid = room.getRoomUid();

        //获取所有直播间缓存数据并写入数据库后并清理缓存
        CompletableFuture.runAsync(() -> insertAndCleanLiveData(roomUid, room.getSpeakerId()));
        log.info("roomDestroy>>>> insertAndCleanLiveData {}", JSONObject.toJSONString(room));
        //将房间状态改为已销毁
        LiveRoom liveRoomUpdate = new LiveRoom();
        liveRoomUpdate.setId(room.getId());

        Date date = new Date();
        liveRoomUpdate.setRoomState(2);
        liveRoomUpdate.setLiveState(2);
        Long userId = -1L;
        try {
            userId = getSysUser().getId();
        } catch (Exception ignored) {
        }
        liveRoomUpdate.setUpdatedBy(userId);
        liveRoomUpdate.setUpdatedTime(date);
        liveRoomUpdate.setLiveEndTime(date);

        this.updateById(liveRoomUpdate);


        try {
            LivePluginService pluginService = livePluginContext.getPluginService(room.getServiceProvider());

            //销毁直播间
            pluginService.chatRoomDestroy(roomUid);

            TencentWrapper.LiveStreamState liveStreamState = pluginService.liveStreamState(
                    getStreamId(room.getRoomUid(), room.getSpeakerId()));
            if (liveStreamState == null) {
                log.error("查询直播间流失败,返回结果为空");
            } else if ("active".equals(liveStreamState.getStreamState())) {
                pluginService.liveStreamStop(getStreamId(room.getRoomUid(), room.getSpeakerId()));
            }

            // 录制任务Id
            if (room.getServiceProvider().equals(TencentCloudLivePlugin.PLUGIN_NAME)) {

                List<String> collect = liveRoomService.lambdaQuery()
                        .eq(LiveRoom::getRoomUid, roomUid).list().stream()
                        .map(LiveRoom::getVideoRecord)
                        .filter(StringUtils::isNotEmpty)
                        .distinct().collect(Collectors.toList());

                for (String taskId : collect) {
                    // 删除录制任务
                    pluginService.rtcRoomRecordStop(taskId);
                }
            }
        } catch (Exception e) {
            log.error("roomDestroy>>>> errorMsg{}", e.getMessage(), e.getCause());
            return false;
        }
        return true;
    }

    //获取该直播间所有数据写入数据库-并清理缓存
    private void insertAndCleanLiveData(String roomUid, Long speakerId) {
        log.info("insertAndCleanLiveData >>>> roomUid : {}", roomUid);
        Date now = new Date();
        //总观看人数
        List<ImLiveBroadcastRoomMember> memberList = new ArrayList<>();
        //获取直播间所有人数据写入 im_live_broadcast_room_member
        RMap<Long, String> roomTotalUserCache = getTotalUserCache(roomUid);
        if (roomTotalUserCache.isExists()) {
            List<ImLiveBroadcastRoomMemberWrapper.RoomUserInfoVo> roomTotalUser = roomTotalUserCache.values().parallelStream()
                    .map(a -> JSONObject.toJavaObject(JSONObject.parseObject(a), ImLiveBroadcastRoomMemberWrapper.RoomUserInfoVo.class))
                    .collect(Collectors.toList());
            for (ImLiveBroadcastRoomMemberWrapper.RoomUserInfoVo v : roomTotalUser) {
                ImLiveBroadcastRoomMember member = new ImLiveBroadcastRoomMember();
                member.setRoomUid(roomUid);
                member.setUserId(v.getUserId());
                member.setOnlineStatus(0);
                member.setLiveRoomStatus(0);
                member.setJoinTime(v.getFirstJoinTime());
                member.setTotalTime(getLookMinutes(v.getDynamicLookTime(), now, v.getTotalViewTime()));
                memberList.add(member);
            }
        }

        //获取直播点赞
        int like = 0;
        like = syncLikeCount(roomUid);
        RBucket<Object> likeCache = redissonClient.getBucket(LIVE_ROOM_LIKE.replace(ROOM_UID, roomUid));
        if (likeCache.isExists()) {
            //删除房间点赞数据
            likeCache.delete();
        }
        int speakerLiveTime = 0;
        //获取直播间主讲人信息 写入im_live_broadcast_room_data
        RBucket<RoomSpeakerInfo> speakerCache = getRoomSpeakerInfoCache(roomUid, speakerId.toString());
        if (speakerCache.isExists()) {
            LiveBroadcastRoomData liveData = new LiveBroadcastRoomData();
            RoomSpeakerInfo speakerInfo = speakerCache.get();
            log.info("insertAndCleanLiveData >>>> speakerInfo : {}", JSONObject.toJSONString(speakerInfo));
            liveData.setRoomUid(roomUid);
            liveData.setLikeNum(like);
            liveData.setTotalUserNum(CollectionUtils.isNotEmpty(memberList) ? memberList.size() : 0);
            liveData.setUpdatedTime(now);
            liveData.setLiveTime(getLookMillisecond(speakerInfo.getStartLiveTime(), now, speakerInfo.getTotalLiveTime()));
            liveBroadcastRoomDataService.save(liveData);
            //删除房间主讲人数据
            speakerCache.delete();
            //获取主讲人直播时长
            speakerLiveTime = liveData.getLiveTime();
        }

        //写入im_live_broadcast_room_member表，校验用户观看时长，不能大于主讲人直播时长
        if (CollectionUtils.isNotEmpty(memberList)) {
            for (ImLiveBroadcastRoomMember member : memberList) {
                if (member.getTotalTime() > speakerLiveTime) {
                    member.setTotalTime(speakerLiveTime);
                }
            }
            //添加人员数据
//            Lists.partition(memberList, 500)
//                    .forEach(list -> imLiveBroadcastRoomMemberMapper.insertBatch(list));
        }
        //获取在线人员信息
        RMap<Long, String> onlineUserCache = getOnlineUserCache(roomUid);
        //删除人员对应直播间编号信息
//        onlineUserCache.forEach((id, s) -> redissonClient.getBucket(LIVE_USER_ROOM.replace(USER_ID, id.toString())).delete());
        //删除直播间所有用户数据
        roomTotalUserCache.delete();
        //删除在线用户数据
        onlineUserCache.delete();
        //删除当前主讲人最后一次进入房间的ip
        redissonClient.getBucket(LIVE_USER_LAST_CLIENT_IP.replace(ROOM_UID, roomUid).replace(USER_ID, speakerId.toString())).deleteAsync();

        //删除当前主讲人最后一次心跳
        redissonClient.getBucket(LIVE_ROOM_HEART_BEAT.replace(ROOM_UID, roomUid)).deleteAsync();
        //删除房间全部人员缓存
        this.getTotalUserCache(roomUid).deleteAsync();
        //删除该临时直播间列表
        RMap<Long, String> map = redissonClient.getMap(TEACHER_TEMP_LIVE_ROOM);
        map.remove(speakerId);
    }


    /**
     * 更新主播直播间状态
     *
     * @param liveRoom 直播间状态
     */
    @Override
    public Boolean updateRoomStatus(LiveRoomStatus liveRoom) {
        // 获取直播间信息
        LiveRoom room = this.getByRoomUid(liveRoom.getRoomUid());
        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());
        }


        if (liveRoom.getBanStatus() != null) {
            room.setBanStatus(liveRoom.getBanStatus());
            setGroupDefinedData(room, EGroupDefinedDataType.GLOBAL_BAN, liveRoom.getBanStatus() == 1 ?
                    EOnOffStatus.ON.getCode() : EOnOffStatus.OFF.getCode());
        }

        // 设置摄像头状态
        if (liveRoom.getCameraStatus() != null) {
            setGroupDefinedData(room, EGroupDefinedDataType.ANCHOR_CAMERA,
                    liveRoom.getCameraStatus() == 1 ? EOnOffStatus.ON.getCode():EOnOffStatus.OFF.getCode());
        }

        // 设置全员闭麦状态
        if (Objects.nonNull(liveRoom.getMicStatus())) {
            setGroupDefinedData(room, EGroupDefinedDataType.ANCHOR_MIC,
                    liveRoom.getMicStatus() == 1 ? EOnOffStatus.ON.getCode() : EOnOffStatus.OFF.getCode());
        }

        // 设置全员闭麦状态
        if (Objects.nonNull(liveRoom.getLinkMic())) {
            setGroupDefinedData(room, EGroupDefinedDataType.BAN_LINK_MIC,
                    liveRoom.getLinkMic() == 1 ? EOnOffStatus.ON.getCode() : EOnOffStatus.OFF.getCode());
        }
        return this.updateById(room);
    }

    /**
     * 进入直播间检查数据
     *
     * @param roomUid 房间uid
     * @param userId  用户id
     * @param osType  默认学生端  0:pc端 1:学生端 2:老师端
     */
    @Override
    public LiveRoomWrapper.LiveRoomVo queryRoomAndCheck(String roomUid, Long userId, int osType) {

        SysUser sysUser = Optional.ofNullable(userId)
                .map(this::getSysUser)
                .orElseGet(this::getSysUser);


        // 如果是直播课程，判断是否已经结束
        Integer count = courseScheduleService.lambdaQuery()
                .eq(CourseSchedule::getRoomUid, roomUid)
                .eq(CourseSchedule::getStatus, "COMPLETE")
                .count();
        if (count >0 ) {
            throw new BizException("直播课已结束");
        }

        // 默认学生端查询
        osType = Optional.ofNullable(osType).orElse(1);
        ClientEnum clientEnum = ClientEnum.TEACHER;

        // 移动端-学生端
        if (osType == 1) {
            clientEnum = ClientEnum.STUDENT;
            //学生端
            this.checkStudentRoom(roomUid, sysUser);
        }
        // 移动端-老师端
        if (osType == 2) {
            //老师端
//            return teacherQueryRoomAndCheck(roomUid, sysUser);
        }
        // PC端-直播助手
        if (osType == 3) {
            // 校验直播间是否存在
            LiveRoomWrapper.LiveRoomVo vo = Optional.ofNullable(roomUid).map(this::queryRoomInfo)
                    .orElseThrow(() -> new BizException("直播间不存在"));

            if (vo.getOs().equals("mobile")) {
                throw new BizException("该直播间仅可在手机端直播");
            }
        }

        return this.queryRoomAndCheck(queryRoomInfo(roomUid), sysUser,clientEnum);
    }


    public LiveRoomWrapper.LiveRoomVo queryRoomAndCheck(LiveRoomWrapper.LiveRoomVo room, SysUser sysUser,ClientEnum clientType) {


        if (room.getLiveState() == 2) {
            //如果直播结束了还是推广状态则将推广修改为取消
            throw new BizException("直播已结束！");
        }

        if (room.getRoomState() == 1) {
            throw new BizException("直播间不存在");
        }


        room.setBlacklistFlag(0);

        LivePluginService pluginService = livePluginContext.getPluginService(room.getServiceProvider());
        // 直播房间配置信息
        String userSig = "";
        try {
            userSig = pluginService.register(imGroupService.getImUserId(sysUser.getId().toString(),clientType.getCode()), sysUser.getUsername(),
                    sysUser.getAvatar());
        } catch (Exception e) {
            log.error("直播房间用户注册失败: userId={}", sysUser.getId(), e);
        }

        // 直播房间统计信息
        getRoomData(room);
        room.setUserSig(userSig);
        room.setLiveRoomConfig(pluginService.getLiveRoomConfig());

        // 直播间配置信息
        return room;
    }


    /**
     * 直播间推流状态查询
     *
     * @param roomUid 直播间ID
     * @return TencentWrapper.LiveStreamState
     */
    @Override
    public TencentWrapper.LiveStreamState roomLiveStreamStatus(String roomUid) {

        // 直播间信息
        LiveRoomWrapper.LiveRoomVo roomVo = queryRoomInfo(roomUid);
        if (Objects.isNull(roomVo)) {
            throw new BizException("无效的直播间ID");
        }

        TencentWrapper.LiveStreamState liveStreamState = TencentWrapper.LiveStreamState
                .builder()
                .streamState("inactive")
                .requestId("")
                .build();
        // 直播间推流状态
        try {
            LivePluginService pluginService = livePluginContext.getPluginService(roomVo.getServiceProvider());

            liveStreamState = pluginService.liveStreamState(getStreamId(roomUid, roomVo.getSpeakerId()));
        } catch (Exception e) {
            log.error("roomLiveStreamStatus roomUid={}", roomUid, e);
        }

        return liveStreamState;
    }


    /**
     * 查询直播间信息
     *
     * @param roomUid 直播间uid
     */
    @Override
    public LiveRoomWrapper.LiveRoomVo queryRoomInfo(String roomUid) {
        LiveRoom liveRoom = getByRoomUid(roomUid);
        if (Objects.isNull(liveRoom)) {
            throw new BizException("直播间不存在");
        }
        LiveRoomWrapper.LiveRoomVo liveRoomVo = JSON.parseObject(JSON.toJSONString(liveRoom), LiveRoomWrapper.LiveRoomVo.class);
        liveRoomVo.setSpeakerImUserId(imGroupService.getImUserId(liveRoomVo.getSpeakerId().toString(),ClientEnum.TEACHER.getCode()));
        return getRoomData(liveRoomVo);
    }

    private LiveRoomWrapper.LiveRoomVo getRoomData(LiveRoomWrapper.LiveRoomVo roomVo) {

        // 同步点赞数
        int likeNums = syncLikeCount(roomVo.getRoomUid());

        roomVo.setLikeNum(likeNums);
        roomVo.setTotalLookNum(0);
        roomVo.setLookNum(0);

        ImLiveBroadcastRoomMemberWrapper.RoomMemberNumDto roomMemberNumDto = getRoomMemberNumDto(roomVo.getRoomUid());
        roomVo.setTotalLookNum(roomMemberNumDto.getTotalNum() == null? 0 : roomMemberNumDto.getTotalNum());
        roomVo.setLookNum(roomMemberNumDto.getOnlineNum() == null? 0 : roomMemberNumDto.getOnlineNum());

        // 直播视频数
        List<LiveRoomVideo> imLiveRoomVideoVos = liveRoomVideoService.queryVideo(roomVo.getRoomUid());
        if (CollectionUtils.isNotEmpty(imLiveRoomVideoVos)) {
            roomVo.setVideoNum(imLiveRoomVideoVos.size());
        } else {
            roomVo.setVideoNum(0);
        }

        // 直播间主讲人信息
        SysUser sysUser = sysUserFeignService.queryUserById(roomVo.getSpeakerId());
        if (sysUser !=null) {
            roomVo.setSpeakerName(sysUser.getRealName());
            roomVo.setSpeakerPic(sysUser.getAvatar());
        }


        return roomVo;
    }


    @Override
    public ImLiveBroadcastRoomMemberWrapper.RoomMemberNumDto getRoomMemberNumDto(String roomUid) {
        ImLiveBroadcastRoomMemberWrapper.RoomMemberNumDto roomMemberNumDto = imLiveBroadcastRoomMemberMapper.queryMemberNum(roomUid);
        if (roomMemberNumDto == null) {
            roomMemberNumDto = new ImLiveBroadcastRoomMemberWrapper.RoomMemberNumDto();
            roomMemberNumDto.setTotalNum(0);
            roomMemberNumDto.setOnlineNum(0);
        }
        return roomMemberNumDto;
    }


    /**
     * 同步直播间点赞数
     *
     * @param roomUid 直播间uid
     * @return int
     */
    @Override
    public int syncLikeCount(String roomUid) {

        // 直播间信息
        LiveRoom room = getByRoomUid(roomUid);
        if (room == null) {
            return 0;
        }

        // 融云直播间
        if (room.getServiceProvider().equals(RongCloudLivePlugin.PLUGIN_NAME)) {
            //点赞数
            return (int) Optional.ofNullable(redissonClient.getBucket(LIVE_ROOM_LIKE.replace(ROOM_UID, roomUid)).get()).orElse(0);
        }


        // 腾讯云直播
        LivePluginService pluginService = livePluginContext.getPluginService(room.getServiceProvider());
        List<TencentWrapper.ChatRoomGroupCounter> chatRoomGroupCounters = null;
        try {
            chatRoomGroupCounters = pluginService.chatRoomGroupCounterDataList(
                    room.getRoomUid());
        } catch (Exception e) {
            log.error("同步直播间点赞数失败", e);
        }
        if (CollectionUtils.isEmpty(chatRoomGroupCounters)) {
            return 0;
        }
        Optional<TencentWrapper.ChatRoomGroupCounter> first = chatRoomGroupCounters.stream()
                .filter(a -> a.getKey()
                        .equals(
                                EGroupDefinedDataType.LIKES.getCode()))
                .findFirst();

        if (first.isPresent()) {
            int i = Integer.parseInt(first.get().getValue());

            LiveBroadcastRoomData one = liveBroadcastRoomDataService.lambdaQuery()
                    .eq(LiveBroadcastRoomData::getRoomUid, roomUid)
                    .last("limit 1")
                    .one();
            // 同步点赞数
            if (one != null) {
                LiveBroadcastRoomData imLiveBroadcastRoomData = new LiveBroadcastRoomData();
                imLiveBroadcastRoomData.setId(one.getId());
                imLiveBroadcastRoomData.setLikeNum(i);
                liveBroadcastRoomDataService.updateById(imLiveBroadcastRoomData);
            }

            return i;
        }
        return 0;
    }


    /**
     * 查询直播间在线的用户
     *
     * @param roomUid 直播间uid
     */
    @Override
    public List<LiveRoomWrapper.BaseRoomUserVo> queryRoomLimitOnlineUserInfo(String roomUid) {
        RMap<Long, String> onlineUserCache = getOnlineUserCache(roomUid);
        return onlineUserCache.values().parallelStream()
                .map(a -> JSONObject.toJavaObject(JSONObject.parseObject(a), LiveRoomWrapper.BaseRoomUserVo.class))
                .limit(200)
                .collect(Collectors.toList());
    }

    /**
     * 修改用户连麦状态
     *
     * @param roomUid          直播房间id
     * @param userId           用户id
     * @param whetherMicStatus 连麦状态
     */
    @Override
    public void userWhetherMic(String roomUid, Long userId, Integer whetherMicStatus) {
        imLiveBroadcastRoomMemberMapper.userWhetherMic(roomUid, userId, whetherMicStatus);
    }

    @Override
    public long joinRoom(String roomUid, Long userId, Boolean microphoneFlag) {
        Optional.ofNullable(roomUid).orElseThrow(() -> new BizException("房间编号不能为空！"));
        Optional.ofNullable(userId).orElseThrow(() -> new BizException("人员编号不能为空！"));

        // 如果是直播课程，判断是否已经结束
        Integer count = courseScheduleService.lambdaQuery()
                .eq(CourseSchedule::getRoomUid, roomUid)
                .eq(CourseSchedule::getStatus, "COMPLETE")
                .count();
        if (count >0 ) {
            throw new BizException("直播课已结束");
        }

        // 设置进入时间
        RBucket<Long> userStateTimeCache = redissonClient.getBucket(LIVE_USER_LAST_TIME.replace(USER_ID, userId.toString()));
        long time = DateTime.now().plusSeconds(1).getMillis();
        log.info(" join cacheTime: {}, userStateTime: {}", userStateTimeCache.get(), time);
        userStateTimeCache.set(time, 60L, TimeUnit.MINUTES);

        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);
        return new Date().getTime();
    }

    /**
     * 更新直播推流时间
     *
     * @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, Long userId, String sequence) {
        //查询房间主播信息
        RBucket<RoomSpeakerInfo> speakerCache = getRoomSpeakerInfoCache(roomUid, userId.toString());
        if (!speakerCache.isExists()) {
            return;
        }
        RoomSpeakerInfo roomSpeakerInfo = speakerCache.get();
        // 设置推流状态房间信息
        // 查询房间信息
        LiveRoom imLiveBroadcastRoomVo = getByRoomUid(roomSpeakerInfo.getRoomUid());

        if (imLiveBroadcastRoomVo == null) {
            log.warn("closeLive imLiveBroadcastRoomVo is null");
            return;
        }
        //关闭直播
        LivePluginService pluginService = livePluginContext.getPluginService(imLiveBroadcastRoomVo.getServiceProvider());
        if (StringUtils.isBlank(sequence)) {
            try {
                TencentWrapper.LiveStreamState liveStreamState = pluginService.liveStreamState(getStreamId(imLiveBroadcastRoomVo.getRoomUid(), imLiveBroadcastRoomVo.getSpeakerId()));
                if (liveStreamState == null) {
                    log.error("查询直播间流失败,返回结果为空");
                } else {
                    log.info("查询直播间流状态:{},roomUid:{}", JSON.toJSONString(liveStreamState), imLiveBroadcastRoomVo.getRoomUid());
                    if (!"active".equals(liveStreamState.getStreamState())) {
                        setPushStatus(roomSpeakerInfo.getRoomUid(), 0);
                    }
                }
            } catch (Exception e) {
                log.error("查询直播间流失败,roomUid:{}", imLiveBroadcastRoomVo.getRoomUid(), e);
            }
        } else 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 !=null &&roomSpeakerInfo.getState() !=null && 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;
    }


    /**
     * 主讲人信息
     */
    @Data
    public static class RoomSpeakerInfo implements Serializable {
        //主讲人id
        private Long speakerId;
        //主讲人名称
        private String speakerName;
        //直播状态 0 直播中 1关闭直播
        private Integer state;
        //房间uid
        private String roomUid;
        // 直播流sequence
        private String sequence;

        //房间创建时间
        private Date createRoomTime;
        //进入房间时间
        @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
        private Date joinRoomTime;
        //开始直播时间
        @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
        private Date startLiveTime;
        //开始直播时间
        @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
        private Date endLiveTime;
        //退出房间时间
        @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
        private Date exitRoomTime;
        //总直播时间
        private Integer totalLiveTime = 0;
        //是否录像 0允许 1不允许
        private Integer whetherVideo;
        //播出端-  pc网页端 移动端mobile
        private String os = "pc";

        // 腾讯视频模板 0: 没有 1：有
        private Integer videoTemplate;

    }
}

