「这是我参与11月更文挑战的第16天,活动详情查看:2021最后一次更文挑战」。
前言
上一篇 我们已经打通了微信服务端和我们本地项目之间的通道,接下来,我们来实现扫码登录的功能。
登录逻辑
- 用户扫码
- 带参传到服务端,服务端根据信息获取用户的具体信息
- 本地服务端根据获取到的微信信息去做逻辑处理,用户已存在则登录,返回登录认证信息,不存在则先注册,之后返回登录认证信息。
具体实现
目前微信服务端已经可以和本地实现通信,接下来我们要去调取微信服务端的接口获取用户的信息,以及公众号二维码等信息。
获取这些信息的前提是获取用户微信服务端授权,基于Oauth2协议,我们已经有了appID,appsecret,接下来获取accessToken,就可以了。
因为频繁的使用到accessToken,所有这里我存在了redis里。
具体实习可以看后面的工具类
1. 获取微信公众号二维码
获取一个带参数的二维码,用户扫码时会将用户一些信息以及参数传给后端,接下我们就可以围绕着这些参数做文章了
2. 扫码触发事件
这里有个小坑,之前看文档一直不明白,微信扫码触发事件,参数是通过xml包的形式发送给我们的,所以正常拿是拿不到的,这里需要整合一下xml解析的工具类,拿到参数。
主要是我们可以拿到用户的openid,之后就是获取用户信息,然后具体的登录逻辑就可以根据实际情况去写代码了。
添加依赖
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.thoughtworks.xstream/xstream -->
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.18</version>
</dependency>
复制代码
实现逻辑(伪代码)
@ApiOperation(value = "用户关注/或者取消关注")
@PostMapping("/wechat")
public void follow(HttpServletRequest request) throws Exception {
try {
// 接受扫描二维码回调参数
Map<String, String> map = new HashMap<>();
// 读取输入流
SAXReader reader = new SAXReader();
Document document = reader.read(request.getInputStream());
// 得到xml根元素
Element root = document.getRootElement();
XmlUtil.parserXml(root, map);
log.info("【map】= {}", map);
String userInfo = weChatUtil.getUserInfo(request.getParameter("openid"));
JSONObject userInfoJson = JSONUtil.parseObj(userInfo);
String openid = userInfoJson.getStr("openid");
String eventKey = map.get("EventKey").replace("qrscene_", "");
log.info(eventKey);
// 监听扫码回调事件
String event = map.get("Event");
switch (event) {
// 扫码触发
case "SCAN":
//查询用户存在就直接返回登录信息,不存在先注册再返回
break;
// 关注
case "subscribe":
//查询用户存在就直接返回登录信息,不存在先注册再返回
break;
// 取关
case "unsubscribe":
System.out.println("取关了");
break;
default:
log.info("【map】= {}", map);
break;
}
} catch (Exception e) {
throw new ServiceException("获取微信用户信息异常");
}
}
复制代码
工具类
XmlUtil
public class XmlUtil {
/**
* 扩展 xstream,获取CDATA内容
*/
public static XStream xstream = new XStream(new XppDriver() {
@Override
public HierarchicalStreamWriter createWriter(Writer out) {
return new PrettyPrintWriter(out) {
// 对所有xml节点的转换都增加CDATA标记
boolean cdata = true;
@Override
public void startNode(String name, @SuppressWarnings("rawtypes") Class clazz) {
super.startNode(name, clazz);
}
@Override
protected void writeText(QuickWriter writer, String text) {
if (cdata) {
writer.write("<![CDATA[");
writer.write(text);
writer.write("]]>");
} else {
writer.write(text);
}
}
};
}
});
/**
* xml解析为map
*
* @param root 根节点
* @param map 返回的map
*/
@SuppressWarnings("unchecked")
public static void parserXml(Element root, Map<String, String> map) {
// 得到根元素的所有子节点
List<Element> elementList = root.elements();
// 判断有没有子元素列表
if (elementList.size() == 0) {
map.put(root.getName(), root.getText());
} else {
// 遍历
for (Element e : elementList) {
parserXml(e, map);
}
}
}
}
复制代码
WeChatUtil
@Component
public class WeChatUtil {
@Value("${wechat.appID}")
private String appID;
@Value("${wechat.appsecret}")
private String appsecret;
@Autowired
private RedisCache redisCache;
private static final String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s";
private static final String TICKET_URL = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=%s";
private static final String USER_INFO_URL = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=%s&openid=%s&lang=zh_CN";
/**
* 获取 access_token
*/
public String getAccessToken() {
String accessTokenUrl = String.format(ACCESS_TOKEN_URL, appID, appsecret);
String s = HttpUtil.get(accessTokenUrl);
JSONObject result = JSONUtil.parseObj(s);
String access_token = result.getStr("access_token");
Integer expires_in = result.getInt("expires_in");
redisCache.setCacheObject(Constants.ACCESS_TOKEN, access_token, expires_in, TimeUnit.SECONDS);
return access_token;
}
/**
* 获取 创建二维码的ticket
* @param scene_str
*/
public JSONObject getTicket(String scene_str) throws Exception {
String ticketUrl = String.format(TICKET_URL, getReidAccessToken());
String body = "{" +
""expire_seconds": 1200," +
""action_name": "QR_STR_SCENE"," +
""action_info": {" +
""scene": {" +
""scene_str": "" + scene_str + """ +
"}" +
"}" +
"}";
String ticketJson = HttpUtil.post(ticketUrl, body);
return JSONUtil.parseObj(ticketJson);
}
/**
* 根据 openid 和 access_token 获取用户信息
*/
public String getUserInfo(String openid) throws Exception {
String userInfoUrl = String.format(USER_INFO_URL, getReidAccessToken(), openid);
return HttpUtil.get(userInfoUrl);
}
private String getReidAccessToken() throws Exception {
String access_token = redisCache.getCacheObject(Constants.ACCESS_TOKEN);
if (StringUtils.isBlank(access_token)) {
// access_token 过期了
// 刷新 access_token
access_token = getAccessToken();
}
return access_token;
}
}
复制代码
近期评论