Kotlin x Nodejs 實現上傳檔案
看這篇前,你需要有閱讀過上一篇的基礎,若是還未讀過,建議先點我去讀。
在 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() }