看这篇前,你需要有阅读过上一篇的基础,若是还未读过,建议先点我去读。
在 Kotlin x Nodejs 体系下,实现上传文件是非常轻松的事,跟我一步步操作即可。
首先加入对 multer
和 body-parser
的依赖,完成后的 package.json
是这样的:
{
"name": "ktnode",
"version": "1.0.0",
"description": "",
"scripts": {
"start": "node ./web/ktnode.js"
},
"author": "rarnu",
"license": "GPLv3",
"dependencies": {
"kotlin": "^1.3.20",
"express": "^4.15.4",
"mongoose": "^4.11.7",
"body-parser": "^1.15.2",
"multer": "^1.4.1"
}
}
然后进行依赖库的安装:
$ npm install
在以后的文章里,如果遇到修改 package.json
的场景,将不再提示你安装,这应当是一个类似于条件反射的,约定俗成的操作。
然后需要引用 multer
,并实现对于上传目录的配置:
val multer = require("multer")
val upload = multer(js("({ dest: 'upload/'})"))
此处有一个小技巧,由于 multer
构造时需要传入一个 js 的 object,直接以 Kotlin 方式传参会引起错误,需要以 js 的方式来传,因此我们有两种方法来构造这样的参数:
val obj = js("({key: value, key2:value2, ... })")
multer(obj)
另一种方法是:
val obj: dynamic = object {}
obj["key"] = value
... ...
multer(obj)
初学者容易犯的错误是将以上代码写成:
multer(dest = "upload/")
这样是会报错的,在 Kotlin Javascript 体系内,必须按 js 的要求构造和传参,这点和 Kotlin 本身的写法并不一致。
然后我们可以完成服务器端的请求接受:
app.post("/upload", upload.single("file")) { req, resp ->
println(req.file.path)
resp.end()
}
此处可以看到上传的临时文件的路径,需要注意的是,在请求的最后需要使用 resp.end()
来通知请求已被处理完。不然如果是在浏览器内上传文件,你将会看到小圆圈一直在转,一直是上传中的状态。当然了,如果最后有数据返回,代码包含 resp.send()
的情况,可以不写 resp.end()
,因为 send 方法自身带了 end 的操作。
对于 req.file
类型,其内部字段如下表所示:
字段 | 含义 |
---|---|
fieldname | 请求时传入的字段名 |
originalname | 请求时传入的原始文件名 |
encoding | 文件的编码 |
mimetype | MIMETYPE,通常是 application/octet-stream |
destination | 临时目录,不含文件名 |
filename | 临时文件名,不含目录 |
path | 临时文件完整路径,含目录和文件名 |
size | 文件大小,单位为 byte |
上述例子是上传单个文件,Nodejs 同样允许上传多个文件,如下:
app.post("/upload", upload.array("file", 2)) { req, resp ->
println(req.files.length)
req.files.forEach { f -> println(f.path) }
resp.end()
}
这里就是允许上传2个文件,并且可以通过 forEach
进行遍历。
除此之外,我们还有需要将上传的文件保存到自己想要的目录的情况,这里提供一个简单的做法:
app.post("/upload", upload.single("file")) { req, resp ->
loadFile(req.file.path) { c ->
saveFile("files/${uuid()}", c) { succ ->
println(if (succ) "succ" else "fail")
}
}
resp.end()
}
最后,为了统一各种上传请求,在路由层面让开发者可以更快的编写代码,进行一定的抽象:
fun routing(path: String, method: String = "get", block: (req: dynamic, resp: dynamic) -> Unit) =
when (method.toLowerCase()) {
"get" -> app.get(path) { req, resp -> block(req, resp) }
"post" -> app.post(path) { req, resp -> block(req, resp) }
else -> {
}
}
fun routing(path: String, fileField: String = "file", isArray: Boolean = false, arraySize: Int = 0, block: (req: dynamic, file: dynamic, resp: dynamic) -> Unit) =
if (isArray) {
app.post(path, upload.array(fileField, arraySize)) { req, resp -> block(req, req.files, resp) }
} else {
app.post(path, upload.single(fileField)) { req, resp -> block(req, req.file, resp) }
}
这样我们在接受一个上传文件的请求时,就可以写成这种很简单的形式了:
// 上传单个文件
routing("/upload", isArray = false) { _, file, resp ->
println(file.path)
resp.end()
}
// 上传多个文件
routing("/upload2", isArray = true, arraySize = 2) { _, file, resp ->
file.forEach { f -> println(f.path) }
resp.end()
}