1.新增用户表
新增一个名为bidu
的数据库,并增加一张用户表users
,其中id
为主动递增。请留神字段的数据类型,不要选错了。
2.配置常量
(1).env
在server
根目录,新建一个文件server/.env
,并配置jwt的密钥和过期工夫,数据库信息,以及微信小程序的相干信息。(注:.env文件不要同步git,外面放的都是一些敏感数据)
# .env# jwt 密钥,任意字符串JWT_TOKEN_SECRET=ZM_j3@faF93Mdaie2f# jwt 生成的 token 的过期工夫,单位秒JWT_TOKEN_EXPIRATION_TIME=3600# 微信小程序 appidWECHAT_MINI_APP_ID=wx1f196182a0f906e7# 微信小程序 secretWECHAT_MINI_APP_SECRET=74d89************47c9f66aaeab# mysql 数据库配置MYSQL_HOST=localhostMYSQL_USER=rootMYSQL_PASSWORD=12345678MYSQL_DATABASE=biduMYSQL_PORT=3306MYSQL_CHARSET=utf8mb4_unicode_ci
(2)constants.js
在后盾根目录里,新建一个server/constants/constants.js
文件,用来寄存常量。增加微信小程序相干信息
/* constants.js */const WEAPP = { APP_ID: process.env.WECHAT_MINI_APP_ID, APP_SECRET: process.env.WECHAT_MINI_APP_SECRET}module.exports = { WEAPP}
3.微信小程序登录
1.创立server/utils/WXBizDataCrypt.js
,用于微信解密
/* WXBizDataCrypt.js */var cryptoWx = require('crypto')function WXBizDataCrypt(appId, sessionKey) { // @ts-ignore this.appId = appId // @ts-ignore this.sessionKey = sessionKey}WXBizDataCrypt.prototype.decryptData = function (encryptedData, iv) { // base64 decode var sessionKey = Buffer.from(this.sessionKey, 'base64') encryptedData = Buffer.from(encryptedData, 'base64') iv = Buffer.from(iv, 'base64') try { // 解密 var decipher = cryptoWx.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') } if (decoded.watermark.appid !== this.appId) { throw new Error('Illegal Buffer') } return decoded}module.exports = WXBizDataCrypt
2.创立server/utils/jwt.js
,用于jwt验证和token生成
/* jwt.js */const mysql = require('access-db')const jwt = require('jsonwebtoken')const genToken = (uid) => { const jwt = require('jsonwebtoken') const token = jwt.sign({id: uid}, process.env.JWT_TOKEN_SECRET, {expiresIn: parseInt(process.env.JWT_TOKEN_EXPIRATION_TIME || '0')}) return token}const authUse = async (req, res, next) => { if(!req.headers.authorization){ res.send(401, '用户未登录') } const raw = req.headers.authorization.split(' ').pop() if(raw === 'Bearer'){ res.send(401, '用户未登录') } const {id} = jwt.verify(raw, process.env.JWT_TOKEN_SECRET) req.user = (await mysql.get('users', id)).data next()}module.exports = { genToken, authUse}
- getToken: 获取通过用户id生成的token,
expiresIn
为token的过期工夫(秒) - authUse: 判断用户是否登录的中间件,并将用户信息写入到
req.user
,方便使用。
3.创立server/utils/utils.js
,工具
/* utils.js */// 获取以后工夫 2021-4-7 16:25:21 const getTime = (type, stamp) => { let nowTime = stamp ? new Date(stamp) : new Date() let Y = nowTime.getFullYear() let M = nowTime.getMonth() + 1 < 10 ? '0' + (nowTime.getMonth() + 1) : (nowTime.getMonth() + 1) let D = nowTime.getDate() < 10 ? '0' + nowTime.getDate() : nowTime.getDate() let h = nowTime.getHours() < 10 ? '0' + nowTime.getHours() : nowTime.getHours() let m = nowTime.getMinutes() < 10 ? '0' + nowTime.getMinutes() : nowTime.getMinutes() let s = nowTime.getSeconds() < 10 ? '0' + nowTime.getSeconds() : nowTime.getSeconds() switch (type) { case 'date': return `${Y}-${M}-${D}` case 'date_time': return `${Y}-${M}-${D} ${h}:${m}:${s}` case 'stamp': return nowTime.getTime() default: return nowTime.getTime() }}module.exports = { getTime}
4.创立server/routes/login.js
,登录接口
/* login.js */const express = require('express')const {mysql} = require('access-db')const axios = require('axios')const {WEAPP} = require('../constants/constants')const {getTime} = require('../utils/utils')const {genToken} = require('../utils/jwt')const WXBizDataCrypt = require('../utils/WXBizDataCrypt')const loginRouter = express.Router()// 仅获取sessionkeyloginRouter.post('/wechat_session_key', async function(req, res, next) { try{ let {code} = req.body let sessionRes = await axios({ url: 'https://api.weixin.qq.com/sns/jscode2session', params: { appid: WEAPP.APP_ID, secret: WEAPP.APP_SECRET, js_code: code, grant_type: 'authorization_code', } }) res.json({ session_key: sessionRes.data.session_key, openid: sessionRes.data.openid }) }catch(err){ res.status(500).send(err) }})// 小程序受权登录loginRouter.post('/wechat', async function(req, res, next) { let {code, userInfo} = req.body if(!userInfo){ userInfo = { nickName: null, avatarUrl: null, } } let sessionRes = await axios({ url: 'https://api.weixin.qq.com/sns/jscode2session', params: { appid: WEAPP.APP_ID, secret: WEAPP.APP_SECRET, js_code: code, grant_type: 'authorization_code', } }) // 如果小程序绑定了微信开放平台,则也会返回unionid let userRes = await mysql.find('users', { p0: ['openid', '=', sessionRes.data.openid], r: 'p0' }) let nowTime = getTime('date_time') let resUser = {} if(userRes.data.objects.length === 0){ //没有,新增用户 let setRes = await mysql.set('users', { nickname: userInfo.nickName, avatar: userInfo.avatarUrl, openid: sessionRes.data.openid, created_at: nowTime, updated_at: nowTime, }) if(setRes.data.insertId){ let getRes = await mysql.get('users', setRes.data.insertId) resUser = { ...getRes.data, session_key: sessionRes.data.session_key, token: genToken(setRes.data.insertId) } } }else{ //有用户,更新根本信息 if(userInfo.avatarUrl){ let updateRes = await mysql.update('users', userRes.data.objects[0].id, { nickname: userInfo.nickName, avatar: userInfo.avatarUrl, updated_at: nowTime, }) } let getRes = await mysql.get('users', userRes.data.objects[0].id) resUser = { ...getRes.data, session_key: sessionRes.data.session_key, token: genToken(userRes.data.objects[0].id) } } res.json(resUser)})// 小程序获取手机号loginRouter.post('/wechat_phone', async function(req, res, next) { let {openid, sessionKey, iv, encryptedData} = req.body var pc = new WXBizDataCrypt(WEAPP.APP_ID, sessionKey) var data = pc.decryptData(encryptedData , iv) let userList = (await mysql.find('users', { p0: ['openid', '=', openid], r: 'p0' })).data.objects let nowTime = getTime('date_time') let resUser = {} if(userList.length === 0){ //没有,新增用户 let id = (await mysql.set('users', { phone: data.phoneNumber, created_at: nowTime, updated_at: nowTime, openid: openid, })).data.insertId if(id){ resUser = (await mysql.get('users', id)).data } }else{ //有用户,更新根本信息 if(userList[0].phone != data.phoneNumber){ await mysql.update('users', userList[0].id, { phone: data.phoneNumber, updated_at: nowTime, }) } resUser = (await mysql.get('users', userList[0].id)).data } res.json(resUser)})module.exports = loginRouter
并在server/app.js
里,引入,
/* app.js */...var indexRouter = require('./routes/index');var usersRouter = require('./routes/users');var loginRouter = require('./routes/login');...app.use('/', indexRouter);app.use('/users', usersRouter);app.use('/login', loginRouter)
5.在mini/app.js
外面,新增接口配置
/* app.js */... globalData: { userInfo: null, }, config: { api: 'http://localhost:3000', // 接口根底地址 file: 'http://localhost:3000', // 文件根底地址 }...
6.批改小程序home
页面,如下
<button bindtap="login"> 登录 </button>
const app = getApp()Page({ data: { userInfo: {}, hasUserInfo: false, }, ... login() { // 举荐应用wx.getUserProfile获取用户信息,开发者每次通过该接口获取用户个人信息均需用户确认 // 开发者妥善保存用户疾速填写的头像昵称,防止反复弹窗 let that = this wx.getUserProfile({ desc: '用于欠缺会员资料', success: ({userInfo}) => { wx.login({ success ({code}) { if (code) { wx.request({ url: app.config.api + '/login/wechat', method: 'POST', data: { code: code, userInfo: { nickName: userInfo.nickName, avatarUrl: userInfo.avatarUrl, } }, success: ({data}) => { console.log('登录胜利:', data) wx.setStorageSync('TOKEN', data.token) that.setData({ userInfo: data, hasUserInfo: true }) } }) } } }) } }) }, ...})
登录胜利后,将用户的登录信息打印进去,并将token
保留在本地。
7.测试
启动服务器: 在server
目录下,运行命令npm run start
。
点击小程序外面的登录
按钮,此时就会登录胜利。且后盾也有了用户openid
信息。
demo地址