小程式授權登入前後端對接及使用者資訊完善

风希落發表於2024-09-07

對接後臺登入流程

微信官方早都已經禁止開發者直接透過 api 獲取使用者資訊資料了,大家拿個使用者的 openid 註冊好,剩下的讓使用者填寫就行了。

先上官方的經典登入流程圖:

登入流程時序

步驟拆分解析:

  1. 前端透過 呼叫官方 API wx.login,將回撥中的 code 臨時登陸憑證傳遞給(請求)後臺
  2. 後臺去請求微信的介面 https://api.weixin.qq.com/sns/jscode2session。具體用法參考 官方文件
  3. 後臺請求該介面的響應成功的資料中可以拿到兩個值 openidsession_key其中 openid 是唯一值,可以當作使用者標識,比如判斷該使用者是否存在,是否註冊該使用者等。session_key 自行處理。

如果需要獲取使用者資訊(現在使用者資訊毛都沒有,獲取也沒用,別獲取了),需要在 wx.login 之前呼叫 wx.getUserProfilerawDatasignature 聯合 code 一併傳遞給後臺,後臺透過 session_keyrawData反解析 signature 的正確性。

根據現在微信官方的說法,註冊時新增微信使用者真實資訊(頭像暱稱)是不可能的,前後端互動時,透過 wx.login 傳遞一個 code 僅註冊就可以,其它引數目前完全沒用

前端程式碼

不是我不用 wx.getUserProfile,而是這個 api 真沒啥資訊可獲取了,老老實實拿個 openid 註冊就行了。

wx.login({
  success(res) {
    wx.request({
      url: 'http://localhost:8888/testApi/wx/login',
      data: {
        // encryptedData 和 iv 是用來解析私密使用者資訊的,但是現在啥也獲取不到了,所以沒用
        code: res.code,
        // encryptedData: InfoRes.encryptedData,
        // iv: InfoRes.iv,
        // rawData: InfoRes.rawData,
        // signature: InfoRes.signature,
      },
    }).then((res) => {
      // 登入認證成功,後臺一般會返回登陸憑證(token),儲存使用即可
    });
  },
  fail(err) {
    console.log(err.errMsg);
  },
});

後端程式碼

請求第三方(微信)示例

請求第三方介面,這裡給出兩種獲取第三方介面的程式碼方式,任選其中一種即可。

  1. pom 中整合 httpclient 包。

HttpClientUtils.java

