小程式怎麼實現登入功能?這篇文章為大家介紹小程式登入的正確開啟方式,希望對大家有幫助!
小程式網路元件
#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)屬性
這裡隻列比較常用的屬性,全部屬性請查看連結。
屬性 | 類型 | 預設值 | 必填 | 說明 |
---|---|---|---|---|
#url | string | #是 | #開發者伺服器介面位址 | |
data | string/object/ArrayBuffer | 否 | ##請求的參數||
Object | 設定請求的header,在header 中不能設定Referer。 | content-type 預設為 application/json
|
||
number | 逾時時間,單位為毫秒 | |||
string | ##GET | 否 | HTTP 請求方法 | |
function |
否 | 介面呼叫成功的回呼函數 | ||
function |
#介面呼叫失敗的回呼函數 |
complete | ||
否 | ##介面呼叫結束的回呼函數(呼叫成功、失敗都會執行)就算是abort掉的請求! |
總結一下:所有的小程式介面基本上都有兩個特徵: |
##參數都是一個物件。便於記憶的同時方便擴展。
回呼屬性
errMsg物件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); 小程式登入介面
後端登入介面程式碼實作 #後端使用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('oauth'); * var api = new OAuth('appid', 'secret'); * ``` * @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); } }, }); }, }); }, }); }, }); #針對登入程式碼可以做哪些最佳化? 對於一個軟體,就程式碼層面而言,需要追求最基本的幾個面向(遠不止這些,但是先姑且先做個好這些吧):
那么接下来就来优化一下代码吧: 模块化 可以把登录的代码模块化,代码如下: // 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的几个方法简介
小程序API接口Promise化并且把需要登录的调用接口模块化 1、安装插件。请先查看npm支持文档。 npm install --save miniprogram-api-promise 2、在微信开发者工具右方详情中勾选使用npm模块,并在菜单栏工具中点击构建npm。 3、初始化代码。 // app.js import {promisifyAll} from 'miniprogram-api-promise' 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中文網其他相關文章!