前几天写了《Kotlin x Nodejs》的具体操作方法,然而这些操作方法却并不简单,甚至有不少地方是完全没有代码提示的(取决于 dynamic
的特性,无法提示),这会对开发造成很大的困扰。
因此我们就必须有办法来对相应的 js 库进行包装,把相关的 api 暴露出来。按下面的这段代码举例:
app.post("/upload", upload.single("file")) { req, resp ->
println(req.file.path)
resp.end()
}
其中 req.file.path
怎么来的?说白了很简单,无非是 req
对象里有一个 file
对象,而 file
对象里又有一个名为 path
的属性,问题是我们怎么知道有这些。在标准的 Nodejs 开发中,我们可以看到相关模块的源码,而在 Kotlin 中却无法很直观的去看了。
这个时候我们有两种做法,一种当然是去 Nodejs 模块的源码里翻了,还有一种比较简单:
app.post("/upload", upload.single("file")) { req, resp ->
js("for (var prop in req.file) { console.log(prop); }")
resp.end()
}
然后就可以在终端看到打印出来的内容:
fieldname
originalname
encoding
mimetype
destination
filename
path
size
这就是对象里面包含的字段了,如果对字段里的具体内容感兴趣,可以改造一下代码:
js("for (var prop in req.file) { console.log(prop + ' = ' + req.file[prop]); }")
这样就可以把具体内容也打印出来,方便我们判断数据类型。这里可能会有人问,js()
命令执行一句 js 代码,而 req.file
其实是在这段代码外的,为什么可以执行到呢,对象是如何传进去的?其实在这边,就是一个典型的 “想多了” 的场景。因为 Kotlin Javascript 在编译后,就是一个 js,生成的代码如下:
function App$get$lambda(closure$callback) {
return function (req, resp) {
for (var prop in req.file) {
console.log(prop + ' = ' + req.file[prop]);
}
closure$callback(new Request(req), new Response(resp));
return Unit;
};
}
所以在这里,是完全不会有上下文的问题的,对象可以传入的原理就这么简单。
然后的事情就会比较有意思了,对 req.file
做一个简单的封装:
class File(private val base: dynamic) {
val fieldname: String get() = base.fieldname
val originalname: String get() = base.originalname
val encoding: String get() = base.encoding
val mimetype: String get() = base.mimetype
val destination: String get() = base.destination
val filename: String get() = base.filename
val path: String get() = base.path
val size: Long get() = base.size
companion object
}
然后我们就可以在 app.post()
里这么用:
app.post("/upload", upload.single("file")) { req, resp ->
val file = File(req.file)
println(file.path)
resp.end()
}
由于有了这么一个类的包装,Idea 终于可以提示代码了,这意味着会使开发变得更加的方便。
所以,在这些的基础上,我们可以轻松的写出各种包装器,把 Nodejs 相关的库进一步 Kotlin 化。例如:
class App(val base: dynamic) {
fun use(c: dynamic) = base.use(c)
fun set(key: String, value: dynamic) = base.set(key, value)
fun listen(port: Int = 8888, callback: () -> Unit) = base.listen(port) { callback() }
fun get(path: String, callback: (req: Request, resp: Response) -> Unit) =
base.get(path) { req, resp -> callback(Request(req), Response(resp)) }
fun post(path: String, callback: (req: Request, resp: Response) -> Unit) =
base.post(path) { req, resp -> callback(Request(req), Response(resp)) }
fun post(path: String, option: dynamic, callback: (req: Request, resp: Response) -> Unit) =
base.post(path, option) { req, resp -> callback(Request(req), Response(resp)) }
}
好了,说了这么多,也是时候来点真材实料的东西了,其实我已经封装好了一个最简单的 Nodejs + Express 的包装器,在开发的时候简单的使用就好了,在这包装器的基础上,编写 Kotlin 代码是非常轻松的,如下:
fun main(args: Array<String>) {
errorHandler = object : ErrorHandler() { // 全局的异常处理
override fun handleRejection(err: String?, promise: dynamic) { }
override fun handleException(err: String?) { }
}
initServer() // 初始化服务器
mongoConnect("sampledb") { succ -> } // 连接到 mongoldb
routing("/index") { req, resp -> // 路由处理
if (req.session.views) { // session 处理
req.session.views += 1
} else {
req.session.views = 1
}
println("view => ${req.session.views}")
val name = req.query.name // 参数处理
resp.type("text/html")
val html = renderFile("index.html", optionOf("username" to name)) // 页面模板渲染
resp.send(html)
}
routing("/sql") { _, resp -> // 查询 mongodb
mongo?.select("user", listOf("name", "age"), optionOf()) { succ, result ->
if (succ) {
result.forEach {
println("data => ${it.name}, ${it.age}")
}
}
resp.end()
}
}
routing("/user/:id") { req, resp -> // restful 路由处理
val id = req.params.id
println("userid => $id")
resp.end()
}
startListen(8888) // 开始监听
}
其实关键问题不在代码本身,而在于可以很爽的进行开发,在大部分的情况下,你的 IDE 都会给予提示了。
其实 Kotlin 服务端系列(含Ktor)讲到这里已经差不多没有什么额外的内容了,下面我会写一些 Kotlin 前端的开发,包括与 React 协同等。
另外,按目前的经验,官方提供的新建项目的向导非常的不友好,每次写文,光是建项目,改配置就有一大堆的事情要做,我打算在近期开发一个全新的新建向导,以便帮助到各位。
最后,本文所说的 wrapper 在哪里?点击此处访问我的Github 就好了!