搜尋
首頁微信小程式小程式開發淺析小程式怎麼實現登入功能

淺析小程式怎麼實現登入功能

Dec 06, 2021 am 10:11 AM
小程式登入

小程式怎麼實現登入功能?這篇文章為大家介紹小程式登入的正確開啟方式,希望對大家有幫助!

淺析小程式怎麼實現登入功能

小程式網路元件

#https://developers.weixin.qq.com/miniprogram /dev/api/network/request/wx.request.html

RequestTask說明

方法 #說明
RequestTask.abort() 中斷請求任務。
RequestTask.onHeadersReceived(function callback) 監聽 HTTP Response Header 事件。會比請求完成事件更早。
RequestTask.offHeadersReceived(function callback) 取消監聽 HTTP Response Header 事件。
RequestTask.onChunkReceived(function callback) 監聽 Transfer-Encoding Chunk Received 事件。當接收到新的chunk時觸發。
RequestTask.offChunkReceived(function callback) 取消監聽 Transfer-Encoding Chunk Received 事件。

wx.request(Object object)屬性

這裡隻列比較常用的屬性,全部屬性請查看連結

##請求的參數headerObject#否設定請求的header,在header 中不能設定Referer。 #timeoutnumber否逾時時間,單位為毫秒#methodstringsuccessfailfunction否總結一下:所有的小程式介面基本上都有兩個特徵:
屬性 類型 預設值 必填 說明
#url string
#是 #開發者伺服器介面位址
data string/object/ArrayBuffer

content-type 預設為application/json

##GET HTTP 請求方法
function
介面呼叫成功的回呼函數
function
#介面呼叫失敗的回呼函數
complete
##介面呼叫結束的回呼函數(呼叫成功、失敗都會執行)就算是abort掉的請求!

##參數都是一個物件。便於記憶的同時方便擴展。

    都有相同的結果處理方式:都有success、fail、complete三個回呼屬性。
  • 介面執行的各種情況下的errMsg物件介紹。

回呼屬性

errMsg物件{errMsg:"request:ok"...}
success
fail {errMsg:"request:fail "...} 有的系統這個fail後面有個空格,所以要用這個判斷,最好是用正規表示式。也可以使用indexOf函數,大於-1來判斷。
abort {errMsg:"request:fail abort"...}
#

範例程式碼

  let reqTask = wx.request({
      url: getApp().globalData.api,
      success(res) {
        if (res.errMsg === "request:ok") console.log("res", res);
      },
      fail(err) {
        // if(err.errMsg.indexOf('request:fail')>-1) console.log('err', err);
        if (/^request:fail/i.test(err.errMsg)) console.log("err", err);
      },
      complete(res) {
        console.log("resOrErr", res);
      },
    });
   const reqTaskOnHeadersReceived = (headers) => {
      reqTask.offHeadersReceived(reqTaskOnHeadersReceived);
      console.log("headers", headers);
      // 由于请求还未完全结束,所以我们没办法获得请求的状态码,但是我们可以通过返回的requestBody的长度来进行判断。
      // 两点说明:1. 两个~~可以把字符串数字快速转化为数字。
      // 2. 为什么取小于19,是由于后台返回没有权限的requestBody的时候Content-length为“18”,正常情况下是大于19的。所以具体多少得看一下具体情况。
      if (~~headers.header["Content-length"] < 19) reqTask.abort();
    };
    reqTask.onHeadersReceived(reqTaskOnHeadersReceived);

