微信的openid,前端登录时,会发送一个code码到后端,后端接收到这个code,然后配合appid、appsecret参数,像wx发送一个get请求。

wx的请求地址:GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code

具体查看文档:微信官方文档-服务端

可以看到这段get请求有四个参数:

属性类型默认值必填说明
appidstring 小程序 appId
secretstring 小程序 appSecret
js_codestring 登录时获取的 code
grant_typestring 授权类型,此处只需填写 authorization_code

js_code就时前端发送给后端的code值。

那么我们怎么快速的拼接好这个get请求地址呢?

我们可以使用node的util api,他有一个format方法,其中有一个占位符%s

format接收多个参数,第一个参数为需要改变的值,后面为改变的参数,比如:

const util = require("util");

const url = "http://www.%s.com";

console.log(util.format(url,"baidu"));
//http://www.baidu.com

大概是这么一个效果,而且这个是可以多个的,比如有两个%s占位符,那么url后面再传入两个参数,这两个参数按顺序对应占位符。

为了方便使用,我们将wx的设置放在config文件里面

config.js:

module.exports = {
  //dev、prod
  env: "dev",
  //数据库配置
  database: {
    dbName: "koa-test", //数据库名
    host: "localhost", //数据库地址
    port: 3306, //数据库端口
    user: "root", //数据库用户名
    password: "123456", //数据库密码
  },
  //token配置
  tokenConfig: {
    key: "askdhjasgdhjasghgu&^%^^%&**2164.as21s8",  //加密用的key
    //token过期时间,数字为s,字符则需要加上d(天),h(小时),否则默认为ms
    expiresIn: "2h",
  },
  // 微信小程序配置
  wx: {
    appid: "",
    appsecret: "",
    loginUrl: "https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code"
  }
};

这样wx的基本配置就有了。

分层

事实上我们的业务逻辑是不建议写在api里面的,一般是将它分层出去,这里我们也将微信小程序分层出去,在项目根目录创建services文件夹,里面存放分层出去的业务代码,然后我们创建一个wx.js文件,内容如下:

wx.js:

const util = require("util");
const axios = require("axios");
const { wx } = require("@config/config");
const { AuthFailed } = require("@core/http-error");
const { User } = require("@models/user");
const { createToken } = require("@core/token");
const { UserLevel } = require("@lib/enum");

class WXManager {
  static async condeToToken(code) {
    const url = util.format(wx.loginUrl, wx.appid, wx.appsecret, code);

    const result = await axios.get(url);
    if (result.status !== 200) {
      throw new AuthFailed("openid获取失败");
    }

    if (result.data.errcode !== 0) {
      throw new AuthFailed(`openid获取失败:${result.data.errcode} ${result.data.errmsg}`);
    }

    let user = await User.getUserByOpenid(result.data.openid);
    //不存在就创建
    if (!user) {
      user = await User.registerByOpenid(result.data.openid);
    }

    //创建token
    return await createToken(user.id, UserLevel.USER);
  }
}

module.exports = {
  WXManager
}

result.status不等于200说明请求失败

result.data.errcode不等于0,说明wx那边检测参数没有达到要求

这两个我们需要进行报错。

成功的话,result.data.openid是有值的。

这个时候我们就需要判断数据库里面有没有这个人的openid,毕竟他有可能已经注册过了,那么我们就直接返回这个人的user数据表信息,如果没有,我们则需要创建,然后再返回user表信息。

最终我们则需要创建一个token,将user.id传入,不建议使用openid,因为openid很危险,如果被盗取就很麻烦,每个用户针对每个小程序只有一个唯一的openid,所以,这里使用的是我们数据的自增id。

然后就是用户权限了,USER完事。

由于我们需要判断openid是否存在,以及创建用户,那么我们就需要用到user模型,于是在user模型里面,我们加上两个方法:

user.js:

const bcrypt = require("bcryptjs");
const { sequelize } = require("@core/db");
const { Sequelize, Model, DataTypes } = require("sequelize");
const { AuthFailed } = require("@core/http-error");

class User extends Model {
  static async verifyEmailPassword(email, password) {
    //查找对应email用户
    const user = await User.findOne({
      where: { email }
    });
    if (!user) throw new AuthFailed("账号不存在!");
    //判断密码是否正确
    const correct = bcrypt.compareSync(password, user.password);
    if (!correct) throw new AuthFailed("密码不正确!");
    //存在
    return user;
  }
  //查找数据库是否存在openid
  static async getUserByOpenid(openid) {
    const user = await User.findOne({
      where: {
        openid
      }
    });
    return user;
  }
  //创建openid用户
  static async registerByOpenid(openid) {
    const user = await User.create({
      openid
    });
    return user;
  }
};

User.init({
  id: {
    type: DataTypes.INTEGER,
    primaryKey: true, //主键
    autoIncrement: true, //自增
  },
  nickname: DataTypes.STRING,
  email: {
    type: DataTypes.STRING(128),
    unique: true, //唯一
  },
  password: {
    type: DataTypes.STRING,
    set(val) {
      const salt = bcrypt.genSaltSync(10);
      const psw = bcrypt.hashSync(val, salt);
      this.setDataValue('password', psw);
    }
  },
  openid: {
    type: DataTypes.STRING(64),
    unique: true, //唯一
  },
}, {
  sequelize,
  tableName: "user", //指定生成的表名
});


module.exports = {
  User
}

于是乎,wx登录的业务代码我们拆分好了,现在去登录api那使用:

const { WXManager } = require("@services/wx");

//登录
router.post("/login", async (ctx) => {
  const { account, password, type } =
    await loginValidate(ctx.request.body);
  //根据登录类型进行登录
  let token;
  switch (type) {
    case LoginType.USER_EMAIL:
      // 邮箱+密码
      token = await emailLogin(account, password);
      break;
    case LoginType.USER_MINI_PROGRAM:
      // 小程序
      token = await WXManager.condeToToken(account);
      break;
    case LoginType.USER_MOBILE:
      // 手机
      break;
    default:
      throw new NotFound("没有相应的处理函数!")
  }

  //成功
  throw new Success("登录成功!", { token });
  // console.log(LoginType.isThisType(ctx.request.body.type))

});

这里我对Success错误对象加了个数据,就是多了一个属性data,具体就不多写了,很简单。

然后我们登录,就可以拿到token值。

分类: Node 标签: nodekoa微信登录openid

评论

暂无评论数据

暂无评论数据

目录