|
@@ -1,48 +1,211 @@
|
|
|
package cn.reghao.tnb.message.app.ws.media;
|
|
package cn.reghao.tnb.message.app.ws.media;
|
|
|
|
|
|
|
|
|
|
+import cn.reghao.jutil.jdk.serializer.JdkSerializer;
|
|
|
|
|
+import cn.reghao.jutil.jdk.serializer.JsonConverter;
|
|
|
|
|
+import cn.reghao.tnb.account.api.constant.TokenType;
|
|
|
|
|
+import cn.reghao.tnb.account.api.dto.AuthedAccount;
|
|
|
import cn.reghao.tnb.account.api.iface.AccountQuery;
|
|
import cn.reghao.tnb.account.api.iface.AccountQuery;
|
|
|
import cn.reghao.tnb.message.api.dto.MediaProgress;
|
|
import cn.reghao.tnb.message.api.dto.MediaProgress;
|
|
|
|
|
+import cn.reghao.tnb.message.api.model.EventType;
|
|
|
|
|
+import cn.reghao.tnb.message.api.model.resp.EventMessageResp;
|
|
|
|
|
+import cn.reghao.tnb.message.app.config.SpringLifecycle;
|
|
|
import cn.reghao.tnb.message.app.rabbit.RabbitProducer;
|
|
import cn.reghao.tnb.message.app.rabbit.RabbitProducer;
|
|
|
|
|
+import cn.reghao.tnb.message.app.redis.RedisKeys;
|
|
|
|
|
+import cn.reghao.tnb.message.app.redis.ds.RedisHash;
|
|
|
|
|
+import cn.reghao.tnb.message.app.redis.ds.RedisOps;
|
|
|
|
|
+import cn.reghao.tnb.message.app.ws.chat.msg.EventResp;
|
|
|
|
|
+import cn.reghao.tnb.message.app.ws.config.PingPayload;
|
|
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
import org.apache.dubbo.config.annotation.DubboReference;
|
|
import org.apache.dubbo.config.annotation.DubboReference;
|
|
|
-import org.springframework.stereotype.Service;
|
|
|
|
|
|
|
+import org.springframework.stereotype.Component;
|
|
|
|
|
+import org.springframework.web.socket.BinaryMessage;
|
|
|
|
|
+import org.springframework.web.socket.TextMessage;
|
|
|
|
|
+import org.springframework.web.socket.WebSocketSession;
|
|
|
|
|
|
|
|
|
|
+import java.io.IOException;
|
|
|
import java.util.HashMap;
|
|
import java.util.HashMap;
|
|
|
|
|
+import java.util.HashSet;
|
|
|
import java.util.Map;
|
|
import java.util.Map;
|
|
|
|
|
+import java.util.Set;
|
|
|
|
|
+import java.util.stream.Collectors;
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* @author reghao
|
|
* @author reghao
|
|
|
* @date 2024-12-02 11:36:31
|
|
* @date 2024-12-02 11:36:31
|
|
|
*/
|
|
*/
|
|
|
-@Service
|
|
|
|
|
|
|
+@Slf4j
|
|
|
|
|
+@Component
|
|
|
public class MediaService {
|
|
public class MediaService {
|
|
|
- @DubboReference(check = false)
|
|
|
|
|
|
|
+ @DubboReference(check = false, retries = 0, timeout = 60_000)
|
|
|
private AccountQuery accountQuery;
|
|
private AccountQuery accountQuery;
|
|
|
|
|
|
|
|
- private final Map<String, MediaProgress> map = new HashMap<>();
|
|
|
|
|
- private final MediaConnection mediaConnection;
|
|
|
|
|
private final RabbitProducer rabbitProducer;
|
|
private final RabbitProducer rabbitProducer;
|
|
|
|
|
+ private final RedisHash<Long> redisHash;
|
|
|
|
|
+ private final RedisOps redisOps;
|
|
|
|
|
+ // userId -> session
|
|
|
|
|
+ private final Map<Long, WebSocketSession> userSessions = new HashMap<>();
|
|
|
|
|
+ // sessionId -> userId
|
|
|
|
|
+ private final Map<String, Long> sessionUsers = new HashMap<>();
|
|
|
|
|
+ // sessionId -> mediaProgress
|
|
|
|
|
+ private final Map<String, MediaProgress> map = new HashMap<>();
|
|
|
|
|
+ private final SpringLifecycle springLifecycle;
|
|
|
|
|
|
|
|
- public MediaService(MediaConnection mediaConnection, RabbitProducer rabbitProducer) {
|
|
|
|
|
- this.mediaConnection = mediaConnection;
|
|
|
|
|
|
|
+ public MediaService(RabbitProducer rabbitProducer, RedisHash<Long> redisHash,
|
|
|
|
|
+ RedisOps redisOps, SpringLifecycle springLifecycle) {
|
|
|
this.rabbitProducer = rabbitProducer;
|
|
this.rabbitProducer = rabbitProducer;
|
|
|
|
|
+ this.redisHash = redisHash;
|
|
|
|
|
+ this.redisOps = redisOps;
|
|
|
|
|
+ this.springLifecycle = springLifecycle;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- public void putMediaProgress(String sessionId, MediaProgress mediaProgress) {
|
|
|
|
|
- map.put(sessionId, mediaProgress);
|
|
|
|
|
- if (mediaProgress.getEnded()) {
|
|
|
|
|
- persistProgress(sessionId, mediaProgress);
|
|
|
|
|
|
|
+ public boolean addSession(WebSocketSession webSocketSession) {
|
|
|
|
|
+ String sessionId = webSocketSession.getId();
|
|
|
|
|
+ String userToken = (String) webSocketSession.getAttributes().get("userToken");
|
|
|
|
|
+ String videoId = (String) webSocketSession.getAttributes().get("videoId");
|
|
|
|
|
+ AuthedAccount authedAccount = accountQuery.getAuthedAccount(TokenType.token.getValue(), userToken);
|
|
|
|
|
+ if (authedAccount != null) {
|
|
|
|
|
+ String loginId = authedAccount.getLoginId();
|
|
|
|
|
+ long userId = authedAccount.getUserId();
|
|
|
|
|
+
|
|
|
|
|
+ String nodeId = springLifecycle.getNodeId();
|
|
|
|
|
+ String redisKey = RedisKeys.getVideoUsersKey(videoId, nodeId);
|
|
|
|
|
+ redisHash.hset(redisKey, sessionId, userId);
|
|
|
|
|
+
|
|
|
|
|
+ addUserSession(userId, webSocketSession);
|
|
|
|
|
+ check(videoId, nodeId);
|
|
|
|
|
+ sendViewEvent(videoId);
|
|
|
|
|
+ return true;
|
|
|
}
|
|
}
|
|
|
|
|
+ return false;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- public void removeMediaProgress(String sessionId) {
|
|
|
|
|
|
|
+ private void addUserSession(long userId, WebSocketSession webSocketSession) {
|
|
|
|
|
+ String sessionId = webSocketSession.getId();
|
|
|
|
|
+ userSessions.put(userId, webSocketSession);
|
|
|
|
|
+ sessionUsers.put(sessionId, userId);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private void check(String videoId, String nodeId) {
|
|
|
|
|
+ String keyPattern = RedisKeys.getVideoUsersKey(videoId) + ":*";
|
|
|
|
|
+ Set<String> redisKeys = redisOps.keys(keyPattern);
|
|
|
|
|
+ for (String redisKey : redisKeys) {
|
|
|
|
|
+ if (redisKey.endsWith(nodeId)) {
|
|
|
|
|
+ Map<String, Long> redisMap = redisHash.hgetall(redisKey);
|
|
|
|
|
+ Set<String> redisSessionSet = redisMap.keySet();
|
|
|
|
|
+ Set<String> localSessionSet = sessionUsers.keySet();
|
|
|
|
|
+ boolean ret = redisSessionSet.removeAll(localSessionSet);
|
|
|
|
|
+ if (ret) {
|
|
|
|
|
+ for (String hashKey : redisSessionSet) {
|
|
|
|
|
+ redisHash.delete(redisKey, hashKey);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void removeSession(WebSocketSession webSocketSession) {
|
|
|
|
|
+ String sessionId = webSocketSession.getId();
|
|
|
MediaProgress mediaProgress = map.remove(sessionId);
|
|
MediaProgress mediaProgress = map.remove(sessionId);
|
|
|
if (mediaProgress != null) {
|
|
if (mediaProgress != null) {
|
|
|
persistProgress(sessionId, mediaProgress);
|
|
persistProgress(sessionId, mediaProgress);
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ Long userId = sessionUsers.get(sessionId);
|
|
|
|
|
+ userSessions.remove(userId);
|
|
|
|
|
+ sessionUsers.remove(sessionId);
|
|
|
|
|
+
|
|
|
|
|
+ String videoId = (String) webSocketSession.getAttributes().get("videoId");
|
|
|
|
|
+ String nodeId = springLifecycle.getNodeId();
|
|
|
|
|
+ String redisKey = RedisKeys.getVideoUsersKey(videoId, nodeId);
|
|
|
|
|
+ redisHash.delete(redisKey, sessionId);
|
|
|
|
|
+ sendViewEvent(videoId);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public Long getUserId(String sessionId) {
|
|
|
|
|
+ return sessionUsers.get(sessionId);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void sendPingPayload(WebSocketSession session) throws IOException {
|
|
|
|
|
+ PingPayload pingPayload = new PingPayload(60, 120);
|
|
|
|
|
+ EventResp eventResp = new EventResp();
|
|
|
|
|
+ eventResp.setEvent("connect");
|
|
|
|
|
+ eventResp.setPayload(pingPayload);
|
|
|
|
|
+
|
|
|
|
|
+ TextMessage textMessage = new TextMessage(JsonConverter.objectToJson(eventResp));
|
|
|
|
|
+ session.sendMessage(textMessage);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void sendHeartbeatPong(WebSocketSession session) throws IOException {
|
|
|
|
|
+ EventMessageResp<String> resp = new EventMessageResp<>(EventType.heartbeat, "pong");
|
|
|
|
|
+ TextMessage textMessage = new TextMessage(JsonConverter.objectToJson(resp));
|
|
|
|
|
+ session.sendMessage(textMessage);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void sendViewEvent(String videoId) {
|
|
|
|
|
+ sendViewCount(videoId);
|
|
|
|
|
+
|
|
|
|
|
+ Map<String, Object> wsMessage = new HashMap<>();
|
|
|
|
|
+ wsMessage.put("broadcastBy", springLifecycle.getNodeId());
|
|
|
|
|
+ wsMessage.put("type", "sendViewCount");
|
|
|
|
|
+ wsMessage.put("videoId", videoId);
|
|
|
|
|
+ broadcastCluster(JsonConverter.objectToJson(wsMessage));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void send(String receiverId, Object message) throws IOException {
|
|
|
|
|
+ WebSocketSession session = userSessions.get(receiverId);
|
|
|
|
|
+ if (session != null) {
|
|
|
|
|
+ byte[] bytes = JdkSerializer.serialize(message);
|
|
|
|
|
+ BinaryMessage binaryMessage = new BinaryMessage(bytes);
|
|
|
|
|
+ session.sendMessage(binaryMessage);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void sendViewCount(String videoId) {
|
|
|
|
|
+ String keyPattern = RedisKeys.getVideoUsersKey(videoId) + ":*";
|
|
|
|
|
+ Set<String> redisKeys = redisOps.keys(keyPattern);
|
|
|
|
|
+ Set<Long> userSet = new HashSet<>();
|
|
|
|
|
+ for (String redisKey : redisKeys) {
|
|
|
|
|
+ Map<String, Long> redisMap = redisHash.hgetall(redisKey);
|
|
|
|
|
+ userSet.addAll(new HashSet<>(redisMap.values()));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ int viewCount = userSet.size();
|
|
|
|
|
+ Map<String, Object> message = new HashMap<>();
|
|
|
|
|
+ message.put("viewCount", viewCount);
|
|
|
|
|
+ userSet.forEach(userId -> {
|
|
|
|
|
+ WebSocketSession session = userSessions.get(userId);
|
|
|
|
|
+ if (session != null) {
|
|
|
|
|
+ String json = JsonConverter.objectToJson(message);
|
|
|
|
|
+ TextMessage textMessage = new TextMessage(json);
|
|
|
|
|
+ try {
|
|
|
|
|
+ session.sendMessage(textMessage);
|
|
|
|
|
+ } catch (IOException e) {
|
|
|
|
|
+ e.printStackTrace();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 集群中广播
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param
|
|
|
|
|
+ * @return
|
|
|
|
|
+ * @date 2025-10-11 20:10:127
|
|
|
|
|
+ */
|
|
|
|
|
+ public void broadcastCluster(String payload) {
|
|
|
|
|
+ rabbitProducer.broadcastWsMessage(payload);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public void putMediaProgress(String sessionId, MediaProgress mediaProgress) {
|
|
|
|
|
+ map.put(sessionId, mediaProgress);
|
|
|
|
|
+ if (mediaProgress.getEnded()) {
|
|
|
|
|
+ persistProgress(sessionId, mediaProgress);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public void persistProgress(String sessionId, MediaProgress mediaProgress) {
|
|
public void persistProgress(String sessionId, MediaProgress mediaProgress) {
|
|
|
- Long userId = mediaConnection.getUserId(sessionId);
|
|
|
|
|
|
|
+ Long userId = getUserId(sessionId);
|
|
|
if (mediaProgress != null) {
|
|
if (mediaProgress != null) {
|
|
|
mediaProgress.setUserId(userId);
|
|
mediaProgress.setUserId(userId);
|
|
|
rabbitProducer.sendMediaProgress(mediaProgress);
|
|
rabbitProducer.sendMediaProgress(mediaProgress);
|