作者

一、简介

本次教程是基于之前的扫码购物项目的后台,实现后台登录注册接口认证。

二、实战步骤

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: false
            }
        }, 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
defaultValue:new Date()  #修改时间
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: {id: cart.id}}).then(cart => {
                    cart.increment('number');
                    res.json({success: true, message: '添加成功'})
                })
            }
            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.jsusers.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

defaultValue:new Date()  #修改时间

修改时间类型后执行迁移:sequelize db:migrate

2、根据需求,定义关联关系,两个模型都要写关联 在model文件夹中创建order.jsorder_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)
})

转载请注明,来自https://itfun.tv/news/155