Rumah > Artikel > applet WeChat > Analisis ringkas tentang cara melaksanakan fungsi log masuk dalam program mini
Bagaimana untuk melaksanakan fungsi log masuk dalam program mini? Artikel ini akan memperkenalkan kepada anda cara yang betul untuk membuka log masuk program mini Saya harap ia akan membantu anda!
Komponen rangkaian program mini
https://developers.weixin.qq.com/miniprogram /dev/api/network/request/wx.request.html
Perihalan 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 事件。 |
atribut wx.request(objek objek)
Hanya atribut yang lebih biasa digunakan disenaraikan di sini Untuk semua atribut, sila lihat pautan.
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
url | string | 是 | 开发者服务器接口地址 | |
data | string/object/ArrayBuffer | 否 | 请求的参数 | |
header | Object | 否 | 设置请求的 header,header 中不能设置 Referer。 content-type 默认为 application/json
|
|
timeout | number | 否 | 超时时间,单位为毫秒 | |
method | string | GET | 否 | HTTP 请求方法 |
success | function | 否 | 接口调用成功的回调函数 | |
fail | function | 否 | 接口调用失败的回调函数 | |
complete | function | 否 | 接口调用结束的回调函数(调用成功、失败都会执行)哪怕是abort掉的请求! |
Untuk meringkaskan: semua antara muka program mini pada asasnya mempunyai dua ciri:
Parameter semuanya adalah satu objek. Mudah diingat dan mudah dikembangkan.
semuanya mempunyai kaedah pemprosesan hasil yang sama: kesemuanya mempunyai tiga atribut panggil balik: kejayaan, gagal dan lengkap.
Pengenalan kepada objek errMsg dalam pelbagai situasi pelaksanaan antara muka.
回调属性 | errMsg对象 |
---|---|
success | {errMsg:"request:ok"...} |
fail | {errMsg:"request:fail "...} 有的系统这个fail后面有个空格,所以要使用这个判断,最好是使用正则表达式。也可以使用indexOf函数,大于-1进行判断。 |
abort | {errMsg:"request:fail abort"...} |
Kod contoh
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);
Antara muka log masuk program mini
wx.getUserProfile(Object object)
Dapatkan maklumat pengguna. Ia hanya boleh dipanggil selepas peristiwa klik dijana pada halaman (contohnya, dalam panggilan balik button
pada bindtap
Tetingkap kebenaran akan muncul untuk setiap permintaan dan pengguna akan kembali ke userInfo
). selepas bersetuju. Antara muka ini digunakan untuk menggantikan wx.getUserInfo
Untuk butiran, lihat Arahan Pelarasan Antara Muka Maklumat Pengguna.
wx.checkSession(Object object)
Semak sama ada status log masuk telah tamat tempoh. Status log masuk pengguna yang diperoleh melalui antara muka wx.login mempunyai ketepatan masa tertentu. Semakin lama pengguna tidak menggunakan program mini, semakin besar kemungkinan status log masuk pengguna akan menjadi tidak sah. Sebaliknya, jika pengguna telah menggunakan program mini, status log masuk pengguna akan sentiasa sah. Logik pemasaan khusus dikekalkan oleh WeChat dan telus kepada pembangun. Pembangun hanya perlu memanggil antara muka wx.checkSession untuk menyemak sama ada status log masuk pengguna semasa adalah sah.
Selepas status log masuk tamat tempoh, pembangun boleh menghubungi wx.login untuk mendapatkan status log masuk pengguna baharu. Panggilan yang berjaya menunjukkan bahawa session_key semasa belum tamat tempoh, dan panggilan yang gagal menunjukkan bahawa session_key telah tamat tempoh. Untuk butiran penggunaan lanjut, lihat Log Masuk Program Mini.
Panggil antara muka untuk mendapatkan bukti kelayakan log masuk (kod). Maklumat status log masuk pengguna ditukar melalui baucar, termasuk pengenalan unik pengguna dalam program mini semasa (openid), pengenalan unik di bawah akaun platform terbuka WeChat (unionid, jika program mini semasa telah terikat pada platform terbuka WeChat akaun) dan log masuk ini Kunci sesi (session_key), dsb. Penyulitan dan penyahsulitan komunikasi data pengguna bergantung pada kunci sesi. Untuk butiran penggunaan lanjut, lihat Log Masuk Program Mini.
Pelaksanaan kod antara muka log masuk belakang
Halaman belakang menggunakan NodeJS, rangka kerja web versi KOA ^2.13 . 4, rangka kerja penghalaan @koa/router versi ^10.1.1, permintaan rangka kerja, versi ^2.88.2, jsonwebtoken digunakan untuk menyulitkan dan menyahsulit maklumat token, versi ^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;
Pelaksanaan kod log masuk program mini
<!--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); } }, }); }, }); }, }); }, });
Apakah pengoptimuman yang boleh dilakukan untuk kod log masuk?
Untuk perisian, pada peringkat kod, adalah perlu untuk mengejar aspek yang paling asas (jauh lebih daripada ini, tetapi mari kita lakukan ini dengan baik dahulu):
Kebolehselenggaraan (maintainability)
Apa yang dipanggil "penyelenggaraan" tidak lebih daripada mengubah suai pepijat, mengubah suai kod lama dan menambah kod baharu. Apa yang dipanggil "kod mudah diselenggara" bermakna kod itu boleh diubah suai atau ditambah dengan cepat tanpa memusnahkan reka bentuk kod asal atau memperkenalkan pepijat baharu. Apa yang dipanggil "kod tidak mudah diselenggara" bermakna mengubah suai atau menambah kod memerlukan risiko besar untuk memperkenalkan pepijat baharu dan mengambil masa yang lama untuk diselesaikan.
Kebolehbacaan
Guru reka bentuk perisian Martin Fowler pernah berkata: "Mana-mana orang bodoh boleh menulis kod yang boleh difahami oleh komputer. Pengaturcara yang baik menulis kod yang boleh difahami oleh manusia." ke dalam bahasa Cina: "Mana-mana orang bodoh boleh menulis kod yang boleh difahami oleh komputer. Pengaturcara yang baik boleh menulis kod yang boleh difahami oleh manusia malah terdapat pensijilan dalam Google yang dipanggil Kebolehbacaan." Hanya jurutera yang telah memperoleh pensijilan ini layak untuk meluluskan orang lain untuk menyerahkan kod semasa semakan kod. Ia dapat dilihat betapa pentingnya kebolehbacaan kod itu Lagipun, bilangan kali kod dibaca jauh melebihi bilangan kali ia ditulis dan dilaksanakan. Kita perlu menyemak sama ada kod itu mematuhi piawaian pengekodan, sama ada penamaan itu bermakna, sama ada ulasan terperinci, sama ada panjang fungsi sesuai, sama ada pembahagian modul jelas, sama ada ia memenuhi kohesi tinggi dan gandingan rendah, dsb.
Kebolehlanjutan (extensibility)
Extensibility juga merupakan kriteria yang sangat penting untuk menilai kualiti kod. Kod ini menyimpan beberapa titik sambungan fungsi Anda boleh memasukkan kod fungsi baharu terus ke titik sambungan tanpa perlu bersusah payah dan menukar sejumlah besar kod asal hanya untuk menambah fungsi.
Kebolehgunaan semula
代码的可复用性可以简单地理解为,尽量减少重复代码的编写,复用已有的代码。
那么接下来就来优化一下代码吧:
模块化
可以把登录的代码模块化,代码如下:
// 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都已经fulfilled 或rejected 后的promise,并带有一个对象数组,每个对象表示对应的promise结果。相比之下,Promise.all() 更适合彼此相互依赖或者在其中任何一个reject 时立即结束。 |
小程序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) }, });
【相关学习推荐:小程序开发教程】
Atas ialah kandungan terperinci Analisis ringkas tentang cara melaksanakan fungsi log masuk dalam program mini. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!