# 开放接口服务

「开放接口服务」是微信云托管推出的云调用彻底免鉴权解决方案服务。

通过「开放接口服务」，就不需在容器内维护 `cloudbase_access_token` 等鉴权信息，即可轻松接入微信体系访问后台接口。

## 获取微信用户信息

### 问：可以用云调用通过[auth.code2Session](/miniprogram/dev/server/API/user-login/api_code2session.md)接口获取用户 openid 吗？
答：无需借助云调用，小程序或公众号（需要是登录状态）向云托管服务发起[callcontainer 调用](/minigame/dev/wxcloudrun/src/development/call/mini)时，你的服务请求 header 中会自动带有[用户信息](/minigame/dev/wxcloudrun/src/development/weixin/index)，包括 openid、unionid、IP 地址、可信来源等等，无需再通过小程序 wx.login 登录，然后调接口置换，大幅简化了流程。公众号的登录也都自己闭环了，不需要再用 code 转换成对应用户信息。
**公众号处于未登录状态时，header 中不包含微信用户信息。**

## 功能介绍

「开放接口服务」以旁加载形式部署到服务中，占用资源和内存极小，与业务服务共用资源。开放接口服务中转 `api.weixin.qq.com` 的请求，并自动处理 `token` 和鉴权问题。

「开放接口服务」使用方式如下：

1. 将需要调用的接口，配置在[云调用-微信令牌权限配置](https://cloud.weixin.qq.com/cloudrun/openapi)中。
2. 查看[服务端接口](/miniprogram/dev/server/API/)的文档。容器内云调用形式与原有接口文档相同，但不需要携带接口调用凭证（`access_token` 和 `cloudbase_access_token`）。
3. 打开[控制台-云调用](https://cloud.weixin.qq.com/cloudrun/openapi)中开放接口服务的开关。
4. 在打开开关的状态下，创建服务版本。服务项目应用代码中使用 **HTTP** 协议，向 `api.weixin.qq.com` 发起请求即可。如果云调用请求正常，会在返回的 `Header` 中附带云调用唯一的 `x-openapi-seqid`，代表该请求正在使用云调用链路。

**资源复用情况下，参考[此文档指引](../reuse/index#%E8%B5%84%E6%BA%90%E5%A4%8D%E7%94%A8%E4%B8%8B%E7%9A%84%E5%BC%80%E6%94%BE%E6%8E%A5%E5%8F%A3%E8%B0%83%E7%94%A8)。**

> 注意：如果你在开启「开放接口服务」前，已经存在服务版本，需要在开启「开放接口服务」后重新构建版本才会生效。

开启「开放接口服务」后，使用形式与传统方式相同，只是不需携带 access_token，下面拿 nodejs 举例子，举一反三理解：

```js
const fs = require('fs')
const request = require('request')
return new Promise((resolve, reject) => {
  request({
    method: 'POST',
    // url: 'http://api.weixin.qq.com/wxa/msg_sec_check?access_token=TOKEN',
    url: 'http://api.weixin.qq.com/wxa/msg_sec_check', // 这里就是少了一个token
    body: JSON.stringify({
      openid: '用户的openid', // 可以从请求的header中直接获取 req.headers['x-wx-openid']
      version: 2,
      scene: 2,
      content: '安全检测文本'
    })
  },function (error, response) {
    console.log('接口返回内容', response.body)
    resolve(JSON.parse(response.body))
  })
})
```

「开放接口服务」还集成了「微信支付」「对象存储」等接口集合，代替了业务侧必须的鉴权步骤。以下是对比图：
![1.png](https://res.wx.qq.com/op_res/4tnvBm9712ooMkeHQ8IbH9EIyGzJToO3IlSAfTxC884e6rlK0WMYzcoSrVA7UdQwh4DfWWMU9r7TKcllHjfrvA)

## 资源复用情况下的使用【重点】

如果你的环境共享给其他小程序/公众号使用时，需要注意云调用归属问题。

当你直接调用接口，不带任何参数的情况下，归属为云托管环境所属的「小程序/公众号」。

如果你想指定其他「小程序/公众号」身份，需要在地址后加 `from_appid` 参数

``` bash
# 原接口形式，权限归属为云托管环境所属的小程序/公众号
http://api.weixin.qq.com/wxa/getwxadevinfo

# 资源复用的形式，APPID为你要指定的小程序/公众号appid
http://api.weixin.qq.com/wxa/getwxadevinfo?from_appid=APPID
```

比如，你的小程序 `wxaaaa` 有一个云托管环境，共享给小程序 `wxbbbb` 和 公众号 `wxcccc`

则下面的接口调用身份是

``` bash
# 调用时，创建小程序 `wxaaaa` 的小程序码
https://api.weixin.qq.com/wxa/getwxacode

# 调用时，创建小程序 `wxbbbb` 的小程序码
https://api.weixin.qq.com/wxa/getwxacode?from_appid=wxbbbb

# 调用时，报无权限，因为公众号不能生成小程序码
https://api.weixin.qq.com/wxa/getwxacode?from_appid=wxcccc
```

再比如，你的公众号 `wxcccc` 有一个云托管环境，共享给小程序 `wxbbbb` 和 小程序 `wxaaaa`

``` bash
# 调用时，报无权限，因为公众号不能生成小程序码
https://api.weixin.qq.com/wxa/getwxacode

# 调用时，创建小程序 `wxbbbb` 的小程序码
https://api.weixin.qq.com/wxa/getwxacode?from_appid=wxbbbb

# 调用时，创建小程序 `wxaaaa` 的小程序码
https://api.weixin.qq.com/wxa/getwxacode?from_appid=wxaaaa
```

## 使用注意事项

### 1. 证书与 HTTPS

**使用云调用时建议使用 HTTP 协议以获得更好的性能**，如确有需求使用 HTTPS 协议，请参考以下说明。官方模板的 Dockerfile 中，也都加入了安装命令，可直接参考。

为了允许用户使用 HTTPS 协议访问容器云调用，云调用会在用户容器内安装 `api.weixin.qq.com` 的自签名证书，该证书在容器内一次性生成使用，仅容器内有效，仅容器云调用内使用该证书进行认证，与用户容器完全隔离。

由于容器系统版本、服务框架不一，在容器启动过程中，会调用 `update-ca-certificate` 信任证书。如需使用 HTTPS，需提前确认容器中包含 `update-ca-certificate`：

```dockerfile
# debian/ubuntu
RUN apt install ca-certificates -y
# alpine
RUN apk add ca-certificates
# centos/fedora/rhel
RUN yum install ca-certificates
```

若容器安装证书不生效，或使用部分自带根证书的运行时（如 Node.JS），需手动信任以下目录的根证书：

```bash
/app/cert/certificate.crt
```

针对不同的语言和框架，可以将以下命令添加到 Dockerfile 中从而信任根证书：

```dockerfile
# Java 手动导入根证书
RUN keytool -importcert -file /app/cert/certificate.crt -alias apiweixin -keystore $JAVA_HOME/jre/lib/security/cacerts

# Node 指定根证书环境变量
ENV NODE_EXTRA_CA_CERTS=/app/cert/certificate.crt

# Python requests 指定环境变量
ENV REQUESTS_CA_BUNDLE=/app/cert/certificate.crt
```

信任后，即可使用 HTTPS 协议进行容器云调用。

### 2. HTTP Header 区别

根据标准，以下的 HTTP Header 将不会被传递（包括请求/回包）。

- connection
- proxy-connection
- keep-alive
- proxy-authenticate
- proxy-authorization
- te*
- trailer
- transfer-encoding
- upgrade
- expect

容器内云调用会增加的 Header，用于区分请求和排查问题：

- x-openapi-seqid

容器内云调用会修改的 Header，用于标记请求来源：

- x-forwarded-for

> 服务使用 java spring 框架时，如果 header 中找不到“x-forwarded-for”,请在 “application.properties” 中设置 “server.use-forward-headers=true”.

### 3. 请求包体大小限制

请求包体最大限制：32 MiB

请求回包最大限制：32 MiB

### 4. 请求时间超时

云调用最大超时时间为 30s。

### 5. 存在时机

「开放接口服务」开关在关闭状态期间，产生的任何服务的版本，其下运行的实例都不会存在「开放接口服务」。

「开放接口服务」开关在开启状态期间，产生的任何服务的版本，其下运行的实例都会存在「开放接口服务」。

也就是说，环境中可以同时存在具有「开放接口服务」的版本和不具有「开放接口服务」的版本。

实例扩缩容时，不会遵照当前的「开放接口服务」开关的状态，而是遵循版本创建时的开关状态。

一个版本下的实例，要么全有「开放接口服务」，要么全都没有，没有其他第三种状态。

### 6. 其他注意事项

1. 开启「开放接口服务」，请求中如果仍然附带 access_token，不会覆盖掉，这类操作通常用于环境共享场景下。
2. 使用 `secret` 置换的 `access_token`，经过开放服务不需要配置白名单，原因如第1条，不覆盖。但如果使用「开放接口服务」鉴权，则必须使用白名单，因为其仍然使用 `cloudbase_access_token`
3. 通过请求头返回 `x-openapi-seqid` 和解析地址为 `Docker` 内部地址判断是否使用了开放接口服务。
4. 部分自带根证书的运行时，需要手动设置证书，证书目录为： `/app/cert/certificate.crt`
5. 开放接口服务集成的其他能力（如[微信支付](pay)、[对象存储](/minigame/dev/wxcloudrun/src/guide/storage/index)），应按照微信云托管提供的文档指引使用。
6. 开放接口服务依赖 `shell`， 如镜像内无 `sh/bash` 将无法正常部署容器（如 `scratch` 作为基础镜像时）。

## 三、本地调试中如何使用「开放接口服务」

使用 VSCode 插件，可以在支持 Docker 环境的系统进行本地调试云调用。

### （一）快速开始

1. 如果未安装 VSCode，请[下载 VSCode](https://code.visualstudio.com)。
2. 在 VSCode 拓展栏搜索 [weixin-cloudbase](https://marketplace.visualstudio.com/items?itemName=weixin-cloudbase.vscode-docker-wx-cloudbase) 然后安装。
3. 完成配置后，在左侧 Docker 面板内，右击 `Proxy nodes for VPC access` 中的 `api.weixin.qq.com`，点击启动（Start）。
4. 右击用户容器，点击启动（Start），容器内即可访问本地云调用。

更新 vscode 插件到最新版，启动自己的业务服务，在业务服务运行过程中，启动 vpc 中的 `api.weixin.qq.com` 服务，或者先启动`api.weixin.qq.com` 服务，再启动业务服务，没有先后限制。

插件将会在你的云托管环境中开启一个代理服务，用于和本地 `api.weixin.qq.com` 服务，同时和业务服务共享同一个网络，就实现了本地的「开放接口服务」，需要注意，本地调试中只是模拟了业务服务的所处环境，不是真实的线上部署情况。

### （二）常见问题

#### 1. 如何确定请求经过本地云调用？

- 请求头返回了 `x-openapi-seqid`。
- 请求解析到的地址为 Docker 内部地址（如 `10.0.0.2`）。

#### 2. 是否支持 HTTPS 云调用？

由于技术原因，本地调试暂不支持 HTTPS 云调用，需要先暂时使用 HTTP 协议进行本地调试。

#### 3. 为何不支持外网直接访问开放接口服务？

为保证小程序凭证以及小程序敏感接口安全，开放接口服务不允许外网直接访问。
通过本地云调用，会启动与线上版本能力一致的调试镜像，且通过本地调试可以安全地访问内网 VPC 接口。

## 四、服务使用示例

以「获取当前小程序的业务域名」为例。

使用 cURL 请求地址：

```bash
curl -v http://api.weixin.qq.com/wxa/getwxadevinfo
```

返回 `errcode` 为 0，代表请求成功。

各语言的调用形式仅供参考，根据具体库和语言版本的不同，可能存在一定差别。

语言/框架 | 调用示例代码 | 备注
-- | -- | --
Java | 
```
HttpRequest.newBuilder()
    .uri(URI.create("http://api.weixin.qq.com/wxa/getwxadevinfo"))
    .build();
```
 | Java HttpClient (Java 11+)	
Go | 
```
resp, err := HTTP.Get("http://api.weixin.qq.com/wxa/getwxadevinfo")
```
 | -
C# | 
```
new HttpClient().GetAsync("http://api.weixin.qq.com/wxa/getwxadevinfo");
```
 | .NET SDK 5.0+
NodeJS | 
```
request('http://api.weixin.qq.com/wxa/getwxadevinfo', console.log)
```
 | request
Python | 
```
import requests
response = requests.get("http://api.weixin.qq.com/wxa/getwxadevinfo")
```
 | requests

开放接口服务调用微信支付相关例子，参见[指引文档](pay)

### 使用云托管获取用户手机号

使用云托管结合开放接口服务，服务端可以无需加解密逻辑，免鉴权获取用户手机号（需要小程序拥有相关权限，且用户确认允许获取）。使用类似的方法，也可以获取用户的步数等数据。

使用步骤如下：

1. 开启「开放接口服务」，配置接口 `/wxa/getopendata` 到权限列表中。
2. 在小程序中使用 `<button open-type="getPhoneNumber">` 获取用户手机号。
3. 将按钮回调中的 CloudID 发送到云托管服务，后台换取手机号信息。

参考代码如下：

云托管端（Express.js 示例）

```js
// 需要使用 body-parser 处理 JSON 数据
// 示例路径为 /phone，根据业务场景可自行修改
app.post('/phone', (req, res) => {
  // 拼接 Header 中的 x-wx-openid 到接口中
  const api = `http://api.weixin.qq.com/wxa/getopendata?openid=${req.headers['x-wx-openid']}`;
  request(api, {
    method: 'POST',
    body: JSON.stringify({
      cloudid_list: [req.body.cloudid], // 传入需要换取的 CloudID
    }),
    headers: {
      'Content-Type': 'application/json',
    },
  }, (err, resp, body) => {
    try {
      const data = JSON.parse(body).data_list[0]; // 从回包中获取手机号信息
      const phone = JSON.parse(data.json).data.phoneNumber;
      // 将手机号发送回客户端，此处仅供示例
      // 实际场景中应对手机号进行打码处理，或仅在后端保存使用
      res.send(phone);
    } catch (error) {
      res.send('get phone failed');
    }
  });
});
```

小程序端

```js
getPhoneNumber (e) {
  wx.cloud.callContainer({
      "config": {
        "env": "环境ID" // 修改为云托管的环境ID
      },
      "path": "/phone", // 与服务端的 Path 相同，示例为 /phone
      "header": {
        "X-WX-SERVICE": "服务名称", // 修改为服务名称
        "content-type": "application/json"
      },
      "method": "POST",
      "data": {
          cloudid: e.detail.cloudID,
      }
    }).then(res => console.log('用户的手机号为：', res.data));
},
```

示例输出如下：

```
用户的手机号为：xxxxxx
```
