亚马逊AWS官方博客

使用 AWS Lambda 和 API Gateway 实现微信小程序后端服务

微信小程序

微信是一款跨平台移动通讯工具,目前已经拥有12亿月活跃用户量,是中国区“国民级”超级应用。而基于这个庞大的用户平台,微信平台上衍生出来了多个商业化生态,涉及到医疗、餐饮、零售及交通等各行各业。微信小程序是基于微信平台中的一种前端轻量化应用,现日活跃用户已经超过了4.5亿。因此,在中国区的企业 IT 运营配置中,微信小程序已经继网站、App 之后成为了企业标配之一。

小程序是微信整合了现有的前端技术栈提供的新的开放能力,开发者可以通过微信提供的开发者工具快速地开发一个小程序。小程序可以在微信内被便捷地获取和传播,同时具有出色的使用体验。除了开发者工具外,微信为小程序提供了一个简单、高效的应用开发框架和丰富的组件及 API,帮助开发者在微信中开发具有原生 App 体验的服务。相对通用的技术栈,更轻量的部署方式,以及微信官方的大力支持,让微信小程序成为了很多中国轻量创业公司快速实现业务价值的首选。

选择 AWS Lambda 和 API Gateway

微信的小程序为前端提供了快速开发业务逻辑的能力,同时后端的服务也应该有同样的能力可以支持业务的快速发展。AWS 提供的 Lambda 和 API Gateway 配合的 Severless 解决方案无疑非常契合小程序后台服务的需求,可以快速部署扩展,同时免去高昂的设施维护成本。

架构图

打通小程序与AWS服务认证

总体流程图

来自微信文档

开发的重点在打通微信的认证体系与API Gateway,让AWS的服务可以甄别出来自微信小程序的请求,并解析出微信提供的用户信息。微信并没有强制要求采用特定的认证方式,方便我们直接采用 API Gateway 的 Authorizers(授权方)功能。在本文中,为了简化配置流程,我们采用 Amazon Cognito 作为 API Gateway 的 Authorizer。

代码示例

如流程图所示,在示例中,我们先在小程序侧使用微信的 API(wx.login)获得 code,再将 code 发送给AWS的后端服务,后端服务将 code 发送给微信的 API(/jscode2session)获取 openid 与 session_key,我们将 openid 作为 Cognito 的登录状态,生成 JWT 返回给小程序,小程序将 JWT 在前端缓存,组装到后续的请求中完成鉴权。

在小程序端获取 code

PS:注意区分小程序 API 的异步、同步方法

let code = ''
let appId = ''

wx.login({
    success (res) {
        if (res.code) {
            // 获取登陆code成功
            code = res.code
            // 获取小程序ID
            appId = wx.getAccountInfoSync().miniProgram.appId
            // 组装传给AWS后端服务获取token的参数
            const params = {
                appId,
                code,
            }
            // 从入口获取信息,结合调试部分参考微信文档
            const { params1 = '' } = options.query
            
            // 向AWS后端请求token
            try {
                getAcessToken(params)
            } catch (error) {
                console.log(error)
            }
        } else {
            // 获取登陆code失败
        }
    }
})

发送给 AWS 后台服务获取 JWT

async function getAcessToken(params) {
    const userInfo = await post('path/to/login', params)
    
    const token = userInfo.data.token
    
    // 在小程序端缓存 JWT
    wx.setStorage({ key: 'authorizationToken', data: token })
}

后端获取 openid 并返回 JWT

def login_api():
    # 获取小程序发送的 code
    code = app.current_event.json_body.get("code")

    url = "https://api.weixin.qq.com/sns/jscode2session?appid={appid}&secret={secret}&js_code={code}&grant_type=authorization_code"
    url = url.format(appid=os.getenv("APP_ID"), secret=os.getenv("APP_SECRET"), code=code)
    # 请求微信服务端 API 
    r = requests.get(url)
    data = r.json()

    if data.get("errcode") != 0:
        raise UnauthorizedError(data.get("errmsg"))
    
    openid = data.get("openid")
    session_key = data.get("session_key")

    cognito = Cognito()
    # Cognito 登录逻辑,获取 JWT
    if cognito.is_user_exists(openid):
        token = cognito.user_login(openid)
    else:
        cognito.create_user(openid)
        token = cognito.user_login(openid)

    return token

配置 API Gateway Authorizer

进入 API Gateway 控制台,创建 Authorizers

对需要认证授权的 API 添加 Authorization

将缓存的 JWT  应用于业务请求

从小程序的前端缓存中取出 JWT 放入到后续的请求头中

const tokenKey = 'authorizationToken'
const exceptionAddrArr = []