小程式登入介面

  • ##wx .getUserProfile(Object object)

    取得使用者資訊。頁面產生點擊事件(例如

    buttonbindtap 的回調中)後才可調用,每次請求都會彈出授權窗口,用戶同意後返回 userInfo。此介面用於取代 wx.getUserInfo,詳見 使用者資訊介面調整說明

  • wx.checkSession(Object object)

    檢查登入態是否過期。透過 wx.login 介面獲得的使用者登入態擁有一定的時效性。使用者越久未使用小程序,使用者登入態越有可能失效。反之如果使用者一直在使用小程序,則使用者登入態一直保持有效。具體時效邏輯由微信維護,對開發者透明。開發者只需要呼叫 wx.checkSession 介面檢測目前使用者登入態是否有效。

    登入態過期後開發者可以再呼叫 wx.login 取得新的使用者登入狀態。呼叫成功說明目前 session_key 未過期,呼叫失敗說明 session_key 已過期。更多使用方法詳見

    小程式登入

  • wx.login(Object object)

    #呼叫介面取得登入憑證(code)。透過憑證進而換取使用者登入態訊息,包括使用者在目前小程式的唯一識別(openid)、微信開放平台帳號下的唯一識別(unionid,若目前小程式已綁定到微信開放平台帳號)及本次登錄的會話金鑰(session_key)等。使用者資料的加解密通訊需要依賴會話金鑰完成。更多使用方法詳見

    小程式登入

後端登入介面程式碼實作

#後端使用NodeJS,web框架KOA版本^2.13 .4,路由框架@koa/router版本^10.1.1,框架request,版本^2.88.2,jsonwebtoken用來加密解密token訊息,版本^8.5.1#

// app.js
const Koa = require("koa");
const Router = require("@koa/router");
const WeixinAuth = require("./lib/koa2-weixin-auth");
const jsonwebtoken = require("jsonwebtoken");

const app = new Koa();
// 小程序机票信息
const miniProgramAppId = "*********";
const miniProgramAppSecret = "***********";
const weixinAuth = new WeixinAuth(miniProgramAppId, miniProgramAppSecret);

const JWT_SECRET = "JWTSECRET";
// 路由中间件需要安装@koa/router
// 开启一个带群组的路由
const router = new Router({
  prefix: "/user",
});
// 这是正规的登陆方法
// 添加一个参数,sessionKeyIsValid,代表sessionKey是否还有效
router.post("/weixin-login", async (ctx) => {
  let { code, userInfo, encryptedData, iv, sessionKeyIsValid } =
    ctx.request.body;
   // 解析openid
  const token = await weixinAuth.getAccessToken(code);
  userInfo.openid = token.data.openid;
  // 这里可以自己进行处理,比方说记录到数据库,处理token等
  let authorizationToken = jsonwebtoken.sign(
    { name: userInfo.nickName },
    JWT_SECRET,
    { expiresIn: "1d" }
  );
  Object.assign(userInfo, { authorizationToken });
  ctx.status = 200;
  ctx.body = {
    code: 200,
    msg: "ok",
    data: userInfo,
  };
});
// lib/koa2-weixin-auth.js
const querystring = require("querystring");
const request = require("request");

const AccessToken = function (data) {
  if (!(this instanceof AccessToken)) {
    return new AccessToken(data);
  }
  this.data = data;
};

/*!
 * 检查AccessToken是否有效,检查规则为当前时间和过期时间进行对比
 *
 * Examples:
 * ```
 * token.isValid();
 * ```
 */
AccessToken.prototype.isValid = function () {
  return (
    !!this.data.session_key &&
    new Date().getTime() < this.data.create_at + this.data.expires_in * 1000
  );
};

/**
 * 根据appid和appsecret创建OAuth接口的构造函数
 * 如需跨进程跨机器进行操作,access token需要进行全局维护
 * 使用使用token的优先级是:
 *
 * 1. 使用当前缓存的token对象
 * 2. 调用开发传入的获取token的异步方法,获得token之后使用(并缓存它)。

 * Examples:
 * ```
 * var OAuth = require(&#39;oauth&#39;);
 * var api = new OAuth(&#39;appid&#39;, &#39;secret&#39;);
 * ```
 * @param {String} appid 在公众平台上申请得到的appid
 * @param {String} appsecret 在公众平台上申请得到的app secret
 */
const Auth = function (appid, appsecret) {
  this.appid = appid;
  this.appsecret = appsecret;
  this.store = {};

  this.getToken = function (openid) {
    return this.store[openid];
  };

  this.saveToken = function (openid, token) {
    this.store[openid] = token;
  };
};

