【愚公系列】2022年09月 微信小程序-小程序登录功能完整流程

举报
愚公搬代码 发表于 2022/09/30 22:40:25 2022/09/30
【摘要】 前言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

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。