从零开始,搭建一个简单的购物平台(一):https://blog.csdn.net/time_____/article/details/105191286
项目源码(持续更新):https://gitee.com/DieHunter/myCode/tree/master/shopping
上一篇文章将项目主体以及插件模块搭建完成,这篇文章目的主要搭建服务端token验证及登录功能
服务端:
文件目录结构
入口文件为server.js,简单配置一下,先让他能运行起来
const express = require("express"); const app = express(); app.listen(1024, () => { console.log("Server Start~"); });
使用终端cd到server.js目录,输入node server运行入口文件,显示Server Start~表示运行成功,之后引入一些模块
const express = require("express"); const app = express(); const routes = require("./routes/routes"); const cors = require("cors"); //引入cors模块(解决跨域问题) const path = require("path"); app.use(cors()); app.all("*", function (req, res, next) { //设置允许跨域的域名,*代表允许任意域名跨域 res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "content-type"); //允许的header类型 res.header("Access-Control-Allow-Methods", "DELETE,PUT,POST,GET,OPTIONS"); //跨域允许的请求方式 next(); //是否继续向下执行 }); let bodyParser = require("body-parser"); //post传输数据类型 app.use( bodyParser.urlencoded({ extended: false, }) ); new routes(app);//初始化路由 app.use(bodyParser.json()); app.use("/public", express.static(path.join(__dirname, "./public")));//静态目录 app.listen(1024, () => { console.log("Server Start~"); });
全部设置完之后进行下一步,配置路由与token验证
在utils.js文件中新建一个静态类,并且引入jwt(jsonwebtoken),在config.js文件中新建config静态类用于存放配置变量(接口名,公钥秘钥,常量),此外还需用到crypto进行数据加解密,Bcrypt密码盐加密
utils.js
const jwt = require("jsonwebtoken"); const config = require("../config/config"); const cryptoJS = require("crypto-js");//用来加密解密前端参数 let { UserKey, AdminKey,CryptoKey} = config;//token加密关键字,秘钥 let key = cryptoJS.enc.Utf8.parse(CryptoKey);//生成16进制秘钥 module.exports = class Utils { static parseUrl(req, res) {//获取前端传递的参数 return req.method == "POST" ? req.body : this.urlSplit(req.url); } static urlSplit(url) {//get获取的参数解析 let list = url.split("?")[1].split("&"); let leng = list.length; let obj = {}; for (let i = 0; i < leng; i++) { let key = list[i].split("=")[0]; let val = list[i].split("=")[1]; obj[key] = val; } return obj; } /* * @param {string} type 'user'||'admin' 用户类型 * @param {string} user 用户名 * @param {bool} rempsd 是否记住密码 */ static createToken = (type, user, rempsd) => {//生成token,用户登录时调用 let payload = { user: user, }; return jwt.sign(payload, type == "admin" ? AdminKey : UserKey, { expiresIn: rempsd ? "3d" : "6h", }); }; /* * @param {object} req 前端请求对象 * @param {object} res 服务端接收对象 * @param {fn} next 中间件响应方法 */ static checkToken = (req, res, next) => { let _data = this.parseUrl(req, res); //解析前端参数 if (_data.crypto) { _data = this.getCrypto(_data.crypto); //对前端参数解密 } let isUser = true; //用户 let isAdmin = true; //管理员 let _decoded = ""; //加密的用户名 jwt.verify(_data.token, UserKey, function (err, decoded) { if (err) { isUser = false; } else { _decoded = decoded; } }); jwt.verify(_data.token, AdminKey, function (err, decoded) { if (err) { isAdmin = false; } else { _decoded = decoded; } }); if (isUser || isAdmin) { _data.id = _decoded; _data.userTokenType = isAdmin ? "admin" : "user"; res._data = _data; next(); //中间件响应 } else { res.send({ result: -999, msg: "登录超时,请重新登录", }); } }; /* Crypto加密方法 * @param {object} _data 对用户请求后端的参数进行加密 */ static setCrypto(_data) { let encrypted = cryptoJS.AES.encrypt(JSON.stringify(_data), key, { mode: cryptoJS.mode.ECB, padding: cryptoJS.pad.Pkcs7, }); return encrypted.toString(); } /* Crypto解密方法 * @param {string} _token 将秘文解密成对象形式 */ static getCrypto(_token) { _token = decodeURIComponent(_token); //前端传参有特殊字符(中文)时转义(替换百分号) let decrypt = cryptoJS.AES.decrypt(_token, key, { mode: cryptoJS.mode.ECB, padding: cryptoJS.pad.Pkcs7, }); return JSON.parse(cryptoJS.enc.Utf8.stringify(decrypt).toString()); } static createBcrypt(password) {//加密密码 return bcrypt.hashSync(password, bcrypt.genSaltSync(10)); } static checkBcrypt(_password, _hash) {//对比密码 return bcrypt.compareSync(_password, _hash); } }
配置完成utils后进行路由配置,在routes文件夹下新建routes.js文件,并使用token验证用户是否正确
const Util = require("../utils/utils"); const Config = require("../config/config"); module.exports = class Route { constructor(app) { app.get(Config.ServerApi.checkToken, Util.checkToken, (req, res) => { res.send({ result: 1, msg: "验证成功", data: res._data }); }); } };
配置config.js
module.exports = { Agreement: "http://",//协议 DataBaseUrl: "127.0.0.1",//ip或域名 DataBasePort: ":27017",//数据库端口 DataBaseName: "shopping",//数据库文档名称 ServerUrl: "", ServerPort: ":1024",//服务端请求端口 Path: "/",//路由名 UserKey: "user",//用户token加密标识 AdminKey: "admin",//管理员token加密标识 CryptoKey: "tokenkey",//Crypto加密关键字,用于生成16进制秘钥 ServerApi: {//接口名称 checkToken: "/checkToken",//token验证 userLogin: "/userLogin",//用户登录 } }
随后,新建用户管理路由接口,但是在此之前,我们需要配置一下数据库
1.下载Robo3t,并安装
2.新建数据库,这里我新建的数据库名称是Shopping
3.新建数据库表,这里我新建的是users表
连接数据库
1.在model.js中新建数据库连接(原理观察者模式,类似与socket监听自定义事件)
const mongoose = require('mongoose'); const config = require('../../config/config') module.exports = class Mongoose { constructor() { mongoose.connect(`mongodb://${config.DataBaseUrl+config.DataBasePort+config.Path+config.DataBaseName}`, { useNewUrlParser: true }); this.db = mongoose.connection; this.db.on("error", function (error) { console.log("Err:" + error); }); this.db.on("open", function () { console.log("Connet Success~"); }); this.db.on('disconnected', function () { console.log('Connet Stop~'); }); return mongoose } }
2.新建数据库Schema用于连接数据表,这里我们直接将配置项剥离到config.js文件
const _mongoose = require('./model'); let mongoose = new _mongoose() const _schema = mongoose.Schema; module.exports = class Schema { constructor(config) { let schema = new _schema(config.data); let Model = mongoose.model(config.modelName, schema); //新建数据库 return Model } }
3.config.js中新增用户表默认字段配置
Collections: { Users: { modelName: "users", data: { headPic: {//头像 type: String, required: false, default: "public/assets/img/default.gif" }, userType: {//用户类型(管理员/用户) type: String, required: true, }, username: {//用户名 type: String, required: true, }, password: {//密码 type: String, required: true }, sex: {//性别 type: String, required: true }, mailaddress: {//邮箱地址 type: String, required: true }, mailurl: {//邮箱类型 type: String, required: true }, alladdress: {//省市县 type: Array, required: false, default: [] }, address: {//具体地址 type: String, required: false, default: '' }, descript: {//个人说明 type: String, required: false, default: '' }, time: {//注册时间 type: String, required: true }, isactive: {//是否冻结用户 type: Boolean, default: true } } } },
以上是配置,token验证及登录的后端代码
总结
将项目搭建成类似于mvc的设计思想很有必要,数据模型和控制层的剥离,可以使代码思路清晰,复用性以及可维护性提升