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

如何在Kotlin Ktor中传递ISBN参数调用Google Books API

问题描述

我想用Google Books API实现ISBN书籍搜索,技术栈为Kotlin+Ktor框架,前端采用HTML/CSS实现。当前遇到的问题是:输入ISBN后提交表单无任何反应,页面不跳转,无法传递ISBN参数调用API。

相关代码如下:

Routing.kt

fun Application.configureRouting() {
    routing {
        static("/static") {
            resources("files")
        }
        get("/") {
            call.respondRedirect("books")
        }

        route("search") {
            get {
                call.respond(FreeMarkerContent("search.ftl", model = null))
            }

            get("field") {
                val formParameters = call.receiveParameters()
                val isbn = formParameters.getOrFail("isbn").toLong()
                val client = HttpClient(CIO)
                val response: HttpResponse = client.get("https://www.googleapis.com/books/v1/volumes?q=isbn:$isbn")
                println(response.status)
                val stringBody: String = response.body()
                println(stringBody)
                client.close()
                call.respondRedirect("/search")
            }
        }
    }
}

注:直接在URL中硬编码ISBN时能正常获取API响应,说明API调用逻辑本身无问题。

search.ftl

<#import "_layout.ftl" as layout />
<@layout.header>
    <div>
        <div class="text-center">
            <h1 class="display-4">Search</h1>
        </div>
        <div class="container">
            <div class="row">
                <div class="form-group has-search">
                    <span class="fa fa-search form-control-feedback"></span>
                    <form action="/search/field" method="get">
                    <input type="text" class="form-control" name="isbn">
                    </form>
                </div>
            </div>
</@layout.header>
解决方法

1. 修复前端表单提交触发问题

当前表单缺少提交按钮,用户输入ISBN后无法触发提交动作,导致请求未发送至后端。需添加提交按钮:

<#import "_layout.ftl" as layout />
<@layout.header>
    <div>
        <div class="text-center">
            <h1 class="display-4">Search</h1>
        </div>
        <div class="container">
            <div class="row">
                <div class="form-group has-search">
                    <span class="fa fa-search form-control-feedback"></span>
                    <form action="/search/field" method="get">
                        <input type="text" class="form-control" name="isbn">
                        <!-- 添加提交按钮 -->
                        <button type="submit" class="btn btn-primary mt-2">搜索</button>
                    </form>
                </div>
            </div>
</@layout.header>

2. 修正后端参数获取方式

GET请求的参数存于URL的query字符串中,而非请求体的表单参数,不能使用call.receiveParameters(),需改用call.request.queryParameters获取:

get("field") {
    // 从query参数中获取ISBN,无需转Long(ISBN可能含分隔符/前缀)
    val isbn = call.request.queryParameters.getOrFail("isbn")
    val apiUrl = "https://www.googleapis.com/books/v1/volumes?q=isbn:$isbn"
    
    val client = HttpClient(CIO)
    try {
        val response = client.get(apiUrl)
        val bookJson = response.body<String>()
        // 直接返回JSON给前端,或传入模板渲染结果页
        call.respondText(bookJson, ContentType.Application.Json)
    } catch (e: Exception) {
        // 处理异常场景(如ISBN无效、API请求失败)
        call.respondText("搜索失败:${e.message}", ContentType.Text.Plain, HttpStatusCode.BadRequest)
    } finally {
        client.close()
    }
}

3. 优化HttpClient复用(推荐)

每次请求新建HttpClient会造成资源浪费,建议在Application级别初始化全局实例:

fun Application.configureRouting() {
    // 全局初始化HttpClient
    val httpClient = HttpClient(CIO) {
        install(HttpTimeout) {
            requestTimeoutMillis = 10000
        }
    }
    
    routing {
        static("/static") {
            resources("files")
        }
        get("/") {
            call.respondRedirect("books")
        }

        route("search") {
            get {
                call.respond(FreeMarkerContent("search.ftl", model = null))
            }

            get("field") {
                val isbn = call.request.queryParameters.getOrFail("isbn")
                val apiUrl = "https://www.googleapis.com/books/v1/volumes?q=isbn:$isbn"
                try {
                    val response = httpClient.get(apiUrl)
                    val bookJson = response.body<String>()
                    call.respondText(bookJson, ContentType.Application.Json)
                } catch (e: Exception) {
                    call.respondText("搜索失败:${e.message}", ContentType.Text.Plain, HttpStatusCode.BadRequest)
                }
            }
        }
    }
    
    // 应用停止时关闭HttpClient
    environment.monitor.subscribe(ApplicationStopped) {
        httpClient.close()
    }
}

4. 移除不必要的重定向

原代码中的call.respondRedirect("/search")会丢失API返回数据,若需展示搜索结果,可将数据传入模板渲染:

get("field") {
    val isbn = call.request.queryParameters.getOrFail("isbn")
    val apiUrl = "https://www.googleapis.com/books/v1/volumes?q=isbn:$isbn"
    try {
        val response = httpClient.get(apiUrl)
        val bookJson = response.body<String>()
        // 传入结果模板渲染
        call.respond(FreeMarkerContent("search_result.ftl", mapOf("bookData" to bookJson), null))
    } catch (e: Exception) {
        // 错误信息传回搜索页展示
        call.respond(FreeMarkerContent("search.ftl", mapOf("error" to "搜索失败:${e.message}"), null))
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 07:25:17