摘要:本文使用署名國(guó)際許可協(xié)議,歡迎轉(zhuǎn)載或重新修改使用,但需要注明來(lái)源。署名國(guó)際本文作者蘇洋創(chuàng)建時(shí)間年月日統(tǒng)計(jì)字?jǐn)?shù)字閱讀時(shí)間分鐘閱讀本文鏈接使用和快速實(shí)現(xiàn)一個(gè)在線的解碼服務(wù)本文將會(huì)介紹如何使用完成一個(gè)簡(jiǎn)單的二維碼解析服務(wù),全部代碼在行以內(nèi)。
本文使用「署名 4.0 國(guó)際 (CC BY 4.0)」許可協(xié)議,歡迎轉(zhuǎn)載、或重新修改使用,但需要注明來(lái)源。 署名 4.0 國(guó)際 (CC BY 4.0)
本文作者: 蘇洋
創(chuàng)建時(shí)間: 2018年12月09日
統(tǒng)計(jì)字?jǐn)?shù): 5453字
閱讀時(shí)間: 11分鐘閱讀
本文鏈接: https://soulteary.com/2018/12...
本文將會(huì)介紹如何使用 Docker、Node、JavaScript、Traefik完成一個(gè)簡(jiǎn)單的二維碼解析服務(wù),全部代碼在 300 行以內(nèi)。
最近折騰文章相關(guān)的東西比較多,其中有一個(gè)現(xiàn)代化要素其實(shí)挺麻煩的,就是二維碼。
不論是“生成動(dòng)態(tài)、靜態(tài)的二維碼”,還是“對(duì)已經(jīng)生成的二維碼進(jìn)行解析”,其實(shí)都不難實(shí)現(xiàn)。只是在日常工作中如果只是基于命令行去操作,會(huì)很不方便。
所以花了點(diǎn)時(shí)間,實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的 QRCode 在線解析工具,在完成這個(gè)工具之后,原本需要“打開(kāi)終端,定位文件,執(zhí)行命令,等待結(jié)果”就簡(jiǎn)化成了“打開(kāi)網(wǎng)頁(yè),CTRL+V 粘貼,片刻展示結(jié)果”,當(dāng)然,因?yàn)轭~外提供了接口,所以也可以當(dāng)一個(gè)無(wú)狀態(tài)服務(wù)使用。
實(shí)現(xiàn)服務(wù)端核心解析邏輯核心邏輯其實(shí)很簡(jiǎn)單,偽代碼三行就差不多了,比如。
const uploadContentByUser = req.body.files; const decodeContent = decodeImage(uploadContentByUser); const result = decodeQR.load(decodeContent);
但是實(shí)際使用的情況,出于性能的考慮,我不會(huì)過(guò)分使用新語(yǔ)法進(jìn)行代碼封裝,更傾向盡可能使用“原生”的回調(diào)模式進(jìn)行異步編程,避免各種“wrapper”造成不必要的損耗。
因?yàn)樽罱K的目的是“在瀏覽器里一個(gè)粘貼/拖拽操作就完事”。所以我們需要將上面的核心邏輯展開(kāi),根據(jù)“簡(jiǎn)單項(xiàng)目不過(guò)度封裝”的思想,代碼會(huì)膨脹為下面三十行左右的樣子。
app.post("/api/decode", multipartMiddleware, function(req, res) { let filePath = ""; try { if (req.files.imageFile.path) filePath = req.files.imageFile.path; } catch (e) { return res.json({code: 500, content: "request params error."}); } fs.readFile(filePath, function(errorWhenReadUploadFile, fileBuffer) { if (errorWhenReadUploadFile) return res.json({code: 501, content: "read upload file error."}); decodeImage(fileBuffer, function(errorWhenDecodeImage, image) { if (errorWhenDecodeImage) return res.json({code: 502, content: errorWhenDecodeImage}); let decodeQR = new qrcodeReader(); decodeQR.callback = function(errorWhenDecodeQR, result) { if (errorWhenDecodeQR) return res.json({code: 503, content: errorWhenDecodeQR}); if (!result) return res.json({code: 404, content: "gone with wind"}); return res.json({code: 200, content: result.result, points: result.points}); }; decodeQR.decode(image.bitmap); }); }); });
上面的邏輯很簡(jiǎn)單,主要做了下面幾件事:
接受用戶上傳的文件
讀取用戶上傳的文件
解析用戶上傳的文件
嘗試將文件中的信息解碼并反饋用戶
其中依賴了一個(gè) express 三方的中間件 multipartMiddleware,我將主要使用它來(lái)進(jìn)行上傳文件的請(qǐng)求序列化,源碼十分簡(jiǎn)潔,一百行左右,有興趣可以去瀏覽一下。
它的使用也十分簡(jiǎn)單,無(wú)需配置,只需要兩行就能發(fā)揮作用。
const multipart = require("connect-multiparty"); const multipartMiddleware = multipart();
當(dāng)然,為了能夠配合客戶端 JavaScript 完成我們的最終目標(biāo),我們需要一些額外的代碼,比如:提供一個(gè)瀏覽器可以瀏覽的頁(yè)面。
這里額外提一點(diǎn),如果使用類(lèi) express 的框架,一般會(huì)有一個(gè) static 方法,讓你設(shè)置一個(gè)靜態(tài)文件目錄,可以免編程路由邏輯對(duì)一些文件進(jìn)行對(duì)外訪問(wèn),比如這樣:
app.use(express.static(__dirname + "/static", {dotfiles: "ignore", etag: false, extensions: ["html"], index: false, maxAge: "1h", redirect: false}));
但是,本例中我其實(shí)只需要一個(gè)入口頁(yè)面就能滿足需求,根本不需要外部資源,比如 vue、react、jq、各種css框架…
這個(gè)時(shí)候,我推薦直接將要展示的頁(yè)面使用 fs API 進(jìn)行內(nèi)存緩存,直接提供用戶即可,比如按照下面的代碼進(jìn)行編寫(xiě),大概十行就能滿足需求。
const indexCache = fs.readFileSync("./index.html"); app.get("/", function(req, res) { res.redirect("/index.html"); }); app.get("/index.html", function(req, res) { res.setHeader("charset", "utf-8"); res.setHeader("Content-Type", "text/html"); res.send(indexCache); });
當(dāng)然,如果你想要和 static 方式的文件一樣,在調(diào)試過(guò)程中,可以“熱更新”文件的話,需要將這個(gè) indexCache 改寫(xiě)成一個(gè)方法,在攔截用戶請(qǐng)求之后,每次都去動(dòng)態(tài)讀取文件,或者更高階一些,根據(jù)文件最后編輯時(shí)間戳,實(shí)現(xiàn)一個(gè)簡(jiǎn)單的 LRU 緩存。
實(shí)現(xiàn)客戶端交互邏輯在實(shí)現(xiàn)完畢接口后,我們把欠缺的前端交互邏輯補(bǔ)全。
這里因?yàn)闆](méi)有什么重度的操作,界面也很簡(jiǎn)單,所以既不需要 jQ 這類(lèi)庫(kù),也不需要 Vue、React 這類(lèi)框架,直接寫(xiě)腳本就是了。
腦補(bǔ)我需要的界面,上面是一個(gè)數(shù)據(jù)交互的區(qū)域,下面是我的交互結(jié)果列表,因?yàn)轫?yè)面也沒(méi)幾個(gè)元素,所以直接使用腳本進(jìn)行元素的創(chuàng)建和操作吧。
let uploadBox = document.createElement("textarea"); uploadBox.id = "upload"; uploadBox.placeholder = "Paste Here."; document.body.appendChild(uploadBox); let list = document.createElement("ul"); list.id = "result"; document.body.appendChild(list);
瀏覽器端核心的操作有三個(gè):
接受用戶的拖拽和粘貼圖片的操作
將用戶給予的圖片數(shù)據(jù)進(jìn)行上傳
對(duì)服務(wù)端接口解析的結(jié)果進(jìn)行展示
我們先來(lái)實(shí)現(xiàn)第一個(gè)操作,拖拽、粘貼富交互功能,大概三十行代碼就能解決戰(zhàn)斗。
function getFirstImage(data, isDrop) { let i = 0, item; let target = isDrop ? data.dataTransfer && data.dataTransfer.files : data.clipboardData && data.clipboardData.items; if (!target) return false; while (i < target.length) { item = target[i]; if (item.type.indexOf("image") !== -1) return item; i++; } return false; } function getFilename(event) { return event.clipboardData.getData("text/plain").split(" ")[0]; } uploadBox.addEventListener("paste", function(event) { event.preventDefault(); const image = getFirstImage(event); if (image) return uploadFile(image.getAsFile(), getFilename(event) || "image.png"); }); uploadBox.addEventListener("drop", function(event) { event.preventDefault(); const image = getFirstImage(event, true); if (image) return uploadFile(image, event.dataTransfer.files[0].name || "image.png"); });
如果你需要支持多張圖片上傳,服務(wù)端接口需要做一個(gè)簡(jiǎn)單的改動(dòng),我沒(méi)有這個(gè)需求,就不做了,有興趣可以實(shí)踐下,理論上加兩個(gè)循環(huán)就完事。
接著我們繼續(xù)實(shí)現(xiàn)上傳功能,因?yàn)楝F(xiàn)代的瀏覽器都支持了 fetch,所以實(shí)現(xiàn)起來(lái)也很簡(jiǎn)單,二十多行解決戰(zhàn)斗:
function getMimeType(file, filename) { if (!file) return console.warn("不支持該文件類(lèi)型"); const mimeType = file.type; const extendName = filename.substring(filename.lastIndexOf(".") + 1); if (mimeType !== "image/" + extendName) return "image/" + extendName; return mimeType; } function uploadFile(file, filename) { let formData = new FormData(); formData.append("imageFile", file); let fileType = getMimeType(file, filename); if (!fileType || ["jpg", "jpeg", "gif", "png", "bmp"].indexOf(fileType) > -1) return console.warn("文件格式不正確"); formData.append("mimeType", fileType); fetch("/api/decode", {method: "POST", body: formData}). then((response) => response.json()). then((data) => { if (data.code === 200) return addResult(filename, data.content); return addResult(filename, data.content); }). catch((error) => addResult(filename, error)); }
最后,寫(xiě)幾條樣式規(guī)則,額外優(yōu)化一下解析結(jié)果展示就完事了,比如能夠更輕松的復(fù)制解析結(jié)果。
list.addEventListener("mouseover", function(e) { let target = e.target; if (target && target.nodeName) { if (target.nodeName.toLowerCase() === "input") { target.select(); } } }); function result(file, text) { let li = document.createElement("li"); li.innerHTML = "" + file + "" + ""; document.getElementById("result").appendChild(li); }將程序容器化
如果你認(rèn)真閱讀了上面的文章,你會(huì)發(fā)現(xiàn),實(shí)際的程序只有兩個(gè)文件,一個(gè)是服務(wù)端的 Node 程序,另外一個(gè)則是我們的客戶端頁(yè)面,但是實(shí)際上,我們還需要一個(gè)記錄 Node 依賴的 package.json 以及一個(gè)用戶構(gòu)建容器鏡像的 Dockerfile,最簡(jiǎn)化的目錄結(jié)構(gòu)如下:
. ├── Dockerfile ├── index.html ├── index.js └── package.json
考慮實(shí)際維護(hù),我們還需要額外創(chuàng)建一些其他的問(wèn)題,不過(guò)都不重要,相關(guān)的文件內(nèi)容,可以瀏覽我稍后提供的源碼倉(cāng)庫(kù)。
此刻,當(dāng)我們執(zhí)行 node index.js,然后在瀏覽器中打開(kāi) localhost:3000 就能實(shí)現(xiàn)文章一開(kāi)頭我們提到的一鍵粘貼完成對(duì)二維碼的解析操作了。
不過(guò)為了部署的便捷,我們還是需要將程序進(jìn)行容器化操作。我們來(lái)著重瀏覽一下容器構(gòu)建文件,同樣很簡(jiǎn)單,幾行就足夠我們的使用。
FROM node:11.4.0-alpine MAINTAINER soultearyRUN apk update && apk add yarn WORKDIR /app COPY . /app RUN yarn ENTRYPOINT [ "node", "index.js" ]
配合簡(jiǎn)單的構(gòu)建命令:
docker build -t "docker.soulteary.com/decode-qrcode.soulteary.com:0.0.1" .
稍等一兩分鐘,就能夠獲得一個(gè)可以脫離當(dāng)前環(huán)境,隨處運(yùn)行的容器鏡像了。如果你想讓容器運(yùn)行起來(lái),也只需要一條命令,即可。
docker run -it -p 3000:3000 "docker.soulteary.com/decode-qrcode.soulteary.com:0.0.1"
如果每次都使用這樣的命令,未免麻煩,我們不妨使用 compose 配合 Traefik 進(jìn)行服務(wù)化。
配合 Traefik 進(jìn)行服務(wù)化操作配合 compose 和 Traefik 使用起來(lái)非常簡(jiǎn)單,我之前的文章有提過(guò)多次,所以這里就簡(jiǎn)單貼出配置文件示例:
version: "3" services: decode: image: docker.soulteary.com/decode-qrcode.soulteary.com:0.0.1 expose: - 3000 networks: - traefik labels: - "traefik.enable=true" - "traefik.port=3000" - "traefik.frontend.rule=Host:decode-qrcode.lab.com" - "traefik.frontend.entryPoints=http,https" networks: traefik: external: true
然后使用 docker-compose -f compose.yml up -d 即可自動(dòng)啟動(dòng)服務(wù),并將服務(wù)自動(dòng)注冊(cè)到 Traefik 的服務(wù)發(fā)現(xiàn)上。
如果需要擴(kuò)容,scale decode=4 即可,如果還不會(huì)操作,可以翻閱之前的文章,進(jìn)一步學(xué)習(xí),: )
最后附上完整示例代碼: https://github.com/soulteary/decode-your-qrcode
最近結(jié)束了休假,換了新公司,手頭事情比較多,寫(xiě)文章的速度會(huì)慢一些,不過(guò)沒(méi)有關(guān)系,草稿箱里的東西積累的再多一些,文章的質(zhì)量會(huì)再上一層樓,一起期待一下吧。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/27637.html
摘要:是一款開(kāi)源的微信個(gè)人號(hào),進(jìn)行了一系列的封裝,提供簡(jiǎn)單好用的接口,然后開(kāi)發(fā)者可以在其之上進(jìn)行微信機(jī)器人的開(kāi)發(fā)。注意這行代碼實(shí)現(xiàn)了登錄微信個(gè)人號(hào)并打印出所收到的消息。大家可以根據(jù)自己的需要定制出強(qiáng)大的個(gè)人微信號(hào)機(jī)器人。 現(xiàn)在,日常生活已經(jīng)離不開(kāi)微信,本文將會(huì)拋磚引玉演示如何使用wechaty操作微信個(gè)人號(hào)做一些有意思的東西,可以實(shí)現(xiàn)自動(dòng)通過(guò)好友請(qǐng)求、關(guān)鍵詞回復(fù)、自動(dòng)拉群等功能。大大提高了社...
摘要:頁(yè)面調(diào)試騰訊開(kāi)發(fā)維護(hù)的代碼調(diào)試發(fā)布,錯(cuò)誤監(jiān)控上報(bào),用戶問(wèn)題定位。同樣是由騰訊開(kāi)發(fā)維護(hù)的代碼調(diào)試工具,是針對(duì)移動(dòng)端的調(diào)試工具。前端業(yè)務(wù)代碼工具庫(kù)。動(dòng)畫(huà)庫(kù)動(dòng)畫(huà)庫(kù),也是目前通用的動(dòng)畫(huà)庫(kù)。 本人微信公眾號(hào):前端修煉之路,歡迎關(guān)注 本篇文章整理自己使用過(guò)的和看到過(guò)的一些插件和工具,方便日后自己查找和使用。 另外,感謝白小明,文中很多的工具來(lái)源于此。 彈出框 layer:http://layer....
摘要:微信小程序官方開(kāi)放了個(gè)創(chuàng)建二維碼的接口,其中有一個(gè)是生成二維碼的,還有一個(gè)是葵花狀的小程序碼,我這里就用生成二維碼。 微信小程序官方開(kāi)放了3個(gè)創(chuàng)建二維碼的接口,其中有一個(gè)是生成二維碼的,還有一個(gè)是葵花狀的小程序碼,我這里就用php生成二維碼。 首先要獲取Access_token 這個(gè)請(qǐng)求起來(lái)也是很容易的,微信開(kāi)發(fā)文檔有請(qǐng)求接口:要把自己的小程序的APPID和APPSECRET獲取到 h...
摘要:微信小程序官方開(kāi)放了個(gè)創(chuàng)建二維碼的接口,其中有一個(gè)是生成二維碼的,還有一個(gè)是葵花狀的小程序碼,我這里就用生成二維碼。 微信小程序官方開(kāi)放了3個(gè)創(chuàng)建二維碼的接口,其中有一個(gè)是生成二維碼的,還有一個(gè)是葵花狀的小程序碼,我這里就用php生成二維碼。 首先要獲取Access_token 這個(gè)請(qǐng)求起來(lái)也是很容易的,微信開(kāi)發(fā)文檔有請(qǐng)求接口:要把自己的小程序的APPID和APPSECRET獲取到 h...
閱讀 982·2023-04-25 23:55
閱讀 2710·2023-04-25 14:13
閱讀 3297·2019-08-26 13:47
閱讀 2972·2019-08-23 18:16
閱讀 628·2019-08-23 17:20
閱讀 3228·2019-08-23 16:55
閱讀 3146·2019-08-22 15:39
閱讀 3196·2019-08-20 18:10