微信小程序云开发(二)

云函数

云函数即在云端(服务器端)运行的函数。在物理设计上,一个云函数可由多个文件组成,占用一定量的 CPU 内存等计算资源;各云函数完全独立;可分别部署在不同的地区。开发者无需购买、搭建服务器,只需编写函数代码并部署到云端即可在小程序端调用,同时云函数之间也可互相调用。
一个云函数的写法与一个在本地定义的 JavaScript 方法无异,代码运行在云端 Node.js 中。当云函数被小程序端调用时,定义的代码会被放在 Node.js 运行环境中执行。我们可以如在 Node.js 环境中使用 JavaScript 一样在云函数中进行网络请求等操作,而且我们还可以通过云函数后端 SDK 搭配使用多种服务,比如使用云函数 SDK 中提供的数据库和存储 API 进行数据库和存储的操作,这部分可参考数据库存储后端 API 文档。

云开发的云函数的独特优势在于与微信登录鉴权的无缝整合。当小程序端调用云函数时,云函数的传入参数中会被注入小程序端用户的 openid,开发者无需校验 openid 的正确性因为微信已经完成了这部分鉴权,开发者可以直接使用该 openid

设置云函数根目录

在项目根目录找到 project.config.json 文件,新增 cloudfunctionRoot 字段,指定本地已存在的目录作为云开发的本地根目录


image.png

由于我有两套环境,所有配置好之后没有需要选择环境, 在mycloud文件夹上右键选择环境


image.png

然后右键新建node.js云函数testClound1
会自动生成一个例子


image.png

我们再创建的云函数文件夹上右键可以看到三个选项


image.png

分别是本地调试、创建并部署所有文件(包括node_modules),以及创建并部署文件(云端安装依赖);

我们先本地调试好之后,再部署。
先进入testClound1文件夹安装依赖


image.png
image.png

我们新建一个页面,调用云函数

    wx.cloud.callFunction({
      // 云函数名称
      name: 'testClound1',
      // 传给云函数的参数
      data: {
      },
      success: function(res) {
        console.log('success-clound',res) // 3
      },
      fail: console.error
    })

控制台打印


image.png

调用成功,并且返回了调用者的openid.

现在来讲解一些云函数

  1. 在云函数中使用 wx-server-sdk

云函数属于管理端,在云函数中运行的代码拥有不受限的数据库读写权限和云文件读写权限

云函数中使用 wx-server-sdk 需在对应云函数目录下安装 wx-server-sdk 依赖,在创建云函数时会在云函数目录下默认新建一个 package.json 并提示用户是否立即本地安装依赖。请注意云函数的运行环境是 Node.js,因此在本地安装依赖时务必保证已安装 Node.js,同时 node 和 npm 都在环境变量中。如不本地安装依赖,可以用命令行在该目录下运行:

npm install --save wx-server-sdk@latest

在云函数中调用其他 API 前,同小程序端一样,也需要执行一次初始化方法:

const cloud = require('wx-server-sdk')
// 给定字符串环境 ID:接下来的 API 调用都将请求到环境 some-env-id
cloud.init({
  env: 'some-env-id'
})

或者

const cloud = require('wx-server-sdk')
// 给定 DYNAMIC_CURRENT_ENV 常量:接下来的 API 调用都将请求到与该云函数当前所在环境相同的环境
// 请安装 wx-server-sdk v1.1.0 或以上以使用该常量
cloud.init({
  env: cloud.DYNAMIC_CURRENT_ENV
})

现在云函数升级之后,直接在云函数根目录右键就可以选择环境。 init也不用传递参数选择环境。

然后是云函数的入口函数

// 云函数入口函数
exports.main = async (event, context) => {
  const wxContext = cloud.getWXContext()

  return {
    event,
    openid: wxContext.OPENID,
    appid: wxContext.APPID,
    unionid: wxContext.UNIONID,
  }
}

云函数的传入参数有两个,一个是event 对象,一个是 context对象。event 指的是触发云函数的事件,当小程序端调用云函数时,event 就是小程序端调用云函数时传入的参数,外加后端自动注入的小程序用户的 openid 和小程序的 appidcontext 对象包含了此处调用的调用信息和运行状态,可以用它来了解服务运行的情况。

