koa教程2 微信openid登录
微信的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请求有四个参数:
属性 | 类型 | 默认值 | 必填 | 说明 |
---|---|---|---|---|
appid | string | 是 | 小程序 appId | |
secret | string | 是 | 小程序 appSecret | |
js_code | string | 是 | 登录时获取的 code | |
grant_type | string | 是 | 授权类型,此处只需填写 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值。
本文系作者 @木灵鱼儿 原创发布在木灵鱼儿站点。未经许可,禁止转载。
暂无评论数据