/**
 * 获取授权页面的URL地址
 * @param {String} redirect 授权后要跳转的地址
 * @param {String} state 开发者可提供的数据
 * @param {String} scope 作用范围,值为snsapi_userinfo和snsapi_base,前者用于弹出,后者用于跳转
 */
Auth.prototype.getAuthorizeURL = function (redirect_uri, scope, state) {
  return new Promise((resolve, reject) => {
    const url = "https://open.weixin.qq.com/connect/oauth2/authorize";
    let info = {
      appid: this.appid,
      redirect_uri: redirect_uri,
      scope: scope || "snsapi_base",
      state: state || "",
      response_type: "code",
    };
    resolve(url + "?" + querystring.stringify(info) + "#wechat_redirect");
  });
};

/*!
 * 处理token,更新过期时间
 */
Auth.prototype.processToken = function (data) {
  data.create_at = new Date().getTime();
  // 存储token
  this.saveToken(data.openid, data);
  return AccessToken(data);
};

/**
 * 根据授权获取到的code,换取access token和openid
 * 获取openid之后,可以调用`wechat.API`来获取更多信息
 * @param {String} code 授权获取到的code
 */
Auth.prototype.getAccessToken = function (code) {
  return new Promise((resolve, reject) => {
    const url = "https://api.weixin.qq.com/sns/jscode2session";
    //由于此框架版本很久没有更新了,此处地址发生了变化,需要修改为以上地址,不然会出现
    //41008错误。这也是没有直接使用框架,引用本地使用的原因。
    // const url = "https://api.weixin.qq.com/sns/oauth2/access_token";
    const info = {
      appid: this.appid,
      secret: this.appsecret,
      js_code: code,
      grant_type: "authorization_code",
    };
    request.post(url, { form: info }, (err, res, body) => {
      if (err) {
        reject(err);
      } else {
        const data = JSON.parse(body);
        resolve(this.processToken(data));
      }
    });
  });
};

/**
 * 根据refresh token,刷新access token,调用getAccessToken后才有效
 * @param {String} refreshToken refreshToken
 */
Auth.prototype.refreshAccessToken = function (refreshToken) {
  return new Promise((resolve, reject) => {
    const url = "https://api.weixin.qq.com/sns/oauth2/refresh_token";
    var info = {
      appid: this.appid,
      grant_type: "refresh_token",
      refresh_token: refreshToken,
    };
    request.post(url, { form: info }, (err, res, body) => {
      if (err) {
        reject(err);
      } else {
        const data = JSON.parse(body);
        resolve(this.processToken(data));
      }
    });
  });
};

/**
 * 根据openid,获取用户信息。
 * 当access token无效时,自动通过refresh token获取新的access token。然后再获取用户信息
 * @param {Object|String} options 传入openid或者参见Options
 */
Auth.prototype.getUser = async function (openid) {
  const data = this.getToken(openid);
  console.log("getUser", data);
  if (!data) {
    var error = new Error(
      "No token for " + options.openid + ", please authorize first."
    );
    error.name = "NoOAuthTokenError";
    throw error;
  }
  const token = AccessToken(data);
  var accessToken;
  if (token.isValid()) {
    accessToken = token.data.session_key;
  } else {
    var newToken = await this.refreshAccessToken(token.data.refresh_token);
    accessToken = newToken.data.session_key;
  }
  console.log("accessToken", accessToken);
  return await this._getUser(openid, accessToken);
};

Auth.prototype._getUser = function (openid, accessToken, lang) {
  return new Promise((resolve, reject) => {
    const url = "https://api.weixin.qq.com/sns/userinfo";
    const info = {
      access_token: accessToken,
      openid: openid,
      lang: lang || "zh_CN",
    };
    request.post(url, { form: info }, (err, res, body) => {
      if (err) {
        reject(err);
      } else {
        resolve(JSON.parse(body));
      }
    });
  });
};

