好久又没有更新文章了,最近又在忙项目,开发中遇到了一个需求,就是用户扫码二维码之后没有关注公众号,要先关注之后才能登陆,如果已经关注了就直接跳登陆成功,其实这个功能应用场景还是蛮多的,包括腾讯自己的应用也使用了这样的功能。下面开始分享给需要的伙伴们,当然,这我自己的摸索实现的,可能还有更好的方案,如果小伙伴们有更好的方法,欢迎分享交流,话不多说开干!
一.思路分析:
1.使用公众号接口生成二维码。
2.系统接收微信推送过来的事件(关注/扫码)。
3.用户点击关注或者扫码二维码后台都会接收到推送通知,然后根据通知实现自己的业务就可以了。
二.开发环境:
1.idea
2.redis
3.springboot2.x
三.基础环境搭建:
1.新建一个springboot项目
2.添加一个控制器,并运行,保证项目正常。
四.业务开发:
1.让系统和微信系统对接上,能够接收微信推送事件,在控制器上面新增两个方法
/***
* 微信服务器触发get请求用于检测签名
* @return
*/
@GetMapping("/handleWxCheckSignature")
@ResponseBody
public String handleWxCheckSignature(HttpServletRequest request){
//todo 严格来说这里需要做签名验证,我这里为了方便就不做了
String echostr = request.getParameter("echostr");
return echostr;
}
/**
* 接收微信推送事件
* @param request
* @return
*/
@PostMapping("/handleWxCheckSignature")
@ResponseBody
public String handleWxEvent(HttpServletRequest request){
try {
InputStream inputStream = request.getInputStream();
Map<String, Object> map = XmlUtil.parseXML(inputStream);
String userOpenId = (String) map.get("FromUserName");
String event = (String) map.get("Event");
if("subscribe".equals(event)){
logger.info("用户关注:{}",userOpenId);
}else if("SCAN".equals(event)){
logger.info("用户扫码:{}",userOpenId);
}
logger.info("接收参数:{}",map);
} catch (IOException e) {
e.printStackTrace();
}
return "success";
}
2.在公众号后台设置URL和token,为了方面演示,我使用了公众号测试账号,同时使用了natapp做了外网映射,保存验证成功即可:
3.用手机扫一下自己的公众号测试专用二维码:
点击关注看看控制台是否有接收到通知,接收到即可继续往下:
2.注入RestTemplate用于http请求 :
/**
* 注入restTemplate用于http请求
*/
@Configuration
public class RestTemplateConfig {
@Resource
private RestTemplateBuilder templateBuilder;
@Bean
public RestTemplate restTemplate(){
return templateBuilder.build();
}
}
3.新增一个微信服务接口,用于调用微信公众号接口
public interface WxService {
//获取token
String getAccessToken();
//获取生成二维码参数
Map<String,Object> getQrCode();
}
4.实现接口调用, 不清楚请看微信公众号开发文档。
yml中配置公众号参数
spring:
# 模版配置
thymeleaf:
cache: false
#redis 配置
redis:
host: 127.0.0.1
database: 1
password: 123456
#公众号参数配置
wx:
gz:
appid: 你的appid
secret: 你的appsecret
接口服务实现:
@Service
public class WxServiceImpl implements WxService {
Logger logger = LoggerFactory.getLogger(WxServiceImpl.class);
@Value("${wx.gz.appid:''}")
private String appid;
@Value("${wx.gz.secret:''}")
private String secret;
@Resource
private RestTemplate restTemplate;
@Resource
private RedisCacheManager redisCacheManager;
@Override
public String getAccessToken() {
String key = "wx_access_token";
//从redis缓存中获取token
if(redisCacheManager.hasKey(key)){
return (String) redisCacheManager.get(key);
}
String url = String.format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s",appid,secret);
ResponseEntity<String> result = restTemplate.getForEntity(url, String.class);
if(result.getStatusCode()== HttpStatus.OK){
JSONObject jsonObject = JSON.parseObject(result.getBody());
String access_token = jsonObject.getString("access_token");
Long expires_in = jsonObject.getLong("expires_in");
//缓存toekn到redis
redisCacheManager.set(key,access_token,expires_in);
return access_token;
}
return null;
}
@Override
public Map<String, Object> getQrCode() {
//获取临时二维码
String url = String.format("https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=%s",getAccessToken());
ResponseEntity<String> result = restTemplate.postForEntity(url, "{\"expire_seconds\": 604800, \"action_name\": \"QR_STR_SCENE\", \"action_info\": {\"scene\": {\"scene_str\": \"test\"}}}", String.class);
logger.info("二维码:{}",result.getBody());
JSONObject jsonObject = JSON.parseObject(result.getBody());
Map<String,Object> map=new HashMap<>();
map.put("ticket",jsonObject.getString("ticket"));
map.put("url",jsonObject.getString("url"));
return map;
}
}
5.ok,接下来就是在控制器中新增一个首页和一个登陆和登陆成功方法
@GetMapping("")
public String index(){
return "index";
}
@GetMapping("/login")
public String login(){
return "login";
}
@GetMapping("/success")
public String loginSuccess(){
return "登陆成功";
}
简单html页面
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>微信公众号扫码关注登陆实现</title>
</head>
<body>
<a href="/login">扫码登陆</a>
</body>
</html>
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登陆</title>
</head>
<body>
<div style="width: 200px;margin: 50px auto">
<div id="qrcode"></div>
</div>
<script type='text/javascript' src='http://cdn.staticfile.org/jquery/2.1.1/jquery.min.js'></script>
<script type="text/javascript" src="http://cdn.staticfile.org/jquery.qrcode/1.0/jquery.qrcode.min.js"></script>
<script>
$(function () {
//获取二维码参数
$.get('/getQrCode',function (res) {
//生成二维码
$('#qrcode').qrcode(res.url);
//轮训获取用户扫码登陆状态
var task = setInterval(function () {
$.post('/checkLogin',{ticket:res.ticket},function (ok) {
//扫码成功登陆成功
if(ok){
clearInterval(task)
location.href='/success'
}
})
},2000)
})
})
</script>
</body>
</html>
用到的maven依赖
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.68</version>
</dependency>
自己用dom4j简单解析xml
public class XmlUtil {
/**
* 简单解析xml
* @param in
* @return
*/
public static Map<String,Object> parseXML(InputStream in){
Map<String,Object> map=new HashMap<>();
try {
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(in);
Element root = document.getRootElement();
Iterator iterator = root.elementIterator();
while (iterator.hasNext()){
Element element = (Element) iterator.next();
map.put(element.getName(),element.getStringValue());
}
} catch (DocumentException e) {
e.printStackTrace();
}
return map;
}
}
自己简单包装的redis缓存工具
@Component
public class RedisCacheManager {
@Resource
private RedisTemplate<String,Object> redisTemplate;
public void set(String key,Object value,long expire){
redisTemplate.opsForValue().set(key,value,expire, TimeUnit.SECONDS);
}
public Object get(String key){
return redisTemplate.opsForValue().get(key);
}
public Boolean delete(String key){
return redisTemplate.delete(key);
}
public boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}
}
改造controller
@Controller
public class HomeController {
Logger logger = LoggerFactory.getLogger(HomeController.class);
@Resource
private WxService wxService;
@Resource
private RedisCacheManager redisCacheManager;
/**
* 首页
* @return
*/
@GetMapping("/")
public String index(){
return "index";
}
/**
* 登陆页面
* @return
*/
@GetMapping("/login")
public String login(){
return "login";
}
/**
* 用于检测扫码和关注状态
* @return
*/
@PostMapping("/checkLogin")
@ResponseBody
public Object checkLogin(String ticket){
//如果redis中有ticket凭证则说明用户已扫码说明登陆成功
if(redisCacheManager.hasKey(ticket)){
//扫码通过则删除
redisCacheManager.delete(ticket);
return true;
}
return false;
}
/**
* 获取二维码参数
* @return
*/
@GetMapping("/getQrCode")
@ResponseBody
public Object getQrCode(){
return wxService.getQrCode();
}
/**
* 登陆成功跳转
* @return
*/
@GetMapping("/success")
@ResponseBody
public String loginSuccess(){
return "登陆成功";
}
/***
* 微信服务器触发get请求用于检测签名
* @return
*/
@GetMapping("/handleWxCheckSignature")
@ResponseBody
public String handleWxCheckSignature(HttpServletRequest request){
//todo 严格来说这里需要做签名验证,我这里为了方便就不做了
String echostr = request.getParameter("echostr");
return echostr;
}
/**
* 接收微信推送事件
* @param request
* @return
*/
@PostMapping("/handleWxCheckSignature")
@ResponseBody
public String handleWxEvent(HttpServletRequest request){
try {
InputStream inputStream = request.getInputStream();
Map<String, Object> map = XmlUtil.parseXML(inputStream);
String userOpenId = (String) map.get("FromUserName");
String event = (String) map.get("Event");
if("subscribe".equals(event)){
// TODO:获取openid判断用户是否存在,不存在则获取新增用户,自己的业务
//自己生成的二维码不管是关注还是扫码都能取到ticket凭证,这里我使用Ticket作为每次二维码的唯一标识
String ticket = (String) map.get("Ticket");
redisCacheManager.set(ticket,"",10*60);
logger.info("用户关注:{}",userOpenId);
}else if("SCAN".equals(event)){
//自己生成的二维码不管是关注还是扫码都能取到ticket凭证
String ticket = (String) map.get("Ticket");
redisCacheManager.set(ticket,"",10*60);
logger.info("用户扫码:{}",userOpenId);
}
logger.info("接收参数:{}",map);
} catch (IOException e) {
e.printStackTrace();
}
return "success";
}
}
说明: 通过微信获取二维码参数时里面有ticket和用户扫码之后都会携带这个参数,所以我就将就使用这个凭证作为用户是否扫码的判断了。即,当用户扫我们生成的二维码,收到关注或者扫码事件时,说明用户已扫码或关注,此时将这个二维码的ticket存到redis中, 与前端传过来的ticket对比,如果一致则说明扫码成功,跳转到登陆成功页面!
6.测试效果:
扫码
扫码登陆成功
因为这个测试不好演示,我就简单的模拟结果了。
总结:公众号扫码登陆就简简单单的完成了,当然这只是一个模拟流程,具体的业务
还需要自己实现。这也是我在做项目中遇到的一个实际需求,这里分享出来,如果有不对的地方欢迎大家指正,如果喜欢我的文章,记得关注不迷路,后续会分享更多干货😊!