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

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

公众号登录需要遵守[公众号-网页授权](/doc/service/api/webdev/access/api_snsauth.md)，目前情况下只有 `已经微信认证的服务号` 才可以完成下述登录鉴权流程。

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

> 微信云托管服务目前以支持下述转发。
> 如果你使用自有服务器，或者其他非微信云托管提供公众号 WEB 分发服务，遇到`Cannot GET /__wx__/oauth`，可以在服务内（自有服务器或其他服务）配置转发规则，转发规则如下：

> `https://domain/__wx__/xxx -> https://servicewechat.com/wxa-qbase/xxx`

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

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

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

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

## 使用前提

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

1. `A公众号` 和 `B公众号`/`B小程序`同主体：需要配置[资源复用](/minigame/dev/wxcloudrun/src/guide/reuse/index)

2. `A公众号` 和 `B公众号`/`B小程序`不同主体：推荐使用「[微信开放平台-第三方平台](/doc/oplatform/Third-party_Platforms/2.0/product/Third_party_platform_appid.md)」方式。

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

> 如果你对以上有任何建议和期待，欢迎在[官方交流群](https://www.cloudbase.vip/scan/?code=werun)联系我们。

## 基本使用

下述使用代码中，我们提供了一个 JS 模块 [mplogin.min.js](https://web-9gikcbug35bad3a8-1304825656.tcloudbaseapp.com/sdk/1.3.0/mplogin.min.js) ，方便你可以直接调用就可以完成登录初始化，节省了你的接入成本。有一些注意点需要提前知晓：

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

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

```bash
# 微信公众号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
```

> 如果你因为业务原因，需要自己实现授权步骤，请参考[此文档](/miniprogram/dev/wxcloudservice/wxcloud/reference-sdk-api/web/Cloud.startLogin.md)前后 SDK 方法。

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

```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，可参考[此文档](/miniprogram/dev/server/API/cloudbase/others/api_getopendata.md)获取明文信息

如果你想深入了解 `callContainer` 的原理，建议阅读[这篇文章](/minigame/dev/wxcloudrun/src/practice/call)

## 请求参数

`callContainer` 其他参数，直接参考 [wx.request](/miniprogram/dev/api/network/request/wx.request.md) 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 |  |

## 示例代码

```html
<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 中会自动带有[用户信息](/minigame/dev/wxcloudrun/src/development/weixin/index)，包括 openid、unionid、IP 地址、可信来源等等，无需再通过小程序 wx.login 登录，然后调接口置换，大幅简化了流程。

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

## 使用限制

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

## 常见报错

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