亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > JavaScript > 正文

Vue+Koa2+mongoose寫一個像素繪板的實現方法

2019-11-19 10:50:25
字體:
來源:轉載
供稿:網友

前言

GitHub: server | 前端

為什么是繪板:v2ex

作為一名前端,總會有意無意接觸到 NodeJS 、有意無意會去看文檔、有意無意會注意到框架,但真當需要我們需要在工作中善用它時,多半還是要感嘆一句“紙上得來終覺淺”。所以一周前我決定進行一個實踐嘗試,希望能把以往無意中學到的知識融匯貫通,最終選擇把以前的一個畫板 Demo 重寫并添加 server 端。

技術棧

  • [vue + vuex + vue-router] 頁面渲染 + 數據共享 + 路由跳轉
  • [axios] 以 Promise 的方式使用 HTTP 請求
  • [stylus] CSS 預處理
  • [element-ui] UI 庫
  • [Webpack] 打包上面這些東西
  • [koa 2 & koa-generator] NodeJS 框架和框架腳手架
  • [mongodb & mongoose] 數據庫和操作數據庫的庫
  • [node-canvas] 服務端數據副本記錄
  • [Socket.io] 實時推送
  • [pm2] Node 服務部署
  • [nginx] 部署靜態資源訪問服務(HTTPS),代理請求
  • [letsencrypt] 生成免費的 HTTPS 證書

Webpack 之所以也被列出來,是因為本項目作為項目 luwuer.com 的一個模塊,需要 webpack 來實現獨立打包

node-canvas

安裝

node-canvas 是我目前遇到過最難安裝的依賴,以至于我根本不想在 Windows 下安裝他,它的功能依賴很多系統下默認不存在的包,在 Github 上也能看到很多 issue 的標簽是 installation help。以 CentOS 7 純凈版為例,在安裝它之前你需要安裝以下這些依賴,值得注意的是 npm 文檔上提供的命令沒有 cairo 。

# centos 前置條件sudo yum install gcc-c++ cairo cairo-devel pango-devel libjpeg-turbo-devel giflib-devel# 安裝本體yarn add canvas -D

還有一個不明所以的坑,如果前置條件準備就緒后,安裝本體仍然一直卡取包這一步(不報錯),此時需要單獨更新一下 npm

使用示例

參考文檔很容易就能掌握基本用法,下方例子中先取到像素點數據生成 ImageData ,然后通過 putImageData 把歷史數據畫到 canvas 。

const { createCanvas, createImageData} = require('canvas')const canvas = createCanvas(canvasWidth, canvasHeight)const ctx = canvas.getContext('2d')// 初始化const init = callback => { Dot.queryDots().then(data => {  let imgData = new createImageData(   Uint8ClampedArray.from(data),   canvasWidth,   canvasHeight  )  // 移除 Smooth  ctx.mozImageSmoothingEnabled = false  ctx.webkitImageSmoothingEnabled = false  ctx.msImageSmoothingEnabled = false  ctx.imageSmoothingEnabled = false  ctx.putImageData(imgData, 0, 0, 0, 0, canvasWidth, canvasHeight)  successLog('canvas render complete !')  callback() })}

Socket.io

本項目在設計上有兩個必須用到推送的地方,一是其他用戶的建點信息,二是所有用戶發送的聊天消息。

client

// socket.io init// transports: [ 'websocket' ]window.socket = io.connect(window.location.origin.replace(/https/, 'wss'))// 接收圖片window.socket.on('dataUrl', data => { this.imageObject.src = data.url this.loadInfo.push('渲染圖像...') this.init()})// 接收其他用戶建點window.socket.on('newDot', data => { this.saveDot(  {   x: data.index % this.width,   y: Math.floor(data.index / this.width),   color: data.color  },  false )})// 接收所有人的最新推送消息window.socket.on('newChat', data => { if (this.msgs.length === 50) {  this.msgs.shift() } this.msgs.push(data)})

server /bin/www

