摘要:本文使用署名國際許可協議,歡迎轉載或重新修改使用,但需要注明來源。署名國際本文作者蘇洋創建時間年月日統計字數字閱讀時間分鐘閱讀本文鏈接使用和快速實現一個在線的解碼服務本文將會介紹如何使用完成一個簡單的二維碼解析服務,全部代碼在行以內。
本文使用「署名 4.0 國際 (CC BY 4.0)」許可協議,歡迎轉載、或重新修改使用,但需要注明來源。 署名 4.0 國際 (CC BY 4.0)
本文作者: 蘇洋
創建時間: 2018年12月09日
統計字數: 5453字
閱讀時間: 11分鐘閱讀
本文鏈接: https://soulteary.com/2018/12...
本文將會介紹如何使用 Docker、Node、JavaScript、Traefik完成一個簡單的二維碼解析服務,全部代碼在 300 行以內。
最近折騰文章相關的東西比較多,其中有一個現代化要素其實挺麻煩的,就是二維碼。
不論是“生成動態、靜態的二維碼”,還是“對已經生成的二維碼進行解析”,其實都不難實現。只是在日常工作中如果只是基于命令行去操作,會很不方便。
所以花了點時間,實現了一個簡單的 QRCode 在線解析工具,在完成這個工具之后,原本需要“打開終端,定位文件,執行命令,等待結果”就簡化成了“打開網頁,CTRL+V 粘貼,片刻展示結果”,當然,因為額外提供了接口,所以也可以當一個無狀態服務使用。
實現服務端核心解析邏輯核心邏輯其實很簡單,偽代碼三行就差不多了,比如。
const uploadContentByUser = req.body.files; const decodeContent = decodeImage(uploadContentByUser); const result = decodeQR.load(decodeContent);
但是實際使用的情況,出于性能的考慮,我不會過分使用新語法進行代碼封裝,更傾向盡可能使用“原生”的回調模式進行異步編程,避免各種“wrapper”造成不必要的損耗。
因為最終的目的是“在瀏覽器里一個粘貼/拖拽操作就完事”。所以我們需要將上面的核心邏輯展開,根據“簡單項目不過度封裝”的思想,代碼會膨脹為下面三十行左右的樣子。
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); }); }); });
上面的邏輯很簡單,主要做了下面幾件事:
接受用戶上傳的文件
讀取用戶上傳的文件
解析用戶上傳的文件
嘗試將文件中的信息解碼并反饋用戶
其中依賴了一個 express 三方的中間件 multipartMiddleware,我將主要使用它來進行上傳文件的請求序列化,源碼十分簡潔,一百行左右,有興趣可以去瀏覽一下。
它的使用也十分簡單,無需配置,只需要兩行就能發揮作用。
const multipart = require("connect-multiparty"); const multipartMiddleware = multipart();
當然,為了能夠配合客戶端 JavaScript 完成我們的最終目標,我們需要一些額外的代碼,比如:提供一個瀏覽器可以瀏覽的頁面。
這里額外提一點,如果使用類 express 的框架,一般會有一個 static 方法,讓你設置一個靜態文件目錄,可以免編程路由邏輯對一些文件進行對外訪問,比如這樣:
app.use(express.static(__dirname + "/static", {dotfiles: "ignore", etag: false, extensions: ["html"], index: false, maxAge: "1h", redirect: false}));
但是,本例中我其實只需要一個入口頁面就能滿足需求,根本不需要外部資源,比如 vue、react、jq、各種css框架…
這個時候,我推薦直接將要展示的頁面使用 fs API 進行內存緩存,直接提供用戶即可,比如按照下面的代碼進行編寫,大概十行就能滿足需求。
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); });
當然,如果你想要和 static 方式的文件一樣,在調試過程中,可以“熱更新”文件的話,需要將這個 indexCache 改寫成一個方法,在攔截用戶請求之后,每次都去動態讀取文件,或者更高階一些,根據文件最后編輯時間戳,實現一個簡單的 LRU 緩存。
實現客戶端交互邏輯在實現完畢接口后,我們把欠缺的前端交互邏輯補全。
這里因為沒有什么重度的操作,界面也很簡單,所以既不需要 jQ 這類庫,也不需要 Vue、React 這類框架,直接寫腳本就是了。
腦補我需要的界面,上面是一個數據交互的區域,下面是我的交互結果列表,因為頁面也沒幾個元素,所以直接使用腳本進行元素的創建和操作吧。
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);
瀏覽器端核心的操作有三個:
接受用戶的拖拽和粘貼圖片的操作
將用戶給予的圖片數據進行上傳
對服務端接口解析的結果進行展示
我們先來實現第一個操作,拖拽、粘貼富交互功能,大概三十行代碼就能解決戰斗。
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"); });
如果你需要支持多張圖片上傳,服務端接口需要做一個簡單的改動,我沒有這個需求,就不做了,有興趣可以實踐下,理論上加兩個循環就完事。
接著我們繼續實現上傳功能,因為現代的瀏覽器都支持了 fetch,所以實現起來也很簡單,二十多行解決戰斗:
function getMimeType(file, filename) { if (!file) return console.warn("不支持該文件類型"); 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)); }
最后,寫幾條樣式規則,額外優化一下解析結果展示就完事了,比如能夠更輕松的復制解析結果。
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); }將程序容器化
如果你認真閱讀了上面的文章,你會發現,實際的程序只有兩個文件,一個是服務端的 Node 程序,另外一個則是我們的客戶端頁面,但是實際上,我們還需要一個記錄 Node 依賴的 package.json 以及一個用戶構建容器鏡像的 Dockerfile,最簡化的目錄結構如下:
. ├── Dockerfile ├── index.html ├── index.js └── package.json
考慮實際維護,我們還需要額外創建一些其他的問題,不過都不重要,相關的文件內容,可以瀏覽我稍后提供的源碼倉庫。
此刻,當我們執行 node index.js,然后在瀏覽器中打開 localhost:3000 就能實現文章一開頭我們提到的一鍵粘貼完成對二維碼的解析操作了。
不過為了部署的便捷,我們還是需要將程序進行容器化操作。我們來著重瀏覽一下容器構建文件,同樣很簡單,幾行就足夠我們的使用。
FROM node:11.4.0-alpine MAINTAINER soultearyRUN apk update && apk add yarn WORKDIR /app COPY . /app RUN yarn ENTRYPOINT [ "node", "index.js" ]
配合簡單的構建命令:
docker build -t "docker.soulteary.com/decode-qrcode.soulteary.com:0.0.1" .
稍等一兩分鐘,就能夠獲得一個可以脫離當前環境,隨處運行的容器鏡像了。如果你想讓容器運行起來,也只需要一條命令,即可。
docker run -it -p 3000:3000 "docker.soulteary.com/decode-qrcode.soulteary.com:0.0.1"
如果每次都使用這樣的命令,未免麻煩,我們不妨使用 compose 配合 Traefik 進行服務化。
配合 Traefik 進行服務化操作配合 compose 和 Traefik 使用起來非常簡單,我之前的文章有提過多次,所以這里就簡單貼出配置文件示例:
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 即可自動啟動服務,并將服務自動注冊到 Traefik 的服務發現上。
如果需要擴容,scale decode=4 即可,如果還不會操作,可以翻閱之前的文章,進一步學習,: )
最后附上完整示例代碼: https://github.com/soulteary/decode-your-qrcode
最近結束了休假,換了新公司,手頭事情比較多,寫文章的速度會慢一些,不過沒有關系,草稿箱里的東西積累的再多一些,文章的質量會再上一層樓,一起期待一下吧。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/27637.html
摘要:是一款開源的微信個人號,進行了一系列的封裝,提供簡單好用的接口,然后開發者可以在其之上進行微信機器人的開發。注意這行代碼實現了登錄微信個人號并打印出所收到的消息。大家可以根據自己的需要定制出強大的個人微信號機器人。 現在,日常生活已經離不開微信,本文將會拋磚引玉演示如何使用wechaty操作微信個人號做一些有意思的東西,可以實現自動通過好友請求、關鍵詞回復、自動拉群等功能。大大提高了社...
摘要:頁面調試騰訊開發維護的代碼調試發布,錯誤監控上報,用戶問題定位。同樣是由騰訊開發維護的代碼調試工具,是針對移動端的調試工具。前端業務代碼工具庫。動畫庫動畫庫,也是目前通用的動畫庫。 本人微信公眾號:前端修煉之路,歡迎關注 本篇文章整理自己使用過的和看到過的一些插件和工具,方便日后自己查找和使用。 另外,感謝白小明,文中很多的工具來源于此。 彈出框 layer:http://layer....
摘要:微信小程序官方開放了個創建二維碼的接口,其中有一個是生成二維碼的,還有一個是葵花狀的小程序碼,我這里就用生成二維碼。 微信小程序官方開放了3個創建二維碼的接口,其中有一個是生成二維碼的,還有一個是葵花狀的小程序碼,我這里就用php生成二維碼。 首先要獲取Access_token 這個請求起來也是很容易的,微信開發文檔有請求接口:要把自己的小程序的APPID和APPSECRET獲取到 h...
摘要:微信小程序官方開放了個創建二維碼的接口,其中有一個是生成二維碼的,還有一個是葵花狀的小程序碼,我這里就用生成二維碼。 微信小程序官方開放了3個創建二維碼的接口,其中有一個是生成二維碼的,還有一個是葵花狀的小程序碼,我這里就用php生成二維碼。 首先要獲取Access_token 這個請求起來也是很容易的,微信開發文檔有請求接口:要把自己的小程序的APPID和APPSECRET獲取到 h...
閱讀 980·2023-04-25 23:55
閱讀 2702·2023-04-25 14:13
閱讀 3295·2019-08-26 13:47
閱讀 2968·2019-08-23 18:16
閱讀 625·2019-08-23 17:20
閱讀 3227·2019-08-23 16:55
閱讀 3144·2019-08-22 15:39
閱讀 3192·2019-08-20 18:10