Express实现微信小程序授权登录

2,569 阅读3分钟

项目搭建

  1. 快速初始化Express项目
npm i -g express-generator
express demo -e  //指定模板引擎ejs
cd demo
npm install

//全局安装 sequelize cli工具
npm install sequelize-cli -g
//安装sequelize 和 mysql2依赖
npm  i sequelize mysql2 -S

// 初始化models
sequelize init
/**
* 将会新建以下四个文件夹
* config ---数据库配置文件
* models ---数据库模型
* migrations ---数据库迁移文件
* seeders ---种子文件(测试数据)
*/
  • 配置数据库
{
  "development": {
    "username": "root",
    "password": "123456",
    "database": "mina_dev",
    "host": "127.0.0.1",
    "dialect": "mysql",
    "timezone": "+8:00"
  },
  "production": {
    "username": "root",
    "password": "123456",
    "database": "mina_prod",
    "host": "127.0.0.1",
    "dialect": "mysql",
    "timezone": "+8:00"
  }
}

//在mysql新建mina_dev和mina_prod
  1. 目录划分
  • 根目录新增了service和utils,主要用来实现结构分层和工具类
  1. 新建users模型
sequelize model:generate --name User --attributes  nickName:string,avatarUrl:string,openid:string,gender:integer,city:string,province:string

修改migrations/xxxx-user.js,添加unique键

...
openid: {
    type: Sequelize.STRING,
    allowNull: false,
    unique: true
},

主要逻辑

正式开始主要逻辑代码的编写

  1. 统一返回值类编写
  • utils目录下新建ResultVo.js
*ResultVo.js

class ResultVo{
 static success(data){
   return {
     code: 0,
     msg: 'success',
     data
   }
 }

 static successNull(){
   return {
     code: 0,
     msg: 'success'
   }
 }

 static fail(code, msg){
   return {
     code,
     msg
   }
 }
}

module.exports = ResultVo;

  1. 实现微信开放数据解密
// WXBizDataCrypt.js可在微信开发文档下载,部分Buffer api已过期,做简单修改
var crypto = require('crypto')

function WXBizDataCrypt(appId, sessionKey) {
 this.appId = appId
 this.sessionKey = sessionKey
}

WXBizDataCrypt.prototype.decryptData = function (encryptedData, iv) {
 // base64 decode
 var sessionKey = new Buffer.from(this.sessionKey, 'base64')
 encryptedData = new Buffer.from(encryptedData, 'base64')
 iv = new Buffer.from(iv, 'base64')

 try {
    // 解密
   var decipher = crypto.createDecipheriv('aes-128-cbc', sessionKey, iv)
   // 设置自动 padding 为 true,删除填充补位
   decipher.setAutoPadding(true)
   var decoded = decipher.update(encryptedData, 'binary', 'utf8')
   decoded += decipher.final('utf8')
   
   decoded = JSON.parse(decoded)

 } catch (err) {
   throw new Error('Illegal Buffer',err)
 }

 if (decoded.watermark.appid !== this.appId) {
   throw new Error('Illegal Buffer')
 }

 return decoded
}

module.exports = WXBizDataCrypt
  1. 简单封装获取openId的api
// 安装 axios请求微信开发者服务器
npm i axios -S
mkdir GetOpenid.js

const axios = require('axios')
class GetOpenid{
 static async requestWeChat(appid,secret,code){
   const url = `https://api.weixin.qq.com/sns/jscode2session?appid=${appid}&secret=${secret}&js_code=${code}&grant_type=authorization_code`
   const res = await axios.default.get(url);
   return res.data
 }
}

module.exports = GetOpenid
  1. 登录凭证校验。通过 wx.login 接口获得临时登录凭证 code 后传到开发者服务器调用此接口完成登录流程。
GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code
  • 用户信息需要微信服务器传回的session_key及前端传入的encrytedData和iv
  1. 新建config文件,配置小程序信息
config.js

const mina = {
  appid: 'xxxxxxxxxx',
  secret: 'xxxxxxxxxxxxxxx',
  tokenSecret: 'aaaaaa'
}
module.exports = mina;
  1. 创建UserService.js
  • routes及部分逻辑共同充当controller角色, service层单独编写方便复用
const models = require('../models')
const ResultVo = require('../utils/ResultVo')
const GetOpenid = require('../utils/GetOpenid')
const mina = require('../config/mina')
const WXBizDataCrypt = require('../utils/WXBizDataCrypt')

class UserService {

  static async save(data) {
    //获取openid和session_key
    const secretData = await GetOpenid.requestWeChat(mina.appid, mina.secret, data.code);
    //数据解密
    const pc = new WXBizDataCrypt(mina.appid, secretData.session_key)
    const resData = pc.decryptData(data.encryptedData, data.iv)
    //查询或创建数据 返回数组[object, boolean]
    const res = await models.User.findOrCreate({
      where: {
        openid: resData.openId
      },
      attributes: {
        exclude: ['openid', 'createdAt', 'updatedAt']
      }
    });
    return ResultVo.success(res[0])
  }
}

module.exports = UserService;
  1. 配置路由
const express = require('express');
const router = express.Router();
const UserService = require('../service/UserService')

/* GET users listing. */
router.get('/', function(req, res, next) {
  res.send('respond with a resource');
});

router.post('/save', async (req, res) => {
  const body = req.body;
  const response = await UserService.save(body);
  res.json(response)
})

module.exports = router;

至此服务端api编写完成 小程序端比较简单

  • user.wxml
<view class="info" wx:if="{{!hasUserInfo && canIUse}}">
      <view class="avator">
        <image class="avator-image" src="../../assets/images/mine_pic_avator.png" mode="aspectFit"></image>
      </view>
      <button class="nickName" open-type="getUserInfo" bindgetuserinfo="getUserInfo">点击登录/注册</button>
    </view>
  • user.js
getUserInfo(e) {
    wx.login({
      success:(res) => {
        if (res.code){
          const data = e.detail;
          wx.request({
            method: 'POST',
            url: 'http://127.0.0.1:3000/users/save',
            data: {
              code: res.code,
              encryptedData: data.encryptedData,
              iv: data.iv
            },
            success:(response) => {
              if (response.data.code === 0){
                this.setData({
                  userInfo: e.detail.userInfo,
                  hasUserInfo: true
                });
                wx.setStorageSync('token', response.data.data.token)
              }
            }
          })
        }
      }
    });
  }

写在最后

菜鸟前端学习之路,如果有不合理的地方请大佬指正!