Overview
Ktor 是一个基于 Kotlin 的异步 Web框架,旨在构建现代 Web 应用程序和 API。它为使用Kotlin协程进行异步编程提供了强大的支持。
在本教程中,您将学习;了解如何使用 Ktor、 Kotlin驾驶员和MongoDB Atlas设立可运行的API 。
Tutorial
验证先决条件
在开始本教程之前,请确保您具备以下条件:
具有集群的MongoDB Atlas帐户。要查看说明,请参阅入门指南。
设置 Ktor 项目
使用 Ktor 项目生成器创建新的 Ktor项目。使用文本输入字段将项目工件命名为 com.mongodb.fitness-tracker。单击Configure 按钮并指定以下选项:
构建系统:Gradle Kotlin
引擎:Tomcat
配置:HOCON 文件
使用 New Ktor Project 页面上的 Plugins 菜单,将以下插件添加到您的项目中:
内容协商:在客户端和服务器之间协商媒体类型
GSON:提供序列化和反序列化支持
路由:管理传入请求
Swagger:生成API文档
单击 Generate Project 按钮,下载包含新 Ktor项目的 ZIP文件。
解压缩下载的文件并打开项目目录根目录中的 build.gradle.kts文件。将以下插件添加到 plugins区块:
plugins { kotlin("jvm") version "1.9.22" id("io.ktor.plugin") version "2.3.7" id("org.jetbrains.kotlin.plugin.serialization") version "1.9.22" }
将以下依赖项添加到同一文件中的 dependencies区块:
dependencies { implementation("io.ktor:ktor-server-core-jvm") implementation("io.ktor:ktor-server-swagger-jvm") implementation("io.ktor:ktor-server-content-negotiation-jvm") implementation("io.ktor:ktor-serialization-gson-jvm") implementation("io.ktor:ktor-server-tomcat-jvm") implementation("ch.qos.logback:logback-classic:$logback_version") implementation("io.ktor:ktor-server-config-yaml:2.3.8") // MongoDB implementation("org.mongodb:mongodb-driver-kotlin-coroutine:5.6.4") // Koin dependency injection implementation("io.insert-koin:koin-ktor:3.5.3") implementation("io.insert-koin:koin-logger-slf4j:3.5.3") // Client implementation("io.ktor:ktor-client-core:$ktor_version") implementation("io.ktor:ktor-client-cio:$ktor_version") }
实施增删改查操作
在 src/main/kotlin目录下创建以下三个包:
applicationdomaininfrastructure
在 domain包内,创建一个名为 entity 的新子包,然后在 entity 中创建一个名为 Fitness.kt 的文件。将以下代码复制到该文件中:
package com.mongodb.domain.entity import com.mongodb.application.response.FitnessResponse import org.bson.codecs.pojo.annotations.BsonId import org.bson.types.ObjectId data class Fitness( val id: ObjectId, val exerciseType: String, val notes: String, val details: FitnessDetails ){ fun toResponse() = FitnessResponse( id = id.toString(), exerciseType = exerciseType, notes = notes, details = details ) } data class FitnessDetails( val durationMinutes: Int, val distance: Double, val caloriesBurned: Int )
上述代码中的 toResponse() 方法会抛出错误,因为您尚未创建 FitnessResponse 类。在 application/request 包下创建 request 和 response 包,并在相应包中创建两个名为 FitnessRequest.kt 和 FitnessResponse.kt 的文件。
将以下代码复制到 FitnessRequest.kt文件中:
package com.mongodb.application.response import com.mongodb.domain.entity.FitnessDetails data class FitnessResponse( val id: String, val exerciseType: String, val notes: String, val details: FitnessDetails )
将以下代码复制到 FitnessResponse.kt文件中:
package com.mongodb.application.response import com.mongodb.domain.entity.FitnessDetails data class FitnessResponse( val id: String, val exerciseType: String, val notes: String, val details: FitnessDetails )
在 domain包下创建 ports包,并在 ports包中创建名为 FitnessRepository.kt 的文件。该文件代表将与数据库通信的接口。将以下代码复制到 FitnessRepository.kt文件中:
package com.mongodb.domain.ports import com.mongodb.domain.entity.Fitness import org.bson.BsonValue import org.bson.types.ObjectId interface FitnessRepository { suspend fun insertOne(fitness: Fitness): BsonValue? suspend fun deleteById(objectId: ObjectId): Long suspend fun findById(objectId: ObjectId): Fitness? suspend fun updateOne(objectId: ObjectId, fitness: Fitness): Long }
在 infrastructure包下创建 repository包,并在 repository包中创建名为 FitnessRepositoryImpl.kt 的文件。此文件实现接口的方法。将以下代码复制到 FitnessRepositoryImpl.kt文件中:
package com.mongodb.infrastructure.repository import com.mongodb.MongoException import com.mongodb.client.model.Filters import com.mongodb.client.model.UpdateOptions import com.mongodb.client.model.Updates import com.mongodb.domain.entity.Fitness import com.mongodb.domain.ports.FitnessRepository import com.mongodb.kotlin.client.coroutine.MongoDatabase import kotlinx.coroutines.flow.firstOrNull import org.bson.BsonValue import org.bson.types.ObjectId class FitnessRepositoryImpl( private val mongoDatabase: MongoDatabase ) : FitnessRepository { companion object { const val FITNESS_COLLECTION = "fitness" } override suspend fun insertOne(fitness: Fitness): BsonValue? { try { val result = mongoDatabase.getCollection<Fitness>(FITNESS_COLLECTION).insertOne( fitness ) return result.insertedId } catch (e: MongoException) { System.err.println("Unable to insert due to an error: $e") } return null } override suspend fun deleteById(objectId: ObjectId): Long { try { val result = mongoDatabase.getCollection<Fitness>(FITNESS_COLLECTION).deleteOne(Filters.eq("_id", objectId)) return result.deletedCount } catch (e: MongoException) { System.err.println("Unable to delete due to an error: $e") } return 0 } override suspend fun findById(objectId: ObjectId): Fitness? = mongoDatabase.getCollection<Fitness>(FITNESS_COLLECTION).withDocumentClass<Fitness>() .find(Filters.eq("_id", objectId)) .firstOrNull() override suspend fun updateOne(objectId: ObjectId, fitness: Fitness): Long { try { val query = Filters.eq("_id", objectId) val updates = Updates.combine( Updates.set(Fitness::exerciseType.name, fitness.exerciseType), Updates.set(Fitness::notes.name, fitness.notes), Updates.set(Fitness::details.name, fitness.details) ) val options = UpdateOptions().upsert(true) val result = mongoDatabase.getCollection<Fitness>(FITNESS_COLLECTION) .updateOne(query, updates, options) return result.modifiedCount } catch (e: MongoException) { System.err.println("Unable to update due to an error: $e") } return 0 } }
配置插件
将以下代码粘贴到 Application.kt 文件中。 此代码执行以下操作:
设置 ContentNegotiation 插件以使用 Gson 格式化程序处理JSON序列化和反序列化
使用 Koin 启用依赖项注入
配置 Swagger API路由,该路由可在
/swagger端点使用
package com.mongodb import com.mongodb.application.routes.fitnessRoutes import com.mongodb.domain.ports.FitnessRepository import com.mongodb.infrastructure.repository.FitnessRepositoryImpl import com.mongodb.kotlin.client.coroutine.MongoClient import io.ktor.serialization.gson.gson import io.ktor.server.application.Application import io.ktor.server.application.install import io.ktor.server.plugins.contentnegotiation.ContentNegotiation import io.ktor.server.plugins.swagger.swaggerUI import io.ktor.server.routing.routing import io.ktor.server.tomcat.EngineMain import org.koin.dsl.module import org.koin.ktor.plugin.Koin import org.koin.logger.slf4jLogger fun main(args: Array<String>): Unit = EngineMain.main(args) fun Application.module() { install(ContentNegotiation) { gson { } } install(Koin) { slf4jLogger() modules(module { single { MongoClient.create( environment.config.propertyOrNull("ktor.mongo.uri")?.getString() ?: throw RuntimeException("Failed to access MongoDB URI.") ) } single { get<MongoClient>().getDatabase(environment.config.property("ktor.mongo.database").getString()) } }, module { single<FitnessRepository> { FitnessRepositoryImpl(get()) } }) } routing { swaggerUI(path = "swagger-ui", swaggerFile = "openapi/documentation.yaml") { version = "4.15.5" } fitnessRoutes() } }
打开 application.conf文件并粘贴以下内容:
ktor { deployment { port = 8080 } application { modules = [ com.mongodb.ApplicationKt.module ] } mongo { uri = ${?MONGO_URI} database = ${?MONGO_DATABASE} } }
打开 documentation.yaml文件并粘贴以下内容:
openapi: 3.0.0 info: title: Fitness API version: 1.0.0 description: | This Swagger documentation file outlines the API specifications for a Fitness Tracker application built with Ktor and MongoDB. The API allows users to manage fitness records including creating new records, updating and deleting records by ID. The API uses the Fitness and FitnessDetails data classes to structure the fitness-related information. paths: /fitness: post: summary: Create a new fitness record requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/FitnessRequest' responses: '201': description: Fitness created successfully '400': description: Bad request /fitness/{id}: get: summary: Retrieve fitness record by ID parameters: - name: id in: path required: true schema: type: string responses: '200': description: Successful response content: application/json: example: {} '404': description: Fitness not found delete: summary: Delete fitness record by ID parameters: - name: id in: path required: true schema: type: string responses: '200': description: Fitness deleted successfully '400': description: Bad request '404': description: Fitness not found patch: summary: Update fitness record by ID parameters: - name: id in: path required: true schema: type: string requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/FitnessRequest' responses: '200': description: Fitness updated successfully '400': description: Bad request '404': description: Fitness not found components: schemas: Fitness: type: object properties: id: type: string format: uuid exerciseType: type: string notes: type: string details: $ref: '#/components/schemas/FitnessDetails' required: - id - notes - details FitnessDetails: type: object properties: durationMinutes: type: integer format: int32 distance: type: number format: double caloriesBurned: type: integer format: int32 required: - durationMinutes - distance - caloriesBurned FitnessRequest: type: object properties: exerciseType: type: string notes: type: string details: $ref: '#/components/schemas/FitnessDetails' required: - exerciseType - notes - details
此文件定义了我们的API通过 /swagger-ui 端点提供的方法。
实施API端点
此步骤实施以下API端点:
GET /fitness/{id}:按ID检索适应度条目
POST /fitness:创建新的适应度条目
PATCH /fitness/{id}:更新指定的适应度条目
DELETE /fitness/{id}:删除指定的适应度条目
在 application包下创建 routes包,并在 routes包中创建名为 FitnessRoutes.kt 的文件。将以下代码复制到 FitnessRoutes.kt文件中:
package com.mongodb.application.routes import com.mongodb.application.request.FitnessRequest import com.mongodb.application.request.toDomain import com.mongodb.domain.ports.FitnessRepository import io.ktor.http.HttpStatusCode import io.ktor.server.application.call import io.ktor.server.request.receive import io.ktor.server.response.respond import io.ktor.server.response.respondText import io.ktor.server.routing.route import io.ktor.server.routing.Route import io.ktor.server.routing.post import io.ktor.server.routing.delete import io.ktor.server.routing.get import io.ktor.server.routing.patch import org.bson.types.ObjectId import org.koin.ktor.ext.inject fun Route.fitnessRoutes() { val repository by inject<FitnessRepository>() route("/fitness") { post { val fitness = call.receive<FitnessRequest>() val insertedId = repository.insertOne(fitness.toDomain()) call.respond(HttpStatusCode.Created, "Created fitness with id $insertedId") } delete("/{id?}") { val id = call.parameters["id"] ?: return@delete call.respondText( text = "Missing fitness id", status = HttpStatusCode.BadRequest ) val delete: Long = repository.deleteById(ObjectId(id)) if (delete == 1L) { return@delete call.respondText("Fitness Deleted successfully", status = HttpStatusCode.OK) } return@delete call.respondText("Fitness not found", status = HttpStatusCode.NotFound) } get("/{id?}") { val id = call.parameters["id"] if (id.isNullOrEmpty()) { return@get call.respondText( text = "Missing id", status = HttpStatusCode.BadRequest ) } repository.findById(ObjectId(id))?.let { call.respond(it.toResponse()) } ?: call.respondText("No records found for id $id") } patch("/{id?}") { val id = call.parameters["id"] ?: return@patch call.respondText( text = "Missing fitness id", status = HttpStatusCode.BadRequest ) val updated = repository.updateOne(ObjectId(id), call.receive()) call.respondText( text = if (updated == 1L) "Fitness updated successfully" else "Fitness not found", status = if (updated == 1L) HttpStatusCode.OK else HttpStatusCode.NotFound ) } } }
前面的代码定义了API端点及其处理程序。
在运行应用程序之前,删除包含 HTTP.kt、Routing.kt 和 Serialization.kt 文件的 plugins 文件夹,因为我们已将它们包含在 Application.kt文件中。
运行应用程序
从MongoDB Atlas 集群检索连接 URI。有关如何执行此操作的更多信息,请参阅 连接到MongoDB指南的 连接 URI 部分。
打开 application.conf文件并添加以下配置值:
-DMONGO_URI= <your connection URI> -DMONGO_DATABASE= <your database name>
在 IntelliJ 中导航到 Application.kt文件,然后单击 Run 按钮。现在,您可以通过在网络浏览器中导航到 http://localhost:8080/swagger-ui/访问权限该API 。
更多信息
要学习;了解有关 Ktor 的更多信息,请参阅Ktor 文档。
要查看本教程的源代码,请参阅 GitHub 上的Kotlin Ktor MongoDB Vector Search存储库。