27、Kotlin与后端开发:Ktor框架入门、路由与中间件、数据序列化与反序列化、数据库操作

说实话,很多学Kotlin的朋友都问我一个问题:Kotlin到底能不能做后端?

我的回答很直接——不仅能做,而且做得相当漂亮。Ktor这个框架,就是JetBrains官方为Kotlin量身打造的后端解决方案。它轻量、异步、而且写起来特别有Kotlin的味道。

今天我们就来聊聊Ktor的核心玩法。我会带着你从零搭一个简单的后端服务,把路由、中间件、序列化、数据库操作这几个关键点都过一遍。

本章知识体系

Ktor框架 → 路由配置 → 中间件机制 → 数据序列化 → 数据库操作 → 完整实战

Ktor后端开发核心知识体系 Ktor 框架 路由配置 get / post / put / delete 中间件机制 拦截器 / 管道 / 认证 序列化/反序列化 JSON / kotlinx.serialization 数据库操作 Exposed / 连接池 / CRUD 四者结合,构成完整的Ktor后端开发闭环

一、Ktor框架入门:从Hello World开始

Ktor的启动方式非常简洁。我个人习惯用Gradle来管理依赖,核心就两个东西:ktor-server-core和对应的引擎。

// build.gradle.kts
dependencies {
    implementation("io.ktor:ktor-server-core:2.3.7")
    implementation("io.ktor:ktor-server-netty:2.3.7")
    implementation("io.ktor:ktor-server-content-negotiation:2.3.7")
    implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.7")
    implementation("org.jetbrains.exposed:exposed-core:0.41.1")
    implementation("org.jetbrains.exposed:exposed-dao:0.41.1")
    implementation("org.jetbrains.exposed:exposed-jdbc:0.41.1")
    implementation("com.h2database:h2:2.2.224")
}

启动一个服务,其实就这几行代码:

fun main() {
    embeddedServer(Netty, port = 8080) {
        routing {
            get("/") {
                call.respondText("Hello Ktor!")
            }
        }
    }.start(wait = true)
}

嗯,这里要注意——embeddedServer是内嵌服务器,开发调试非常方便。生产环境你可以换成其他引擎,比如Tomcat或者Jetty。

二、路由配置:把请求分清楚

路由说白了就是URL分发器。你想想看,一个后端服务少说几十个接口,没有清晰的路由管理,代码很快就变成一锅粥。

routing {
    // 用户相关
    route("/user") {
        get("/{id}") {
            val userId = call.parameters["id"]
            call.respondText("查询用户: $userId")
        }
        post("/create") {
            // 创建用户逻辑
        }
    }
    
    // 商品相关
    route("/product") {
        get("/list") {
            call.respondText("商品列表")
        }
    }
}

我在项目中遇到过一个问题——路由层级太深,导致URL又长又难记。后来我建议团队把路由控制在两层以内,超过两层就考虑用子路由模块拆分。

小技巧:可以用route()函数做分组,这样代码结构清晰,也方便后期维护。

三、中间件:请求的守门员

中间件这个概念,说白了就是在请求到达你的业务逻辑之前,或者响应返回给客户端之前,插入一些处理逻辑。

Ktor的中间件机制叫"管道"(Pipeline)。你可以往管道里塞各种拦截器:日志、认证、CORS、请求限流等等。

fun Application.module() {
    // 全局中间件:记录请求日志
    install(CallLogging) {
        level = Level.INFO
    }
    
    // 认证中间件
    install(Authentication) {
        bearer {
            validate { credential ->
                if (credential.token == "my-secret-token") {
                    UserIdPrincipal("admin")
                } else {
                    null
                }
            }
        }
    }
    
    routing {
        authenticate {
            get("/secure") {
                call.respondText("这是安全接口")
            }
        }
    }
}

我曾经踩过一个坑——中间件的安装顺序会影响执行结果。比如CORS中间件如果放在认证后面,跨域请求还没到认证就被拦截了。所以建议把CORS、日志这类通用中间件放在最前面。

