java 掃描微信公衆號二維碼,關注並登陸邏輯

場景:戶掃描微信公衆號的二維碼,關注後自動登陸網站,若已關注則直接登陸。javascript

邏輯:html

  1. 系統生成帶參數的臨時二維碼:參數 scene_str 自定義爲惟一值(能夠是uuid),臨時二維碼的生成方式參照官方接口的開發文檔
  2. 用戶使用微信掃描該二維碼,關注後微信服務器會將關注事件的響應數據返回給咱們的應用服務器:微信經過咱們在公衆號配置的「服務器地址(URL)」,將響應數據返回給咱們的應用服務器,響應數據包括 參數 scene_str 和 openid
  3. 應用服務器將接收到的openid再次向微信服務器發起請求,獲取該用戶的信息(暱稱、頭像、地域、unionid(若綁定了微信開放平臺,則有此參數))
  4. 將返回的信息存儲到數據庫,用於登陸:特別是 scene_str 也要記錄,由於須要根據這個惟一值來判斷當前是誰在掃描關注並登陸

準備工做:登陸微信公衆號平臺,在基本配置下獲取 AppID,AppSecret,「服務器地址(URL)」即「回調函數」,令牌Token。以下圖:前端

  

 

 本人用的開發框架是 springboot,代碼以下:java

  1. 後臺代碼:
    • HttpClientKit.java:封裝https請求,調用微信接口
      package com.ht.website.utils; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.URL; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import com.alibaba.fastjson.JSONObject; public class HttpClientKit { /** * https請求信任管理器 * @author Timely-03 */
          private static class TrustAnyTrustManager implements X509TrustManager { public X509Certificate[] getAcceptedIssuers() { return null; } public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } } /** * 發送https請求 */
          private static JSONObject https(String requestUrl, String requestMethod, String submitData) { HttpsURLConnection httpsURLCon = null; try { // 設置https訪問模式,採用SSL加密
                  TrustManager[] tm = { new TrustAnyTrustManager() }; // 建立SSLContext對象,並使用咱們指定的信任管理器初始化
                  SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE"); sslContext.init(null, tm, new java.security.SecureRandom()); // 從sslContext獲取SSLSocketFactory
                  SSLSocketFactory ssf = sslContext.getSocketFactory(); URL url = new URL(requestUrl); httpsURLCon = (HttpsURLConnection) url.openConnection(); httpsURLCon.setSSLSocketFactory(ssf); httpsURLCon.setDoInput(true); httpsURLCon.setDoOutput(true); httpsURLCon.setUseCaches(false); httpsURLCon.setRequestMethod(requestMethod);// 設置請求方式get;post
                  
                  if ("GET".equalsIgnoreCase(requestMethod)) { httpsURLCon.connect(); } // 當須要有數據提交給微信接口時
                  if (submitData != null) { OutputStream outputStream = httpsURLCon.getOutputStream(); outputStream.write(submitData.getBytes(WSConst.ENCODE_UTF_8)); outputStream.close(); } // 從輸入流讀取返回內容
                  InputStream inputStream = httpsURLCon.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream, WSConst.ENCODE_UTF_8); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); String str = null; StringBuffer buffer = new StringBuffer(); while ((str = bufferedReader.readLine()) != null) { buffer.append(str); } // 釋放資源
       bufferedReader.close(); inputStreamReader.close(); inputStream.close(); inputStream = null; httpsURLCon.disconnect(); return JSONObject.parseObject(buffer.toString()); } catch (Exception e) { throw new RuntimeException(e); } finally { if (httpsURLCon != null) { httpsURLCon.disconnect(); } } } /** * 發送https請求,請求方法:POST * @param requestUrl 請求地址,好比:https://api.weixin.qq.com/cgi-bin/template/api_set_industry?access_token= * @param submitData 提交參數 * @return 返回請求結果字符串 */
          public static JSONObject httpsPost(String requestUrl, String submitData) { return https(requestUrl, "POST", submitData); } /** * 發送https請求,請求方法:GET * @param requestUrl 請求地址,好比:https://api.weixin.qq.com/cgi-bin/template/api_set_industry?access_token= * @param submitData 提交參數 * @return 返回請求結果字符串 */
          public static JSONObject httpsGet(String requestUrl, String submitData) { return https(requestUrl, "GET", submitData); } }
      View Code
    • application.properties:配置微信appid,appSecret
      server.port=8088 ##############數據鏈接開始 #mysql spring.datasource.url=jdbc:mysql://10.10.10.10:3306/database_wc?useUnicode=true&characterEncoding=utf8 spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.username=root spring.datasource.password=123456 spring.datasource.dbcp2.validation-query=SELECT 1 spring.datasource.dbcp2.time-between-eviction-runs-millis=3600000 ##############數據庫鏈接結束 ##############前端模板開始 spring.thymeleaf.suffix=.html spring.thymeleaf.cache=false spring.thymeleaf.prefix=/ ##############前端模板結束 ##############日誌配置開始 logging.level.root=INFO logging.level.org.springframework.web=INFO ##############日誌配置結束 ##############微信配置參數開始 proconfig.wechatAppId = wx21abcdbafe0aaac proconfig.wechatAppSecret = e472b01d2fdsadfsafdsafdsafad ##############微信配置參數結束
      View Code
    • WSConfig.java:獲取配置文件配置信息
      package com.ht.website.utils; import org.springframework.boot.context.properties.ConfigurationProperties; /** * 配置文件類,獲取對應配置文件中以 proconfig 開頭的屬性值 * @author Timely-03 */ @ConfigurationProperties(prefix = "proconfig") public class WSConfig { //微信AppId
          private String wechatAppId; //微信AppSecret
          private String wechatAppSecret; public String getWechatAppId() { return wechatAppId; } public void setWechatAppId(String wechatAppId) { this.wechatAppId = wechatAppId; } public String getWechatAppSecret() { return wechatAppSecret; } public void setWechatAppSecret(String wechatAppSecret) { this.wechatAppSecret = wechatAppSecret; } }
      View Code
    • WeChatAccessToken.java:定時獲取微信access_token的類,這裏用的Springboot的cache 和 定時器來管理access_token
      package com.ht.website.wechat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import com.alibaba.fastjson.JSONObject; import com.ht.website.utils.HttpClientKit; import com.ht.website.utils.WSConfig; /** * 微信 access_token 生成並緩存 * 1.調用微信接口,獲取access_token * 2.存入緩存,緩存名稱爲「accesstoken」 * 3.定時獲取access_token,更新「accesstoken」緩存 * @author Timely-03 * */ @Service public class WeChatAccessToken { private static final Logger _log = LoggerFactory.getLogger(WeChatAccessToken.class); // 獲取access_token url
          private static final String _URL_GET_ACCESS_TOKEN = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET"; // 記錄更新
          private int _fixedRateCount = 0; // 屬性配置 ProConfig
       @Autowired private WSConfig _wsConfig; /** * 緩存 token * @return token信息 */ @Cacheable(cacheNames= "accesstokencache") public String getAccessToken() { return getAccessTokenStr(); } /** * 更新緩存token,定時更新(100分鐘更新一次) * @return token信息 */ @CachePut(cacheNames = "accesstokencache") @Scheduled(fixedRate = 100*60*1000) public String updateAccessToken() { return getAccessTokenStr(); } /** * 獲取access_token字符串 * @return 獲取{"access_token":"ACCESS_TOKEN","expires_in":7200}中的access_token */
          private String getAccessTokenStr() { // 調用接口,獲取access_token
              JSONObject accessTokenInfo = getAccessTokenInfo(); if (accessTokenInfo == null) { return null; } return accessTokenInfo.containsKey("access_token") ? accessTokenInfo.getString("access_token") : null; } /** * 調用微信接口,獲取access_token * @return 正常狀況下返回:{"access_token":"ACCESS_TOKEN","expires_in":7200}<br/>錯誤時返回錯誤碼:{"errcode":40013,"errmsg":"invalid appid"} */
          private JSONObject getAccessTokenInfo() { // 微信appId
              String appId = _wsConfig.getWechatAppId(); // 微信 appSecret
              String appSecret = _wsConfig.getWechatAppSecret(); if (appId.isEmpty() || appSecret.isEmpty()) { return null; } _fixedRateCount++; _log.info("fixedRateCount = " + _fixedRateCount); //調用微信接口url,返回access_token信息
              JSONObject responseJson = HttpClientKit.httpsGet(_URL_GET_ACCESS_TOKEN.replace("APPID", appId).replace("APPSECRET", appSecret), null); if (responseJson.containsKey("access_token") == false) { _log.info("定時刷新access_token失敗,微信返回的信息是" + responseJson.toJSONString()); } return responseJson; } }
      View Code
    • WeChatService.java:調用微信接口服務類
      package com.ht.website.wechat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.alibaba.fastjson.JSONObject; import com.ht.website.utils.HttpClientKit; import com.ht.website.utils.WSConst; @Service public class WeChatService { private static final Logger _log = LoggerFactory.getLogger(WeChatService.class); // 生成帶參數的二維碼
          public static final String _URL_CREATE_QRCODE = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token="; // 獲取帶參數的二維碼
          public static final String _URL_GET_QRCODE    = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket="; // 獲取關注用戶信息
          public static final String _URL_GET_USERINFO    = "https://api.weixin.qq.com/cgi-bin/user/info?access_token="; // 二維碼業務類型:【QRLOGIN:登陸二維碼】
          public static final String _QR_TYPE_LOGIN = "WSLOGIN-"; public static final String _QR_TYPE_LOGIN_SUPPLIER = "WSLOGIN-SUPPLIER-"; public static final String _QR_TYPE_LOGIN_CUSTOMER = "WSLOGIN-CUSTOMER-"; @Autowired private WeChatAccessToken _wechatAccessToken; /** * 獲取帶參數的 臨時二維碼(登陸用) * 若是返回access_token過時,則從新調用一次獲取access_token的方法,再用新的access_token去生成二維碼 * @return 
           */
          public JSONObject getQRCodeInfoLogin(String scene_str) { // 生成臨時二維碼
              JSONObject qrcodeInfo = getQrCodeInfo(scene_str); // 生成二維碼返回消息:若是access_token過時,則執行一次獲取access_token,而且從新生成臨時二維碼
              if (WSConst.WECHAT_ERROR_CODE_40001.equals(qrcodeInfo.getString("errcode"))) { _wechatAccessToken.updateAccessToken(); // 生成臨時二維碼 
                  qrcodeInfo = getQrCodeInfo(scene_str); } return qrcodeInfo; } /** * 獲取帶參數的 臨時二維碼 * @return
           */
          private JSONObject getQrCodeInfo(String scene_str) { _log.info("access_token = " + _wechatAccessToken.getAccessToken()); // access_token驗證
              String accessToken = _wechatAccessToken.getAccessToken(); // 請求參數: scene(場景字符串)
              JSONObject scene = new JSONObject(); scene.put("scene_str", scene_str);    // 長度:8 + 36 = 44 // 請求參數:action_info
              JSONObject actioninfo = new JSONObject(); actioninfo.put("scene", scene); // 獲取二維碼提交的參數
              JSONObject param = new JSONObject(); param.put("expire_seconds", 120);            // 二維碼過時時間:120秒
              param.put("action_name", "QR_STR_SCENE");    // 字符串形式的二維碼參數 
              param.put("action_info", actioninfo); return HttpClientKit.httpsPost(_URL_CREATE_QRCODE + accessToken, param.toJSONString()); } /** * 經過openid,獲取關注用戶信息 * @param openid * @return * @throws Exception */
          public JSONObject getUserInfoByOpenId(String openid) throws Exception { return HttpClientKit.httpsGet(_URL_GET_USERINFO + _wechatAccessToken.getAccessToken(), null); } }
      View Code
    • WeChatController.java:生成臨時二維碼,將二維碼的 URL 返回到前端;接收微信的關注事件;登陸驗證
      package com.ht.website.controller; import java.io.InputStream; import java.util.List; import java.util.UUID; import javax.servlet.http.HttpServletRequest; import org.dom4j.Element; import org.dom4j.io.SAXReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import com.alibaba.fastjson.JSONObject; import com.ht.website.entity.WeChatUser; import com.ht.website.service.WeChatUserService; import com.ht.website.wechat.WeChatService; /** * 微信相關控制器 * @author Timely-03 */ @Controller @RequestMapping(value="/wechat") public class WeChatController { private static final Logger _log = LoggerFactory.getLogger(WeChatController.class); //微信接口服務類
       @Autowired private WeChatService _weChatService; //微信用戶服務類
       @Autowired private WeChatUserService _weChatUserService; /** * 跳到login頁面,並生成登陸二維碼的URL */ @RequestMapping(value = "/login",method = RequestMethod.GET) public String login(Model model, String eventKey) { // 帶參數二維碼的 scene_str:QRLOGIN-{UUID},此字符串會看成關注事件的eventKey返回,寫入到數據庫表的eventKey中
              String scene_str = WeChatService._QR_TYPE_LOGIN + UUID.randomUUID(); // 獲取登陸二維碼
              JSONObject qrcodeInfo = _weChatService.getQRCodeInfoLogin(scene_str); if (qrcodeInfo.containsKey("ticket")) { model.addAttribute("qrcodeurl", WeChatService._URL_GET_QRCODE + qrcodeInfo.getString("ticket")); } model.addAttribute("eventKey", scene_str); _log.info("獲取登陸二維碼:" + qrcodeInfo.toJSONString()); return "login"; } /** * 回調函數,配置在微信公衆號的服務器地址URL中,接收微信的推送事件 * @param httpServletRequest * @throws Exception */ @RequestMapping(value = "/channel") public void channel(HttpServletRequest httpServletRequest) throws Exception { JSONObject callBackInfo = xmlToJson(httpServletRequest); //獲取回調信息 //下面是返回的xml //<xml><ToUserName><![CDATA[gh_f6b4da984c87]]></ToUserName> //微信公衆號的微信號 //<FromUserName><![CDATA[oJxRO1Y2NgWJ9gMDyE3LwAYUNdAs]]></FromUserName> //openid用於獲取用戶信息,作登陸使用 //<CreateTime>20190531</CreateTime> //回調時間 //<MsgType><![CDATA[event]]></MsgType> //<Event><![CDATA[SCAN]]></Event> //<EventKey><![CDATA[lrfun.com.UxJkWC1531967386903]]></EventKey> //上面自定義的參數(scene_str) //<Ticket><![CDATA[gQF57zwAAAAAAAAAAS5odHRwOi8vd2VpeGluLnFxLmNvbS9xLzAyY2ljbjB3RGtkZWwxbExLY3hyMVMAAgTvM0NbAwSAOgkA]]></Ticket> //換取二維碼的ticket //</xml>
              if (callBackInfo != null && StringUtils.isEmpty(callBackInfo.getString("FromUserName")) == false) { // 經過openid獲取用戶信息
                  JSONObject wechatUserInfo = _weChatService.getUserInfoByOpenId(callBackInfo.getString("FromUserName")); // 將數據寫入到數據庫中,前面自定義的參數(scene_str)也需記錄到數據庫中,後面用於檢測匹配登陸 // INSERT INTO wechat_user_info......(數據庫操做)
       } } /** * 是否登陸驗證,若是在表中找到了當前的eventKey,則表示是當前用戶的關注登陸動做: * 1. eventKey在用戶關注後,微信將關注的事件,經過配置的「服務器地址URL」推送出來,返回到channel方法中 * 2. channel方法將eventKey寫入到用戶表中 * 3. 前端用定時器,以1秒的頻率不停的調用checkLogin請求,經過eventKey到用戶表中檢測是否存在這個eventKey的數據 */ @RequestMapping(value = "/checkLogin") @ResponseBody public JSONObject checkLogin(String eventKey) { JSONObject retObj = new JSONObject(); _log.info("eventKey = " + eventKey); // 取用戶信息
              List<WeChatUser> wechatUserList = _weChatUserService.getUserByEventKey(eventKey); if (wechatUserList.size() > 0) { retObj.put("errCode",  "0"); retObj.put("errMsg",   "登陸成功!"); retObj.put("eventKey", eventKey); } else { retObj.put("errCode", "-1"); retObj.put("errMsg",  "登陸失敗"); } return retObj; } // xml轉爲map
          private JSONObject xmlToJson(HttpServletRequest httpServletRequest) { JSONObject info = new JSONObject(); try { InputStream inputStream = httpServletRequest.getInputStream(); SAXReader reader = new SAXReader(); // 讀取輸入流
                  org.dom4j.Document document = reader.read(inputStream); Element root = document.getRootElement(); // 獲得xml根元素
                  List<Element> elementList = root.elements(); // 獲得根元素的全部子節點 // 遍歷全部子節點
                  for (Element e : elementList) { info.put(e.getName(), e.getText()); } // 釋放資源
       inputStream.close(); inputStream = null; return info; } catch (Exception e) { e.getMessage(); } return null; } }
      View Code
  2. 前端代碼:
    • login.html:顯示登陸二維碼,定時請求後臺
      <!DOCTYPE html>
      <!-- saved from url=(0058)http://www.17sucai.com/preview/1/2017-11-05/nav/index.html -->
      <html lang="zh">
      <head>
          <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
          <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>掃描登陸</title>
      
      </head>
      <body>
          <!-- 內容 -->
          <div id="content" class="bg-color-gray">
              <div class="login-container">
                  <div class="row padding-20">
                      <div class="col-sm-12">
                          <div class="padding-20">
                              <span id="loginMsg" style="font-size: 25px;">掃描二維碼註冊/登陸</span>
                          </div>
                          <img th:src="${qrcodeurl}" /> <input type="hidden" id="eventKey" name="eventKey" th:value="${eventKey}">
                          <div class="padding-20">
                              <i class="glyphicon glyphicon-qrcode"></i>&nbsp;微信掃描二維碼 </div>
                      </div>
                  </div>
              </div>
          </div>
      
          <script type="text/javascript">
                 var wechatCheckLoginFunTimer = null; $(document).ready(function () { wechatCheckLoginFunTimer = setInterval("wechatCheckLogin()", 1000); }); function wechatCheckLogin(){ $.post("/wechat/checkLogin", {eventKey:$("input#eventKey").val()}, function(data){ if(data.errCode == "0"){ clearInterval(wechatCheckLoginFunTimer); $("#loginMsg").text(data.errMsg); //停頓1秒
       setTimeout(function(){ window.location.href = "/user/loginSuccess?eventKey=" + data.eventKey; },1000) } else { $("input#eventKey").innerHTML = data.errMsg; } }, "JSON"); } </script>
      
      </body>
      </html>
      View Code

      掃描登陸邏輯到此完成!mysql

相關文章
相關標籤/搜索