比如我们在小程序端传递参数

    wx.cloud.callFunction({
      // 云函数名称
      name: 'testClound1',
      // 传给云函数的参数
      data: {
        num1: 2,
        num2: 3
      },
      success: function(res) {
        console.log('success-clound',res) // 3
      },
      fail: console.error
    })

云函数打印event对象

// 云函数入口文件
const cloud = require('wx-server-sdk')

cloud.init({
  env: cloud.DYNAMIC_CURRENT_ENV
})

// 云函数入口函数
exports.main = async (event, context) => {
  const wxContext = cloud.getWXContext()
  console.log('event', event)
  return {
    event,
    openid: wxContext.OPENID,
    appid: wxContext.APPID,
    unionid: wxContext.UNIONID,
  }
}

右键testCound1开启本地调试
打开云函数本地调试控制台


image.png

在云函数本地调试控制台看到接受到了小程序端传递的参数

我们可以在云函数中返回给小程序端


// 云函数入口函数
exports.main = async (event, context) => {
  const wxContext = cloud.getWXContext()
  console.log('event', event)
  return {
    event,
    openid: wxContext.OPENID,
    appid: wxContext.APPID,
    unionid: wxContext.UNIONID,
    num: event.num1+event.num2
  }
}

小程序端在调用云函数成功之后返回了结果


image.png

当然我们也可以使用then的语法获取云函数的结果

wx.cloud.callFunction({
      // 云函数名称
      name: 'testClound1',
      // 传给云函数的参数
      data: {
        num1: 2,
        num2: 3
      }
    }).then(res=>{
      console.log('云函数调用成功', res);
    }).catch(err =>{
      console.log('云函数调用失败', err);
    })

获取用户信息

云开发的云函数的独特优势在于与微信登录鉴权的无缝整合。当小程序端调用云函数时,云函数的传入参数中会被注入小程序端用户的 openid,开发者无需校验 openid 的正确性,因为微信已经完成了这部分鉴权,开发者可以直接使用该 openid。与 openid 一起同时注入云函数的还有小程序的 appid。

