ソースを参照

Merge remote-tracking branch 'origin/master_saas' into master_saas

zouxuan 2 年 前
コミット
00afa60394
20 ファイル変更342 行追加14 行削除
  1. 4 2
      mec-auth/mec-auth-server/src/main/java/com/ym/mec/auth/core/handler/BaseAuthenticationSuccessEventHandler.java
  2. 2 1
      mec-auth/mec-auth-server/src/main/java/com/ym/mec/auth/core/provider/PhoneAuthenticationProvider.java
  3. 8 1
      mec-auth/mec-auth-server/src/main/java/com/ym/mec/auth/core/service/CustomAuthenticationKeyGenerator.java
  4. 14 1
      mec-auth/mec-auth-server/src/main/java/com/ym/mec/auth/web/controller/UserController.java
  5. 6 0
      mec-biz/pom.xml
  6. 5 0
      mec-biz/src/main/java/com/ym/mec/biz/service/ImLiveBroadcastRoomService.java
  7. 76 4
      mec-biz/src/main/java/com/ym/mec/biz/service/impl/ImLiveBroadcastRoomServiceImpl.java
  8. 9 0
      mec-biz/src/main/resources/config/mybatis/ImLiveBroadcastRoomMapper.xml
  9. 3 0
      mec-biz/src/main/resources/config/mybatis/ImLiveBroadcastRoomMemberMapper.xml
  10. 6 0
      mec-client-api/src/main/java/com/ym/mec/task/TaskRemoteService.java
  11. 5 0
      mec-client-api/src/main/java/com/ym/mec/task/fallback/TaskRemoteServiceFallback.java
  12. 12 2
      mec-im/src/main/java/com/ym/controller/UserController.java
  13. 115 1
      mec-im/src/main/java/com/ym/service/Impl/LiveRoomServiceImpl.java
  14. 19 0
      mec-im/src/main/java/com/ym/service/LiveRoomService.java
  15. 24 0
      mec-task/src/main/java/com/ym/mec/task/jobs/DestroyLiveRoomTask.java
  16. 10 1
      mec-thirdparty/src/main/java/com/ym/mec/thirdparty/storage/StoragePluginContext.java
  17. 8 1
      mec-web/src/main/java/com/ym/mec/web/controller/ImLiveBroadcastRoomController.java
  18. 1 0
      mec-web/src/main/java/com/ym/mec/web/controller/ImLiveBroadcastRoomMemberController.java
  19. 8 0
      mec-web/src/main/java/com/ym/mec/web/controller/TaskController.java
  20. 7 0
      pom.xml

+ 4 - 2
mec-auth/mec-auth-server/src/main/java/com/ym/mec/auth/core/handler/BaseAuthenticationSuccessEventHandler.java

