本次教程是基于之前的扫码购物项目的后台,实现后台登录注册接口认证。
1、创建数据表(Users)
注:Windows电脑自行手动创建,并添加数据
sequelize model:generate --name User --attributes username:string,password:string,openid:string,admin:boolean
sequelize db:migrate #运行迁移
手动往表里面添加一个用户
2、配置路由,在app.js
文件中,屏蔽之前的usersRouter
,添加如下代码:
var usersRouter = require('./routes/admin/users');
***
app.use('/admin/users', usersRouter);
3、安装接口认证包文件,参考文档:https://github.com/auth0/express-jwt#readme
cnpm install express-jwt --save
var jwt = require('express-jwt'); #app.js中引入
4、在routes/admin
文件夹中创建users.js
文件,里面写上登录接口代码:
var models = require('../../models');
var jwt = require('jsonwebtoken');
**********************************
//登录接口
router.post('/login', function (req, res, next) {
var username = req.body.username
var password = req.body.password
if (!username || !password) {
res.json({success: false, message: '用户名或密码错误!'})
return;
}
models.User.findOne({
where: {
username: username,
password: password,
}
}).then(user => {
if (!user) {
res.json({success: false, message: '用户名或密码错误!'})
}
var token = jwt.sign({ user: user }, '123123');
res.json({
success: true,
message: '请求成功',
token: token
})
})
});
module.exports = router;
解析:里面的jwt.sign
方法,参考文档:https://github.com/auth0/node-jsonwebtoken
测试:打开postman
,做如下测试,若得到token
即为成功!
5、接下来,在app.js中,添加如下代码:
app.use(express.static(path.join(__dirname, 'public'))); #此行代码以自带
app.use(jwt({secret: '123123'}));
然后postman
中访问商品分类接口,应该会报错,如图:
接下来,在该接口中,带上如下参数,即可访问。
authorization Bearer空格token
如图所示:
后面所有接口测试中都要带上此请求头才能拿到数据。
6、接下来获取接口认证成功的用户id
,在商品分类接口首页中,修改代码如下:
// 所有分类
router.get('/', function (req, res, next) {
// models.Category.findAll({order: [['id', 'DESC']]}).then(categories => {
// res.json({categories: categories});
// })
res.json(req.user); #打印用户信息
});
postman
测试,如图
那么,获取用户id
的写法应该就是
res.json(req.user.user.id);
此写法明显不优雅,后面的代码继续优化!
7、由于目前密码在数据表中存的是明文的,不安全,所以需要对密码进行加密处理。参考文档:https://github.com/dcodeIO/bcrypt.js#readme
cnpm install bcryptjs --save #安装密码加密包
var bcrypt = require('bcryptjs'); #在`users.js`中引入
然后在users.js
中增加注册接口,代码如下:
router.post('/register', function (req, res, next) {
var username = req.body.username
var password = req.body.password
var check_password = req.body.check_password
if (!username || !password) {
res.json({success: false, message: '用户名或密码必填!'})
return;
}
if(check_password != password){
res.json({success: false, message: '两次密码输入不一致!'})
return;
}
models.User.findOne({
where: {
username: username,
}
}).then(user => {
if (user) {
res.json({success: false, message: '用户名已注册!'})
return;
}
password = bcrypt.hashSync(password, 8);
// res.json({password: password})
models.User.create({
username: username,
password: password,
admin: true
}).then((user) => {
res.json({
success: true,
message: '请求成功',
user: user
})
});
})
});
postman
中测试注册接口,提示失败,如图所示:
出现此问题是因为,登录和注册不需要经过接口认证,所以要排除这个两个路由。在app.js
中,修改代码如下:
//后台登录接口认证
app.use(jwt({secret: '123123'}).unless({
path: [
'/admin/users/login',
'/admin/users/register'
]
}));
postman
再次访问,如图:
接下来,换个新账号测试,看是否能注册成功。
至此,注册接口已完成,并实现了密码加密。
最后来实现登录接口密码验证的问题,修改登录接口代码,并在postman
中测试:
//登录接口
router.post('/login', function (req, res, next) {
var username = req.body.username
var password = req.body.password
if (!username || !password) {
res.json({success: false, message: '用户名或密码错误!'})
return;
}
models.User.findOne({
where: {
username: username,
}
}).then(user => {
if (!user) {
res.json({success: false, message: '用户名不存在!'})
return;
}
if(!bcrypt.compareSync(password, user.password)){
res.json({success: false, message: '密码错误!'})
return;
}
var token = jwt.sign({
user:{
id: user.id,
username: username,
admin: true
}
}, process.env.SECRET, {expiresIn: 60 * 60 * 24 * 7});
res.json({
success: true,
message: '请求成功',
token: token
})
})
});
8、把SECRET
配置到公共文件,参考文档:https://github.com/motdotla/dotenv#readme
cnpm install dotenv --save
require('dotenv').config() #在`app.js`中引入
在项目文件夹创建.env
文件,里面做如下配置
SECRET=123123 #注:秘钥要复杂一点,我这里写的简单只为演示。
最后把app.js
和登录接口中的123123
修改为process.env.SECRET
。
至此,接口认证的全部过程已完成!
总结:首先通过express-jwt
包获取token
,然后,所有请求接口都必须带上此token
才能拿到数据,并获取用户id
后台通过登录注册接口,实现对应功能。
修改app.js
中的后端路由,如下:
//后台接口路由
var adminCategoriesRouter = require('./routes/admin/categories');
var adminProductsRouter = require('./routes/admin/products');
var adminPhotosRouter = require('./routes/admin/photos');
var adminUsersRouter = require('./routes/admin/users');
//注册后台接口路由
app.use('/admin/categories', adminCategoriesRouter);
app.use('/admin/products', adminProductsRouter);
app.use('/admin/photos', adminPhotosRouter);
app.use('/admin/users', adminUsersRouter);
一、创建购物车表
sequelize model:generate --name Cart --attributes productId:integer,userId:integer,number:integer
sequelize db:migrate #运行迁移文件
二、小程序所需接口。
在app.js
中添加路由
var cartsRouter = require('./routes/carts');
******************************************
app.use('/carts', cartsRouter);
接下来,屏蔽掉购物车路由:
//后台登录接口认证
app.use(jwt({secret: process.env.SECRET}).unless({
path: [
'/admin/users/login',
'/admin/users/register',
'/carts',
]
}));
在routes
文件夹下创建carts.js
文件
1、扫码加入购物车接口,前端传商品条形码code
var express = require('express');
var router = express.Router();
var models = require('../models');
router.post('/', function (req, res, next) {
var code = req.body.code;
models.Product.findOne({
where: {
code: code,
}
}).then(product => {
// res.json(product);return;
if (!product) {
res.json({success: false, message: '此商品不存在!'})
return;
}
models.Cart.findOrCreate({
where: {
productId: product.id,
userId: 4
},
defaults: {
number: 1,
userId: 4,
productId: product.id
}
}).spread((cart, created) => { // spread 把数组转换成对象,方便下面的取值
// res.json(!created);return;
if (!created) { //如果为fasle,则说明carts表里面已经有了该商品,只需增加它的数量
models.Cart.findOne({where: {productId: product.id}}).then(cart => {
return cart.increment('number');
}).then(cart => {
return cart.reload();
}).then(cart => {
res.json({success: true, message: '添加成功', data: cart})
})
return;
}
res.json({success: true, message: '添加购物车成功', data: cart})
})
})
});
module.exports = router;
在models/cart.js
中,定义关联关系,如下:
'use strict';
module.exports = (sequelize, DataTypes) => {
const Cart = sequelize.define('Cart', {
productId: DataTypes.INTEGER,
userId: DataTypes.INTEGER,
number: DataTypes.INTEGER
}, {});
Cart.associate = function(models) {
// associations can be defined here
models.Cart.belongsTo(models.Product);
models.Cart.belongsTo(models.User);
};
return Cart;
};
2、购物车首页,关联查出当前用户的商品信息
router.get('/', function (req, res, next) {
models.Cart.findAll({
include: [
models.Product,
],
where: {userId: 8}
}).then(cart => {
// console.log(cart)
let total_price = 0;
let number = 0;
cart.map(item => {
total_price += item.number * item.Product.price;
number += item.number;
});
res.json({
success: true, message: '查询成功',
data: cart,
total: total_price,
number: number,
})
})
});
3、购物车数量加减,前端传增减type
和购物车的id
router.put('/', function (req, res, next) {
let type = req.body.type;
let cart_id = req.body.cart_id;
models.Cart.findByPk(cart_id).then(cart => {
if (type === 'inc') {
cart.increment('number')
return res.json({success: true, message: '修改成功'})
}
if (cart.number > 1) {
cart.decrement('number')
return res.json({success: true, message: '修改成功'})
}
cart.destroy()
res.json({success: true, message: '删除成功'})
})
});
4、清空购物车,关联到用户,清空当前用户的购物车。认证成功后传userId。
router.delete('/', function (req, res, next) {
models.Cart.destroy({
include: [models.User],
where: {userId: 8}
}).then(() => {
res.json({success: true, message: '清空成功'})
})
});
三、获取小程序用户id
上面的接口虽然写完了,但是用户的id
是固定的,接下来获取用户id
。由于数据库设计的是前端用户和后台管理员共用一张表。所以我们通过users
表的admin
字段来区分当前用户到底是前端用户还是后台管理员。
接下来,在app.js
中修改中间件代码如下:
app.use(function (req, res, next) {
//不需要验证的URL
var allowUrl = ['/admin/users/login', '/admin/users/register', '/users/login'];
if (allowUrl.indexOf(req.url) != '-1') return next(); //js的indexOf函数:如果没有找到匹配的字符串则返回 -1
var token; //需要验证的URL则带上token
if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
token = req.headers.authorization.split(' ')[1];
} else if (req.query && req.query.token) {
token = req.query.token;
}
//此写法参考文档:https://github.com/auth0/express-jwt#readme 中`Multi-tenancy`的上一段。
//token可能存在post请求和get请求
if (!token) {
return res.status(401).send({
success: false,
message: '当前接口需要认证才能访问.'
});
}
//验证token是否正确
jwt.verify(token, process.env.SECRET, function (err, decoded) {
if (err) {
return res.status(401).send({
success: false,
message: 'token过期,请重新登录'
});
}
//用正则验证匹配是否是后台管理员
var reg = /\/admin/
if(reg.test(req.url) && !decoded.user.admin){
return res.status(401).send({
success: false,
message: '当前接口是管理员接口'
});
}
//将解析出来的数据存入req
req.decoded = decoded;
next();
})
})
四、创建小程序用户登录接口
在app.js
中打开之前屏蔽的用户路由
var usersRouter = require('./routes/users');
******************************************
app.use('/users', usersRouter);
接下来,在routes
文件夹下的users.js
文件中添加如下代码:
var express = require('express');
var router = express.Router();
var models = require('../models');
var request = require('request');
var jwt = require('jsonwebtoken');
//小程序登录接口,前端传code过来,最终要获取前端用户登录的token。微信开发者打开小程序,找到app.js的wx.login方法,里面console.log(res.code),然后通过postman把code传过来做接口测试
router.post('/login', function (req, res, next) {
// console.log(req.query)
var code = req.body.code
request.get({
uri: 'https://api.weixin.qq.com/sns/jscode2session',
json: true,
qs: {
grant_type: 'authorization_code',
appid: 'wx4a9965771e11b4bd',
secret: 'e94f1c12cb09e31bff0f12826f945b60',
js_code: code
}
}, async (err, response, data) => {
// res.json(data.openid);return;
if (response.statusCode != 200) {
return res.json(err)
}
let user = await models.User.findOne({
where: {openid: data.openid}
})
if (!user) {
user = await models.User.create({openid: data.openid, admin: 0})
}
var token = jwt.sign({
user: {
id: user.id,
openid: data.openid,
admin: false
},
}, process.env.SECRET, {expiresIn: 60 * 60 * 24 * 7});
res.json({success: true, message: '登录成功', token: token})
})
})
module.exports = router;
终端安装request
包
cnpm i request --S
重新刷新前端小程序把code
传过来,打开上面的调试代码,最后postman
中测试。看能否获取到openid
。如图所示:
接下来关闭调试,把获取到的openid
存入users
表中,如图:
当admin
字段值为0的时候,代表是前端用户。
接下来,修改carts.js
接口中的用户id
,把之前固定的数字8改成req.decoded.user.id
即可。最后postman
测试会报错,说jwt
未定义,在app.js
和users.js
中引入:
cnpm install jsonwebtoken --S
// 在app.js中屏蔽之前的jwt引入,并引入jsonwebtoken
// var jwt = require('express-jwt');
var jwt = require('jsonwebtoken');
然后小程序重新获取code
,通过postman
测试获取前端用户登录的token
。如图:
测试前端购物车接口,如图:
最后测试后端接口是否还能用。修改routes/admin/users.js
里面的admin: false
改成admin: true
。后台登录接口重新生成token
,去访问后台分类接口地址,数据正常即可。
后台用户获取方式:
res.json(req.decoded.user);
1、创建订单表和订单商品表
sequelize model:generate --name Order --attributes userId:integer,status:integer,out_trade_no:string
sequelize model:generate --name Order_product --attributes productId:integer,orderId:integer,number:integer
修改时间类型后执行迁移:sequelize db:migrate
2、根据需求,定义关联关系,两个模型都要写关联
在model
文件夹中创建order.js
和order_product.js
,分别写上如下关联关系:
models.Order.hasMany(models.Order_product);
models.Order.belongsTo(models.User);
******************************************
models.Order_product.belongsTo(models.Product);
models.Order_product.belongsTo(models.Order);
3、在app.js
中添加订单接口路由
var ordersRouter = require('./routes/orders');
******************************************
app.use('/orders', ordersRouter);
4、结算即下单接口,点击结算需往orders
表和order_products
表插入数据
var express = require('express');
var router = express.Router();
var models = require('../models');
// 结算即下单接口,点击结算需往orders表和order_products表插入数据
router.post('/', async function (req, res, next) {
let user_id = req.decoded.user.id
// 查询该用户购物车的商品
let carts = await models.Cart.findAll({include: [models.Product], where: {userId: user_id}})
if (carts.length == 0) {
return res.json({success: false, message: '请添加商品在提交订单'})
}
let num = new Date().getTime() + Math.floor(Math.random() * 100) + user_id; //生成随机订单号
let order = await models.Order.create({out_trade_no: num, status: 1, userId: user_id})
let Order_product = carts.map(item => {
return {
productId: item.productId,
number: item.number,
orderId: order.id
}
})
await models.Order_product.bulkCreate(Order_product)
// 删除购物车表
// await models.Cart.destroy({
// where: {userId: user_id}
// });
res.json({
success: true,
message: '请求成功',
orderId: order.id
})
})
module.exports = router;
5、我的订单接口,根据前端传过来的订单id
去查出当前用户的订单和商品信息
router.get('/', function(req, res, next) {
var id = req.query.id;
// res.json(id);return;
models.Order.findOne({
include: {
model: models.Order_product,
include: [{
model: models.Product,
}]
}, where: {id: id}
}).then(order => {
let total = 0;
order.Order_products.forEach(item => {
total += item.number * parseFloat(item.Product.price)
})
res.json({success: true, message: '请求成功', order, total})
})
});
接下来,我们来实现前端微信小程序登录功能,微信开发者工具打开小程序项目,在app.js
中传入code
并获取token
保存。
// 登录
wx.login({
success: res => {
// 发送 res.code 到后台换取 openId, sessionKey, unionId
// console.log(res.code)
wx.request({
url: 'http://10.0.0.42:3000/users/login',
method: 'POST',
data: {
code: res.code
},
success: res => {
// console.log(res);return;
wx.setStorageSync('token', 'Bearer ' + res.data.token)
}
})
}
}),
查看终端,看到token
即为成功!最后自行完成扫码加入购物车、购物车列表、数量增减、结算等一系列操作。
接下来安装基于支持node.js
的微信支付依赖包,参考文档:https://github.com/befinal/node-tenpay
cnpm i tenpay -S //安装
const tenpay = require('tenpay'); //引入
6、微信支付接口,前端需传订单号
router.post('/pay', async function (req, res, next) {
let out_trade_no = req.body.out_trade_no
// 根据订单号去查当前订单
let order = await models.Order.findOne({
where: {
out_trade_no: out_trade_no
}
});
//获取总价
let Order_products = await models.Order_product.findAll({
where: {
orderId: order.id
},
include: [models.Product],
});
let total = 0;
Order_products.forEach(item => {
total += parseFloat(item.Product.price) * item.number
})
const config = {
appid: 'wx4a9965771e11b4bd', //公众号ID
mchid: '1230390602', //微信商户号
partnerKey: 'phpwh56fgdhdghjtyeq3luiughjfeft3', //微信支付安全密钥
notify_url: 'http://localhost:3000/notify', //支付回调网址
};
const api = new tenpay(config, true);
//获取微信JSSDK支付参数
let result = await api.getPayParams({
out_trade_no: out_trade_no, //商户内部订单号
body: '沃尔玛商城', //商品简单描述
total_fee: total * 100, //订单金额(分)
openid: req.decoded.user.openid //付款用户的openid
});
res.json(result)
})