express+mysql 搭建服务端(上篇)

2,077 阅读2分钟

为啥选择express呢,是因为蛤,koa比较迷你,微内核,拓展性强,所以一些web框架例如阿里的eggjs就是基于koa。而express集成了路由和static中间件所以显得重一些(本猴只用过express)。

好了,介绍下之后会用到的技术栈:

  1. express
  2. mysql
  3. sequelize
  4. jsonwebtoken 和 express-jwt(jwt)
  5. node-schedule(定时任务)

项目目录:

    ├── bin/                           // 服务配置文件
    ├── db/                            // mysql 配置以及表的配置
    ├── controllers/                   // 模块存放地
    ├── public/                        // 静态资源(存放用于上传静态)
    ├── routes/                        // 路由配置文件
    ├── servers/                       // 数据库数据处理文件
    ├── tool/                          // 工具函数
    ├── app.js                         // 服务器配置文件
    └── package.json

接下来涉及的环境有mysql,没有的小伙伴,之前写过一篇环境配置的篇章,mysql环境配置以及搭配 sequelize 使用,我们就拿这个篇章的初始代码来敲了。

安装所需依赖包

    npm i sequelize boom express-jwt express-validator jsonwebtoken multer -S

在db目录下创建两个表的描述js文件:

// user
const Sequelize = require('sequelize')
const db = require('./index') // 引入刚刚写的sequelize配置

module.exports = db.defineModel('user_list', {
  u_id: { type: Sequelize.INTEGER, allowNull: false, primaryKey: true, unique: true, autoIncrement: true },
  u_name: { type: Sequelize.STRING },
  u_email: { type: Sequelize.STRING },
  u_capacity: { type: Sequelize.INTEGER(), defaultValue: 10 }, // 容量
  u_password: { type: Sequelize.STRING, defaultValue: 123456 },
  u_pic: { type: Sequelize.STRING(), defaultValue: 'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png' } // 用户头像
})

// file
const Sequelize = require('sequelize')
const db = require('./index')

module.exports = db.defineModel('file_list', {
  f_id: { type: Sequelize.INTEGER, allowNull: false, primaryKey: true, unique: true, autoIncrement: true },
  f_name: { type: Sequelize.STRING() },
  f_size: { type: Sequelize.STRING() },
  f_dow_url: { type: Sequelize.STRING() },
  f_type: { type: Sequelize.STRING() },
  f_grouping: { type: Sequelize.STRING() },
  f_transfer_state: { type: Sequelize.INTEGER, defaultValue: 0 }, // 0 未下载, 1 已下载
  f_history_state: { type: Sequelize.INTEGER, defaultValue: 0 }, // 0 未删除, 1 已删除
  u_id: { type: Sequelize.INTEGER },
  u_name: { type: Sequelize.STRING }
})

// 创建表
// user
const user_list = require('../user_list')

user_list.sync({
  force: true // 在尝试创建表之前添加了一个DROP TABLE IF EXISTS – 如果强制,现有表将被覆盖。
})

// file
const fileList = require('../fileList')

fileList.sync({
  force: true
})

常用的常量

const { env } = require('./env')

const UPLOAD_PATH = env === 'dev' ? 'public/upload/users' : 'public/upload/users'

module.exports = {
  CODE_ERROR: -1,
  CODE_SUCCESS: 200,
  PWD_SALT: 'admin_imooc_node',
  PRIVATE_KEY: 'admin_imooc_node_shan',
  JWT_EXPIRED: 60 * 60, // TOKEN 失效时间
  CODE_TOKEN_ERROR: -2,
  UPLOAD_PATH,
  TOURIST_PATH: `${UPLOAD_PATH}/tourist`
  // UPLOAD_URL
}

写一个返回结果控制器

const {
  CODE_SUCCESS,
  CODE_ERROR,
  CODE_TOKEN_ERROR
} = require('../tool/constant')

class Result {
  constructor(data, msg = '操作成功!', options) {
    this.data = null
    if (arguments.length === 0) {
      this.msg = '操作成功!'
    } else if (arguments.length === 1) {
      this.msg = data
    } else {
      this.data = data
      this.msg = msg
      if (options) this.options = options
    }
  }

  content() {
    if (!this.code) {
      this.code = CODE_SUCCESS
    }

    const base = {
      code: this.code,
      msg: this.msg
    }

    if (this.data) {
      base.data = this.data
    }

    if (this.options) {
      base.options = this.options
    }

    return base
  }

  success(res) {
    this.code = CODE_SUCCESS
    this.send(res)
  }

  fail(res) {
    this.code = CODE_ERROR
    this.send(res)
  }

  send(res) {
    res.send(this.content())
  }

  tokenError(res) {
    this.code = CODE_TOKEN_ERROR
    this.send(res)
  }
}

module.exports = Result

数据库操作封装

/*
* 根据条件查找新数据
* @params option 配置对象
* */
function findOne(example, option, cb) {
  example.findOne({
    where: option
  }).then(res => {
    cb && cb(res)
  }).catch(err => {
    cb && cb(err)
  })
}

function findAll(example, option, cb) {
  example.findAll(
    option
  ).then(res => {
    cb && cb(res)
  })
}

/*
* 创建一条新数据
* @params option 配置对象
* */
function create(example, option, cb) {
  example.create(
    option
  ).then(list => {
    cb && cb(list)
  }).catch(() => {
    cb && cb(false)
  })
}

/*
* 根据ID查一条数据
* @params option 配置对象
* */
function findById(example, id, cb) {
  example.findById(id).then(list => {
    cb && cb(list)
  }).catch(err => {
    cb && cb(err)
  })
}

/*
* 根据条件更新数据
* @params option 配置对象
* 示例:
*  option = { firstName: "King" },
      {
        where: { firstName: null }
      }
* */
function update(example, option, cb) {
  example.update(...option).then(list => {
    cb && cb(list)
  }).catch(err => {
    cb && cb(err)
  })
}

/*
* 根据条件删除一条数据
* @params option 配置对象
* */
function destroy(example, option, cb) {
  example.destroy(option).then(list => {
    cb && cb(list)
  }).catch(err => {
    cb && cb(err)
  })
}

module.exports = {
  findOne,
  create,
  findById,
  update,
  destroy,
  findAll
}

接下来,我们先实现一个登录接口试试看,数据库创建一条数据:

image.png

// 在工具函数上创建一个 专门处理接收参数的处理中间件(利用boom噢)
function errorChecking(next, req, cb) {
  const err = validationResult(req)
  if (!err.isEmpty()) {
    const [{ msg }] = err.errors
    next(boom.badRequest(msg))
  } else {
    cb && cb()
  }
}

// 查询数据库
function login(options, cb) {
  findOne(UserList, options, data => {
    cb && cb(data)
  })
}

router.post('/login', [
  body('password').isLength({ min: 5 }).withMessage('密码长度太低'),
  body('username').isLength({ min: 4 }).withMessage('用户名长度太低')
], (req, res, next) => {
  errorChecking(next, req, () => { // 封装的错误处理中间件
  
    let { username, password } = req.body
    // 开发模式密码123456
    password = password === '123456' ? password : md5(`${password}${PWD_SALT}`)
    login({ u_name: username, u_password: password }, user => { // 登录查询数据库处理中间件
      if (!user || user.length === 0) { return new Result('登录失败').fail(res) }
      const token = jwt.sign( // 登录token
        { username },
        PRIVATE_KEY,
        { expiresIn: JWT_EXPIRED }
      )
      new Result({ token }, '登录成功').success(res)
    })
  })
})

app.js 配置路由以及利用cors解决跨域

const createError = require('http-errors')
const express = require('express')
const path = require('path')
const cookieParser = require('cookie-parser')
const logger = require('morgan')
const cors = require('cors')
const indexRouter = require('./routes/index')
const { scheduleCronstyle } = require('./tool/schedule')

const app = express()

// 开启全局定时任务
scheduleCronstyle()

app.set('views', path.join(__dirname, 'views'))
app.set('view engine', 'jade')

app.use(logger('dev'))
app.use(express.json())
app.use(express.urlencoded({ extended: false }))
app.use(cookieParser())
app.use(express.static(path.join(__dirname, 'public')))
app.use(cors())

app.use('/public', express.static(path.join(__dirname, 'public')))
app.use('/', indexRouter)

app.use(function(req, res, next) {
  next(createError(404))
})

app.use(function(err, req, res, next) {
  res.locals.message = err.message
  res.locals.error = req.app.get('env') === 'development' ? err : {}

  res.status(err.status || 500)
  res.render('error')
})

module.exports = app

我们接下来拿项目测试一下我们这个接口

image.png

image.png

确实是登录成功了哦

可能讲的有点块,因为项目东西有点多,很多基础性的东西我没讲到,只是讲一些必要性的东西,我已经整理好代码啦,大家可以去GitHub clone下来看看具体源码然后再看看文章,或许有更好的理解哦,桌面百度云源码

前端篇章:

什么是electron,vue如何应用electron

electron仿百度云桌面版,Vue端基础配置和electronAPI封装