@@ -118,7 +118,7 @@ public class BaseAuthenticationSuccessEventHandler extends SavedRequestAwareAuth
 		sysUserLoginLogService.insert(sysUserLoginLog);
 		
 		try {
-			String clientId = request.getParameter("clientId");
+			String clientId = request.getParameter("clientId").replace("QR_", "");
 			String clientSecret = request.getParameter("clientSecret");
 			if (clientId == null || clientSecret == null) {
 				throw new UnapprovedClientAuthenticationException("请求头中client信息为空");
@@ -129,7 +129,9 @@ public class BaseAuthenticationSuccessEventHandler extends SavedRequestAwareAuth
 			headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
 
 			ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
-			TokenRequest tokenRequest = new TokenRequest(MapUtils.EMPTY_MAP, clientId, clientDetails.getScope(), "password");
+			Map<String, String> requestParameters = new HashMap<>();
+			requestParameters.put("client_type", request.getParameter("clientType"));
+			TokenRequest tokenRequest = new TokenRequest(requestParameters, clientId, clientDetails.getScope(), "password");
 			OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
 
 			OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);

+ 2 - 1
mec-auth/mec-auth-server/src/main/java/com/ym/mec/auth/core/provider/PhoneAuthenticationProvider.java

@@ -74,6 +74,7 @@ public class PhoneAuthenticationProvider extends AbstractAuthenticationProvider
 			if (data == null) {
 				throw new LockedException("用户不存在");
 			} else {
+				redisCache.delete(loginEntity.getPhone());
 				QRLoginDto loginDto = (QRLoginDto) data;
 				if (loginDto.getPrivateKey().equals(loginEntity.getSmsCode())) {
 					userInfo = loginDto.getUserInfo();
@@ -110,7 +111,7 @@ public class PhoneAuthenticationProvider extends AbstractAuthenticationProvider
 				sysUserDeviceService.bindDevice(clientId, user.getId(), deviceNum, userInfo.getSysUser().getTenantId());
 			}
 
-			if (clientId.startsWith("QR_" )) {
+			if (clientId.startsWith("QR_")) {
 			} else  if (!userInfo.getSysUser().getUserType().contains(clientId)) {
 				if (isRegister == false || StringUtils.equals("SYSTEM", clientId)) {
 					throw new LockedException("用户不存在");

+ 8 - 1
mec-auth/mec-auth-server/src/main/java/com/ym/mec/auth/core/service/CustomAuthenticationKeyGenerator.java

@@ -13,6 +13,7 @@ import org.springframework.security.oauth2.provider.token.DefaultAuthenticationK
 public class CustomAuthenticationKeyGenerator extends DefaultAuthenticationKeyGenerator {
 	
 	private static final String CLIENT_ID = "client_id";
+	private static final String CLIENT_TYPE = "client_type";
 
 	private static final String SCOPE = "scope";
 
@@ -25,10 +26,16 @@ public class CustomAuthenticationKeyGenerator extends DefaultAuthenticationKeyGe
 		if (!authentication.isClientOnly()) {
 			values.put(USERNAME, StringUtils.substringAfter(authentication.getName(), ":"));
 		}
-		values.put(CLIENT_ID, authorizationRequest.getClientId());
+		String clientId = authorizationRequest.getClientId();
+		clientId = clientId.replace("QR_", "");
+		values.put(CLIENT_ID, clientId);
 		if (authorizationRequest.getScope() != null) {
 			values.put(SCOPE, OAuth2Utils.formatParameterList(new TreeSet<String>(authorizationRequest.getScope())));
 		}
+		String deviceId = authorizationRequest.getRequestParameters().get(CLIENT_TYPE);
+		if (StringUtils.isNotBlank(deviceId)) {
+			values.put(CLIENT_TYPE, deviceId);
+		}
 		return generateKey(values);
 	}
 

+ 14 - 1
mec-auth/mec-auth-server/src/main/java/com/ym/mec/auth/web/controller/UserController.java

@@ -48,6 +48,7 @@ import java.util.Base64;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.UUID;
 import java.util.concurrent.TimeUnit;
@@ -452,8 +453,14 @@ public class UserController extends BaseController {
 			qrLoginDto.setExpireFlag(true);
 			return succeed(qrLoginDto);
 		}
+
+		QRLoginDto dto = (QRLoginDto) data;
+		if (!sysUser.getUserType().contains((dto).getClientId().replace("QR_", "").toUpperCase(Locale.ROOT))) {
+			throw new BizException("登录失败");
+		}
 		redisCache.put(code,data,5*60);
-		return succeed( (QRLoginDto) data);
+		dto.setUserInfo(null);
+		return succeed(dto);
 	}
 
 	@GetMapping(value = "/doQrLogin")
@@ -477,6 +484,12 @@ public class UserController extends BaseController {
 
 		SysUserInfo userInfo = sysUserService.queryUserInfoByPhone(sysUser.getPhone());
 		QRLoginDto dto = (QRLoginDto) data;
+
+
+		if (!sysUser.getUserType().contains((dto).getClientId().replace("QR_", "").toUpperCase(Locale.ROOT))) {
+			throw new BizException("登录失败");
+		}
+
 		dto.setUserInfo(userInfo);
 
 

+ 6 - 0
mec-biz/pom.xml

@@ -58,5 +58,11 @@
 			<version>1.0.0</version>
 		</dependency>
 
+		<!--修复依赖冲突-->
+		<dependency>
+			<groupId>org.jetbrains.kotlin</groupId>
+			<artifactId>kotlin-stdlib</artifactId>
+		</dependency>
+
     </dependencies>
 </project>

+ 5 - 0
mec-biz/src/main/java/com/ym/mec/biz/service/ImLiveBroadcastRoomService.java

@@ -151,5 +151,10 @@ public interface ImLiveBroadcastRoomService extends IService<ImLiveBroadcastRoom
      * 新成员入群之后回调
      */
     void callbackAfterNewMemberJoin(TencentData.CallbackAfterNewMemberJoin callbackAfterNewMemberJoin);
+
+    /**
+     * 直播间销毁定时任务
+     */
+    void destroyLiveRoom();
 }
 

+ 76 - 4
mec-biz/src/main/java/com/ym/mec/biz/service/impl/ImLiveBroadcastRoomServiceImpl.java

@@ -16,6 +16,7 @@ import com.microsvc.toolkit.middleware.live.message.LiveRoomMessage;
 import com.microsvc.toolkit.middleware.live.message.LiveRoomUser;
 import com.microsvc.toolkit.middleware.live.message.RTCRequest;
 import com.microsvc.toolkit.middleware.live.message.RTCRoom;
+import com.microsvc.toolkit.middleware.live.message.TencentWrapper;
 import com.ym.mec.auth.api.client.SysUserFeignService;
 import com.ym.mec.auth.api.entity.SysUser;
 import com.ym.mec.auth.api.enums.SysUserType;
@@ -31,6 +32,7 @@ import com.ym.mec.biz.dal.entity.ImLiveBroadcastRoom;
 import com.ym.mec.biz.dal.entity.ImLiveBroadcastRoomData;
 import com.ym.mec.biz.dal.entity.ImLiveBroadcastRoomMember;
 import com.ym.mec.biz.dal.entity.ImLiveRoomBlack;
+import com.ym.mec.biz.dal.entity.ImLiveRoomVideo;
 import com.ym.mec.biz.dal.enums.MessageTypeEnum;
 import com.ym.mec.biz.dal.page.LiveRoomGoodsOrderQueryInfo;
 import com.ym.mec.biz.dal.vo.*;
@@ -107,7 +109,8 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
     private ImLiveRoomReservationService imLiveRoomReservationService;
     @Autowired
     private ImLiveRoomBlackService imLiveRoomBlackService;
-
+    @Autowired
+    private ImLiveRoomVideoService imLiveRoomVideoService;
     @Autowired
     private LivePluginContext livePluginContext;
 
@@ -285,7 +288,7 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
             param.put("clientType", SysUserType.TEACHER.getCode());
         }
         Page<ImLiveBroadcastRoomVo> pageInfo = PageUtil.concatTimePage(param, "startTime", "endTime");
-        pageInfo.setDesc("a.created_time_");
+        // pageInfo.setDesc("a.created_time_");
         param.put("tenantId", TenantContextHolder.getTenantId());
         IPage<ImLiveBroadcastRoomVo> page = baseMapper.queryPage(pageInfo, param);
 
@@ -658,8 +661,25 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
             LivePluginService pluginService = livePluginContext.getPluginService(room.getServiceProvider());
             pluginService.sendChatRoomMessage(message);
             log.info("roomDestroy>>>> FORCED_OFFLINE {}", JSONObject.toJSONString(message));
+
             //销毁直播间
             pluginService.chatRoomDestroy(roomUid);
+
+            // 录制任务Id
+            if (room.getServiceProvider().equals(TencentCloudLivePlugin.PLUGIN_NAME)) {
+
+                List<String> collect = imLiveRoomVideoService.lambdaQuery()
+                        .eq(ImLiveRoomVideo::getRoomUid, roomUid).list().stream()
+                        .map(ImLiveRoomVideo::getRecordId)
+                        .filter(StringUtils::isNotEmpty)
+                        .distinct().collect(Collectors.toList());
+
+                for (String taskId : collect) {
+                    // 删除录制任务
+                    pluginService.rtcRoomRecordStop(taskId);
+                }
+            }
+
 //            imFeignService.destroyLiveRoom(roomUid);
             log.info("roomDestroy>>>> destroyLiveRoom {}", JSONObject.toJSONString(message));
         } catch (Exception e) {
@@ -667,6 +687,10 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
         }
     }
 
+    private String getStreamId(String roomUid, Integer speakerId) {
+        return roomUid + "_" + speakerId;
+    }
+
     //获取该直播间所有数据写入数据库-并清理缓存
     private void insertAndCleanLiveData(String roomUid, Integer speakerId) {
         log.info("insertAndCleanLiveData >>>> roomUid : {}", roomUid);
@@ -1311,7 +1335,7 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
                 RTCRoom.RecordResp recordResp = pluginService.rtcRoomRecordStart(RTCRequest.RecordStart.builder()
                         .startTime(dateTime.plusMillis(5).getMillis() / 1000)
                         .endTime(dateTime.plusDays(1).getMillis() / 1000)
-                        .steamName(MessageFormat.format("{0}_{1}", imLiveBroadcastRoomVo.getRoomUid(), String.valueOf(imLiveBroadcastRoomVo.getSpeakerId())))
+                        .streamName(MessageFormat.format("{0}_{1}", imLiveBroadcastRoomVo.getRoomUid(), String.valueOf(imLiveBroadcastRoomVo.getSpeakerId())))
                         .sessionId(rtcRoom.getSessionId())
                         .config(RTCRequest.RecordConfig.builder()
                                 .videoResolution(videoResolution)
@@ -1496,6 +1520,22 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
             // 创建直播间IM群
             pluginService.chatRoomCreate(room.getRoomUid(), room.getRoomTitle(),sysUser.getId().toString());
 
+            // 腾讯云直播,提前生成录制规则
+            if (room.getServiceProvider().equals(TencentCloudLivePlugin.PLUGIN_NAME)) {
+
+                DateTime now = DateTime.now();
+
+                RTCRequest.RecordStart recordStart = RTCRequest.RecordStart.builder()
+                        .streamName(MessageFormat.format("{0}_{1}", room.getRoomUid(), room.getSpeakerId().toString()))
+                        .extra("")
+                        .startTime(now.toDateTime().getMillis())
+                        .endTime(now.plusDays(1).toDateTime().getMillis())
+                        .build();
+
+                // 生成录制任务
+                pluginService.rtcRoomRecordStart(recordStart);
+            }
+
             // imFeignService.createLiveRoom(room.getRoomUid(), room.getRoomTitle());
             //推送预约直播间消息
             imLiveRoomReservationService.push(room);
@@ -1714,7 +1754,7 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
         sysMessageService.batchSendImGroupMessage(MessageTypeEnum.IM_SHARE_LIVE_URL, sysUser.getId().toString(), "liveRoom?" + roomUid, groupIds.split(","), null,
                 imLiveBroadcastRoomVo.getTenantName(), imLiveBroadcastRoomVo.getRoomTitle(), imLiveBroadcastRoomVo.getSpeakerName(),
                 DateUtil.format(imLiveBroadcastRoomVo.getLiveStartTime(), DateUtil.CHINESE_DATA_FORMAT_1),
-                imLiveBroadcastRoomVo.getLiveRemark(), HttpUtil.getSortUrl(baseApiUrl + "/#/liveClassTransfer?roomUid=" + roomUid));
+                imLiveBroadcastRoomVo.getLiveRemark(), baseApiUrl + "/#/liveClassTransfer?roomUid=" + roomUid);
     }
 
     /**
@@ -1987,6 +2027,38 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
         liveBroadcastRoomMemberDao.updateLiveRoomStatus(userIds, callbackAfterNewMemberJoin.getGroupId(),liveRoomStatus);
     }
 
+    @Override
+    public void destroyLiveRoom() {
+        //查询状态是 未开始和已开始 的直播间
+        List<ImLiveBroadcastRoom> list = this.list(Wrappers.<ImLiveBroadcastRoom>lambdaQuery()
+                                                           .in(ImLiveBroadcastRoom::getLiveState,  1)
+                                                           .eq(ImLiveBroadcastRoom::getRoomState, 0));
+        if (CollectionUtils.isEmpty(list)) {
+            return;
+        }
+        for (ImLiveBroadcastRoom imLiveBroadcastRoom : list) {
+
+            LivePluginService pluginService = livePluginContext.getPluginService(
+                imLiveBroadcastRoom.getServiceProvider());
+            try {
+                TencentWrapper.LiveStreamState liveStreamState = pluginService
+                    .liveStreamState(getStreamId(imLiveBroadcastRoom.getRoomUid(),imLiveBroadcastRoom.getSpeakerId()));
+                if (liveStreamState == null) {
+                    log.error("查询直播间流失败,返回结果为空");
+                    continue;
+                }
+                log.info("查询直播间流状态:{},roomUid:{}", JSON.toJSONString(liveStreamState), imLiveBroadcastRoom.getRoomUid());
+                if (!"active".equals(liveStreamState.getStreamState())) {
+                    roomDestroy(imLiveBroadcastRoom.getRoomUid());
+                }
+            } catch (Exception e) {
+
+                log.error("查询直播间流失败", e);
+            }
+
+        }
+    }
+
 
     /**
      * 查询直播间所有用户信息

+ 9 - 0
mec-biz/src/main/resources/config/mybatis/ImLiveBroadcastRoomMapper.xml

@@ -104,6 +104,15 @@
                 and  a.client_type_ = #{param.clientType}
             </if>
         </where>
+        order by
+        <choose>
+            <when test="param.sort == null or param.sort == ''">
+                a.created_time_ desc
+            </when>
+            <when test="param.sort == 1">
+                field(a.live_state_, 1, 0, 2) asc, a.live_start_time_ desc
+            </when>
+        </choose>
     </select>
 
     <select id="queryBaseUserInfo" resultType="com.ym.mec.biz.dal.vo.BaseRoomUserVo">

+ 3 - 0
mec-biz/src/main/resources/config/mybatis/ImLiveBroadcastRoomMemberMapper.xml

@@ -53,6 +53,9 @@
             OR su.username_ LIKE CONCAT('%', #{param.search},'%')
             )
         </if>
+        <if test="param.onlineStatus != null">
+            AND a.online_status_ = #{param.onlineStatus}
+        </if>
         group by a.user_id_
     </select>
 

+ 6 - 0
mec-client-api/src/main/java/com/ym/mec/task/TaskRemoteService.java

@@ -264,6 +264,12 @@ public interface TaskRemoteService {
     void destroyExpiredLiveRoom();
 
     /**
+     * 每日凌晨执行定时任务
+     */
+    @GetMapping("task/destroyLiveRoom")
+    void destroyLiveRoom();
+
+    /**
      * 学员小课统计
      */
     @GetMapping("task/studentSmallClassStatistics")

+ 5 - 0
mec-client-api/src/main/java/com/ym/mec/task/fallback/TaskRemoteServiceFallback.java

@@ -306,6 +306,11 @@ public class TaskRemoteServiceFallback implements TaskRemoteService {
     }
 
     @Override
+    public void destroyLiveRoom() {
+        logger.error("销毁直播间失败");
+    }
+
+    @Override
     public void studentSmallClassStatistics() {
         logger.error("学员小课统计失败");
     }

+ 12 - 2
mec-im/src/main/java/com/ym/controller/UserController.java

@@ -8,6 +8,7 @@ import com.ym.mec.biz.dal.dto.TencentImCallbackResult;
 import com.ym.mec.biz.dal.enums.ETencentImCallbackCommand;
 import com.ym.mec.biz.service.ImLiveBroadcastRoomService;
 import com.ym.mec.common.entity.ImUserState;
+import com.ym.service.LiveRoomService;
 import com.ym.service.UserService;
 import io.rong.models.user.UserModel;
 import io.swagger.annotations.ApiOperation;
@@ -27,6 +28,8 @@ public class UserController {
     private UserService userService;
     @Autowired
     private ImLiveBroadcastRoomService imLiveBroadcastRoomService;
+    @Autowired
+    private LiveRoomService liveRoomService;
 
     @RequestMapping(value = "/register", method = RequestMethod.POST)
     public Object register(@RequestBody UserModel userModel) throws Exception {
@@ -103,12 +106,16 @@ public class UserController {
 
         TencentData.CallbackStreamStateEvent event = TencentData.CallbackStreamStateEvent.from(body);
 
+        // 断流事件通知
         if (event.getEventType() == 0) {
-            // 断流事件通知
+            // 自动关闭录制
+            // liveRoomService.stopTencentLiveVideoRecord(event.getStreamId());
         }
 
+        // 推流事件通知
         if (event.getEventType() == 1) {
-            // 推流事件通知
+            // 自动开启录制
+            // liveRoomService.startTencentLiveVideoRecord(event.getStreamId());
         }
 
         return TencentData.StreamEventCallbackResult.builder().code(0).build();
@@ -124,6 +131,9 @@ public class UserController {
 
         log.info("taskId={}, url={}", event.getTaskId(), event.getVideoUrl());
 
+        // 生成直播录制信息
+        liveRoomService.createLiveRoomVideoRecord(event);
+
         return TencentData.StreamEventCallbackResult.builder().code(0).build();
     }
 

+ 115 - 1
mec-im/src/main/java/com/ym/service/Impl/LiveRoomServiceImpl.java

@@ -1,15 +1,20 @@
 package com.ym.service.Impl;
 
+import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.microsvc.toolkit.middleware.live.LivePluginContext;
+import com.microsvc.toolkit.middleware.live.impl.TencentCloudLivePlugin;
+import com.microsvc.toolkit.middleware.live.message.RTCRequest;
+import com.microsvc.toolkit.middleware.live.message.RTCRoom;
 import com.ym.http.HttpHelper;
+import com.ym.mec.biz.dal.dto.TencentData;
 import com.ym.mec.biz.dal.entity.ImLiveRoomVideo;
 import com.ym.mec.biz.service.ImLiveRoomVideoService;
 import com.ym.mec.common.entity.ImRoomMessage;
 import com.ym.mec.common.exception.BizException;
 import com.ym.mec.im.IMHelper;
 import com.ym.mec.thirdparty.storage.StoragePluginContext;
-import com.ym.mec.thirdparty.storage.provider.KS3StoragePlugin;
 import com.ym.pojo.IMApiResultInfo;
 import com.ym.pojo.IMUserOnlineInfo;
 import com.ym.pojo.RecordConfig;
@@ -17,6 +22,7 @@ import com.ym.pojo.RecordNotify;
 import com.ym.service.LiveRoomService;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang.StringUtils;
+import org.joda.time.DateTime;
 import org.redisson.api.RBucket;
 import org.redisson.api.RedissonClient;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -44,6 +50,8 @@ public class LiveRoomServiceImpl implements LiveRoomService {
     private ImLiveRoomVideoService imLiveRoomVideoService;
     @Autowired
     private StoragePluginContext storagePluginContext;
+    @Autowired
+    private LivePluginContext livePluginContext;
 
     /**
      * 创建房间-聊天室
@@ -319,4 +327,110 @@ public class LiveRoomServiceImpl implements LiveRoomService {
         return sessionId;
     }
 
+
+    /**
+     * 创建tencent云直播录制记录
+     *
+     * @param streamId 推流Id
+     */
+    @Override
+    public void startTencentLiveVideoRecord(String streamId) {
+
+        DateTime now = DateTime.now();
+        // 创建直播录制
+        RTCRequest.RecordStart recordStart = RTCRequest.RecordStart.builder()
+                .streamName(streamId)
+                .extra("")
+                .startTime(now.getMillis())
+                .endTime(now.plusDays(1).getMillis())
+                .build();
+        // 创建录制任务失败,重试3次后,发送IM消息通知主播老师
+        int maxRetry = 0;
+        // 录制任务ID
+        String taskId = "";
+        do {
+            try {
+
+                // 创建录制任务
+                RTCRoom.RecordResp resp = livePluginContext.getPluginService(TencentCloudLivePlugin.PLUGIN_NAME)
+                        .rtcRoomRecordStart(recordStart);
+
+                taskId = resp.getRecordId();
+                if (StringUtils.isNotBlank(taskId)) {
+                    maxRetry = 3;
+                }
+                log.info("createTencentLiveRoomVideoRecord resp={}", JSON.toJSONString(resp));
+            } catch (Exception e) {
+                log.error("创建直播录制失败", e);
+            }
+        } while (maxRetry++ < 3);
+
+        log.info("startTencentLiveVideoRecord taskId={}", taskId);
+        // 生成录制记录
+        // 直播间ROOM_UID
+        String roomId = streamId.split("_")[0];
+
+        ImLiveRoomVideo video = imLiveRoomVideoService.getOne(new QueryWrapper<ImLiveRoomVideo>()
+                .eq("room_uid_", roomId)
+                .eq("record_id_", taskId)
+                .eq("type", 0));
+        if (Objects.nonNull(video)) {
+            return;
+        }
+        imLiveRoomVideoService.save(initImLiveRoomVideo(roomId, taskId, new Date()));
+    }
+
+    /**
+     * 关闭tencent云直播录制记录
+     *
+     * @param streamId 推流Id
+     */
+    @Override
+    public void stopTencentLiveVideoRecord(String streamId) {
+
+        // 关闭录制任务失败,重试3次后,发送IM消息通知主播老师
+        int maxRetry = 0;
+        do {
+            try {
+
+                // 关闭直播录制
+                RTCRoom.RecordResp ret = livePluginContext.getPluginService(TencentCloudLivePlugin.PLUGIN_NAME)
+                        .rtcRoomRecordStop(streamId);
+
+                if (StringUtils.isNotBlank(ret.getRequestId())) {
+                    // 重试最大次数
+                    maxRetry = 3;
+                }
+            } catch (Exception e) {
+                log.error("关闭直播录制失败", e);
+            }
+        } while (maxRetry++ < 3);
+
+    }
+
+    /**
+     * 生成直播录制信息
+     *
+     * @param event TencentData.CallbackSteamRecordEvent
+     */
+    @Override
+    public void createLiveRoomVideoRecord(TencentData.CallbackSteamRecordEvent event) {
+
+        // 直播间ROOM_UID
+        String roomId = event.getStreamId().split("_")[0];
+
+        //云端录制文件地址
+        String fileUrl = storagePluginContext.getPublicUrl(event.getVideoUrl(),"live-rewind");
+
+        // 录制开始时间
+        long startTime =  (event.getEndTime() - event.getDuration()) * 1000L;
+        //保存切片
+        ImLiveRoomVideo imLiveRoomVideo = initImLiveRoomVideo(roomId, event.getTaskId(), DateTime.now().toDate());
+        imLiveRoomVideo.setStartTime(new DateTime(startTime).toDate());
+        imLiveRoomVideo.setEndTime(new DateTime(event.getEndTime() * 1000L).toDate());
+        imLiveRoomVideo.setUrl(fileUrl);
+        imLiveRoomVideo.setType(2);
+
+        imLiveRoomVideoService.save(imLiveRoomVideo);
+    }
 }

+ 19 - 0
mec-im/src/main/java/com/ym/service/LiveRoomService.java

@@ -1,5 +1,6 @@
 package com.ym.service;
 
+import com.ym.mec.biz.dal.dto.TencentData;
 import com.ym.mec.common.entity.ImRoomMessage;
 import com.ym.pojo.IMApiResultInfo;
 import com.ym.pojo.RecordNotify;
@@ -62,4 +63,22 @@ public interface LiveRoomService {
      * @param userId  用户id
      */
     boolean removeUserUnableSpeak(String roomUid, String userId);
+
+    /**
+     * 开始tencent云直播录制记录
+     * @param streamId 推流Id
+     */
+    void startTencentLiveVideoRecord(String streamId);
+
+    /**
+     * 关闭tencent云直播录制记录
+     * @param streamId 推流Id
+     */
+    void stopTencentLiveVideoRecord(String streamId);
+
+    /**
+     * 生成直播录制信息
+     * @param event TencentData.CallbackSteamRecordEvent
+     */
+    void createLiveRoomVideoRecord(TencentData.CallbackSteamRecordEvent event);
 }

+ 24 - 0
mec-task/src/main/java/com/ym/mec/task/jobs/DestroyLiveRoomTask.java

@@ -0,0 +1,24 @@
+package com.ym.mec.task.jobs;
+
+import com.ym.mec.task.TaskRemoteService;
+import com.ym.mec.task.core.BaseTask;
+import com.ym.mec.task.core.TaskException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author hgw
+ * Created by 2022-03-04
+ */
+@Service
+public class DestroyLiveRoomTask extends BaseTask {
+
+    @Autowired
+    private TaskRemoteService taskRemoteService;
+
+    @Override
+    public void execute() throws TaskException {
+        taskRemoteService.destroyLiveRoom();
+    }
+
+}

+ 10 - 1
mec-thirdparty/src/main/java/com/ym/mec/thirdparty/storage/StoragePluginContext.java

@@ -45,7 +45,16 @@ public class StoragePluginContext {
 
 	public String getPublicUrl(String fileName,String bucketName){
 		try {
-			String substring = fileName.substring(0, fileName.lastIndexOf("?"));
+
+			// 文件地址
+			String substring = fileName;
+
+			// 访问后缀参数
+			int indexOf = fileName.lastIndexOf("?");
+			if (indexOf > -1) {
+				substring = fileName.substring(0, fileName.lastIndexOf("?"));
+			}
+
 			String substring1 = substring.substring(substring.lastIndexOf("/") + 1);
 			this.setFileAcl(KS3StoragePlugin.PLUGIN_NAME,substring1,true,bucketName);
 			return substring;

+ 8 - 1
mec-web/src/main/java/com/ym/mec/web/controller/ImLiveBroadcastRoomController.java

@@ -59,7 +59,8 @@ public class ImLiveBroadcastRoomController extends BaseController {
             @ApiImplicitParam(name = "startTime", dataType = "String", value = "开始时间"),
             @ApiImplicitParam(name = "endTime", dataType = "String", value = "结束时间"),
             @ApiImplicitParam(name = "popularize", dataType = "Integer", value = "是否在首页推广 0否 1是"),
-            @ApiImplicitParam(name = "clientType", dataType = "String", value = "用户类型 TEACHER 老师 EDUCATION 教务端"),
+            @ApiImplicitParam(name = "sort", dataType = "String", value = "用户类型 TEACHER 老师 EDUCATION 教务端"),
+            @ApiImplicitParam(name = "sortType", dataType = "String", value = "不传是默认web端排序  1:直播中--未开始--已结束(状态相同时,根据直播时间倒序)"),
             @ApiImplicitParam(name = "page", dataType = "Integer", value = "页数"),
             @ApiImplicitParam(name = "rows", dataType = "Integer", value = "每页数量"),
     })
@@ -223,6 +224,12 @@ public class ImLiveBroadcastRoomController extends BaseController {
         return succeed();
     }
 
+    @GetMapping("/destroyLiveRoom")
+    public Object destroyLiveRoom() {
+        imLiveBroadcastRoomService.destroyLiveRoom();
+        return succeed();
+    }
+
     @GetMapping("/shareGroup")
     public HttpResponseResult<Object> shareGroup(@ApiParam(value = "房间uid", required = true) String roomUid,
                                                  @ApiParam(value = "群编号", required = true) String groupIds) {

+ 1 - 0
mec-web/src/main/java/com/ym/mec/web/controller/ImLiveBroadcastRoomMemberController.java

@@ -46,6 +46,7 @@ public class ImLiveBroadcastRoomMemberController extends BaseController {
     @ApiImplicitParams({
             @ApiImplicitParam(name = "search", dataType = "String", value = "模糊搜索 学员编号姓名"),
             @ApiImplicitParam(name = "roomUid", dataType = "String", value = "房间uid"),
+            @ApiImplicitParam(name = "onlineStatus", dataType = "String 0:离线 1:在线  不传是全部", value = "在线状态"),
             @ApiImplicitParam(name = "page", dataType = "Integer", value = "页数"),
             @ApiImplicitParam(name = "rows", dataType = "Integer", value = "每页数量"),
     })

+ 8 - 0
mec-web/src/main/java/com/ym/mec/web/controller/TaskController.java

@@ -634,4 +634,12 @@ public class TaskController extends BaseController {
     public void  pushStartTrain(){
         tempLittleArtistTrainingCampService.pushStartTrain();
     }
+
+	@ApiOperation("每天凌晨2点钟-直播间销毁定时任务")
+	@GetMapping("/destroyLiveRoom")
+	public Object destroyLiveRoom() {
+		imLiveBroadcastRoomService.destroyLiveRoom();
+		return succeed();
+	}
+
 }

+ 7 - 0
pom.xml

@@ -229,6 +229,13 @@
 				<version>1.2.2</version>
 			</dependency>
 
+			<!--修复依赖冲突-->
+			<dependency>
+				<groupId>org.jetbrains.kotlin</groupId>
+				<artifactId>kotlin-stdlib</artifactId>
+				<version>1.3.70</version>
+			</dependency>
+
 		</dependencies>
 	</dependencyManagement>