const createHeader = url => {
  const header = {
    'content-type': 'application/json',
  }

  if (exceptionAddrArr.indexOf(url) === -1) {
    // 排除请求的地址不需要token的地址
    const token = wx.getStorageSync(tokenKey)
    header[tokenKey] = token
  }

  return header
}

可选:将 wx.request() Promise 化,以便兼容其他针对 Promise 的扩展,推荐使用

const createPromise = ({ url = '', params = {}, method = '' } = {}) =>
  new Promise((resolve, reject) => {
    wx.request({
      url: serverUrl + url,
      data: params,
      header: createHeader(url),
      method,
      success: res => {
        if (res.statusCode === 200) {
          resolve(res)
        } else {
          reject(res)
        }
      },
      fail: res => {
        reject(res)
      },
    })
  })

// 实际调用的请求方法
const get = (url: string, params: object | undefined = {}) => createPromise({ url, params, method: 'GET' })

const post = (url: string, params: object = {}) => createPromise({ url, params, method: 'POST' })

const put = (url: string, params: object = {}) => createPromise({ url, params, method: 'PUT' })

const del = (url: string, params: object = {}) => createPromise({ url, params, method: 'DELETE' })

小程序调试

在后端服务和小程序端都开发完成后,开发者可以借助微信提供的小程序开发者工具进行调试,验证整个链路是否畅通。由于小程序通常有几种不同的进入方式,不同的进入方式对应不同业务逻辑,调试时需要分别验证。

首先需要在小程序开发者开启不校验合法域名,这样可以直接连接调试服务器,不必等待域名申请。

选择普通编译,点击编译,可以从默认的路径打开小程序,等同于从微信搜索进入。

在下拉菜单中选择添加编译模式,可以模拟扫码不同二维码进入小程序。启动参数可以设置不同参数,会以 URL 参数的形式传入小程序,在小程序的 onLaunch 生命周期中,还可以在这里选择小程序的启动场景。

上述两种编译模式基本可以覆盖开发需要的各种场景,除此之外,微信开发者工具还提供了其他的编译模式,在此就不赘述了。

小程序发布

小程序的发布分为两部分,前(小程序)、后端需要分别进行发布。前端的发布在微信开发者工具中进行,发布完成后需要再去微信的管理后台提交审核,审核完成后才能正式发布。后端发布则在 AWS 控制台上进行,因为小程序端需要审核的原因,前后端的发布节奏需要配合好。

PS:小程序发布前还需要给后端服务申请域名并且备案,完成后需要在小程序后台加入到信任域名中,才能让小程序发布后正常运行。

先点击上传,会弹出提示:是否替换体验版?


选择确定,在版本管理中就可以看到开发版已经更新,点击下方的二维码可以在手机上预览查看。验证预览版后,点击提交审核,当前版本就会变成审核版。审核通过后,变成线上版本,就可以被用户正常使用了。

总结

从小程序的例子可以看到,AWS提供的成熟稳定的 Serverless 服务与国内厂商提供的快速推广业务逻辑的能力是很好的配合。对于已经使用 AWS 的客户来说,AWS 和微信小程序对接,扩展了他们的业务发展方式,减少了他们修改、迁移后端服务的成本。虽然两者的中间需要额外搭建一些桥梁才能实现整体方案,但是这些投入无疑取得了1 + 1大于2的结果。

希望我们的经验可以帮助您应对类似的挑战,并且让 AWS 为您解决更多的问题,让您的团队可以更专注业务本身,产生更大的价值。

参考

微信小程序登陆:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html

小程序端获取code:https://developers.weixin.qq.com/miniprogram/dev/api/open-api/login/wx.login.html

后端从微信获取session:https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html

Use API Gateway Lambda authorizers https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html

本篇作者

汪靖涵

AWS 前端工程师,负责 AWS Professional Service 项目的前端架构设计和实施。对现代前端各种方向均有涉猎,帮助客户解决数字化转型,应用现代化,和上云过程中前端相关的问题。同时对于云场景的前端工程化,和应用云能力赋能前端开发有深入研究。

许和风

AWS 云原生应用工程师,负责基于 AWS 的云计算方案架构的设计和实施。对公有云、DevOps、微服务、容器化、Serverless、全栈开发等有深入的研究,同时致力于推广云原生应用,帮助客户利用云原生来实现业务需求。

李雪阳

AWS—ProServe Product Manager , 负责ProServe客户的业务分析及产品研究与交付。针对产品全生命周期中所涉及的 用研、创意、原型及交付等均有涉猎,同时也参与帮助客户利用Amazon的能力进行创新咨询与设计。