如何使用 Redis 和 Ktor 进行 JSON 数据类型的文本搜索?

问题描述 投票:0回答:1

寻求有关如何设置 Ktor 与 Redis 一起处理 JSON 类型的文本搜索的帮助。

库和示例代码都是为 Java 编写的。

寻求专门针对 JSON 对象进行文本搜索的帮助。

即:

redis.ftSearch(query, searchOptions)

查看 Redis 文档,都是针对 Java 的,没有关于 Kotlin 的。我找不到任何 kotlin 特定的 Redis 库。

json kotlin search redis ktor
1个回答
0
投票

我终于找到了几个可用的库。

// 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
}
© www.soinside.com 2019 - 2024. All rights reserved.