微信小程序静默登录流程等待登录完成方可请求业务,request网络请求封装,上锁避免重复login,区分测试环境体验版
目录
微信小程序静默登录流程(等待登录完成方可请求业务),request网络请求封装,上锁避免重复login,区分测试环境(体验版)
如有错误或不好的地方,感谢指正
官方文档是本文的基础
为什么有这个
- 开发小程序时是否用户每次进入小程序都重新wx.login请求更新token?
当我刚接触做微信小程序的时候我这样做过,后端同样是这种方式,但显然根据官方的意思这是不合理的,不然你压根用不着checkSession,而且这样是有问题的
因此,才想把这一方面的东西记录下来,虽然现在没做小程序的项目,但也简单实现一下,以便自己以后用得着
- 关于静默登录的调用时机
当初始页面请求需要身份认证时,也许会遇到还没登录完成,token还没拿到的情况,这是小程序生命周期以及请求异步产生的。
这带来很多问题,比如你可能需要在页面onLoad监测token延时请求,但这样是麻烦的,所以我希望在request上再封装一层,
设置了当login 上锁,并且避免了多个请求都触发login的问题,但这样请求没办法拦截。这里想到了维护一个单队列或许能很好解决
- 如何在微信小程序根据不同环境配置不同的域名?
这里使用了wx.getAccountInfoSync().miniProgram 对应区分开发,体验(测试)、生产环境
可以直接在此下载运行,但建议你从下面代码中选择你需要的,因为,我也不知道有没有bug。
以下是所有代码,都有详细注释
network目录
token认证请配合utils.login.js & app.js=>onLaunch中关于登录相关代码使用 ,当然也可以根据业务自行修改 ,其他均为项目自动生成文件
目前仅有wx.request
wx.downloadFile wx.uploadFile 等未在涉及范围内
- 适用token认证方式,静默登录
- 默认request接口传参 identity 区分该请求是否需要token认证
- 默认后端返回格式为 { code: xxx, message: xxx, result: xxx } , code,message,result字段名可根据业务在config内修改,如有很大差异,这可能需要你在逻辑代码中调整
- 为达到不同环境切换不同域名的目的,采用 wx.getAccountInfoSync().miniProgram 以此区分开发版,体验版(用作测试版本),正式版
/network/config.js 主要的配置
const { envVersion } = wx.getAccountInfoSync().miniProgram;
// **请根据业务修改**
const domainOptions = {
develop: "http://127.0.0.1:3000", //开发版
trial: "", //体验版
release: "", //正式版
};
export const DOMAIN = domainOptions[envVersion]; //不同环境的域名
// **请根据业务修改**
// 后端响应字段
export const resCode = "code"; //对应业务逻辑自定的状态码 如果是request http请求响应的状态码处理逻辑,请修改为 statusCode
// export const resMes = "message";//对应业务逻辑自定的提示
export const resResult = "result"; //对应业务逻辑自定的数据结果
export const DELAYED = 200; //每次等待登录的延时时间,毫秒
export const Authorization = "Authorization"; //token命名,请求header以及Storage中的名字
export const CONTENT_TYPE = "application/json"; //application/x-www-form-urlencoded
export const TIMEOUT = 12000; //超时
export const HTTP_STATUS = {
SUCCESS: 20000, //成功,也可以是一个数组 **请根据业务修改** 必填
CLIENT_ERROR: 40000, //客户端错误
AUTHENTICATE: 40100, //默认401为token验证失效 **请根据业务修改** 必填
FORBIDDEN: 40003, //禁止,无权限
NOT_FOUND: 40004, //无请求地址
SERVER_ERROR: 50000, //服务器错误
BAD_GATEWAY: 50002, //网关问题
SERVICE_UNAVAILABLE: 50003, //服务不可用
GATEWAY_TIMEOUT: 50004, //超时
};
/network/index.js 所有请求接口将在这里维护
import { request } from "./request";
/**
*
* @param method 以GET,POST为例,需要其他请求方法的自行修改
* @param data 一个json对象参数
* @param header 自定的header,与默认重复的key将覆盖默认的header设置
*/
//登录接口,根据业务替换为真实的请求路径 默认参数传递 为 {code:wxCode} 如有必要可在utils/login.js => refreshLogin 方法中更改
export const login = (data,header) => request({method:'POST',url:'/login', identity:false, data,header});
//GET
export const testGet = (data,header) => request({method:'GET',url:'/test', identity:true, data,header});
//testGet2
export const testGet2 = (data,header) => request({method:'GET',url:'/test2', identity:true, data,header});
//tokenInvalid
export const tokenInvalid = (data,header) => request({method:'GET',url:'/tokenInvalid', identity:true, data,header});
/network/request.js request请求相关逻辑
import {
DOMAIN,
TIMEOUT,
CONTENT_TYPE,
HTTP_STATUS,
DELAYED,
Authorization,
resCode,
resResult,
} from "./config";
import { refreshLogin } from "../utils/login.js";
export const request = (params) => {
return new Promise((resolve, reject) => {
//request更多配置参考文档 https://developers.weixin.qq.com/miniprogram/dev/api/network/request/wx.request.html
let { method, url, identity, data = {}, header = {} } = params;
let token = wx.getStorageSync(Authorization);
//LOGIN_SWITCH不在此判断 是因为请求异步 除非维护一个请求队列 否则 无法避免 所以 折中选择token失效后再重新请求
const app = getApp();
const FIRST_RUN = app.globalData.FIRST_RUN; // 小程序首次启动,等待checklogin
if (identity && (!token || FIRST_RUN)) {
//首次进入需要认证并且当前无登录状态时延时请求,默认200毫秒等待 小程序初始化的登录
setTimeout(() => {
resolve(request(params));
}, DELAYED);
return;
}
let defaultHeader = {
[Authorization]: token,
"content-type": CONTENT_TYPE, // 默认值
};
wx.request({
method, //请求方式
url: /^http(s?):\/\//.test(url) ? url : DOMAIN + url, //当传入url带协议时,将不加配置项的域名
data, //请求参数
header: Object.assign(defaultHeader, header), //相同header设置将覆盖默认
timeout: TIMEOUT, //设置超时
success(res) {
// 默认对请求响应的res.data中后端自行定义的code处理,如你需要 request http请求响应的状态码处理逻辑,需要自行修改
const code = res.data[resCode];
// 如果响应状态码在正确范围内,直接响应数据
if (
(Array.isArray(HTTP_STATUS.SUCCESS) &&
HTTP_STATUS.SUCCESS.indexOf(code) != -1) ||
code === HTTP_STATUS.SUCCESS
) {
resolve(res.data[resResult]);
return;
} else if (code === HTTP_STATUS.AUTHENTICATE) {
//当提示token失效时
const app = getApp();
const LOGIN_SWITCH = app.globalData.LOGIN_SWITCH; // login锁,确保没有登录冲突
if (LOGIN_SWITCH) {
//如果已锁说明有 在重新登录 只需要等待一下 继续请求
console.log(1231);
setTimeout(() => {
resolve(request(params));
}, DELAYED);
} else {
//重新登录并继续请求
refreshLogin().then(() => {
resolve(request(params));
});
}
return;
}
//更多状态码处理在这处理 建议success都返回res.data 以便未来需要在业务逻辑处理不同状态,当然,你也可以 非20000 就直接reject
reject(res.data);
},
fail(err) {
wx.showToast({
title: "网络出错啦!",
icon: "error",
duration: 3000,
});
reject(err);
},
});
});
};
server 目录
server/index.js 写的时候用到的一些模拟响应的接口,依赖express
const express = require('express')
const app = express()
let num = 0;
app.use(express.urlencoded({ extended: false }))
app.use(express.json())
app.get('/test', function (req, res) {
res.json({ code: 20000, message: "ok", result: 'get响应数据' })
})
app.get('/test2', function (req, res) {
res.json({ code: 20000, message: "ok", result: 'get响应数据2' })
})
app.get('/tokenInvalid', function (req, res) {
if (req.headers.token === '2222222222') {
res.json({ code: 40001, message: "token失效,请重新登录", result: '' })
} else {
res.json({ code: 20000, message: "ok", result: '成功拿到' })
}
})
app.post('/login', function (req, res) {
res.json({ code: 20000, message: "ok", result: '1111111111' })
// res.json({ code: 20000, message: "ok", result: '2222222222' })
})
// 监听端口
app.listen(8888)
utils 原文件夹新增了一个login.js
utils/login.js 登录逻辑写在这里了
/**
* 登录相关 **仅做参考** 如有必要请自行修改
* 遵循官方给出的 wx.login 的最佳实践
* 尽可能以 session_key 有效期作为自身登录态有效期
* 这里默认后端缓存sesion_key,每次进入小程序前端会监测是否过期, 重新登录后端就可以刷新sesion_key
*/
import { Authorization } from "../network/config";
import { login } from "../network/index.js";
/**
* 用户登录逻辑,首次进入小程序或任意时刻重新登录
*/
export const userLogin = () => {
return new Promise((resolve, reject) => {
// 检查登录状态 成功的话就用久的token
checkLogin()
.then((token) => {
console.log("old_token ====", token);
resolve(token);
})
.catch(async (err) => {
// 失败就重新获取登录凭证 总之都会返回一个token
try {
const token = await refreshLogin();
console.log("new_token:", token);
resolve(token);
} catch (err) {
reject(err);
}
});
});
};
/**
* 用户进入小程序静默登录前监测登录态
*/
export const checkLogin = () => {
return new Promise((resolve, reject) => {
// wxapi直接判断是否过期
wx.checkSession({
success() {
//session_key 未过期,并且在本生命周期内将一直有效
// session 成功但 token 不存在时,这种情况基本不会出现
const token = wx.getStorageSync(Authorization);
if (token) {
resolve(token);
} else {
reject("token_err");
}
},
fail() {
// session_key 已经失效,不管有没有token都需要重新执行登录流程
reject("session_key_err");
},
});
});
};
/**
* 重新登录======token不存在或过期 session_key失效时
*/
export const refreshLogin = () => {
const app = getApp();
app.globalData.LOGIN_SWITCH = true;
wx.removeStorageSync(Authorization);
return new Promise(async (resolve, reject) => {
try {
// 获取到微信code
const code = await getWxCode();
// 请求login接口,把code给服务端进行微信登录
login({ code })
.then((data) => {
console.log("重新登录", data);
// 拿到token存到storage
const { token } = data;
wx.setStorageSync(Authorization, token);
// 登录锁解掉
app.globalData.LOGIN_SWITCH = false;
resolve(token);
})
.catch((err) => {
reject(err);
});
} catch (err) {
reject(err);
}
});
};
/**
* 获取 小程序的code
*/
export const getWxCode = () => {
return new Promise((resolve, reject) => {
wx.login({
success(res) {
if (res.code) {
// 把微信code返回
resolve(res.code);
} else {
wx.showToast({
title: "登录失败,请重新进入小程序",
icon: "none",
duration: 3000,
});
}
},
});
});
};
app.js
/app.js
// app.js
// app.js
import {userLogin } from "./utils/login";
import {getUserInfo } from './network/index.js';
App({
onLaunch() {
// login start
this.globalData.FIRST_RUN = true;
userLogin().then((token)=>{
console.log('登录流程结束')
//如果需要获取用户信息等 可以选择在此处理 或 修改utils/login.js的逻辑
// dosometion
getUserInfo().then((result)=>{
this.globalData.userInfo = result;
})
}).catch((err)=>[
]).finally(()=>{
this.globalData.FIRST_RUN = false;
})
// login end
/* 官方生成代码
// 展示本地存储能力
const logs = wx.getStorageSync('logs') || []
logs.unshift(Date.now())
wx.setStorageSync('logs', logs)
// 登录
wx.login({
success: res => {
// 发送 res.code 到后台换取 openId, sessionKey, unionId
}
})
*/
},
globalData: {
FIRST_RUN:false,
LOGIN_SWITCH: false,
userInfo: null,
BASE_URL:"http://media.top/",
userInfo:{}
}
})