云函數(shù)的運(yùn)行環(huán)境是 Node.js,我們可以在云函數(shù)中使用Nodejs內(nèi)置模塊以及使用 npm 安裝第三方依賴來幫助我們更快的開發(fā)。借助于一些優(yōu)秀的開源項(xiàng)目,避免了我們重復(fù)造輪子,相比于小程序端,能夠大大擴(kuò)展云函數(shù)的使用
由于云函數(shù)與Nodejs息息相關(guān),需要我們對云函數(shù)與Node的模塊以及Nodejs的一些基本知識(shí)有一些基本的了解。下面只介紹一些基礎(chǔ)的概念,如果你想詳細(xì)深入了解,建議去翻閱一下Nodejs的官方技術(shù)文檔:
技術(shù)文檔:Nodejs API 中文技術(shù)文檔
在前面我們已經(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ù)里直接使用的:
在云函數(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');
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是一個(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)目,避免了自己去收集收藏。
開發(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');
有時(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ù)就可以做很多事情啦。
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是一個(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ù)文檔: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ù)庫,只要我們在
默認(rèn)情況下,云開發(fā)的函數(shù)部署在公共網(wǎng)絡(luò)中,只可以訪問公網(wǎng)。如果開發(fā)者需要訪問騰訊云的 Redis、TencentDB、CVM、Kafka 等資源,需要建立私有網(wǎng)絡(luò)來確保數(shù)據(jù)安全及連接安全。
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
}
}
技術(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是存儲(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
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模塊
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)
}
request('https://www.jmjc.tech/public/home/img/flower.png').pipe(fs.createWriteStream('./flower.png')) // 下載文件到本地
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種加密算法。
“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)
}
})
更多建議: