校园二手交易平台-后端初始化

89 阅读7分钟

初始化后端

mkdir beetle-master

cd beetle-master

运行npm init,按照提示填写信息,初始化项目。这将生成一个package.json文件,用于管理项目依赖和配置。

安装依赖

npm install body-parser express jsonwebtoken mysql2 nodemailer sequelize

  • express: Web应用框架
  • mysql2: 适用于Node.js的MySQL客户端,专注于性能优化。
  • sequelize:是一个易于使用且基于承诺的Node.js ORM 工具,适用于PostgresMySQLMariaDBSQLiteDB2Microsoft SQL ServerSnowflake
  • nodemailer: Nodemailer是一个简单易用的Node.js邮件发送组件
  • body-parser: 非常常用的一个express 中间件,作用是对post请求的请求体进行解析
  • jsonwebtoken 生成和验证JSON Web Token(JWT)

搭建服务器

现在我们要创建文件夹config/config.js配置文件

/**
 * @description 服务器配置
 */
exports.serverOptions = {
  host: "http://127.0.0.1", // 本地开发
  port: 10001, // 端口 10001

  // host: '服务器地址', // 阿里云服务器
  // port: 3000, // 端口 3000

  baseUrl: "/static/file/", // 伪造路径
};

**
 * @description 随机昵称
 */
exports.nickNameOptions = [
  "彩虹",
  "白云",
  "森林",
  "蓝天",
  "大海",
  "领悟",
  "实诚",
  "飞鸟",
  "老鹰",
  "白兔",
  "绿竹",
];

/**
 * @description 加盐配置, 用于加强加密
 */
exports.saltOptions = {
  pwd: "loser", // 密码加盐
};

/**
 * @description 数据库配置
 */
exports.mysqlOptions = {
  database: "server_db", // 数据库名称
  username: "root", // 用户名
  password: "root", // 登录密码
  host: "localhost", // 数据库地址
  dialect: "mysql", // 数据库类型
  timezone: "+08:00", // 时区
  underscored: true, // 字段以_命名
  forceRemove: false, // 是否强制删除表
};

/**
 * @description 邮件配置, 验证码
 */
exports.emailOptions = {
  host: "smtp.qq.com", // 邮件服务器地址
  port: 465, // 端口, 25端口在阿里云服务器被禁止的, 建议使用465
  secure: true, // 如果端口为465, 此项需要设置true, 其他端口需要修改为false
  user: "xxxx@qq.com", // 用户名,发件地址
  pass: "xxxxxx", // 邮箱授权码
  expires: 5 * 60 * 1000, // 验证码有效时间, 单位:毫秒
};

/**
 * @description 允许请求(白名单)
 */
exports.hostOptions = [
  "http://127.0.0.1:3000",
  "http://127.0.0.1:8080",
  "http://127.0.0.1:8081",
  "http://127.0.0.1:8082",
  "http://127.0.0.1:9000",
  "http://localhost:3000",
  "http://localhost:8080",
  "http://localhost:8081",
  "http://localhost:8082",
  "http://localhost:9000",
];

/**
 * @description 验证验证码请求路径
 */
exports.codeUrlOptions = ["/register"];

/**
 * @description token配置
 */
exports.tokenOptions = {
  salt: "_t_k", // token加盐
  expires: "1d", // 有效时间
  tokenUrls: [
    "/getUserInfo",
    "/postProduct",
    "/search",
    "/count",
    "/updown",
    "/probyid",
    "/updateProduct",
    "/remove",
    "/like",
    "/removeLike",
    "/findLike",
    "/likeCount",
    "/addShopcart",
    "/removeShopcart",
    "/shopcartProducts",
    "/shopcartCount",
  ], // 需要验证token的请求路径
};

/**
 * @description 文件存放配置
 */
exports.filePathOptions = {
  falsePath: "/static/file", // 伪路径
  imgPath: "/upload", // 图片存储路径
};

现在创建db/connct.js 连接数据库

// 导入sequelize, 并且解构Sequelize
let { Sequelize } = require("sequelize");

// 连接MySQL数据库  config是全局变量
let sequelize = new Sequelize(
  config.mysqlOptions.database,
  config.mysqlOptions.username,
  config.mysqlOptions.password,
  {
    // 数据库地址
    host: config.mysqlOptions.host,

    // 数据库类型
    dialect: config.mysqlOptions.dialect,

    // 时区
    timezone: config.mysqlOptions.timezone,

    // 字段命名 以 _ 分隔
    define: {
      underscored: config.mysqlOptions.underscored,
    },
  }
);

// 导出实例,以便后续创建模型需要使用该连接实例
module.exports = sequelize;

现在创建db/model/model.js文件进行导入数据模型


/**
 * @description 导出所有模型
 */
module.exports = {};

创建routes_controller.js路由控制器

// 导入sequelize的Op模块
let { Op } = require("sequelize");

  /**
   * @description holle word 
   * @param {object} req 请求体
   * @param {object} res 响应体
   */
  home(req, res) {
    res.send("hello word")
  }
}

// 导出实例
module.exports = new RoutesController();

创建router/router.js文件


// 导入路由控制器
let routesController = require(__basename +
  "/routes_controller/routes_controller.js");

// 导出路由函数
module.exports = (app) => {
  //首页
  app.post("/", routesController.home);
};

创建一个名为index.js的文件并写入代码

// global: nodejs全局变量对象
global.__basename = __dirname;

// 导入express
let express = require("express");

// 导入body-parser 中间件
let bodyParser = require("body-parser")

// 导入config.js
global.config = require(__basename + "/config/config.js");

// 导出connect.js, 连接mysql数据库
global.sequelize = require(__basename + "/db/connect.js");

// 导入model.js ==> 所有模型, 全局挂载model,以便后面操作数据
global.model = require(__basename + "/db/model/model.js");

// 导入路由
let routes = require(__basename + "/routes/routes.js");

// 创建实例
let app = express();

// 解析post请求体
// 将post请求参数解析为json
// limit限制解析post请求体大小 5MB
app.use(
  bodyParser.json({
    limit: 5 * 1024 + "kb",
  })
);

// extended:false接收任何数据格式, true接收字符串或者数组
app.use(
  bodyParser.urlencoded({
    extended: false,
    limit: 5 * 1024 + "kb",
  })
);

/ 设置CORS, 跨域处理
// CORS 跨域资源共享
// app.all(*)表示所有请求路径必须经过
app.all("*", (req, res, next) => {
  // 允许跨域地址
  // res.header('Access-Control-Allow-Origin', '跨域地址')

  // *表示允许所有域请求, 但是不允许跨域携带cookie, 在实际开发中,一般指定允许某个域请求,如上面设置
  // res.header('Access-Control-Allow-Origin', '*')

  // 动态允许域名请求,这样可以允许跨域携带cookie
  res.header("Access-Control-Allow-Origin", req.headers.origin);

  // 如果浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。
  res.header("Access-Control-Allow-Headers", "X-Requested-With,token");

  // 该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。
  res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");

  // 该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可
  res.header("Access-Control-Allow-Credentials", true);

  // 将请求传递给下一个中间件或者路由
  next();
});

// 加载所有路由
routes(app);

// 监听端口
app.listen(config.serverOptions.port, () => {});

设置白名单

在router.js里面设置

 // 请求域拦截(白名单)
  app.use(routesController.verfiyHost);

  // 验证码拦截(白名单)
  app.use(routesController.verifyCode);

  // 验证登录(白名单)
  app.use(routesController.verifyToken);

接下来我们到路由控制器里面写verfiyHost,verifyCode,verifyToken三个方法

实现verifyCode方法

  /**
   * @description 请求域拦截
   * @param {object} req 请求体
   * @param {object} res 返回体
   * @param {function} next 下一步
   */
  verfiyHost(req, res, next) {
    if (config.hostOptions.indexOf(req.headers.origin) === -1) {
      return res.send({ msg: "请求域不合法", status: 1020 });
    }
    next();
  }

实现verifyCode方法

创建code验证码实体

let { DataTypes, Model } = require("sequelize");

class Code extends Model {}

/**
 * @description 定义模型结构, 数据表结构
 */
Code.init(
  {
    id: {
      type: DataTypes.INTEGER.UNSIGNED,
      autoIncrement: true,
      primaryKey: true,
      allowNull: false,
      comment: "表id",
    },

    codeId: {
      type: DataTypes.STRING(30),
      allowNull: false,

      // 默认值
      defaultValue: "",
      comment: "验证码id",
    },

    code: {
      type: DataTypes.STRING(6),
      allowNull: false,
      defaultValue: "",
      comment: "验证码",
    },

    email: {
      type: DataTypes.STRING(40),
      allowNull: false,
      defaultValue: "",
      comment: "邮箱",
    },
  },
  {
    // 指定连接实例,这样才知道在指定数据库创建code表
    sequelize,

    // 模型名称, 当没有指定表名时,sequelize推断名称为模型名称的复数 ==> codes作为表名
    modelName: "code",

    // 不推断,直接使用模型作为表名 ==> code作为表名
    freezeTableName: true,

    // 指定表名
    tableName: "code",
  }
);

// 同步数据库表
// force: true ==> 删除原有code表,新建code表
// force: false ==> 如果code存在,则不创建,反之,不创建code表
Code.sync({ force: config.mysqlOptions.forceRemove });

module.exports = Code;

在model.js中导入code实体

// 导入验证码模型
let Code = require(__basename + "/db/model/code.js");

/**
 * @description 导出所有模型
 */
module.exports = {
  Code,
};

创建api.js

// 导入sequelize模块
const { QueryTypes } = require("sequelize");

/**
 * @description 数据库API接口
 * @class API
 */
class API {
 
    /**
   * @description 查询数据库表数据
   * @param {{ modelName: string, condition: object, attributes: array }} o o.modelName: 模型名称, 			    * o.condition: 查询条件, o.attributes: 查询字段 ['a', 'b'] 或者 具有别名 ['a', ['b', 'b别名']]
   * @returns {object}
   */
  findData(o) {
    return model[o.modelName].findAll({
      where: o.condition,
      attributes: o.attributes,
    });
  }

}

module.exports = new API();

在路由控制器里面实现

/**
   * @description 验证码拦截
   * @param {object} req 请求体
   * @param {object} res 返回体
   * @param {function} next 下一步
   */
  verifyCode(req, res, next) {
    // 验证验证码
    let url = req.url.split("?")[0];
    if (config.codeUrlOptions.includes(url)) {
      api
        .findData({
          modelName: "Code",
          condition: {
            codeId: req.body.codeId,
          },
        })
        .then((result) => {
          // 获取当前时间和验证码有效时间差
          let time = new Date().getTime() - config.emailOptions.expires;

          // 获取验证码保存时间
          let codeTime = new Date(result[0].dataValues.createdAt).getTime();

          // 如果验证码保存时间 >= time
          let isPass =
            req.body.validcode == result[0].dataValues.code &&
            req.body.email == result[0].dataValues.email &&
            codeTime >= time;

          // 如果验证通过,则将请求传递给下一个中间件或者路由
          isPass ? next() : res.send({ msg: "验证码错误", status: 1031 });
        })
        .catch((err) => {
          console.log("verifyCode err ==>", err);
          res.send({ msg: "验证码错误", status: 1031 });
        });
    } else {
      next();
    }
  }

实现verifyToken方法

创建utils.js方法

// 导入crypto, 用于加密字符串, nodejs核心模块
let crypto = require("crypto");

// 导入jsonwebtoken, 用于登录验证
let jsonwebtoken = require("jsonwebtoken");

// 导入文件系统模块, nodejs核心模块
let fs = require("fs");

// 导入nodemailer, 用于发邮件
let nodemailer = require("nodemailer");

// 发邮件配置
// 创建发邮件实例
let transporter = nodemailer.createTransport({
  // 服务器地址
  host: config.emailOptions.host,

  // 端口, 25端口在阿里云服务器被禁止的, 建议使用465,
  port: config.emailOptions.port,

  // 如果端口为465,此项需要设置true, 其他端口需要修改为false
  secure: config.emailOptions.secure,

  // 授权认证
  auth: {
    // 用户邮箱地址
    user: config.emailOptions.user,

    // 授权码(不是邮箱登录密码)
    pass: config.emailOptions.pass,
  },
});

/**
 * @description 公共方法
 * @class Utils
 */
class Utils {
  /**
   * @description 解析token
   * @param {string} token
   */
  verifyToken(token) {
    return new Promise((resolve, reject) => {
      jsonwebtoken.verify(token, config.tokenOptions.salt, (err, info) => {
        // 如果验证失败
        if (err) {
          reject(err);
        } else {
          resolve(info);
        }
      });
    });
  }

  /**
   * @description 将cookie字符串转成对象
   * @param {string} cookie cookie字符串
   */
  transformCookie(cookie) {
    // 按照; 切割
    let cookieObj = {};
    let cookies = cookie.split("; ");
    cookies.map((v) => {
      let c = v.split("=");
      cookieObj[c[0]] = c[1];
    });
    return cookieObj;
  }

  
}

module.exports = new Utils();

在路由控制器里面导入utils.js

// 导入utils, 调用公共方法
let utils = require(__basename + "/utils/utils.js");

/**
   * @description 验证Token(验证登录)
   * @param {object} req 请求体
   * @param {object} res 响应体
   * @param {function} next 下一步
   */
  verifyToken(req, res, next) {
    // 需要验证token
    let url = req.url.split("?")[0];
    if (config.tokenOptions.tokenUrls.indexOf(url) > -1) {
      if (!req.headers.token) {
        res.send({ msg: "请先登录", status: 1034 });
      } else {
        let cookie = utils.transformCookie(req.headers.token);
        let token = [cookie.mama12, cookie.nana20, cookie.mama20].join(".");

        // 验证token
        utils
          .verifyToken(token)
          .then((result) => {
            // 将userId传递
            req.userId = result.data;
            next();
          })
          .catch((err) => {
            console.log("verifyToken err ==>", err);
            res.send({ msg: "请先登录", status: 1034 });
          });
      }
    } else {
      next();
    }
  }