本文介绍对于同一主体(企业)如何在其小程序内部进行服务号授权,从而获取服务号的 openid,实现小程序端与服务号的关联。
openid 与 UnionID 机制
在关注者与服务号产生消息交互后,服务号可获得关注者的OpenID(加密后的微信号,每个用户对每个服务号的OpenID是唯一的。对于不同服务号,同一用户的openid不同)。服务号可通过获取用户基本信息来根据OpenID获取用户基本信息,包括语言和关注时间。
请注意,如果开发者有在多个服务号,或在服务号、移动应用、小程序、小店、带货助手、网站应用之间统一用户账号的需求,需要前往微信开放平台(open.weixin.qq.com)绑定服务号后,才可利用 UnionID 机制来满足上述需求。
openid
,用户的身份标记,关注服务号后产生(取消关注后依然存在),只要服务号不变,openid 就不会变。
unionid
,用户(主要针对开发者)在开放平台注册后产生,不是所有的用户都有 unionid 。
所以,对于一般用户而言,如果想在小程序内使用服务号的功能(比如发送模板消息),就需要知道这个用户的 openid。
服务号网页授权
获取 openid 的前提是必须得到用户的授权。好在服务号提供了微信网页授权机制,来获取用户基本信息,进而实现业务逻辑。
网页授权流程分为四步:
- 引导用户进入授权页面;
- 接受用户授权同意,在回调页面中获取 code;
- 通过code换取用户授权 access_token(与服务端接口 access_token 不同);
- 通过用户授权 access_token 和 openid 获取用户基本信息(支持 UnionID 机制);
访问授权链接
用户在小程序中使用 web-view 访问授权链接,链接格式如下:
具体参数配置说明,可以直接查看 👉️跳转授权链接。
其中有几点需要注意:
第一,链接的参数顺序不要更改,因为授权操作安全等级较高,微信会对授权链接做正则强匹配校验;
第二,这里提及到一个回调页面 ,这个页面需要我们自己提供,在我们引导用户进入授权页面时,需要提前将回调页面的网址作为 redirect_uri
参数一并发送给服务号的授权接口。这样,code 才会返回至 redirect_uri 页面,供开发者使用。
接下来,我们就先部署一下回调页面。
部署回调中转网页
这个中转页面其实内容很简单,就是获取地址后面拼接的 code 参数,然后根据自己的实际业务需求跳转到对应的小程序页面。
处理 code
我们可以通过以下方法快速获取查询参数中的 code 字符串。
js
// 获取查询参数
getUrlSearchParams() {
const obj = {}
const params = new URLSearchParams(window.location.search);
params.forEach((value, key) => {
obj[key] = value;
});
return obj;
},
返回小程序
基于 web-view 的能力,我们可以在网页中可使用 JSSDK 1.3.2提供的接口返回小程序页面。
js
// 处理 code
getCode() {
const { code /* 也可以有其他参数 platform */ } = this.getUrlSearchParams();
switch (platform) {
// 1. 返回小程序中的 aa 页面
case 'aa':
wx.miniProgram.redirectTo({ url: '/pages/common/aa/index?code=' + code });
break;
// 2. 返回小程序中的 bb 页面
case 'bb':
wx.miniProgram.redirectTo({ url: '/pages/common/bb/index?code=' + code });
break;
// 3. 也可以跳转到 web 页面
case 'cc':
window.location.href = 'https://your.web.com/login?code=' + code;
break;
default:
break;
}
}
完整代码详见 👉️github | 服务号网页授权
网页授权回调域名
在部署网页时,需要遵守服务号的一些配置要求。比如,必须通过 ICP 备案,使用 https,安装校验文件等。

之后,我们便可在服务号中进行功能配置。

至此,前端部分的工作就完成了,我们接下来要做的就是将获取到的 code 发送给服务端,让服务端去调用 API 获取用户信息。
服务端获取 openid
换取用户授权凭证
服务端接受到 code 后,调用服务端 API 换取用户授权凭证,即可获取 access_token、openid、unionid 等信息。
java
@Override
public String wechatQueryOpenid(String code) {
String authStr = restTemplate.exchange(
"https://api.weixin.qq.com/sns/oauth2/access_token?appid=appid&secret=secret&code=" + code + "&grant_type=authorization_code",
HttpMethod.GET,
null,
new ParameterizedTypeReference<String>() {}
).getBody();
Map authMap = (Map) JSON.parse(authStr);
if (authMap == null || authMap.get("openid") == null) {
return null;
}
String openid = (String) authMap.get("openid");
// todo: 保存到用户的信息中......
return openid;
}
发送模板消息
我们以发送服务号模板消息为例,来演示一下获取服务号 openid 之后的业务场景。
java
@Override
public void sendModelMessage(String openId, Map wechatNoticeVO) {
try {
//获取微信公众号 token
String serviceAccessToken = getServiceAccessToken();
//封装请求头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
wechatNoticeVO.put("touser", openId);
HttpEntity<HashMap> requestEntity = new HttpEntity(wechatNoticeVO, headers);
//调用请求
HashMap result = restTemplate.exchange(
"https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + serviceAccessToken,
HttpMethod.POST,
requestEntity,
new ParameterizedTypeReference<HashMap>() {}
).getBody();
Integer successode = new Integer("0");
if (result == null || !successode.equals((Integer) result.get("errcode"))) {
log.info("发送消息异常" + (Integer) result.get("errcode") + ":" + (String) result.get("errmsg"));
Integer tokenExpired = new Integer("42001");
if (tokenExpired.equals((Integer) result.get("errcode"))) {
redisTemplate.delete(RedisKeyConstants.WECHAT_AUTHTOKEN);
}
}
} catch (Exception e) {
log.info("发送服务号模板消息异常!", e);
}
}
private String getServiceAccessToken() {
// 尝试先从 redis 中获取 token
String authToken = (String) redisTemplate.opsForValue().get(RedisKeyConstants.WECHAT_AUTHTOKEN);
// 当没有获取到 token 的时候重新获取 token
if (StringUtils.isBlank(authToken)) {
Map authMap = restTemplate.exchange(
"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appId + "&secret=" + secret,
HttpMethod.POST,
null,
new ParameterizedTypeReference<HashMap>() {}
).getBody();
if (authMap != null && authMap.get("access_token") != null) {
authToken = (String) authMap.get("access_token");
redisTemplate.opsForValue().set(RedisKeyConstants.WECHAT_AUTHTOKEN, authToken, 1000, TimeUnit.SECONDS);
} else {
authToken = getServiceAccessToken();
}
}
return authToken;
}
完整流程

当然,获取 openid 的方法不止一种,欢迎大家可以留言讨论~ 🤗