欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Vue+Koa2+mongoose寫一個像素繪板的實現(xiàn)方法

 更新時間:2019年09月10日 15:01:55   作者:莫得鹽  
這篇文章主要介紹了Vue+Koa2+mongoose寫一個像素繪板的實現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

前言

GitHub: server | 前端

為什么是繪板:v2ex

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

技術(shù)棧

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

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

node-canvas

安裝

node-canvas 是我目前遇到過最難安裝的依賴,以至于我根本不想在 Windows 下安裝他,它的功能依賴很多系統(tǒng)下默認(rèn)不存在的包,在 Github 上也能看到很多 issue 的標(biāo)簽是 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

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

使用示例

參考文檔很容易就能掌握基本用法,下方例子中先取到像素點數(shù)據(jù)生成 ImageData ,然后通過 putImageData 把歷史數(shù)據(jù)畫到 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

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

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/letsencrypt
cd letsencrypt
# 自動生成證書(環(huán)境安裝完畢后會有兩次確認(rèn)),證書目錄 /etc/letsencrypt/live/{輸入的第一個域名} 我這里是 /etc/letsencrypt/live/www.luwuer.com/
./letsencrypt-auto certonly --standalone --email html6@foxmail.com -d www.luwuer.com -d luwuer.com

自動續(xù)期

# 進入定時任務(wù)編輯
crontab -e
# 提交申請,我這里設(shè)置每兩月一次,過期時間為三月
* * * */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,并設(shè)置有效期為“6307200秒”(6個月),包括子域名(根據(jù)情況可刪掉),預(yù)加載到瀏覽器緩存(根據(jù)情況可刪掉)
 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是用于在線查詢證書吊銷情況的服務(wù),使用OCSP Stapling能將證書有效狀態(tài)的信息緩存到服務(wù)器,提高TLS握手速度
 ssl_stapling_verify on;
 #OCSP Stapling 驗證開啟
 ssl_stapling on; 
 #用于查詢OCSP服務(wù)器的DNS 
 resolver 8.8.8.8 8.8.4.4 valid=300s;
 # DH-Key交換密鑰文件位置
 ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
 # 指定協(xié)議 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;
 # 由服務(wù)器協(xié)商最佳的加密算法
 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;
}

附錄

數(shù)據(jù)庫存儲結(jié)構(gòu)思考?xì)v程

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

以點為對象存儲,也就是說會有 524,288 條數(shù)據(jù)

  1. 顏色 rbga 存儲,后優(yōu)化為 rgb 存儲
  2. 顏色 16 進制存儲

整個畫布數(shù)據(jù)當(dāng)作一條數(shù)據(jù)存儲

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

1.1

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

1.2

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

結(jié)構(gòu) 2 如果取數(shù)據(jù)不是毫秒級,就是死刑,因為這種結(jié)構(gòu)下單個像素變動就需要存儲整個圖片數(shù)據(jù)

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

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 的實際存儲大小

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

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

數(shù)據(jù)傳輸思考?xì)v程

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

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

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

工作記錄

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

Day 4 說的實際問題,我只能大概定位在 NodeJS 變量大小限制或?qū)ο髠€數(shù)限制,因為在我將 50w 長度 Array[Object] 轉(zhuǎn)換為 200w 長度 Array[Number] 后問題消失了,知道具體原因的大佬望不吝賜教。

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

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • Vue?2?和?Vue?3?中?toRefs函數(shù)的不用用法

    Vue?2?和?Vue?3?中?toRefs函數(shù)的不用用法

    Vue?是一款流行的JavaScript?框架,用于構(gòu)建用戶界面,在Vue2和?Vue3中,都存在一個名為toRefs的函數(shù),但其行為在這兩個版本中有所不同,這篇文章主要介紹了Vue2和Vue3中toRefs的區(qū)別,需要的朋友可以參考下
    2023-08-08
  • Vue中使用icon的幾種常用方法

    Vue中使用icon的幾種常用方法

    這篇文章主要給大家介紹了關(guān)于Vue中使用icon的幾種常用方法,icon圖標(biāo)的使用 Vue是現(xiàn)在前端最流行的框架之一,作為前端開發(fā)人員應(yīng)該要熟練的掌握它,需要的朋友可以參考下
    2023-07-07
  • Vue+Echarts繪制餅圖的示例詳解

    Vue+Echarts繪制餅圖的示例詳解

    這篇文章主要為大家詳細(xì)介紹了如何利用Vue和Echarts實現(xiàn)繪制餅圖,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-03-03
  • Vue resource中的GET與POST請求的實例代碼

    Vue resource中的GET與POST請求的實例代碼

    本篇文章主要介紹了Vue resource中的GET與POST請求的實例代碼,非常具有實用價值,需要的朋友可以參考下
    2017-07-07
  • WebPack配置vue多頁面的技巧

    WebPack配置vue多頁面的技巧

    這篇文章主要介紹了WebPack配置vue多頁面的方法,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下
    2018-05-05
  • Vue.js數(shù)字輸入框組件使用方法詳解

    Vue.js數(shù)字輸入框組件使用方法詳解

    這篇文章主要為大家詳細(xì)介紹了Vue.js數(shù)字輸入框組件的使用方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-10-10
  • 詳解webpack打包vue時提取css

    詳解webpack打包vue時提取css

    本篇文章主要介紹了詳解webpack打包vue時提取css,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-05-05
  • Element中el-form表單舉例詳解

    Element中el-form表單舉例詳解

    Form組件提供了表單驗證的功能,只需要通過屬性傳入約定的驗證規(guī)則,并將Form-Item的屬性設(shè)置為需校驗的字段名即可,下面這篇文章主要給大家介紹了關(guān)于Element中el-form表單的相關(guān)資料,需要的朋友可以參考下
    2023-01-01
  • Vue.js中的extend綁定節(jié)點并顯示的方法

    Vue.js中的extend綁定節(jié)點并顯示的方法

    在本篇內(nèi)容里小編給大家整理了關(guān)于Vue.js中的extend綁定節(jié)點并顯示的方法以及相關(guān)知識點,需要的朋友們學(xué)習(xí)下。
    2019-06-06
  • vue 處理跨域問題及解決方法小結(jié)

    vue 處理跨域問題及解決方法小結(jié)

    跨域問題的出現(xiàn)是因為瀏覽器的同源策略問題,如果沒有同源策略我們的瀏覽器將會十分的不安全,隨時都可能受到攻擊,今天小編通過本文給大家介紹下vue 處理跨域問題,感興趣的朋友一起看看吧
    2021-09-09

最新評論