背景简述
项目部分需求的流程中涉及跨微信应用,需要借助微信开放平台实现应用间的对接。特整理此文档记录。
涉及的应用对象
整个项目中涉及3个应用:推广服务号、ToC的业务小程序、ToB的业务小程序
项目需求
当关注服务号的用户在使用【ToC的业务小程序】或【ToB的业务小程序】时,触发了需要后续消息通知的事件(例如:投递简历后,有进一步结果会发消息通知),会在服务号里通知用户事件进展。
单个应用梳理——获取当前应用下的用户信息
获取当前应用的凭证信息
每个应用在申请完毕后,微信会生成当前应用的AppID(该应用的唯一凭证)和 AppSecret(该应用的唯一凭证密钥)。
注:AppSecret需要自行保存,在微信后台不会像AppID那样明示。
获取接口调用凭据
在微信平台下,接口调用凭据为【access_token】,在后续调微信提供的其他接口时,绝大多数下(除:小程序登录接口不需要)都需要在接口信息里加入access_token参数,才能获取到各接口正确响应信息。
注:每个应用的接口凭证是需要单独维护的,跨应用的接口凭证是不可不共用的。
调用说明
调用方式:HTTPS 调用
接口名和方式
GET https://api.weixin.qq.com/cgi-bin/token
接口参数说明
接口调用注意点
- access_token的有效期通过返回的expires_in来传达,目前是7200秒之内的值。
- 重复获取将导致上次获取的access_token失效。
- 建议在同一个应用下的不同业务接口调用中,将access_token做统一管理。不应该各自去刷新,否则容易造成冲突,导致access_token覆盖而影响业务。(也就是在自己的项目中,对单个应用,根据返回的expires_in来作为过期时间,统一获取和刷新access_token)
- 小程序应用无需配置 IP 白名单。公众号调用接口时,需要提前将服务器 IP 地址添加到 IP 白名单中,否则将无法调用成功。
详见微信文档
公众号:https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html
小程序:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-access-token/getAccessToken.html
不同应用下获取用户信息
在单应用下,微信用户在应用中的唯一标识叫【OpenID】。
注:每个应用的用户OpenID也是需要单独管理的,跨应用的用户OpenID不互通。
下面提供公众平台下获取微信用户OpenID的方式 和 小程序里获取微信用户OpenID的方式。
公众平台下获取微信用户OpenID
在微信用户关注公众号后,使得微信服务器通过事件推送的形式,通知到我们在公众号后台配置的服务器地址,从而可以获取到关注用户的信息(包括openID)。
步骤如下:
1、在微信公众号后台配置自己服务的信息。主要包括:
- 提供一个接口地址(配置里的服务器地址),供微信在捕捉到用户事件后,调取我们接口发送用户信息。
- 自己设置一个token,后面用作签名校验。
注意:在开启【服务期配置】后,在公众号后台设置的菜单和自定义回复语会失效。需要通过接口形式触发回复语和设置菜单
2、在自己项目里,编写接口接收微信发来的事件通知。请求接口的参数里会带上关注用户的openID。
代码如下:
例如在上步配置里填的接口地址为:http://demo.com/token/wechat
package com.demo.upms.admin.controller;
import com.demo.common.core.dto.WxServiceMsgDto;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Date;
import java.util.Map;
@RestController
@AllArgsConstructor
@RequestMapping("/token")
@Slf4j
public class TokenController {
/**
* 公众号后台配置的token值
*/
public static final String Token="123456";
/**
* 功能描述: 微信对接验证接口
* @Param wxServiceMsgDto 用户事件信息
* @return String
*/
@PostMapping(value="/wechat", produces = "application/xml;charset=utf-8")
public String wechat(@RequestParam(value = "signature", required = false) String signature,
@RequestParam(value = "timestamp", required = false) String timestamp,
@RequestParam(value = "nonce", required = false) String nonce,
@RequestParam(value = "echostr", required = false) String echostr,
@RequestBody(required = false) WxServiceMsgDto wxServiceMsgDto) throws Exception{
if (null != echostr && !"".equals(echostr.trim())) {
// 用于微信校验接口存在性,必须返回echostr
return echostr;
}
// 触发事件的用户openID
String openId = wxServiceMsgDto.getFromUserName();
String[] str = { Token, timestamp, nonce };
// 字典排序
Arrays.sort(str);
String bigStr = str[0] + str[1] + str[2];
// SHA1加密
String digest = sha1(bigStr);
// 确认请求来至微信
if (digest.equals(signature)) {
return echostr;
}
return echostr;
}
/**
* SHA1加密
* @Param data 需加密的字符串
* @return String
*/
private static String sha1(String data) throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("SHA1");
//把字符串转为字节数组
byte[] b = data.getBytes();
//使用指定的字节来更新我们的摘要
md.update(b);
//获取密文 (完成摘要计算)
byte[] b2 = md.digest();
//获取计算的长度
int len = b2.length;
//16进制字符串
String str = "0123456789abcdef";
//把字符串转为字符串数组
char[] ch = str.toCharArray();
//创建一个40位长度的字节数组
char[] chs = new char[len*2];
//循环20次
for(int i=0,k=0;i<len;i++) {
//获取摘要计算后的字节数组中的每个字节
byte b3 = b2[i];
// >>>:无符号右移
// &:按位与
//0xf:0-15的数字
chs[k++] = ch[b3 >>> 4 & 0xf];
chs[k++] = ch[b3 & 0xf];
}
//字符数组转为字符串
return new String(chs);
}
}
package com.demo.common.core.dto;
import lombok.Data;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement(name = "xml")
@XmlAccessorType(XmlAccessType.FIELD)
@Data
public class WxServiceMsgDto {
@XmlElement(name = "Event")
private String event;
@XmlElement(name = "Content")
private String content;
@XmlElement(name = "MsgType")
private String msgType;
@XmlElement(name = "ToUserName")
private String toUserName;
/**
* fromUserName为关注人的openId
**/
@XmlElement(name = "FromUserName")
private String fromUserName;
@XmlElement(name="CreateTime")
private String createTime;
}
公众号接收事件相关接口文档:https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_event_pushes.html
小程序下获取微信用户OpenID
通过小程序登录获取openID。需要在小程序里通过 wx.login 接口,获得临时登录凭证 code 后,将code传到后端服务器,调用【小程序登录】接口完成登录流程。
调用方式
调用方式:HTTPS 调用
接口名和方式
GET https://api.weixin.qq.com/sns/jscode2session
接口参数说明
跨应用关系梳理
如果涉及跨应用实现需求,需要申请微信开放平台。将微信公众号和小程序都绑定到微信开放平台里。就可以获取单个微信用户在多应用下的唯一标识 unionid
OpenID与unionid的关系与使用
开发者可通过 OpenID 来获取用户基本信息。特别需要注意的是,如果开发者拥有多个移动应用、网站应用和公众帐号,可通过获取用户基本信息中的 unionid 来区分用户的唯一性,因为只要是同一个微信开放平台帐号下的移动应用、网站应用和公众帐号,用户的 unionid 是唯一的。换句话说,同一用户,对同一个微信开放平台下的不同应用,unionid是相同的。
理解图:
根据 toC小程序中的 openID,可以获取到 unionid,存储 unionid。
下次在数据库可以根据 unionid 查到服务号里的 openID。
由此,就可以借助 unionid 给关注公众号的小程序用户发送服务通知了。
不同应用下获取 unionid
前提:
- 小程序、公众号都需要绑定到微信开放平台里。
- 用户如果没有登录过公众号,也没有关注过公众号的情况下,是获取不到 unionid 的
小程序里
【小程序端】在调登录接口时,就可以获取到unionid。
公众号下
通过传用户{openID}参数,请求【获取用户信息】接口,查询用户unionid
ps. 也可以通过该接口,获取用户最新的关注状态。
调用方式:HTTPS 调用
接口名和方式
GET https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
接口参数说明
请求参数:
响应参数: