一圖講清楚公眾號掃碼關注繫結手機號自動登入

2023-06-28 06:00:34

日常開發中,相信不管做 C 端還是 B 端業務的同學都會遇到微信相關的業務,比如微信登入、微信支付、公眾號掃碼關注等場景。

最近博主在做公眾號掃碼關注自動登入這一塊的業務,因此總結繪製了一張公眾號掃碼關注繫結手機號自動登入流程圖分享給大家。

推薦博主開源的 H5 商城專案waynboot-mall,這是一套全部開源的微商城專案,包含三個專案:運營後臺、H5 商城前臺和伺服器端介面。實現了商城所需的首頁展示、商品分類、商品詳情、商品 sku、分詞搜尋、購物車、結算下單、支付寶/微信支付、收單評論以及完善的後臺管理等一系列功能。 技術上基於最新得 Springboot3.0、jdk17,整合了 MySql、Redis、RabbitMQ、ElasticSearch 等常用中介軟體。分模組設計、簡潔易維護,歡迎大家點個 star、關注博主。

github 地址:https://github.com/wayn111/waynboot-mall

1. 準備工作

想要達到使用者掃描二維條碼開啟微信公眾號主頁並且關注後自動登入,目前只能通過接入公眾號的伺服器設定來完成,下面介紹下前置準備。

  1. 註冊微信公眾平臺服務號(注意:不要註冊成訂閱號!因為生成帶引數的二維條碼這個介面只有服務號能呼叫)

  2. 開通微信認證(注意:微信認證每年需要交 300 塊錢),如下圖展示即可認為前兩步設定已完成。

  3. 進入公眾號後管【設定與開發】-【基本設定】,點選開發者密碼(AppSecret)啟用,拿到 appId、secret

  4. 部署伺服器端程式碼(需公網,因為第五步的伺服器地址需要公網才能驗證通過)

    • 準備公網伺服器一臺。
    • 下載 weixin-java-mp-demo 專案程式碼,地址:https://github.com/binarywang/weixin-java-mp-demo。
    • 按照 readme 檔案說明,修改 yml 檔案的 appId、secret 為第二步中的 appId、secrettoken、aesKey 等第五步設定完替換,如下是 weixin-java-mp-demo 專案的使用步驟。
      weixin-java-mp-demo使用步驟
    • 將 weixin-java-mp-demo 部署到公網伺服器上就 ok 了。
  5. 進入公眾號後管【設定與開發】-【基本設定】,點選伺服器設定啟用後,填寫相關的伺服器地址、令牌、訊息加解密金鑰、訊息加解密方式,點選提交等待伺服器地址驗證通過後即完成了所有前置準備工作。

ps: 公眾號接入伺服器設定後,以前設定的自動回覆和自定義選單就失效了,後續自定義選單隻能通過呼叫公眾號的api介面來進行設定,自動回覆則需要在 weixin-java-mp-demo 專案的事件接收程式碼中進行回覆。

2. 掃碼關注自動流程

現在我們基於公眾號內提供的 api 來完成掃碼關注自動登入的操作,流程如下,

2.1 使用者端流程

  1. 使用者開啟網頁、TV 端時請求伺服器端介面獲取公眾號二維條碼以及使用者標識。
  2. 根據使用者標識輪詢使用者掃碼狀態介面,獲取使用者是否註冊資訊。
  3. 使用者掃碼後如果是已註冊就根據輪詢介面返回的 token 進行登入。
  4. 使用者掃碼後如果是未註冊就彈出繫結手機號彈窗,當用戶繫結成功根據繫結介面返回的 token 進行登入。

2.2 伺服器端流程

伺服器端需要提供三個介面以及監聽掃碼事件來獲取使用者 openId 並以此判斷該掃碼使用者是否註冊。

  1. 生成帶引數的二維條碼以及使用者標識介面,生成帶引數的二維條碼主要根據公眾號提供的介面檔案中生成帶引數的二維條碼這個介面,以此當用戶掃碼後點選關注,伺服器端便可以接收到使用者的關注事件。如果是已關注使用者掃碼,伺服器端就會接收到掃碼事件,下面是生成引數二維條碼後的掃碼事件相關說明。

  2. 使用者掃碼狀態輪詢介面,輪詢介面需要返回三個基本狀態。狀態一繼續輪詢,狀態二未註冊提示繫結手機,狀態三已註冊就返回 token 進行登入,是否註冊的判斷需要在接收到關注掃碼事件時根據 openId 去資料庫中查詢使用者的註冊狀態。

  3. 使用者掃碼關注後,伺服器端接收到相關事件,根據 openId 判斷使用者是否已註冊,已註冊就將輪詢介面設定為已註冊,並生成使用者token。未註冊就將輪詢介面設定為未註冊,提示繫結手機。

  4. 繫結手機號介面,到了繫結手機號介面就相對獨立一些,不在依賴公眾號相關介面以及事件通知,繫結成功返回使用者登入 token 即可。

2.3 使用者掃碼流程

使用者掃碼流程只有使用者掃碼的動作。

  1. 掃碼後未關注時,只有使用者點選關注按鈕,伺服器端就會收到關注事件。
  2. 掃碼後已關注,伺服器端就會收到掃碼事件。

3. 程式碼範例

3.1 生成帶引數二維條碼

