小程序云的进阶开发技巧

云数据库

云数据库(数据存储服务)是基于MongoDB托管在云端的数据库,数据以JSON格式存储。作为开发者,您可以在客户端内直接操作数据,也可以在云函数中读写数据。

数据库常用语法说明

数据存储服务是基于MongoDB托管在云端的数据库,使用MongoDB 3.4版本,常用的数据库操作语法如下。

聚合查询aggregate
计算记录条数count
删除一条数据记录deleteOne
删除一批数据记录deleteMany
获取去重后的记录distinct
查询记录find
查询单条记录findOne
查询并删除一条记录findOneAndDelete
查询并替换一条记录findOneAndReplace
查询并更新记录findOneAndUpdate
替换记录replaceOne
添加一批记录insertMany
添加一条记录insertOne
更新一批记录updateMany
更新一条记录updateOne

使用地理位置数据

小程序云支持以下几种地理位置数据类型:

  • Point,点
{ type: "LineString", coordinates: [ [ 40, 5 ], [ 41, 6 ] ] }
  • LineString,线
{ type: "LineString", coordinates: [ [ 40, 5 ], [ 41, 6 ] ] }
  • Polygon,多边形
{
  type: "Polygon",
  coordinates: [ [ [ 0 , 0 ] , [ 3 , 6 ] , [ 6 , 1 ] , [ 0 , 0  ] ] ]
}
  • MultiPoint, 点集合
{
  type: "MultiPoint",
  coordinates: [
     [ -73.9580, 40.8003 ],
     [ -73.9498, 40.7968 ],
     [ -73.9737, 40.7648 ],
     [ -73.9814, 40.7681 ]
  ]
}
  • MultiLineString,线段集合
{
  type: "MultiLineString",
  coordinates: [
     [ [ -73.96943, 40.78519 ], [ -73.96082, 40.78095 ] ],
     [ [ -73.96415, 40.79229 ], [ -73.95544, 40.78854 ] ],
     [ [ -73.97162, 40.78205 ], [ -73.96374, 40.77715 ] ],
     [ [ -73.97880, 40.77247 ], [ -73.97036, 40.76811 ] ]
  ]
}
  • MultiPolygon,多边形集合
{
  type: "MultiPolygon",
  coordinates: [
     [ [ [ -73.958, 40.8003 ], [ -73.9498, 40.7968 ], [ -73.9737, 40.7648 ], [ -73.9814, 40.7681 ], [ -73.958, 40.8003 ] ] ],
     [ [ [ -73.958, 40.8003 ], [ -73.9498, 40.7968 ], [ -73.9737, 40.7648 ], [ -73.958, 40.8003 ] ] ]
  ]
}

插入地理位置数据示例:

// 在小程序端  
my.serverless.db.collection('places').insertMany([
        {
          location: { type: "Point", coordinates: [-73.88, 40.78] },
          name: "La Guardia Airport",
          category: "Airport"
        },
        {
          location: { type: "LineString", coordinates: [[113, 23], [120, 50]] },
          name: "ss Airport",
          category: "Airport"
        }
  ]}
  // 在云函数中插入
 ctx.mpserverless.db.collection('places').insertMany([
        {
          location: { type: "Point", coordinates: [-73.88, 40.78] },
          name: "La Guardia Airport",
          category: "Airport"
        },
        {
          location: { type: "LineString", coordinates: [[113, 23], [120, 50]] },
          name: "ss Airport",
          category: "Airport"
        }
  ]}                                                

创建地理位置索引

可以通过控制台或者调用RunDBComand接口来创建地理位置类型的索引。以下为使用RunDBComand接口创建地理位置索引的Body参数示例。

{
    "command": "createIndex",
    "collection": "places",
    "field": {
        "location": "2dsphere"
    },
    "options": {
        "name": "location_2dsphere",
        "unique": false
    }
}

查询地理位置数据

您可以在小程序端或者云函数中来使用地理位置查询,两者的查询语法是相同的。支持以下几种查询方法:

  • nearSphere

     • 按从近到远的顺序,找出字段值在给定地点附近的记录。
    
// 使用nearSphere必须给查询的字段添加地理位置索引,否则会查询失败。
my.serverless.db.collection('places').find(
  {
    location: {
      $nearSphere: {
        $geometry: {
          type: "Point",
          coordinates: [-73.9667, 40.78]
        },
        $minDistance: 1000,
        $maxDistance: 5000
      }
    }
  }
);
  • geoWithin

     • 找出字段值在给定区域内的记录。给定区域可以是Polygon、MultiPolygon或者CenterSphere。
    
my.serverless.db.collection('places').find(
  {
    location: {
      $geoWithin: {
        $geometry: {
          type: "Polygon",
          coordinates: [
            [[0, 0], [30, 20], [20, 30], [0, 0]],
            [[10, 10], [16, 14], [14, 16], [10, 10]]
          ]
        }
      }
    }
  }
);
  • geoNear(聚合查询)

     • 适用于聚合阶段,将记录按照给定点从近到远输出。
    
my.serverless.db.collection('places').aggregate(
  [
    {
      $geoNear: {
        near: { type: "Point", coordinates: [113.323809, 23.097732] },
        distanceField: "distance",
        maxDistance: 2,
        query: { category: "Parks" },
        includeLocs: "location",
        spherical: true
      }
    }
  ]
);

设置数据库索引

使用数据库时,对成为查询条件的字段设置索引可以有效提高查询效率,更快的获取信息。缺省情况下会
对_id_字段默认创建一个非唯一索引。

1.在数据库页面,单击目标数据表,然后单击索引页签进入索引设置页面。
2.点击添加索引进入索引设置页面。
3.填写索引名称,设置索引属性、索引字段。

  • 索引属性:可以设置为唯一索引或非唯一索引。

将字段设置为唯一索引后,可以防止不同记录的被索引键上存储相同值。

  • 索引字段:支持单键索引和复合索引设置,字段按升序或降序排列。

单键索引是最常见的索引形式,针对一个指定字段建立索引。对于单字段索引,升序、降序的查询效果一样。
复合索引是是单键索引的升级版,针对多个字段联合创建索引,先按照第一个字段排列,第一个字段相同的记录按第二个字段排列,依次类推。

4.单击保存按钮,保存索引设置。

小程序云的进阶开发技巧

小程序云的进阶开发技巧

云函数

云函数(FaaS)是一段运行在云端的、轻量的、无关联的、并且可重用的代码。无需管理服务器,只需编写和上传代码,即可获得对应的数据结果。使用云函数可以使企业和开发者不需要担心服务器或底层运维设施,可以更专注代码和业务本身,也可以使代码进一步解耦,增加其重用性。

云函数示例

云函数目录结构

在Serverless小程序工程的server/functions目录下创建云函数目录,其中index.js是云函数getImageList的入口文件。

└── server/
    └── functions
           └── getImageList
                   └── index.js

云函数代码结构

以下代码示例展示了如何从数据库images里面查出特定用户上传的图片记录。云函数里可以直接通过API调用数据存储,文件存储的服务资源以及其他云函数。

  • 当云函数被客户端或者其他云函数调用时,可以通过ctx.args获得调用传来的参数。
  • 可以通过ctx.logger方法打印不同类型的日志信息,然后在云函数控制台中查看执行日志。
  • 在云函数内通过ctx.env来获取环境参数,例如SpaceId、调用来源、客户端源IP和客户端UserAgent等信息。ctx.env是一个object,它包含MP_SPACE_ID(SpaceId),MP_SOURCE(调用来源),MP_USER_AGENT(客户端User-agent),MP_CLIENT_IP(客户端IP)。
  • 在云函数中,您可以直接调用同一环境(空间)的数据存储和文件存储服务以及获取当前请求用户的基本信息。ctx.mpserverless在云函数封装了小程序的基础服务, API使用方式和客户端一致。

     • 通过ctx.mpserverless.db调用云数据库能力,如ctx.mpserverless.db.collection('user').find({ uid: args.uid })。
     • **通过ctx.cloud调用支付宝/阿里云的各种开放接口能力,包括了支付宝的基础/会员/支付/安全/营销等开放接口,如创建小程序二维码,调用ctx.cloud.base.qrcode.create({urlParam:'pages/index/index', queryParam:'key=value', describe:'我是二维码'})**
     • 通过ctx.mpserverless.file调用文件存储能力,如ctx.mpserverless.**file**.deleteFile(**args**.filePathUrl)。
     • 通过ctx.mpserverless.user获取当前访问用户基本信息,如ctx.mpserverless.user.getInfo()。
     • 通过ctx.mpserverless.function调用云函数能力,如mpserverless.function.invoke('dataAnalytics', {range: 30})调用其他云函数。
     • 通过ctx.httpclient HTTP 请求方法,无需额外依赖,您就可以请求任何 HTTP 和 HTTPS 协议的 Web 服务。如ctx.httpclient.request(' https://www.alipay.com/x/notFound.htm' )。
    
module.exports = async (ctx) => {
  ctx.logger.info('%s %s', ctx.env.MP_SPACE_ID, ctx.args.username);
  const images = await ctx.mpserverless.db.collection('images').find({ owner: ctx.args.username });
  return { images };
};

云函数接收消息推送

支持通过云函数接收小程序消息推送(如支付状态变化触发云函数,应用授权消息触发云函数,应用审核/驳回消息触发云函数)等。

首先:设置云函数HTTP触发器路径

支付宝小程序云开发支持通过云函数接收小程序消息推送,如接收应用授权消息,应用审核以及驳回消息,支付状态变化消息。首先,我们需要在自己的云服务空间下新建并部署云函数用于处理对应的消息通知。云函数部署成功之后,我们需要开启云函数的HTTP触发器功能,并获取一个该云函数对应的HTTP触发路径,只要使用 HTTP请求就可以访问到云函数,同时该HTTP触发路径可以作为开放平台的应用网关以及支付接口的notify_url用作接收各类消息。开启云函数HTTP触发功能有两种途径

在阿里云OpenAPI Explorer控制台调用API开启

——在阿里云的API在线调试界面 https://help.aliyun.com/document_detail/158669.html 以及 https://help.aliyun.com/document_detail/153370.html 发起在线API调用。
——开启Space的HTTP触发功能调用接口 UpdateHttpTriggerConfig接口来开启Space的HTTP触发功能,注意需要设置EnableService为true。
系统会为开启HTTP触发功能的Space分配一个默认域名 (DefaultEndpoint) ,供HTTP触发使用。
——开启云函数的HTTP触发功能调用接口 UpdateFunction接口来为云函数设置HTTP触发路径
HttpTriggerPath ,只有设置了触发路径的云函数才能使用该功能。
——HTTP触发函数执行访问 https://${DefaultEndpoint}${HttpTriggerPath} http://${DefaultEndpoint}${HttpTriggerPath} ,即可触发对应的云函数执行。

通过支付宝小程序命令行工具alipaydev一键开启(该功能将于2020/10上线,敬请期待)
• 安装alipaydev命令行工具,进行初始化配置。参考 https://opendocs.alipay.com/mini/alipaydev 的安装以及工具初始化配置说明。
• 执行 alipaydev cloud function httptrigger 命令,执行成功后会在命令行界面返回完整的云函数HTTP触发路径,该路径既可以直接使用HTTP请求访问,也可以作为应用网关或notify_url接收支付消息变化推送。
需要注意的是,每次调用UpdateFunction接口都会使之前生成的云函数触发路径的默认域名
(DefaultEndpoint) 发生变化。

云函数接收应用授权消息

将云函数的HTTP触发路径设置为应用的应用网关。
小程序云的进阶开发技巧
在云函数里处理应用授权的通知消息,应用授权通知的消息格式详细见 授权通知消息参数说明。在云函数里,我们解析出应用授权通知里的参数,将授权的应用appId,应用授权令牌appAuthToken,商户userId等参数存储在云数据库。云函数示例代码如下:

module.exports = async function (ctx) {
    const { args, mpserverless } = ctx
    // 1.解析args里的授权通知参数
    const { body } = args;
    ctx.logger.info('<---authCallback body--->',body);
    let jsonObj = query2Json(body);
    ctx.logger.info('<---authCallback jsonObj--->',jsonObj);
    // 若 notify_type=open_app_auth_notify 且 status=execute_auth 表示开放平台应用授权发生了重新授权
    // execute_auth 执行授权 cancel_auth 取消授权 refresh_auth 刷新授权
    const { notify_type, status, notify_id, app_id, auth_app_id, biz_content } = jsonObj;
    ctx.logger.info('<---authCallback notify_id|notify_type|status --->',notify_id, notify_type, status);
    const bizContentObj = JSON.parse(biz_content);
    const { detail } = bizContentObj;
    const { app_auth_token, auth_time, user_id } = detail;
    ctx.logger.info('<---authCallback auth_app_id|user_id|app_auth_token|auth_time --->',auth_app_id, user_id, app_auth_token, auth_time);
    if(notify_type == 'open_app_auth_notify' && (status == 'execute_auth'||status=='refresh_auth')) {
        // 执行授权通知+刷新授权通知消息,存储(auth_app_id)(uk) app_auth_token user_id auth_time
        await mpserverless.db.collection('auth_config').replaceOne({
            auth_app_id: auth_app_id
        }, {
            auth_app_id, app_auth_token, user_id, auth_time, 
            // 执行授权或刷新,valid=true
            valid: true
        }, {
            upsert: true
        })
    } else if(notify_type == 'open_app_auth_notify' && status == 'cancel_auth') {
        // 取消授权通知消息
        await mpserverless.db.collection('auth_config').updateOne({
            auth_app_id: auth_app_id
        }, {
            $set: { 
                valid: false
            }
        })
    }
    return {
        stat: 'ok'
    }
  };
  
  function query2Json(str) {
    var obj = {}, pairs = str.split('&'), d = decodeURIComponent, name, value;
    pairs.forEach(element => {
        pair = element.split('=');
        name = d(pair[0]);
        value = d(pair[1]);
        obj[name] = value;
    });
    return obj;    
  }

云函数接收支付状态变化消息

**将云函数的HTTP触发路径作为支付创建云调用API的notify_url参数。
**
——在小程序端使用云调用客户端SDK发起支付创建

//app.js中初始化云调用SDK时,将云函数的HTTP触发路径作为参数传入进行初始化
import cloud from 'alipay-serverless-sdk';

cloud.init(my.serverless,{
  notify:{url:"https://e1b3d2d79-bf5b-455d-8198-b762f0a24450.bspapp.com/http/notifyForPay"}
});

//在小程序页面js代码里,调用支付创建接口API
const res = await cloud.payment.common.crerate({
    subject: '小程序Serverless支付测试',
    outTradeNo: "demo" + new Date().getTime(),
    totalAmount: '0.01',
    buyerId: '2088202286335281'
  });

——在云函数代码中调用支付创建云调用API

// 云函数中使用云调用无需引入其他依赖,只需要使用ctx.cloud调用
module.exports = async function (ctx) {
  const res = await ctx.cloud.payment.common.create({
    // 参数接收自云函数调用端传入的参数
    subject: ctx.args.subject,
    outTradeNo: ctx.args.outTradeNo,
    totalAmount: ctx.args.totalAmount,
    buyerId: ctx.args.buyerId
  }, {
    // 传入接收支付宝服务器交易通知的http/https路径,也可以是云函数的http触发路径
    notify: {
        url: 'https://e1b3d2d79-bf5b-455d-8198-b762f0a24450.bspapp.com/http/notifyForPay'
    }
  });
  return res;
};

云调用

云调用是基于小程序 Serverless 的云函数来使用支付宝小程序开放接口的能力,可以方便的让开发者在小程序中和云函数中直接调用支付宝的后端开放接口,不需要关注服务端的相关配置,极大的减少了接入的流程,进一步降低了支付宝小程序的开发门槛。

云调用开放能力说明

云调用提供了基础能力、营销能力、会员能力、支付能力、安全能力和资金能力以及通用调用能力的接口调用,只需要开通云调用,你就能在小程序端和云函数端快捷的调用各类接口。

