# 公众号H5-访问云托管服务

公众号H5调用,是指在公众号登录状态下进行的调用。如果公众号未登录则和普通网页一样,没有微信用户体系。

公众号登录需要遵守公众号 - 网页授权,目前情况下只有 已经微信认证的服务号 才可以完成下述登录鉴权流程。

另外需要注意,使用此方案实施登录,推荐前端页面放置在静态资源存储。目前云托管服务里动态服务提供页面目前已经支持,如果你的服务仍然表现是 Cannot GET /__wx__/oauth,可以重新部署更新一下服务,使策略生效。

微信云托管服务目前以支持下述转发。 如果你使用自有服务器,或者其他非微信云托管提供公众号 WEB 分发服务,遇到Cannot GET /__wx__/oauth,可以在服务内(自有服务器或其他服务)配置转发规则,转发规则如下:
https://domain/__wx__/xxx -> https://servicewechat.com/wxa-qbase/xxx

location /__wx__/ {
    proxy_pass  https://servicewechat.com/wxa-qbase/;
}

# 使用前提

我们约定H5使用的公众号为A公众号,如果你希望另外一个 B公众号或者B小程序 的云托管环境也可以被 A公众号 访问,可以按照如下指引操作:

  1. A公众号B公众号/B小程序同主体:需要配置资源复用

  2. A公众号B公众号/B小程序不同主体:推荐使用「微信开放平台 - 第三方平台」方式。

  3. A公众号B公众号/B小程序不同主体:将B公众号/B小程序视为「其他客户端」,通过公网访问。参考文档其他客户端 - 访问云托管服务。此方式下,B公众号/B小程序无法使用云调用/微信令牌,且需要配置「服务器域名」(使用云托管服务的默认公网域名/自定义域名均可)。

如果你对以上有任何建议和期待,欢迎在官方交流群联系我们。

# 基本使用

下述使用代码中,我们提供了一个 JS 模块 mplogin.min.js ,方便你可以直接调用就可以完成登录初始化,节省了你的接入成本。有一些注意点需要提前知晓:

  1. 云托管 SDK 在初始化前,需要自己登录过程,不兼容原生的登录;所以如果你使用了原生登录,请替换掉。
  2. SDK登录初始化时,页面将经过2-4次跳转,最终在页面栈中增加一级,mplogin模块自动处理回退了,如果想自己处理,可以传noback:true
  3. mplogin模块可能不太适应部分框架型项目,会出现循环跳转的情况,有部分冲突,请在上线前在各种平台上充分测试,一般传noback:true可以解决大部分问题。
  4. mplogin不适合时,可参考此文档前后 SDK 方法,自行实现整个登录初始化过程。
  5. 下述方法每个页面的登录初始化是独立的,如果你的项目页面比较多,推荐使用独立登录页形式,减少初始化步骤,降低完成时间。

首先,在网页中引入如下3个 JS 文件

# 微信公众号SDK,用于使用前端公众号接口
https://res.wx.qq.com/open/js/jweixin-1.6.0.js

# 微信云服务SDK,用于调用微信云服务资源
https://web-9gikcbug35bad3a8-1304825656.tcloudbaseapp.com/sdk/1.3.0/cloud.js

# 封装的登录授权模块,用于一步完成授权登录步骤
https://web-9gikcbug35bad3a8-1304825656.tcloudbaseapp.com/sdk/1.3.1/mplogin.min.js

如果你因为业务原因,需要自己实现授权步骤,请参考此文档前后 SDK 方法。

然后在 js 中使用如下代码:

