好久又沒有更新文章了,最近又在忙項目,開發中遇到了一個需求,就是用戶掃碼二維碼之后沒有關注公眾號,要先關注之后才能登陸,如果已經關注了就直接跳登陸成功,其實這個功能應用場景還是蠻多的,包括騰訊自己的應用也使用了這樣的功能。下面開始分享給需要的伙伴們,當然,這我自己的摸索實現的,可能還有更好的方案,如果小伙伴們有更好的方法,歡迎分享交流,話不多說開干!
一.思路分析:
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.測試效果:
掃碼

掃碼登陸成功
因為這個測試不好演示,我就簡單的模擬結果了。
總結:公眾號掃碼登陸就簡簡單單的完成了,當然這只是一個模擬流程,具體的業務
還需要自己實現。這也是我在做項目中遇到的一個實際需求,這里分享出來,如果有不對的地方歡迎大家指正,如果喜歡我的文章,記得關注不迷路,后續會分享更多干貨😊!