云開發(fā) 云開發(fā)與 Node.js

2020-07-20 11:22 更新

云函數(shù)的運(yùn)行環(huán)境是 Node.js,我們可以在云函數(shù)中使用Nodejs內(nèi)置模塊以及使用 npm 安裝第三方依賴來幫助我們更快的開發(fā)。借助于一些優(yōu)秀的開源項(xiàng)目,避免了我們重復(fù)造輪子,相比于小程序端,能夠大大擴(kuò)展云函數(shù)的使用

云函數(shù)與Nodejs

由于云函數(shù)與Nodejs息息相關(guān),需要我們對云函數(shù)與Node的模塊以及Nodejs的一些基本知識(shí)有一些基本的了解。下面只介紹一些基礎(chǔ)的概念,如果你想詳細(xì)深入了解,建議去翻閱一下Nodejs的官方技術(shù)文檔:

技術(shù)文檔:Nodejs API 中文技術(shù)文檔

Nodejs的內(nèi)置模塊

在前面我們已經(jīng)接觸過Nodejs的fs模塊、path模塊,這些我們稱之為Nodejs的內(nèi)置模塊,內(nèi)置模塊不需要我們使用npm install下載,就可以直接使用require引入:

const fs = require('fs')
const path = require('path')

Nodejs的常用內(nèi)置模塊以及功能如下所示,這些模塊都是可以在云函數(shù)里直接使用的:

  • fs 模塊:文件目錄的創(chuàng)建、刪除、查詢以及文件的讀取和寫入,下面的createReadStream方法類似于讀取文件,
  • path 模塊:提供了一些用于處理文件路徑的API
  • url模塊:用于處理與解析 URL
  • http模塊:用于創(chuàng)建一個(gè)能夠處理和響應(yīng) http 響應(yīng)的服務(wù)
  • querystring模塊:解析查詢字符串
  • until模塊 :提供用于解析和格式化 URL 查詢字符串的實(shí)用工具;
  • net模塊:用于創(chuàng)建基于流的 TCP 或 IPC 的服務(wù)器
  • crypto模塊:提供加密功能,包括對 OpenSSL 的哈希、HMAC、加密、解密、簽名、以及驗(yàn)證功能的一整套封裝

在云函數(shù)中使用HTTP請求訪問第三方服務(wù)可以不受域名限制,即不需要像小程序端一樣,要將域名添加到request合法域名里;也不受http和https的限制,沒有域名只有IP都是可以的,所以云函數(shù)可以應(yīng)用的場景非常多,即能方便的調(diào)用第三方服務(wù),也能夠充當(dāng)一個(gè)功能復(fù)雜的完整應(yīng)用的后端。不過需要注意的是,云函數(shù)是部署在云端,有些局域網(wǎng)等終端通信的業(yè)務(wù)只能在小程序里進(jìn)行。

常用變量

module、exports、require

require用于引入模塊、 JSON、或本地文件。 可以從 node_modules 引入模塊,可以使用相對路徑(例如 ./、)引入本地模塊或 JSON 文件,路徑會(huì)根據(jù) __dirname 定義的目錄名或當(dāng)前工作目錄進(jìn)行處理。

node模塊化遵循的是commonjs規(guī)范,CommonJs定義的模塊分為: 模塊標(biāo)識(shí)(module)、模塊導(dǎo)出(exports) 、模塊引用(require)。

在node中,一個(gè)文件即一個(gè)模塊,使用exports和require來進(jìn)行處理。

exports表示該模塊運(yùn)行時(shí)生成的導(dǎo)出對象。如果按確切的文件名沒有找到模塊,則 Node.js 會(huì)嘗試帶上 .js、 .json 或 .node 拓展名再加載。 .js 文件會(huì)被解析為 JavaScript 文本文件, .json 文件會(huì)被解析為 JSON 文本文件。 .node 文件會(huì)被解析為通過 process.dlopen() 加載的編譯后的插件模塊。以 '/' 為前綴的模塊是文件的絕對路徑。 例如, require('/home/marco/foo.js') 會(huì)加載 /home/marco/foo.js 文件。以 './' 為前綴的模塊是相對于調(diào)用 require() 的文件的。 也就是說, circle.js 必須和 foo.js 在同一目錄下以便于 require('./circle') 找到它。