window.onload = async function () {
  // 使用登录模块,传入信息开始授权登录过程
  // 如果首次登录,页面会经历一系列跳转过程,请不要在登录前加业务处理代码,以免登录被中断。
  const result = await window.mplogin({    
    scope: "snsapi_userinfo",             // 必填,登录方式:snsapi_userinfo、snsapi_base
    appid: 'wxaa01testenvid99',           // 必填,公众号appid,将以此 appid 名义进行请求
    // redirect: '',                      // 选填,授权成功后路由的地址,目标地址应能处理授权参数,不填为当前页面
    envid: 'prod-testenvid',              // 选填,资源方微信云托管环境,如果传递此参数则会返回初始化的 cloud 操作对象
    resourceAppid: 'wxaa02testenvid99',   // 选填,如果是资源复用模式,需要填环境共享下资源方微信账号
    signature: window.location.href,      // 选填,如果需要微信 SDK 的API方法,则填写要使用的地址,会返回 signature 签名对象,envid参数不填则无效。
    // region: ''                         // 选填,环境的地域,可选ap-guangzhou、ap-beijing,不填默认为ap-shanghai
    // traceUser:false                    // 选填,默认true,是否在将用户访问记录到用户管理中,非上海地域请设置成false
    // noback: true                       // 选填,默认noback:false,此时初次跳转授权后,模块将重新回退加载页面。
                                          // 由于 CloudSDK 在初次登录时需要多个页面跳转,最终带参返回页面在浏览器页面栈中多了一到两层
                                          // 你可以根据业务自行用 history 处理上述问题,不需要模块介入则填noback:true
  })
  // 登录完成后,结果会以对象形式展示
  /* 返回信息结构:
    ret: number               // 登录的状态
    |_ -1: 参数错误,一般是 appid 缺失和 scope 类型有误
    |_  0: 成功
    |_  1: 当前页面或 redirect 页面非 https 协议,授权机制无法作用,请使用 https 协议
    |_  2: 系统判定有风险,阻止了此次登录,一般是授权耗时太长,导致过期了,清除缓存刷新可解决问题
    |_  3: 未引入微信WEBSDK,无法初始化 cloud 操作对象,或者因为环境未授权此账号使用。
    msg: string               // 登录失败的问题描述
    cloud: object[function]   // envid参数存在并成功初始化后,返回的操作函数对象
    signature: object[string] // envid、signature参数存在并成功初始化后,返回的签名信息
    info: string              // 如果snsapi_userinfo登录,则会返回用户信息的cloudID,可以使用转换接口获取信息,见下文
  */
  console.log(result)
  if (result.ret === 0) {                 // ret为0时,代表登录已经完成,可以进行业务操作
    window.app = result.cloud             // result.cloud 返回初始化可操作的 cloud 函数对象,将其放置全局

    // 向云托管服务发起调用
    const callres = await window.app.callContainer({
      path: '/xxx',                       // 填入业务自定义路径和参数,根目录,就是 / 
      method: 'POST',                     // 按照自己的业务开发,选择对应的方法
      header: {
        'X-WX-SERVICE': 'xxx',            // xxx中填入服务名称(微信云托管 - 服务管理 - 服务列表 - 服务名称)
      }
      // 其余参数同 wx.request
    })
    console.log(callres)
    
    // 使用微信公众号SDK,开始签名,签名信息在 result.signature 中
    
    // 监听签名注册成功
    wx.ready(function () {
      // 发起网络类型获取,只是测试,可以替换自己想要的 API 方法
      wx.getNetworkType({
        success: function (res) {
          window.alert('当前设备网络类型:'+res.networkType)
        }
      })
    })
    // 发起签名注册,是一个异步操作,成功会触发wx.ready
    wx.config({
      appId: 'wxaa01testenvid99',                   // 微信公众号appid
      timestamp: result.signature.timestamp + '',   // 时间戳,从返回 result.signature 中获取
      nonceStr: result.signature.nonceStr,          // 随机字符串,从返回 result.signature 中获取
      signature: result.signature.signature,        // 签名,从返回 result.signature 中获取
      jsApiList: ['getNetworkType']                 // 注册的 api 列表
    })
  } else {  // ret不为0时,代表登录出现错误,一般出现在开发调试中,正式使用一般只有2-系统拦截错误
    // 登录出现问题,打印问题描述
    window.alert(result.msg)
  }
}

