寻求有关如何设置 Ktor 与 Redis 一起处理 JSON 类型的文本搜索的帮助。
库和示例代码都是为 Java 编写的。
寻求专门针对 JSON 对象进行文本搜索的帮助。
即:
redis.ftSearch(query, searchOptions)
查看 Redis 文档,都是针对 Java 的,没有关于 Kotlin 的。我找不到任何 kotlin 特定的 Redis 库。
我终于找到了几个可用的库。
// build.gradle.kts
dependencies {
// Lettuce for Redis
implementation("io.lettuce:lettuce-core:6.2.6.RELEASE")
implementation("com.redis:lettucemod:3.6.3") // Extension library
}
//RedisSearchCommands.kt
// Define the Redis Search Commands
// Yes, its odd that we have to define the commands this way, but it's how it works.
interface RedisSearchCommands : Commands {
@Command("FT.CREATE")
@CommandNaming(strategy = CommandNaming.Strategy.DOT)
fun ftCreate(index: String, vararg args: String): String
@Command("FT.ADD")
@CommandNaming(strategy = CommandNaming.Strategy.DOT)
fun ftAdd(index: String, docId: String, score: Double, vararg fields: Any): String
@Command("FT.CONFIG SET")
@CommandNaming(strategy = CommandNaming.Strategy.DOT)
fun ftConfigSet(key: String, value: String): String
@Command("FT.CONFIG GET")
@CommandNaming(strategy = CommandNaming.Strategy.DOT)
fun ftConfigGet(key: String): String
@Command("FT.DROPINDEX")
@CommandNaming(strategy = CommandNaming.Strategy.DOT)
fun ftDropindex(index: String): String
}
// 应用程序.kt
@OptIn(InternalSerializationApi::class, ExperimentalLettuceCoroutinesApi::class)
fun Application.module() {
val redisClient: RedisModulesClient = RedisModulesClient.create("redis://localhost:6379")
val redisConnection: StatefulRedisModulesConnection<String, String> = redisClient.connect()
val redisSyncCommands: RedisModulesCommands<String, String> = redisConnection.sync()
val redisCoroutineCommand = redisConnection.coroutines()
val redisReactiveCommand = redisConnection.reactive(
// setup the search commands not included in libraries
val redisSearchCommands = RedisCommandFactory(redisConnection).getCommands(RedisSearchCommands::class.java)
redisSearchCommands.ftConfigSet("MINPREFIX", "1") // allow one character prefix for FT.SEARCH
// TEST REDIS JSON SEARCH
try {
// check if index exists
val result = redisSyncCommand.ftInfo("users_index")
} catch (e: Exception) {
// setup json text search index
val result = redisSyncCommand.ftCreate(
"users_index",
CreateOptions.builder<String, String>()
.prefix("user:")
.on(CreateOptions.DataType.JSON)
.build(),
Field.tag("$.id") // note: TAGs do not separate words/special characters
.`as`("id")
.build(),
Field.tag("$.email")
.`as`("email")
.build(),
Field.text("$.name")
.`as`("name")
.sortable()
.withSuffixTrie() // for improved search (go -> going, goes, gone)
.build()
)
if (result != "OK") {
ktorLogger.error("Error creating index: $result")
}
}
val redisInfo = redisSyncCommand.info("users_index")
println("redisInfo: $redisInfo")
val resultRedisAdd1 = redisSyncCommand.jsonSet(
"user:1",
"$", // path
"""
{
"id": "00000000-0000-0000-0000-000000000001",
"email": "[email protected]",
"name": "Chris"
}
""".trimIndent()
)
println("resultRedisAdd1: $resultRedisAdd1")
val resultRedisAdd2 = redisSyncCommand.jsonSet(
"user:2",
"$",
"""
{
"id": "00000000-0000-0000-0000-000000000002",
"email": "[email protected]",
"name": "Billy"
}
""".trimIndent()
)
println("resultRedisAdd2: $resultRedisAdd2")
val escapedSearchId = "0000-000000000001".escapeRedisSearchSpecialCharacters()
val resultIdSearch = redisSyncCommand.ftSearch(
"users_index",
"@id:{*$escapedSearchId*}" // search for '0000-000000000001' in id
)
println("resultIdSearch: $resultIdSearch")
val resultTagSearch = redisSyncCommand.ftSearch(
"users_index",
"@name:*ch*" // search for 'ch' in name
)
println("resultTagSearch: $resultTagSearch")
val resultTextSearch = redisSyncCommand.ftSearch(
"users_index",
"@name:{*bi*}" // search for 'bi' in name
)
println("resultTextSearch: $resultTextSearch")
@Serializable
data class UserSearchResult(
val email: String,
val name: String,
)
val resultArray = resultTagSearch.map { resultMap ->
val resultValue = resultMap.get("$") as String
jsonConfig.decodeFromString<UserSearchResult>(resultValue)
}
println("resultArray: $resultArray")
routing {
route("/redis") {
get("/get") {
val key = call.request.queryParameters["key"]
key ?: run {
call.respondJson(mapOf("error" to "Missing key"), HttpStatusCode.BadRequest)
return@get
}
val value = redisCoroutineCommand.get(key) ?: run {
call.respondJson(mapOf("error" to "Key not found"), HttpStatusCode.NotFound)
return@get
}
call.respondJson(mapOf("key" to key, "value" to value))
}
get("/set") {
val key = call.request.queryParameters["key"]
val value = call.request.queryParameters["value"]
key ?: run {
call.respondJson(mapOf("error" to "Missing key"), HttpStatusCode.BadRequest)
return@get
}
value ?: run {
call.respondJson(mapOf("error" to "Missing value"), HttpStatusCode.BadRequest)
return@get
}
val result = redisCoroutineCommand.set(key, value) ?: run {
call.respondJson(mapOf("error" to "Failed to set key"), HttpStatusCode.InternalServerError)
return@get
}
call.respondJson(mapOf("success" to result))
}
get("/keys") {
val keys = redisCoroutineCommand.keys("*")
val output: ArrayList<String> = arrayListOf()
keys.collect { key ->
output += key
}
call.respondJson(mapOf("keys" to output.toString()))
}
get("/jsonGet") {
val key = call.request.queryParameters["key"]
key ?: run {
call.respondJson(mapOf("error" to "Missing key"), HttpStatusCode.BadRequest)
return@get
}
val value = redisReactiveCommand.jsonGet("json1", key) ?: run {
call.respondJson(mapOf("error" to "Key not found"), HttpStatusCode.NotFound)
return@get
}
call.respondJson(mapOf("key" to key, "value" to (value.block()?.toString() ?: "null")))
}
get("/jsonSet") {
val key = call.request.queryParameters["key"] ?: run {
call.respondJson(mapOf("error" to "Missing key"), HttpStatusCode.BadRequest)
return@get
}
val value = call.request.queryParameters["value"] ?: run {
call.respondJson(mapOf("error" to "Missing value"), HttpStatusCode.BadRequest)
return@get
}
val result = redisReactiveCommand.jsonSet("json1", key, value) ?: run {
call.respondJson(mapOf("error" to "Failed to set key"), HttpStatusCode.InternalServerError)
return@get
}
call.respondJson(mapOf("success" to Json.encodeToString(result.block())))
}
}
}
fun String.escapeRedisSearchSpecialCharacters(): String {
val escapeChars =
"""
,.<>{}[]"':;!@#$%^&*()-+=~"
""".trimIndent()
var result = this
escapeChars.forEach {
result = result.replace(it.toString(), "\\$it")
}
return result
}