
以下是一个使用 Kotlin 和 Ktor 构建 Android 后端 API 的详细示例,包含常见功能实现:
plugins {
application
kotlin("jvm") version "1.9.0"
id("io.ktor.plugin") version "2.3.4"
id("org.jetbrains.kotlin.plugin.serialization") version "1.9.0"
}
dependencies {
implementation("io.ktor:ktor-server-core")
implementation("io.ktor:ktor-server-netty")
implementation("io.ktor:ktor-server-content-negotiation")
implementation("io.ktor:ktor-serialization-kotlinx-json")
implementation("io.ktor:ktor-server-status-pages")
implementation("io.ktor:ktor-server-auth")
implementation("io.ktor:ktor-server-auth-jwt")
// 数据库
implementation("org.jetbrains.exposed:exposed-core:0.41.1")
implementation("org.jetbrains.exposed:exposed-jdbc:0.41.1")
implementation("com.h2database:h2:2.1.214")
// 日志
implementation("ch.qos.logback:logback-classic:1.4.7")
// 测试
testImplementation("io.ktor:ktor-server-test-host")
}fun main() {
embeddedServer(Netty, port = 8080, host = "0.0.0.0") {
install(ContentNegotiation) {
json()
}
install(StatusPages) {
exception<Throwable> { call, cause ->
when (cause) {
is ApiException -> {
call.respond(
HttpStatusCode.fromValue(cause.statusCode),
ErrorResponse(cause.message ?: "Unknown error")
)
}
else -> {
call.respond(
HttpStatusCode.InternalServerError,
ErrorResponse("Internal server error")
)
}
}
}
}
install(Authentication) {
jwt {
realm = "My API"
verifier(JwtConfig.verifier)
validate { credential ->
if (credential.payload.getClaim("userId").asString() != "") {
JWTPrincipal(credential.payload)
} else {
null
}
}
}
}
DatabaseFactory.init()
configureRouting()
}.start(wait = true)
}
class ApiException(
val statusCode: Int,
override val message: String
) : RuntimeException(message)
data class ErrorResponse(val message: String)fun Application.configureRouting() {
routing {
// 公共路由
route("/api") {
post("/register") {
// 用户注册逻辑
}
post("/login") {
// 用户登录逻辑
}
// 需要认证的路由
authenticate {
route("/users") {
get {
// 获取所有用户
}
get("{id}") {
// 获取单个用户
}
}
route("/posts") {
get {
// 获取所有帖子
}
post {
// 创建新帖子
}
put("{id}") {
// 更新帖子
}
}
}
}
}
}object DatabaseFactory {
fun init() {
val database = Database.connect(
url = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1",
driver = "org.h2.Driver"
)
transaction {
SchemaUtils.create(Users, Posts)
}
}
}
object Users : Table() {
val id = uuid("id").primaryKey().autoGenerate()
val username = varchar("username", 50).uniqueIndex()
val password = varchar("password", 100)
val createdAt = datetime("created_at")
}
object Posts : Table() {
val id = uuid("id").primaryKey().autoGenerate()
val title = varchar("title", 100)
val content = text("content")
val authorId = (uuid("author_id") references Users.id)
val createdAt = datetime("created_at")
}object JwtConfig {
private const val SECRET = "your_secret_key_here"
private const val ISSUER = "your_issuer"
private const val AUDIENCE = "your_audience"
private const val EXPIRATION = 3600L // 1小时
val verifier = JWT
.require(Algorithm.HMAC256(SECRET))
.withIssuer(ISSUER)
.withAudience(AUDIENCE)
.build()
fun generateToken(userId: UUID): String {
return JWT.create()
.withIssuer(ISSUER)
.withAudience(AUDIENCE)
.withClaim("userId", userId.toString())
.withExpiresAt(Date(System.currentTimeMillis() + EXPIRATION * 1000))
.sign(Algorithm.HMAC256(SECRET))
}
}@Serializable
data class UserRequest(
val username: String,
val password: String
)
@Serializable
data class UserResponse(
val id: String,
val username: String,
val createdAt: String
)
@Serializable
data class PostRequest(
val title: String,
val content: String
)
@Serializable
data class PostResponse(
val id: String,
val title: String,
val content: String,
val authorId: String,
val createdAt: String
)fun Route.userRoutes() {
route("/users") {
get {
val users = transaction {
Users.selectAll().map { row ->
UserResponse(
id = row[Users.id].toString(),
username = row[Users.username],
createdAt = row[Users.createdAt].toString()
)
}
}
call.respond(users)
}
get("{id}") {
val id = call.parameters["id"] ?: throw ApiException(400, "Missing ID")
val user = transaction {
Users.select { Users.id eq UUID.fromString(id) }.singleOrNull()
} ?: throw ApiException(404, "User not found")
call.respond(
UserResponse(
id = user[Users.id].toString(),
username = user[Users.username],
createdAt = user[Users.createdAt].toString()
)
)
}
}
}class UserRoutesTest {
@Test
fun testGetUsers() = testApplication {
application {
configureRouting()
}
client.get("/api/users").apply {
assertEquals(HttpStatusCode.OK, status)
val users = Json.decodeFromString<List<UserResponse>>(bodyAsText())
assertTrue(users.isNotEmpty())
}
}
}./gradlew runGET http://localhost:8080/api/usersPOST http://localhost:8080/api/register注意:实际部署时需要:
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。