注意:中间件的顺序很重要!先安装的先执行。如果你有多个中间件,一定要想清楚它们的执行顺序。

四、数据序列化与反序列化

后端开发免不了要和JSON打交道。Ktor官方推荐用kotlinx.serialization,用起来非常顺手。

// 定义数据类
@Serializable
data class User(
    val id: Int,
    val name: String,
    val email: String
)

// 配置序列化
install(ContentNegotiation) {
    json(Json {
        prettyPrint = true
        isLenient = true
        ignoreUnknownKeys = true
    })
}

// 使用
routing {
    get("/user/json") {
        val user = User(1, "张三", "zhangsan@example.com")
        call.respond(user)  // 自动序列化为JSON
    }
    
    post("/user") {
        val user = call.receive<User>()  // 自动反序列化
        call.respond(mapOf("status" to "ok"))
    }
}

我个人习惯把ignoreUnknownKeys设为true。为什么呢?因为前后端联调时,前端多传一个字段是常有的事,设为true可以避免接口直接挂掉。

五、数据库操作:用Exposed操作数据库

Ktor本身不提供ORM,但和Exposed配合起来,简直天作之合。Exposed是JetBrains出品的Kotlin SQL框架,写起来非常Kotlin风格。

// 定义表结构
object Users : Table() {
    val id = integer("id").autoIncrement()
    val name = varchar("name", 50)
    val email = varchar("email", 100)
    
    override val primaryKey = PrimaryKey(id)
}

// 数据操作
class UserRepository {
    fun getAll(): List<User> = transaction {
        Users.selectAll().map {
            User(
                id = it[Users.id],
                name = it[Users.name],
                email = it[Users.email]
            )
        }
    }
    
    fun create(name: String, email: String): User = transaction {
        val id = Users.insert {
            it[Users.name] = name
            it[Users.email] = email
        } get Users.id
        
        User(id, name, email)
    }
}

这里有个坑——Exposed的transaction是阻塞的。在Ktor这种异步框架里,如果直接在主协程里调用,会阻塞线程。我建议用withContext切换到IO线程池:

suspend fun getAllAsync(): List<User> = withContext(Dispatchers.IO) {
    transaction {
        Users.selectAll().map { /* ... */ }
    }
}

避坑指南:我曾经在生产环境遇到过数据库连接池耗尽的问题。原因是忘了给Exposed配置连接池大小。默认值太小,并发一高就挂了。建议用HikariCP做连接池管理。

六、完整实战:一个简单的用户管理API

把上面这些知识点串起来,就是一个完整的CRUD接口:

fun Application.module() {
    install(ContentNegotiation) { json() }
    install(CallLogging) { level = Level.INFO }
    
    val repo = UserRepository()
    
    routing {
        get("/users") {
            val users = repo.getAllAsync()
            call.respond(users)
        }
        
        get("/users/{id}") {
            val id = call.parameters["id"]?.toIntOrNull()
            if (id == null) {
                call.respondText("参数错误", status = HttpStatusCode.BadRequest)
                return@get
            }
            val user = repo.getById(id)
            if (user != null) call.respond(user)
            else call.respondText("未找到", status = HttpStatusCode.NotFound)
        }
        
        post("/users") {
            val user = call.receive<User>()
            val created = repo.create(user.name, user.email)
            call.respond(HttpStatusCode.Created, created)
        }
    }
}

你看,从框架启动到路由配置,从中间件到序列化,再到数据库操作,整个链路就打通了。Ktor的优势在于——它没有Spring那么重的容器,启动快、资源占用少,特别适合微服务和云原生场景。

当然,Ktor也不是万能的。如果你的项目需要大量第三方集成、或者团队习惯了Spring的生态,那还是用Spring Boot更稳妥。但如果你想要一个轻量、高效、纯Kotlin的后端方案,Ktor绝对值得一试。

我的建议:小项目、API网关、或者作为微服务的一部分,用Ktor非常合适。大而全的企业级应用,还是Spring Boot更成熟。


公众号:蓝海资料掘金营,微信deep3321