从小程序端调用云函数时,开发者可以在云函数内使用 wx-server-sdk 提供的 getWXContext 方法获取到每次调用的上下文(appidopenid 等),无需维护复杂的鉴权机制,即可获取天然可信任的用户登录态(openid

const cloud = require('wx-server-sdk')
exports.main = async (event, context) => {
  // 这里获取到的 openId、 appId 和 unionId 是可信的,
// 注意 unionId 仅在满足 unionId 获取条件时返回
  let { OPENID, APPID, UNIONID } = cloud.getWXContext()

  return {
    OPENID,
    APPID,
    UNIONID,
  }
}

异步返回结果

经常,我们需要在云函数中处理一些异步操作,在异步操作完成后再返回结果给到调用方。此时我们可以通过在云函数中返回一个 Promise 的方法来完成

// index.js
exports.main = async (event, context) => {
  return new Promise((resolve, reject) => {
    // 在 3 秒后返回结果给调用方(小程序 / 其他云函数)
    setTimeout(() => {
      resolve(event.num1 + event.num2)
    }, 3000)
  })
}

这次我们上传并部署云函数 (node_modules不上传) (注意,每次有修改都需要重新上传和部署)


image.png

点击左上角云开发 去云函数控制台


image.png

每次上传后要看函数状态变为已部署 才能测试(所有更多推荐本地云开发测试)

刷新小程序


image.png

云函数中调用其他云函数

假设我们要在云函数中调用另一个云函数 sum 并返回 sum 所返回的结果:

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

cloud.init({
  env: cloud.DYNAMIC_CURRENT_ENV
})

exports.main = async (event, context) => {
  return await cloud.callFunction({
    name: 'sum',
    data: {
      x: 1,
      y: 2,
    }
  })
}

灰度和版本

云开发提供发布版本(快照)和多版本间调整流量比例的能力。借此能力用户可以完成灰度,同时可以灰度函数配置


image.png
image.png

云函数版本

一个云函数可以发布多个版本,一个版本就是一个函数在当前时刻的快照,包含其代码和配置(超时时间、环境变量等)。

云函数始终存在一个 LATEST 版,即最新版。编辑器中上传云函数和在控制台更改配置始终更改 LATEST 版本。

在发布版本时,总是从 LATEST 当前的状态发布一个版本(快照)。

要进行灰度,只需更改版本之前的流量配比,即可实现灰度。

流量比例

在没有设置流量比例前,默认情况下都是 100% 流量导向 LATEST 版本,在发布一个或多个版本后,即可调整各个版本之间的流量比例。

比如现在要进行灰度,首先我们发布版本 1,然后设置 100% 流量到版本 1,接着更改 LATEST 代码,此时希望 10% 的线上流量给到需要灰度观察的最新代码,则设置 10% 流量给到 LATEST,90% 流量给到 1

日志服务

开发者可通过微信云开发提供的日志服务实现日志采集和检索分析等功能,方便开发者通过日志快速的发现和定位问题。每条日志可最长存储30天,超过 30 天的日志将被自动清理。

开发者可前往微信开发者工具云控制台的云函数高级日志界面开启高级日志服务

image.png

日志采集

可使用 wx-server-sdk 提供的 logger 方法打日志:

  1. 通过 logger() 方法取得 log 对象
  2. 调用 log 对象上的 log / info / warn / error (对应不同 level 的日志等级)方法,传入一个对象作为参数,每调用一次会产生一条日志记录
  3. 对象的每一个 <key, value> 对都会成为日志一条记录中的一个可检索的键值对,其中 value 不论值是什么都会被转成字符串
// 云函数入口文件
const cloud = require('wx-server-sdk')
cloud.init({
  env: cloud.DYNAMIC_CURRENT_ENV,
})
// 云函数入口函数
exports.main = async (event, context) => {
  const wxContext = cloud.getWXContext()

  const log = cloud.logger()
  log.info({
    name: 'xx',
    cost: 10,
    attributes: {
      width: 100,
      height: 200,
    },
    colors: ['red', 'blue'],
  })

  // 输出到日志记录中会有这么一条记录:
  // {
  //   "level": "info",
  //   "name": "xx",
  //   "cost": "10",
  //   "attributes": "{ width: 100, height: 200 }",
  //   "colors": "[ "red", "blue" ]"
  //   ..., // 其他系统字段
  // }

  return {
    event,
    openid: wxContext.OPENID,
    appid: wxContext.APPID,
    unionid: wxContext.UNIONID,
  }
}

定时触发器

如果云函数需要定时 / 定期执行,也就是定时触发,我们可以使用云函数定时触发器。配置了定时触发器的云函数,会在相应时间点被自动触发,函数的返回结果不会返回给调用方。

在需要添加触发器的云函数目录下新建文件 config.json,格式如下:

{
  // triggers 字段是触发器数组,目前仅支持一个触发器,即数组只能填写一个,不可添加多个
  "triggers": [
    {
      // name: 触发器的名字,规则见下方说明
      "name": "myTrigger",
      // type: 触发器类型,目前仅支持 timer (即 定时触发器)
      "type": "timer",
      // config: 触发器配置,在定时触发器下,config 格式为 cron 表达式,规则见下方说明
      "config": "0 0 2 1 * * *"
    }
  ]
}
  • 定时触发器名称 (name):最大支持 60 个字符,支持 a-z, A-Z, 0-9, -_。必须以字母开头,且一个函数下不支持同名的多个定时触发器。
  • 定时触发器触发周期 (config):指定的函数触发时间。填写自定义标准的 cron 表达式来决定何时触发函数。有关 cron 表达式的更多信息,请参考下面的内容。

Cron 表达式
Cron 表达式有七个必需字段,按空格分隔

image.png

image.png

image.png

实现增删改查

把微信小程序云开发(一)中的例子拿过来,用云函数实现
创建云函数 list 实现查询

// 云函数入口文件
const cloud = require('wx-server-sdk')

cloud.init()
const db = cloud.database() //获取数据库的引用
const userCollection = db.collection('user')

// 云函数入口函数
exports.main = async (event, context) => {
 return userCollection
    .get() // 查询
    .then(res => {
      console.log('获取user成功', res);
      return {
        code: 0,
        list: res.data
      }
    }).catch(err => {
      console.log('获取user失败', err);
      return {
        code: -1,
        msg: err
      }
    })
}

小程序端int方法调用list函数

    wx.cloud.callFunction({
      name: 'list',
      data:{},
    }).then(res=>{
      console.log('获取列表', res);
      if(res.result.code === 0){
        this.setData({
          list: res.result.list
        })
      }
    }).catch(err=>{
      console.log('err', err);
    })
    return false;   
image.png

接下来分别创建add ,delete,update云函数实现剩下功能

add

// 云函数入口文件
const cloud = require('wx-server-sdk')

cloud.init()
const db = cloud.database() //获取数据库的引用
const userCollection = db.collection('user')

// 云函数入口函数
exports.main = async (event, context) => {
  const {name, age, sex} = event
  return  userCollection
  .add({        // 新增
    data: {
      name,
      age: +age,
      sex: +sex
    }
  }).then(res => {
    return {
      code: 0
    }
  }).catch(err => {
    return {
      code: -1,
      msg: err
    }
  })
}

udpate

// 云函数入口文件
const cloud = require('wx-server-sdk')

cloud.init()
const db = cloud.database() //获取数据库的引用
const userCollection = db.collection('user')

// 云函数入口函数
exports.main = async (event, context) => {
  const {
    id,
    data
  } = event

  return userCollection
    .doc(id) // 找到需要修改的数据索引
    .update({ // 修改数据
      data
    }).then(res => {
      return {
        code: 0
      }
    }).catch(err => {
      return {
        code: -1,
        msg: err
      }
    })
}

delete

// 云函数入口文件
const cloud = require('wx-server-sdk')

cloud.init()
const db = cloud.database() //获取数据库的引用
const userCollection = db.collection('user')

// 云函数入口函数
exports.main = async (event, context) => {
  const {
    id
  } = event
  return userCollection
    .doc(id)
    .remove()
    .then(res => {
      return {
        code: 0
      }
    }).catch(err => {
      return {
        code: -1,
        msg: err
      }
    })
}

小程序端代码
cloud.js

const db = wx.cloud.database() //获取数据库的引用
const userCollection = db.collection('user') // 获取集合的引用
Page({
  data: {   
    sex: 1,
    name: '',
    age: 0,
    list: []
  },  
  onLoad() {
   this.init()
  },
  init(){
    wx.cloud.callFunction({
      name: 'list',
      data:{},
    }).then(res=>{
      console.log('获取列表', res);
      if(res.result.code === 0){
        this.setData({
          list: res.result.list
        })
      }
    }).catch(err=>{
      console.log('获取列表失败-err', err);
    })  
  },
  // 获取输入框 的姓名和年龄
  bindinput(e) {
    const type = e.currentTarget.dataset.type
    this.setData({
      [type]: e.detail.value
    })
    console.log(e.detail.value);
  },
  // 获取性别
  radioChange(e) {
    this.setData({
      sex: e.detail.value
    })
    console.log('radio', e.detail.value);
  },
  //  新增方法的实现
  addFn() {
    const {
      name,
      age,
      sex
    } = this.data;
    if (!name || !age) {
      return wx.showToast({
        title: '需要输入姓名或年龄',
      })
    }
    wx.cloud.callFunction({
      name: 'add',
      data:{
        name,
        age: +age,
        sex: +sex
      },
    }).then(res=>{
      console.log('新增成功', res);
      if(res.result.code === 0){
        this.init()
      }
    }).catch(err=>{
      console.log('新增失败-err', err);
    })  
  },
  updateFn(e) {
    const id = e.currentTarget.dataset.id
    const {name, age, sex} = this.data;
    const data = {sex}
    if (name) {
      data.name = name
    }
    if (age) {
      data.age = age
    }

    wx.cloud.callFunction({
      name: 'update',
      data:{
        id,
        data
      },
    }).then(res=>{
      console.log('更新成功', res);
      if(res.result.code === 0){
        this.init()
      }
    }).catch(err=>{
      console.log('更新失败-err', err);
    })
  },
  deleteFn(e) {
    const id = e.currentTarget.dataset.id
    wx.cloud.callFunction({
      name: 'delete',
      data:{
        id
      },
    }).then(res=>{
      console.log('删除成功', res);
      if(res.result.code === 0){
        this.init()
      }
    }).catch(err=>{
      console.log('删除失败-err', err);
    })
  },
})

增删改查也实现了。

上传文件 (可以是图片、音频、视频、文件等)

wx.cloud.uploadFile
将本地资源上传至云存储空间,如果上传至同一路径则是覆盖写

image.png

新建一个页面 ,创建button按钮 点击 上传本地的图片

upload(){
    console.log('点击上传');
    wx.cloud.uploadFile({
      cloudPath: 'imgs/葫芦娃.jpg',
      filePath: '/static/image/葫芦娃.jpg', // 文件路径
      success: res => {
        // get resource ID
        console.log('上传成功',res);
        console.log(res.fileID)
      },
      fail: err => {
        console.log('err', err);
        // handle error
      }
    })
  },
image.png

然后我们去云开发控制台
点击储存


image.png
image.png

点击图片,右侧查看详情信息


image.png

我们可以看到图片的fileId(小程序使用),以及下载地址(链接可以直接浏览器打开)

本地选择图片上传本展示

由于wx.chooseImage无法获取图片名,只能获取图片的本地临时文件路径列表 (本地路径) ,所有上传数据库的图片需要自己命名
pic.wxml

<button bindtap="upload">上传文件</button>
<image src="{{imgSrc}}"></image>

pic.js

Page({
  data: {
    imgSrc: ''
  },
  upload() {
    const that = this
    wx.chooseImage({
      count: 1,
      sizeType: ['original', 'compressed'],
      sourceType: ['album', 'camera'],
      success(res) {
        console.log('res', res);
        // tempFilePath可以作为img标签的src属性显示图片
        const tempFilePaths = res.tempFilePaths
        that.cloundUpdate(tempFilePaths[0], '测试图片.jpg')
      }
    })
  },
  cloundUpdate(filePath, nameStr) {
    wx.cloud.uploadFile({
      cloudPath: 'imgs/' + nameStr,
      filePath: filePath,
      success: res => {
        // get resource ID
        console.log('上传成功', res);
        console.log(res.fileID)
        this.setData({
          imgSrc: res.fileID
        })
      },
      fail: err => {
        console.log('err', err);
        // handle error
      }
    })
  }  
})

image.png

视频、音频,文件道理一样,无非就是 api不同而已。

wx.chooseMedia: 拍摄或从手机相册中选择图片或视频
wx.chooseVideo: 拍摄视频或从手机相册中选视频
wx.chooseImage: 从本地相册选择图片或使用相机拍照
wx.chooseMessageFile: 从客户端会话选择文件 (通过type可以上传视频、图片和其他)

文件下载

wx.cloud.downloadFile

image.png

download(){
    wx.cloud.downloadFile({
      fileID: 'cloud://test-0ge6yywxbfeb7294.7465-test-0ge6yywxbfeb7294-1306429729/imgs/葫芦娃.jpg'
    }).then(res => {
      // get temp file path
      console.log('下载文件成功', res);
      console.log(res.tempFilePath)
      this.setData({
        imgSrc: res.tempFilePath
      })
    }).catch(error => {
      // handle error
      console.log('下载文件失败', err);
    })
  },

fileID 就是我们上次的fileID. 也可以到云开发控制台点击图片右侧获取


image.png

从云存储空间删除文件,一次最多 50 个

我们去云开发控制台获取测试普通的file ID

delete(){
    wx.cloud.deleteFile({
      fileList: ['cloud://test-0ge6yywxbfeb7294.7465-test-0ge6yywxbfeb7294-1306429729/imgs/测试图片.jpg']
    }).then(res => {
      // handle success
      console.log(res.fileList)
      console.log('删除测试图片成功', res);
    }).catch(error => {
      // handle error
      console.log('删除测试图片失败', err);
    })
  },
image.png
image.png

云文件 ID 换取真实链接

用云文件 ID 换取真实链接,公有读的文件获取的链接不会过期,私有的文件获取的链接十分钟有效期。一次最多取 50 个

getUrl(){
    wx.cloud.getTempFileURL({
      fileList: ['cloud://test-0ge6yywxbfeb7294.7465-test-0ge6yywxbfeb7294-1306429729/imgs/葫芦娃.jpg']
    }).then(res => {
      // get temp file URL
      console.log(res.fileList)
      this.setData({
        imgSrc: res.fileList[0].tempFileURL
      })
    }).catch(error => {
      // handle error
    })
  },
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容