module.exports 用于指定一個(gè)模塊所導(dǎo)出的內(nèi)容,即可以通過 require() 訪問的內(nèi)容。

// 引入本地模塊:
const myLocalModule = require('./path/myLocalModule');

 
// 引入 JSON 文件:
const jsonData = require('./path/filename.json');

 
// 引入 node_modules 模塊或 Node.js 內(nèi)置模塊:
const crypto = require('crypto');

wx-server-sdk的模塊

tcb-admin-node、protobuf、jstslib

第三方模塊

Nodejs有 npm官網(wǎng)地址

Nodejs庫推薦:awesome Nodejs

當(dāng)沒有以 '/'、 './' 或 '../' 開頭來表示文件時(shí),這個(gè)模塊必須是一個(gè)核心模塊或加載自 node_modules 目錄,比如wx-server-sdk就加載自node_modules文件夾:

const cloud = require('wx-server-sdk')

Lodash實(shí)用工具庫

Lodash是一個(gè)一致性、模塊化、高性能的 JavaScript 實(shí)用工具庫,通過降低 array、number、objects、string 等數(shù)據(jù)類型的使用難度從而讓 JavaScript 變得更簡單。Lodash 的模塊化方法非常適用于:遍歷 array、object 和 string;對值進(jìn)行操作和檢測;創(chuàng)建符合功能的函數(shù)。

技術(shù)文檔:Lodash官方文檔、Lodash中文文檔

使用開發(fā)者工具新建一個(gè)云函數(shù),比如lodash,然后在package.json增加lodash最新版latest的依賴:

    "dependencies": {
        "lodash": "latest"
    }

在index.js里的代碼修改為如下,這里使用到了lodash的chunk方法來分割數(shù)組:

const cloud = require('wx-server-sdk')
var _ = require('lodash');
cloud.init({
    env: cloud.DYNAMIC_CURRENT_ENV,
  })
exports.main = async (event, context) => {
    //將數(shù)組拆分為長度為2的數(shù)組
    const arr= _.chunk(['a', 'b', 'c', 'd'], 2);
    return arr
}

右鍵lodash云函數(shù)目錄,選擇“在終端中打開”,npm install 安裝模塊之后右鍵部署并上傳所有文件。我們就可以通過多種方式來調(diào)用它(前面已詳細(xì)介紹)即可獲得結(jié)果。Lodash作為工具,非常好用且實(shí)用,它的源碼也非常值得學(xué)習(xí),更多相關(guān)內(nèi)容則需要大家去Github和官方技術(shù)文檔里深入了解。

awesome Nodejs頁面我們了解到還有Ramba、immutable、Mout等類似工具庫,這些都非常推薦。借助于Github的awesome清單,我們就能一手掌握最酷炫好用的開源項(xiàng)目,避免了自己去收集收藏。

moment時(shí)間處理

開發(fā)小程序時(shí)經(jīng)常需要格式化時(shí)間、處理相對時(shí)間、日歷時(shí)間以及時(shí)間的多語言問題,這個(gè)時(shí)候就可以使用比較流行的momentjs了。

技術(shù)文檔:moment官方文檔moment中文文檔

使用開發(fā)者工具新建一個(gè)云函數(shù),比如moment,然后在package.json增加moment最新版latest的依賴:

    "dependencies": {
        "moment": "latest"
    }

在index.js里的代碼修改為如下,我們將moment區(qū)域設(shè)置為中國,將時(shí)間格式化為 十二月 23日 2019, 4:13:29 下午的樣式以及相對時(shí)間多少分鐘前:

const cloud = require('wx-server-sdk')
const moment = require("moment");
cloud.init({
    env: cloud.DYNAMIC_CURRENT_ENV,
  })
exports.main = async (event, context) => {
    moment.locale('zh-cn');
    time1 = moment().format('MMMM Do YYYY, h:mm:ss a');
    time2 = moment().startOf('hour').fromNow();
    return  { time1,time2}
}

