You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Kotlin-Exposed中实现PostgreSQL全文搜索并规避SQL注入?

在Kotlin-Exposed中安全实现PostgreSQL全文搜索

1. 自定义Exposed函数封装全文搜索操作

Exposed默认没有内置to_tsvector和to_tsquery的API,你可以通过扩展CustomFunction来安全封装这两个函数,参数会自动做SQL注入防护:

import org.jetbrains.exposed.sql.CustomFunction
import org.jetbrains.exposed.sql.Expression
import org.jetbrains.exposed.sql.QueryParameter
import org.jetbrains.exposed.sql.StringColumnType
import org.jetbrains.exposed.sql.BooleanColumnType
import org.jetbrains.exposed.sql.VarCharColumnType

// 封装to_tsvector函数
class ToTsVector(
    config: Expression<String>,
    text: Expression<String>
) : CustomFunction<Boolean>(
    "to_tsvector",
    BooleanColumnType(),
    config,
    text
)

// 简化调用的扩展函数
fun Expression<String>.toTsVector(config: String = "english") = 
    ToTsVector(QueryParameter(config, StringColumnType()), this)

// 封装to_tsquery函数
class ToTsQuery(
    config: Expression<String>,
    query: Expression<String>
) : CustomFunction<String>(
    "to_tsquery",
    VarCharColumnType(),
    config,
    query
)

// 简化调用的扩展函数
fun String.toTsQuery(config: String = "english") = 
    ToTsQuery(QueryParameter(config, StringColumnType()), QueryParameter(this, StringColumnType()))

2. 使用封装函数编写安全查询

有了上面的扩展,就可以像使用Exposed内置API一样编写全文搜索查询,完全避免SQL注入:

import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction

// 示例表定义
object Articles : Table() {
    val id = integer("id").autoIncrement()
    val title = varchar("title", 255)
    val content = text("content")
}

// 映射用的数据类
data class Article(val id: Int, val title: String, val content: String)

// 全文搜索示例
fun searchArticles(query: String): List<Article> {
    return transaction {
        Articles.select {
            (Articles.content.toTsVector() match query.toTsQuery()) or 
            (Articles.title.toTsVector() match query.toTsQuery())
        }.map {
            Article(it[Articles.id], it[Articles.title], it[Articles.content])
        }
    }
}

3. (可选)创建全文索引优化性能

为了提升搜索速度,建议给目标字段创建GIN索引,用Exposed的SchemaUtils可以安全执行:

transaction {
    SchemaUtils.createIndex(
        indexName = "articles_content_tsv_idx",
        table = Articles,
        columns = listOf(
            CustomFunction<Any>(
                "to_tsvector",
                StringColumnType(),
                QueryParameter("english", StringColumnType()),
                Articles.content
            )
        ),
        indexType = "GIN"
    )
}

4. 原生SQL的安全写法

如果必须使用原生SQL,一定要用参数绑定,绝对不要直接拼接用户输入的字符串:

fun searchArticlesNative(query: String): List<Article> {
    return transaction {
        val result = exec(
            "SELECT * FROM articles WHERE to_tsvector('english', content) @@ to_tsquery('english', ?)",
            listOf(query)
        ) { rs ->
            mutableListOf<Article>().apply {
                while (rs.next()) {
                    add(
                        Article(
                            rs.getInt("id"),
                            rs.getString("title"),
                            rs.getString("content")
                        )
                    )
                }
            }
        }
        result ?: emptyList()
    }
}

内容的提问来源于stack exchange,提问作者Anandakrishnan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 03:45:42