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
# 微信小程序 appid
WECHAT_MINI_APP_ID=wx1f196182a0f906e7
# 微信小程序 secret
WECHAT_MINI_APP_SECRET=74d89************47c9f66aaeab
# mysql 数据库配置
MYSQL_HOST=localhost
MYSQL_USER=root
MYSQL_PASSWORD=12345678
MYSQL_DATABASE=bidu
MYSQL_PORT=3306
MYSQL_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()
// 仅获取sessionkey
loginRouter.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
页面,如下
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
信息。