@PostMapping("userQrcodeCreate")
private Result<WeixinQrcodeResponseVO> userQrcodeCreate(@RequestBody @Validated WeixinMPRequestVO req) {
    log.info("========================userQrcodeCreate begin, req:{}========================", req);
    WeixinQrcodeResponseVO weixinQrcodeResponseVO = new WeixinQrcodeResponseVO();
    try {
        if (!this.wxMpService.switchover(properties.getCurAppid())) {
            throw new IllegalArgumentException(String.format("未找到對應appid=[%s]的設定,請核實!", properties.getCurAppid()));
        }
        String sceneType = req.getSceneType();
        String uuid = IdUtil.fastSimpleUUID();

        // 臨時ticket
        WxMpQrCodeTicket ticket = wxMpService.getQrcodeService().qrCodeCreateTmpTicket(sceneType + WEIXIN_MP_SCENCE_SPLIT + uuid, WEIXIN_MP_USER_STATUS.getExpireTime());
        String showQrCodeUrl = wxMpService.getQrcodeService().qrCodePictureUrl(ticket.getTicket());
        Response response = HttpToolUtil.getRequest(showQrCodeUrl);
        if (!response.isSuccessful()) {
            return ResultUtil.error(ErrorCode.WEIXIN_CREATE_QRCODE_ERROR);
        }
        try (InputStream inputStream = response.body().byteStream()) {
            String base64 = ImageUtil.imgToBase64(inputStream, "image/jpg");
            weixinQrcodeResponseVO.setImgBase64str(base64);
            weixinQrcodeResponseVO.setUuid(uuid);
            // 設定開始輪詢
            redisUtil.set(WEIXIN_MP_USER_STATUS.getKey(uuid), UserSanLoopStatusEnum.LOOP.getType(), WEIXIN_MP_USER_STATUS.getExpireTime());
        }
    } catch (Exception e) {
        log.error("建立場景二維條碼失敗", e);
        return ResultUtil.error(ErrorCode.WEIXIN_CREATE_QRCODE_ERROR);
    }
    Result<WeixinQrcodeResponseVO> success = ResultUtil.success(weixinQrcodeResponseVO);
    log.info("userQrcodeCreate end, resp:{}", success);
    return success;
}

3.2 掃碼後輪詢

// 輪詢狀態列舉
public enum UserSanLoopStatusEnum {
    /**
     * 已過期
     */
    EXPIRED(1),
    /**
     * 繼續輪詢
     */
    LOOP(2),
    /**
     * 已註冊
     */
    REG(3),
    /**
     * 未註冊
     */
    NOT_REG(4),
    ;
}

// 開始輪詢
public WeixinUserStatusResponseVO userStatus(WeixinMPRequestVO req) {
    String uuid = req.getUuid();
    String source = req.getSource();
    String openId = (String) redisUtil.get(WEIXIN_MP_USER_OPENID.getKey(uuid));
    WeixinUserStatusResponseVO weixinUserStatusResponseVO = new WeixinUserStatusResponseVO();
    Object value = redisUtil.get(WEIXIN_MP_USER_STATUS.getKey(uuid));
    if (value == null) {
        return weixinUserStatusResponseVO.setStatus(UserSanLoopStatusEnum.EXPIRED.getType());
    }
    Integer status = (Integer) value;
    // 如果使用者掃碼是已註冊,就直接根據openId獲取使用者token
    if (status.equals(UserSanLoopStatusEnum.REG.getType())) {
        String token = getToken(openId);
        weixinUserStatusResponseVO.setToken(token);
    }
    return weixinUserStatusResponseVO.setStatus(status);
}

3.3 關注事件處理

@Component
public class SubscribeHandler extends AbstractHandler {
    @Autowired
    private ScanScenesHandler scanScenesHandler;
    @Override
    public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage,
                                    Map<String, Object> context, WxMpService weixinService,
                                    WxSessionManager sessionManager) throws WxErrorException {
        String openId = wxMessage.getFromUser();
        this.logger.info("根據openid判斷使用者是否已註冊" );
        WxMpXmlOutMessage responseResult = null;
        try {
            responseResult = scanScenesHandler.handleSpecial(wxMessage, userWxInfo);
        } catch (Exception e) {
            this.logger.error(e.getMessage(), e);
        }
        ...
        return null;
    }
}

3.3 掃碼事件處理

@Component
public class ScanHandler extends AbstractHandler {
    @Autowired
    private ScanScenesHandler scanScenesHandler;
    @Autowired
    private WeixinMpMsgReplyService weixinMpMsgReplyService;
    @Override
    public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> map,
                                    WxMpService weixinService, WxSessionManager wxSessionManager) throws WxErrorException {
        String openId = wxMessage.getFromUser();
        this.logger.info("根據openid判斷使用者是否已註冊" );
        WxMpXmlOutMessage responseResult = null;
        try {
            responseResult = scanScenesHandler.handleSpecial(wxMessage, userWxInfo);
        } catch (Exception e) {
            this.logger.error(e.getMessage(), e);
        }
        ...
        return null;
    }
}

總結

自此,本文所講述的公眾號掃碼關注繫結手機號自動登入流程就講解完畢了。

關注公眾號【waynblog】每週分享技術乾貨、開源專案、實戰經驗、高效開發工具等,您的關注將是我的更新動力!