微信公众号开发之扫码关注并登陆实战
好久又没有更新文章了,最近又在忙项目,开发中遇到了一个需求,就是用户扫码二维码之后没有关注公众号,要先关注之后才能登陆,如果已经关注了就直接跳登陆成功,其实这个功能应用场景还是蛮多的,包括腾讯自己的应用也使用了这样的功能。下面开始分享给需要的伙伴们,当然,这我自己的摸索实现的,可能还有更好的方案,如果小伙伴们有更好的方法,欢迎分享交流,话不多说开干!
一.思路分析:
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.测试效果:
扫码
扫码登陆成功
因为这个测试不好演示,我就简单的模拟结果了。
总结:公众号扫码登陆就简简单单的完成了,当然这只是一个模拟流程,具体的业务
还需要自己实现。这也是我在做项目中遇到的一个实际需求,这里分享出来,如果有不对的地方欢迎大家指正,如果喜欢我的文章,记得关注不迷路,后续会分享更多干货😊!