let http = require('http');let io = require('socket.io')let server = http.createServer(app.callback())let ws = io.listen(server)server.listen(port)ws.on('connection', socket => { // 建立連接的 client 加入房間 chatroom ,為了下方可以廣播 socket.join('chatroom') socket.emit('dataUrl', {  url: cv.getDataUrl() }) socket.on('saveDot', async data => {  // 推送給其他用戶,即廣播  socket.broadcast.to('chatroom').emit('newDot', data)  saveDotHandle(data) }) socket.on('newChat', async data => {  // 推送給所有用戶  ws.sockets.emit('newChat', data)  newChatHandle(data) })})

letsencrypt

申請證書

# 獲得程序git clone https://github.com/letsencrypt/letsencryptcd letsencrypt# 自動生成證書(環境安裝完畢后會有兩次確認),證書目錄 /etc/letsencrypt/live/{輸入的第一個域名} 我這里是 /etc/letsencrypt/live/www.luwuer.com/./letsencrypt-auto certonly --standalone --email html6@foxmail.com -d www.luwuer.com -d luwuer.com

自動續期

# 進入定時任務編輯crontab -e# 提交申請,我這里設置每兩月一次,過期時間為三月* * * */2 * cd /root/certificate/letsencrypt && ./letsencrypt-auto certonly --renew

nginx

yum install -y nginx

/etc/nginx/config.d/https.conf

server { # 使用 HTTP/2,需要 Nginx1.9.7 以上版本 listen 443 ssl http2 default_server; # 開啟HSTS,并設置有效期為“6307200秒”(6個月),包括子域名(根據情況可刪掉),預加載到瀏覽器緩存(根據情況可刪掉) add_header Strict-Transport-Security "max-age=6307200; preload"; # add_header Strict-Transport-Security "max-age=6307200; includeSubdomains; preload"; # 禁止被嵌入框架 add_header X-Frame-Options DENY; # 防止在IE9、Chrome和Safari中的MIME類型混淆攻擊 add_header X-Content-Type-Options nosniff; # ssl 證書 ssl_certificate /etc/letsencrypt/live/www.luwuer.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/www.luwuer.com/privkey.pem; # OCSP Stapling 證書 ssl_trusted_certificate /etc/letsencrypt/live/www.luwuer.com/chain.pem; # OCSP Stapling 開啟,OCSP是用于在線查詢證書吊銷情況的服務,使用OCSP Stapling能將證書有效狀態的信息緩存到服務器,提高TLS握手速度 ssl_stapling_verify on; #OCSP Stapling 驗證開啟 ssl_stapling on;  #用于查詢OCSP服務器的DNS  resolver 8.8.8.8 8.8.4.4 valid=300s; # DH-Key交換密鑰文件位置 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # 指定協議 TLS ssl_protocols TLSv1 TLSv1.1 TLSv1.2;  # 加密套件,這里用了CloudFlare's Internet facing SSL cipher configuration ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5; # 由服務器協商最佳的加密算法 ssl_prefer_server_ciphers on; server_name ~^(/w+/.)?(luwuer/.com)$; # $1 = 'blog.' || 'img.' || '' || 'www.' ; $2 = 'luwuer.com' set $pre $1; if ($pre = 'www.') {  set $pre ''; } set $next $2;  root /root/apps/$pre$next; location / {  try_files $uri $uri/ /index.html;  index index.html; } location ^~ /api/ {  proxy_pass http://43.226.147.135:3000/;  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }  # socket代理配置 location /socket.io/ {  proxy_pass http://43.226.147.135:3000;  proxy_http_version 1.1;  proxy_set_header Upgrade $http_upgrade;  proxy_set_header Connection "upgrade"; } # location /weibo/ { #   proxy_pass https://api.weibo.com/; # } include /etc/nginx/utils/cache.conf;}server { listen 80; server_name www.luwuer.com; rewrite ^(.*)$ https://$server_name$request_uri;}

附錄

數據庫存儲結構思考歷程

首先需求是畫板可以作畫實際大小為 { width: 1024px, height: 512px } ,這就意味著有 1024 * 512 = 524,288 個像素點,或則有 524,288 * 4 = 2,097,152 個表示顏色的數字,這些數據量在不做壓縮的情況下,最小存儲方式是后者剔除掉 rgba 中的 a ,也就是一個長度為 524,288 * 3 = 1,572,864 的數組,如果賦值給變量占用內存大概 1.5M (數據來源于 Chrome Memory)。為了存儲以上結構,我首先分了兩種類型的存儲結構:

以點為對象存儲,也就是說會有 524,288 條數據

  1. 顏色 rbga 存儲,后優化為 rgb 存儲
  2. 顏色 16 進制存儲

整個畫布數據當作一條數據存儲

雖然看起來結構2有點蠢,但起初我確實思考過這樣的結構,那時我還不清楚原來取數據最耗時的不是查詢而是 IO 。
后來我分別測試 1.1 和 1.2 這兩種結構,然后直接否定了結構 2,因為在測試中我發現了 IO 耗時占總耗時超過 98% ,而結構 2 無疑不能因為單條數據取得絕對的性能優勢。

1.1

  • 存儲大小 10M
  • 取出全部數據 8000+ms
  • 全表查詢 150ms (findOne 和 find 對比結果)
  • 其余耗時 20ms (findOne 和 find 對比結果)

1.2

  • 存儲大小 10M
  • 取出全部數據 7500+ms
  • 全表查詢
  • 其余耗時

結構 2 如果取數據不是毫秒級,就是死刑,因為這種結構下單個像素變動就需要存儲整個圖片數據

老實講這個測試結果讓我有些難以接受,問了好幾個認識的后端為什么性能這么差、有沒有解決辦法,但都沒什么結果。更可怕的是,測試是在我 i7 CPU 的臺式電腦上進行的,當我把測試環境放到單核服務器上時,取全表數據的耗時還要乘以 10 。好在只要想一個問題久了,即使有時只是想著這個問題發呆,也總能迸發出一些莫名的靈感。我想到了關鍵之一數據可以只在服務啟動時取出放到內存中,像素發生改變時數據庫和內存數據副本同步修改,于是得以繼續開發下去。最終我選擇了 1.1 的結構,選擇原因和下文的“數據傳輸”有關。

const mongoose = require('mongoose')let schema = new mongoose.Schema({ index: {  type: Number,  index: true }, r: Number, g: Number, b: Number}, { collection: 'dots'})

index 代替 x & y 以及移除 rgba 中的 a 在代碼中再補上,都能顯著降低 collection 的實際存儲大小

在測試過程中其實還有個特別奇怪的問題,就是單核小霸王服務器上,我如果一次性取出所有數據存儲到一個 Array 中,程序會在中途奔潰,沒有任何報錯信息。起初我以為是 CPU 滿荷載久了導致的奔潰(top 查看硬件使用信息),所以還特意新租了一個服務器,想用一個群里的朋友提醒的“分布式”。再后面一段時間,我通過分頁取數據,發現程序總是在取第二十萬零幾百條(一個固定數字)是陡然奔潰,所以為 CPU 證了清白。

PS:好在以前沒分布式經驗,不然一條路走到黑,可能現在都還以為是 CPU 的問題呢。

數據傳輸思考歷程

上面有提到過,長度為 1,572,864 的顏色數組占用內存為 1.5M ,我猜想數據傳輸時也是這個大小。起初我想,我得把這個數據壓縮壓縮(不是指 gzip ),但由于不會,就想到了替代方案。前面已經為了避免取數時高額的 IO 消耗,會在內存中存儲一個數據副本,我想到這個數據我可以通過拼接(1.1 的結構相對而言 CPU 消耗少得多)生成 ImageData 再通過 ctx.putImageData 畫到 Canvas 上,這就是關鍵之二把數據副本畫在服務器上的一個 canvas 上。

然后就好辦了,可以通過 ctx.toDataURL || fs.writeFile('{path}', canvas.toBuffer('image/jpeg') 把數據以圖片的方式推送給客戶端,圖片本身的算法幫助我們壓縮了數據,不用自己搗鼓。事實上壓縮率非??捎^,前期畫板上幾乎都是重復顏色時,1.5M 數據甚至可以壓縮到小于 10k,后期估計應該也在 300k 以內。

鑒于 DataURL 更方便,這里我采用的 DataURL 的方式傳遞圖片數據。

工作記錄

  • Day 1 把像素畫板前端內容重構一遍,解決圖像過大時放大視圖卡頓的問題
  • Day 2 處理后端邏輯,由于數據庫IO限制,嘗試不同的存儲結構,但性能都不理想
  • Day 3 繼續問題研究,最后決定在服務端也同步一份 canvas 操作,而不是只存在庫里,但流程還沒走通,因為下午睡了一覺
  • Day 4 1核1G服務器在訪問數據庫取50w條數據時崩潰,后通過和朋友討論,在無意中發現了實際問題,就有了解決方案(部分時間在新服務器配了套環境,不過由于問題解決又棄用了)
  • Day 5 增加公告、用戶、聊天、像素點歷史信息查詢功能
  • Day 6/7 解決 socket.io https 問題,通宵兩天最后發現是 CDN 加速問題,差點螺旋升天

Day 4 說的實際問題,我只能大概定位在 NodeJS 變量大小限制或對象個數限制,因為在我將 50w 長度 Array[Object] 轉換為 200w 長度 Array[Number] 后問題消失了,知道具體原因的大佬望不吝賜教。

記錄是從日記里復制過來的,Day 6/7 確實是最艱難的兩天,其實代碼從一開始就沒什么錯,有問題的是又拍云的 CDN 加速,可怖的是我根本沒想到罪魁禍首是他。其實在兩天的重復測試中,因為實在是無計可施,我也有兩次懷疑 CDN 。第一次,我把域名解析到服務器 IP ,但測試結果仍然報錯,之后就又恢復了加速。第二次是在第七天的早上五點,當時頭很脹很難受就直接停了 CDN ,想著最后測試一下不行就去掉 CDN 的 https 證書用 http 訪問。那時我才發現,在我 ping 域名確定解析已經改變后(修改解析后大概 10 分鐘),域名又會間隙性被重新解析到 CDN (這個反復原因不知道為什么,阿里云的域名解析服務),第一次測試不準應該就是這個原因,稍長時間后就不再會了。解決后我有意恢復 CDN 加速測試,但始終沒找出究竟是哪一個配置導致了問題,所以最終我也沒能恢復加速。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
国产91成人video| 国产欧美日韩中文| 亚洲a中文字幕| 美女av一区二区三区| 亚洲第一精品久久忘忧草社区| 日韩亚洲精品电影| 日韩av不卡在线| 国产亚洲精品美女久久久| 伊人伊成久久人综合网小说| 国产福利精品av综合导导航| 欧美富婆性猛交| 国产在线观看一区二区三区| 中文字幕在线精品| 日韩成人av一区| 日本高清不卡在线| 国产精品日韩在线一区| 久久九九免费视频| 丝袜亚洲另类欧美重口| 91精品国产91久久久久久最新| 成人免费视频97| 中文字幕亚洲字幕| 亚洲在线www| 国产亚洲激情视频在线| 欧美成人高清视频| 亚洲va久久久噜噜噜久久天堂| 亚洲男人的天堂网站| 伊人久久男人天堂| 91精品国产91久久久久久不卡| 国产69久久精品成人看| 国产精品久久久久久超碰| 日韩在线观看成人| 久久影院中文字幕| 日韩大陆欧美高清视频区| 亚洲人成网站免费播放| 97超碰蝌蚪网人人做人人爽| 日韩欧美亚洲国产一区| 国产精国产精品| 最近2019年手机中文字幕| 国产一区二区三区丝袜| 日韩av网址在线| 精品久久久久久亚洲国产300| 日韩av在线免费播放| 亚洲精品一二区| 日韩免费在线视频| 一区二区日韩精品| 欧美精品在线网站| 日韩成人av在线播放| 精品中文字幕在线观看| 成人精品视频99在线观看免费| 国产精品高清网站| 欧美日韩黄色大片| 91九色综合久久| 亚洲精品不卡在线| 成人免费观看网址| 91精品视频一区| 91精品久久久久久| 粗暴蹂躏中文一区二区三区| 奇米四色中文综合久久| 日韩精品视频观看| 久久亚洲综合国产精品99麻豆精品福利| 伊人久久综合97精品| 欧美一级成年大片在线观看| 久久亚洲综合国产精品99麻豆精品福利| 久久久黄色av| 九九热精品视频在线播放| 91国内揄拍国内精品对白| 亚洲综合成人婷婷小说| 亚洲国产小视频在线观看| 亚洲人成自拍网站| 日韩精品免费看| 欧美在线精品免播放器视频| 97视频免费在线看| 日韩a**站在线观看| 国产亚洲欧洲高清一区| 久久久久久久91| 亚洲人成啪啪网站| 在线播放国产一区二区三区| 亚洲一二三在线| 精品亚洲一区二区三区四区五区| 亚洲一二三在线| 久久频这里精品99香蕉| 欧美日韩裸体免费视频| 91手机视频在线观看| 亚洲精品久久久久中文字幕欢迎你| 欧美高清性猛交| 亚洲国产日韩欧美在线动漫| 91精品啪在线观看麻豆免费| 亚洲丝袜在线视频| 欧美激情精品久久久久久久变态| 国产精品久久久久久av下载红粉| 1769国内精品视频在线播放| 一区二区亚洲欧洲国产日韩| 日本一区二区在线免费播放| 国产区精品在线观看| 亚洲国产第一页| 精品日韩中文字幕| 91精品视频在线免费观看| 国产精品www色诱视频| 国产日本欧美一区| 久久天天躁狠狠躁老女人| 九九九久久久久久| 日韩电影第一页| 亚洲人成77777在线观看网| 日本高清久久天堂| 91精品国产高清久久久久久91| 色无极影院亚洲| 久久国产精品电影| 亚洲а∨天堂久久精品喷水| 欧美又大又粗又长| 欧美日韩在线免费| 国产精品久久久久久一区二区| 45www国产精品网站| 亚州成人av在线| 国产精品视频公开费视频| 久久男人av资源网站| 国产成人短视频| 国产日韩欧美影视| 国产日韩欧美成人| www国产亚洲精品久久网站| 亚洲男人第一网站| 亚洲人午夜精品| 亚洲网址你懂得| 国产精品久久久久福利| 中文字幕久精品免费视频| 亚洲成人激情在线观看| 亚洲欧美国产高清va在线播| 国产精品网红福利| 亚洲一区av在线播放| 久久中国妇女中文字幕| 国产91色在线|免| 欧美激情第1页| 欧美日韩激情视频8区| 亚洲精品国产精品乱码不99按摩| 91免费精品视频| 91国语精品自产拍在线观看性色| 91精品成人久久| 成人两性免费视频| 另类图片亚洲另类| 国产91成人在在线播放| 国产在线精品一区免费香蕉| 亚洲第一视频网站| 欧美成人午夜视频| 色综久久综合桃花网| 国自产精品手机在线观看视频| 91精品久久久久久久久久久| 国产日韩欧美夫妻视频在线观看| 成人性教育视频在线观看| 日韩精品极品在线观看播放免费视频| 国产视频999| 亚洲欧洲成视频免费观看| 97视频在线观看视频免费视频| 国产精品一区久久| 欧美精品在线网站| 91久久国产婷婷一区二区| 成人福利在线视频| 8x拔播拔播x8国产精品| 欧美日韩中文字幕日韩欧美| 一区二区av在线| 91精品国产乱码久久久久久蜜臀| 亚洲天天在线日亚洲洲精| 日韩av成人在线| 蜜臀久久99精品久久久久久宅男| 日韩在线观看成人| 日韩中文字幕国产|