【愚公系列】2022年09月 微信小程序-小程序登录功能完整流程
【摘要】 前言wx.getUserProfile(Object object)获取用户信息。页面产生点击事件(例如 button 上 bindtap 的回调中)后才可调用,每次请求都会弹出授权窗口,用户同意后返回 userInfo。该接口用于替换 wx.getUserInfo。wx.checkSession(Object object)检查登录态是否过期。 通过 wx.login 接口获得的用户登录...
前言
- wx.getUserProfile(Object object)
获取用户信息。页面产生点击事件(例如 button 上 bindtap 的回调中)后才可调用,每次请求都会弹出授权窗口,用户同意后返回 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)等。用户数据的加解密通讯需要依赖会话密钥完成。
一、小程序登录面板的封装
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;
2.小程序端登录代码实现
<!--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>
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 login();
}
if (!args.header) args.header = {};
args.header["Authorization"] = `Bearer ${token}`;
return wxp.request(args).catch(console.error);
};
// app.js
App({
wxp:wxp,
});
// 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)
},
});
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)