企业级微信小程序服务号授权关联机制

本文介绍对于同一主体(企业)如何在其小程序内部进行服务号授权,从而获取服务号的 openid,实现小程序端与服务号的关联。

openid 与 UnionID 机制

在关注者与服务号产生消息交互后,服务号可获得关注者的OpenID(加密后的微信号,每个用户对每个服务号的OpenID是唯一的。对于不同服务号,同一用户的openid不同)。服务号可通过获取用户基本信息来根据OpenID获取用户基本信息,包括语言和关注时间。

请注意,如果开发者有在多个服务号,或在服务号、移动应用、小程序、小店、带货助手、网站应用之间统一用户账号的需求,需要前往微信开放平台(open.weixin.qq.com)绑定服务号后,才可利用 UnionID 机制来满足上述需求。

openid,用户的身份标记,关注服务号后产生(取消关注后依然存在),只要服务号不变,openid 就不会变。

unionid,用户(主要针对开发者)在开放平台注册后产生,不是所有的用户都有 unionid 。

所以,对于一般用户而言,如果想在小程序内使用服务号的功能(比如发送模板消息),就需要知道这个用户的 openid

服务号网页授权

获取 openid 的前提是必须得到用户的授权。好在服务号提供了微信网页授权机制,来获取用户基本信息,进而实现业务逻辑。

网页授权流程分为四步:

  1. 引导用户进入授权页面;
  2. 接受用户授权同意,在回调页面中获取 code;
  3. 通过code换取用户授权 access_token(与服务端接口 access_token 不同);
  4. 通过用户授权 access_token 和 openid 获取用户基本信息(支持 UnionID 机制);

访问授权链接

用户在小程序中使用 web-view 访问授权链接,链接格式如下:

open.weixin.qq.com/connect/oau...

具体参数配置说明,可以直接查看 👉️跳转授权链接

其中有几点需要注意:

第一,链接的参数顺序不要更改,因为授权操作安全等级较高,微信会对授权链接做正则强匹配校验;

第二,这里提及到一个回调页面 ,这个页面需要我们自己提供,在我们引导用户进入授权页面时,需要提前将回调页面的网址作为 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 的方法不止一种,欢迎大家可以留言讨论~ 🤗

参考资料

相关推荐
夏小花花1 天前
Java 日常开发笔记(小程序页面交互传参-id)
java·微信小程序·vue
青青子衿越2 天前
微信小程序右上角分享页面找不到路径bug
微信小程序·小程序·bug
江-月*夜2 天前
微信小程序miniprogram-ci 模块实现微信小程序的自动上传功能
ci/cd·微信小程序·小程序
九点五亿少女的梦2 天前
uniapp开发微信小程序遇到富文本内容大小变形问题v-html
微信小程序·uni-app·html
xkxnq3 天前
微信小程序性能优化
微信小程序·小程序
小小黑0073 天前
总结运行CRMEB标准版(uniapp)微信小程序的问题
微信小程序·小程序·uni-app
你喜欢喝可乐吗?4 天前
微信小程序与后台管理系统开发全流程指南
spring boot·微信小程序·vue
CRMEB定制开发5 天前
CRMEB会员电商系统集群部署 + 腾讯云日志托管优化方案
微信小程序·公众号商城·商城源码·crmeb·开源商城
郭邯5 天前
小程序中动画的几种实现方式
前端·微信小程序