微信小程序静默登录流程(等待登录完成方可请求业务),request网络请求封装,上锁避免重复login,区分测试环境(体验版)

如有错误或不好的地方,感谢指正

小程序登录

官方文档是本文的基础

为什么有这个

  1. 开发小程序时是否用户每次进入小程序都重新wx.login请求更新token?

当我刚接触做微信小程序的时候我这样做过,后端同样是这种方式,但显然根据官方的意思这是不合理的,不然你压根用不着checkSession,而且这样是有问题的

因此,才想把这一方面的东西记录下来,虽然现在没做小程序的项目,但也简单实现一下,以便自己以后用得着

  1. 关于静默登录的调用时机

当初始页面请求需要身份认证时,也许会遇到还没登录完成,token还没拿到的情况,这是小程序生命周期以及请求异步产生的。

这带来很多问题,比如你可能需要在页面onLoad监测token延时请求,但这样是麻烦的,所以我希望在request上再封装一层,

设置了当login 上锁,并且避免了多个请求都触发login的问题,但这样请求没办法拦截。这里想到了维护一个单队列或许能很好解决

  1. 如何在微信小程序根据不同环境配置不同的域名?

这里使用了wx.getAccountInfoSync().miniProgram 对应区分开发,体验(测试)、生产环境

gitee上的demo可以直接在此下载运行,但建议你从下面代码中选择你需要的,因为,我也不知道有没有bug。

以下是所有代码,都有详细注释

network目录

token认证请配合utils.login.js & app.js=>onLaunch中关于登录相关代码使用 ,当然也可以根据业务自行修改 ,其他均为项目自动生成文件

目前仅有wx.request

wx.downloadFile wx.uploadFile 等未在涉及范围内

  1. 适用token认证方式,静默登录

  2. 默认request接口传参 identity 区分该请求是否需要token认证

  3. 默认后端返回格式为 { code: xxx, message: xxx, result: xxx }, code,message,result字段名可根据业务在config内修改,如有很大差异,这可能需要你在逻辑代码中调整

  4. 为达到不同环境切换不同域名的目的,采用 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:{}
  }
})

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值