package com.ym.service.Impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.microsvc.toolkit.middleware.rtc.RTCRoomPluginContext;
import com.microsvc.toolkit.middleware.rtc.RTCRoomPluginService;
import com.microsvc.toolkit.middleware.rtc.enums.EMemberAction;
import com.microsvc.toolkit.middleware.rtc.impl.RongCloudRTCPlugin;
import com.microsvc.toolkit.middleware.rtc.impl.TencentCloudRTCPlugin;
import com.microsvc.toolkit.middleware.rtc.message.ImGroupMemberWrapper;
import com.microsvc.toolkit.middleware.rtc.message.RTCRoomMessage;
import com.ym.common.ApiException;
import com.ym.common.BaseResponse;
import com.ym.common.DisplayEnum;
import com.ym.common.ErrorEnum;
import com.ym.config.IMProperties;
import com.ym.config.RoomProperties;
import com.ym.dao.RoomDao;
import com.ym.dao.RoomMemberDao;
import com.ym.dao.UserDao;
import com.ym.dao.WhiteboardDao;
import com.ym.enums.ActionEnum;
import com.ym.enums.DeviceTypeEnum;
import com.ym.enums.RoleEnum;
import com.ym.job.ScheduleManager;
import com.ym.mec.auth.api.client.SysUserFeignService;
import com.ym.mec.auth.api.entity.SysUser;
import com.ym.mec.biz.dal.dao.*;
import com.ym.mec.biz.dal.dto.BasicUserDto;
import com.ym.mec.biz.dal.dto.RongyunBasicUserDto;
import com.ym.mec.biz.dal.entity.*;
import com.ym.mec.biz.dal.enums.GroupType;
import com.ym.mec.biz.dal.enums.TeachModeEnum;
import com.ym.mec.biz.service.*;
import com.ym.mec.common.exception.BizException;
import com.ym.mec.common.page.WrapperUtil;
import com.ym.mec.im.IMHelper;
import com.ym.mec.im.message.*;
import com.ym.mec.util.collection.MapUtil;
import com.ym.mec.util.date.DateUtil;
import com.ym.pojo.*;
import com.ym.service.RoomService;
import com.ym.utils.CheckUtils;
import com.ym.utils.CodeUtil;
import com.ym.utils.DateTimeUtils;
import com.ym.utils.IdentifierUtils;
import com.ym.whiteboard.WhiteBoardHelper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.ym.enums.RoleEnum.RoleStudent;
import static com.ym.enums.RoleEnum.RoleTeacher;

/**
 * Created by super_zou on 2019/11/28.
 */
@Slf4j
@Service
public class RoomServiceImpl implements RoomService {
    @Autowired
    private IMHelper imHelper;
    @Autowired
    private RoomProperties roomProperties;
    @Autowired
    private RoomDao roomDao;
    @Autowired
    private RoomMemberDao roomMemberDao;
    @Autowired
    private CourseScheduleStudentPaymentDao courseScheduleStudentPaymentDao;
    @Autowired
    private WhiteBoardHelper whiteBoardHelper;
    @Autowired
    private WhiteboardDao whiteboardDao;
    @Autowired
    private ScheduleManager scheduleManager;
    @Autowired
    private UserDao userDao;
    @Autowired
    private TeacherDao teacherDao;
    @Autowired
    private CourseScheduleDao courseScheduleDao;
    @Autowired
    private TeacherAttendanceService teacherAttendanceService;
    @Autowired
    private StudentAttendanceService studentAttendanceService;
    @Autowired
    private IMProperties imProperties;
    @Autowired
    private SysUserFeignService sysUserFeignService;
    @Autowired
    private SysExamSongDao sysExamSongDao;
    @Autowired
    private CourseScheduleStudentMusicScoreDao courseScheduleStudentMusicScoreDao;
    @Autowired
    private SysMusicScoreAccompanimentDao sysMusicScoreAccompanimentDao;
    @Autowired
    private SysTenantConfigService sysTenantConfigService;
    @Autowired
    private SysConfigDao sysConfigDao;
    @Autowired
    private TenantAssetsInfoService tenantAssetsInfoService;
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    @Autowired
    private RTCRoomPluginContext rtcRoomPluginContext;
    @Autowired
    private VipGroupDao vipGroupDao;

    @Override
    public Integer getCurrentCourseId(String roomId) {
        CourseSchedule courseSchedule = courseScheduleDao.get(Long.parseLong(roomId));
        //是否是连堂课
        String continueCourseTime = sysTenantConfigService.getTenantConfigValue(SysConfigService.ONLINE_CONTINUE_COURSE_TIME, courseSchedule.getTenantId());
        if (StringUtils.isEmpty(continueCourseTime)) {
            continueCourseTime = "5";
        }
        CourseSchedule schedule = courseSchedule;
        //如果当前课程是连堂课,那么获取第一节课的课程编号
        while (true) {
            String classDate = DateUtil.format(schedule.getClassDate(), DateUtil.DEFAULT_PATTERN);
            String startClassTime = DateUtil.format(schedule.getStartClassTime(), DateUtil.EXPANDED_TIME_FORMAT);
            schedule = courseScheduleDao.getFirstCourse(schedule.getClassGroupId(), classDate + " " + startClassTime, schedule.getActualTeacherId(), continueCourseTime);
            if (schedule != null) {
                roomId = schedule.getId().toString();
            } else {
                break;
            }
        }
        return Integer.parseInt(roomId);
    }

    @Transactional(rollbackFor = Exception.class)
    public String getCloseNetworkRoomTime(CourseSchedule courseSchedule, String continueCourseTime) {
        String autoCloseNetworkRoomTime = sysTenantConfigService.getTenantConfigValue(SysConfigService.COURSE_AFTER_BUFFER_TIME, courseSchedule.getTenantId());
        if (StringUtils.isEmpty(autoCloseNetworkRoomTime)) {
            autoCloseNetworkRoomTime = "15";
        }
        CourseSchedule schedule = courseSchedule;
        //如果当前课程是连堂课,那么获取第一节课的课程编号
        while (true) {
            String classDate = DateUtil.format(schedule.getClassDate(), DateUtil.DEFAULT_PATTERN);
            String endClassTime = DateUtil.format(schedule.getEndClassTime(), DateUtil.EXPANDED_TIME_FORMAT);
            schedule = courseScheduleDao.getLastCourse(schedule.getClassGroupId(), classDate + " " + endClassTime, schedule.getActualTeacherId(), continueCourseTime);
            if (schedule != null) {
                autoCloseNetworkRoomTime = DateUtil.minutesBetween(new Date(), schedule.getEndClassTime()) + "";
            } else {
                break;
            }
        }
        return autoCloseNetworkRoomTime;
    }

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    @Override
    public BaseResponse joinRoom(String roomId, Boolean joinRoom) throws Exception {
        CheckUtils.checkArgument(roomId != null, "roomId must't be null");
        SysUser sysUser = sysUserFeignService.queryUserInfo();
        String userId = sysUser.getId().toString();
        log.info("joinRoom: roomId={}, userId={}", roomId, userId);

        Teacher teacher = teacherDao.get(Integer.parseInt(userId));
        CourseSchedule courseSchedule = courseScheduleDao.get(Long.parseLong(roomId));
        if (courseSchedule.getTeachMode() == TeachModeEnum.OFFLINE) {
            return new BaseResponse(ErrorEnum.JOIN_ROOM_ERROR, ErrorEnum.JOIN_ROOM_ERROR.getErrMsg(), null);
        }
        Date curTime = DateTime.now().toDate();
        //是否提前进入教室
        String courseBeforeBufferTime = sysTenantConfigService.getTenantConfigValue(SysConfigService.COURSE_BEFORE_BUFFER_TIME, courseSchedule.getTenantId());
        if (StringUtils.isEmpty(courseBeforeBufferTime)) {
            courseBeforeBufferTime = "5";
        }
        Date addMinutes = DateUtil.addMinutes(curTime, Integer.parseInt(courseBeforeBufferTime));
        if (courseSchedule.getStartClassTime().compareTo(addMinutes) > 0) {
            return new BaseResponse(ErrorEnum.ROOM_NOT_START, ErrorEnum.ROOM_NOT_START.getErrMsg(), null);
//            throw new BizException("网络教室暂未开启,请在{}分钟后进入教室",DateUtil.minutesBetween(addMinutes,courseSchedule.getStartClassTime()));
        }
        final TenantAssetsInfo one = tenantAssetsInfoService.getOne(new WrapperUtil<TenantAssetsInfo>()
                .hasEq("tenant_id_", courseSchedule.getTenantId())
                .queryWrapper()
                .gt("balance_", 0));
        if (one == null) {
            return new BaseResponse(ErrorEnum.CLOUD_BALANCE_NOT_FEE, ErrorEnum.CLOUD_BALANCE_NOT_FEE.getErrMsg(), null);
        }

        //是否是连堂课
        String continueCourseTime = sysTenantConfigService.getTenantConfigValue(SysConfigService.ONLINE_CONTINUE_COURSE_TIME, courseSchedule.getTenantId());
        if (StringUtils.isEmpty(continueCourseTime)) {
            continueCourseTime = "5";
        }

        RoomResult roomResult = new RoomResult();
        roomResult.setAutoCloseNetworkRoomTime(this.getCloseNetworkRoomTime(courseSchedule, continueCourseTime));
        CourseSchedule schedule = courseSchedule;
        //如果当前课程是连堂课,那么获取第一节课的课程编号
        while (true) {
            String classDate = DateUtil.format(schedule.getClassDate(), DateUtil.DEFAULT_PATTERN);
            String startClassTime = DateUtil.format(schedule.getStartClassTime(), DateUtil.EXPANDED_TIME_FORMAT);
            schedule = courseScheduleDao.getFirstCourse(schedule.getClassGroupId(), classDate + " " + startClassTime, schedule.getActualTeacherId(), continueCourseTime);
            if (schedule != null) {
                roomId = schedule.getId().toString();
//                roomResult.setAutoCloseFlag(false);
            } else {
                break;
            }
        }
        Long courseId = Long.parseLong(roomId);
        //记录用户实际选择的房间
        if (courseSchedule.getGroupType() == GroupType.COMM) {
            roomId = "I" + roomId;
        } else {
            roomId = "S" + roomId;
        }
        redisTemplate.opsForValue().setIfAbsent(roomId + userId, courseSchedule.getId().toString());
        log.info("joinRoom current: roomId={}, userId={}", roomId, userId);
        RoleEnum roleEnum;

        // 获取RTC服务提供方
        if (StringUtils.isBlank(courseSchedule.getServiceProvider())) {

            String rtcServiceProvider = Optional.ofNullable(sysConfigDao.findConfigValue(SysConfigService.RTC_SERVICE_PROVIDER))
                    .orElse(RongCloudRTCPlugin.PLUGIN_NAME);

            // 按分部强制开始腾讯网络教室
            {
                List<Integer> collect = Arrays.stream(Optional.ofNullable(sysConfigDao.findConfigValue(SysConfigService.TENCENT_RTC_SERVICE_PROVIDER)).orElse("").split(","))
                        .filter(StringUtils::isNotBlank)
                        .map(Integer::parseInt).distinct().collect(Collectors.toList());

                if (collect.contains(courseSchedule.getOrganId())) {
                    // 强制开启腾讯网络教室
                    rtcServiceProvider = TencentCloudRTCPlugin.PLUGIN_NAME;
                }
            }

            courseSchedule.setServiceProvider(rtcServiceProvider);
        }

        // 全员静音状态
        Boolean muteAll = Optional.ofNullable(courseSchedule.getMuteAll()).orElse(false);

        RoomResult.MemberResult userResult = new RoomResult.MemberResult();
        RoomMember member = roomMemberDao.findByRidAndUid(roomId, userId);
        String userName;
        if (member == null) {
            int count = roomMemberDao.countByRidAndExcludeRole(roomId, RoleEnum.RoleAudience.getValue());
            if (count == roomProperties.getMaxCount()) {
                log.info("join error Over max count: roomId = {}, userId = {}", roomId, userId);
                return new BaseResponse(ErrorEnum.ERR_OVER_MAX_COUNT, ErrorEnum.ERR_OVER_MAX_COUNT.getErrMsg(), null);
            }

            boolean microphone = true;
            if (teacher != null && teacher.getId().equals(courseSchedule.getActualTeacherId())) {
                roleEnum = RoleTeacher;
                userName = sysUser.getRealName();
            } else {
                roleEnum = RoleStudent;
                userName = sysUser.getUsername();

                // 学生加入房间,判定老师是否已开启全员静音
                if (TencentCloudRTCPlugin.PLUGIN_NAME.equals(courseSchedule.getServiceProvider())) {
                    microphone = !muteAll;
                }
            }

            userResult.setMicrophone(microphone);
            userResult.setCamera(true);
            userResult.setHandUpOn(false);
            userResult.setJoinTime(curTime);
            if (Optional.ofNullable(joinRoom).orElse(true)) {
                saveRoomMember(userId, sysUser.getAvatar(), userName, roomId, roleEnum.getValue(), curTime, microphone);
            }
        } else {
            roleEnum = RoleEnum.getEnumByValue(member.getRole());
            if (roleEnum == RoleTeacher && Optional.ofNullable(joinRoom).orElse(true)) {
                courseScheduleStudentPaymentDao.adjustPlayMidi(courseId, null, null);
            }
            userName = member.getName();
            //userResult.setRole(member.getRole());
            userResult.setMicrophone(member.isMic());
            userResult.setCamera(member.isCamera());
            userResult.setHandUpOn(member.isHand());
            userResult.setJoinTime(member.getJoinDt());
        }
//        imHelper.joinGroup(new String[]{userId}, roomId, roomId);

        // 主讲老师信息
        SysUser teacherInfo = sysUserFeignService.queryUserById(courseSchedule.getActualTeacherId());
        if (Objects.isNull(teacherInfo)) {
            throw new BizException("主讲老师不存在");
        }

        RTCRoomPluginService pluginService = rtcRoomPluginContext.getPluginService(courseSchedule.getServiceProvider());
        if (TencentCloudRTCPlugin.PLUGIN_NAME.equals(pluginService.pluginName())) {
            // 腾讯云RTC
            // 用户IM帐号创建
            String userSig = "";
            try {
                userSig = pluginService.register(String.valueOf(sysUser.getId()), sysUser.getRealName(), sysUser.getAvatar());
            } catch (Exception e) {
                log.error("直播房间用户注册失败: userId={}", sysUser.getId(), e);
            }

            // 返回配置参数
            roomResult.userSig(userSig)
                    .rtcRoomConfig(rtcRoomPluginContext.getPluginService().getRTCRoomConfig(String.valueOf(sysUser.getId())))
                    .setGroupId(roomId);
        }

        if (Optional.ofNullable(joinRoom).orElse(true)) {
            // 创建IM群聊
            this.joinImGroup(roomId, courseSchedule.getActualTeacherId(), courseSchedule);
            // RTC服务提供方
            roomResult.setServiceProvider(Optional.ofNullable(courseSchedule.getServiceProvider()).orElse("rongCloud"));
        }

        List<CourseScheduleStudentMusicScore> scheduleStudentMusicScores = courseScheduleStudentMusicScoreDao.queryByScoreIdAndCourseId(null, courseId, null, null, null);
        Room room = roomDao.findByRid(roomId);
        String display = "";
        if (roleEnum == RoleTeacher || roleEnum == RoleEnum.RoleAssistant) {

            display = "display://type=" + roleEnum.ordinal() + "?userId=" + userId + "?uri=";
            // 发送显示主屏消息
            if (TencentCloudRTCPlugin.PLUGIN_NAME.equals(roomResult.getServiceProvider())) {

                // 网络教室人员信息
                RoomMember roomMember = roomMemberDao.findByRidAndUid(roomId, userId);
                // 发送消息
                sendDisplayMessage(display, roomMember);
            }
        } else {
            ExamSongDownloadData examSongDownloadData;
            String json = courseScheduleStudentPaymentDao.getExamJsonByCourseIdAndUserId(courseId, sysUser.getId());
            if (StringUtils.isEmpty(json)) {
                examSongDownloadData = new ExamSongDownloadData();
            } else {
                examSongDownloadData = JSON.parseObject(json, ExamSongDownloadData.class);
            }
            //获取学员曲目下载状态
            userResult.setExamSongDownloadJson(examSongDownloadData);
            if (room != null) {
                display = room.getDisplay();
            }
        }
        //已下载的伴奏列表
        if (scheduleStudentMusicScores != null && scheduleStudentMusicScores.size() > 0) {
            List<CourseScheduleStudentMusicScore> musicScores = scheduleStudentMusicScores.stream().filter(e -> e.getUserId().equals(sysUser.getId())).collect(Collectors.toList());
            String toJSONString = JSON.toJSONString(musicScores, SerializerFeature.DisableCircularReferenceDetect);
            List<CourseScheduleStudentMusicScore> lists = JSON.parseArray(toJSONString, CourseScheduleStudentMusicScore.class);
            userResult.setScheduleStudentMusicScores(lists);
        }

        userResult.setUserName(userName);
        userResult.setUserId(userId);
        userResult.setRole(roleEnum.getValue());
        //获取节拍器信息
        String midi = courseScheduleStudentPaymentDao.getMidiByCourseIdAndUserId(courseId.toString(), userId);
        userResult.setPlayMidiJson(JSONObject.parseObject(midi, CustomMessage.class));

        //获取当前课程剩余时长
        roomResult.setSurplusTime(DateUtil.secondsBetween(new Date(), courseSchedule.getEndClassTime()));

        roomResult.setUserInfo(userResult);
        roomResult.setDisplay(display);
        roomResult.setRoomId(roomId);

        // 课程人数
        {
            // 当前教室人数
            int studentNums = courseScheduleStudentPaymentDao.findByCourseSchedule(Long.parseLong(roomId.substring(1))).size();
            // 网管课
            /*if (courseSchedule.getGroupType() == GroupType.VIP) {
                studentNums = vipGroupDao.getCourseStudentNumsByGroupId(Long.parseLong(courseSchedule.getMusicGroupId()));
            }*/
            roomResult.setStudentNums(studentNums);
        }

        List<RoomMember> roomMemberList = roomMemberDao.findByRid(roomId);
        if (CollectionUtils.isNotEmpty(roomMemberList)) {

            // 获取当前房间所有用户去重
            Collection<RoomMember> roomMembers = roomMemberList.stream()
                    .collect(Collectors.toMap(RoomMember::getUid, Function.identity(), (o, n) -> n)).values();

            // 重置房间用户信息
            roomMemberList = Lists.newArrayList(roomMembers);

            Set<String> userIds = roomMemberList.stream().map(RoomMember::getUid).collect(Collectors.toSet());
            Map<Integer, String> midiMap = MapUtil.convertMybatisMap(courseScheduleStudentPaymentDao.queryMidiByUserIdsAndCourseId(userIds, courseId.toString()));
            Map<Integer, String> examSongMap = MapUtil.convertMybatisMap(courseScheduleStudentPaymentDao.queryExamSongByUserIdsAndCourseId(userIds, courseId.toString()));
            roomResult.setMembers(roomMemberList, midiMap, examSongMap, scheduleStudentMusicScores);

            // 全员静音状态开启
            if (muteAll && TencentCloudRTCPlugin.PLUGIN_NAME.equals(courseSchedule.getServiceProvider())) {
                for (RoomResult.MemberResult item : roomResult.getMembers()) {
                    // 重置学生用户当前静音状态
                    if (RoleStudent.getValue() == item.getRole()) {
                        item.setMicrophone(false);
                    }
                }
            }
        }
        roomResult.setWhiteboards(whiteboardDao.findByRid(roomId));
        if (room != null) {
            roomResult.setSoundVolume(room.getSoundVolume());
        }
        //是否使用自定义白板
        String rongyun_here_white_flag = sysConfigDao.findConfigValue("rongyun_here_white_flag");
        if(StringUtils.isNotEmpty(rongyun_here_white_flag)){
            HashMap<Integer,Integer> hashMap = JSONObject.parseObject(rongyun_here_white_flag, HashMap.class);
            Integer s = hashMap.get(courseSchedule.getOrganId());
            if(s == null){
                roomResult.setRandomNumeric("0");
            }else {
                roomResult.setRandomNumeric(s.toString());
            }
        }else {
            roomResult.setRandomNumeric("1");
        }
        log.info("join room: roomId = {}, userId = {}, userName={}, role = {}", roomId, userId, userName, roleEnum);
        return new BaseResponse(roomResult);
    }

    private void joinImGroup(String roomId, Integer actualTeacherId, CourseSchedule courseSchedule) throws Exception {

        String joinImGroupKey = "joinImGroup:" + roomId;
        // VIP课或网络课,IM群聊已创建标识
        Boolean exists = redisTemplate.opsForValue().setIfAbsent(joinImGroupKey, roomId, 1L, TimeUnit.DAYS);

        RTCRoomPluginService pluginService = rtcRoomPluginContext.getPluginService(courseSchedule.getServiceProvider());
        if (Optional.ofNullable(exists).orElse(false)) {

            try {
                // 创建群组
                log.info("createImGroup: roomId = {}, userId = {}", roomId, actualTeacherId);
                if (TencentCloudRTCPlugin.PLUGIN_NAME.equals(pluginService.pluginName())) {

                    // 群组帐号注册
                    Teacher teacher = teacherDao.get(courseSchedule.getTeacherId());
                    if (Objects.nonNull(teacher)) {
                        try {
                            pluginService.register(courseSchedule.getTeacherId().toString(), teacher.getRealName(), teacher.getAvatar());
                        } catch (Exception e) {
                            log.error("直播房间群主注册失败: userId={}", courseSchedule.getTeacherId(), e);
                        }
                    }

                    // 生成群组
                    pluginService.chatRoomCreate(roomId, courseSchedule.getName(), courseSchedule.getTeacherId().toString());

                    // 群组老师信息
                    List<ImGroupMemberWrapper.ImGroupMember> groupMembers = Lists.newArrayList(ImGroupMemberWrapper.ImGroupMember
                            .builder()
                            .userId(Long.valueOf(actualTeacherId))
                            .imUserIdFormat(false)
                            .build());

                    List<CourseScheduleStudentPayment> payments = courseScheduleStudentPaymentDao.findByCourseSchedule(Long.parseLong(roomId.substring(1)));
                    // 群组学生信息
                    if (CollectionUtils.isNotEmpty(payments)) {
                        for (CourseScheduleStudentPayment item : payments) {
                            groupMembers.add(ImGroupMemberWrapper.ImGroupMember
                                    .builder()
                                    .userId(Long.valueOf(item.getUserId()))
                                    .imUserIdFormat(false)
                                    .build());
                        }
                    }

                    // 加入群组成员
                    log.info("joinImGroup: roomId = {}, serviceProvider={}, userIds = {}", roomId, courseSchedule.getServiceProvider(), groupMembers);
                    pluginService.chatRoomGroupJoin(roomId, courseSchedule.getName(), groupMembers);
                } else {

                    List<CourseScheduleStudentPayment> payments = courseScheduleStudentPaymentDao.findByCourseSchedule(Long.parseLong(roomId.substring(1)));
                    List<String> collect = payments.stream().map(e -> e.getUserId().toString()).collect(Collectors.toList());
                    collect.add(actualTeacherId.toString());
                    String[] integers = collect.toArray(new String[]{});
                    imHelper.joinGroup(integers, roomId, roomId);
                }

            } catch (Exception e) {

                redisTemplate.delete(joinImGroupKey);
                // 异常抛出,删除缓存标识
                throw e;
            }

            // 更新网络课服务提供方
            courseScheduleDao.updateServiceProvider(courseSchedule.getId(), courseSchedule.getServiceProvider());
        }

    }
    private void dismissImGroup(String userId,String roomId, String serviceProvider) throws Exception {
        log.info("dismissImGroup: roomId = {}, userId = {}", roomId, userId);
        String joinImGroupKey = "joinImGroup:" + roomId;
        redisTemplate.delete(joinImGroupKey);

        RTCRoomPluginService pluginService = rtcRoomPluginContext.getPluginService(serviceProvider);
        if (TencentCloudRTCPlugin.PLUGIN_NAME.equals(pluginService.pluginName())) {
            // 腾讯云群销毁
            pluginService.chatRoomDestroy(roomId);
        } else {
            // 融云群销毁
            // 销毁群组
            imHelper.dismiss(userId, roomId);
        }

    }


    public RoomMember saveRoomMember(String roomId, String userId) {
        SysUser sysUser = sysUserFeignService.queryUserById(Integer.parseInt(userId));

        Teacher teacher = teacherDao.get(Integer.parseInt(userId));
        CourseSchedule courseSchedule = courseScheduleDao.get(Long.parseLong(roomId.substring(1)));

        Date curTime = DateTimeUtils.currentUTC();

        RoleEnum roleEnum;
        RoomMember member = roomMemberDao.findByRidAndUid(roomId, userId);
        String userName;
        if (member == null) {

            boolean microphone = true;
            if (teacher != null && teacher.getId().equals(courseSchedule.getActualTeacherId())) {
                roleEnum = RoleTeacher;
                userName = sysUser.getRealName();
            } else {
                roleEnum = RoleStudent;
                userName = sysUser.getUsername();

                // 判定老师是否已开启全员静音
                if (TencentCloudRTCPlugin.PLUGIN_NAME.equals(courseSchedule.getServiceProvider())) {
                    microphone = !courseSchedule.getMuteAll();
                }
            }
            member = saveRoomMember(userId, sysUser.getAvatar(), userName, roomId, roleEnum.getValue(), curTime, microphone);
        }
        return member;
    }


    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    @Override
    public void joinRoomFailure(String roomId, String userId) {
        RoomMember roomMember = roomMemberDao.findByRidAndUid(roomId, userId);
        if (roomMember == null) {
            return;
        }
        log.info("joinRoomFailure : roomId={}, userId={}", roomId, userId);
        //如果加入失败,删除该用户数据
        roomMemberDao.deleteUserByRidAndUid(roomId, userId);
    }

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    @Override
    public void joinRoomSuccess(String roomId, String userId, String deviceNum) throws Exception {
        log.info("joinRoomSuccess: roomId={}, userId={}, deviceNum={}", roomId, userId, deviceNum);
        RoomMember roomMember = roomMemberDao.findByRidAndUid(roomId, userId);
        if (roomMember == null) {
            roomMember = saveRoomMember(roomId, userId);
        }
        String joinSuccessKey = "joinRoomSuccess" + roomId + userId;
        Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(joinSuccessKey, roomId, 1l, TimeUnit.SECONDS);
        if (!aBoolean) {
            RoleEnum roleEnum = RoleEnum.getEnumByValue(roomMember.getRole());
            if (roleEnum == RoleTeacher && StringUtils.isNotEmpty(deviceNum)) {
                teacherAttendanceService.updateDeviceNum(Integer.parseInt(roomId.substring(1)), userId, deviceNum, null);
//                signInSuccess(roomMember,deviceNum);
            }
            return;
        }

        RoleEnum roleEnum = RoleEnum.getEnumByValue(roomMember.getRole());
        CourseSchedule schedule = courseScheduleDao.getLock(Long.parseLong(roomId.substring(1)));

        String display = "";
        if (roleEnum == RoleTeacher) {
            //如果是老师加入房间,调整节拍器状态
            courseScheduleStudentPaymentDao.adjustPlayMidi(schedule.getId(), null, null);
            display = "display://type=1?userId=" + userId + "?uri=";
        } else if (roleEnum == RoleEnum.RoleAssistant) {
            display = "display://type=0?userId=" + userId + "?uri=";
        }
        Date curTime = DateTimeUtils.currentUTC();
        Room room = roomDao.findByRid(roomId);
        log.info("joinRoomSuccess: roomId={}, userId={}, roleEnum={}, room={}", roomId, userId, roleEnum.name(), Objects.isNull(room));
        if (room == null) {
            saveRoom(roomId, roomId, curTime, display);
            this.joinImGroup(roomId, schedule.getActualTeacherId(), schedule);
        } else {
            if (roleEnum == RoleTeacher || roleEnum == RoleEnum.RoleAssistant) {
                updateDisplay(roomId, userId, display, 0);
            }
        }

        UserInfo userInfo = userDao.findByUid(userId);
        if (userInfo == null) {
            userInfo = new UserInfo();
            userInfo.setUid(userId);
            userInfo.setName(roomMember.getName());
            userInfo.setCreateDt(curTime);
            userInfo.setUpdateDt(curTime);
            userDao.save(userInfo);
        }
        MemberChangedMessage msg = new MemberChangedMessage(MemberChangedMessage.Action_Join, userId, roleEnum.getValue());
        msg.setTimestamp(curTime);
        msg.setUserName(roomMember.getName());
        msg.setCamera(true);
        Boolean playMidi = false;
        Boolean examSong = false;
        if (roleEnum == RoleStudent) {
            String midiByCourseIdAndUserId = courseScheduleStudentPaymentDao.getMidiByCourseIdAndUserId(schedule.getId().toString(), userId);
            //获取节拍器信息
            if (StringUtils.isNotEmpty(midiByCourseIdAndUserId)) {
                JSONObject jsonObject = JSONObject.parseObject(midiByCourseIdAndUserId);
                if (jsonObject.get("enable") != null) {
                    playMidi = Boolean.parseBoolean(jsonObject.get("enable").toString());
                }
            }
            String examJson = courseScheduleStudentPaymentDao.getExamJsonByCourseIdAndUserId(schedule.getId(), Integer.parseInt(userId));
            if (StringUtils.isNotEmpty(examJson)) {
                ExamSongDownloadData examSongDownloadData = JSON.parseObject(examJson, ExamSongDownloadData.class);
                examSong = examSongDownloadData.getEnable();
            }
        }
        msg.setHandUpOn(roomMember.isHand());
        msg.setMetronomeSwitch(playMidi);
        msg.setExamSongSwitch(examSong);


        // 获取RTC服务提供方
        String rtcServiceProvider = Optional.ofNullable(schedule.getServiceProvider()).orElse("rongCloud");

        RTCRoomPluginService pluginService = rtcRoomPluginContext.getPluginService(rtcServiceProvider);
        if (rtcServiceProvider.equals(TencentCloudRTCPlugin.PLUGIN_NAME)) {
            // 腾讯云RTC服务
            RTCRoomMessage.MessageContent.MessageContentBuilder action = RTCRoomMessage.MessageContent.builder()
                    .action(EMemberAction.JOIN.getValue());

            if (roleEnum == RoleTeacher) {
                action.role(roleEnum.getValue());
            } else {
                action.role(roleEnum.getValue());
            }
            action.handUpOn(roomMember.isHand())
                    .microphone(roomMember.isMic())
                    .timestamp(curTime.getTime())
                    .camera(true)
                    .sendUserInfo(getSendUser(userId,roleEnum));

            // 开启全员静音,重置学员状态
            if (schedule.getMuteAll()) {
                action.microphone(false);
            }

            RTCRoomMessage roomMessage = RTCRoomMessage.builder()
                    .objectName(RTCRoomMessage.MEMBER_CHANGE_MESSAGE)
                    .fromUserId(userId)
                    .content(action.build())
                    .toChatRoomId(roomId)
                    .isPersisted(1)
                    .isIncludeSender(1)
                    .build();

            pluginService.sendChatRoomMessage(roomMessage);
        } else {
            imHelper.publishMessage(userId, roomId, msg);
        }
        log.info("join room success: roomId = {}, userId = {}, role = {}", roomId, userId, roleEnum);
        signInSuccess(roomMember, deviceNum);
    }

