サンプル コード
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)#ユーザー情報を取得します。これは、ページ上でクリック イベントが生成された後にのみ呼び出すことができます (たとえば、
button の bindtap
のコールバック内)。リクエストごとに認証ウィンドウがポップアップ表示され、 userInfo
は、ユーザーが同意した後に返されます。このインターフェイスは、wx.getUserInfo
を置き換えるために使用されます。詳細については、ユーザー情報インターフェイスの調整手順
を参照してください。
- wx.checkSession(Object object)
ログイン状態の有効期限が切れているかどうかを確認します。 wx.login インターフェースを通じて取得されるユーザーのログインステータスには、一定の適時性があります。ユーザーがミニ プログラムを使用していない期間が長いほど、ユーザーのログイン ステータスが無効になる可能性が高くなります。一方、ユーザーがミニプログラムを使用している場合、ユーザーのログイン状態は常に有効のままです。特定のタイミング ロジックは WeChat によって維持され、開発者には透過的です。開発者は、wx.checkSession インターフェイスを呼び出して、現在のユーザーのログイン ステータスが有効かどうかを確認するだけで済みます。
ログイン状態の有効期限が切れた後、開発者は wx.login を呼び出して新しいユーザーのログイン状態を取得できます。呼び出しが成功した場合は、現在の session_key の有効期限が切れていないことを示し、呼び出しが失敗した場合は、session_key の有効期限が切れたことを示します。その他の利用方法については、
ミニプログラムログインをご覧ください。
- wx.login(Object object)
インターフェースを呼び出して、ログイン資格情報 (コード) を取得します。ユーザーのログイン ステータス情報はバウチャーを通じて交換されます。これには、現在のミニ プログラムでのユーザーの一意の ID (openid)、WeChat オープン プラットフォーム アカウントでの一意の ID (現在のミニ プログラムが WeChat オープン プラットフォームにバインドされている場合は、unionid) が含まれます。アカウント)とこのログイン、セッションキー(session_key)など。ユーザーデータ通信の暗号化と復号化はセッションキーに依存します。その他の利用方法については、
ミニプログラムログインをご覧ください。
#バックエンド ログイン インターフェイス コードの実装バックエンドは、NodeJS、Web フレームワーク KOA バージョン ^2.13 を使用します。 4、ルーティング フレームワーク @koa/router バージョン ^10.1.1、フレームワーク リクエスト、バージョン ^2.88.2、トークン情報の暗号化と復号化に jsonwebtoken が使用されます、バージョン ^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);
}
},
});
},
});
},
});
},
});
ログインコードに対してどのような最適化が可能ですか? ソフトウェアの場合、コード レベルで最も基本的な側面を追求する必要があります (これらよりもはるかに多くの側面がありますが、最初にこれらの側面をしっかりと理解しましょう):
Maintainability (保守性)
那么接下来就来优化一下代码吧:
模块化
可以把登录的代码模块化,代码如下:
// 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 の終了時には、結果が満たされたか拒否されたかに関係なく、指定されたコールバック関数が実行されます。これにより、 Promise が正常に完了するかどうかに関係なく、コードを実行する必要がある方法が提供されます。
|
#Promise.all
これにより、 | then()## で同じステートメントを使用する必要がなくなります。 # と catch() はそれぞれ 1 回ずつ書き込まれます。 Promise.all メソッドは、複数の Promise インスタンスを新しい Promise インスタンスにラップするために使用されます。 Promise.all メソッドは配列をパラメーターとして受け入れます。 var p = Promise.all([p1,p2,p3]);p1、p2、p3 はすべて Promise オブジェクトのインスタンスです。 (Promise.all メソッドのパラメーターは必ずしも配列である必要はありませんが、イテレーター インターフェイスが必要であり、返される各メンバーは Promise インスタンスです。) p の状態は、p1、p2、および p3 によって決定され、次のように分割されます。 2つの状況。 (1) p1、p2、p3 の状態がすべて満たされた場合にのみ、p の状態が満たされます このとき、p1、p2、p3 の戻り値は配列を形成し、 pのコールバック関数。 (2) p1、p2、p3 のいずれかが拒否されると、p の状態は拒否されますが、このとき最初に拒否されたインスタンスの戻り値が p のコールバック関数に渡されます。
| Promise.race
Promise.race メソッドは、複数の Promise インスタンスを新しい Promise インスタンスにラップします。 var p = Promise.race([p1,p2,p3]);上記のコードでは、p1、p2、p3 のうちの 1 つのインスタンスが最初に状態を変更する限り、p の状態が変更されます。それに応じて。 pの戻り値には、最初に変更されたPromiseインスタンスの戻り値が渡されます。 |
| Promise.any
Promise 反復可能オブジェクトを受け取り、Promise の 1 つが成功する限り、成功した Promise が返されます。すべてのサブインスタンスは拒否された状態になり、Promise 全体も拒否された状態になります。
|
| Promise.allSettled
指定されたすべての Promise が fulfilled または | rejected# された場合に値を返します。 ## の後の Promise にはオブジェクトの配列が伴い、各オブジェクトは対応する Promise の結果を表します。対照的に、Promise.all() は、相互の依存関係、またはいずれかの依存関係が reject になったときにすぐに終了する場合により適しています。
<p><strong>小程序API接口Promise化并且把需要登录的调用接口模块化</strong></p>
<p>1、安装插件。请先查看<a href="https://developers.weixin.qq.com/miniprogram/dev/devtools/npm.html" target="_blank" ref="nofollow noopener noreferrer">npm支持</a>文档。</p><pre class="brush:js;toolbar:false;">npm install --save miniprogram-api-promise</pre><p>2、在微信开发者工具右方详情中勾选使用npm模块,并在菜单栏工具中点击构建npm。</p>
<p>3、初始化代码。</p><pre class="brush:js;toolbar:false;">// 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,
});</pre><p>4、改写login.js代码</p><pre class="brush:js;toolbar:false;">// 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;</pre><p>5、调用代码</p><pre class="brush:html;toolbar:false;"><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></pre><pre class="brush:js;toolbar:false;">// 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)
},
});</pre><p>【相关学习推荐:<a href="https://www.php.cn/xiaochengxu.html" target="_blank">小程序开发教程</a>】</p>
|