/**
 * 根据code,获取用户信息。
 * @param {String} code 授权获取到的code
 */
Auth.prototype.getUserByCode = async function (code) {
  const token = await this.getAccessToken(code);
  return await this.getUser(token.data.openid);
};

module.exports = Auth;

小程式端登入程式碼實作

<!--pages/index.wxml-->
<view class="page-section">
    <text class="page-section__title">微信登录</text>
    <view class="btn-area">
        <button  bindtap="getUserProfile" type="primary">登录</button>
    </view>
</view>
// pages/index.js
Page({
  /**
   * 页面的初始数据
   */
  data: {},
  // 正确的登录方式
  getUserProfile() {
    // 推荐使用wx.getUserProfile获取用户信息,开发者每次通过该接口获取用户个人信息均需用户确认
    // 开发者妥善保管用户快速填写的头像昵称,避免重复弹窗
    wx.getUserProfile({
      desc: "用于完善会员资料", // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写
      success: (res) => {
        let { userInfo, encryptedData, iv } = res;
        const requestLoginApi = (code) => {
          // 发起网络请求
          wx.request({
            url: "http://localhost:3000/user/weixin-login",
            method: "POST",
            header: {
              "content-type": "application/json",
            },
            data: {
              code,
              userInfo,
              encryptedData,
              iv,
            },
            success(res) {
              console.log("请求成功", res.data);
              let token = res.data.data.authorizationToken;
              wx.setStorageSync("token", token);
              onUserLogin(token);
              console.log("authorization", token);
            },
            fail(err) {
              console.log("请求异常", err);
            },
          });
        };
        const onUserLogin = (token) => {
          getApp().globalData.token = token;
          wx.showToast({
            title: "登录成功了",
          });
        };
        //必须进行session是否过期检查,不然会出现第一次点击登录,服务器报Illegal Buffer
        //的错误,但是第二次点击登录正常。
        wx.checkSession({
          success: (res) => {
            // session_key 未过期,并且在本生命周期一直有效
            console.log("在登陆中");
            let token = wx.getStorageSync("token");
            if (token) onUserLogin(token);
          },
          fail: (res) => {
            // session_key已经失效,需要重新执行登录流程
            wx.login({
              success(res0) {
                if (res0.code) {
                  requestLoginApi(res0.code);
                } else {
                  console.log("登录失败!" + res0.errMsg);
                }
              },
            });
          },
        });
      },
    });
  },
});

#針對登入程式碼可以做哪些最佳化?

對於一個軟體,就程式碼層面而言,需要追求最基本的幾個面向(遠不止這些,但是先姑且先做個好這些吧):

  • 可維護性(maintainability)

    所謂的「維護」無外乎就是修改bug、修改舊的程式碼、新增新的程式碼之類的工作。所謂「程式碼易​​維護」就是指,在不破壞原有程式碼設計、不引入新的 bug 的情況下,能夠快速地修改或新增程式碼。所謂「程式碼不易維護」就是指,修改或新增程式碼需要冒著極大的引入新 bug 的風險,並且需要花費很長的時間才能完成。

  • 可讀性(readability)

    軟體設計大師Martin Fowler 曾經說過:「Any fool can write code that a computer can understand. Good programmers write code that humans can understand.」翻譯成中文就是:「任何傻瓜都會寫電腦能理解的程式碼。好的程式設計師能夠編寫人能夠理解的程式碼。」Google 內部甚至專門有個認證就叫作Readability。只有拿到這個認證的工程師,才有資格在 code review 的時候,就批准別人提交代碼。可見程式碼的可讀性有多重要,畢竟,程式碼被閱讀的次數遠遠超過被編寫和執行的次數。我們需要看程式碼是否符合編碼規範、命名是否達意、註解是否詳盡、函數是否長短適當、模組劃分是否清晰、是否符合高內聚低耦合等等。

  • 可擴充性(extensibility)

    可擴充性也是一個評價程式碼品質非常重要的標準。程式碼預留了一些功能擴充點,你可以把新功能程式碼,直接插到擴充點上,而不需要因為要增加一個功能而大動幹戈,改動大量的原始程式碼。

  • 可重複使用性(reusability)

    代码的可复用性可以简单地理解为,尽量减少重复代码的编写,复用已有的代码。