基础能力

  • 用户授权

• 获取授权访问令牌 cloud.base.oauth.getToken
• 刷新授权访问令牌 cloud.base.oauth.refreshToken

// 在云函数中使用云调用,无需引入其他依赖,只需要使用ctx.cloud调用
module.exports = async function (ctx) {
  const res = await ctx.cloud.base.oauth.getToken({
    // code参数接收自云函数调用处传入的参数
    code: ctx.args.authCode
  });
  return res;
};
// 在小程序端使用云调用
import cloud from 'alipaytest-serverless-sdk'

const res = await cloud.base.oauth.getToken('yourauthcode');
  • 小程序二维码

• 创建小程序二维码 cloud.base.qrcode.create

// 在云函数中使用云调用,无需引入其他依赖,只需要使用ctx.cloud调用
module.exports = async function (ctx) {
  const res = await ctx.cloud.base.qrcode.create({
    urlParam: 'pages/index/index',
    describe: '我的二维码描述',
    queryParam: 'key=value'
  });
  return res;
};
// 在小程序端使用云调用
import cloud from 'alipaytest-serverless-sdk'

const res = await cloud.base.qrcode.create('pages/index/index','key=value','我的二维码描述');

营销能力

  • 小程序模板消息

• 发送模板消息 cloud.marketing.templateMessage.send

// 在云函数中使用云调用,无需引入其他依赖,只需要使用ctx.cloud调用
module.exports = async function (ctx) {
  const res = await ctx.cloud.marketing.templateMessage.send({
    // 参数接收自云函数调用端传入的参数
    toUserId: ctx.args.toUserId,
    formId: ctx.args.formId,
    page: ctx.args.page,
    data: ctx.args.data,
    userTemplateId: ctx.args.userTemplateId  });
  return res;
};
// 在小程序端使用云调用
import cloud from 'alipaytest-serverless-sdk'

const res = await cloud.marketing.templateMessage.send('2088xxxxx','2017010100000000580012345678','MDI4YzIxMDE2M2I5YTQzYjUxNWE4MjA4NmU1MTIyYmM=','page/component/index','');

资金能力

  • 无密转账到支付宝账号

• 单笔转账 cloud.fund.transferAccount.transfer

// 在云函数中使用云调用,无需引入其他依赖,只需要使用ctx.cloud调用
module.exports = async function (ctx) {
  let payeeInfo = new Object({
    identity: this.data.payeeId,
    identity_type: 'ALIPAY_USER_ID'
  });
  const res = await ctx.cloud.fund.transferAccount.transfer({
    // 参数接收自云函数调用端传入的参数
    outBizNo: ctx.args.outBizNo,
    transAmount: ctx.args.transAmount,
    payee: payeeInfo,
    orderTitle: ctx.args.orderTitle
  });
  return res;
};
// 在小程序端使用云调用
import cloud from 'alipaytest-serverless-sdk'

const res = await cloud.fund.transferAccount.transfer('155983843433000','1.00',{
    identity: '2088xxxxxxxxx',
  identity_type: 'ALIPAY_USER_ID'
},'转账标题');

支付能力

  • 当面付

• 当面付交易付款 cloud.payment.faceToFace.pay

// 在云函数中使用云调用,无需引入其他依赖,只需要使用ctx.cloud调用
module.exports = async function (ctx) {
  const res = await ctx.cloud.payment.faceToFace.pay({
    // 参数接收自云函数调用端传入的参数
    subject: ctx.args.subject,
    outTradeNo: ctx.args.outTradeNo,
    totalAmount: ctx.args.totalAmount,
    authCode: ctx.args.authCode
  });
  return res;
};
// 在小程序端使用云调用
import cloud from 'alipaytest-serverless-sdk'

const res = await cloud.payment.faceToFace.pay('Iphone6 16G','20150320010101001','88.88','28763443825664394');

更多云调用支持的开放能力可以在 开放平台小程序云云调用专区 获得

上一篇:成本直降50% | 阿里云发布云原生网关,开启下一代网关新进程


下一篇:最近读cocoaui源代码有感