snsapi_userinfo登录,会返回用户信息的cloudID,可参考此文档获取明文信息

如果你想深入了解 callContainer 的原理,建议阅读这篇文章

# 请求参数

callContainer 其他参数,直接参考 wx.request API,在这里列举常用参数:

属性 类型 默认值 必填 说明 最低版本
config.env string 云环境 ID
path string 开发者服务器接口地址
data string/object/ArrayBuffer 请求的参数
header Object 设置请求的 header,header 中不能设置 Referer。
content-type 默认为 application/json
timeout number 超时时间,单位为毫秒。最大值不能超过15秒,否则无效
method string GET HTTP 请求方法
dataType string json 返回的数据格式
responseType string text 响应的数据类型
success function 接口调用成功的回调函数
fail function 接口调用失败的回调函数
complete function 接口调用结束的回调函数(调用成功、失败都会执行)

如果希望 wx.cloud.container 返回 Promise,请勿传 success, fail 和 complete

# object.method 的合法值

说明 最低版本
OPTIONS HTTP 请求 OPTIONS
GET HTTP 请求 GET
HEAD HTTP 请求 HEAD
POST HTTP 请求 POST
PUT HTTP 请求 PUT
DELETE HTTP 请求 DELETE
TRACE HTTP 请求 TRACE
CONNECT HTTP 请求 CONNECT

# object.dataType 的合法值

说明 最低版本
json 返回的数据为 JSON,返回后会对返回的数据进行一次 JSON.parse
其他 不对返回的内容进行 JSON.parse

# object.responseType 的合法值

说明 最低版本
text 响应的数据为文本
arraybuffer 响应的数据为 ArrayBuffer

# 示例代码