那么接下来就来优化一下代码吧:

模块化

可以把登录的代码模块化,代码如下:

// lib/login.js
function loginWithCallback(cb) {
  // 推荐使用wx.getUserProfile获取用户信息,开发者每次通过该接口获取用户个人信息均需用户确认
  // 开发者妥善保管用户快速填写的头像昵称,避免重复弹窗
  wx.getUserProfile({
    desc: "用于完善会员资料", // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写
    success: (res) => {
      let { userInfo, encryptedData, iv } = res;
      const requestLoginApi = (code) => {
        // 发起网络请求
        wx.request({
          url: "http://localhost:3000/user/weixin-login",
          method: "POST",
          header: {
            "content-type": "application/json",
          },
          data: {
            code,
            userInfo,
            encryptedData,
            iv,
          },
          success(res) {
            console.log("请求成功", res.data);
            let token = res.data.data.authorizationToken;
            wx.setStorageSync("token", token);
            onUserLogin(token);
            console.log("authorization", token);
          },
          fail(err) {
            console.log("请求异常", err);
          },
        });
      };

      const onUserLogin = (token) => {
        getApp().globalData.token = token;
        wx.showToast({
          title: "登录成功了",
        });
        if (cb && typeof cb == "function") cb(token);
      };
      wx.checkSession({
        success: (res) => {
          // session_key 未过期,并且在本生命周期一直有效
          console.log("在登陆中");
          let token = wx.getStorageSync("token");
          if (token) onUserLogin(token);
        },
        fail: (res) => {
          // session_key已经失效,需要重新执行登录流程
          wx.login({
            success(res0) {
              if (res0.code) {
                requestLoginApi(res0.code);
              } else {
                console.log("登录失败!" + res0.errMsg);
              }
            },
          });
        },
      });
    },
  });
}

export default loginWithCallback;

Promise化

回调地狱问题,不利于代码的阅读,所以接下来我们基于Promise进行代码优化。有了 Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise 对象提供统一的接口,使得控制异步操作更加容易。

Promise的几个方法简介

方法名稱 說明
#Promise.prototype.then 方法傳回的是一個新的Promise 對象,因此可以採用鍊式寫法。這種設計使得嵌套的非同步操作,可以被很容易得改寫,從回呼函數的"橫向發展"改為"向下發展"。
Promise.prototype.catch #是Promise.prototype.then(null, rejection) 的別名,用於指定發生錯誤時的回調函數。 Promise 物件的錯誤具有"冒泡"性質,會一直向後傳遞,直到被捕獲為止。也就是說,錯誤總是會被下一個 catch 語句捕捉。
Promise.prototype.finally #方法傳回一個Promise。在promise結束時,無論結果是fulfilled或是rejected,都會執行指定的回呼函數。這為在Promise是否成功完成後都需要執行的程式碼提供了一種方式。
Promise.all 這避免了同樣的語句需要在then()catch()中各寫一次的情況。 Promise.all 方法用於將多個 Promise 實例,包裝成一個新的 Promise 實例。 Promise.all 方法接受一個陣列作為參數,var p = Promise.all([p1,p2,p3]);p1、p2、p3 都是 Promise 物件的實例。 (Promise.all 方法的參數不一定是數組,但是必須具有 iterator 接口,且返回的每個成員都是 Promise 實例。)p 的狀態由 p1、p2、p3 決定,分成兩種情況。 (1)只有p1、p2、p3的狀態都變成fulfilled,p的狀態才會變成fulfilled,此時p1、p2、p3的回傳值組成一個數組,傳遞給p的回呼函數。 (2)只要p1、p2、p3之中有一個被rejected,p的狀態就變成rejected,此時第一個被reject的實例的回傳值,就會傳遞給p的回呼函數。
Promise.race Promise.race 方法同樣是將多個 Promise 實例,包裝成新的 Promise 實例。 var p = Promise.race([p1,p2,p3]);上面程式碼中,只要p1、p2、p3之中有一個實例率先改變狀態,p的狀態就跟著改變。那個率先改變的Promise實例的回傳值,就傳遞給p的回傳值。
Promise.any 接收一個Promise可迭代對象,只要其中的一個 promise 成功,就返回那個已經成功的 promise 。所有子實例都處於rejected狀態,總的promise才會處於rejected狀態。
Promise.allSettled #回傳一個在所有給定的promise都已經fulfilledrejected後的promise,並帶有一個物件數組,每個物件表示對應的promise結果。相較之下,Promise.all() 更適合彼此相互依賴或在其中任何一個reject時立即結束。

