feat(user-creator-chat): WebSocket Redis presence를 추가한다
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
package kr.co.vividnext.sodalive.v2.usercreatorchat.websocket
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import org.springframework.beans.factory.annotation.Qualifier
|
||||
import org.springframework.data.redis.core.StringRedisTemplate
|
||||
import org.springframework.stereotype.Service
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
|
||||
@Service
|
||||
class UserCreatorChatPresenceService(
|
||||
private val stringRedisTemplate: StringRedisTemplate,
|
||||
private val objectMapper: ObjectMapper,
|
||||
@Qualifier("userCreatorChatWebSocketServerId") private val serverId: String
|
||||
) {
|
||||
fun markJoined(roomId: Long, memberId: Long, sessionId: String) {
|
||||
stringRedisTemplate.opsForValue().set(
|
||||
presenceKey(roomId, memberId, sessionId),
|
||||
presenceJson(roomId, memberId, sessionId),
|
||||
PRESENCE_TTL
|
||||
)
|
||||
stringRedisTemplate.opsForSet().add(memberSessionsKey(roomId, memberId), sessionId)
|
||||
stringRedisTemplate.expire(memberSessionsKey(roomId, memberId), PRESENCE_TTL)
|
||||
stringRedisTemplate.opsForSet().add(roomKey(roomId), memberId.toString())
|
||||
stringRedisTemplate.expire(roomKey(roomId), PRESENCE_TTL)
|
||||
}
|
||||
|
||||
fun refresh(roomId: Long, memberId: Long, sessionId: String) {
|
||||
stringRedisTemplate.opsForValue().set(
|
||||
presenceKey(roomId, memberId, sessionId),
|
||||
presenceJson(roomId, memberId, sessionId),
|
||||
PRESENCE_TTL
|
||||
)
|
||||
stringRedisTemplate.expire(memberSessionsKey(roomId, memberId), PRESENCE_TTL)
|
||||
stringRedisTemplate.expire(roomKey(roomId), PRESENCE_TTL)
|
||||
}
|
||||
|
||||
fun markLeft(roomId: Long, memberId: Long, sessionId: String) {
|
||||
stringRedisTemplate.delete(presenceKey(roomId, memberId, sessionId))
|
||||
stringRedisTemplate.opsForSet().remove(memberSessionsKey(roomId, memberId), sessionId)
|
||||
|
||||
removeMemberPresenceIfNoLiveSession(roomId, memberId)
|
||||
}
|
||||
|
||||
fun hasPresence(roomId: Long, memberId: Long): Boolean {
|
||||
return findLiveSessionIds(roomId, memberId).isNotEmpty()
|
||||
}
|
||||
|
||||
private fun removeMemberPresenceIfNoLiveSession(roomId: Long, memberId: Long) {
|
||||
if (findLiveSessionIds(roomId, memberId).isNotEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
stringRedisTemplate.delete(memberSessionsKey(roomId, memberId))
|
||||
stringRedisTemplate.opsForSet().remove(roomKey(roomId), memberId.toString())
|
||||
}
|
||||
|
||||
private fun findLiveSessionIds(roomId: Long, memberId: Long): Set<String> {
|
||||
val sessionIds = stringRedisTemplate.opsForSet().members(memberSessionsKey(roomId, memberId)) ?: emptySet()
|
||||
return sessionIds.filterTo(mutableSetOf()) { sessionId ->
|
||||
val isLive = stringRedisTemplate.hasKey(presenceKey(roomId, memberId, sessionId)) == true
|
||||
if (!isLive) {
|
||||
stringRedisTemplate.opsForSet().remove(memberSessionsKey(roomId, memberId), sessionId)
|
||||
}
|
||||
isLive
|
||||
}
|
||||
}
|
||||
|
||||
private fun presenceJson(roomId: Long, memberId: Long, sessionId: String): String {
|
||||
return objectMapper.writeValueAsString(
|
||||
UserCreatorChatRedisPresence(
|
||||
serverId = serverId,
|
||||
memberId = memberId,
|
||||
roomId = roomId,
|
||||
sessionId = sessionId,
|
||||
lastSeenAt = Instant.now()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val PRESENCE_TTL = Duration.ofSeconds(90)
|
||||
|
||||
fun presenceKey(roomId: Long, memberId: Long, sessionId: String): String {
|
||||
return "v2:user-creator-chat:ws:presence:$roomId:$memberId:$sessionId"
|
||||
}
|
||||
|
||||
fun memberSessionsKey(roomId: Long, memberId: Long): String {
|
||||
return "v2:user-creator-chat:ws:room:$roomId:member:$memberId:sessions"
|
||||
}
|
||||
|
||||
fun roomKey(roomId: Long): String {
|
||||
return "v2:user-creator-chat:ws:room:$roomId"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class UserCreatorChatRedisPresence(
|
||||
val serverId: String,
|
||||
val memberId: Long,
|
||||
val roomId: Long,
|
||||
val sessionId: String,
|
||||
val lastSeenAt: Instant
|
||||
)
|
||||
@@ -0,0 +1,16 @@
|
||||
package kr.co.vividnext.sodalive.v2.usercreatorchat.websocket
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import java.util.UUID
|
||||
|
||||
@Configuration
|
||||
class UserCreatorChatWebSocketServerIdConfig {
|
||||
@Bean
|
||||
fun userCreatorChatWebSocketServerId(
|
||||
@Value("\${user-creator-chat.websocket.server-id:}") configuredServerId: String
|
||||
): String {
|
||||
return configuredServerId.takeIf { it.isNotBlank() } ?: UUID.randomUUID().toString()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user