不過云函數(shù)中的時(shí)區(qū)為 UTC+0,不是 UTC+8,格式化得到的時(shí)間和在國內(nèi)的時(shí)間是有8個(gè)小時(shí)的時(shí)間差的,我們可以給小時(shí)數(shù)+8,也可以修改時(shí)區(qū)。云函數(shù)修改時(shí)區(qū)我們可以使用timezone依賴(和moment是同一個(gè)開源作者)。

技術(shù)文檔:timezone技術(shù)文檔

在package.json增加moment-timezone最新版latest的依賴,然后修改上面相應(yīng)的代碼即可,使用起來非常方便:

const moment = require('moment-timezone');
time1 = moment().tz('Asia/Shanghai').format('MMMM Do YYYY, h:mm:ss a');

獲取公網(wǎng)IP

有時(shí)我們希望能夠獲取到服務(wù)器的公網(wǎng)IP,比如用于IP地址的白名單,或者想根據(jù)IP查詢到服務(wù)器所在的地址,ipify就是一個(gè)免費(fèi)好用的依賴,通過它我們也可以獲取到云函數(shù)所在服務(wù)器的公網(wǎng)IP。

技術(shù)文檔:ipify Github地址

使用開發(fā)者工具新建一個(gè)getip的云函數(shù),然后輸入以下代碼,并在package.json的”dependencies”里新增 "ipify":"latest" ,即最新版的ipify依賴:

const cloud = require('wx-server-sdk')
    const ipify = require('ipify');
    cloud.init({
      env: cloud.DYNAMIC_CURRENT_ENV,
    })
    exports.main = async (event, context) => {
      return await ipify({ useIPv6: false })
    }

然后右鍵getip云函數(shù)根目錄,選擇在終端中打開,輸入npm install安裝依賴,之后上傳并部署所有文件。我們可以在小程序端調(diào)用這個(gè)云函數(shù),就可以得到云函數(shù)服務(wù)器的公網(wǎng)IP,這個(gè)IP是隨機(jī)而有限的幾個(gè),反復(fù)調(diào)用getip,就能夠窮舉所有云函數(shù)所在服務(wù)器的ip了。

可能你會(huì)在使用云函數(shù)連接數(shù)據(jù)庫或者用云函數(shù)來建微信公眾號(hào)的后臺(tái)時(shí)需要用到IP白名單,我們可以把這些ip都添加到白名單里面,這樣云函數(shù)就可以做很多事情啦。

Buffer文件流