小程序API接口Promise化并且把需要登录的调用接口模块化

1、安装插件。请先查看npm支持文档。

npm install --save miniprogram-api-promise

2、在微信开发者工具右方详情中勾选使用npm模块,并在菜单栏工具中点击构建npm。

3、初始化代码。

// app.js
import {promisifyAll} from &#39;miniprogram-api-promise&#39;
import login from "../lib/login";
const wxp ={}
promisifyAll(wx,wxp)
// 需要token的请求统一处理登录和设置header,并且处理错误信息
wxp.requestNeedLogin = async function (args) {
  let token = wx.getStorageSync("token");
  if (!token) {
    token = await loginWithPromise();
  }
  if (!args.header) args.header = {};
  args.header["Authorization"] = `Bearer ${token}`;
  return wxp.request(args).catch(console.error);
};
// app.js
App({
  wxp:wxp,
});

4、改写login.js代码

// lib/login.js
function login() {
  return new Promise((resolve, reject) => {
    // 推荐使用wx.getUserProfile获取用户信息,开发者每次通过该接口获取用户个人信息均需用户确认
    // 开发者妥善保管用户快速填写的头像昵称,避免重复弹窗
    wx.getUserProfile({
      desc: "用于完善会员资料", // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写
       success:async (res0) => {
        let {
          userInfo,
          encryptedData,
          iv
        } = res0;
        const app = getApp();
        try {
          app.wxp.checkSession();
        } catch (err) {
          reject(err);
        }
        let token = wx.getStorageSync("token");
        if (!token) {
          let res1 = await app.wxp.login().catch(err => reject(err));
          let code = res1.code;
          let res = await app.wxp.request({
            url: "http://localhost:3000/user/weixin-login",
            method: "POST",
            header: {
              "content-type": "application/json",
            },
            data: {
              code,
              userInfo,
              encryptedData,
              iv,
            }
          }).catch(err => reject(err));
          token = res.data.data.authorizationToken;
          wx.setStorageSync("token", token);
          app.globalData.token = token;
          wx.showToast({
            title: "登录成功了",
          });
          resolve(token);
        }
      },
    });
  })
}

export default login;

5、调用代码

<view class="container page-head">
  <text class="page-section__title">需要登录的请求调用</text>
  <view class="btn-area">
    <button bindtap="request1" type="primary">请求1</button>
    <button bindtap="request2" type="primary">请求2</button>
  </view>
</view>
// pages/index.js
Page({
  /**
   * 页面的初始数据
   */
  data: {},
  request1() {
    getApp().wxp.requestNeedLogin({
        url: "http://localhost:3000/user/home?name=andying",
      }).then(console.log)
  },
  request2() {
    getApp().wxp.requestNeedLogin({
        url: "http://localhost:3000/user/home?name=eva",
      }).then(console.log)
  },
});

【相关学习推荐:小程序开发教程

以上是淺析小程式怎麼實現登入功能的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文轉載於:掘金社区。如有侵權,請聯絡admin@php.cn刪除

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

SublimeText3 英文版

SublimeText3 英文版

推薦:為Win版本,支援程式碼提示!

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

mPDF

mPDF

mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境