feat(redis): migrate from Redisson to Quarkus Redis client and update configuration
This commit is contained in:
@@ -54,7 +54,7 @@ dependencies {
|
||||
implementation("io.quarkus:quarkus-config-yaml")
|
||||
implementation("io.quarkus:quarkus-smallrye-health")
|
||||
implementation("com.fasterxml.jackson.module:jackson-module-scala_3:${versions["JACKSON_SCALA"]!!}")
|
||||
implementation("org.redisson:redisson:${versions["REDISSON"]!!}")
|
||||
implementation("io.quarkus:quarkus-redis-client")
|
||||
|
||||
testImplementation(platform("org.junit:junit-bom:${versions["JUNIT_BOM"]!!}"))
|
||||
testImplementation("org.junit.jupiter:junit-jupiter")
|
||||
|
||||
@@ -3,6 +3,8 @@ quarkus:
|
||||
port: 8084
|
||||
application:
|
||||
name: nowchess-ws
|
||||
redis:
|
||||
hosts: redis://${REDIS_HOST:localhost}:${REDIS_PORT:6379}
|
||||
swagger-ui:
|
||||
always-include: true
|
||||
path: /swagger-ui
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
package de.nowchess.ws.config
|
||||
|
||||
import de.nowchess.ws.resource.ConnectionMeta
|
||||
import io.quarkus.runtime.annotations.RegisterForReflection
|
||||
|
||||
@RegisterForReflection(
|
||||
targets = Array(
|
||||
classOf[ConnectionMeta],
|
||||
),
|
||||
targets = Array(),
|
||||
)
|
||||
class NativeReflectionConfig
|
||||
|
||||
|
||||
@@ -7,12 +7,6 @@ import scala.compiletime.uninitialized
|
||||
@ApplicationScoped
|
||||
class RedisConfig:
|
||||
// scalafix:off DisableSyntax.var
|
||||
@ConfigProperty(name = "nowchess.redis.host", defaultValue = "localhost")
|
||||
var host: String = uninitialized
|
||||
|
||||
@ConfigProperty(name = "nowchess.redis.port", defaultValue = "6379")
|
||||
var port: Int = uninitialized
|
||||
|
||||
@ConfigProperty(name = "nowchess.redis.prefix", defaultValue = "nowchess")
|
||||
var prefix: String = uninitialized
|
||||
// scalafix:on DisableSyntax.var
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
package de.nowchess.ws.config
|
||||
|
||||
import jakarta.annotation.PreDestroy
|
||||
import jakarta.enterprise.context.ApplicationScoped
|
||||
import jakarta.enterprise.inject.Produces
|
||||
import jakarta.inject.Inject
|
||||
import org.redisson.Redisson
|
||||
import org.redisson.api.RedissonClient
|
||||
import org.redisson.config.Config
|
||||
import scala.compiletime.uninitialized
|
||||
|
||||
@ApplicationScoped
|
||||
class RedissonProducer:
|
||||
|
||||
// scalafix:off DisableSyntax.var
|
||||
@Inject
|
||||
var redisConfig: RedisConfig = uninitialized
|
||||
|
||||
private var clientOpt: Option[RedissonClient] = None
|
||||
// scalafix:on DisableSyntax.var
|
||||
|
||||
@Produces
|
||||
@ApplicationScoped
|
||||
def produceRedissonClient(): RedissonClient =
|
||||
val config = new Config()
|
||||
config.useSingleServer().setAddress(s"redis://${redisConfig.host}:${redisConfig.port}")
|
||||
config.useSingleServer().setConnectionMinimumIdleSize(1)
|
||||
config.useSingleServer().setConnectTimeout(500)
|
||||
val client = Redisson.create(config)
|
||||
clientOpt = Some(client)
|
||||
client
|
||||
|
||||
@PreDestroy
|
||||
def shutdown(): Unit =
|
||||
clientOpt.foreach(_.shutdown())
|
||||
@@ -1,7 +1,9 @@
|
||||
package de.nowchess.ws.resource
|
||||
|
||||
import io.quarkus.redis.datasource.pubsub.PubSubCommands
|
||||
|
||||
final case class ConnectionMeta(
|
||||
gameId: String,
|
||||
listenerId: Int,
|
||||
subscriber: PubSubCommands.RedisSubscriber,
|
||||
playerId: Option[String],
|
||||
)
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
package de.nowchess.ws.resource
|
||||
|
||||
import de.nowchess.ws.config.RedisConfig
|
||||
import io.quarkus.redis.datasource.RedisDataSource
|
||||
import io.quarkus.redis.datasource.pubsub.PubSubCommands
|
||||
import io.quarkus.websockets.next.*
|
||||
import io.smallrye.jwt.auth.principal.JWTParser
|
||||
import jakarta.inject.Inject
|
||||
import org.redisson.api.listener.MessageListener
|
||||
import org.redisson.api.RedissonClient
|
||||
|
||||
import scala.compiletime.uninitialized
|
||||
import scala.util.Try
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.function.Consumer
|
||||
|
||||
@WebSocket(path = "/api/board/game/{gameId}/ws")
|
||||
class GameWebSocketResource:
|
||||
|
||||
// scalafix:off DisableSyntax.var
|
||||
@Inject
|
||||
var redisson: RedissonClient = uninitialized
|
||||
var redis: RedisDataSource = uninitialized
|
||||
|
||||
@Inject
|
||||
var redisConfig: RedisConfig = uninitialized
|
||||
@@ -40,18 +40,13 @@ class GameWebSocketResource:
|
||||
.filter(_.nonEmpty)
|
||||
.flatMap(token => Try(jwtParser.parse(token)).toOption)
|
||||
.map(_.getSubject)
|
||||
val topic = redisson.getTopic(s2cTopic(gameId))
|
||||
val listenerId = topic.addListener(
|
||||
classOf[String],
|
||||
new MessageListener[String]:
|
||||
def onMessage(channel: CharSequence, msg: String): Unit =
|
||||
connection.sendText(msg).subscribe().`with`(_ => (), _ => ()),
|
||||
)
|
||||
connections.put(connection.id(), ConnectionMeta(gameId, listenerId, playerId))
|
||||
val handler: Consumer[String] = msg => connection.sendText(msg).subscribe().`with`(_ => (), _ => ())
|
||||
val subscriber = redis.pubsub(classOf[String]).subscribe(s2cTopic(gameId), handler)
|
||||
connections.put(connection.id(), ConnectionMeta(gameId, subscriber, playerId))
|
||||
val connectedMsg = playerId match
|
||||
case Some(pid) => s"""{"type":"CONNECTED","gameId":"$gameId","playerId":"$pid"}"""
|
||||
case None => s"""{"type":"CONNECTED","gameId":"$gameId"}"""
|
||||
redisson.getTopic(c2sTopic(gameId)).publish(connectedMsg)
|
||||
redis.pubsub(classOf[String]).publish(c2sTopic(gameId), connectedMsg)
|
||||
|
||||
@OnTextMessage
|
||||
def onTextMessage(connection: WebSocketConnection, message: String): Unit =
|
||||
@@ -59,13 +54,13 @@ class GameWebSocketResource:
|
||||
val enriched = meta.playerId match
|
||||
case Some(pid) => injectPlayerId(message, pid)
|
||||
case None => message
|
||||
redisson.getTopic(c2sTopic(meta.gameId)).publish(enriched)
|
||||
redis.pubsub(classOf[String]).publish(c2sTopic(meta.gameId), enriched)
|
||||
}
|
||||
|
||||
@OnClose
|
||||
def onClose(connection: WebSocketConnection): Unit =
|
||||
Option(connections.remove(connection.id())).foreach { meta =>
|
||||
redisson.getTopic(s2cTopic(meta.gameId)).removeListener(meta.listenerId)
|
||||
meta.subscriber.unsubscribe(s2cTopic(meta.gameId))
|
||||
}
|
||||
|
||||
private def injectPlayerId(msg: String, pid: String): String =
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
package de.nowchess.ws.resource
|
||||
|
||||
import de.nowchess.ws.config.RedisConfig
|
||||
import io.quarkus.redis.datasource.RedisDataSource
|
||||
import io.quarkus.redis.datasource.pubsub.PubSubCommands
|
||||
import io.quarkus.websockets.next.*
|
||||
import io.smallrye.jwt.auth.principal.JWTParser
|
||||
import jakarta.inject.Inject
|
||||
import org.redisson.api.listener.MessageListener
|
||||
import org.redisson.api.RedissonClient
|
||||
|
||||
import scala.compiletime.uninitialized
|
||||
import scala.util.Try
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.function.Consumer
|
||||
|
||||
@WebSocket(path = "/api/user/ws")
|
||||
class UserWebSocketResource:
|
||||
|
||||
// scalafix:off DisableSyntax.var
|
||||
@Inject
|
||||
var redisson: RedissonClient = uninitialized
|
||||
var redis: RedisDataSource = uninitialized
|
||||
|
||||
@Inject
|
||||
var redisConfig: RedisConfig = uninitialized
|
||||
@@ -25,7 +25,7 @@ class UserWebSocketResource:
|
||||
var jwtParser: JWTParser = uninitialized
|
||||
// scalafix:on DisableSyntax.var
|
||||
|
||||
private val connections = new ConcurrentHashMap[String, (String, Int)]()
|
||||
private val connections = new ConcurrentHashMap[String, (String, PubSubCommands.RedisSubscriber)]()
|
||||
|
||||
private def userTopic(userId: String): String =
|
||||
s"${redisConfig.prefix}:user:$userId:events"
|
||||
@@ -40,18 +40,14 @@ class UserWebSocketResource:
|
||||
userIdOpt match
|
||||
case None => connection.close().subscribe().`with`(_ => (), _ => ())
|
||||
case Some(userId) =>
|
||||
val listenerId = redisson.getTopic(userTopic(userId)).addListener(
|
||||
classOf[String],
|
||||
new MessageListener[String]:
|
||||
def onMessage(channel: CharSequence, msg: String): Unit =
|
||||
connection.sendText(msg).subscribe().`with`(_ => (), _ => ()),
|
||||
)
|
||||
connections.put(connection.id(), (userId, listenerId))
|
||||
val handler: Consumer[String] = msg => connection.sendText(msg).subscribe().`with`(_ => (), _ => ())
|
||||
val subscriber = redis.pubsub(classOf[String]).subscribe(userTopic(userId), handler)
|
||||
connections.put(connection.id(), (userId, subscriber))
|
||||
val connectedMsg = s"""{"type":"CONNECTED","userId":"$userId"}"""
|
||||
connection.sendText(connectedMsg).subscribe().`with`(_ => (), _ => ())
|
||||
|
||||
@OnClose
|
||||
def onClose(connection: WebSocketConnection): Unit =
|
||||
Option(connections.remove(connection.id())).foreach { (userId, listenerId) =>
|
||||
redisson.getTopic(userTopic(userId)).removeListener(listenerId)
|
||||
Option(connections.remove(connection.id())).foreach { (userId, subscriber) =>
|
||||
subscriber.unsubscribe(userTopic(userId))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user