public class HttpClientUtil {
    public static String doGet(String url, Map<String, String> param) {

        // 建立Httpclient物件
        CloseableHttpClient httpclient = HttpClients.createDefault();

        String resultString = "";
        CloseableHttpResponse response = null;
        try {
            // 建立uri
            URIBuilder builder = new URIBuilder(url);
            if (param != null) {
                for (String key : param.keySet()) {
                    builder.addParameter(key, param.get(key));
                }
            }
            URI uri = builder.build();

            // 建立http GET請求
            HttpGet httpGet = new HttpGet(uri);

            // 執行請求
            response = httpclient.execute(httpGet);
            // 判斷返回狀態是否為200
            if (response.getStatusLine().getStatusCode() == 200) {
                resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (response != null) {
                    response.close();
                }
                httpclient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return resultString;
    }

    public static String doGet(String url) {
        return doGet(url, null);
    }

    public static String doPost(String url, Map<String, String> param) {
        // 建立Httpclient物件
        CloseableHttpClient httpClient = HttpClients.createDefault();
        CloseableHttpResponse response = null;
        String resultString = "";
        try {
            // 建立Http Post請求
            HttpPost httpPost = new HttpPost(url);
            // 建立引數列表
            if (param != null) {
                List<NameValuePair> paramList = new ArrayList<>();
                for (String key : param.keySet()) {
                    paramList.add(new BasicNameValuePair(key, param.get(key)));
                }
                // 模擬表單
                UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList);
                httpPost.setEntity(entity);
            }
            // 執行http請求
            response = httpClient.execute(httpPost);
            resultString = EntityUtils.toString(response.getEntity(), "utf-8");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return resultString;
    }

    public static String doPost(String url) {
        return doPost(url, null);
    }

    public static String doPostJson(String url, String json) {
        // 建立Httpclient物件
        CloseableHttpClient httpClient = HttpClients.createDefault();
        CloseableHttpResponse response = null;
        String resultString = "";
        try {
            // 建立Http Post請求
            HttpPost httpPost = new HttpPost(url);
            // 建立請求內容
            StringEntity entity = new StringEntity(json, ContentType.APPLICATION_JSON);
            httpPost.setEntity(entity);
            // 執行http請求
            response = httpClient.execute(httpPost);
            resultString = EntityUtils.toString(response.getEntity(), "utf-8");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return resultString;
    }
}

使用

Map<String, String> params = new HashMap<>();
// .... 引數新增 params.put("...","...")
String s = HttpClientUtil.doPost(url, params);
SONObject object = JSON.parseObject(s);
  1. 使用 Springboot 自帶的 RestTemplate

RestTemplateUtil

public class RestTemplateUtil {
    public static JSONObject doPost(String url, MultiValueMap<String, Object> param){
        RestTemplate restTemplate=new RestTemplate();
        String s = restTemplate.postForObject(url, param, String.class);
        return JSONObject.parseObject(s);
    }
}

使用

MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
// .... 引數新增 params.add("...","...")
JSONObject object = RestTemplateUtil.doPost(url, params);

登入流程對接

// 現在 rawData 和 signature 已經沒什麼用了,所以這兩個引數相關的邏輯可以刪除,此處保留僅做記錄
public R wxLogin(String code) {
    String url = "https://api.weixin.qq.com/sns/jscode2session";
    MultiValueMap<String, Object> params = new LinkedMultiValueMap<>();
    params.add("appid",appid);
    params.add("secret",secret);
    params.add("js_code",code);
    params.add("grant_type",grantType);
    JSONObject object = RestTemplateUtil.doPost(url, params);
    System.out.println(object);
    //接收微信介面服務 獲取返回的引數,這裡拿到 openid 就足夠了,後邊的簽名校驗沒啥必要
    String openid = object.getString("openid");
    String sessionKey = object.getString("session_key");
    //校驗簽名 小程式傳送的簽名signature與伺服器端生成的簽名signature2 = sha1(rawData + sessionKey)
    // String signature2 = DigestUtils.sha1Hex(rawData + sessionKey);
    // System.out.println(signature2);
    // System.out.println(openid);
    // if(!signature.equals(signature2)){
    //     return R.fail("簽名校驗失敗");
    // }
    // SONObject rawDataJson = JSONObject.parseObject(rawData);
    // return R.ok(rawDataJson);

    // --------這裡可以使用 openid 判斷使用者是否註冊,將使用者資訊插入資料庫
}

獲取使用者資訊

透過 API 呼叫方式直接獲取使用者資訊的方式已經完全沒有了,只能讓使用者自己手動完善個人資訊,類似於表單填寫,但在此基礎上,微信官方提供了相應的開放能力。

此處使用原生的小程式做示例,Taro 和 uniapp 的使用邏輯相同。

獲取使用者暱稱

小程式的 input 元件配置 type="nickname"

<input type="nickname" model:value="{{ nameValue }}" placeholder="請輸入暱稱" />
Page({
  data: {
    nameValue: '',
  },

  onLoad(options) {
    const { name } = options;
    this.setData({
      nameValue: name,
    });
  },

  onSubmit() {
   // 此處可將填充的使用者暱稱提交給後端做更新
    console.log(this.data.nameValue);
  },
});

獲取使用者暱稱示意圖

獲取使用者頭像

小程式的 button 元件需設定 open-type="chooseAvatar",並繫結對應的事件,獲取使用者的(上傳)頭像臨時地址,將其傳給後端進行資訊儲存。

<button open-type="chooseAvatar" bind:chooseavatar="onChooseAvatar">
  <image src="{{ avatarUrl }}" />
</button>
const defaultAvatarUrl = 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0';

Page({
  data: {
    avatarUrl: defaultAvatarUrl,
  },

  async onChooseAvatar(e) {
    const { avatarUrl = '' } = e.detail;
    const { data, code } = await uploadImg(avatarUrl);  // 此處將頭像上傳到後端,並返回圖片 url 地址
    code === 200 &&
      this.setData({
        avatarUrl: data,
      });
  },
});

獲取使用者頭像示意圖

相關文章