const cloud = require('wx-server-sdk')
    cloud.init({
      env: cloud.DYNAMIC_CURRENT_ENV,
    })
    exports.main = async (event, context) => {
      const fileID = 'cloud://xly-xrlur.786c-xly-xrlur-1300446086/cloudbase/1576500614167-520.png'
      const res = await cloud.downloadFile({
        fileID: fileID,
      })
      const buffer = res.fileContent
      return buffer.toString('base64')
    }
  getServerImg(){
    wx.cloud.callFunction({
      name: 'downloadimg',
      success: res => {
        console.log("云函數(shù)返回的數(shù)據(jù)",res.result)
        this.setData({
          img:res.result
        })
      },
      fail: err => {
        console.error('云函數(shù)調(diào)用失?。?, err)
      }
    })
  }
<image width="400px" height="200px" src="data:image/jpeg;base64,{{img}}"></image>

Buffer String

Buffer JSON

圖像處理sharp

sharp是一個(gè)高速圖像處理庫,可以很方便的實(shí)現(xiàn)圖片編輯操作,如裁剪、格式轉(zhuǎn)換、旋轉(zhuǎn)變換、濾鏡添加、圖片合成(如添加水?。?、圖片拼接等,支持JPEG, PNG, WebP, TIFF, GIF 和 SVG格式。在云函數(shù)端使用sharp來處理圖片,而云存儲(chǔ)則可以作為服務(wù)端和小程序端來傳遞圖片的橋梁。

技術(shù)文檔:sharp官方技術(shù)文檔

使用開發(fā)者工具新建一個(gè)

const cloud = require('wx-server-sdk')
const fs = require('fs')
const path = require('path')
cloud.init({
    env: cloud.DYNAMIC_CURRENT_ENV,
})
const sharp = require('sharp');
exports.main = async (event, context) => {
    //這里換成自己的fileID,也可以在小程序端上傳文件之后,把fileID傳進(jìn)來event.fileID
    const fileID = 'cloud://xly-xrlur.786c-xly-xrlur-1300446086/1572315793628-366.png'

 
    //要用云函數(shù)處理圖片,需要先下載圖片,返回的圖片類型為Buffer
    const res = await cloud.downloadFile({
      fileID: fileID,
    })
    const buffer = res.fileContent  

 
    //sharp對圖片進(jìn)行處理之后,保存為output.png,也可以直接保存為Buffer
    await sharp(buffer).rotate().resize(200).toFile('output.png')

 
    // 云函數(shù)讀取模塊目錄下的圖片,并上傳到云存儲(chǔ)
    const fileStream = await fs.createReadStream(path.join(__dirname, 'output.png'))
    return await cloud.uploadFile({
        cloudPath: 'sharpdemo.jpg',
        fileContent: fileStream,
    })  
}

也可以讓sharp不需要先toFile轉(zhuǎn)成圖片,而是直接轉(zhuǎn)成Buffer,這樣就可以直接作為參數(shù)傳給fileContent上傳到云存儲(chǔ),如:

    const buffer2 = await sharp(buffer).rotate().resize(200).toBuffer();
    return await cloud.uploadFile({
        cloudPath: 'sharpdemo2.jpg',
        fileContent: buffer2,
    })

連接數(shù)據(jù)庫MySQL

公網(wǎng)連接數(shù)據(jù)庫MySQL

技術(shù)文檔:Sequelize

const sequelize = new Sequelize('database', 'username', 'password',  {
  host: 'localhost',    //數(shù)據(jù)庫地址,默認(rèn)本機(jī)
  port:'3306',
  dialect: 'mysql',
  pool: {   //連接池設(shè)置
    max: 5, //最大連接數(shù)
    min: 0, //最小連接數(shù)
    idle: 10000
  },
 });
無論是MySQL,還是PostgreSQL、Redis、MongoDB等其他數(shù)據(jù)庫,只要我們在

私有網(wǎng)絡(luò)連接MySQL

默認(rèn)情況下,云開發(fā)的函數(shù)部署在公共網(wǎng)絡(luò)中,只可以訪問公網(wǎng)。如果開發(fā)者需要訪問騰訊云的 Redis、TencentDB、CVM、Kafka 等資源,需要建立私有網(wǎng)絡(luò)來確保數(shù)據(jù)安全及連接安全。

連接數(shù)據(jù)庫Redis

const cloud = require('wx-server-sdk')
cloud.init({
    env: cloud.DYNAMIC_CURRENT_ENV,
  })
const Redis = require('ioredis')
const redis = new Redis({
  port: 6379,
  host: '10.168.0.15', 
  family: 4, 
  password: 'CloudBase2018',
  db: 0,
})

 
exports.main = async (event, context) => {
    const wxContext = cloud.getWXContext()
    const cacheKey = wxContext.OPENID
    const cache = await redis.get(cacheKey) 
    if (!cache) {
      const result = await new Promise((resolve, reject) => {
        setTimeout(() => resolve(Math.random()), 2000)
      })
      redis.set(cacheKey, result, 'EX', 3600)
      return result
    } else {
      return cache
    }
  }

二維碼qrcode

技術(shù)文檔:node-qrcode Github地址

郵件處理

技術(shù)文檔:Nodemailer Github地址、Nodemailer官方文檔

使用開發(fā)者工具創(chuàng)建一個(gè)云函數(shù),比如nodemail,然后在package.json增加nodemailer最新版latest的依賴:

  "dependencies": {
    "nodemailer": "latest"
  }

發(fā)送郵件服務(wù)器:smtp.qq.com,使用SSL,端口號(hào)465或587

const cloud = require('wx-server-sdk')
cloud.init({
  env: cloud.DYNAMIC_CURRENT_ENV,
})
exports.main = async (event, context) => {
  const nodemailer = require("nodemailer");
  let transporter = nodemailer.createTransport({
    host: "smtp.qq.com", //SMTP服務(wù)器地址
    port: 465, //端口號(hào),通常為465,587,25,不同的郵件客戶端端口號(hào)可能不一樣
    secure: true, //如果端口是465,就為true;如果是587、25,就填false
    auth: {
      user: "344169902@qq.com",  //你的郵箱賬號(hào)
      pass: "你的QQ郵箱授權(quán)碼"   //郵箱密碼,QQ的需要是獨(dú)立授權(quán)碼
    }
  });

 
  let message = {
    from: '來自李東bbsky <344169902@qq.com>',   //你的發(fā)件郵箱
    to: '你要發(fā)送給誰', //你要發(fā)給誰
    // cc:'',  支持cc 抄送
    // bcc: '', 支持bcc 密送
    subject: '歡迎大家參與云開發(fā)技術(shù)訓(xùn)練營活動(dòng)',

 
    //支持text純文字,html代碼
    text: '歡迎大家',
    html:
      '<p><b>你好:</b><img src="https://hackwork-1251009918.cos.ap-shanghai.myqcloud.com/handbook/html5/weapp.jpg" rel="external nofollow" /></p>' +
      '<p>歡迎歡迎<br/></p>',
    attachments: [  //支持多種附件形式,可以是String, Buffer或Stream
      {
        filename: 'image.png',
        content: Buffer.from(
          'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD/' +
          '//+l2Z/dAAAAM0lEQVR4nGP4/5/h/1+G/58ZDrAz3D/McH8yw83NDDeNGe4U' +
          'g9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAAAAElFTkSuQmCC',
          'base64'
        ),
      },
    ]
  };

 
  let res = await transporter.sendMail(message);
  return res;
}

Excel文檔處理

Excel是存儲(chǔ)數(shù)據(jù)比較常見的格式,那如何讓云函數(shù)擁有讀寫Excel文件的能力呢?我們可以在Github上搜索關(guān)鍵詞“Node Excel”,去篩選Star比較多,條件比較契合的。

Github地址:node-xlsx

使用開發(fā)者工具新建一個(gè)云函數(shù),在package.json里添加latest最新版的node-xlsx:

"dependencies": {
  "wx-server-sdk": "latest",
  "node-xlsx": "latest"
}

讀取云存儲(chǔ)的Excel文件

const cloud = require('wx-server-sdk')
cloud.init({
  env: cloud.DYNAMIC_CURRENT_ENV,
})

 
const xlsx = require('node-xlsx');
const db = cloud.database()

 
exports.main = async (event, context) => {
  const fileID = 'cloud://xly-xrlur.786c-xly-xrlur-1300446086/china.csv'
  const res = await cloud.downloadFile({
    fileID: fileID,
  })
  const buffer = res.fileContent

 
  const tasks = [] 
  var sheets = xlsx.parse(buffer); 
  sheets.forEach(function (sheet) {
    for (var rowId in sheet['data']) {
      console.log(rowId);
      var row = sheet['data'][rowId]; 
      if (rowId > 0 && row) { 
        const promise = db.collection('chinaexcel')
          .add({
            data: {
              city: row[0], 
              province: row[1], 
              city_area: row[2], 
              builtup_area: row[3],
              reg_pop: row[4],
              resident_pop: row[5],
              gdp: row[6]
            }
          })
        tasks.push(promise)
      }
    }
  });

 
  let result = await Promise.all(tasks).then(res => {
    return res
  }).catch(function (err) {
    return err
  })
  return result
}

將數(shù)據(jù)庫里的數(shù)據(jù)保存為CSV

技術(shù)文檔:json2CSV

HTTP處理

got、superagent、request、axios、request-promise

盡管云函數(shù)的Nodejs版本比較低(目前為8.9),但絕大多數(shù)模塊我們都可以使用Nodejs 12或13的環(huán)境來測試,不過有時(shí)候也要留意有些模塊不支持8.9,比如got 10.0.1以上的版本。

node中,http模塊也可作為客戶端使用(發(fā)送請求),第三方模塊request對其使用方法進(jìn)行了封裝,操作更方便!所以來介紹一下request模塊

get請求

const cloud = require('wx-server-sdk')
cloud.init({
  env: cloud.DYNAMIC_CURRENT_ENV,
})
const rp = require('request-promise')
exports.main = async (event, context) => {
  const options = {
    url: 'https://news-at.zhihu.com/api/4/news/latest',
    json: true,
    method: 'GET',
  };
  return await rp(options)
}

post請求

結(jié)合文件流

request('https://www.jmjc.tech/public/home/img/flower.png').pipe(fs.createWriteStream('./flower.png')) // 下載文件到本地

加解密Crypto

crypto模塊是nodejs的核心模塊之一,它提供了安全相關(guān)的功能,包含對 OpenSSL 的哈希、HMAC、加密、解密、簽名、以及驗(yàn)證功能的一整套封裝。由于crypto模塊是內(nèi)置模塊,我們引入它是無需下載,就可以直接引入。

使用開發(fā)者工具新建一個(gè)云函數(shù),比如crypto,在index.js里輸入以下代碼,我們來了解一下crypto支持哪些加密算法,并以MD5加密為例:

const cloud = require('wx-server-sdk')
cloud.init({
    env: cloud.DYNAMIC_CURRENT_ENV,
})
const crypto = require('crypto');
exports.main = async (event, context) => {
    const hashes = crypto.getHashes(); //獲取crypto支持的加密算法種類列表

 
    //md5 加密 CloudBase2020 返回十六進(jìn)制
    var md5 = crypto.createHash('md5');
    var message = 'CloudBase2020';
    var digest = md5.update(message, 'utf8').digest('hex');   

 
    return {
        "crypto支持的加密算法種類":hashes,
        "md5加密返回的十六進(jìn)制":digest
    };
}

將云函數(shù)部署之后調(diào)用從返回的結(jié)果我們可以了解到,云函數(shù)crypto模塊支持46種加密算法。

發(fā)短信

“qcloudsms_js”: “^0.1.1”

const cloud = require('wx-server-sdk')
const QcloudSms = require("qcloudsms_js")
const appid = 1400284950 // 替換成您申請的云短信 AppID 以及 AppKey
const appkey = "a33b602345f5bb866f040303ac6f98ca"
const templateId = 472078 // 替換成您所申請模板 ID
const smsSign = "統(tǒng)計(jì)小助理" // 替換成您所申請的簽名

 
cloud.init()

 
// 云函數(shù)入口函數(shù)
exports.main = async (event, context) => new Promise((resolve, reject) => {
  /*單發(fā)短信示例為完整示例,更多功能請直接替換以下代碼*/
  var qcloudsms = QcloudSms(appid, appkey);
  var ssender = qcloudsms.SmsSingleSender();
  var params = ["1234", "15"];
  // 獲取發(fā)送短信的手機(jī)號(hào)碼
  var mobile = event.mobile
  // 獲取手機(jī)號(hào)國家/地區(qū)碼
  var nationcode = event.nationcode
  ssender.sendWithParam(nationcode, mobile, templateId, params, smsSign, "", "", (err, res, resData) => {
    /*設(shè)置請求回調(diào)處理, 這里只是演示,您需要自定義相應(yīng)處理邏輯*/
    if (err) {
      console.log("err: ", err);
      reject({ err })
    } else {
      resolve({ res: res.req, resData })
    }
  }
  );

 
})

使用開發(fā)者工具

wx.cloud.callFunction({
  name: 'sendphone',
  data: {
    // mobile: '13217922526',
    mobile: '18565678773',
    nationcode: '86'
  },
  success: res => {
    console.log('[云函數(shù)] [sendsms] 調(diào)用成功')
    console.log(res)
  },
  fail: err => {
    console.error('[云函數(shù)] [sendsms] 調(diào)用失敗', err)
  }
})
以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)