<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
<script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
<script src="https://web-9gikcbug35bad3a8-1304825656.tcloudbaseapp.com/sdk/1.3.0/cloud.js"></script>
<script src="https://web-9gikcbug35bad3a8-1304825656.tcloudbaseapp.com/sdk/1.3.1/mplogin.min.js"></script>
<script>
  window.onload = async function () {
    // 如果有需要,可以使用调试组件,在手机端查看
    // const vConsole = new window.VConsole();
    // 使用登录模块,传入信息开始授权登录过程
    // 如果首次登录,页面会经历一系列跳转过程,请不要在登录前加业务处理代码,以免登录被中断。
    const result = await window.mplogin({    
      scope: "snsapi_userinfo",             // 必填,登录方式:snsapi_userinfo、snsapi_base
      appid: 'wxaa01testenvid99',           // 必填,公众号appid,将以此 appid 名义进行请求
      // redirect: '',                      // 选填,授权成功后路由的地址,目标地址应能处理授权参数,不填为当前页面
      envid: 'prod-testenvid',              // 选填,资源方微信云托管环境,如果传递此参数则会返回初始化的 cloud 操作对象
      resourceAppid: 'wxaa02testenvid99',   // 选填,如果是资源复用模式,需要填资源方微信账号
      signature: window.location.href       // 选填,如果需要微信 SDK 的API方法,则填写要使用的地址,会返回 signature 签名对象,envid参数不填则无效
      // region: ''                         // 选填,环境的地域,可选ap-guangzhou、ap-beijing,不填默认为ap-shanghai
      // traceUser:false                    // 选填,默认true,是否在将用户访问记录到用户管理中,非上海地域请设置成false
      // noback: true                       // 选填,默认noback:false,此时初次跳转授权后,模块将重新回退加载页面。
                                            // 由于 CloudSDK 在初次登录时需要多个页面跳转,最终带参返回页面在浏览器页面栈中多了一到两层
                                            // 你可以根据业务自行用 history 处理上述问题,不需要模块介入则填noback:true
    })
    // 登录完成后,结果会以对象形式展示
    /* 返回信息结构:
      ret: number               // 登录的状态
      |_ -1: 参数错误,一般是 appid 缺失和 scope 类型有误
      |_  0: 成功
      |_  1: 当前页面或 redirect 页面非 https 协议,授权机制无法作用,请使用 https 协议
      |_  2: 系统判定有风险,阻止了此次登录,一般是授权耗时太长,导致过期了,清除缓存刷新可解决问题
      |_  3: 未引入微信WEBSDK,无法初始化 cloud 操作对象,或者因为环境未授权此账号
      msg: string               // 登录失败的问题描述
      cloud: object[function]   // envid参数存在并成功初始化后,返回的操作函数对象
      signature: object[string] // envid、signature参数存在并成功初始化后,返回的签名信息
      info: string              // 如果snsapi_userinfo登录,则会返回用户信息的cloudID,可以使用转换接口获取信息,见下文
    */
    console.log(result)
    if (result.ret === 0) {                 // ret为0时,代表登录已经完成,可以进行业务操作
      window.app = result.cloud             // result.cloud 返回初始化可操作的 cloud 函数对象,将其放置全局

      // 向云托管服务发起调用
      const callres = await window.app.callContainer({
        path: '/xxx',                       // 填入业务自定义路径和参数,根目录,就是 / 
        method: 'POST',                     // 按照自己的业务开发,选择对应的方法
        header: {
          'X-WX-SERVICE': 'xxx',            // xxx中填入服务名称(微信云托管 - 服务管理 - 服务列表 - 服务名称)
        }
        // 其余参数同 wx.request
      })
      console.log(callres)
      
      // 使用微信公众号SDK,开始签名,签名信息在 result.signature 中
      
      // 监听签名注册成功
      wx.ready(function () {
        // 发起网络类型获取,只是测试,可以替换自己想要的 API 方法
        wx.getNetworkType({
          success: function (res) {
            window.alert('当前设备网络类型:'+res.networkType)
          }
        })
      })
      // 发起签名注册,是一个异步操作,成功会触发wx.ready
      wx.config({
        appId: 'wxaa01testenvid99',                   // 微信公众号appid
        timestamp: result.signature.timestamp + '',   // 时间戳,从返回 result.signature 中获取
        nonceStr: result.signature.nonceStr,          // 随机字符串,从返回 result.signature 中获取
        signature: result.signature.signature,        // 签名,从返回 result.signature 中获取
        jsApiList: ['getNetworkType']                 // 注册的 api 列表
      })
    } else {  // ret不为0时,代表登录出现错误,一般出现在开发调试中,正式使用一般只有2-系统拦截错误
      // 登录出现问题,打印问题描述
      window.alert(result.msg)
    }
  }
</script>

# 后端直接获取用户信息

公众号向云托管服务发起 callcontainer 调用时,你的服务请求 header 中会自动带有用户信息,包括openid、unionid、ip地址、可信来源等等,无需再通过小程序 wx.login 登录,然后调接口置换,大幅简化了流程。

注意,资源复用情况下,获取 openid 的字段和普通获取不一致。

# 使用限制

  1. 请求大小限制100K(请求中不建议包含图片,可通过对象存储处理);
  2. 返回包大小限制 1000k。

# 使用优势(对比wx.request)

  1. 不耗费任何公网流量,前后端通信走内网;
  2. 天然免疫 DDoS 攻击,仅授权小程序/公众号可访问后端,其他人无法访问;
  3. 无视后端服务地域影响,没有跨地域延迟,后端无需多地部署;
  4. 无需配置「服务器域名」;
  5. 后端可直接获取用户信息。

因此,如果云托管服务只有微信小程序/公众号会调用,建议在服务设置中关闭公网访问

# 常见报错

错误码 原因
-601012 环境不属于该公众号,或者没有资源复用给该公众号
-601034 公众号是测试号或者其他类型的变形账号,不符合要求,请使用已认证的服务号(非订阅号)
-601013 当前公众号账号为受限类型,一般不会出现,如果存在请提交工单处理