# 开放接口服务

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

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

# 获取微信用户信息

# 问:可以用云调用通过auth.code2Session接口获取用户 openid 嘛?

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

# 功能介绍

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

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

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

资源复用情况下,参考此文档指引

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

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

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

# 使用注意事项

# 1. 证书与 HTTPS

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

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

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

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

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

/app/cert/certificate.crt

针对不同的语言和框架,可以将以下命令添加到 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

信任后,即可使用 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. 开放接口服务集成的其他能力(如微信支付对象存储),应按照微信云托管提供的文档指引使用。
  6. 开放接口服务依赖 shell, 如镜像内无 sh/bash 将无法正常部署容器(如 scratch 作为基础镜像时)。

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

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

# (一)快速开始

  1. 如果未安装 VSCode,请下载 VSCode
  2. 在 VSCode 拓展栏搜索 weixin-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 请求地址:

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

开放接口服务调用微信支付相关例子,参见指引文档

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

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

使用步骤如下:

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

参考代码如下:

云托管端(Express.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');
    }
  });
});

小程序端

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