瀏覽代碼

Merge branch 'hgw-直播课修改' into saas_2022_05_17_activity

# Conflicts:
#	mec-biz/src/main/java/com/ym/mec/biz/service/impl/ClassGroupServiceImpl.java
#	mec-biz/src/main/java/com/ym/mec/biz/service/impl/MusicGroupPaymentCalenderServiceImpl.java
hgw 3 年之前
父節點
當前提交
002133a5f0
共有 29 個文件被更改,包括 853 次插入318 次删除
  1. 30 7
      .gitignore
  2. 0 16
      mec-auth/mec-auth-server/src/main/resources/bootstrap-dev_server.properties
  3. 1 1
      mec-biz/src/main/java/com/ym/mec/biz/dal/dao/CourseScheduleStudentPaymentDao.java
  4. 2 0
      mec-biz/src/main/java/com/ym/mec/biz/dal/dao/ImLiveBroadcastRoomDao.java
  5. 30 0
      mec-biz/src/main/java/com/ym/mec/biz/dal/entity/ImLiveBroadcastRoom.java
  6. 27 0
      mec-biz/src/main/java/com/ym/mec/biz/dal/vo/BaseRoomUserVo.java
  7. 11 0
      mec-biz/src/main/java/com/ym/mec/biz/dal/vo/ImLiveBroadcastRoomVo.java
  8. 7 48
      mec-biz/src/main/java/com/ym/mec/biz/dal/vo/RoomUserInfoVo.java
  9. 22 0
      mec-biz/src/main/java/com/ym/mec/biz/handler/SysUserContextHolder.java
  10. 11 0
      mec-biz/src/main/java/com/ym/mec/biz/service/ClassGroupService.java
  11. 28 13
      mec-biz/src/main/java/com/ym/mec/biz/service/ImLiveBroadcastRoomService.java
  12. 385 98
      mec-biz/src/main/java/com/ym/mec/biz/service/impl/ImLiveBroadcastRoomServiceImpl.java
  13. 4 6
      mec-biz/src/main/java/com/ym/mec/biz/service/impl/SysMessageServiceImpl.java
  14. 26 0
      mec-biz/src/main/java/com/ym/mec/biz/service/impl/SysUserServiceImpl.java
  15. 2 2
      mec-biz/src/main/resources/config/mybatis/CourseScheduleStudentPaymentMapper.xml
  16. 8 8
      mec-biz/src/main/resources/config/mybatis/FinancialExpenditureMapper.xml
  17. 16 5
      mec-biz/src/main/resources/config/mybatis/ImLiveBroadcastRoomMapper.xml
  18. 1 1
      mec-biz/src/main/resources/config/mybatis/SysMessageMapper.xml
  19. 20 10
      mec-client-api/src/main/java/com/ym/mec/im/ImFeignService.java
  20. 10 6
      mec-client-api/src/main/java/com/ym/mec/im/fallback/ImFeignServiceFallback.java
  21. 2 1
      mec-im/src/main/java/com/ym/config/ResourceServerConfig.java
  22. 7 0
      mec-im/src/main/java/com/ym/controller/LiveRoomController.java
  23. 26 0
      mec-im/src/main/java/com/ym/mec/im/IMHelper.java
  24. 2 0
      mec-im/src/main/java/com/ym/pojo/IMApiResultInfo.java
  25. 48 19
      mec-im/src/main/java/com/ym/service/Impl/LiveRoomServiceImpl.java
  26. 2 0
      mec-im/src/main/java/com/ym/service/LiveRoomService.java
  27. 57 57
      mec-im/src/main/resources/logback-spring.xml
  28. 38 0
      mec-student/src/main/java/com/ym/mec/student/controller/ImLiveBroadcastRoomController.java
  29. 30 20
      mec-web/src/main/java/com/ym/mec/web/controller/ImLiveBroadcastRoomController.java

+ 30 - 7
.gitignore

@@ -1,14 +1,37 @@
 *.class
 .metadata
-target
+/target/
 *.classpath
 .settings
 .project
-/p2p-biz/test-output
-bin
-/manage-center/src/main/resources/config/properties/generatorConfig.xml
-.gitignore
-.idea
 *.iml
 /lib/
-rebel.xml
+
+### 忽略子模块的文件 ###
+**/*.ims
+**/*.iml
+**/*.ipr
+**/dep.txt
+**/.mvn/
+**/.idea/
+**/out/
+**/logs/
+**/target/
+**/mvnw
+**/mvnw.cmd
+
+### IntelliJ IDEA ###
+/.idea/
+/out/
+/logs/
+/.mvn/
+!.gitignore
+mvnw
+mvnw.cmd
+*.iws
+*.ipr
+/codegen/src/main/resources/generateConfigration.xml
+**/bootstrap-dev.yml
+**/bootstrap-dev.properties
+/bin/
+**/logback-spring.xml

+ 0 - 16
mec-auth/mec-auth-server/src/main/resources/bootstrap-dev_server.properties

@@ -1,16 +0,0 @@
-#\u6307\u5b9a\u5f00\u53d1\u73af\u5883
-#spring.profiles.active=dev
-#\u670d\u52a1\u5668\u5730\u5740
-spring.cloud.nacos.config.server-addr=47.114.1.200:8848
-#\u9ed8\u8ba4\u4e3aPublic\u547d\u540d\u7a7a\u95f4,\u53ef\u4ee5\u7701\u7565\u4e0d\u5199
-spring.cloud.nacos.config.namespace=fd352683-69df-439a-802f-c44f0c21329c
-#\u6307\u5b9a\u914d\u7f6e\u7fa4\u7ec4 --\u5982\u679c\u662fPublic\u547d\u540d\u7a7a\u95f4 \u5219\u53ef\u4ee5\u7701\u7565\u7fa4\u7ec4\u914d\u7f6e
-spring.cloud.nacos.config.group=DEFAULT_GROUP
-#\u6587\u4ef6\u540d -- \u5982\u679c\u6ca1\u6709\u914d\u7f6e\u5219\u9ed8\u8ba4\u4e3a ${spring.appliction.name}
-spring.cloud.nacos.config.prefix=auth
-#\u6307\u5b9a\u6587\u4ef6\u540e\u7f00
-spring.cloud.nacos.config.file-extension=yaml
-#\u662f\u5426\u52a8\u6001\u5237\u65b0
-spring.cloud.nacos.config.refresh.enabled=true
-#\u662f\u5426\u542f\u7528nacos\u914d\u7f6e\u4e2d\u5fc3
-spring.cloud.nacos.config.enabled=true

+ 1 - 1
mec-biz/src/main/java/com/ym/mec/biz/dal/dao/CourseScheduleStudentPaymentDao.java

@@ -574,7 +574,7 @@ public interface CourseScheduleStudentPaymentDao extends BaseDAO<Long, CourseSch
 
     List<Mapper> queryUserMusicGroupCourseNumByClassTime(@Param("groupType") GroupType groupType, @Param("musicGroupIds") Set<String> musicGroupIds,
 			@Param("userId") Integer userId, @Param("startDate") Date startDate, @Param("endDate") Date endDate);
-    
+
 	Boolean hasStudentMusicTheoryCourseInfo(@Param("organId") String organId, @Param("tenantId") Integer tenantId, @Param("groupType") String groupType);
 
     List<ExportStudentCourseInfoDto> queryStudentCourseInfo(@Param("organId") String organId, @Param("tenantId") Integer tenantId, @Param("groupType") String groupType);

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

@@ -24,5 +24,7 @@ public interface ImLiveBroadcastRoomDao extends BaseMapper<ImLiveBroadcastRoom>
 
     int insertBatch(@Param("entities") List<ImLiveBroadcastRoom> entities);
 
+    IPage<Map<Integer,Object>> queryUserPageByTenantId(Page<Map<Integer,Object>> page);
+
 }
 

+ 30 - 0
mec-biz/src/main/java/com/ym/mec/biz/dal/entity/ImLiveBroadcastRoom.java

@@ -67,6 +67,10 @@ public class ImLiveBroadcastRoom implements Serializable {
     @ApiModelProperty(value = "房间状态 0正常 1已删除 2销毁")
     private Integer roomState;
 
+    @TableField("popularize_")
+    @ApiModelProperty(value = "是否在首页推广 0否 1是 - 每个机构只能有一个直播间在首页推广")
+    private Integer popularize;
+
     @TableField("created_by_")
     @ApiModelProperty(value = "创建人")
     private Integer createdBy;
@@ -165,22 +169,48 @@ public class ImLiveBroadcastRoom implements Serializable {
         this.roomConfig = roomConfig;
     }
 
+    /**
+     * 直播状态 0未开始 1开始 2已结束
+     */
     public Integer getLiveState() {
         return liveState;
     }
 
+    /**
+     * 直播状态 0未开始 1开始 2已结束
+     */
     public void setLiveState(Integer liveState) {
         this.liveState = liveState;
     }
 
+    /**
+     * 房间状态 0正常 1已删除 2销毁
+     */
     public Integer getRoomState() {
         return roomState;
     }
 
+    /**
+     * 房间状态 0正常 1已删除 2销毁
+     */
     public void setRoomState(Integer roomState) {
         this.roomState = roomState;
     }
 
+    /**
+     * 是否在首页推广 0否 1是 - 每个机构只能有一个直播间在首页推广
+     */
+    public Integer getPopularize() {
+        return popularize;
+    }
+
+    /**
+     * 是否在首页推广 0否 1是 - 每个机构只能有一个直播间在首页推广
+     */
+    public void setPopularize(Integer popularize) {
+        this.popularize = popularize;
+    }
+
     public Integer getCreatedBy() {
         return createdBy;
     }

+ 27 - 0
mec-biz/src/main/java/com/ym/mec/biz/dal/vo/BaseRoomUserVo.java

@@ -0,0 +1,27 @@
+package com.ym.mec.biz.dal.vo;
+
+import java.io.Serializable;
+
+public class BaseRoomUserVo implements Serializable {
+
+    private Integer userId;
+
+    private String userName;
+
+    public Integer getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Integer userId) {
+        this.userId = userId;
+    }
+
+    public String getUserName() {
+        return userName;
+    }
+
+    public void setUserName(String userName) {
+        this.userName = userName;
+    }
+
+}

+ 11 - 0
mec-biz/src/main/java/com/ym/mec/biz/dal/vo/ImLiveBroadcastRoomVo.java

