开发者学堂课程【玩转EMAS Serverless精品课-疫苗预约小程序:实战:小程序疫苗预约 - 云函数】学习笔记,与课程紧密联系,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/926/detail/14579
实战:小程序疫苗预约 - 云函数
内容介绍:
一、云函数开发介绍
二、实战:业务逻辑设计
三、实战:业务逻辑实现
四、实战:集成测试
五、课程总结
本节课程主题是云函数,围绕云函数来探讨如何基于 EMAS Serverless 的云函数进行预约疫苗小程序的业务逻辑设计、业务逻辑实现,包括最后的集成测试,本节课偏实验。
一、云函数开发介绍
1.云函数开发流程
本地开发一个云函数,然后打包部署到 EMAS Serverless 的控制台,在控制台上根据日志去调试看结果,根据结果反馈到本地开发,最后将成熟的云函数到端侧集成。
2.参数及日志
(1)云函数传参
云函数传参的流程:
调用一个 hello 的云函数,然后传入一个 hello 为 world 的云函数
await mpserverless.function.invoke (‘hello’,{hello :‘world’ });
可以在对应的云函数中通过 ctx.args ,hello 这个成员参数来获得该成员
module.exports=async.ctx=>{
ctx,logger.info(‘hello %s’,ctx.args.hello);
};
async.ctx 是云函数中特有的,云函数开发中有一些能够帮助提高开发效率的成员,是云函数中的入参
ctx.args 是包括云函数所有的传参成员
参数对象 |
类型 |
ctx.args |
Object |
(2)云函数日志
(云函数提供一种日志工具)
module.exports=async(ctx)=>{
ctx.logger.info(‘invoke arge: %j’,ctx.args);
};
可以直接调用 logger.info 打印日志,打印的日志在 EMAS Serverless 的控制面板中。
对应 js 日志中的级别:
日志 |
类型 |
ctx.logger.info() |
Info 级别的日志 |
ctx.logger.error() |
Error 级别的日志 |
ctx.logger.warn() |
Warning 级别的日志 |
ctx.logger.debug() |
Debug 级别的日志 |
3.云端 SDK 及外部集成
云函数经常被用作一个服务编排的工具,EMAS Serverless 有两种途径实现
内部集成,在云函数中内部去集成 EMAS Serverless 的云数据库、云存储,包括用户服务这些云资源。
实现通过 ctx 中的 mpserverless 对象去实现。
mpserverless 是已经被实例化好的 mpserverless 对象,可以理解为SDK 已经初始化好的实例对象。
module.exports=async ctx=>{
const args=ctx.args;
return awit ctx. mpserverless.db.collection(‘user’).find({uid:args.uid});
};
参数对象 |
类型 |
ctx. mpserverless |
Serverless SDK 实例 |
外部集成:
云函数中自行集成了实例,可以调用 ctx.httpclient 得到 urllib 实例
musule.exports=asyns ctx=>{
//外端有一个客户端的接口,想将其集成到疫苗预约的小程序中可以在云函数中对外部发起一个请求,然后对数据进行编排、拼装,最后返回到端侧
const res=awat ctx.httpclient.reques(‘
https://www.alipay.com/x/notFound.htm
’);
return {
html:res.status===200 ? res.date :‘’,
};
};
参数对象 |
类型 |
ctx.httpclient |
urllib 实例 |
4.云函数异常
云函数如何处理异常?
在写程序过程中常会有异常的行为,要在业务逻辑中抛出一个异常可以直接 throw new Error 的形式抛出。
在云函数的开发模式中:
在云函数返回的地方 throw new Error,例如:
‘ use strict ';
module.exports = async (ctx)=>{
const { name } =ctx.args;
ctx.logger.info( ‘---log---', name)
throw new Error(‘hello world ${name}’);
}
在端侧可以得到一个标准异常对象,格式一般为:
throw new Error(‘some message’)
{
code :‘FunctionBizError’,
message :‘some message’
// message 就是 new Error 的信息
}
该标准异常对象:
5.控制台部署(回顾)
(1)新建或打开一个原有的云函数
(2)设置每次及运行环境
(3)本地编写云函数 index.js
(4)打包同名 zip 文件
(5)上传 zip 包并部署代码
6.云函数控制台测试
打开 helloWorld
先看传参:
‘use strict’;
module.exports=async(ctx)=>{
condt {name}=ctx.args;
ctx.logger.info{‘----log----’,name}
return ‘hello world ${name}’;
打开云函数,更新 js 包:
执行,得到结果是正确的
打印的日志查询地点:
(日志可以看到其请求参数)
异常情况:
抛出异常
use strict’;
module.exports=async(ctx)=>{
condt {name}=ctx.args;
ctx.logger.info{‘----log----’,name}
throw new Error{‘hello world ${name’}};
}
重新部署云函数
控制台遇到异常的效果:
可以看到其原生的错误信息
在代码中查看其情况:
(用该节课的 demo 查看)
运行有异常报错:
7.云函数本地调试方案
本地开发后再去控制台部署然后开发部署的流程非常繁琐,本地调试更加方便,云函数本地调试需要借助 miniu 进行。
miniu 是开源的一个包,界面如下:
(1)全局安装 miniu
nom install-nminiu
(2)构建和组织云函数目录结构
外面是项目的主目录,里面云函数的目录
(3)支付宝授权登录
miniu login
(4)在云函数目录执行本地调试指令
miniu cloud function local-i<小程序id>-s<服务空间id>-p<云函数项目目录名>-n<目标云函数目录名>--input<参数>
注意:输入参数是 JSON 字符串,不是普通的 JSON
实例:
本地调试不用部署
运行结果:
二、实战:业务逻辑设计
1.业务场景分析
用户进入疫苗预约小程序后,需建立一个个人档案,抽象为一个登录或注册的步骤。如果用户在后台的 user 库中,可以直接将用户相关的信息(头像、昵称等等)返回,如果 userId 不在 user 库中,则是一个注册的流程,这样的话需要建一个个人档案,然后返回初始化信息。
将登录和注册接口合二为一,用 login(userId) ,根据 userId 是否在库中判断用户是否有个人档案。(该接口相对比较复杂,后面会介绍其主路流程)
完成用户档案后,会抛出修改用户信息的需求,简化用户信息只有昵称和头像,然后分解出接口 updateUser,根据 userId, userName, avatarUrl 修改昵称或头像的网络地址。
接下来是业务核心(疫苗和疫苗预约),首先,一个用户想要接种疫苗,先要知道有哪些疫苗,这样就是一个自然的需求。listVaccine() 接口实现该需求,出于安全考虑,通常不会在小程序中直接使用数据库,所以这里将其封装为原函数。
还需要一个获得疫苗详情信息的功能,
describeVaccineDetail(vaccineId) 根据疫苗 id 查询疫苗的详细信息。
在疫苗预约中,需要 userId、vaccineId、siteId(用户 id,疫苗 id,接种点 id),接口为 bookAppointment
listAppointment(userId) 可以查询疫苗预约单
赴约和取消预约的简化场景,赴约简化为更新疫苗单的形式,取消预约简化为将预约单删除。
业务拆分 |
函数名 |
说明 |
用户 |
login(userId) |
登录+注册二合一 |
updateUser(userId, userName, avatarUrl) |
更新用户信息〔姓名和头像) |
|
疫苗 |
listVaccine() |
查询可接种疫苗列表摘要信息 |
describeVaccineDetail(vaccineId) |
获取疫苗详细信息 |
|
预约 |
bookAppointment(userId, vaccineId,siteId) |
疫苗预约 |
listAppointment(userId) |
获取预约单列表 |
|
updateAppointment(userId, appointmentId,date) |
更新预约单状态 |
|
cancelAppointment(userId, appointmentId) |
取消预约 |
2.演示页面:
疫苗详情页,预约后查询预约记录,取消接种,在预约信息中成功删除,演示成功预约的情况,点击完成接种,完成接种
三、实战:业务逻辑实现
1.业务流程设计(登录及注册)
确定该业务云函数的形式:
函数接口接收一个 userId (注册的情况返回新注册用户信息,这里定义的是两个空字符串,如果是已经注册的用户返回其头像和昵称信息,注意有异常行为,因为没有强制要求函数签名一定要保持一致,所以可能会获得一个空的 userId。返回空的头像和昵称后,需要把用户真实的昵称和头像更新)
login(userId) |
|
输入参数 |
用户 id: userId |
输出参数 |
用户信息或新注册用户信息 |
前置条件 |
无 |
处理逻辑 |
1、根据 EMAS Serverless 用户 id 获取 user 表中存储的用户头像及昵称 2、为 EMAS Serverless 用户在系统中初始化注册 |
异常行为 |
1、未携带用户id |
登录注册环节涉及另一个云函数,在注册环节会调用该函数:
updateUser(userId, userName, userAvatar) |
|
输入参数 |
用户 id: userId 用户昵称: username 用户头像: userAvatar |
输出参数 |
更新用户信息 |
前置条件 |
用户处于登录态 |
处理逻辑 |
根据 userId 修改表中对应用户的昵称和头像 |
异常行为 |
1、用户不存在 |
实际流程图:
首先用户打开小程序,根据 serverless 的一个接口 getInfo (得到 serverless 的用户信息及2088开头的支付宝用户信息)获取平台 id(从 serverless 后台得到),得到 userId 后调用 login (登录注册二合一的接口),然后原函数检测是否有该用户信息,未注册时将新用户在表中 inside 后返回,已经注册的用户直接返回其信息即可(未注册用户在demo 中是空字符串的形式)。
然后将信息本地缓冲到全局对象中,方便在各个页面中使用。在用户新注册的情况下,去获得用户当前支付宝的昵称和头像,再调用接口更新该系统中的档案,再在页面中展示本地缓冲的信息。
2.实际代码:
注意:以空字符串为新注册态
3.业务流程设计(疫苗预约)
bookAppointment(userId, vaccineId, siteId) |
|
输入参数 |
用户id: userId 疫苗id: vaccineId 接种点id: siteId |
输出参数 |
无或异常 |
前置条件 |
用户处于登录态根据用户id、疫苗id、接种 |
处理逻辑 |
点id为用户创建一个疫苗预约单 |
异常行为 |
用户不存在 2、疫苗不存在 3、接种点不存在 4、重复预约 5、疫苗数量不足 6、接种点与疫苗种类不匹配 7、参数缺失 |
用户打开小程序,查看疫苗信息,发生疫苗预约请求到 Serverless ,经过异常检查如果没问题则生成预约单(成功创建),如果有问题,则返回一个异常信息,最后调用预约单接口展示预约状态。
4.实际代码:
看到其异常处理,缺少参数的情况、用户不存在的情况(传入的 useId 不在 user 表中)、接种地点不存在、疫苗不存在都返回错误信息。对应重复预约的情况,查询 appointment 这张表,然后查找 useId 、vaccineId、未预约状态的情况,如果有该数据,则返回错误。
如果发现库存大于0(有库存),则完成预约,没有库存的情况下,返回失败信息。预约后要减少疫苗库存,在疫苗的表中 update 这条信息,根据疫苗信息将相应的疫苗数量减1。
创建预约单,包括预约的基本信息及未完成疫苗接种的信息,还有接种点的信息也要在预约单中体现,
四、实战:集成测试
1.端侧集成(用户登录及注册)
调用 login
部分代码如下:
//注册的处理
let {result:{ userAvatar,userName }} = await my.serverless.function.invoke(' login',{
userId: oAuthUserId
})
if (userAvatar are " " && userName === ""){
//主动授权获取头像和昵称
await my.getAuthCode({
scopes: 'auth_user",
})
const { nickName,avatar ) = await my.getAuthUserInfo();
userAvatar = avatar;
userName = nickName;
//更新信息
await my.serverless.function.invoke( " updateuser', {
userId: oAuthUserId,
userAvatar,
userName
});
}
//用户信息放置
//本地缓存
my.userInfo = {
alipayId: oAuthUserid,
nickName: userName,
avatar: userAvatar
}
注册情况的处理当 userAvatar 和 useName 返回空字符串时规定过是一个空的注册信息时,主动授权支付宝小程序,主动授权才能获得支付宝昵称和头像。在调用时会唤起支付宝的弹窗,要求获得支付宝头像和昵称信息,用户点击运行即可。得到信息后,将 updateUser 上传最后本地缓存,就能在项目中使用。
2.端侧集成(疫苗预约)
按钮的情况,绑定 appointment 的一个事件
部分代码如下:
//参数传递、事件绑定
data-siteId=”{{site.siteId}}”
onTap=”appointment”
然后在小程序中完成预约:
调用 bookAppointment 把相应的 userId 、vaccineId 的信息传递
在预约成功和预约失败时都弹窗
部分代码如下:
//相关参数获取
async appointment(e) {
const { target: { dataset: { siteId }}}=e;
con st { vaccineId } =this.data.vaccine;
//云函数调用
try {
await my.serverless.function.invoke( " bookAppointment', {
userId: my.userInfo.alipayId,
vaccineId: vaccineId,
siteId,
})
//正确/异常返回处理
my.showToast({
type: "success' ,
content: "预约成功"
})
catch(e){
my.showToast({
type: 'fail',
content:`预约失败:${e.message}
});
}
五、课程总结
本节课主要学习云函数的开发以及如何基于 EMAS Serverless 进行预约小程序的业务逻辑设计,首先,了解了云函数开发的基本开发模式,本地开发到打包部署到控制台调试及最后的端侧集成。本地开发中,学习了如何处理 SDK 的传参,如何在云函数中使用日志工具打印日志,包括如何在云函数中做一个编排,如何内部集成 EMAS Serverless 的云资源,使用云函数的 SDK 去调用云数据库、云存储以及用户信息。
回顾了云函数打包部署的关键(命名 index.js,然后打包同名的 zip 包)。然后演练一遍控制台调试的步骤,学习了端侧集成,最后扩展了如何去本地调试云函数(介绍了 miniu 工具,包括 miniu 调试),关键是在云函数中建立一个项目目录,在项目目录中建立云函数目录,项目工程的外层执行改命令。还实践了小程序疫苗预约的场景,进行了云函数业务逻辑的拆分及两个重点的业务逻辑设计,最后预览了 demo 中云函数的实践及端侧的集成。