云开发 云开发与 Node.js
云函数的运行环境是 Node.js,我们可以在云函数中使用Nodejs内置模块以及使用 npm 安装第三方依赖来帮助我们更快的开发。借助于一些优秀的开源项目,避免了我们重复造轮子,相比于小程序端,能够大大扩展云函数的使用
云函数与Nodejs
由于云函数与Nodejs息息相关,需要我们对云函数与Node的模块以及Nodejs的一些基本知识有一些基本的了解。下面只介绍一些基础的概念,如果你想详细深入了解,建议去翻阅一下Nodejs的官方技术文档:
技术文档:Nodejs API 中文技术文档
Nodejs的内置模块
在前面我们已经接触过Nodejs的fs模块、path模块,这些我们称之为Nodejs的内置模块,内置模块不需要我们使用npm install下载,就可以直接使用require引入:
const fs = require('fs')
const path = require('path')
Nodejs的常用内置模块以及功能如下所示,这些模块都是可以在云函数里直接使用的:
- fs 模块:文件目录的创建、删除、查询以及文件的读取和写入,下面的createReadStream方法类似于读取文件,
- path 模块:提供了一些用于处理文件路径的API
- url模块:用于处理与解析 URL
- http模块:用于创建一个能够处理和响应 http 响应的服务
- querystring模块:解析查询字符串
- until模块 :提供用于解析和格式化 URL 查询字符串的实用工具;
- net模块:用于创建基于流的 TCP 或 IPC 的服务器
- crypto模块:提供加密功能,包括对 OpenSSL 的哈希、HMAC、加密、解密、签名、以及验证功能的一整套封装
在云函数中使用HTTP请求访问第三方服务可以不受域名限制,即不需要像小程序端一样,要将域名添加到request合法域名里;也不受http和https的限制,没有域名只有IP都是可以的,所以云函数可以应用的场景非常多,即能方便的调用第三方服务,也能够充当一个功能复杂的完整应用的后端。不过需要注意的是,云函数是部署在云端,有些局域网等终端通信的业务只能在小程序里进行。
常用变量
module、exports、require
require用于引入模块、 JSON、或本地文件。 可以从 node_modules 引入模块,可以使用相对路径(例如 ./、)引入本地模块或 JSON 文件,路径会根据 __dirname 定义的目录名或当前工作目录进行处理。
node模块化遵循的是commonjs规范,CommonJs定义的模块分为: 模块标识(module)、模块导出(exports) 、模块引用(require)。
在node中,一个文件即一个模块,使用exports和require来进行处理。
exports表示该模块运行时生成的导出对象。如果按确切的文件名没有找到模块,则 Node.js 会尝试带上 .js、 .json 或 .node 拓展名再加载。 .js 文件会被解析为 JavaScript 文本文件, .json 文件会被解析为 JSON 文本文件。 .node 文件会被解析为通过 process.dlopen() 加载的编译后的插件模块。以 '/' 为前缀的模块是文件的绝对路径。 例如, require('/home/marco/foo.js') 会加载 /home/marco/foo.js 文件。以 './' 为前缀的模块是相对于调用 require() 的文件的。 也就是说, circle.js 必须和 foo.js 在同一目录下以便于 require('./circle') 找到它。
module.exports 用于指定一个模块所导出的内容,即可以通过 require() 访问的内容。
// 引入本地模块:
const myLocalModule = require('./path/myLocalModule');
// 引入 JSON 文件:
const jsonData = require('./path/filename.json');
// 引入 node_modules 模块或 Node.js 内置模块:
const crypto = require('crypto');
wx-server-sdk的模块
tcb-admin-node、protobuf、jstslib
第三方模块
Nodejs有 npm官网地址
Nodejs库推荐:awesome Nodejs
当没有以 '/'、 './' 或 '../' 开头来表示文件时,这个模块必须是一个核心模块或加载自 node_modules 目录,比如wx-server-sdk就加载自node_modules文件夹:
const cloud = require('wx-server-sdk')
Lodash实用工具库
Lodash是一个一致性、模块化、高性能的 JavaScript 实用工具库,通过降低 array、number、objects、string 等数据类型的使用难度从而让 JavaScript 变得更简单。Lodash 的模块化方法非常适用于:遍历 array、object 和 string;对值进行操作和检测;创建符合功能的函数。
技术文档:Lodash官方文档、Lodash中文文档
使用开发者工具新建一个云函数,比如lodash,然后在package.json增加lodash最新版latest的依赖:
"dependencies": {
"lodash": "latest"
}
在index.js里的代码修改为如下,这里使用到了lodash的chunk方法来分割数组:
const cloud = require('wx-server-sdk')
var _ = require('lodash');
cloud.init({
env: cloud.DYNAMIC_CURRENT_ENV,
})
exports.main = async (event, context) => {
//将数组拆分为长度为2的数组
const arr= _.chunk(['a', 'b', 'c', 'd'], 2);
return arr
}
右键lodash云函数目录,选择“在终端中打开”,npm install 安装模块之后右键部署并上传所有文件。我们就可以通过多种方式来调用它(前面已详细介绍)即可获得结果。Lodash作为工具,非常好用且实用,它的源码也非常值得学习,更多相关内容则需要大家去Github和官方技术文档里深入了解。
在awesome Nodejs页面我们了解到还有Ramba、immutable、Mout等类似工具库,这些都非常推荐。借助于Github的awesome清单,我们就能一手掌握最酷炫好用的开源项目,避免了自己去收集收藏。
moment时间处理
开发小程序时经常需要格式化时间、处理相对时间、日历时间以及时间的多语言问题,这个时候就可以使用比较流行的momentjs了。
技术文档:moment官方文档、moment中文文档
使用开发者工具新建一个云函数,比如moment,然后在package.json增加moment最新版latest的依赖:
"dependencies": {
"moment": "latest"
}
在index.js里的代码修改为如下,我们将moment区域设置为中国,将时间格式化为 十二月 23日 2019, 4:13:29 下午的样式以及相对时间多少分钟前:
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}
}
不过云函数中的时区为 UTC+0,不是 UTC+8,格式化得到的时间和在国内的时间是有8个小时的时间差的,我们可以给小时数+8,也可以修改时区。云函数修改时区我们可以使用timezone依赖(和moment是同一个开源作者)。
技术文档:timezone技术文档
在package.json增加moment-timezone最新版latest的依赖,然后修改上面相应的代码即可,使用起来非常方便:
const moment = require('moment-timezone');
time1 = moment().tz('Asia/Shanghai').format('MMMM Do YYYY, h:mm:ss a');
获取公网IP
有时我们希望能够获取到服务器的公网IP,比如用于IP地址的白名单,或者想根据IP查询到服务器所在的地址,ipify就是一个免费好用的依赖,通过它我们也可以获取到云函数所在服务器的公网IP。
技术文档:ipify Github地址
使用开发者工具新建一个getip的云函数,然后输入以下代码,并在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云函数根目录,选择在终端中打开,输入npm install安装依赖,之后上传并部署所有文件。我们可以在小程序端调用这个云函数,就可以得到云函数服务器的公网IP,这个IP是随机而有限的几个,反复调用getip,就能够穷举所有云函数所在服务器的ip了。
可能你会在使用云函数连接数据库或者用云函数来建微信公众号的后台时需要用到IP白名单,我们可以把这些ip都添加到白名单里面,这样云函数就可以做很多事情啦。
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("云函数返回的数据",res.result)
this.setData({
img:res.result
})
},
fail: err => {
console.error('云函数调用失败:', err)
}
})
}
<image width="400px" height="200px" src="data:image/jpeg;base64,{{img}}"></image>
Buffer String
Buffer JSON
图像处理sharp
sharp是一个高速图像处理库,可以很方便的实现图片编辑操作,如裁剪、格式转换、旋转变换、滤镜添加、图片合成(如添加水印)、图片拼接等,支持JPEG, PNG, WebP, TIFF, GIF 和 SVG格式。在云函数端使用sharp来处理图片,而云存储则可以作为服务端和小程序端来传递图片的桥梁。
技术文档:sharp官方技术文档
使用开发者工具新建一个
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传进来event.fileID
const fileID = 'cloud://xly-xrlur.786c-xly-xrlur-1300446086/1572315793628-366.png'
//要用云函数处理图片,需要先下载图片,返回的图片类型为Buffer
const res = await cloud.downloadFile({
fileID: fileID,
})
const buffer = res.fileContent
//sharp对图片进行处理之后,保存为output.png,也可以直接保存为Buffer
await sharp(buffer).rotate().resize(200).toFile('output.png')
// 云函数读取模块目录下的图片,并上传到云存储
const fileStream = await fs.createReadStream(path.join(__dirname, 'output.png'))
return await cloud.uploadFile({
cloudPath: 'sharpdemo.jpg',
fileContent: fileStream,
})
}
也可以让sharp不需要先toFile转成图片,而是直接转成Buffer,这样就可以直接作为参数传给fileContent上传到云存储,如:
const buffer2 = await sharp(buffer).rotate().resize(200).toBuffer();
return await cloud.uploadFile({
cloudPath: 'sharpdemo2.jpg',
fileContent: buffer2,
})
连接数据库MySQL
公网连接数据库MySQL
技术文档:Sequelize
const sequelize = new Sequelize('database', 'username', 'password', {
host: 'localhost', //数据库地址,默认本机
port:'3306',
dialect: 'mysql',
pool: { //连接池设置
max: 5, //最大连接数
min: 0, //最小连接数
idle: 10000
},
});
无论是MySQL,还是PostgreSQL、Redis、MongoDB等其他数据库,只要我们在
私有网络连接MySQL
默认情况下,云开发的函数部署在公共网络中,只可以访问公网。如果开发者需要访问腾讯云的 Redis、TencentDB、CVM、Kafka 等资源,需要建立私有网络来确保数据安全及连接安全。
连接数据库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
技术文档:node-qrcode Github地址
邮件处理
技术文档:Nodemailer Github地址、Nodemailer官方文档
使用开发者工具创建一个云函数,比如nodemail,然后在package.json增加nodemailer最新版latest的依赖:
"dependencies": {
"nodemailer": "latest"
}
发送邮件服务器:smtp.qq.com,使用SSL,端口号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服务器地址
port: 465, //端口号,通常为465,587,25,不同的邮件客户端端口号可能不一样
secure: true, //如果端口是465,就为true;如果是587、25,就填false
auth: {
user: "344169902@qq.com", //你的邮箱账号
pass: "你的QQ邮箱授权码" //邮箱密码,QQ的需要是独立授权码
}
});
let message = {
from: '来自李东bbsky <344169902@qq.com>', //你的发件邮箱
to: '你要发送给谁', //你要发给谁
// cc:'', 支持cc 抄送
// bcc: '', 支持bcc 密送
subject: '欢迎大家参与云开发技术训练营活动',
//支持text纯文字,html代码
text: '欢迎大家',
html:
'<p><b>你好:</b><img src="https://hackwork-1251009918.cos.ap-shanghai.myqcloud.com/handbook/html5/weapp.jpg"/></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是存储数据比较常见的格式,那如何让云函数拥有读写Excel文件的能力呢?我们可以在Github上搜索关键词“Node Excel”,去筛选Star比较多,条件比较契合的。
Github地址:node-xlsx
使用开发者工具新建一个云函数,在package.json里添加latest最新版的node-xlsx:
"dependencies": {
"wx-server-sdk": "latest",
"node-xlsx": "latest"
}
读取云存储的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
}
将数据库里的数据保存为CSV
技术文档:json2CSV
HTTP处理
got、superagent、request、axios、request-promise
尽管云函数的Nodejs版本比较低(目前为8.9),但绝大多数模块我们都可以使用Nodejs 12或13的环境来测试,不过有时候也要留意有些模块不支持8.9,比如got 10.0.1以上的版本。
node中,http模块也可作为客户端使用(发送请求),第三方模块request对其使用方法进行了封装,操作更方便!所以来介绍一下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请求
结合文件流
request('https://www.jmjc.tech/public/home/img/flower.png').pipe(fs.createWriteStream('./flower.png')) // 下载文件到本地
加解密Crypto
crypto模块是nodejs的核心模块之一,它提供了安全相关的功能,包含对 OpenSSL 的哈希、HMAC、加密、解密、签名、以及验证功能的一整套封装。由于crypto模块是内置模块,我们引入它是无需下载,就可以直接引入。
使用开发者工具新建一个云函数,比如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 返回十六进制
var md5 = crypto.createHash('md5');
var message = 'CloudBase2020';
var digest = md5.update(message, 'utf8').digest('hex');
return {
"crypto支持的加密算法种类":hashes,
"md5加密返回的十六进制":digest
};
}
将云函数部署之后调用从返回的结果我们可以了解到,云函数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 = "统计小助理" // 替换成您所申请的签名
cloud.init()
// 云函数入口函数
exports.main = async (event, context) => new Promise((resolve, reject) => {
/*单发短信示例为完整示例,更多功能请直接替换以下代码*/
var qcloudsms = QcloudSms(appid, appkey);
var ssender = qcloudsms.SmsSingleSender();
var params = ["1234", "15"];
// 获取发送短信的手机号码
var mobile = event.mobile
// 获取手机号国家/地区码
var nationcode = event.nationcode
ssender.sendWithParam(nationcode, mobile, templateId, params, smsSign, "", "", (err, res, resData) => {
/*设置请求回调处理, 这里只是演示,您需要自定义相应处理逻辑*/
if (err) {
console.log("err: ", err);
reject({ err })
} else {
resolve({ res: res.req, resData })
}
}
);
})
使用开发者工具
wx.cloud.callFunction({
name: 'sendphone',
data: {
// mobile: '13217922526',
mobile: '18565678773',
nationcode: '86'
},
success: res => {
console.log('[云函数] [sendsms] 调用成功')
console.log(res)
},
fail: err => {
console.error('[云函数] [sendsms] 调用失败', err)
}
})