@@ -71,6 +71,9 @@ public class ImLiveBroadcastRoomVo implements Serializable {
     @ApiModelProperty(value = "播出端")
     private String os = "pc";
 
+    @ApiModelProperty(value = "是否在首页推广 0否 1是 - 每个机构只能有一个直播间在首页推广")
+    private Integer popularize;
+
     @ApiModelProperty(value = "点赞数")
     private Integer likeNum;
     @ApiModelProperty(value = "当前观看人数")
@@ -245,5 +248,13 @@ public class ImLiveBroadcastRoomVo implements Serializable {
     public void setTotalLookNum(Integer totalLookNum) {
         this.totalLookNum = totalLookNum;
     }
+
+    public Integer getPopularize() {
+        return popularize;
+    }
+
+    public void setPopularize(Integer popularize) {
+        this.popularize = popularize;
+    }
 }
 

+ 7 - 48
mec-biz/src/main/java/com/ym/mec/biz/dal/vo/RoomUserInfoVo.java

@@ -9,10 +9,7 @@ import java.util.Date;
  * @author hgw
  * Created by 2022-02-24
  */
-public class RoomUserInfoVo implements Serializable {
-    private Integer userId;
-
-    private String userName;
+public class RoomUserInfoVo extends BaseRoomUserVo implements Serializable  {
 
     private Integer tenantId;
 
@@ -23,32 +20,9 @@ public class RoomUserInfoVo implements Serializable {
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date firstJoinTime;
 
-    //动态进入房间时间  多次进入房间每次进入更新
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
-    private Date dynamicJoinTime;
-
-    //最后一次退出时间 该字段是为了控制连续退出的 如果2次退出时间间隔不足1分钟则不处理
+    //动态观看直播时间  主播每次开启直播后更新
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
-    private Date lastOutTime;
-
-    //0:在房间 1:不在房间
-    private Integer state;
-
-    public Integer getUserId() {
-        return userId;
-    }
-
-    public void setUserId(Integer userId) {
-        this.userId = userId;
-    }
-
-    public String getUserName() {
-        return userName;
-    }
-
-    public void setUserName(String userName) {
-        this.userName = userName;
-    }
+    private Date dynamicLookTime;
 
     public Integer getTotalViewTime() {
         return totalViewTime;
@@ -66,20 +40,12 @@ public class RoomUserInfoVo implements Serializable {
         this.firstJoinTime = firstJoinTime;
     }
 
-    public Date getDynamicJoinTime() {
-        return dynamicJoinTime;
+    public Date getDynamicLookTime() {
+        return dynamicLookTime;
     }
 
-    public void setDynamicJoinTime(Date dynamicJoinTime) {
-        this.dynamicJoinTime = dynamicJoinTime;
-    }
-
-    public Date getLastOutTime() {
-        return lastOutTime;
-    }
-
-    public void setLastOutTime(Date lastOutTime) {
-        this.lastOutTime = lastOutTime;
+    public void setDynamicLookTime(Date dynamicLookTime) {
+        this.dynamicLookTime = dynamicLookTime;
     }
 
     public Integer getTenantId() {
@@ -90,11 +56,4 @@ public class RoomUserInfoVo implements Serializable {
         this.tenantId = tenantId;
     }
 
-    public Integer getState() {
-        return state;
-    }
-
-    public void setState(Integer state) {
-        this.state = state;
-    }
 }

+ 22 - 0
mec-biz/src/main/java/com/ym/mec/biz/handler/SysUserContextHolder.java

@@ -0,0 +1,22 @@
+package com.ym.mec.biz.handler;
+
+
+import com.ym.mec.auth.api.entity.SysUser;
+
+public class SysUserContextHolder {
+
+	private static final ThreadLocal<SysUser> sysUserContextHolder = new ThreadLocal<SysUser>();
+
+	public static void setSysUser(SysUser sysUser) {
+		sysUserContextHolder.set(sysUser);
+	}
+
+	public static SysUser getSysUser() {
+		return sysUserContextHolder.get();
+	}
+
+	public static void clearSysUser() {
+		sysUserContextHolder.remove();
+	}
+
+}

+ 11 - 0
mec-biz/src/main/java/com/ym/mec/biz/service/ClassGroupService.java

@@ -12,6 +12,7 @@ import com.ym.mec.common.entity.ImUserModel;
 import com.ym.mec.common.page.PageInfo;
 import com.ym.mec.common.page.QueryInfo;
 import com.ym.mec.common.service.BaseService;
+import org.apache.ibatis.annotations.Param;
 
 import java.lang.reflect.InvocationTargetException;
 import java.math.BigDecimal;
@@ -601,4 +602,14 @@ public interface ClassGroupService extends BaseService<Integer, ClassGroup> {
 
     //修改班级基本信息
     int updateClassGroup(ClassGroup classGroup);
+
+    /**
+     * 根据班级编号冻结所选班级
+     *
+     * @param classGroupIds
+     * @param lockFlag
+     * @return
+     * @author zouxuan
+     */
+    int batchUpdateLockByClassGroupIds(List<Integer> classGroupIds,int lockFlag);
 }

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

@@ -3,8 +3,8 @@ package com.ym.mec.biz.service;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.ym.mec.biz.dal.dto.ImLiveBroadcastRoomDto;
 import com.ym.mec.biz.dal.entity.ImLiveBroadcastRoom;
+import com.ym.mec.biz.dal.vo.BaseRoomUserVo;
 import com.ym.mec.biz.dal.vo.ImLiveBroadcastRoomVo;
-import com.ym.mec.biz.dal.vo.RoomUserInfoVo;
 import com.ym.mec.common.entity.ImUserState;
 import com.ym.mec.common.page.PageInfo;
 
@@ -35,6 +35,19 @@ public interface ImLiveBroadcastRoomService extends IService<ImLiveBroadcastRoom
 
     void delete(Integer id);
 
+    /**
+     * 推广直播间-每个机构只能有一个直播间在首页推广
+     *
+     * @param id         直播间id
+     * @param popularize 是否在首页推广 0否 1是
+     */
+    void opsPopularize(Integer id, Integer popularize);
+
+    /**
+     * 查询该机构目前推广的直播间
+     */
+    ImLiveBroadcastRoomVo queryPopularizeRoom();
+
     void destroyExpiredLiveRoom();
 
     void syncLike(String roomUid, Integer likeNum);
@@ -45,23 +58,25 @@ public interface ImLiveBroadcastRoomService extends IService<ImLiveBroadcastRoom
 
     void joinRoom(String roomUid, Integer userId);
 
-    void startLive(String roomUid,Integer userId);
+    void startLive(String roomUid, Integer userId);
 
-    void closeLive(String roomUid,Integer userId);
+    void closeLive(String roomUid, Integer userId);
 
     void createLiveRoom();
 
-    Map<String, Object> test(String roomUid, Integer userId);
+    Map<String, Object> test(String roomUid);
 
     /**
-    * @description: 分享直播链接
-     * @param roomUid
-    * @return void
-    * @author zx
-    * @date 2022/2/23 16:17
-    */
-    void shareGroup(String roomUid,String groupIds);
-
-    List<RoomUserInfoVo> queryRoomUserInfo(String roomUid);
+     * @param roomUid 直播间uid
+     * @author zx
+     * @since 2022/2/23 16:17
+     * 分享直播链接
+     */
+    void shareGroup(String roomUid, String groupIds);
+
+    List<BaseRoomUserVo> queryRoomOnlineUserInfo(String roomUid);
+
+    List<BaseRoomUserVo> queryRoomLimitOnlineUserInfo(String roomUid);
+
 }
 

+ 385 - 98
mec-biz/src/main/java/com/ym/mec/biz/service/impl/ImLiveBroadcastRoomServiceImpl.java

@@ -4,9 +4,11 @@ import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.fasterxml.jackson.annotation.JsonFormat;
+import com.google.common.collect.Lists;
 import com.ym.mec.auth.api.client.SysUserFeignService;
 import com.ym.mec.auth.api.entity.SysUser;
 import com.ym.mec.biz.dal.dao.ImLiveBroadcastRoomDao;
@@ -16,6 +18,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.enums.MessageTypeEnum;
+import com.ym.mec.biz.dal.vo.BaseRoomUserVo;
 import com.ym.mec.biz.dal.vo.ImLiveBroadcastRoomVo;
 import com.ym.mec.biz.dal.vo.RoomUserInfoVo;
 import com.ym.mec.biz.service.*;
@@ -32,6 +35,7 @@ import com.ym.mec.util.http.HttpUtil;
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.redisson.api.RBucket;
+import org.redisson.api.RLock;
 import org.redisson.api.RMap;
 import org.redisson.api.RedissonClient;
 import org.slf4j.Logger;
@@ -43,8 +47,11 @@ import org.springframework.transaction.annotation.Transactional;
 
 import java.io.Serializable;
 import java.util.*;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 
 /**
@@ -73,11 +80,14 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
     @Autowired
     private ImLiveBroadcastRoomDataService liveBroadcastRoomDataService;
 
+    //待替换的变量
     public static final String USER_ID = "${userId}";
     public static final String ROOM_UID = "${roomUid}";
 
     //直播间累计用户信息-指只要进入到该房间的用户都要记录
     public static final String LIVE_ROOM_TOTAL_USER_LIST = "IM:LIVE_ROOM_TOTAL_USER_LIST:" + ROOM_UID;
+    //直播间在线用户信息
+    public static final String LIVE_ROOM_ONLINE_USER_LIST = "IM:LIVE_ROOM_ONLINE_USER_LIST:" + ROOM_UID;
     //主讲人信息
     public static final String LIVE_SPEAKER_INFO = "IM:LIVE_SPEAKER_INFO:" + USER_ID;
     //用户对应的直播间Uid
@@ -86,6 +96,8 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
     public static final String LIVE_USER_STATE_TIME = "IM:LIVE_USER_STATE_TIME:" + USER_ID;
     //房间点赞数
     public static final String LIVE_ROOM_LIKE = "IM:LIVE_ROOM_LIKE:" + ROOM_UID;
+    //计算人员观看时长锁
+    public static final String LIVE_LOOK_LOCK = "IM:LIVE_LOOK_LOCK:" + ROOM_UID;
     //直播提前开始时间
     public static final int PRE_LIVE_TIME_MINUTE = 30;
 
@@ -125,7 +137,6 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
         return room;
     }
 
-
     /**
      * 查询直播间信息
      *
@@ -133,15 +144,20 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
      */
     @Override
     public ImLiveBroadcastRoomVo queryRoomInfo(String roomUid) {
+        ImLiveBroadcastRoomVo roomVo = getImLiveBroadcastRoomVo(roomUid);
+        if (roomVo == null) return null;
+        getRoomData(roomVo);
+        return roomVo;
+    }
+
+    private ImLiveBroadcastRoomVo getImLiveBroadcastRoomVo(String roomUid) {
         List<ImLiveBroadcastRoomVo> list = baseMapper.queryPage(new HashMap<String, Object>() {{
             put("roomUid", roomUid);
         }});
         if (CollectionUtils.isEmpty(list)) {
             return null;
         }
-        ImLiveBroadcastRoomVo roomVo = list.get(0);
-        getRoomData(roomVo);
-        return roomVo;
+        return list.get(0);
     }
 
     /**
@@ -235,10 +251,61 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
         obj.setRoomState(1);
         obj.setUpdatedBy(getSysUser().getId());
         obj.setUpdatedTime(new Date());
+        obj.setPopularize(0);
         this.updateById(obj);
     }
 
     /**
+     * 推广直播间-每个机构只能有一个直播间在首页推广
+     *
+     * @param id         直播间id
+     * @param popularize 是否在首页推广 0否 1是
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void opsPopularize(Integer id, Integer popularize) {
+        if (!WrapperUtil.checkInObj(popularize, 0, 1)) {
+            throw new BizException("参数错误");
+        }
+        Optional<ImLiveBroadcastRoom> roomOptional = Optional.ofNullable(id)
+                .map(this::getById);
+        roomOptional.orElseThrow(() -> new BizException("直播间不存在"));
+        roomOptional.filter(room -> room.getRoomState() == 0)
+                .orElseThrow(() -> new BizException("直播间已经删除,无法设置推广"));
+        roomOptional.filter(room -> room.getLiveState() != 2)
+                .orElseThrow(() -> new BizException("直播已结束,无法设置推广"));
+        ImLiveBroadcastRoom obj = roomOptional.get();
+        if (Objects.equals(obj.getPopularize(), popularize)) {
+            return;
+        }
+        //推广该直播间,先清除其该机构他直播间的推广状态
+        if (popularize == 1) {
+            this.update(Wrappers.<ImLiveBroadcastRoom>lambdaUpdate()
+                    .set(ImLiveBroadcastRoom::getPopularize, 0)
+                    .eq(ImLiveBroadcastRoom::getRoomState, 0)
+                    .eq(ImLiveBroadcastRoom::getTenantId, obj.getTenantId()));
+        }
+        //更新直播间推广状态
+        obj.setPopularize(popularize);
+        this.updateById(obj);
+    }
+
+    /**
+     * 查询该机构目前推广的直播间
+     */
+    @Override
+    public ImLiveBroadcastRoomVo queryPopularizeRoom() {
+        Map<String, Object> param = new HashMap<>();
+        param.put("tenantId", TenantContextHolder.getTenantId());
+        param.put("popularize", 1);
+        List<ImLiveBroadcastRoomVo> list = baseMapper.queryPage(param);
+        if (CollectionUtils.isNotEmpty(list)) {
+            return list.get(0);
+        }
+        return null;
+    }
+
+    /**
      * 定时任务,每分钟执行
      * 销毁主讲人退出超过30分钟的直播间
      */
@@ -281,7 +348,7 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
 
                 //1.主播没有进入房间,则直接销毁房间
                 if (Objects.isNull(speakerInfo.getJoinRoomTime())) {
-                    log.info("roomDestroy not joinRoom >>>> cache : {}", JSONObject.toJSONString(test(room.getRoomUid(), room.getSpeakerId())));
+                    log.info("roomDestroy not joinRoom >>>> cache : {}", JSONObject.toJSONString(test(room.getRoomUid())));
                     roomDestroy(room);
                     return;
                 }
@@ -293,7 +360,7 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
                         //如果退出时间大于进入时间,就将退出时间+expiredMinute分钟
                         Date exitExpiredTime = DateUtil.addMinutes(speakerInfo.getExitRoomTime(), expiredMinute);
                         if (now.getTime() >= exitExpiredTime.getTime()) {
-                            log.info("roomDestroy exitExpiredTime >>>> cache : {}", JSONObject.toJSONString(test(room.getRoomUid(), room.getSpeakerId())));
+                            log.info("roomDestroy exitExpiredTime >>>> cache : {}", JSONObject.toJSONString(test(room.getRoomUid())));
                             roomDestroy(room);
                         }
                     }
@@ -318,31 +385,25 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
         if (room.getLiveState() == 0) {
             throw new BizException("直播未开始");
         }
+        if (room.getLiveState() == 2) {
+            throw new BizException("直播已经结束,请刷新数据!");
+        }
+        //销毁房间
         roomDestroy(room);
     }
 
     public void roomDestroy(ImLiveBroadcastRoom room) {
+        //10秒内同一个房间不能重复销毁-防重复销毁
+        RBucket<Object> bucket = redissonClient.getBucket("IM:ROOMDESTROY:" + room.getRoomUid());
+        if (!bucket.trySet(1, 10, TimeUnit.SECONDS)) {
+            return;
+        }
         log.error("roomDestroy>>>> room : {}", JSONObject.toJSONString(room));
         String roomUid = room.getRoomUid();
         Integer speakerId = room.getSpeakerId();
 
-        try {
-            //向聊天室发自定义消息踢出所有人
-            ImRoomMessage message = new ImRoomMessage();
-            message.setFromUserId(speakerId.toString());
-            message.setToChatroomId(roomUid);
-            message.setObjectName(ImRoomMessage.FORCED_OFFLINE);
-            imFeignService.publishRoomMsg(message);
-            log.info("roomDestroy>>>> FORCED_OFFLINE {}", JSONObject.toJSONString(message));
-            //销毁直播间
-            imFeignService.destroyLiveRoom(roomUid);
-            log.info("roomDestroy>>>> destroyLiveRoom {}", JSONObject.toJSONString(message));
-        } catch (Exception e) {
-            log.error("roomDestroy>>>> errorMsg{}", e.getMessage(), e.getCause());
-        }
-
         //获取所有直播间缓存数据并写入数据库后并清理缓存
-        insertAndCleanLiveData(roomUid, speakerId);
+        CompletableFuture.runAsync(() -> insertAndCleanLiveData(roomUid, speakerId));
         log.info("roomDestroy>>>> insertAndCleanLiveData {}", JSONObject.toJSONString(room));
 
         //将房间状态改为已销毁
@@ -357,38 +418,43 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
         room.setUpdatedBy(userId);
         room.setUpdatedTime(date);
         room.setLiveEndTime(date);
+        room.setPopularize(0);//销毁直播间后要关闭推广
         this.updateById(room);
+
+        //向聊天室发自定义消息踢出所有人
+        try {
+            ImRoomMessage message = new ImRoomMessage();
+            message.setFromUserId(speakerId.toString());
+            message.setToChatroomId(roomUid);
+            message.setObjectName(ImRoomMessage.FORCED_OFFLINE);
+            imFeignService.publishRoomMsg(message);
+            log.info("roomDestroy>>>> FORCED_OFFLINE {}", JSONObject.toJSONString(message));
+            //销毁直播间
+            imFeignService.destroyLiveRoom(roomUid);
+            log.info("roomDestroy>>>> destroyLiveRoom {}", JSONObject.toJSONString(message));
+        } catch (Exception e) {
+            log.error("roomDestroy>>>> errorMsg{}", e.getMessage(), e.getCause());
+        }
     }
 
     //获取该直播间所有数据写入数据库-并清理缓存
     private void insertAndCleanLiveData(String roomUid, Integer speakerId) {
+        Date now = new Date();
         //总观看人数
-        int totalLookNum = 0;
+        List<ImLiveBroadcastRoomMember> memberList = new ArrayList<>();
         //获取直播间所有人数据写入 im_live_broadcast_room_member
-        RMap<Integer, RoomUserInfoVo> roomTotalUserCache = redissonClient.getMap(LIVE_ROOM_TOTAL_USER_LIST.replace(ROOM_UID, roomUid));
+        RMap<Integer, RoomUserInfoVo> roomTotalUserCache = getTotalUserCache(roomUid);
         if (roomTotalUserCache.isExists()) {
             List<RoomUserInfoVo> roomTotalUser = new ArrayList<>(roomTotalUserCache.values());
-            List<ImLiveBroadcastRoomMember> memberList = new ArrayList<>();
-            roomTotalUser.forEach(v -> {
+            for (RoomUserInfoVo v : roomTotalUser) {
                 ImLiveBroadcastRoomMember member = new ImLiveBroadcastRoomMember();
                 member.setTenantId(v.getTenantId());
                 member.setRoomUid(roomUid);
                 member.setUserId(v.getUserId());
                 member.setJoinTime(v.getFirstJoinTime());
-                member.setTotalTime(v.getTotalViewTime());
+                member.setTotalTime(getLookMinutes(v.getDynamicLookTime(), now, v.getTotalViewTime()));
                 memberList.add(member);
-            });
-            if (CollectionUtils.isNotEmpty(memberList)) {
-                liveBroadcastRoomMemberService.getDao().insertBatch(memberList);
-                totalLookNum = roomTotalUser.size();
             }
-            //删除用户对应的直播间关系缓存
-            roomTotalUser.stream()
-                    .map(RoomUserInfoVo::getUserId)
-                    .filter(Objects::nonNull)
-                    .forEach(id -> redissonClient.getBucket(LIVE_USER_ROOM.replace(USER_ID, id.toString())).delete());
-            //删除直播间所有人数据
-            roomTotalUserCache.clear();
         }
 
         //获取直播点赞
@@ -399,7 +465,7 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
             //删除房间点赞数据
             likeCache.delete();
         }
-
+        int speakerLiveTime = 0;
         //获取直播间主讲人信息 写入im_live_broadcast_room_data
         RBucket<RoomSpeakerInfo> speakerCache = redissonClient.getBucket(LIVE_SPEAKER_INFO.replace(USER_ID, speakerId.toString()));
         if (speakerCache.isExists()) {
@@ -408,14 +474,35 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
             liveData.setTenantId(speakerInfo.getTenantId());
             liveData.setRoomUid(roomUid);
             liveData.setLikeNum(like);
-            liveData.setTotalUserNum(totalLookNum);
-            liveData.setUpdatedTime(new Date());
-            liveData.setLiveTime(speakerInfo.getTotalLiveTime());
+            liveData.setTotalUserNum(CollectionUtils.isNotEmpty(memberList) ? memberList.size() : 0);
+            liveData.setUpdatedTime(now);
+            liveData.setLiveTime(getLookMinutes(speakerInfo.getStartLiveTime(), now, speakerInfo.getTotalLiveTime()));
             liveBroadcastRoomDataService.save(liveData);
             //删除房间主讲人数据
             speakerCache.delete();
+            //获取主讲人直播时长
+            speakerLiveTime = speakerInfo.getTotalLiveTime();
         }
 
+        //写入im_live_broadcast_room_member表,校验用户观看时长,不能大于主讲人直播时长
+        if (CollectionUtils.isNotEmpty(memberList)) {
+            for (ImLiveBroadcastRoomMember member : memberList) {
+                if (member.getTotalTime() > speakerLiveTime) {
+                    member.setTotalTime(speakerLiveTime);
+                }
+            }
+            Lists.partition(memberList, 500)
+                    .forEach(list -> liveBroadcastRoomMemberService.getDao().insertBatch(list));
+            //删除用户对应的直播间关系缓存
+            memberList.stream()
+                    .map(ImLiveBroadcastRoomMember::getUserId)
+                    .filter(Objects::nonNull)
+                    .forEach(id -> redissonClient.getBucket(LIVE_USER_ROOM.replace(USER_ID, id.toString())).delete());
+        }
+        //删除直播间所有用户数据
+        roomTotalUserCache.delete();
+        //删除在线用户数据
+        getOnlineUserCache(roomUid).delete();
     }
 
     /**
@@ -458,7 +545,7 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
                 }
             }
             //将最新的时间写入缓存
-            userStateTimeCache.set(userStateTime,5L, TimeUnit.MINUTES);
+            userStateTimeCache.set(userStateTime, 5L, TimeUnit.MINUTES);
             //查询主讲人userId,若是主讲人
             RBucket<RoomSpeakerInfo> speakerCache = redissonClient.getBucket(LIVE_SPEAKER_INFO.replace(USER_ID, userid));
             if (speakerCache.isExists()) {
@@ -490,15 +577,19 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
             Integer userId = Integer.valueOf(userid);
 
             //从房间累计用户信息中查询该用户的信息
-            RMap<Integer, RoomUserInfoVo> roomTotalUser = redissonClient.getMap(LIVE_ROOM_TOTAL_USER_LIST.replace(ROOM_UID, roomUid));
+            RMap<Integer, RoomUserInfoVo> roomTotalUser = getTotalUserCache(roomUid);
             //该房间未查询到用户数据则不处理
             if (!roomTotalUser.isExists() && !roomTotalUser.containsKey(userId)) {
                 return;
             }
             //查询到用户数据
             RoomUserInfoVo userInfo = roomTotalUser.get(userId);
+            //查询在线人员列表
+            RMap<Integer, BaseRoomUserVo> onlineUserInfo = getOnlineUserCache(roomUid);
+            //获取当前用户是否在房间状态 true在 false不在
+            boolean userOnline = onlineUserInfo.isExists() && onlineUserInfo.containsKey(userId);
             //用户是在房间的状态 并且 突然离线 - 那么融云会发送用户离线消息-此刻就发送退出房间消息给主讲人
-            if (userInfo.getState() == 0 && user.getStatus().equals("1")) {
+            if (userOnline && user.getStatus().equals("1")) {
                 ImRoomMessage message = new ImRoomMessage();
                 message.setFromUserId(userId.toString());
                 message.setToChatroomId(roomUid);
@@ -511,16 +602,16 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
                 }
                 log.info("opsRoom>>>> looker LOOKER_LOGIN_OUT : {}", JSONObject.toJSONString(userInfo));
             }
-            //每次退出房间计算当前用户观看时长
-            int minutesBetween = getMinutesBetween(userInfo.getDynamicJoinTime(), now);
-            userInfo.setTotalViewTime(userInfo.getTotalViewTime() + minutesBetween);
-            //记录退出时间 并写入缓存
-            userInfo.setLastOutTime(now);
-            userInfo.setState(1);
+            //只有在主播开播后用户才有观看时间,才需要计算当前用户观看时长
+            if (Objects.nonNull(userInfo.getDynamicLookTime())) {
+                userInfo.setTotalViewTime(getLookMinutes(userInfo.getDynamicLookTime(), userInfo.getTotalViewTime()));
+                userInfo.setDynamicLookTime(null);
+            }
             roomTotalUser.fastPut(userId, userInfo);
+            //从在线人员列表删除该人员
+            onlineUserInfo.fastRemove(userId);
             log.info("opsRoom>>>> looker userInfo: {}", JSONObject.toJSONString(userInfo));
         });
-
     }
 
     /**
@@ -556,10 +647,18 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
      * @param userId  用户id
      */
     public void joinRoom(String roomUid, Integer userId) {
+        //查询房间信息
+        ImLiveBroadcastRoomVo imLiveBroadcastRoomVo = getImLiveBroadcastRoomVo(roomUid);
+        if (Objects.isNull(imLiveBroadcastRoomVo)) {
+            log.info("opsRoom>>>> joinRoom error roomUid: {}", roomUid);
+            return;
+        }
         //记录用户当前房间uid
-        redissonClient.getBucket(LIVE_USER_ROOM.replace(USER_ID, userId.toString())).set(roomUid);
+        redissonClient.getBucket(LIVE_USER_ROOM.replace(USER_ID, userId.toString())).set(roomUid, 12L, TimeUnit.HOURS);
+        //在线人员列表
+        RMap<Integer, BaseRoomUserVo> onlineUserInfo = getOnlineUserCache(roomUid);
         //房间累计用户信息-指只要进入到该房间的用户都要记录
-        RMap<Integer, RoomUserInfoVo> roomTotalUser = redissonClient.getMap(LIVE_ROOM_TOTAL_USER_LIST.replace(ROOM_UID, roomUid));
+        RMap<Integer, RoomUserInfoVo> roomTotalUser = getTotalUserCache(roomUid);
         //判断是否第一次进房间
         RoomUserInfoVo userInfo;
         Date now = new Date();
@@ -572,9 +671,18 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
             userInfo.setFirstJoinTime(now);
             userInfo.setTotalViewTime(0);
         }
-        userInfo.setState(0);//0 进入/在房间
-        userInfo.setDynamicJoinTime(now);
+        //查询主讲人信息
+        RBucket<RoomSpeakerInfo> speakerCache = redissonClient.getBucket(LIVE_SPEAKER_INFO.replace(USER_ID, imLiveBroadcastRoomVo.getSpeakerId().toString()));
+        if (speakerCache.isExists()) {
+            //如果用户进来时主讲人已经开启直播则修改学生观看时间
+            Integer state = speakerCache.get().getState();
+            if (Objects.nonNull(state) && state == 0) {
+                userInfo.setDynamicLookTime(now);
+            }
+        }
         roomTotalUser.fastPut(userId, userInfo);
+        //进入房间写如在线人员列表
+        onlineUserInfo.fastPut(userId, userInfo);
         log.info("joinRoom>>>> userInfo: {}", JSONObject.toJSONString(userInfo));
     }
 
@@ -584,10 +692,18 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
      * @param roomUid 房间uid
      */
     public void startLive(String roomUid, Integer userId) {
-        //查询房间信息是否允许录像
+        //查询房间信息
         RBucket<RoomSpeakerInfo> speakerCache = redissonClient.getBucket(LIVE_SPEAKER_INFO.replace(USER_ID, userId.toString()));
+        if (!speakerCache.isExists()) {
+            return;
+        }
         RoomSpeakerInfo roomSpeakerInfo = speakerCache.get();
-        if (Objects.nonNull(roomSpeakerInfo.getWhetherVideo()) && roomSpeakerInfo.getWhetherVideo() == 0) {
+        //已是直播状态则直接返回
+        if (intEquals(roomSpeakerInfo.getState(), 0)) {
+            return;
+        }
+        //是否允许录像
+        if (intEquals(roomSpeakerInfo.getWhetherVideo(), 0)) {
             //开始录制视频
             try {
                 imFeignService.startRecord(roomUid);
@@ -595,51 +711,110 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
                 log.error("startRecord error: {}", e.getMessage());
             }
         }
+        Date now = new Date();
         //开始直播
         roomSpeakerInfo.setState(0);
-        roomSpeakerInfo.setStartLiveTime(new Date());
+        roomSpeakerInfo.setStartLiveTime(now);
         speakerCache.set(roomSpeakerInfo);
         log.info("startLive>>>> roomSpeakerInfo: {}", JSONObject.toJSONString(roomSpeakerInfo));
+        //主播开启直播,查询所有在直播间的用户并写入观看时间
+        CompletableFuture.runAsync(() -> this.asyncOpsLiveLookTime(roomUid, 1));
     }
 
     /**
      * 关闭直播-录像
      *
      * @param roomUid 房间uid
+     * @param userId  老师id
      */
     public void closeLive(String roomUid, Integer userId) {
         //查询房间主播信息
         RBucket<RoomSpeakerInfo> speakerCache = redissonClient.getBucket(LIVE_SPEAKER_INFO.replace(USER_ID, userId.toString()));
+        if (!speakerCache.isExists()) {
+            return;
+        }
         RoomSpeakerInfo roomSpeakerInfo = speakerCache.get();
         //关闭直播
         closeLive(roomSpeakerInfo);
         speakerCache.set(roomSpeakerInfo);
     }
 
+    /**
+     * 关闭直播-录像
+     *
+     * @param roomSpeakerInfo 房间主播信息
+     */
     private void closeLive(RoomSpeakerInfo roomSpeakerInfo) {
-        boolean stateFlag = Objects.nonNull(roomSpeakerInfo.getState()) && roomSpeakerInfo.getState() == 0;
-        if (Objects.nonNull(roomSpeakerInfo.getWhetherVideo()) && roomSpeakerInfo.getWhetherVideo() == 0
-                && stateFlag) {
-            //停止录制视频
+        //直播状态 true 直播中 false关闭直播
+        boolean stateFlag = intEquals(roomSpeakerInfo.getState(), 0);
+        //是否录像 true允许 false不允许
+        boolean whetherVideoFlag = intEquals(roomSpeakerInfo.getWhetherVideo(), 0);
+        //允许录像并在直播中
+        if (whetherVideoFlag && stateFlag) {
             try {
+                //停止录制视频
                 imFeignService.stopRecord(roomSpeakerInfo.getRoomUid());
             } catch (Exception e) {
                 log.error("stopRecord error: {}", e.getMessage());
             }
         }
+        //直播状态 true 直播中
         if (stateFlag) {
             Date now = new Date();
             roomSpeakerInfo.setEndLiveTime(now);
             roomSpeakerInfo.setState(1);
-            //计算时长
-            int minutesBetween = getMinutesBetween(roomSpeakerInfo.getStartLiveTime(), now);
-            int i = Objects.isNull(roomSpeakerInfo.getTotalLiveTime()) ? 0 : roomSpeakerInfo.getTotalLiveTime();
-            roomSpeakerInfo.setTotalLiveTime(i + minutesBetween);
+            //如果开播时间和本次操作结束播放时间小于1分钟则不计算观看时间
+            int liveMinutes = getLookMinutes(roomSpeakerInfo.getStartLiveTime(), null);
+            if (liveMinutes > 1) {
+                //写入本次直播时长
+                roomSpeakerInfo.setTotalLiveTime(getLookMinutes(roomSpeakerInfo.getStartLiveTime(), roomSpeakerInfo.getTotalLiveTime()));
+                //关闭直播后异步执行计算房间人员观看时长
+                CompletableFuture.runAsync(() -> this.asyncOpsLiveLookTime(roomSpeakerInfo.getRoomUid(), 2));
+            }
+            //计算完后将开始直播时间设置为空,待下次开启后再计算
+            roomSpeakerInfo.setStartLiveTime(null);
         }
         log.info("closeLive>>>> roomSpeakerInfo: {}", JSONObject.toJSONString(roomSpeakerInfo));
     }
 
     /**
+     * 打开/关闭直播后-异步计算房间人员观看时长
+     *
+     * @param roomUid 房间uid
+     * @param type    type 1:开始直播-开始录像     2:关闭直播关闭录像
+     */
+    private void asyncOpsLiveLookTime(String roomUid, Integer type) {
+        //加锁-避免快速点击开启直播和关闭直后异步执行后直播数据错误
+        boolean b = this.runIfLockCanGet(LIVE_LOOK_LOCK.replace(ROOM_UID, roomUid), () -> {
+            //查询所有在直播间的用户并计算观看时长
+            RMap<Integer, RoomUserInfoVo> roomTotalUser = getTotalUserCache(roomUid);
+            if (!roomTotalUser.isExists()) {
+                return;
+            }
+            //查询在线人员列表
+            RMap<Integer, BaseRoomUserVo> onlineUserInfo = getOnlineUserCache(roomUid);
+            roomTotalUser.forEach((id, userInfo) -> {
+                //获取当前用户是否在房间状态 true在 false不在
+                if (onlineUserInfo.isExists() && onlineUserInfo.containsKey(id)) {
+                    if (type.equals(1)) {
+                        //开启直播后对当前在房间的用户写入观看时间
+                        userInfo.setDynamicLookTime(new Date());
+                    } else if (type.equals(2)) {
+                        userInfo.setTotalViewTime(getLookMinutes(userInfo.getDynamicLookTime(), userInfo.getTotalViewTime()));
+                        userInfo.setDynamicLookTime(null);
+                    } else {
+                        return;
+                    }
+                    roomTotalUser.fastPut(id, userInfo);
+                }
+            });
+        }, 2, 1, TimeUnit.MINUTES);
+        if (!b) {
+            this.asyncOpsLiveLookTime(roomUid, type);
+        }
+    }
+
+    /**
      * 定时任务,每分钟执行
      * 提前30分钟主动去融云注册并创建房间
      */
@@ -724,18 +899,24 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
         }
         roomVo.setLikeNum((int) like);
         //累计总用户数量
-        List<RoomUserInfoVo> roomUserInfoVos = queryTotalRoomUserInfo(roomVo.getRoomUid());
-        if (CollectionUtils.isNotEmpty(roomUserInfoVos)) {
-            roomVo.setTotalLookNum(roomUserInfoVos.size());
-            //在房间观看用户数量
-            roomVo.setLookNum(queryRoomUserInfo(roomUserInfoVos).size());
-        } else {
-            roomVo.setTotalLookNum(0);
-            roomVo.setLookNum(0);
-        }
-
+        roomVo.setTotalLookNum(getNum.apply(this::getTotalUserCache, roomVo.getRoomUid()));
+        //在房间观看用户数量
+        roomVo.setLookNum(getNum.apply(this::getOnlineUserCache, roomVo.getRoomUid()));
     }
 
+    /**
+     * 获取房间缓存中的用户数量/观看人数
+     *
+     * <p> func:查询用户数量/观看人数的方法
+     * <p> roomUid :房间uid
+     * <p> return :用户数量/观看人数
+     */
+    private final BiFunction<Function<String, RMap<Integer, ?>>, String, Integer> getNum = (func, roomUid) -> Optional.of(roomUid)
+            .map(func)
+            .filter(RMap::isExists)
+            .map(RMap::size)
+            .orElse(0);
+
     private SysUser getSysUser(Integer userId) {
         return Optional.ofNullable(userId)
                 .map(sysUserFeignService::queryUserById)
@@ -751,7 +932,7 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
     /**
      * 测试
      */
-    public Map<String, Object> test(String roomUid, Integer userId) {
+    public Map<String, Object> test(String roomUid) {
         //test
         Map<String, Object> result = new HashMap<>();
         //点赞数
@@ -763,13 +944,11 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
 
         int totalLook = 0;
         int look = 0;
-        List<RoomUserInfoVo> inRoomUserInfo;
-
+        //正在房间观看的用户数据
+        List<BaseRoomUserVo> inRoomUserInfo = queryRoomOnlineUserInfo(roomUid);
         //累计总观看的用户数量
         List<RoomUserInfoVo> totalUserInfo = queryTotalRoomUserInfo(roomUid);
         if (CollectionUtils.isNotEmpty(totalUserInfo)) {
-            //正在房间观看的用户数据
-            inRoomUserInfo = queryRoomUserInfo(totalUserInfo);
             if (CollectionUtils.isNotEmpty(inRoomUserInfo)) {
                 look = inRoomUserInfo.size();
                 result.put("正在观看的人员信息", inRoomUserInfo);
@@ -781,9 +960,15 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
         } else {
             result.put("总人员数据", "没有人员数据");
         }
+        String userId = "";
+        try {
+            String[] split = roomUid.split("-");
+            userId = split[1];
+        } catch (Exception ignored) {
+        }
 
         //获取主讲人信息
-        RBucket<RoomSpeakerInfo> speakerCache = redissonClient.getBucket(LIVE_SPEAKER_INFO.replace(USER_ID, userId.toString()));
+        RBucket<RoomSpeakerInfo> speakerCache = redissonClient.getBucket(LIVE_SPEAKER_INFO.replace(USER_ID, userId));
         if (speakerCache.isExists()) {
             result.put("主讲人信息", speakerCache.get());
         } else {
@@ -802,37 +987,59 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
         }
         SysUser sysUser = sysUserFeignService.queryUserInfo();
         String baseApiUrl = sysConfigDao.findConfigValue(SysConfigService.BASE_API_URL);
-        StringBuffer pushUrl = new StringBuffer(baseApiUrl).append("/#/liveClassTransfer?roomUid=").append(roomUid);
         sysMessageService.batchSendImGroupMessage(MessageTypeEnum.IM_SHARE_LIVE_URL, sysUser.getId().toString(), null, groupIds.split(","), null,
                 imLiveBroadcastRoomVo.getTenantName(), imLiveBroadcastRoomVo.getRoomTitle(), imLiveBroadcastRoomVo.getSpeakerName(),
                 DateUtil.format(imLiveBroadcastRoomVo.getLiveStartTime(), DateUtil.CHINESE_DATA_FORMAT_1),
-                imLiveBroadcastRoomVo.getLiveRemark(), HttpUtil.getSortUrl(pushUrl.toString()));
+                imLiveBroadcastRoomVo.getLiveRemark(), HttpUtil.getSortUrl(baseApiUrl + "/#/liveClassTransfer?roomUid=" + roomUid));
     }
 
     /**
-     * 查询在观看直播的用户信息
+     * 查询直播间在线的用户
      *
      * @param roomUid 直播间uid
      */
     @Override
-    public List<RoomUserInfoVo> queryRoomUserInfo(String roomUid) {
-        List<RoomUserInfoVo> roomUserInfoVos = queryTotalRoomUserInfo(roomUid);
-        return queryRoomUserInfo(roomUserInfoVos);
+    public List<BaseRoomUserVo> queryRoomLimitOnlineUserInfo(String roomUid) {
+        RMap<Integer, BaseRoomUserVo> onlineUserCache = getOnlineUserCache(roomUid);
+        return onlineUserCache.values().stream()
+                .filter(Objects::nonNull)
+                .limit(200)
+                .collect(Collectors.toList());
     }
 
-    public List<RoomUserInfoVo> queryRoomUserInfo(List<RoomUserInfoVo> totalUserInfo) {
-        return totalUserInfo.stream()
-                .filter(o -> Objects.nonNull(o.getState()) && o.getState() == 0)
+    /**
+     * 查询直播间在线的用户
+     *
+     * @param roomUid 直播间uid
+     */
+    @Override
+    public List<BaseRoomUserVo> queryRoomOnlineUserInfo(String roomUid) {
+        RMap<Integer, BaseRoomUserVo> onlineUserInfo = getOnlineUserCache(roomUid);
+        return onlineUserInfo.values().stream()
+                .filter(Objects::nonNull)
                 .collect(Collectors.toList());
     }
 
-    public List<RoomUserInfoVo> queryTotalRoomUserInfo(String roomUid) {
-        RMap<Integer, RoomUserInfoVo> roomTotalUser = redissonClient.getMap(LIVE_ROOM_TOTAL_USER_LIST.replace(ROOM_UID, roomUid));
+    /**
+     * 查询直播间所有用户信息
+     *
+     * @param roomUid 直播间uid
+     */
+    private List<RoomUserInfoVo> queryTotalRoomUserInfo(String roomUid) {
+        RMap<Integer, RoomUserInfoVo> roomTotalUser = getTotalUserCache(roomUid);
         return roomTotalUser.values().stream()
                 .filter(Objects::nonNull)
                 .collect(Collectors.toList());
     }
 
+    private RMap<Integer, BaseRoomUserVo> getOnlineUserCache(String roomUid) {
+        return redissonClient.getMap(LIVE_ROOM_ONLINE_USER_LIST.replace(ROOM_UID, roomUid));
+    }
+
+    private RMap<Integer, RoomUserInfoVo> getTotalUserCache(String roomUid) {
+        return redissonClient.getMap(LIVE_ROOM_TOTAL_USER_LIST.replace(ROOM_UID, roomUid));
+    }
+
     private RoomUserInfoVo getUserInfo(Integer userId) {
         RoomUserInfoVo userInfo = new RoomUserInfoVo();
         userInfo.setUserId(userId);
@@ -853,15 +1060,89 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
         return userInfo;
     }
 
-    //计算时间差-分钟数不满一分钟为0
-    private int getMinutesBetween(Date startDT, Date endDT) {
-        if (Objects.isNull(startDT) || Objects.isNull(endDT)) {
+    /**
+     * 计算观看时长差-分钟数不满一分钟为0
+     *
+     * @param startDT    开始时间
+     * @param nowMinutes 现在观看时长
+     */
+    private int getLookMinutes(Date startDT, Integer nowMinutes) {
+        return getLookMinutes(startDT, new Date(), nowMinutes);
+    }
+
+    /**
+     * 计算观看时长差-分钟数不满一分钟为0
+     *
+     * @param startDT    开始时间
+     * @param endDT      结束时间
+     * @param nowMinutes 现在观看时长
+     */
+    private int getLookMinutes(Date startDT, Date endDT, Integer nowMinutes) {
+        if (Objects.isNull(startDT)) {
+            return 0;
+        }
+        if (startDT.getTime() > endDT.getTime()) {
             return 0;
         }
         //课程结束时间-课程开始时间
         long durationTime = endDT.getTime() - startDT.getTime();
         //相差多少分钟
-        return new Long(durationTime / 1000 / 60).intValue();
+        int minutesBetween = new Long(durationTime / 1000 / 60).intValue();
+        minutesBetween += Objects.isNull(nowMinutes) ? 0 : nowMinutes;
+        return Math.max(minutesBetween, 0);
+    }
+
+    /**
+     * 判断Integer是否相等-null值不相等
+     *
+     * @param key1 第一个Integer
+     * @param key2 第二个Integer
+     * @return 相等 true 不相等 false
+     */
+    private boolean intEquals(Integer key1, Integer key2) {
+        return Objects.nonNull(key1) && Objects.nonNull(key2) && Objects.equals(key1, key2);
+    }
+
+    /**
+     * 分布式锁
+     *
+     * @param lockName lockKey
+     * @param runnable 任务
+     * @param waitTime 等待抢锁的时间,若该key已被占用则等待抢锁。
+     * @param timeout  超时时间
+     * @param unit     时间单位
+     * @return true 锁成功  false 锁失败
+     */
+    public boolean runIfLockCanGet(final String lockName, Runnable runnable, final long waitTime, final long timeout, TimeUnit unit) {
+        RLock lock = redissonClient.getLock(lockName);
+        if (Objects.isNull(lock)) {
+            log.info("runIfLockCanGet lock is null lockName : {}", lockName);
+            return false;
+        }
+        try {
+            if (lock.tryLock(waitTime, timeout, unit)) {
+                if (Objects.nonNull(runnable)) {
+                    runnable.run();
+                }
+                return true;
+            } else {
+                return false;
+            }
+        } catch (Exception e) {
+            log.error("runIfLockCanGet error lockName : {}", lockName, e);
+            throw new RuntimeException("runIfLockCanGet error lockName :" + lockName, e);
+        } finally {
+            this.unlock(lock);
+        }
+    }
+
+    /**
+     * 解锁
+     */
+    public void unlock(RLock lock) {
+        if (lock.getHoldCount() != 0) {
+            lock.unlock();
+        }
     }
 
     /**
@@ -913,10 +1194,16 @@ public class ImLiveBroadcastRoomServiceImpl extends ServiceImpl<ImLiveBroadcastR
             this.speakerName = speakerName;
         }
 
+        /**
+         * 直播状态 0 直播中 1关闭直播
+         */
         public Integer getState() {
             return state;
         }
 
+        /**
+         * 直播状态 0 直播中 1关闭直播
+         */
         public void setState(Integer state) {
             this.state = state;
         }

+ 4 - 6
mec-biz/src/main/java/com/ym/mec/biz/service/impl/SysMessageServiceImpl.java

@@ -376,7 +376,7 @@ public class SysMessageServiceImpl extends BaseServiceImpl<Long, SysMessage> imp
 			loadEmailInfo(senderTenantId);
 			// debugMode = false;
 		}
-		if(StringUtils.isNotEmpty(jpushType) && jpushType == "STUDENT"){
+		if(StringUtils.isNotEmpty(jpushType) && jpushType.equals("STUDENT")){
 			//如果不是缴费信息
 			if(type != STUDENT_PUSH_VIP_BUY){
 				int hour = DateUtil.getHour(new Date());
@@ -420,8 +420,7 @@ public class SysMessageServiceImpl extends BaseServiceImpl<Long, SysMessage> imp
 		if (triggerTime == null || date.after(triggerTime)) {
 			status = SendStatusEnum.SENDING;
 			try {
-				if (debugMode == true
-						|| messageSenderPluginContext.batchSend(messageSender, messageConfig.getDescription(),
+				if (messageSenderPluginContext.batchSend(messageSender, messageConfig.getDescription(),
 								MessageFormatter.arrayFormat(messageConfig.getContent(), args), tos, url,jpushType,"default",null)) {
 					status = SendStatusEnum.SUCCESSED;
 				} else {
@@ -501,8 +500,7 @@ public class SysMessageServiceImpl extends BaseServiceImpl<Long, SysMessage> imp
 		if (triggerTime == null || date.after(triggerTime)) {
 			status = SendStatusEnum.SENDING;
 			try {
-				if (debugMode == true
-						|| messageSenderPluginContext.batchSend(MessageSender.JIGUANG, messageConfig.getDescription(),
+				if (messageSenderPluginContext.batchSend(MessageSender.JIGUANG, messageConfig.getDescription(),
 								MessageFormatter.arrayFormat(messageConfig.getContent(), args), tos, url,jpushType,sound,channelId)) {
 					status = SendStatusEnum.SUCCESSED;
 				} else {
@@ -540,7 +538,7 @@ public class SysMessageServiceImpl extends BaseServiceImpl<Long, SysMessage> imp
 		if (triggerTime == null || date.after(triggerTime)) {
 			status = SendStatusEnum.SENDING;
 			try {
-				if (debugMode == true || messageSenderPluginContext.send(messageSender, receiver, title, content, url,jpushType,"default",null)) {
+				if (messageSenderPluginContext.send(messageSender, receiver, title, content, url,jpushType,"default",null)) {
 					status = SendStatusEnum.SUCCESSED;
 				} else {
 					status = SendStatusEnum.FAILED;

+ 26 - 0
mec-biz/src/main/java/com/ym/mec/biz/service/impl/SysUserServiceImpl.java

@@ -0,0 +1,26 @@
+package com.ym.mec.biz.service.impl;
+
+import com.ym.mec.auth.api.client.SysUserFeignService;
+import com.ym.mec.auth.api.entity.SysUser;
+import com.ym.mec.biz.handler.SysUserContextHolder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class SysUserServiceImpl {
+
+    @Autowired
+    private SysUserFeignService sysUserFeignService;
+
+    public SysUser getSysUser() {
+        SysUser sysUser = SysUserContextHolder.getSysUser();
+        if(sysUser == null){
+            sysUser = sysUserFeignService.queryUserInfo();
+            if(sysUser == null || sysUser.getId() == null){
+                return null;
+            }
+        }
+        SysUserContextHolder.setSysUser(sysUser);
+        return sysUser;
+    }
+}

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

@@ -945,10 +945,10 @@
 			</if>
 		</where>
 	</sql>
-	
+
 	<select id="queryUserMusicGroupCourseNumByClassTime" resultMap="Mapper" parameterType="map">
 		select cssp.music_group_id_ key_,count(cssp.id_) value_ from course_schedule_student_payment cssp left join course_schedule cs on cssp.course_schedule_id_ = cs.id_
-		where cssp.group_type_ = #{groupType,typeHandler=com.ym.mec.common.dal.CustomEnumTypeHandler} and 
+		where cssp.group_type_ = #{groupType,typeHandler=com.ym.mec.common.dal.CustomEnumTypeHandler} and
 		cssp.music_group_id_ IN
 		<foreach collection="musicGroupIds" separator="," item="musicGroupId" open="(" close=")">
 			#{musicGroupId}

+ 8 - 8
mec-biz/src/main/resources/config/mybatis/FinancialExpenditureMapper.xml

@@ -230,26 +230,26 @@
         <result property="deptId" column="dept_id"/>
     </resultMap>
     <select id="getWorkOrderInfo" resultMap="PWorkOrderInfo">
-        SELECT woi.*,su.mec_user_id FROM oa_pro.p_work_order_info woi
-        LEFT JOIN oa_pro.p_work_order_circulation_history woch ON woi.id = woch.work_order
-        LEFT JOIN oa_pro.p_process_info pi ON pi.id = woi.classify
-        LEFT JOIN oa_pro.sys_user su ON su.user_id = woi.creator
+        SELECT woi.*,su.mec_user_id FROM mec_dev_api.p_work_order_info woi
+        LEFT JOIN mec_dev_api.p_work_order_circulation_history woch ON woi.id = woch.work_order
+        LEFT JOIN mec_dev_api.p_process_info pi ON pi.id = woi.classify
+        LEFT JOIN mec_dev_api.sys_user su ON su.user_id = woi.creator
         WHERE woi.is_end = 1  AND woi.is_denied = 0  AND woi.is_cancel = 0
         AND woch.`status` != 0 AND woi.id = #{workOrderId} AND pi.fee_type = 1 LIMIT 1
     </select>
     <select id="getFormStructure" resultType="java.lang.String">
-        SELECT form_structure FROM oa_pro.p_work_order_tpl_data WHERE work_order = #{workOrderId}
+        SELECT form_structure FROM mec_dev_api.p_work_order_tpl_data WHERE work_order = #{workOrderId}
     </select>
     <select id="getFormData" resultType="java.lang.String">
-        SELECT form_data FROM oa_pro.p_work_order_tpl_data WHERE work_order = #{workOrderId}
+        SELECT form_data FROM mec_dev_api.p_work_order_tpl_data WHERE work_order = #{workOrderId}
     </select>
     <select id="getTplInfo" resultType="java.lang.String">
-        SELECT form_structure FROM oa_pro.p_tpl_info WHERE id = #{tplInfoId}
+        SELECT form_structure FROM mec_dev_api.p_tpl_info WHERE id = #{tplInfoId}
     </select>
     <select id="findByBatchNoAndProcessNo" resultType="integer">
         SELECT id_ FROM financial_expenditure WHERE batch_no_ = #{workOrderId} AND financial_process_no_ = #{workOrderId} LIMIT 1
     </select>
     <select id="getDeptId" resultType="java.lang.Integer">
-        SELECT organ_id FROM oa_pro.sys_dept WHERE dept_id = #{deptId}
+        SELECT organ_id FROM mec_dev_api.sys_dept WHERE dept_id = #{deptId}
     </select>
 </mapper>

+ 16 - 5
mec-biz/src/main/resources/config/mybatis/ImLiveBroadcastRoomMapper.xml

@@ -14,6 +14,7 @@
         <result column="room_config_" jdbcType="VARCHAR" property="roomConfig"/>
         <result column="live_state_" jdbcType="INTEGER" property="liveState"/>
         <result column="room_state_" jdbcType="INTEGER" property="roomState"/>
+        <result column="popularize_" jdbcType="INTEGER" property="popularize"/>
         <result column="created_by_" jdbcType="INTEGER" property="createdBy"/>
         <result column="created_time_" jdbcType="TIMESTAMP" property="createdTime"/>
         <result column="updated_by_" jdbcType="INTEGER" property="updatedBy"/>
@@ -22,19 +23,19 @@
 
     <sql id="Base_Column_List">
         id_
-        , tenant_id_, speaker_id_, room_uid_, room_title_, live_start_time_, live_end_time_, live_remark_, pre_template_, room_config_, live_state_, room_state_, created_by_, created_time_, updated_by_, updated_time_
+        , tenant_id_, speaker_id_, room_uid_, room_title_, live_start_time_, live_end_time_, live_remark_, pre_template_, room_config_, live_state_, room_state_, popularize_, created_by_, created_time_, updated_by_, updated_time_
     </sql>
 
     <insert id="insertBatch" keyColumn="id_" keyProperty="id" useGeneratedKeys="true"
             parameterType="com.ym.mec.biz.dal.entity.ImLiveBroadcastRoom">
         insert into im_live_broadcast_room(tenant_id_, speaker_id_, room_uid_, room_title_, live_start_time_,
-        live_end_time_, live_remark_, pre_template_, room_config_, live_state_, room_state_, created_by_, created_time_,
+        live_end_time_, live_remark_, pre_template_, room_config_, live_state_, room_state_, popularize_, created_by_, created_time_,
         updated_by_, updated_time_)
         values
         <foreach collection="entities" item="entity" separator=",">
             (#{entity.tenantId}, #{entity.speakerId}, #{entity.roomUid}, #{entity.roomTitle}, #{entity.liveStartTime},
             #{entity.liveEndTime}, #{entity.liveRemark}, #{entity.preTemplate}, #{entity.roomConfig},
-            #{entity.liveState}, #{entity.roomState}, #{entity.createdBy}, #{entity.createdTime}, #{entity.updatedBy},
+            #{entity.liveState}, #{entity.roomState}, #{entity.popularize}, #{entity.createdBy}, #{entity.createdTime}, #{entity.updatedBy},
             #{entity.updatedTime})
         </foreach>
     </insert>
@@ -56,7 +57,8 @@
         a.room_state_ AS roomState,
         c.real_name_ AS createdByName,
         a.pre_template_ AS preTemplate,
-        a.room_config_ AS roomConfig
+        a.room_config_ AS roomConfig,
+        a.popularize_ AS popularize
         from im_live_broadcast_room as a
         left join tenant_info AS t on a.tenant_id_ = t.id_
         left join sys_user AS b on a.speaker_id_ = b.id_
@@ -84,9 +86,18 @@
             <if test="param.endTime != null">
                 <![CDATA[ AND a.live_start_time_  <= #{param.endTime} ]]>
             </if>
-
+            <if test="param.popularize != null">
+                and a.popularize_ = #{param.popularize}
+            </if>
         </where>
 
     </select>
 
+    <select id="queryUserPageByTenantId" resultType="map">
+        SELECT id_,username_
+        FROM sys_user
+        WHERE tenant_id_ = 1 and user_type_ = 'STUDENT' and lock_flag_=0 and del_flag_= 0 and is_super_admin_=0
+        and username_ is not null and username_ != ''
+    </select>
+
 </mapper>

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

@@ -195,7 +195,7 @@
 		AND tenant_id_ = #{tenantId}
 		AND (jpush_type_ = #{jpushType} OR jpush_type_ IS NULL)
 		<if test="type != null">
-		and type_ = #{type,typeHandler=com.ym.mec.common.dal.CustomEnumTypeHandler}
+		and (type_ = 3 or type_ = 4)
 		</if>
 		group by group_
 	</select>

+ 20 - 10
mec-client-api/src/main/java/com/ym/mec/im/ImFeignService.java

@@ -144,22 +144,22 @@ public interface ImFeignService {
     Object destroyLiveRoom(@RequestParam("roomId") String roomId);
 
     /**
-    * @description: 录制直播
      * @param roomId
-    * @return void
-    * @author zx
-    * @date 2022/2/25 13:52
-    */
+     * @return void
+     * @description: 录制直播
+     * @author zx
+     * @date 2022/2/25 13:52
+     */
     @PostMapping(value = "/liveRoom/startRecord")
     void startRecord(@RequestParam("roomId") String roomId);
 
     /**
-    * @description: 结束录制直播
      * @param roomId
-    * @return void
-    * @author zx
-    * @date 2022/2/25 13:52
-    */
+     * @return void
+     * @description: 结束录制直播
+     * @author zx
+     * @date 2022/2/25 13:52
+     */
     @PostMapping(value = "/liveRoom/stopRecord")
     void stopRecord(@RequestParam("roomId") String roomId);
 
@@ -169,4 +169,14 @@ public interface ImFeignService {
     @PostMapping(value = "/liveRoom/publishRoomMsg", consumes = MediaType.APPLICATION_JSON_VALUE)
     Object publishRoomMsg(@RequestBody ImRoomMessage message);
 
+    /**
+     * 查询用户是否在聊天室
+     *
+     * @param chatroomId 要查询的聊天室 ID(必传)
+     * @param userId     要查询的用户 ID(必传)
+     * @return true 在聊天室,false 不在聊天室
+     */
+    @PostMapping(value = "/liveRoom/userExistInRoom")
+    boolean userExistInRoom(@RequestParam("chatroomId") String chatroomId, @RequestParam("userId") String userId);
+
 }

+ 10 - 6
mec-client-api/src/main/java/com/ym/mec/im/fallback/ImFeignServiceFallback.java

@@ -1,19 +1,17 @@
 package com.ym.mec.im.fallback;
 
-import java.util.List;
-
 import com.ym.mec.common.entity.*;
-import org.springframework.stereotype.Component;
-
 import com.ym.mec.im.ImFeignService;
 import com.ym.mec.im.entity.GroupModel;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
 
 @Component
 public class ImFeignServiceFallback implements ImFeignService {
     @Override
     public ImResult register(ImUserModel userModel) {
-    	
-    	System.out.println("*******************");
+        System.out.println("*******************");
         return null;
     }
 
@@ -101,4 +99,10 @@ public class ImFeignServiceFallback implements ImFeignService {
     public void stopRecord(String roomId) {
 
     }
+
+    @Override
+    public boolean userExistInRoom(String chatroomId, String userId) {
+        return false;
+    }
+
 }

+ 2 - 1
mec-im/src/main/java/com/ym/config/ResourceServerConfig.java

@@ -14,7 +14,8 @@ public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
                         "/group/join", "/group/create", "/group/quit", "/room/leave", "/room/statusSync",
                         "/room/statusImMsg", "/group/batchDismiss", "/private/send", "/group/send",
                         "/group/dismiss", "/room/statusImMsg", "/history/get", "/user/statusImUser", "/liveRoom/recordSync",
-                        "/liveRoom/publishRoomMsg","/liveRoom/destroy","/liveRoom/create","/liveRoom/startRecord","/liveRoom/stopRecord")
+                        "/liveRoom/publishRoomMsg", "/liveRoom/destroy", "/liveRoom/create", "/liveRoom/startRecord",
+                        "/liveRoom/stopRecord", "/liveRoom/userExistInRoom")
                 .permitAll().anyRequest().authenticated().and().csrf().disable();
     }
 }

+ 7 - 0
mec-im/src/main/java/com/ym/controller/LiveRoomController.java

@@ -62,4 +62,11 @@ public class LiveRoomController {
     public void stopRecord(String roomId) throws Exception {
         liveRoomService.stopRecord(roomId);
     }
+
+    @ApiOperation("查询用户是否在聊天室")
+    @RequestMapping(value = "/userExistInRoom")
+    public boolean userExistInRoom(String chatroomId, String userId) {
+        return liveRoomService.userExistInRoom(chatroomId, userId);
+    }
+
 }

+ 26 - 0
mec-im/src/main/java/com/ym/mec/im/IMHelper.java

@@ -446,4 +446,30 @@ public class IMHelper {
 
         return (IMApiResultInfo) GsonUtil.fromJson(httpHelper.returnResult(conn), IMApiResultInfo.class);
     }
+
+    /**
+     * 查询用户是否在聊天室
+     *
+     * @param chatroomId 要查询的聊天室 ID(必传)
+     * @param userId     要查询的用户 ID(必传)
+     */
+    public IMApiResultInfo isInChartRoom(String chatroomId, String userId) throws Exception {
+        if (chatroomId == null) {
+            throw new BizException("房间Uid不能为空");
+        }
+        if (userId == null) {
+            throw new BizException("用户不能为空");
+        }
+        String body = "&chatroomId=" + URLEncoder.encode(chatroomId, UTF8) +
+                "&userId=" + URLEncoder.encode(userId, UTF8);
+        if (body.indexOf("&") == 0) {
+            body = body.substring(1);
+        }
+
+        HttpURLConnection conn = httpHelper.createIMPostHttpConnection("/chatroom/user/exist.json", "application/x-www-form-urlencoded");
+        httpHelper.setBodyParameter(body, conn);
+
+        return (IMApiResultInfo) GsonUtil.fromJson(httpHelper.returnResult(conn), IMApiResultInfo.class);
+    }
+
 }

+ 2 - 0
mec-im/src/main/java/com/ym/pojo/IMApiResultInfo.java

@@ -11,6 +11,8 @@ public class IMApiResultInfo {
     Integer code;
     // 错误信息。
     String errorMessage;
+    //人员是否存在 true 存在  false 不存在
+    Boolean isInChrm;
 
     public boolean isSuccess() {
         return code == 200;

+ 48 - 19
mec-im/src/main/java/com/ym/service/Impl/LiveRoomServiceImpl.java

@@ -13,7 +13,6 @@ import com.ym.pojo.RecordConfig;
 import com.ym.pojo.RecordNotify;
 import com.ym.service.LiveRoomService;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang3.StringUtils;
 import org.redisson.api.RBucket;
 import org.redisson.api.RedissonClient;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -24,6 +23,7 @@ import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.TimeUnit;
 
 /**
  * @author hgw
@@ -167,10 +167,11 @@ public class LiveRoomServiceImpl implements LiveRoomService {
                     if (Objects.isNull(video)) {
                         return;
                     }
-                    video.setEndTime(new Date());
+                    Date now = new Date();
+                    video.setEndTime(now);
                     video.setType(2);
                     video.setUrl(fileUrl);
-                    video.setCreatedTime(new Date());
+                    video.setCreatedTime(now);
                     imLiveRoomVideoService.updateById(video);
                 } catch (Exception e) {
                     log.error("recordSync >>>>  error : {}", e.getMessage());
@@ -179,32 +180,60 @@ public class LiveRoomServiceImpl implements LiveRoomService {
         }
     }
 
+    /**
+     * 查询用户是否在聊天室
+     *
+     * @param chatroomId 要查询的聊天室 ID(必传)
+     * @param userId     要查询的用户 ID(必传)
+     * @return true 在聊天室,false 不在聊天室
+     * <p>触发融云退出聊天室机制将用户踢出
+     * <p>聊天室中用户在离线 30 秒后有新消息产生时或离线后聊天室中产生 30 条消息时会被自动退出聊天室
+     * <p>此状态需要聊天室中有新消息时才会进行同步
+     */
+    public boolean userExistInRoom(String chatroomId, String userId) {
+        log.info("userExistInRoom chatroomId : {}  userId : {}", chatroomId, userId);
+        IMApiResultInfo resultInfo;
+        try {
+            resultInfo = imHelper.isInChartRoom(chatroomId, userId);
+        } catch (Exception e) {
+            throw new BizException("查询失败" + e.getMessage());
+        }
+        if (!resultInfo.isSuccess()) {
+            log.error("userExistInRoom  chatroomId : {}  userId : {}", chatroomId, userId);
+            throw new BizException("查询失败!");
+        }
+        return resultInfo.isSuccess() && resultInfo.getIsInChrm();
+    }
+
     public String getRoomSessionId(String roomId) {
         RBucket<String> bucket = redissonClient.getBucket("sessionId:" + roomId);
-        String sessionId = bucket.get();
-        if (StringUtils.isNotEmpty(sessionId)) {
-            return sessionId;
+        if (bucket.isExists()) {
+            return bucket.get();
         }
-        JSONObject jsonObject = new JSONObject();
-        jsonObject.put("roomId", roomId);
+        HttpURLConnection conn;
+        JSONObject resultObject;
 
-        HttpURLConnection conn = null;
         try {
+            JSONObject jsonObject = new JSONObject();
+            jsonObject.put("roomId", roomId);
             conn = httpHelper.createIMRtcPostHttpConnection("/rtc/room/query.json", "application/json", null);
             httpHelper.setBodyParameter(jsonObject.toJSONString(), conn);
             String returnResult = httpHelper.returnResult(conn, jsonObject.toJSONString());
-            JSONObject resultObject = JSONObject.parseObject(returnResult);
-            String code = resultObject.get("code").toString();
-            if ("200".equals(code)) {
-                sessionId = resultObject.get("sessionId").toString();
-                bucket.set(sessionId);
-            } else {
-                log.error("获取sessionId失败 returnResult:{}", returnResult);
-                throw new BizException("获取sessionId失败");
-            }
+            resultObject = JSONObject.parseObject(returnResult);
         } catch (Exception e) {
-            e.printStackTrace();
+            throw new BizException("获取sessionId失败", e.getCause());
         }
+
+        String sessionId;
+        if ("200".equals(resultObject.getString("code"))) {
+            sessionId = resultObject.getString("sessionId");
+            bucket.set(sessionId, 1, TimeUnit.HOURS);
+            log.info("getRoomSessionId roomId : {}  sessionId : {}", roomId, sessionId);
+        } else {
+            log.error("获取sessionId失败 roomId : {} returnResult:{}", roomId, resultObject);
+            throw new BizException("获取sessionId失败");
+        }
+
         return sessionId;
     }
 

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

@@ -43,4 +43,6 @@ public interface LiveRoomService {
     */
     void recordSync(RecordNotify recordNotify);
 
+    boolean userExistInRoom(String chatroomId, String userId);
+
 }

+ 57 - 57
mec-im/src/main/resources/logback-spring.xml

@@ -1,62 +1,62 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <configuration scan="true" scanPeriod="10 seconds">
 
-	<property name="LOG_HOME" value="/mdata/logs/im-%d{yyyy-MM-dd_HH}-%i.log" />
-	<property name="CONSOLE_LOG_PATTERN"
-			  value="[%X{username} %X{ip} %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36}] : %msg%n" />
-
-	<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
-		<encoder charset="UTF-8">
-			<pattern>${CONSOLE_LOG_PATTERN}</pattern>
-		</encoder>
-	</appender>
-
-	<appender name="file"
-			  class="ch.qos.logback.core.rolling.RollingFileAppender">
-		<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
-			<FileNamePattern>${LOG_HOME}</FileNamePattern>
-			<MaxHistory>90</MaxHistory>
-			<TimeBasedFileNamingAndTriggeringPolicy
-					class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
-				<MaxFileSize>20MB</MaxFileSize>
-			</TimeBasedFileNamingAndTriggeringPolicy>
-		</rollingPolicy>
-
-		<encoder>
-			<pattern>${CONSOLE_LOG_PATTERN}</pattern>
-		</encoder>
-	</appender>
-
-	<logger name="com.ym.mec" level="INFO" />
-
-	<!--开发环境:打印控制台 -->
-	<springProfile name="dev">
-		<root level="INFO">
-			<appender-ref ref="stdout" />
-			<appender-ref ref="file" />
-		</root>
-	</springProfile>
-
-	<springProfile name="test">
-		<root level="INFO">
-			<appender-ref ref="stdout" />
-			<appender-ref ref="file" />
-		</root>
-	</springProfile>
-
-	<springProfile name="dev_server">
-		<root level="INFO">
-			<appender-ref ref="stdout" />
-			<appender-ref ref="file" />
-		</root>
-	</springProfile>
-
-	<!--生产环境:输出到文件 -->
-	<springProfile name="prod">
-		<root level="INFO">
-			<appender-ref ref="stdout" />
-			<appender-ref ref="file" />
-		</root>
-	</springProfile>
+    <property name="LOG_HOME" value="/mdata/logs/im-%d{yyyy-MM-dd_HH}-%i.log" />
+    <property name="CONSOLE_LOG_PATTERN"
+              value="[%X{username} %X{ip} %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36}] : %msg%n" />
+
+    <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder charset="UTF-8">
+            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
+        </encoder>
+    </appender>
+
+    <appender name="file"
+              class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <FileNamePattern>${LOG_HOME}</FileNamePattern>
+            <MaxHistory>90</MaxHistory>
+            <TimeBasedFileNamingAndTriggeringPolicy
+                    class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                <MaxFileSize>20MB</MaxFileSize>
+            </TimeBasedFileNamingAndTriggeringPolicy>
+        </rollingPolicy>
+
+        <encoder>
+            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
+        </encoder>
+    </appender>
+
+    <logger name="com.ym.mec" level="INFO" />
+
+    <!--开发环境:打印控制台 -->
+    <springProfile name="dev">
+        <root level="INFO">
+            <appender-ref ref="stdout" />
+            <appender-ref ref="file" />
+        </root>
+    </springProfile>
+
+    <springProfile name="test">
+        <root level="INFO">
+            <appender-ref ref="stdout" />
+            <appender-ref ref="file" />
+        </root>
+    </springProfile>
+
+    <springProfile name="dev_server">
+        <root level="INFO">
+            <appender-ref ref="stdout" />
+            <appender-ref ref="file" />
+        </root>
+    </springProfile>
+
+    <!--生产环境:输出到文件 -->
+    <springProfile name="prod">
+        <root level="INFO">
+            <appender-ref ref="stdout" />
+            <appender-ref ref="file" />
+        </root>
+    </springProfile>
 
 </configuration>

+ 38 - 0
mec-student/src/main/java/com/ym/mec/student/controller/ImLiveBroadcastRoomController.java

@@ -0,0 +1,38 @@
+package com.ym.mec.student.controller;
+
+import com.ym.mec.biz.dal.vo.ImLiveBroadcastRoomVo;
+import com.ym.mec.biz.service.ImLiveBroadcastRoomService;
+import com.ym.mec.common.controller.BaseController;
+import com.ym.mec.common.entity.HttpResponseResult;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+
+/**
+ * 直播房间管理表(ImLiveBroadcastRoom)表控制层
+ *
+ * @author hgw
+ * @since 2022-02-17 20:52:04
+ */
+@Api(tags = "直播房间管理表")
+@RestController
+@RequestMapping("/imLiveBroadcastRoom")
+public class ImLiveBroadcastRoomController extends BaseController {
+    /**
+     * 服务对象
+     */
+    @Resource
+    private ImLiveBroadcastRoomService imLiveBroadcastRoomService;
+
+    @ApiOperation("查询该机构目前推广的直播间")
+    @GetMapping(value = "/queryPopularizeRoom")
+    public HttpResponseResult<ImLiveBroadcastRoomVo> queryPopularizeRoom() {
+        return succeed(imLiveBroadcastRoomService.queryPopularizeRoom());
+    }
+
+}
+

+ 30 - 20
mec-web/src/main/java/com/ym/mec/web/controller/ImLiveBroadcastRoomController.java

@@ -2,8 +2,8 @@ package com.ym.mec.web.controller;
 
 
 import com.ym.mec.biz.dal.dto.ImLiveBroadcastRoomDto;
+import com.ym.mec.biz.dal.vo.BaseRoomUserVo;
 import com.ym.mec.biz.dal.vo.ImLiveBroadcastRoomVo;
-import com.ym.mec.biz.dal.vo.RoomUserInfoVo;
 import com.ym.mec.biz.service.ImLiveBroadcastRoomService;
 import com.ym.mec.common.controller.BaseController;
 import com.ym.mec.common.entity.HttpResponseResult;
@@ -40,6 +40,7 @@ public class ImLiveBroadcastRoomController extends BaseController {
             @ApiImplicitParam(name = "liveState", dataType = "Integer", value = "直播状态 0未开始 1开始 2结束"),
             @ApiImplicitParam(name = "startTime", dataType = "String", value = "开始时间"),
             @ApiImplicitParam(name = "endTime", dataType = "String", value = "结束时间"),
+            @ApiImplicitParam(name = "popularize", dataType = "String", value = "结束时间"),
             @ApiImplicitParam(name = "page", dataType = "Integer", value = "页数"),
             @ApiImplicitParam(name = "rows", dataType = "Integer", value = "每页数量"),
     })
@@ -65,14 +66,14 @@ public class ImLiveBroadcastRoomController extends BaseController {
     @ApiOperation("创建直播间")
     @PostMapping("/add")
     @PreAuthorize("@pcs.hasPermissions('imLiveBroadcastRoom/add')")
-    public HttpResponseResult add(@Valid @RequestBody ImLiveBroadcastRoomDto dto) {
+    public HttpResponseResult<Object> add(@Valid @RequestBody ImLiveBroadcastRoomDto dto) {
         imLiveBroadcastRoomService.add(dto);
         return succeed();
     }
 
     @ApiOperation("修改直播间信息-已开播无法修改")
     @PostMapping("/update")
-    public HttpResponseResult update(@Valid @RequestBody ImLiveBroadcastRoomDto dto) {
+    public HttpResponseResult<Object> update(@Valid @RequestBody ImLiveBroadcastRoomDto dto) {
         imLiveBroadcastRoomService.update(dto);
         return succeed();
     }
@@ -85,8 +86,8 @@ public class ImLiveBroadcastRoomController extends BaseController {
      */
     @ApiOperation("修改是否禁言")
     @GetMapping(value = "/whetherChat/{id}")
-    public HttpResponseResult whetherChat(@ApiParam(value = "房间表id", required = true) @PathVariable("id") Integer id,
-                                          @ApiParam(value = "是否允许聊天互动 0允许 1不允许", required = true) Integer whetherChat) {
+    public HttpResponseResult<Object> whetherChat(@ApiParam(value = "房间表id", required = true) @PathVariable("id") Integer id,
+                                                  @ApiParam(value = "是否允许聊天互动 0允许 1不允许", required = true) Integer whetherChat) {
         imLiveBroadcastRoomService.whetherChat(id, whetherChat);
         return succeed();
     }
@@ -98,7 +99,7 @@ public class ImLiveBroadcastRoomController extends BaseController {
      */
     @ApiOperation("关闭直播间")
     @GetMapping(value = "/roomDestroy/{id}")
-    public HttpResponseResult roomDestroy(@ApiParam(value = "房间表id", required = true) @PathVariable("id") Integer id) {
+    public HttpResponseResult<Object> roomDestroy(@ApiParam(value = "房间表id", required = true) @PathVariable("id") Integer id) {
         imLiveBroadcastRoomService.roomDestroy(id);
         return succeed();
     }
@@ -109,22 +110,31 @@ public class ImLiveBroadcastRoomController extends BaseController {
     @ApiOperation("删除直播间信息-已开播无法删除")
     @PostMapping("/delete")
     @PreAuthorize("@pcs.hasPermissions('imLiveBroadcastRoom/delete')")
-    public HttpResponseResult delete(@RequestBody Map<String, Object> param) {
+    public HttpResponseResult<Object> delete(@RequestBody Map<String, Object> param) {
         Integer id = WrapperUtil.toInt(param, "id", "请传入房间id");
         imLiveBroadcastRoomService.delete(id);
         return succeed();
     }
 
+    @ApiOperation("推广直播间-每个机构只能有一个直播间在首页推广")
+    @GetMapping("/opsPopularize")
+    @PreAuthorize("@pcs.hasPermissions('imLiveBroadcastRoom/opsPopularize')")
+    public HttpResponseResult<Object> opsPopularize(@ApiParam(value = "房间id", required = true) Integer id,
+                                                    @ApiParam(value = "是否在首页推广 0否 1是", required = true) Integer popularize) {
+        imLiveBroadcastRoomService.opsPopularize(id, popularize);
+        return succeed();
+    }
+
     @ApiOperation("同步点赞数量")
     @GetMapping("/syncLike")
-    public HttpResponseResult syncLike(@ApiParam(value = "房间uid", required = true) String roomUid,
-                                       @ApiParam(value = "点赞数", required = true) Integer likeNum) {
+    public HttpResponseResult<Object> syncLike(@ApiParam(value = "房间uid", required = true) String roomUid,
+                                               @ApiParam(value = "点赞数", required = true) Integer likeNum) {
         imLiveBroadcastRoomService.syncLike(roomUid, likeNum);
         return succeed();
     }
 
     @PostMapping("/quitRoom")
-    public HttpResponseResult quitRoom(@RequestBody List<ImUserState> userState) {
+    public HttpResponseResult<Object> quitRoom(@RequestBody List<ImUserState> userState) {
         imLiveBroadcastRoomService.opsRoom(userState);
         return succeed();
     }
@@ -137,16 +147,16 @@ public class ImLiveBroadcastRoomController extends BaseController {
 
     @ApiOperation("进入房间")
     @GetMapping("/joinRoom")
-    public HttpResponseResult joinRoom(String roomUid, Integer userId) {
+    public HttpResponseResult<Object> joinRoom(String roomUid, Integer userId) {
         imLiveBroadcastRoomService.joinRoom(roomUid, userId);
         return succeed();
     }
 
     @ApiOperation("开启/关闭直播的录像")
     @GetMapping("/opsLiveVideo")
-    public HttpResponseResult opsLiveVideo(@ApiParam(value = "房间uid", required = true) String roomUid,
-                                           @ApiParam(value = "用户id", required = true) Integer userId,
-                                           @ApiParam(value = "type 1:开始直播-开始录像     2:关闭直播关闭录像", required = true) Integer type) {
+    public HttpResponseResult<Object> opsLiveVideo(@ApiParam(value = "房间uid", required = true) String roomUid,
+                                                   @ApiParam(value = "用户id", required = true) Integer userId,
+                                                   @ApiParam(value = "type 1:开始直播-开始录像     2:关闭直播关闭录像", required = true) Integer type) {
         if (type == 1) {
             imLiveBroadcastRoomService.startLive(roomUid, userId);
         } else if (type == 2) {
@@ -158,8 +168,8 @@ public class ImLiveBroadcastRoomController extends BaseController {
     }
 
     @GetMapping("/test")
-    public Object test(String roomUid, Integer userId) {
-        return imLiveBroadcastRoomService.test(roomUid, userId);
+    public Object test(String roomUid) {
+        return imLiveBroadcastRoomService.test(roomUid);
     }
 
     @GetMapping("/destroyExpiredLiveRoom")
@@ -169,16 +179,16 @@ public class ImLiveBroadcastRoomController extends BaseController {
     }
 
     @GetMapping("/shareGroup")
-    public HttpResponseResult shareGroup(@ApiParam(value = "房间uid", required = true) String roomUid,
-                                         @ApiParam(value = "群编号", required = true) String groupIds) {
+    public HttpResponseResult<Object> shareGroup(@ApiParam(value = "房间uid", required = true) String roomUid,
+                                                 @ApiParam(value = "群编号", required = true) String groupIds) {
         imLiveBroadcastRoomService.shareGroup(roomUid, groupIds);
         return succeed();
     }
 
     @ApiOperation("获取房间人员")
     @GetMapping("/queryRoomUserInfo")
-    public HttpResponseResult<List<RoomUserInfoVo>> queryRoomUserInfo(@ApiParam(value = "房间uid", required = true) String roomUid) {
-        return succeed(imLiveBroadcastRoomService.queryRoomUserInfo(roomUid));
+    public HttpResponseResult<List<BaseRoomUserVo>> queryRoomUserInfo(@ApiParam(value = "房间uid", required = true) String roomUid) {
+        return succeed(imLiveBroadcastRoomService.queryRoomLimitOnlineUserInfo(roomUid));
     }
 
 }