    public void signInSuccess(RoomMember roomMember, String deviceNum) {
        String roomId = roomMember.getRid();
        String userId = roomMember.getUid();
        String currentRoomIdKey = roomId + userId;

        Long firstCourseId = Long.parseLong(roomId.substring(1));
        Long currentRoomId;
        if (redisTemplate.hasKey(currentRoomIdKey)) {
            currentRoomId = Long.parseLong(redisTemplate.opsForValue().get(currentRoomIdKey));
        } else {
            log.error("signInFailure: roomId={}, userId={}", roomId, userId);
            currentRoomId = firstCourseId;
        }
        log.info("signInSuccess: roomId={}, userId={},currentRoomId={}", roomId, userId, currentRoomId);
        Integer userIdInt = Integer.parseInt(userId);

        RoleEnum roleEnum = RoleEnum.getEnumByValue(roomMember.getRole());
        if (roleEnum == RoleTeacher) {
            teacherAttendanceService.addTeacherAttendanceSignIn(firstCourseId, userIdInt, currentRoomId, deviceNum);
        } else {
            studentAttendanceService.addStudentAttendanceSignIn(firstCourseId, userIdInt, currentRoomId);
        }
        redisTemplate.delete(roomId + userId);
    }

    public void saveRoom(String roomId, String roomName, Date createTime, String display) {
        Room room = new Room();
        room.setRid(roomId);
        room.setName(roomName);
        room.setCreateDt(createTime);
        room.setDisplay(display);
        room.setWhiteboardNameIndex(0);
        roomDao.save(room);
    }

    public RoomMember saveRoomMember(String userId, String headUrl, String userName, String roomId, int role, Date joinTime, Boolean muteAll) {
        RoomMember roomMember = new RoomMember();
        roomMember.setUid(userId);
        roomMember.setName(userName);
        roomMember.setRid(roomId);
        roomMember.setRole(role);
        roomMember.setCamera(true);
        roomMember.setJoinDt(joinTime);
        roomMember.setMusicMode(false);
        roomMember.setHeadUrl(headUrl);
        roomMember.setMic(muteAll);
        roomMemberDao.save(roomMember);
        return roomMember;
    }

    public String getRoomServiceProvider(String roomId ) {
        Integer firstCourseId = Integer.parseInt(roomId.substring(1));
        CourseSchedule courseSchedule = courseScheduleDao.get(firstCourseId.longValue());
        return courseSchedule.getServiceProvider();
    }

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED)
    @Override
    public void leaveRoomSuccess(String roomId, String userId, String deviceNum) throws Exception {

        // 用户退出房间多次触发调用判定
        RoomMember roomMember = roomMemberDao.findByRidAndUid(roomId, userId);
        if (Objects.isNull(roomMember)) {
            log.warn("leaveRoomSuccess: REPEATED_EXECUTION roomId={}, userId={}, deviceNum={}", roomId, userId, deviceNum);
            return;
        }
        log.info("leaveRoomSuccess: roomId={}, userId={}, deviceNum={}", roomId, userId, deviceNum);

        Integer firstCourseId = Integer.parseInt(roomId.substring(1));
        RoleEnum roleEnum;
        int parseInt = Integer.parseInt(userId);
        Teacher teacher = teacherDao.get(parseInt);
        CourseSchedule courseSchedule = courseScheduleDao.get(firstCourseId.longValue());
        if (teacher != null && teacher.getId().equals(courseSchedule.getActualTeacherId())) {
            roleEnum = RoleTeacher;
            courseScheduleStudentMusicScoreDao.closePlayStatus(courseSchedule.getId(), null, null);
        } else {
            roleEnum = RoleStudent;
        }

        String leaveSuccessKey = "leaveRoomSuccess" + roomId + userId;
        Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(leaveSuccessKey, roomId, 1L, TimeUnit.SECONDS);
        log.info("leaveRoomSuccess: roomId={}, userId={},deviceNum={},aBoolean={}", roomId, userId, deviceNum, aBoolean);
        if (Boolean.FALSE.equals(aBoolean)) {
            if (StringUtils.isNotEmpty(deviceNum)) {
                //如果设备号不为空,更新设备号
                if (roleEnum == RoleTeacher) {
                    teacherAttendanceService.updateDeviceNum(firstCourseId, userId, null, deviceNum);
//                    teacherAttendanceService.addTeacherAttendanceSignOut(firstCourseId.longValue(),parseInt,deviceNum);
                }
            }
            return;
        }

        String username;
        SysUser sysUser = sysUserFeignService.queryUserById(parseInt);
        if (roleEnum == RoleTeacher) {
            username = sysUser.getRealName();
            courseScheduleStudentPaymentDao.adjustPlayMidi(firstCourseId, null, null);
            courseScheduleStudentPaymentDao.adjustExamSong(firstCourseId.longValue(), null, null);
            teacherAttendanceService.addTeacherAttendanceSignOut(firstCourseId.longValue(), parseInt, deviceNum);

            //关闭所有曲目播放
            courseScheduleStudentMusicScoreDao.closePlayStatus(firstCourseId, null, null);
        } else {
            username = sysUser.getUsername();
            studentAttendanceService.addStudentAttendanceSignOut(firstCourseId.longValue(), parseInt);
        }
        Room room = roomDao.findByRid(roomId);
        if (room == null) {
            roomMemberDao.deleteUserByRidAndUid(roomId, userId);
            userDao.deleteByUid(userId);
            return;
        }
        if (roleEnum != RoleStudent && isUserDisplay(room, userId)) {
            updateDisplay(roomId, userId, "", 0);
        }

        if (roomMemberDao.countByRid(roomId) <= 1) {
            // 删除群组用户信息
            roomMemberDao.deleteUserByRidAndUid(roomId, userId);
            // 删除群组信息
            roomDao.deleteByRid(roomId);
            // 删除白板信息
            deleteWhiteboardByUser(roomId, userId);
            // 删除群组
            this.dismissImGroup(userId, roomId, courseSchedule.getServiceProvider());
            //关闭所有曲目播放
            courseScheduleStudentMusicScoreDao.closePlayStatus(courseSchedule.getId(), null, null);
            log.info("leaveRoomSuccess dismiss the room: {}, userId: {}, role={}", roomId, userId, roleEnum.name());
        } else {
            roomMemberDao.deleteUserByRidAndUid(roomId, userId);

            // 获取RTC服务提供方
            String rtcServiceProvider = Optional.ofNullable(courseSchedule.getServiceProvider()).orElse("rongCloud");

            RTCRoomPluginService pluginService = rtcRoomPluginContext.getPluginService(rtcServiceProvider);
            if (rtcServiceProvider.equals(TencentCloudRTCPlugin.PLUGIN_NAME)) {
                // 腾讯云RTC服务
                RTCRoomMessage.MessageContent messageContent = RTCRoomMessage.MessageContent
                        .builder()
                        .action(EMemberAction.LEAVE.getValue())
                        .role(roleEnum.getValue())
                        .handUpOn(false)
                        .timestamp(System.currentTimeMillis())
                        .microphone(false)
                        .camera(false)
                        .sendUserInfo(getSendUser(userId, roleEnum))
                        .build();

                RTCRoomMessage roomMessage = RTCRoomMessage.builder()
                        .objectName(RTCRoomMessage.MEMBER_CHANGE_MESSAGE)
                        .toChatRoomId(roomId)
                        .content(messageContent)
                        .fromUserId(userId)
                        .isPersisted(1)
                        .isIncludeSender(1)
                        .build();

                pluginService.sendChatRoomMessage(roomMessage);
            } else {
                MemberChangedMessage msg = new MemberChangedMessage(MemberChangedMessage.Action_Leave, userId, roleEnum.getValue());
                msg.setUserName(username);
                imHelper.publishMessage(userId, roomId, msg);
//            imHelper.quit(new String[]{userId}, roomId);
            }

            log.info("leaveRoomSuccess quit group: roomId={},userId: {}", roomId, userId);
        }
        userDao.deleteByUid(userId);
    }

    /**
     * 事件回调通知
     *
     * @param roomId     房间编号
     * @param userId     用户编号
     * @param deviceNum  设备编号
     * @param callbackTs 事件回调时间
     * @throws Exception Exception
     */
    @Transactional
    @Override
    public void leaveRoomSuccess(String roomId, String userId, String deviceNum, Long callbackTs) throws Exception {

        // 用户退出房间多次触发调用判定
        RoomMember roomMember = roomMemberDao.findByRidAndUid(roomId, userId);
        if (Objects.isNull(roomMember)) {
            log.warn("leaveRoomSuccess: REPEATED_EXECUTION roomId={}, userId={}, deviceNum={}", roomId, userId, deviceNum);
            return;
        }

        // 回调整事件延迟通知
        if (callbackTs < roomMember.getJoinDt().getTime()) {
            log.warn("leaveRoomSuccess: q roomId={}, userId={}, deviceNum={}, callbackTs={}, joinTs={}",
                    roomId, userId, deviceNum, callbackTs, roomMember.getJoinDt().getTime());
            return;
        }

        log.info("leaveRoomSuccess: roomId={}, userId={}, deviceNum={}, joinTs={}, callbackTs={}", roomId, userId, deviceNum,
                roomMember.getJoinDt().getTime(), callbackTs);
        // 用户离开事件
        leaveRoomSuccess(roomId, userId, deviceNum);
    }

    public void deleteWhiteboardByUser(String roomId, String userId) throws Exception {
        List<Whiteboard> whiteboardList = whiteboardDao.findByRidAndCreator(roomId, userId);
        if (!whiteboardList.isEmpty()) {
            whiteboardDao.deleteByRidAndCreator(roomId, userId);
            for (Whiteboard wb : whiteboardList) {
                whiteBoardHelper.destroy(wb.getWbRoom());
            }
        }
    }

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    @Override
    public void destroyRoom(String roomId) {

        // 查询课程信息
        CourseSchedule courseSchedule = courseScheduleDao.get(Long.parseLong(roomId.substring(1)));
        if (Objects.isNull(courseSchedule)) {
            throw new BizException("无效的课程编号");
        }

        whiteboardDao.deleteByRid(roomId);
        Room room = roomDao.findByRid(roomId);
        if (room == null) {
            List<RoomMember> list = roomMemberDao.findByRid(roomId);
            if (!list.isEmpty()) {
                try {
                    this.dismissImGroup(list.get(0).getUid(), roomId, courseSchedule.getServiceProvider());
                } catch (Exception e) {
                    log.error("destroyRoom: {}", e.getMessage());
                    e.printStackTrace();
                }
            }
            roomMemberDao.deleteByRid(roomId);
            log.info("destroyRoom: {}", roomId);
        }
    }

    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    @Override
    public Boolean downgrade(String roomId, List<ReqChangeUserRoleData.ChangedUser> users) throws Exception {
        CheckUtils.checkArgument(roomId != null, "roomId must't be null");
        CheckUtils.checkArgument(users.size() > 0, "the changed user list must't be null");
        SysUser authUser = sysUserFeignService.queryUserInfo();
        String userId = authUser.getId().toString();
        Room room = roomDao.findByRid(roomId);
        if (room == null) {
            throw new ApiException(ErrorEnum.ERR_ROOM_NOT_EXIST);
        }

        boolean result = false;
        List<RoleChangedMessage.ChangedUser> changedUsers = new ArrayList<>();
        for (ReqChangeUserRoleData.ChangedUser user : users) {
            String changedUserId = user.getUserId();
            RoleEnum changedRole = RoleEnum.getEnumByValue(user.getRole());
            if (changedUserId.equals(userId)) {
                log.error("can not change self role: {}, {}, {}", roomId, userId, changedRole);
                throw new ApiException(ErrorEnum.ERR_CHANGE_SELF_ROLE);
            } else {
                RoomMember oldUser = roomMemberDao.findByRidAndUid(roomId, changedUserId);
                if (oldUser != null) {
                    if (changedRole.equals(RoleEnum.RoleAudience)) {
                        int r = roomMemberDao.updateRoleByRidAndUid(roomId, changedUserId, changedRole.getValue());
                        RoleChangedMessage.ChangedUser u = new RoleChangedMessage.ChangedUser(changedUserId, changedRole.getValue());
                        UserInfo userInfo = userDao.findByUid(changedUserId);
                        if (userInfo != null) {
                            u.setUserName(userInfo.getName());
                        }
                        changedUsers.add(u);
                        log.info("change the role: {}, {}, {}, result: {}", roomId, userId, changedRole, r);
                        result = true;
                    }
                    if (oldUser.getRole() == RoleTeacher.getValue() && isUserDisplay(room, oldUser.getUid())) {
                        updateDisplay(roomId, userId, "", 1);
                    } else {
                        log.info("don't update display: room={}, userRole={}", room, RoleEnum.getEnumByValue(oldUser.getRole()));
                    }
                } else {
                    log.info("role changed fail, not exist: {} - {} - {}", roomId, userId, changedRole);
                }
            }
        }
        if (result) {
            RoleChangedMessage msg = new RoleChangedMessage(userId);
            msg.setUsers(changedUsers);
            imHelper.publishMessage(userId, roomId, msg, 1);
        }
        return result;
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public Boolean kickMember(String roomId) throws Exception {
        SysUser authUser = sysUserFeignService.queryUserInfo();
        String userId = authUser.getId().toString();
        CheckUtils.checkArgument(userId != null, "userId must't be null");
        CheckUtils.checkArgument(roomId != null, "roomId must't be null");
        log.info("kickMember: roomId={}, userId={}", roomId, userId);
        RoomMember roomMember = roomMemberDao.findByRidAndUid(roomId, userId);
        if (roomMember == null) {
            return true;
        }
        Room room = roomDao.findByRid(roomId);
        Integer firstCourseId = Integer.parseInt(roomId.substring(1));
        CourseSchedule courseSchedule = courseScheduleDao.get(firstCourseId.longValue());
        MemberChangedMessage msg = new MemberChangedMessage(MemberChangedMessage.Action_Kick, userId, roomMember.getRole());
        msg.setUserName(roomMember.getName());

        if (TencentCloudRTCPlugin.PLUGIN_NAME.equals(courseSchedule.getServiceProvider())) {

            sendJoinLeaveMessage(roomMember, MemberChangedMessage.Action_Kick);
        } else {
            imHelper.publishMessage(userId, roomId, msg, 1);
        }
        if (roomMember.getRole() == RoleTeacher.getValue() && isUserDisplay(room, userId)) {
            updateDisplay(roomId, userId, "", 1);
        }

        roomMemberDao.deleteUserByRidAndUid(roomId, userId);
        userDao.deleteByUid(userId);

//        IMApiResultInfo apiResultInfo = imHelper.quit(new String[]{userId}, roomId);
//        if (!apiResultInfo.isSuccess()) {
//            throw new ApiException(ErrorEnum.ERR_EXIT_ROOM_ERROR, apiResultInfo.getErrorMessage());
//        }
        return true;
    }

    private void sendJoinLeaveMessage(RoomMember roomMember, int status) throws Exception {
        // 获取RTC服务提供方
        RTCRoomPluginService pluginService = rtcRoomPluginContext.getPluginService(TencentCloudRTCPlugin.PLUGIN_NAME);
        // 腾讯云RTC服务
        RTCRoomMessage.MessageContent.MessageContentBuilder action = RTCRoomMessage.MessageContent.builder()
                .action(status)
                .timestamp(System.currentTimeMillis())
                .role(roomMember.getRole());
        action.handUpOn(false)
                .microphone(false)
                .camera(false).sendUserInfo(getSendUser(roomMember.getUid(),RoleEnum.getEnumByValue(roomMember.getRole())));


        RTCRoomMessage roomMessage = RTCRoomMessage.builder()
                .objectName(RTCRoomMessage.MEMBER_CHANGE_MESSAGE)
                .content(action.build())
                .toChatRoomId(roomMember.getRid())
                .fromUserId(roomMember.getUid())
                .isIncludeSender(1)
                .isPersisted(1)
                .build();

        pluginService.sendChatRoomMessage(roomMessage);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean display(String roomId, int type, String uri, String targetUserId) throws Exception {

        // 重置uri为空字符串
        if (StringUtils.isBlank(uri) || "null".equals(uri)) {
            uri = "";
        }

        SysUser authUser = sysUserFeignService.queryUserInfo();
        String userId = authUser.getId().toString();
        log.info("display in room: {}, type = {}, uri = {}", roomId, type, uri);
        CheckUtils.checkArgument(roomId != null, "roomId must't be null");
        CheckUtils.checkArgument(type >= 0 && type < DisplayEnum.values().length, "type not exist");
        DisplayEnum displayEnum = DisplayEnum.values()[type];

        RoomMember roomMember = roomMemberDao.findByRidAndUid(roomId, userId);

        String roomServiceProviter = getRoomServiceProvider(roomId);
        if (displayEnum.equals(DisplayEnum.None)) {
            roomDao.updateDisplayByRid(roomId, "");
            if (TencentCloudRTCPlugin.PLUGIN_NAME.equals(roomServiceProviter)) {

                sendDisplayMessage("", roomMember);
            } else {
                DisplayMessage displayMessage = new DisplayMessage("");
                IMApiResultInfo apiResultInfo = imHelper.publishMessage(userId, roomId, displayMessage);
                if (apiResultInfo.isSuccess()) {
                    return true;
                } else {
                    throw new ApiException(ErrorEnum.ERR_MESSAGE_ERROR, apiResultInfo.getErrorMessage());
                }
            }
        }

        String display = "display://type=" + type;
        if (displayEnum.equals(DisplayEnum.Teacher)) {
            List<RoomMember> teachers = roomMemberDao.findByRidAndRole(roomId, RoleTeacher.getValue());
            if (teachers.isEmpty()) {
                throw new ApiException(ErrorEnum.ERR_TEACHER_NOT_EXIST_IN_ROOM);
            } else {
                display += "?userId=" + teachers.get(0).getUid() + "?uri=";
                roomDao.updateDisplayByRid(roomId, display);
                if (TencentCloudRTCPlugin.PLUGIN_NAME.equals(roomServiceProviter)) {

                    sendDisplayMessage(display, roomMember);
                } else {
                    DisplayMessage displayMessage = new DisplayMessage(display);
                    imHelper.publishMessage(userId, roomId, displayMessage);
                }
                log.info("change display to teacher: roomId={}, display={}", roomId, display);

            }
        } else if (displayEnum.equals(DisplayEnum.Assistant)) {
            List<RoomMember> assistants = roomMemberDao.findByRidAndRole(roomId, RoleEnum.RoleAssistant.getValue());
            if (assistants.isEmpty()) {
                throw new ApiException(ErrorEnum.ERR_ASSISTANT_NOT_EXIST_IN_ROOM);
            } else {
                display += "?userId=" + assistants.get(0).getUid() + "?uri=";
                roomDao.updateDisplayByRid(roomId, display);
                if (TencentCloudRTCPlugin.PLUGIN_NAME.equals(roomServiceProviter)) {

                    sendDisplayMessage(display, roomMember);
                } else {
                    DisplayMessage displayMessage = new DisplayMessage(display);
                    imHelper.publishMessage(userId, roomId, displayMessage);
                }
                log.info("change display to assistant: roomId={}, display={}", roomId, display);
            }
        } else if (displayEnum.equals(DisplayEnum.Screen)) {
            display += "?userId=" + userId + "?uri=";
            roomDao.updateDisplayByRid(roomId, display);
            if (TencentCloudRTCPlugin.PLUGIN_NAME.equals(roomServiceProviter)) {

                sendDisplayMessage(display, roomMember);
            } else {
                DisplayMessage displayMessage = new DisplayMessage(display);
                imHelper.publishMessage(userId, roomId, displayMessage);
            }
            log.info("change display to screen: roomId={}, display={}", roomId, display);
        } else if (displayEnum.equals(DisplayEnum.STUDENT)) {
            display += "?userId=" + targetUserId + "?uri=" + uri;
            roomDao.updateDisplayByRid(roomId, display);
            if (TencentCloudRTCPlugin.PLUGIN_NAME.equals(roomServiceProviter)) {

                sendDisplayMessage(display, roomMember);
            } else {
                DisplayMessage displayMessage = new DisplayMessage(display);
                imHelper.publishMessage(userId, roomId, displayMessage);
            }
            log.info("change display to screen: roomId={}, display={}", roomId, display);
        } else {
            display += "?userId=" + userId + "?uri=" + uri;
//            CheckUtils.checkArgument(uri != null, "uri must't be null");
//            CheckUtils.checkArgument(whiteboardDao.findByRidAndWbid(roomId, uri).size() > 0, "whiteboard not exist");
            roomDao.updateDisplayByRid(roomId, display);
            if (TencentCloudRTCPlugin.PLUGIN_NAME.equals(roomServiceProviter)) {

                sendDisplayMessage(display, roomMember);
            } else {
                DisplayMessage displayMessage = new DisplayMessage(display);
                imHelper.publishMessage(userId, roomId, displayMessage);
            }
        }
        log.info("result display in room: {}, type = {}, uri = {}", roomId, type, uri);
        return true;
    }

    private void sendDisplayMessage(String display, RoomMember roomMember) throws Exception {
        // 获取RTC服务提供方
        RTCRoomPluginService pluginService = rtcRoomPluginContext.getPluginService(TencentCloudRTCPlugin.PLUGIN_NAME);
        // 腾讯云RTC服务
        RTCRoomMessage.MessageContent.MessageContentBuilder action = RTCRoomMessage.MessageContent.builder()
                .display(display)
                .sendUserInfo(getSendUser(roomMember.getUid(),RoleEnum.getEnumByValue(roomMember.getRole())));


        RTCRoomMessage roomMessage = RTCRoomMessage.builder()
                .objectName(RTCRoomMessage.DISPLAY_MESSAGE)
                .content(action.build())
                .toChatRoomId(roomMember.getRid())
                .fromUserId(roomMember.getUid())
                .isIncludeSender(1)
                .isPersisted(1)
                .build();

        pluginService.sendChatRoomMessage(roomMessage);
    }

    private RTCRoomMessage.MessageUser getSendUser(String userId,RoleEnum role) {

        SysUser sysUser = sysUserFeignService.queryUserById(Integer.parseInt(userId));
        RTCRoomMessage.MessageUser build = RTCRoomMessage.MessageUser.builder()
                .sendUserId(userId)
                .sendUserName(sysUser.getUsername())
                .avatarUrl(sysUser.getAvatar())
                .build();
        if (role == RoleTeacher) {
            build.setSendUserName(sysUser.getRealName());
        }
        return build;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String createWhiteBoard(String roomId) throws Exception {
        CheckUtils.checkArgument(roomId != null, "roomId must't be null");
        SysUser authUser = sysUserFeignService.queryUserInfo();
        String userId = authUser.getId().toString();

        log.info("createWhiteBoard: roomId = {}", roomId);

        String wbRoom = IdentifierUtils.uuid();
        WhiteBoardApiResultInfo resultInfo = whiteBoardHelper.create(wbRoom);
        if (resultInfo.isSuccess()) {
            String wbId = resultInfo.getData();
            Date date = DateTimeUtils.currentUTC();
            Room room = roomDao.findByRid(roomId);
            int whiteboardNameIndex = room.getWhiteboardNameIndex() + 1;
            String name = "白板" + whiteboardNameIndex;
            roomDao.updateWhiteboardNameIndexByRid(roomId, whiteboardNameIndex);
            Whiteboard wb = new Whiteboard();
            wb.setRid(roomId);
            wb.setWbRoom(wbRoom);
            wb.setWbid(wbId);
            wb.setName(name);
            wb.setCreator(userId);
            wb.setCreateDt(date);
            wb.setCurPg(0);
            whiteboardDao.save(wb);
            WhiteboardMessage wbmsg = new WhiteboardMessage(WhiteboardMessage.Create);
            wbmsg.setWhiteboardId(wbId);
            wbmsg.setWhiteboardName(name);
            imHelper.publishMessage(userId, roomId, wbmsg);
            String display = "display://type=2?userId=" + userId + "?uri=" + Optional.ofNullable(wbId).orElse("");
            roomDao.updateDisplayByRid(roomId, display);

            RoomMember roomMember = roomMemberDao.findByRidAndUid(roomId, userId);

            String roomServiceProviter = getRoomServiceProvider(roomId);
            if (TencentCloudRTCPlugin.PLUGIN_NAME.equals(roomServiceProviter)) {

                sendDisplayMessage(display, roomMember);
            } else {
                DisplayMessage displayMessage = new DisplayMessage(display);
                imHelper.publishMessage(userId, roomId, displayMessage, 1);
            }

            return wbId;
        } else {
            throw new ApiException(ErrorEnum.ERR_CREATE_WHITE_BOARD, resultInfo.getMsg());
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean deleteWhiteboard(String roomId, String whiteBoardId) throws Exception {
        CheckUtils.checkArgument(roomId != null, "roomId must't be null");
        CheckUtils.checkArgument(whiteBoardId != null, "whiteBoardId must't be null");
        SysUser authUser = sysUserFeignService.queryUserInfo();
        String userId = authUser.getId().toString();

        List<Whiteboard> whiteboardList = whiteboardDao.findByRidAndWbid(roomId, whiteBoardId);
        CheckUtils.checkArgument(whiteboardList.size() > 0, "whiteboard not exist");

        Room room = roomDao.findByRid(roomId);
        CheckUtils.checkArgument(room != null, "room not exist");

        log.info("deleteWhiteboard: room={}, whiteBoardId={}", room, whiteBoardId);

        String display = room.getDisplay();
        if (display.contains("uri=" + whiteBoardId)) {
            int result = roomDao.updateDisplayByRid(roomId, "");
            log.info("clear room display, room: {}, result: {}", roomId, result);

            RoomMember roomMember = roomMemberDao.findByRidAndUid(roomId, userId);

            String roomServiceProviter = getRoomServiceProvider(roomId);
            if (TencentCloudRTCPlugin.PLUGIN_NAME.equals(roomServiceProviter)) {

                sendDisplayMessage(display, roomMember);
            } else {
                DisplayMessage displayMessage = new DisplayMessage("");
                imHelper.publishMessage(userId, roomId, displayMessage, 1);
            }
        } else {
            log.info("no display to clean: room={}", room);
        }

        String wbRoom = whiteboardList.get(0).getWbRoom();
        WhiteBoardApiResultInfo resultInfo = whiteBoardHelper.destroy(wbRoom);
        if (resultInfo.isSuccess()) {
            int result = whiteboardDao.deleteByWbid(whiteBoardId);
            log.info("delete whiteboard: roomId = {}, whiteBoardId = {}, result = {}", roomId, whiteBoardId, result);
            WhiteboardMessage wbmsg = new WhiteboardMessage(WhiteboardMessage.Delete);
            wbmsg.setWhiteboardId(whiteBoardId);
            imHelper.publishMessage(userId, roomId, wbmsg, 1);
            return true;
        } else {
            throw new ApiException(ErrorEnum.ERR_DELETE_WHITE_BOARD, resultInfo.getMsg());
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<RoomResult.WhiteboardResult> getWhiteboard(String roomId) throws Exception {
        CheckUtils.checkArgument(roomId != null, "roomId must't be null");
        CheckUtils.checkArgument(roomDao.existsByRid(roomId), "room not exist");

        List<Whiteboard> whiteboards = whiteboardDao.findByRid(roomId);
        List<RoomResult.WhiteboardResult> result = new ArrayList<>();
        for (Whiteboard wb : whiteboards) {
            RoomResult.WhiteboardResult r = new RoomResult.WhiteboardResult();
            r.setName(wb.getName());
            r.setCurPg(wb.getCurPg());
            r.setWhiteboardId(wb.getWbid());
            result.add(r);
        }
        return result;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean turnWhiteBoardPage(String roomId, String whiteBoardId, int page) throws Exception {
        CheckUtils.checkArgument(roomId != null, "roomId must't be null");
        CheckUtils.checkArgument(whiteBoardId != null, "whiteBoardId must't be null");
        SysUser authUser = sysUserFeignService.queryUserInfo();
        String userId = authUser.getId().toString();
        Room room = roomDao.findByRid(roomId);
        CheckUtils.checkArgument(room != null, "room not exist");

        int result = whiteboardDao.updatePageByRidAndWbid(roomId, whiteBoardId, page);
        log.info("turn page to: {}, room: {}, wb : {}; r: {}", page, roomId, whiteBoardId, result);

        TurnPageMessage turnPageMessage = new TurnPageMessage(whiteBoardId, userId, page);
        imHelper.publishMessage(userId, roomId, turnPageMessage);
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean controlDevice(ReqDeviceControlData data) throws Exception {

        // RTC服务方信息
        if (StringUtils.isBlank(data.getServiceProvider())) {

            // 课程信息
            CourseSchedule courseSchedule = courseScheduleDao.get(Long.parseLong(data.getRoomId().substring(1)));
            if (Objects.isNull(courseSchedule)) {
                throw new BizException("无效的课程编号");
            }
            data.setServiceProvider(courseSchedule.getServiceProvider());

            // 课程老师信息
            SysUser sysUser = sysUserFeignService.queryUserById(courseSchedule.getTeacherId());
            if (Objects.nonNull(sysUser)) {
                data.setSendUserId(sysUser.getId().toString());
                data.setSendUserName(sysUser.getRealName());
                data.setAvatar(sysUser.getAvatar());
            }
        }

        String roomId = data.getRoomId();
        String userId = data.getUserId();
        DeviceTypeEnum typeEnum;
        boolean enable;
        if (data.getCameraOn() != null) {
            typeEnum = DeviceTypeEnum.Camera;
            enable = data.getCameraOn();
        } else if (data.getMicrophoneOn() != null) {
            typeEnum = DeviceTypeEnum.Microphone;
            enable = data.getMicrophoneOn();
        } else if (data.getMusicModeOn() != null) {
            typeEnum = DeviceTypeEnum.MusicMode;
            enable = data.getMusicModeOn();
        } else if (data.getHandUpOn() != null) {
            typeEnum = DeviceTypeEnum.HandUp;
            enable = data.getHandUpOn();
        } else if (data.getExamSongOn() != null) {
            typeEnum = DeviceTypeEnum.ExamSong;
            enable = data.getExamSongOn();
        } else if (data.getMusicScoreOn() != null) {
            typeEnum = DeviceTypeEnum.MusicScore;
            enable = data.getMusicScoreOn();
            if (enable) {
                //保存伴奏音量
                roomDao.updateSoundVolumeById(roomId, data.getSoundVolume() == null ? 0 : data.getSoundVolume());
            }
        } else if (data.getAccompanimentOn() != null) {
            typeEnum = DeviceTypeEnum.MusicScoreAccompaniment;
            enable = data.getAccompanimentOn();
            if (enable) {
                //保存伴奏音量
                roomDao.updateSoundVolumeById(roomId, data.getSoundVolume() == null ? 0 : data.getSoundVolume());
            }
        } else {
            throw new ApiException(ErrorEnum.ERR_REQUEST_PARA_ERR);
        }
        SysUser authUser = sysUserFeignService.queryUserInfo();
        log.info("controlDevice: userId = {}, typeEnum = {}, enable = {} ,roomId = {}", userId, typeEnum, enable, roomId);

        // RTC服务对象
        RTCRoomPluginService pluginService = rtcRoomPluginContext.getPluginService(data.getServiceProvider());

        // 通知消息
        RTCRoomMessage.MessageContent notifyContent = RTCRoomMessage.MessageContent
                .builder()
                .type(typeEnum.ordinal())
                .enable(enable)
                .targetId(userId)
                .targetName(Optional.ofNullable(data.getUserName()).orElse(authUser.getUsername()))
                .songId(Optional.ofNullable(data.getMusicScoreAccompanimentId()).map(String::valueOf).orElse(null))
                .songVolume(data.getSoundVolume())
                .sendUserInfo(RTCRoomMessage.MessageUser.builder()
                        .sendUserId(data.getSendUserId())
                        .sendUserName(data.getSendUserName())
                        .avatarUrl(data.getAvatar())
                        .build())
                .build();

        // 消息内容
        /*RTCRoomMessage.MessageContent messageContent = RTCRoomMessage.MessageContent
                .builder()
                .type(typeEnum.ordinal())
                .enable(enable)
                .sendUserInfo(RTCRoomMessage.MessageUser.builder()
                        .sendUserId(userId)
                        .sendUserName(authUser.getUsername())
                        .avatarUrl(authUser.getAvatar())
                        .build())
                .build();*/

        // 腾讯云消息推送
        RTCRoomMessage message = RTCRoomMessage.builder()
                .objectName(RTCRoomMessage.CONTROL_DEVICE_NOTIFY_MESSAGE)
                .fromUserId(userId)
                .toChatRoomId(roomId)
                .content(notifyContent)
                .isPersisted(1)
                .isIncludeSender(0)
                .build();

        if (enable) {
            if (typeEnum.equals(DeviceTypeEnum.ExamSong)) {
                long scheduleId = Long.parseLong(roomId.substring(1));
                ExamSongDownloadData msg;
                String examJson = courseScheduleStudentPaymentDao.getExamJsonByCourseIdAndUserId(scheduleId, Integer.parseInt(userId));
                if (StringUtils.isEmpty(examJson)) {
                    throw new BizException("学员伴奏信息异常");
                } else {
                    msg = JSON.parseObject(examJson, ExamSongDownloadData.class);
                    msg.setEnable(enable);
                }
                courseScheduleStudentPaymentDao.adjustExamSong(scheduleId, Integer.parseInt(userId), JSON.toJSONString(msg));
                DeviceStateChangedMessage deviceResourceMessage = new DeviceStateChangedMessage(typeEnum.ordinal(), enable);
                deviceResourceMessage.setUserId(userId);

                // 消息发送
                if (TencentCloudRTCPlugin.PLUGIN_NAME.equals(pluginService.pluginName())) {
                    // 腾讯云推送
                    pluginService.sendChatRoomMessage(message.objectName(RTCRoomMessage.CONTROL_DEVICE_NOTIFY_MESSAGE).content(notifyContent));
                } else {
                    // 融云推送
                    imHelper.publishMessage(authUser.getId().toString(), roomId, deviceResourceMessage, 1);
                }
            } else if (typeEnum.equals(DeviceTypeEnum.MusicScore)) {
                long scheduleId = Long.parseLong(roomId.substring(1));
                //关闭所有曲目播放
                courseScheduleStudentMusicScoreDao.closePlayStatus(scheduleId, Integer.parseInt(userId), null);
                DeviceStateChangedMessage deviceResourceMessage = new DeviceStateChangedMessage(typeEnum.ordinal(), enable);
                deviceResourceMessage.setMusicScoreAccompanimentId(data.getMusicScoreAccompanimentId());
                deviceResourceMessage.setUserId(userId);
                deviceResourceMessage.setSoundVolume(data.getSoundVolume());
                //原音
                courseScheduleStudentMusicScoreDao.openPlayStatus(scheduleId, data.getMusicScoreAccompanimentId(), Integer.parseInt(userId));
                // 消息发送
                if (TencentCloudRTCPlugin.PLUGIN_NAME.equals(pluginService.pluginName())) {
                    // 腾讯云推送
                    pluginService.sendChatRoomMessage(message.objectName(RTCRoomMessage.CONTROL_DEVICE_NOTIFY_MESSAGE).content(notifyContent));
                } else {
                    // 融云推送
                    imHelper.publishMessage(authUser.getId().toString(), roomId, deviceResourceMessage, 1);
                }
            } else if (typeEnum.equals(DeviceTypeEnum.MusicScoreAccompaniment)) {
                long scheduleId = Long.parseLong(roomId.substring(1));
                //关闭所有曲目播放
                courseScheduleStudentMusicScoreDao.closePlayStatus(scheduleId, Integer.parseInt(userId), null);
                courseScheduleStudentMusicScoreDao.openAccompanimentPlayStatus(scheduleId, data.getMusicScoreAccompanimentId(), Integer.parseInt(userId));
                DeviceStateChangedMessage deviceResourceMessage = new DeviceStateChangedMessage(typeEnum.ordinal(), enable);
                deviceResourceMessage.setMusicScoreAccompanimentId(data.getMusicScoreAccompanimentId());
                deviceResourceMessage.setUserId(userId);
                deviceResourceMessage.setSoundVolume(data.getSoundVolume());// 消息发送
                if (TencentCloudRTCPlugin.PLUGIN_NAME.equals(pluginService.pluginName())) {
                    // 腾讯云推送
                    pluginService.sendChatRoomMessage(message.objectName(RTCRoomMessage.CONTROL_DEVICE_NOTIFY_MESSAGE).content(notifyContent));
                } else {
                    // 融云推送
                    imHelper.publishMessage(authUser.getId().toString(), roomId, deviceResourceMessage, 1);
                }
            } else {
                String ticket = IdentifierUtils.uuid();
                ControlDeviceTaskInfo taskInfo = new ControlDeviceTaskInfo();
                taskInfo.setRoomId(roomId);
                taskInfo.setTypeEnum(typeEnum);
                taskInfo.setOnOff(true);
                taskInfo.setApplyUserId(authUser.getId().toString());
                taskInfo.setTargetUserId(userId);
                taskInfo.setTicket(ticket);
                scheduleManager.addTask(taskInfo);
                ControlDeviceNotifyMessage msg = new ControlDeviceNotifyMessage(ActionEnum.Invite.ordinal());
                msg.setTicket(ticket);
                msg.setType(taskInfo.getTypeEnum().ordinal());
                msg.setOpUserId(authUser.getId().toString());
                msg.setOpUserName(authUser.getUsername());

                // 发送消息
                if (TencentCloudRTCPlugin.PLUGIN_NAME.equals(pluginService.pluginName())) {
                    // 腾讯消息
                    pluginService.sendChatRoomMessage(message.objectName(RTCRoomMessage.CONTROL_DEVICE_NOTIFY_MESSAGE).content(notifyContent));
                } else {
                    // 融云消息
                    imHelper.publishMessage(authUser.getId().toString(), userId, roomId, msg);
                }
            }
        } else {
            if (typeEnum.equals(DeviceTypeEnum.Camera)) {
                roomMemberDao.updateCameraByRidAndUid(roomId, userId, false);
            } else if (typeEnum.equals(DeviceTypeEnum.Microphone)) {
                roomMemberDao.updateMicByRidAndUid(roomId, userId, false);
            } else if (typeEnum.equals(DeviceTypeEnum.HandUp)) {
                roomMemberDao.updateHandByRidAndUid(roomId, userId, false);
            } else if (typeEnum.equals(DeviceTypeEnum.ExamSong)) {
                long scheduleId = Long.parseLong(roomId.substring(1));
                ExamSongDownloadData msg;
                String examJson = courseScheduleStudentPaymentDao.getExamJsonByCourseIdAndUserId(scheduleId, Integer.parseInt(userId));
                if (StringUtils.isEmpty(examJson)) {
                    throw new BizException("学员伴奏信息异常");
                } else {
                    msg = JSON.parseObject(examJson, ExamSongDownloadData.class);
                    msg.setEnable(enable);
                }
                courseScheduleStudentPaymentDao.adjustExamSong(scheduleId, Integer.parseInt(userId), JSON.toJSONString(msg));
            } else if (typeEnum.equals(DeviceTypeEnum.MusicScore)) {
                long scheduleId = Long.parseLong(roomId.substring(1));
                //关闭所有曲目播放
                courseScheduleStudentMusicScoreDao.closePlayStatus(scheduleId, Integer.parseInt(userId), null);
            } else if (typeEnum.equals(DeviceTypeEnum.MusicScoreAccompaniment)) {
                long scheduleId = Long.parseLong(roomId.substring(1));
                //关闭所有曲目播放
                courseScheduleStudentMusicScoreDao.closePlayStatus(scheduleId, Integer.parseInt(userId), null);
            } else {
                roomMemberDao.updateMusicByRidAndUid(roomId, userId, false);
            }
            DeviceStateChangedMessage deviceResourceMessage = new DeviceStateChangedMessage(typeEnum.ordinal(), false);
            deviceResourceMessage.setUserId(userId);
            UserInfo userInfo = userDao.findByUid(userId);
            if (userInfo != null) {
                deviceResourceMessage.setUserName(userInfo.getName());
            }

            // 发送消息
            if (TencentCloudRTCPlugin.PLUGIN_NAME.equals(pluginService.pluginName())) {
                // 腾讯消息
                pluginService.sendChatRoomMessage(message.objectName(RTCRoomMessage.CONTROL_DEVICE_NOTIFY_MESSAGE).content(notifyContent));
            } else {
                // 融云消息
                imHelper.publishMessage(authUser.getId().toString(), roomId, deviceResourceMessage, 1);
            }
        }
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean batchControlDevice(ReqDeviceControlData data) throws Exception {

        // 课程ID
        long courseScheduleId = Long.parseLong(data.getRoomId().substring(1));
        CourseSchedule courseSchedule = courseScheduleDao.get(courseScheduleId);
        if (Objects.isNull(courseSchedule)) {
            throw new BizException("无效的课程编号");
        }
        data.setServiceProvider(courseSchedule.getServiceProvider());

        // 同步全员静音状态
        if (Objects.nonNull(data.getMicrophoneOn())) {
            courseScheduleDao.updateGroupMuteAllStatus(courseScheduleId, !data.getMicrophoneOn());
        }

        // 课程老师信息
        SysUser sysUser = sysUserFeignService.queryUserById(courseSchedule.getTeacherId());
        if (Objects.nonNull(sysUser)) {
            data.setSendUserId(sysUser.getId().toString());
            data.setSendUserName(sysUser.getRealName());
            data.setAvatar(sysUser.getAvatar());
        }

        if (data.getExamSongOn() != null || data.getMusicScoreOn() != null || data.getAccompanimentOn() != null) {
            List<BasicUserDto> students = courseScheduleStudentPaymentDao.findStudents(courseScheduleId);
            for (BasicUserDto e : students) {
                data.setUserId(e.getUserId().toString());
                data.setUserName(e.getName());
                controlDevice(data);
            }
            return true;
        }
        List<RoomMember> roomMembers;
        if (StringUtils.isNotEmpty(data.getUserId())) {
            roomMembers = new ArrayList<>();
            String[] split = data.getUserId().split(",");
            for (int i = 0; i < split.length; i++) {
                roomMembers.add(new RoomMember(split[i]));
            }
        } else {
            roomMembers = roomMemberDao.findByRidAndRole(data.getRoomId(), RoleStudent.getValue());
        }
        if (roomMembers.size() == 0) {
            return false;
        }
        for (RoomMember e : roomMembers) {
            data.setUserId(e.getUid());
            controlDevice(data);
        }
        boolean enable;
        if (data.getCameraOn() != null) {
            enable = data.getCameraOn();
            roomMemberDao.updateCameraByRidAndRole(data.getRoomId(), RoleStudent.getValue(), enable);
        } else if (data.getMicrophoneOn() != null) {
            enable = data.getMicrophoneOn();
            roomMemberDao.updateMicByRidAndRole(data.getRoomId(), RoleStudent.getValue(), enable);
        } else if (data.getMusicModeOn() != null) {
            enable = data.getMusicModeOn();
            roomMemberDao.updateMusicByRidAndRole(data.getRoomId(), RoleStudent.getValue(), enable);
        } else if (data.getHandUpOn() != null) {
            enable = data.getHandUpOn();
            roomMemberDao.updateHandByRidAndRole(data.getRoomId(), RoleStudent.getValue(), enable);
        }  else
            return true;

        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean approveControlDevice(ReqDeviceControlData data) throws Exception {
        String ticket = data.getTicket();
        String roomId = data.getRoomId();
        CheckUtils.checkArgument(ticket != null, "ticket must't be null");
        SysUser authUser = sysUserFeignService.queryUserInfo();
        String userId = authUser.getId().toString();
        log.info("approveControlDevice: ticket={}", ticket);
        ControlDeviceTaskInfo taskInfo = (ControlDeviceTaskInfo) scheduleManager.executeTask(ticket);
        if (taskInfo.getTypeEnum().equals(DeviceTypeEnum.Camera)) {
            roomMemberDao.updateCameraByRidAndUid(roomId, userId, taskInfo.isOnOff());
        } else if (taskInfo.getTypeEnum().equals(DeviceTypeEnum.ExamSong)) {
            long scheduleId = Long.parseLong(roomId.substring(1));
            ExamSongDownloadData msg;
            String examJson = courseScheduleStudentPaymentDao.getExamJsonByCourseIdAndUserId(scheduleId, authUser.getId());
            if (StringUtils.isEmpty(examJson)) {
                throw new BizException("学员伴奏信息异常");
            } else {
                msg = JSON.parseObject(examJson, ExamSongDownloadData.class);
                msg.setEnable(true);
            }
            courseScheduleStudentPaymentDao.adjustExamSong(scheduleId, authUser.getId(), JSON.toJSONString(msg));
        } else {
            roomMemberDao.updateMicByRidAndUid(roomId, userId, taskInfo.isOnOff());
        }

        RoomMember roomMember = roomMemberDao.findByRidAndUid(roomId, userId);

        String roomServiceProviter = getRoomServiceProvider(roomId);
        if (TencentCloudRTCPlugin.PLUGIN_NAME.equals(roomServiceProviter)) {

            // 获取RTC服务提供方
            RTCRoomPluginService pluginService = rtcRoomPluginContext.getPluginService(TencentCloudRTCPlugin.PLUGIN_NAME);
            // 腾讯云RTC服务
            RTCRoomMessage.MessageContent.MessageContentBuilder action = RTCRoomMessage.MessageContent.builder()
                    .type(ActionEnum.Approve.ordinal())
                    .enable(taskInfo.isOnOff())
                    .targetId(userId)
                    .targetName(authUser.getUsername())
                    .sendUserInfo(getSendUser(taskInfo.getApplyUserId(), RoleTeacher));


            RTCRoomMessage roomMessage = RTCRoomMessage.builder()
                    .objectName(RTCRoomMessage.CONTROL_DEVICE_NOTIFY_MESSAGE)
                    .content(action.build())
                    .toChatRoomId(roomMember.getRid())
                    .fromUserId(roomMember.getUid())
                    .isIncludeSender(1)
                    .isPersisted(1)
                    .build();

            pluginService.sendChatRoomMessage(roomMessage);

            // 腾讯云RTC服务
            action = RTCRoomMessage.MessageContent.builder()
                    .type(ActionEnum.Approve.ordinal())
                    .enable(taskInfo.isOnOff())
                    .sendUserInfo(getSendUser(roomMember.getUid(),RoleEnum.getEnumByValue(roomMember.getRole())));

            // 腾讯云消息推送
            RTCRoomMessage message = RTCRoomMessage.builder()
                    .objectName(RTCRoomMessage.DEVICE_MESSAGE)
                    .fromUserId(userId)
                    .toChatRoomId(roomId)
                    .content(action.build())
                    .isPersisted(1)
                    .isIncludeSender(0)
                    .build();

            pluginService.sendChatRoomMessage(message);
        } else {
            ControlDeviceNotifyMessage msg = new ControlDeviceNotifyMessage(ActionEnum.Approve.ordinal());
            msg.setType(taskInfo.getTypeEnum().ordinal());
            msg.setOpUserId(userId);
            msg.setOpUserName(authUser.getUsername());
            imHelper.publishMessage(userId, taskInfo.getApplyUserId(), roomId, msg);

            DeviceStateChangedMessage deviceResourceMessage = new DeviceStateChangedMessage(taskInfo.getTypeEnum().ordinal(), taskInfo.isOnOff());
            deviceResourceMessage.setUserId(userId);
            imHelper.publishMessage(userId, roomId, deviceResourceMessage, 1);
        }
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean rejectControlDevice(String roomId, String ticket) throws Exception {
        CheckUtils.checkArgument(ticket != null, "ticket must't be null");
        SysUser authUser = sysUserFeignService.queryUserInfo();
        String userId = authUser.getId().toString();

        log.info("rejectControlDevice: ticket={}", ticket);
        ControlDeviceTaskInfo taskInfo = (ControlDeviceTaskInfo) scheduleManager.executeTask(ticket);
        ControlDeviceNotifyMessage msg = new ControlDeviceNotifyMessage(ActionEnum.Reject.ordinal());
        msg.setType(taskInfo.getTypeEnum().ordinal());
        msg.setOpUserId(userId);
        msg.setOpUserName(authUser.getUsername());

        RoomMember roomMember = roomMemberDao.findByRidAndUid(roomId, userId);

        String roomServiceProviter = getRoomServiceProvider(roomId);
        if (TencentCloudRTCPlugin.PLUGIN_NAME.equals(roomServiceProviter)) {

            // 获取RTC服务提供方
            RTCRoomPluginService pluginService = rtcRoomPluginContext.getPluginService(TencentCloudRTCPlugin.PLUGIN_NAME);

            RTCRoomMessage.MessageContent.MessageContentBuilder action = RTCRoomMessage.MessageContent.builder()
                    .type(ActionEnum.Reject.ordinal())
                    .enable(taskInfo.isOnOff())
                    .targetId(userId)
                    .targetName(authUser.getUsername())
                    .sendUserInfo(getSendUser(taskInfo.getApplyUserId(), RoleTeacher));


            RTCRoomMessage roomMessage = RTCRoomMessage.builder()
                    .objectName(RTCRoomMessage.CONTROL_DEVICE_NOTIFY_MESSAGE)
                    .content(action.build())
                    .toChatRoomId(roomMember.getRid())
                    .fromUserId(roomMember.getUid())
                    .isIncludeSender(1)
                    .isPersisted(1)
                    .build();

            pluginService.sendChatRoomMessage(roomMessage);
        } else {
            imHelper.publishMessage(userId, taskInfo.getApplyUserId(), roomId, msg);
        }
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean syncDeviceState(ReqDeviceControlData data) throws Exception {
        String roomId = data.getRoomId();
        DeviceTypeEnum type;
        boolean enable;
        if (data.getCameraOn() != null) {
            type = DeviceTypeEnum.Camera;
            enable = data.getCameraOn();
        } else if (data.getMicrophoneOn() != null) {
            type = DeviceTypeEnum.Microphone;
            enable = data.getMicrophoneOn();
        } else if (data.getMusicModeOn() != null) {
            type = DeviceTypeEnum.MusicMode;
            enable = data.getMusicModeOn();
        } else if (data.getHandUpOn() != null) {
            type = DeviceTypeEnum.HandUp;
            enable = data.getHandUpOn();
        } else if (data.getExamSongOn() != null) {
            type = DeviceTypeEnum.ExamSong;
            enable = data.getExamSongOn();
        } else {
            throw new ApiException(ErrorEnum.ERR_REQUEST_PARA_ERR);
        }
        SysUser authUser = sysUserFeignService.queryUserInfo();
        String userId = authUser.getId().toString();

        DeviceStateChangedMessage deviceResourceMessage = new DeviceStateChangedMessage(type.ordinal(), enable);
        if (type.equals(DeviceTypeEnum.Camera)) {
            roomMemberDao.updateCameraByRidAndUid(roomId, userId, enable);
        } else if (type.equals(DeviceTypeEnum.Microphone)) {
            roomMemberDao.updateMicByRidAndUid(roomId, userId, enable);
        } else if (type.equals(DeviceTypeEnum.HandUp)) {
            roomMemberDao.updateHandByRidAndUid(roomId, userId, enable);
        } else if (type.equals(DeviceTypeEnum.ExamSong)) {
            long scheduleId = Long.parseLong(roomId.substring(1));
            ExamSongDownloadData msg;
            String examJson = courseScheduleStudentPaymentDao.getExamJsonByCourseIdAndUserId(scheduleId, authUser.getId());
            if (StringUtils.isEmpty(examJson)) {
                throw new BizException("学员伴奏信息异常");
            } else {
                msg = JSON.parseObject(examJson, ExamSongDownloadData.class);
                msg.setEnable(enable);
            }
            courseScheduleStudentPaymentDao.adjustExamSong(scheduleId, authUser.getId(), JSON.toJSONString(msg));
        } else {
            roomMemberDao.updateMusicByRidAndUid(roomId, userId, enable);
        }
        Room room = roomDao.findByRid(roomId);
        //看了下日志,存在退出房间后短时间内请求该接口的情况
        if(Objects.nonNull(room)){
            deviceResourceMessage.setSoundVolume(room.getSoundVolume());
        }
        deviceResourceMessage.setUserId(userId);

        // 查询课程信息
        CourseSchedule courseSchedule = courseScheduleDao.get(Long.parseLong(roomId.substring(1)));
        if (Objects.isNull(courseSchedule)) {
            throw new BizException("无效的课程ID");
        }
        data.setServiceProvider(courseSchedule.getServiceProvider());

        // RTC服务对象
        RTCRoomPluginService pluginService = rtcRoomPluginContext.getPluginService(data.getServiceProvider());
        // 消息内容
        RTCRoomMessage.MessageContent messageContent = RTCRoomMessage.MessageContent
                .builder()
                .type(deviceResourceMessage.getType())
                .enable(deviceResourceMessage.isEnable())
                .sendUserInfo(getSendUser(authUser.getId().toString(), RoleTeacher))
                .build();

        // 腾讯云消息推送
        RTCRoomMessage message = RTCRoomMessage.builder()
                .objectName(RTCRoomMessage.DEVICE_MESSAGE)
                .fromUserId(userId)
                .toChatRoomId(roomId)
                .content(messageContent)
                .isPersisted(1)
                .isIncludeSender(0)
                .build();

        if (TencentCloudRTCPlugin.PLUGIN_NAME.matches(data.getServiceProvider())) {
            // 发送消息
            pluginService.sendChatRoomMessage(message);
        } else {
            // 融云消息推送
            imHelper.publishMessage(userId, roomId, deviceResourceMessage, 1);
        }

        log.info("syncDeviceState : {}, {}", roomId, enable);
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<RoomResult.MemberResult> getMembers(String roomId, String userId) throws Exception {

        CheckUtils.checkArgument(roomId != null, "roomId must't be null");
        List<RoomMember> roomMemberList = roomMemberDao.findByRid(roomId);

        // 参数过滤
        if (StringUtils.isNotBlank(userId)) {
            roomMemberList = roomMemberList.stream().filter(x -> x.getUid().equals(userId)).collect(Collectors.toList());
        }

        // 网络课用户信息
        if (CollectionUtils.isNotEmpty(roomMemberList)) {

            // 当前房间用户去重
            Collection<RoomMember> roomMembers = roomMemberList.stream()
                    .collect(Collectors.toMap(RoomMember::getUid, Function.identity(), (o, n) -> n)).values();

            // 重置房间用户信息
            roomMemberList = Lists.newArrayList(roomMembers);

            List<CourseScheduleStudentMusicScore> scheduleStudentMusicScores = courseScheduleStudentMusicScoreDao.queryByScoreIdAndCourseId(null, Long.parseLong(roomId.substring(1)), null, null, null);
            RoomResult roomResult = new RoomResult();
            Set<String> userIds = roomMemberList.stream().map(RoomMember::getUid).collect(Collectors.toSet());
            Map<Integer, String> midiMap = MapUtil.convertMybatisMap(courseScheduleStudentPaymentDao.queryMidiByUserIdsAndCourseId(userIds, roomId.substring(1)));
            Map<Integer, String> examSongMap = MapUtil.convertMybatisMap(courseScheduleStudentPaymentDao.queryExamSongByUserIdsAndCourseId(userIds, roomId.substring(1)));
            roomResult.setMembers(roomMemberList, midiMap, examSongMap, scheduleStudentMusicScores);

            // 全员静音开启状态
            CourseSchedule courseSchedule = courseScheduleDao.get(Long.parseLong(roomId.substring(1)));
            if (Optional.ofNullable(courseSchedule.getMuteAll()).orElse(false)
                    && TencentCloudRTCPlugin.PLUGIN_NAME.equals(courseSchedule.getServiceProvider())) {
                for (RoomResult.MemberResult item : roomResult.getMembers()) {
                    // 重置学生用户当前静音状态
                    if (RoleStudent.getValue() == item.getRole()) {
                        item.setMicrophone(false);
                    }
                }
            }

            return roomResult.getMembers();
        }
        return null;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean applySpeech(String roomId) throws Exception {
        CheckUtils.checkArgument(roomId != null, "roomId must't be null");
        CheckUtils.checkArgument(roomDao.existsByRid(roomId), "room not exist");
        SysUser authUser = sysUserFeignService.queryUserInfo();
        String userId = authUser.getId().toString();
        List<RoomMember> assistants = roomMemberDao.findByRidAndRole(roomId, RoleEnum.RoleAssistant.getValue());
        if (assistants.isEmpty()) {
            throw new ApiException(ErrorEnum.ERR_ASSISTANT_NOT_EXIST_IN_ROOM);
        }

        String ticket = IdentifierUtils.uuid();
        ScheduledTaskInfo scheduledTaskInfo = new ScheduledTaskInfo();
        scheduledTaskInfo.setTicket(ticket);
        scheduledTaskInfo.setRoomId(roomId);
        scheduledTaskInfo.setApplyUserId(userId);
        scheduledTaskInfo.setTargetUserId(assistants.get(0).getUid());
        scheduleManager.addTask(scheduledTaskInfo);

        log.info("applySpeech: task = {}", scheduledTaskInfo);

        ApplyForSpeechMessage msg = new ApplyForSpeechMessage();
        msg.setTicket(ticket);
        msg.setReqUserId(userId);
        IMApiResultInfo resultInfo = imHelper.publishMessage(userId, assistants.get(0).getUid(), roomId, msg);

        log.info("apply for speech: {}, task = {}", roomId, scheduledTaskInfo);
        if (resultInfo.isSuccess()) {
            return true;
        } else {
            throw new ApiException(ErrorEnum.ERR_MESSAGE_ERROR, resultInfo.getErrorMessage());
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean approveSpeech(String roomId, String ticket) throws Exception {
        CheckUtils.checkArgument(roomId != null, "roomId must't be null");
        CheckUtils.checkArgument(roomDao.existsByRid(roomId), "room not exist");
        SysUser authUser = sysUserFeignService.queryUserInfo();
        String userId = authUser.getId().toString();

        int count = roomMemberDao.countByRidAndExcludeRole(roomId, RoleEnum.RoleAudience.getValue());
        if (count == roomProperties.getMaxCount()) {
            log.error("approveSpeech error: roomId = {}, ticket={}", roomId, ticket);
            throw new ApiException(ErrorEnum.ERR_OVER_MAX_COUNT);
        }

        ScheduledTaskInfo taskInfo = scheduleManager.executeTask(ticket);
        log.info("approveSpeech: task = {}", taskInfo);
        roomMemberDao.updateRoleByRidAndUid(roomId, taskInfo.getApplyUserId(), RoleStudent.getValue());

        SpeechResultMessage msg = new SpeechResultMessage(SpeechResultMessage.Action_Approve);
        UserInfo userInfo = userDao.findByUid(taskInfo.getApplyUserId());
        msg.setOpUserId(userId);
        msg.setOpUserName(authUser.getUsername());
        msg.setReqUserId(taskInfo.getApplyUserId());
        if (userInfo != null) {
            msg.setReqUserName(userInfo.getName());
        }
        msg.setRole(RoleStudent.getValue());
        IMApiResultInfo resultInfo = imHelper.publishMessage(userId, taskInfo.getApplyUserId(), roomId, msg);
        if (!resultInfo.isSuccess()) {
            throw new ApiException(ErrorEnum.ERR_MESSAGE_ERROR, resultInfo.getErrorMessage());
        }

        RoleChangedMessage rcMsg = new RoleChangedMessage(userId);
        List<RoleChangedMessage.ChangedUser> changedUserList = new ArrayList<>();
        RoleChangedMessage.ChangedUser user = new RoleChangedMessage.ChangedUser(taskInfo.getApplyUserId(), RoleStudent.getValue());
        if (userInfo != null) {
            user.setUserName(userInfo.getName());
        }
        changedUserList.add(user);
        rcMsg.setUsers(changedUserList);
        imHelper.publishMessage(userId, roomId, rcMsg, 1);

        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean rejectSpeech(String roomId, String ticket) throws Exception {
        CheckUtils.checkArgument(roomId != null, "roomId must't be null");
        CheckUtils.checkArgument(roomDao.existsByRid(roomId), "room not exist");
        SysUser authUser = sysUserFeignService.queryUserInfo();
        String userId = authUser.getId().toString();
        ScheduledTaskInfo taskInfo = scheduleManager.executeTask(ticket);

        log.info("rejectSpeech: task = {}", taskInfo);
        SpeechResultMessage msg = new SpeechResultMessage(SpeechResultMessage.Action_Reject);
        msg.setOpUserId(userId);
        msg.setOpUserName(authUser.getUsername());
        msg.setRole(RoleStudent.getValue());
        IMApiResultInfo resultInfo = imHelper.publishMessage(userId, taskInfo.getApplyUserId(), roomId, msg);
        if (resultInfo.isSuccess()) {
            return true;
        } else {
            throw new ApiException(ErrorEnum.ERR_MESSAGE_ERROR, resultInfo.getErrorMessage());
        }
    }

    public void checkOverMax(String roomId, RoomMember targetUser, int targetRole) {
        if (RoleEnum.getEnumByValue(targetUser.getRole()).equals(RoleEnum.RoleAudience)) {
            int count = roomMemberDao.countByRidAndExcludeRole(roomId, RoleEnum.RoleAudience.getValue());
            if (count == roomProperties.getMaxCount()) {
                log.error("assign error: roomId = {}, userId = {}, role = {}", roomId, targetUser.getRid(), targetUser.getRole());
                throw new ApiException(ErrorEnum.ERR_OVER_MAX_COUNT);
            }
        } else if (targetRole > targetUser.getRole()) {
            throw new ApiException(ErrorEnum.ERR_DOWNGRADE_ROLE);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean transfer(String roomId, String userId) throws Exception {
        CheckUtils.checkArgument(roomId != null, "roomId must't be null");
        CheckUtils.checkArgument(userId != null, "userId must't be null");
        CheckUtils.checkArgument(!userId.equals(userId), "can't set self role");

        log.info("transfer: roomId = {}, userId = {}", roomId, userId);
        RoomMember roomMember = roomMemberDao.findByRidAndUid(roomId, userId);
        if (roomMember == null) {
            log.error("assistant transfer error: {} toUser = {}, opUser={}", roomId, userId, userId);
            throw new ApiException(ErrorEnum.ERR_USER_NOT_EXIST_IN_ROOM);
        }

        Room room = roomDao.findByRid(roomId);
        if (room == null) {
            log.error("assistant transfer error: {} toUser = {}, opUser={}", roomId, userId, userId);
            throw new ApiException(ErrorEnum.ERR_ROOM_NOT_EXIST);
        }

        if (isUserDisplay(room, userId) || isUserDisplay(room, userId)) {
            updateDisplay(roomId, userId, "", 1);
        } else {
            log.info("don't update display: room={}", room);
        }

        roomMemberDao.updateRoleByRidAndUid(roomId, userId, RoleStudent.getValue());
        roomMemberDao.updateRoleByRidAndUid(roomId, userId, RoleEnum.RoleAssistant.getValue());

        AssistantTransferMessage msg = new AssistantTransferMessage();
        msg.setOpUserId(userId);
        msg.setToUserId(userId);
        IMApiResultInfo resultInfo = imHelper.publishMessage(userId, roomId, msg, 1);
        if (resultInfo.isSuccess()) {
            return true;
        } else {
            throw new ApiException(ErrorEnum.ERR_MESSAGE_ERROR, resultInfo.getErrorMessage());
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean inviteUpgradeRole(String roomId, String targetUserId, int targetRole) throws Exception {
        CheckUtils.checkArgument(roomId != null, "roomId must't be null");
        CheckUtils.checkArgument(targetUserId != null, "userId must't be null");
        CheckUtils.checkArgument(roomMemberDao.existsByRidAndUid(roomId, targetUserId), "room member not exist");
        SysUser authUser = sysUserFeignService.queryUserInfo();
        String userId = authUser.getId().toString();
        log.info("inviteUpgradeRole roomId = {}, targetUserId = {}, targetRole = {}", roomId, targetUserId, targetRole);

        RoomMember targetUser = roomMemberDao.findByRidAndUid(roomId, targetUserId);
        if (targetUser == null) {
            throw new ApiException(ErrorEnum.ERR_USER_NOT_EXIST_IN_ROOM);
        }

        checkOverMax(roomId, targetUser, targetRole);

        String ticket = IdentifierUtils.uuid();

        UpgradeRoleTaskInfo taskInfo = new UpgradeRoleTaskInfo();
        taskInfo.setTicket(ticket);
        taskInfo.setRoomId(roomId);
        taskInfo.setApplyUserId(userId);
        taskInfo.setTargetUserId(targetUserId);
        taskInfo.setRole(RoleEnum.getEnumByValue(targetRole));
        scheduleManager.addTask(taskInfo);

        UpgradeRoleMessage msg = new UpgradeRoleMessage(ActionEnum.Invite.ordinal());
        msg.setTicket(ticket);
        msg.setOpUserId(userId);
        msg.setOpUserName(authUser.getUsername());
        msg.setRole(targetRole);
        IMApiResultInfo resultInfo = imHelper.publishMessage(userId, targetUserId, roomId, msg);
        if (resultInfo.isSuccess()) {
            return true;
        } else {
            throw new ApiException(ErrorEnum.ERR_MESSAGE_ERROR, resultInfo.getErrorMessage());
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean approveUpgradeRole(String roomId, String ticket) throws Exception {
        CheckUtils.checkArgument(roomId != null, "roomId must't be null");
        CheckUtils.checkArgument(ticket != null, "ticket must't be null");
        CheckUtils.checkArgument(roomDao.existsByRid(roomId), "room not exist");
        SysUser authUser = sysUserFeignService.queryUserInfo();
        String userId = authUser.getId().toString();
        UpgradeRoleTaskInfo taskInfo = (UpgradeRoleTaskInfo) scheduleManager.executeTask(ticket);
        log.info("approveUpgradeRole roomId = {}, task={}", roomId, taskInfo);

        RoomMember targetUser = roomMemberDao.findByRidAndUid(roomId, userId);
        if (targetUser == null) {
            throw new ApiException(ErrorEnum.ERR_USER_NOT_EXIST_IN_ROOM);
        }
        if (!taskInfo.getTargetUserId().equals(userId)) {
            throw new ApiException(ErrorEnum.ERR_APPLY_TICKET_INVALID);
        }

        checkOverMax(roomId, targetUser, taskInfo.getRole().getValue());
        roomMemberDao.updateRoleByRidAndUid(roomId, userId, taskInfo.getRole().getValue());

        UpgradeRoleMessage msg = new UpgradeRoleMessage(ActionEnum.Approve.ordinal());

        msg.setOpUserName(authUser.getUsername());
        msg.setOpUserId(userId);
        msg.setRole(taskInfo.getRole().getValue());
        IMApiResultInfo resultInfo = imHelper.publishMessage(userId, taskInfo.getApplyUserId(), roomId, msg);
        if (!resultInfo.isSuccess()) {
            throw new ApiException(ErrorEnum.ERR_MESSAGE_ERROR, resultInfo.getErrorMessage());
        }

        RoleChangedMessage rcMsg = new RoleChangedMessage(userId);
        List<RoleChangedMessage.ChangedUser> changedUserList = new ArrayList<>();
        RoleChangedMessage.ChangedUser user = new RoleChangedMessage.ChangedUser(userId, taskInfo.getRole().getValue());
        user.setUserName(authUser.getUsername());
        changedUserList.add(user);
        rcMsg.setUsers(changedUserList);
        imHelper.publishMessage(userId, roomId, rcMsg, 1);

        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean rejectUpgradeRole(String roomId, String ticket) throws Exception {
        CheckUtils.checkArgument(roomId != null, "roomId must't be null");
        CheckUtils.checkArgument(ticket != null, "ticket must't be null");
        CheckUtils.checkArgument(roomDao.existsByRid(roomId), "room not exist");
        SysUser authUser = sysUserFeignService.queryUserInfo();
        String userId = authUser.getId().toString();
        UpgradeRoleTaskInfo taskInfo = (UpgradeRoleTaskInfo) scheduleManager.executeTask(ticket);
        UpgradeRoleMessage msg = new UpgradeRoleMessage(ActionEnum.Reject.ordinal());
        msg.setOpUserName(authUser.getUsername());
        msg.setOpUserId(userId);
        msg.setRole(taskInfo.getRole().getValue());
        IMApiResultInfo resultInfo = imHelper.publishMessage(userId, taskInfo.getApplyUserId(), roomId, msg);
        if (resultInfo.isSuccess()) {
            return true;
        } else {
            throw new ApiException(ErrorEnum.ERR_MESSAGE_ERROR, resultInfo.getErrorMessage());
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean changeRole(String roomId, String targetUserId, int targetRole) throws Exception {

        CheckUtils.checkArgument(roomId != null, "roomId must't be null");
        CheckUtils.checkArgument(targetUserId != null, "userId must't be null");
        CheckUtils.checkArgument(roomDao.existsByRid(roomId), "room not exist");
        CheckUtils.checkArgument(RoleEnum.getEnumByValue(targetRole).equals(RoleTeacher), "only set to teacher");
        CheckUtils.checkArgument(roomMemberDao.existsByRidAndUid(roomId, targetUserId), "room member not exist");
        SysUser authUser = sysUserFeignService.queryUserInfo();
        String userId = authUser.getId().toString();

        RoomMember targetUser = roomMemberDao.findByRidAndUid(roomId, targetUserId);
        if (targetUser == null) {
            throw new ApiException(ErrorEnum.ERR_USER_NOT_EXIST_IN_ROOM);
        } else {
            if (!RoleEnum.getEnumByValue(targetUser.getRole()).equals(RoleStudent)) {
                log.error("change role error: targetUserId={}, targetRole = {}", targetUser, RoleEnum.getEnumByValue(targetRole));
                throw new ApiException(ErrorEnum.ERR_CHANGE_ROLE);
            }
        }

        log.info("changeRole: roomId={}, targetUserId={}", roomId, targetUserId);
        List<RoleChangedMessage.ChangedUser> changedUserList = new ArrayList<>();
        RoleChangedMessage msg = new RoleChangedMessage(userId);

        List<RoomMember> teachers = roomMemberDao.findByRidAndRole(roomId, RoleTeacher.getValue());
        if (!teachers.isEmpty()) {
            roomMemberDao.updateRoleByRidAndUid(roomId, teachers.get(0).getUid(), RoleStudent.getValue());
            RoleChangedMessage.ChangedUser user = new RoleChangedMessage.ChangedUser(teachers.get(0).getUid(), RoleStudent.getValue());
            UserInfo userInfo = userDao.findByUid(teachers.get(0).getUid());
            if (userInfo != null) {
                user.setUserName(userInfo.getName());
            }
            changedUserList.add(user);
        } else {
            log.info("change directly cause no teacher exist in room, roomId={}", roomId);
        }

        roomMemberDao.updateRoleByRidAndUid(roomId, targetUserId, targetRole);
        RoleChangedMessage.ChangedUser user = new RoleChangedMessage.ChangedUser(targetUserId, targetRole);
        UserInfo userInfo = userDao.findByUid(targetUserId);
        if (userInfo != null) {
            user.setUserName(userInfo.getName());
        }
        changedUserList.add(user);
        msg.setUsers(changedUserList);
        imHelper.publishMessage(userId, roomId, msg, 1);

        String display = "display://type=1?userId=" + targetUserId + "?uri=";

        RoomMember roomMember = roomMemberDao.findByRidAndUid(roomId, userId);

        String roomServiceProviter = getRoomServiceProvider(roomId);
        roomDao.updateDisplayByRid(roomId, display);
        if (TencentCloudRTCPlugin.PLUGIN_NAME.equals(roomServiceProviter)) {

            sendDisplayMessage(display, roomMember);
        } else {
            DisplayMessage displayMessage = new DisplayMessage(display);
            imHelper.publishMessage(userId, roomId, displayMessage, 1);
        }
        log.info("changeRole, display changed: roomId={}, {}, targetUserId={}", roomId, display, targetUserId);

        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean memberOnlineStatus(List<ReqMemberOnlineStatus> statusList, String nonce, String timestamp, String signature) throws Exception {
        String sign = imProperties.getSecret() + nonce + timestamp;
        String signSHA1 = CodeUtil.hexSHA1(sign);
        if (!signSHA1.equals(signature)) {
            log.info("memberOnlineStatus signature error");
            return true;
        }

        for (ReqMemberOnlineStatus status : statusList) {
            int s = Integer.parseInt(status.getStatus());
            String userId = status.getUserId();

            log.info("memberOnlineStatus, userId={}, status={}", userId, status);
            //1:offline 离线; 0: online 在线
            if (s == 1) {
                List<RoomMember> members = roomMemberDao.findByUid(userId);
                if (!members.isEmpty()) {
                    scheduleManager.userIMOffline(userId);
                }
            } else if (s == 0) {
                scheduleManager.userIMOnline(userId);
            }
        }

        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void userIMOfflineKick(String userId) {
        List<RoomMember> members = roomMemberDao.findByUid(userId);

        // 查询课程信息
        List<Long> collect = members.stream()
                .map(x -> x.getRid().substring(1))
                .map(Long::valueOf).distinct().collect(Collectors.toList());

        Map<String, String> serviceProviderMap = Maps.newHashMap();
        if (CollectionUtils.isNotEmpty(collect)) {
            serviceProviderMap = courseScheduleDao.findByCourseScheduleIds(collect).stream()
                    .collect(Collectors.toMap(x -> x.getId().toString(), CourseSchedule::getServiceProvider, (o, n) -> n));
        }

        for (RoomMember member : members) {
            int userRole = member.getRole();
            log.info("userIMOfflineKick: roomId={}, {}, role={}", member.getRid(), userId, RoleEnum.getEnumByValue(userRole));
            try {
                if (userRole == RoleTeacher.getValue() || userRole == RoleEnum.RoleAssistant.getValue()) {
                    Room room = roomDao.findByRid(member.getRid());
                    if (room == null) {
                        break;
                    }
                    if (isUserDisplay(room, member.getUid())) {
                        updateDisplay(member.getRid(), member.getUid(), "", 0);
                        log.info("memberOnlineStatus offline: roomId={}, {}", member.getRid(), member.getUid());
                    }
                }
                if (roomMemberDao.countByRid(member.getRid()) == 1) {
                    // 群组当前人数为1时,解散群组
                    this.dismissImGroup(member.getUid(), member.getRid(),
                            serviceProviderMap.getOrDefault(member.getRid().substring(1), "rongCloud"));

                    roomMemberDao.deleteUserByRidAndUid(member.getRid(), member.getUid());
                    roomDao.deleteByRid(member.getRid());
                    deleteWhiteboardByUser(member.getRid(), member.getUid());
                    log.info("dismiss the room: {},userId: {}", member.getRid(), userId);
                } else {
                    roomMemberDao.deleteUserByRidAndUid(member.getRid(), member.getUid());
                    MemberChangedMessage msg = new MemberChangedMessage(MemberChangedMessage.Action_Leave, member.getUid(), userRole);
                    msg.setUserName(member.getName());
                    Integer firstCourseId = Integer.parseInt(member.getRid().substring(1));
                    CourseSchedule courseSchedule = courseScheduleDao.get(firstCourseId.longValue());

                    if (TencentCloudRTCPlugin.PLUGIN_NAME.equals(courseSchedule.getServiceProvider())) {
                        sendJoinLeaveMessage(member, MemberChangedMessage.Action_Leave);
                    } else {
                        imHelper.publishMessage(member.getUid(), member.getRid(), msg);
                    }
                }
                userDao.deleteByUid(member.getUid());
            } catch (Exception e) {
                log.error("userIMOfflineKick error: userId={}", userId);
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void sendImPlayMidiMessage(PlayMidiMessageData playMidiMessageData) throws Exception {
        SysUser sysUser = sysUserFeignService.queryUserInfo();
        if (sysUser == null) {
            throw new BizException("用户信息获取失败");
        }
        String content = playMidiMessageData.getContent();
        String roomId = playMidiMessageData.getRoomId();
        CustomMessage customMessage = JSONObject.parseObject(content, CustomMessage.class);
        String userId = sysUser.getId().toString();

        RoomMember roomMember = roomMemberDao.findByRidAndUid(roomId, userId);

        // 获取RTC服务提供方
        RTCRoomPluginService pluginService = rtcRoomPluginContext.getPluginService(getRoomServiceProvider(roomId));
        if (TencentCloudRTCPlugin.PLUGIN_NAME.equals(pluginService.pluginName())) {

            // 腾讯云RTC服务
            RTCRoomMessage.MessageContent messageContent = RTCRoomMessage.MessageContent
                    .builder()
                    .enable(customMessage.getEnable())
                    .rate(customMessage.getRate())
                    .customType(customMessage.getCustomType())
                    .userId(customMessage.getUserId())
                    .playVolume(customMessage.getPlayVolume())
                    .sendUserInfo(getSendUser(roomMember.getUid(), RoleEnum.getEnumByValue(roomMember.getRole())))
                    .build();

            RTCRoomMessage roomMessage = RTCRoomMessage.builder()
                    .objectName(RTCRoomMessage.PLAY_MIDI_MESSAGE)
                    .content(messageContent)
                    .toChatRoomId(roomMember.getRid())
                    .fromUserId(roomMember.getUid())
                    .isIncludeSender(1)
                    .isPersisted(1)
                    .build();

            pluginService.sendChatRoomMessage(roomMessage);
        } else {
            MetronomeMessageMessage displayMessage = new MetronomeMessageMessage(customMessage);
            imHelper.publishMessage(userId, roomId, displayMessage, 1);
        }
        //记录节拍器消息
        courseScheduleStudentPaymentDao.adjustPlayMidi(Long.parseLong(roomId.substring(1)), playMidiMessageData.getUserId(), content);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void pushDownloadMusicScoreMsg(MusicScoreData musicScoreData) throws Exception {
        SysUser authUser = sysUserFeignService.queryUserInfo();
        String roomId = musicScoreData.getRoomId();
        Long courseScheduleId = Long.parseLong(roomId.substring(1));
        List<CourseScheduleStudentMusicScore> scheduleStudentMusicScores =
                courseScheduleStudentMusicScoreDao.queryByScoreIdAndCourseId(musicScoreData.getMusicScoreAccompanimentId(),
                        courseScheduleId, null, null, 0);
        SysMusicScoreAccompaniment accompaniment = sysMusicScoreAccompanimentDao.get(musicScoreData.getMusicScoreAccompanimentId());
        if (scheduleStudentMusicScores.size() == 0) {
            //第一次下载,生成数据
            List<CourseScheduleStudentPayment> courseScheduleStudentPayments = courseScheduleStudentPaymentDao.findByCourseSchedule(courseScheduleId);
            Set<Integer> studentIds = courseScheduleStudentPayments.stream().map(e -> e.getUserId()).collect(Collectors.toSet());
            studentIds.forEach(e -> {
                CourseScheduleStudentMusicScore musicScore = new CourseScheduleStudentMusicScore();
                musicScore.setMusicScoreAccompanimentId(accompaniment.getId());
                musicScore.setSpeed(accompaniment.getSpeed());
                musicScore.setCourseScheduleId(courseScheduleId);
                musicScore.setUserId(e);
                scheduleStudentMusicScores.add(musicScore);
            });
            CourseScheduleStudentMusicScore musicScore = new CourseScheduleStudentMusicScore();
            musicScore.setMusicScoreAccompanimentId(accompaniment.getId());
            musicScore.setSpeed(accompaniment.getSpeed());
            musicScore.setCourseScheduleId(courseScheduleId);
            musicScore.setUserId(authUser.getId());
            musicScore.setUserType(1);
            scheduleStudentMusicScores.add(musicScore);
            courseScheduleStudentMusicScoreDao.batchInsert(scheduleStudentMusicScores);
        }
        MusicScoreMessage musicScoreMessage = JSON.parseObject(JSON.toJSONString(accompaniment), MusicScoreMessage.class);
        // 发送消息
        String serviceProvider = getRoomServiceProvider(musicScoreData.getRoomId());
        if (TencentCloudRTCPlugin.PLUGIN_NAME.equals(serviceProvider)) {

            RTCRoomPluginService pluginService = rtcRoomPluginContext.getPluginService(serviceProvider);
            // 腾讯消息推送
            RTCRoomMessage.MessageContent messageContent = RTCRoomMessage.MessageContent
                    .builder()
                    .url(musicScoreMessage.getUrl())
                    .mp3Url(musicScoreMessage.getMp3Url())
                    .songId(String.valueOf(musicScoreMessage.getId()))
                    .speed(musicScoreMessage.getSpeed())
                    .sendUserInfo(RTCRoomMessage.MessageUser.builder()
                            .sendUserId(String.valueOf(authUser.getId()))
                            .sendUserName(authUser.getUsername())
                            .avatarUrl(authUser.getAvatar())
                            .build())
                    .build();

            RTCRoomMessage roomMessage = RTCRoomMessage.builder()
                    .objectName(RTCRoomMessage.MUSIC_SCORE_MESSAGE)
                    .content(messageContent)
                    .toChatRoomId(roomId)
                    .fromUserId(String.valueOf(authUser.getId()))
                    .isIncludeSender(1)
                    .isPersisted(1)
                    .build();

            // 发送消息
            pluginService.sendChatRoomMessage(roomMessage);

        } else {

            // 融云消息推送
            MusicScoreDownloadMessageMessage msg = new MusicScoreDownloadMessageMessage(musicScoreMessage);
            imHelper.publishMessage(authUser.getId().toString(), roomId, msg, 0);
        }

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void pushDownloadExamSongMsg(String roomId, Integer examSongId) throws Exception {
        SysUser authUser = sysUserFeignService.queryUserInfo();

        SysExamSong sysExamSong = sysExamSongDao.get(examSongId);
        if (sysExamSong == null) {
            throw new BizException("曲目信息不存在");
        }
        //学员曲目下载状态改为未下载
        ExamSongDownloadData json = new ExamSongDownloadData();
        json.setExamSongName(sysExamSong.getName());
        json.setUrl(sysExamSong.getUrl());
        json.setStatus(0);
        json.setExamSongId(examSongId);
        courseScheduleStudentPaymentDao.adjustExamSong(Long.parseLong(roomId.substring(1)), null, JSON.toJSONString(json));

        ExamSongMessage examSongMessage = new ExamSongMessage();
        examSongMessage.setExamSongName(sysExamSong.getName());
        examSongMessage.setUrl(sysExamSong.getUrl());
        examSongMessage.setExamSongId(examSongId);
        ExamSongDownloadMessageMessage msg = new ExamSongDownloadMessageMessage(examSongMessage);
        imHelper.publishMessage(authUser.getId().toString(), roomId, msg, 0);
    }

    @Override
    public List<RongyunBasicUserDto> queryNoJoinStu(String roomId) {
        return courseScheduleStudentPaymentDao.queryNoJoinStu(roomId, roomId.substring(1));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void adjustMusicScore(MusicScoreData musicScoreData) throws Exception {
        SysUser authUser = sysUserFeignService.queryUserInfo();
        Integer studentId = authUser.getId();
        String roomId = musicScoreData.getRoomId();
        Long scheduleId = Long.parseLong(roomId.substring(1));
        Integer accompanimentId = musicScoreData.getMusicScoreAccompanimentId();
        List<CourseScheduleStudentMusicScore> studentMusicScores = courseScheduleStudentMusicScoreDao.queryByScoreIdAndCourseId(accompanimentId, scheduleId, studentId, null, null);
        if (accompanimentId != null) {
            SysMusicScoreAccompaniment accompaniment = sysMusicScoreAccompanimentDao.get(accompanimentId);
            if (accompaniment == null) {
                throw new BizException("曲目信息不存在");
            }
            //修改下载状态
            if (studentMusicScores == null || studentMusicScores.size() == 0) {
                throw new BizException("学员不存在此下载曲目");
            }
            CourseScheduleStudentMusicScore studentMusicScore = studentMusicScores.get(0);
            studentMusicScore.setDownStatus(musicScoreData.getStatus());
            courseScheduleStudentMusicScoreDao.update(studentMusicScore);
        }
        //给老师发送学员曲目下载状态
        CourseSchedule courseSchedule = courseScheduleDao.get(scheduleId);
        MusicScoreDownloadStatusMessage statusMessage = new MusicScoreDownloadStatusMessage(studentId, studentMusicScores);
        imHelper.publishMessage(studentId.toString(), courseSchedule.getActualTeacherId().toString(), roomId, statusMessage);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void adjustExamSong(String roomId, Integer status, Integer examSongId) throws Exception {
        if (roomId == null || status == null || examSongId == null) {
            throw new BizException("参数校验失败");
        }
        SysUser authUser = sysUserFeignService.queryUserInfo();
        long scheduleId = Long.parseLong(roomId.substring(1));

        SysExamSong sysExamSong = sysExamSongDao.get(examSongId);
        if (sysExamSong == null) {
            throw new BizException("曲目信息不存在");
        }
        ExamSongDownloadData msg;
        String examJson = courseScheduleStudentPaymentDao.getExamJsonByCourseIdAndUserId(scheduleId, authUser.getId());
        if (StringUtils.isEmpty(examJson)) {
            msg = new ExamSongDownloadData();
            msg.setExamSongName(sysExamSong.getName());
            msg.setUrl(sysExamSong.getUrl());
            msg.setStatus(status);
            msg.setExamSongId(examSongId);
        } else {
            msg = JSON.parseObject(examJson, ExamSongDownloadData.class);
            msg.setStatus(status);
        }
        courseScheduleStudentPaymentDao.adjustExamSong(scheduleId, authUser.getId(), JSON.toJSONString(msg));

        //给老师发送学员曲目下载状态
        CourseSchedule courseSchedule = courseScheduleDao.get(scheduleId);
        ExamSongDownloadStatusMessage deviceResourceMessage = new ExamSongDownloadStatusMessage(status, authUser.getId(), examSongId);
        imHelper.publishMessage(authUser.getId().toString(), courseSchedule.getActualTeacherId().toString(), roomId, deviceResourceMessage);
    }

    public void updateDisplay(String roomId, String senderId, String display, Integer isIncludeSender) throws Exception {
        roomDao.updateDisplayByRid(roomId, display);

        RoomMember roomMember = roomMemberDao.findByRidAndUid(roomId, senderId);

        String roomServiceProvider = getRoomServiceProvider(roomId);
        if (TencentCloudRTCPlugin.PLUGIN_NAME.equals(roomServiceProvider)) {

            sendDisplayMessage(display, roomMember);
        } else {
            DisplayMessage displayMessage = new DisplayMessage(display);
            imHelper.publishMessage(senderId, roomId, displayMessage, isIncludeSender);
        }
    }

    public boolean isUserDisplay(Room room, String userId) {
        boolean result = false;
        if (!room.getDisplay().isEmpty() && room.getDisplay().contains("userId=" + userId)) {
            if (room.getDisplay().contains("type=0") || room.getDisplay().contains("type=1") || room.getDisplay().contains("type=3")) {
                result = true;
            }
        }
        return result;
    }
}