封装express router Model(模型)复用逻辑

2021-03-05  本文已影响0人  AizawaSayo

如下是一个单独拆分出来处理公告板数据(表)的路由模块,为了逻辑清晰,所有处理请求的函数也都独立放在各自的js中。

import express from 'express'
const router = express.Router()

import board from './board'
import boardAdd from './board-add'
import boardOne from './board-info'
import boardDelete from './board-delete'

//列表路由
router.get('/', board)
//添加功能路由
router.post('/', boardAdd)
//查询功能路由
router.get('/:id', boardOne)
//编辑功能路由
router.put('/:id', boardAdd)
//删除功能路由
router.delete('/:id', boardDelete)

export default router

在一个项目中我们会有很多这样的数据模块,而且请求路由来来去去也就是这几项:获取列表、查询、删除、添加等。
即使模块(Model)不同、处理这些路由的每一项功能其实都大同小异,尤其是只有传递了params(通常是id),没有请求体的时候。那我们就可以把这些简单的逻辑封装一下,以后改需求可以省去很多代码量。

来看实际代码吧。

1. 删除数据

以下就是封装好的数据删除功能的js。原本我写成最终返回一个Promise,调用时再用async/await拿返回值,后来觉得有点繁琐就全都改成直接把路由参数 (req, res, next) 传过去了。(注:这里操作数据库用的是MongoDB的mongoose)

// routes/common/delete.js
/* 
  routerParams: Object {req, res, next} 路由中间件回调函数的固定参数
  Model: 数据模型
*/
export default async (routerParams, Model) => {
  const { req, res, next } = routerParams
  let id = req.params.id
  if (id.indexOf(',') > 0) { // 批量删除    
    id = id.split(',')
    try {
      const response = await Model.deleteMany({ _id: { $in: id } }).exec()
      if(response.ok && response.deletedCount) {
        res.json({
          code: 200,
          message: '批量删除成功'
        })
      } else{
        res.json({
          code: 400,
          message: '删除失败'
        })
      }
    } catch (err) {
      next(err)
    }
  } else {
    try {
      const response = await Model.deleteOne({ _id: { $in: id } }).exec()
      if(response.ok && response.deletedCount) {
        res.json({
          code: 200,
          message: '批量删除成功'
        })
      } else{
        res.json({
          code: 400,
          message: '删除失败'
        })
      }
    } catch (err) {
      next(err)
    }
  }
}

在处理delete路由的函数里引入直接使用即可

// board-delete.js 
import Board from '../../../../model/board' // Board模型
import deleteById from '../../common/delete' // 操作数据删除

export default (req, res, next) => {
  deleteById({ req, res, next }, Board)
}

2. 根据id查询数据

// routes/common/getOne.js
export default async (routerParams, Model, ref) => {
  const { req, res, next } = routerParams
  const id = req.params.id
  const refKey = ref ? ref : ''
  try {
    const doc = await Model.findById(id).populate(refKey).exec()
    res.json({
      code: 200,
      message: '查询成功',
      data: doc
    })
  } catch (err) {
    // 如果想中处理错误,也可以 next(err) 交给最后的中间件
    // next(err)
    res.json( {
      code: 400,
      message: '查询失败' + err.message
    })
  }
}

查询路由处理函数

// board-info.js
import Board from '../../../../model/board'
import getById from '../../common/getOne'

export default (req, res, next) => {
  // 如果需要联表查询,最后一个参数传入Model中关联表的字段名即可
  getById({ req, res, next }, Board, 'author')
}

参考 Board 模型(关联字段 author )

const boardSchema = new mongoose.Schema({
  title: {
    type: String,
    maxlength: 30,
    minlength: 8,
    required: [true, '文章标题不可为空'],
  },
  content: { // 内容
    type: String,
    required: [true, '内容不可为空']
  }, 
  author: { // 关联User表的字段名
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User',
    required: [true, '请传递作者']
  }
  ......
}
const Board = mongoose.model('Boarde', boardSchema)

3. 分页查询

// routes/common/getList.js
// 分页查询
export default (props) => {
  const {
    req,
    res, 
    next,
    page,
    pageSize,
    condition,
    sortCondition,
    Model,
    ref
  } = props
  const limit = parseInt(pageSize)
  const skip = (page - 1) * limit
  const refKey = ref ? ref : ''
  
  Model.countDocuments(condition).exec()
  .then(response => {
    return response
  })
  .then(total => {
    const message = total ? '列表查询成功' : '未查询到符合条件的数据'
    Model.find(condition).skip(skip).limit(limit).populate(refKey).sort(sortCondition).collation({
      locale: 'zh'
    }).exec().then(response => {
      if(response) res.json({
        code: 200,
        message,
        data: {
          list: response,
          total
        }
      })
    })
  })
  .catch (err => {
    res.json({
      code: 400,
      message: '列表查询失败' + err.message
    })
  }) 
}

请求分页列表的路由逻辑我们只关注传参即可。

// board-list.js
import Board from '../../../../model/board'
import getList from '../../common/getList'

export default (req, res, next) => {
  const {
    page,
    pageSize,
    query,
    exchangeType,
    user,
    sort
  } = req.query

  const nameReg = new RegExp(query.trim(), 'i')
  let condition = {
    "detail": nameReg
  }
  // 其他筛选条件处理和添加,筛选参数比较多的情况会比较方便
  const filterList = [{user}, {exchangeType}]
  filterList.forEach(item => {
    if(item) {
      const ckey = Object.keys(item)[0]
      const cVal = Object.values(item)[0]
      if(cVal){
        condition[ckey] = { $in: cVal}
      }
    }
  })

  let sortCondition = {
    validTime: 1
  }
  if (sort) sortCondition = JSON.parse(sort)

  getList({
    req,
    res,
    next,
    page,
    pageSize,
    condition,
    sortCondition,
    Model: Board,
    ref: 'author'
  })
}

4. 新增/修改数据

// routes/common/add.js
// 通用款:没太多特殊需求的新增 /修改
/** 
 * routerParams: Object {req, res, next} 路由中间件回调函数的固定参数
 * Model 表模型名
 * props: Object 其它参数
 *  - key: Array, 新增数据不能重复值的键名数组,默认是['name'] / 若值为nonUnique则表示无需查重
 *  - addTime: Boolean, 是否添加创建时间
 *  - uniqueName: String, 查重处理返回的重复字段名称
*/
export default async (routerParams, Model, props) => {
  const {req, res, next} = routerParams
  const data = req.body
  const options = props ? props : {}
  const { key, addTime, uniqueName } = options
  const uniqueKey = !key ? ( key === false ? false : ['name'] ) : key
  const uniqueText = uniqueName ? uniqueName : '名称'

  const addItem = async () => {
    if (addTime) data.created_time = Date.parse(new Date()) / 1000
    try {
      await Model.create(data)
      res.json({
        code: 200,
        message: '添加成功' 
      })
    } catch(err) {
      res.json({
        code: 400,
        message: '添加失败'
      })
    }
  }
  if (data._id) { // 修改数据
    try {
          await Model.findByIdAndUpdate(data._id, data).exec()
      res.json({
                code: 200,
        message: '修改成功'
            })
      } catch (err) {
      res.json({
        code: 400,
        message: err
      })
    }
    } else { // 新增数据
    if (uniqueKey) { // 查重处理
      let condition = {}
      uniqueKey.forEach(item => condition[item] = data[item])
      const doc = await Model.findOne(condition)
      if(doc) {
        res.json({
          code: 400,
          message: `该${uniqueText}已存在,请勿重复添加`
        })
      } else {
        addItem()
      }
    } else {
      addItem()
    } 
    }
}
// guide-add.js
import Guide from '../../../../model/guide' 
import AddData from '../../common/add'

export default (req, res, next) => {
  if (req.body.author) req.body.author = req.body.author._id
  AddData({ req, res, next }, Guide, { key: ['title'], addTime: true, uniqueName: '标题' })
}

5. 数据查询(不分页)

// routes/common/search.js
// 查询所有符合条件的数据,不分页
export default async (routerParams, Model, key) => {
  const { req, res, next } = routerParams
  const queryKey = key ? key : 'name'
  const queryVal = req.query[queryKey]
  const keyReg = new RegExp(queryVal.trim(), 'i')
  try {
    const response = await Model.find({
      [queryKey]: {
        $regex: keyReg
      }
    }).exec()
    res.json({
      code: 200,
      message: '查询成功',
      data: response
    })
  } catch (err) {
    res.json({
      code: 400,
      message: '查询失败' + err.message
    })
  }   
}
// user-search.js
// 实时搜索用户名字
import User from '../../../../model/user' 
import searchAll from '../../common/search'

export default (req, res, next) => {
  searchAll({ req, res, next }, User, 'username')
}

现在路由处理函数的代码已经非常简洁了,那我们直接放到路由模块里去也可,完全不影响条理的明晰。看个人习惯吧。

像下面就是一个改造后的路由模块,清爽到飞起~

import express from 'express'
const router = express.Router()

// 数据 Model
import Furniture from '../../../../model/furniture'

// 增删改查等共用处理程序
import furnitureList from './furnitureList'
import AddData from '../../common/add'
import getById from '../../common/getOne'
import deleteById from '../../common/delete'
import searchAll from '../../common/search'

// 分页列表路由,代码相对较长,单独放一个js再引入
router.get('/', furnitureList)

// 实时搜索全部符合条件的家具
router.get('/search', (req, res, next) => {
  searchAll({ req, res, next }, Furniture)
})

// 添加功能路由
router.post('/', (req, res, next) => {
  AddData({ req, res, next }, Furniture)
})

// 查询功能路由
router.get('/:id', (req, res, next) => {
  getById({ req, res, next }, Furniture)
})

// 删除功能路由
router.delete('/:id', (req, res, next) => {
  deleteById({ req, res, next }, Furniture)
})

export default router
上一篇 下一篇

猜你喜欢

热点阅读