React+Redux+Ant Design+TypeScript 电子商务实战-服务端应用 04 产品管理api

Mongoose - ObjectId

Mongoose 在创建 Schema 的时候默认配置一个 _id 属性,它的 SchemaType 是 mongoose.Schema.Types.ObjectId(等同于 mongoose.ObjectId)。

当创建一个 document 的时候会自动地生成一个 _id 属性,值是 mongoose.Types.ObjectId实例化的对象。

Mongoose 拒绝保存没有 _id 的文档。

查询到的 _id 是一个对象,可以调到它的 toString() 方法将其转化成字符串。

mongoose.Schema.Types.ObjectId (等同于 mongoose.ObjectId) 和 mongoose.Types.ObjectId 不同,前者仅仅是一个配置项,后者是构建 ObjectId 的类。

默认情况相当于这样手动实现:

const mongoose = require('mongoose')
const blogSchema = new mongoose.Schema({
  _id: mongoose.Schema.Types.ObjectId // 配置类型
})
const blogModel = mongoose.model('Blog', blogSchema)
const blog = new blogModel({
  _id: new mongoose.Types.ObjectId() // 手动生成 ObjectId
})

默认情况下,Mongoose 会自动管理(设置) _id 的值。如果是手动定义,要确保每个 document 都设置了的 _id

Mongoose - 填充(Populate)

MongoDB 有一个类似 join 的聚合运算符 - $lookup。Mongoose 提供了一个更方便的方法 - populate(),用于在 document 中引用其它 collection 中的 document。

创建 Schema 时可以指定属性的 SchemaType 类型为 mongoose.Schema.Types.ObjectId(即存储引用模块的 _id),通过 ref 指定引用的哪个 Model,在填充时(调用 populate())Mongoose 会根据 _idref 查询对应的 Model 下的具体 document,将查询结果替换该属性的值。

const mongoose = require('mongoose')
const Schema = mongoose.Schema

const personSchema = new Schema({
  _id: Schema.Types.ObjectId, // 手动指定 _id 的类型是 ObjectId
  name: String,
  age: Number,
  stories: [
    {
      type: Schema.Types.ObjectId,
      ref: 'Story' // 指定在填充时使用哪个 Model
    }
  ] // stories 存储 ObjectIds 数组
})

const storySchema = Schema({
  author: { type: Schema.Types.ObjectId, ref: 'Person' },
  title: String,
  fans: [{ type: Schema.Types.ObjectId, ref: 'Person' }]
})

const Story = mongoose.model('Story', storySchema)
const Person = mongoose.model('Person', personSchema)

存储引用(ref)

向 Schema.Types.ObjectId 类型的属性存值的时候,可以存储 ObjectId 对象或 <ObjectId>.toString() 返回的字符串,数据库会自动转化为 ObjectId 对象去存储。

const author = new Person({
  // _id 建议由 Mongoose 默认生成
  name: 'Ian Fleming',
  age: 50
})

author.save(err => {
  if (err) return handleError(err)
  
  const story1 = new Story({
    title: 'Casino Royale',
    author: author._id // 存储引用的 `_id`
  })
  
  story1.save()
})

填充

在查询 story 时填充 person 信息:

Story
	// .findOne() 返回一个 Query 对象
  .findOne({title: 'Casino Royale'})
	// Query.prototype.populate() 也返回 Query 对象,不接收回调函数
	.populate('author')
	// 需要调用 Query.prototype.exxc() 执行查询
	.exec((err, story) => {
  	if (err) return handleError(err)
  	console.log(story.author.name) // Ian Fleming
	})
	

api-产品管理-创建产品

api 设计

表单格式

multipart/form-data

基本信息

Path: /product/create/:userId

Method: POST

请求参数

Headers

参数名称是否必须备注
Authorization认证token,格式 Bearer <JSON WEB TOKEN>

Body

参数名称是否必传类型说明
nameString产品名称
descriptionString产品描述
priceNumber产品单价
categoryString产品分类 id
quantityNumber库存
shippingBoolean是否需要运输
photoBuffer产品图片

返回数据

字段名称说明
sold销量
name产品名称
description产品描述
price产品单价
category产品分类 id
quantity库存
shipping是否需要运输
createdAt创建 Schema 时指定分配的自动管理的 createdAt 属性
updatedAt创建 Schema 时指定分配的自动管理的 updatedAt 属性
_idmongoose 默认分配的 id 属性,当访问 id 时就是获取的它
_vmongoose 默认分配的 versionKey 属性

创建产品 Schema

// models\product.js
const mongoose = require('mongoose')

const productSchema = new mongoose.Schema(
  {
    // 产品名称
    name: {
      type: String,
      trim: true,
      required: true,
      maxlength: [32, '产品名称不得多于32个字']
    },
    // 产品描述
    description: {
      type: String,
      required: true,
      maxlength: [2000, '产品描述不得多于2000个字']
    },
    // 单价
    price: {
      type: Number,
      required: true,
      max: [Math.pow(10, 10) - 1, '太贵的商品承受不了']
    },
    // 产品分类
    category: {
      type: mongoose.Schema.Types.ObjectId,
      ref: 'Category',
      required: true
    },
    // 库存
    quantity: {
      type: Number,
      default: 0
    },
    // 销量
    sold: {
      type: Number,
      default: 0
    },
    // 是否需要运输
    shipping: {
      type: Boolean,
      default: false
    },
    // 产品图片信息
    photo: {
      data: Buffer, // Buffer 数据
      contentType: String // 内容类型
    }
  },
  {
    timestamps: true
  }
)

module.exports = mongoose.model('Product', productSchema)

定义 api

暂不处理图片

// controllers\product.js
const { errorHandler } = require('../helpers/dbErrorHandler')
const Product = require('../models/product')
const mongoose = require('mongoose')

// 创建产品
const create = (req, res) => {
  const product = new Product(req.body)

  product.save((error, data) => {
    if (error) {
      return res.status(400).json(errorHandler(error))
    }
    // 图片由其它接口获取
    data.photo = undefined
    res.json(data)
  })
}

module.exports = {
  create
}

配置 api

// routes\product.js
const express = require('express')
const { tokenParser, authValidator, adminValidator } = require('../validator/user')
const { create } = require('../controllers/product')
const { getUserById } = require('../controllers/user')

const router = express.Router()

// 创建产品
router.post('/product/create/:userId', [tokenParser, authValidator, adminValidator], create)

// 监听路由参数
// 根据 id 获取用户信息
router.param('userId', getUserById)

module.exports = router

// routes\index.js
const userRoutes = require('./user')
const categoryRoutes = require('./category')
const productRoutes = require('./product')

module.exports = [userRoutes, categoryRoutes, productRoutes]

产品图片上传

Buffer 类型

常规的图片存储可能是将文件上传到服务器文件管理系统中,并在数据库字段中存储一个文件地址。

本例中的产品图片会转换成 Buffer 存储到数据库中,并记录它的 contentType

产品图片仅用于获取图片的接口,它返回数据库存储的 Buffer - 一个文件流。

了解 Buffer 是什么可以参考 Do you want a better understanding of Buffer in Node.js? Check this out.

网友翻译: 一篇帮你彻底弄懂NodeJs中的Buffer_包磊磊的博客-CSDN博客

Mongoose 在创建 Schema 的时候可以指定属性的 SchemaType 类型为 Buffer,在存储 document 时会强制将该属性的值转化成 Buffer 格式去存储。

const schema = new Schema({ binData: Buffer })
const Data = mongoose.model('Data', schema)
const file = new Data({ binData: 'test' }) // {"type": "Buffer", "data": [116, 101, 115, 116]}

formidable 表单数据解析

formidable 模块用于解析表单数据(Content-Type: multipart/form-data 提交的表单),尤其是文件上传。

它会自动将上传文件写入磁盘(默认是系统的临时文件夹 os.tmpdir()),可通过 options.uploadDir 配置项修改,也可在 form.parse() 解析回调中通过 fs.rename() 移动和重命名。

注意要定义 form 元素的 enctype 属性为 multipart/form-data

在 Express 中使用,不需要配置中间件,formidable 只需要 request 表单对象:

const express = require('express')
const formidable = require('formidable')
const path = require('path')

const app = express()

// 注意表单编码方式是 `multipart/form-data`
app.get('/', (req, res) => {
  res.send(`
    <form action="/api/upload" enctype="multipart/form-data" method="post">
      <div>Text field title: <input type="text" name="title" /></div>
      <div>File: <input type="file" name="someExpressFiles" multiple="multiple" /></div>
      <input type="submit" value="Upload" />
    </form>
  `)
})

app.post('/api/upload', (req, res, next) => {
  // 创建一个表单解析器
  // 接收可选的配置对象
  const form = formidable({
    // 默认 `false` 只接收一个文件上传
    // 如果开启该功能,当调用`form.parse()`方法时,回调函数的 `files` 参数对象中的值将会是一个 file 数组(如果上传了多个文件)
    // 数组每一个成员是一个 File 对象
    // 前提是上传文件的表单支持并开启了 html5 中 `multiple` 特性
    multiples: true,
    
    // 文件路径是否包含源文件的扩展名
    keepExtensions: true,
    
    // 存储路径 默认 os.tmpdir()
    uploadDir: path.resolve(__dirname, '../upload')
  })
  
  // 解析请求对象
  form.parse(req, (err, fields, files) => {
		// fileds 是解析收集的所有字段
    // files 包含所有上传文件的字段
  })
})

定义表单解析器和验证器

使用 formidable 解析 multipart/form-data 的表单对象,将其存储在 req.body 上,就可以用 express-validator 进行校验了

// validator\product.js
const formidable = require('formidable')
const fs = require('fs')
const { body, validationResult } = require('express-validator')
const { errorHandler } = require('../helpers/dbErrorHandler')

// 判断校验是否成功
const validResultCallback = (req, res, next) => {
  const result = validationResult(req)
  if (!result.isEmpty()) {
    return res.status(400).json(errorHandler(result.array()))
  }
  next()
}

// 解析 multipart/form-data 表单,存储到 req.body
const formDataParser = (req, res, next) => {
  // 创建表单解析器
  const form = formidable({
    // 文件路径是否包含源文件的扩展名
    keepExtensions: true
  })
  // 解析请求对象
  form.parse(req, (err, fields, files) => {
    if (err) {
      return res.status(400).json(errorHandler('图片上传失败'))
    }

    req.body = fields

    // 处理图片
    if (files.photo) {
      // 如果图片大小超过 1MB
      if (files.photo.size > 1024 * 1000) {
        res.status(400).json(errorHandler('图片大小不能超过1MB'))
      }

      // readFileSync 同步读取文件返回 Buffer
      req.body.photo = {
        data: fs.readFileSync(files.photo.path),
        contentType: files.photo.type
      }
    }

    next()
  })
}

// 创建产品校验
const createValidator = [
  body('name')
    .notEmpty()
    .withMessage('请传入产品名称')
    .isLength({
      max: 32
    })
    .withMessage('产品名称不得多于32个字'),
  body('description')
    .notEmpty()
    .withMessage('请传入产品描述')
    .isLength({
      max: 2000
    })
    .withMessage('产品描述不得多于2000个字'),
  body('price').notEmpty().withMessage('请传入单价').isDecimal().withMessage('单价必须是数字'),
  body('category').notEmpty().withMessage('请传入产品分类'),
  body('quantity').notEmpty().withMessage('请传入库存').isInt().withMessage('库存必须是整数'),
  body('shipping').notEmpty().withMessage('请传入是否需要运输').isBoolean().withMessage('是否需要运输必须是布尔值'),
  validResultCallback
]

module.exports = {
  formDataParser,
  createValidator
}

配置解析器和验证器

// routes\product.js
const express = require('express')
const { tokenParser, authValidator, adminValidator } = require('../validator/user')
const { create } = require('../controllers/product')
const { getUserById } = require('../controllers/user')
const { formDataParser, createValidator } = require('../validator/product')

const router = express.Router()

// 创建产品
router.post('/product/create/:userId', [tokenParser, authValidator, adminValidator], formDataParser, createValidator, create)

// 监听路由参数
// 根据 id 获取用户信息
router.param('userId', getUserById)

module.exports = router

api-产品管理-根据 id 获取产品信息

api 设计

基本信息

Path: /product/:productId

Method: GET

返回数据

字段名称说明
sold销量
name产品名称
description产品描述
price产品单价
category产品分类信息
├─ name分类名称
├─ _idmongoose 默认分配的 id 属性,当访问 id 时就是获取的它
├─ _vmongoose 默认分配的 versionKey 属性
├─ createdAt创建 Schema 时指定分配的自动管理的 createdAt 属性
├─ updatedAt创建 Schema 时指定分配的自动管理的 updatedAt 属性
quantity库存
shipping是否需要运输
createdAt创建 Schema 时指定分配的自动管理的 createdAt 属性
updatedAt创建 Schema 时指定分配的自动管理的 updatedAt 属性
_idmongoose 默认分配的 id 属性,当访问 id 时就是获取的它
_vmongoose 默认分配的 versionKey 属性

定义获取产品信息的前置路由中间件和 api

// controllers\product.js
const { errorHandler } = require('../helpers/dbErrorHandler')
const Product = require('../models/product')
const mongoose = require('mongoose')

// 创建产品
const create = (req, res) => {...}

// 根据 id 获取产品信息
const getProductById = (req, res, next, id) => {
  Product.findById(id)
    // 填充分类信息
    // Mongoose 会根据 `_id` 查询 Category 模块的数据替换 `category` 属性的值
    .populate('category')
    // 执行查询
    .exec((error, product) => {
      if (error || !product) {
        return res.status(400).json(errorHandler('产品未找到'))
      }

      req.product = product
      next()
    })
}

// 返回产品信息
const read = (req, res) => {
  // 图片由其它接口获取
  req.product.photo = undefined
  res.json(req.product)
}

module.exports = {
  create,
  getProductById,
  read
}

配置 api

// routes\product.js
const express = require('express')
const { tokenParser, authValidator, adminValidator } = require('../validator/user')
const { create, getProductById, read } = require('../controllers/product')
const { getUserById } = require('../controllers/user')
const { formDataParser, createValidator } = require('../validator/product')

const router = express.Router()

// 创建产品
router.post('/product/create/:userId', [tokenParser, authValidator, adminValidator], formDataParser, createValidator, create)
// 根据 id 获取产品信息
router.get('/product/:productId', read)

// 监听路由参数
// 根据 id 获取用户信息
router.param('userId', getUserById)
// 根据 id 获取产品信息
router.param('productId', getProductById)

module.exports = router

api-产品管理-根据 id 更新产品

api 设计

表单格式

multipart/form-data

基本信息

Path: /product/:productId/:userId

Method: POST

请求参数

Headers

参数名称是否必须备注
Authorization认证token,格式 Bearer <JSON WEB TOKEN>

Body

参数名称是否必传
Product Model 的任意属性

返回数据

同查询产品信息

定义 api

// controllers\product.js
const { errorHandler } = require('../helpers/dbErrorHandler')
const Product = require('../models/product')
const mongoose = require('mongoose')
const _ = require('lodash')

...

// 根据 id 更新产品信息
const update = (req, res) => {
  // req.product => 数据库查询的用户信息
  // req.body => formidable 解析的表单信息
  // 合并用户信息
  const product = _.extend(req.product, req.body)

  product.save((error, data) => {
    if (error) {
      return res.status(400).json(errorHandler(error))
    }
    // 图片由其它接口获取
    data.photo = undefined
    res.json(data)
  })
}

module.exports = {
  create,
  getProductById,
  read,
  update
}

配置 api

// routes\product.js
const express = require('express')
const { tokenParser, authValidator, adminValidator } = require('../validator/user')
const { create, getProductById, read, update } = require('../controllers/product')
const { getUserById } = require('../controllers/user')
const { formDataParser, createValidator } = require('../validator/product')

const router = express.Router()

// 创建产品
router.post(
  '/product/create/:userId',
  [tokenParser, authValidator, adminValidator],
  formDataParser,
  createValidator,
  create
)
// 根据 id 获取产品信息
router.get('/product/:productId', read)
// 根据 id 更新产品信息
router.post('/product/:productId/:userId', [tokenParser, authValidator, adminValidator], formDataParser, update)

// 监听路由参数
// 根据 id 获取用户信息
router.param('userId', getUserById)
// 根据 id 获取产品信息
router.param('productId', getProductById)

module.exports = router

api-产品管理-根据 id 删除产品

设计缺陷

一般会定义一个状态字段,通过不同的值标识数据是否是删除状态。

直接删除数据,会影响到关联该产品的订单,导致无法查看订单产品源信息。

而订单中应该保存产品信息的快照(下单时的产品信息副本和产品分类副本),尽管还有些不足,删除产品或产品分类时,也会降低查看下单时的产品详情的影响。

api 设计

基本信息

Path: /product/:productId/:userId

Method: DELETE

请求参数

Headers

参数名称是否必须备注
Authorization认证token,格式 Bearer <JSON WEB TOKEN>

返回数据

字段名称说明
deleteProduct删除的产品信息集合
├─ 产品相关字段同查询产品信息
message删除成功提示

定义 api

// controllers\product.js

...

// 根据 id 删除产品
const remove = (req, res) => {
  const product = req.product
  product.remove((error, deleteProduct) => {
    if (error) {
      return res.status(400).json(errorHandler(error))
    }

    deleteProduct.photo = undefined
    res.json({
      deleteProduct,
      message: '产品删除成功'
    })
  })
}

module.exports = {
  create,
  getProductById,
  read,
  update,
  remove
}

配置 api

// routes\product.js

...

const { create, getProductById, read, update, remove } = require('../controllers/product')

...

// 根据 id 删除产品
router.delete('/product/:productId/:userId', [tokenParser, authValidator, adminValidator], remove)

...

api-产品管理-获取产品列表

api 设计

基本信息

Path: /products

Method: POST

请求参数

Body

参数名称是否必传类型默认值说明
sortByString_id排序字段
orderStringdesc排序方式 asc | desc
limitNumber6查询文档数量上限
skpiNumber0跳过文档数量
searchString搜索字段:产品名称模糊匹配关键字
filtersObject过滤选项,格式:{price: [50, 100], sold: 10}
├─ <字段名>Arraykey 是过滤查询的字段名,value 是一个完全匹配的值组成的数组
如果 key 是 price,且值是多个,则仅将前两个值作为查询范围

示例

{
    "sortBy": "_id",
    "order": "desc",
    "limit": 10,
    "skip": 0,
    "search": "JavaScript",
    "filters": {
        "category": ["610a3d79458ef7766805473d"],
        "price": [50, 100]
    }
}

返回数据

返回一个产品信息组成的数组。

默认查询

// controllers\product.js

...

// 获取产品列表
const list = (req, res) => {
  // 获取请求参数
  const { sortBy = '_id', order = 'asc', limit = 6, skip = 0 } = req.body

  // 检查排序方式
  if (!['asc', 'desc'].includes(order)) {
    return res.status(400).json(errorHandler('错误的排序方式'))
  }

  // 创建查询对象
  Product.find()
    // 查询排除 photo
    .select('-photo')
    // 填充 category 字段对应的文档信息
    .populate('category')
    // 排序规则 `{ id: 'asc' }`
    .sort({ [sortBy]: order })
    // 跳过查询的文档数量
    .skip(skip)
    // 查询文档数量上限
    .limit(limit)
    // 执行查询
    .exec((error, products) => {
      if (error) {
        return res.status(400).json(errorHandler(error))
      }

      res.json(products)
    })
}

module.exports = {
  create,
  getProductById,
  read,
  update,
  remove,
  list
}
// routes\product.js

...

const { create, getProductById, read, update, remove, list } = require('../controllers/product')

...

// 获取产品列表
router.post('/products', list)

过滤查询

正则匹配查询参考 $regex — MongoDB Manual

// 获取产品列表
const list = (req, res) => {
  // 获取请求参数
  const { sortBy = '_id', order = 'asc', limit = 6, skip = 0, filters = {}, search } = req.body
  // 过滤器
  const filter = {}

  // 检查排序方式
  if (!['asc', 'desc'].includes(order)) {
    return res.status(400).json(errorHandler('错误的排序方式'))
  }

  // 根据 filters 生成过滤条件
  if (filters.toString() === '[object Object]') {
    for (let key in filters) {
      const value = filters[key]
      if (!Array.isArray(value) || value.length === 0) continue

      if (key === 'price' && value.length > 1) {
        // `price` 根据前两个值生成查询范围
        filter[key] = {
          $gte: value[0], // 大于等于,
          $lte: value[1] // 小于等于
        }
      } else {
        // 完全匹配传递的匹配值
        // 示例: category: ['匹配项1', '匹配项2']
        filter[key] = value
      }
    }
  }

  // 模糊查询名称的过滤条件
  if (search) {
    filter.name = {
      $regex: search,
      $options: 'i'
    }
  }

  // 创建查询对象
  Product.find(filter)
    // 查询排除 photo
    .select('-photo')
    // 填充 category 字段对应的文档信息
    .populate('category')
    // 排序规则 `{ id: 'asc' }`
    .sort({ [sortBy]: order })
    // 跳过查询的文档数量
    .skip(skip)
    // 查询文档数量上限
    .limit(limit)
    // 执行查询
    .exec((error, products) => {
      if (error) {
        return res.status(400).json(errorHandler(error))
      }

      res.json(products)
    })
}

api-产品管理-根据 id 获取相同分类的产品列表

api 设计

基本信息

Path: /products/related/:productId

Method: GET

请求参数

Query

参数名称是否必传类型默认值说明
limitNumber6查询文档数量上限
skpiNumber0跳过文档数量

返回数据

返回一个产品信息组成的数组。

定义 api

// controllers\product.js

...

// 根据 id 查询相同产品分类的产品
const listRelated = (req, res) => {
  const product = req.product
  // 获取查询数量参数
  let { limit = 6, skip = 0 } = req.query
  limit = parseInt(limit)
  skip = parseInt(skip)

  // 创建查询对象
  Product.find({
    _id: {
      // 查询 _id 不等于当前产品的产品
      // 如果是 `_id` 也可以省略为 `$ne: product`
      $ne: product._id
    },
    category: product.category
  })
    .select('-photo')
    .populate('category')
    .exec((error, products) => {
      if (error) {
        return res.status(400).json(errorHandler(error))
      }

      res.json(products)
    })
}

module.exports = {
  create,
  getProductById,
  read,
  update,
  remove,
  list,
  listRelated
}

配置 api

// routes\product.js
...

const { create, getProductById, read, update, remove, list, listRelated } = require('../controllers/product')

...

// 根据 id 查询相同分类的产品
router.get('/products/related/:productId', listRelated)

...

api-产品管理-获取已有产品中使用的分类

api 设计

基本信息

Path: /products/categories

Method: GET

返回数据

返回一个分类信息组成的数组。

定义 api

// controllers\product.js
const Category = require('../models/category')

...

// 获取已有产品中使用的分类
const listCategories = (req, res) => {
  // distinct 去重查询 返回一个数组
  Product.distinct('category', (err, categoryIds) => {
    if (err) {
      return res.status(400).json(errorHandler(err))
    }

    Category.find(
      {
        _id: categoryIds
      },
      (error, categories) => {
        if (error) {
          return res.status(400).json(errorHandler(error))
        }
        res.json(categories)
      }
    )
  })
}

module.exports = {
  create,
  getProductById,
  read,
  update,
  remove,
  list,
  listRelated,
  listCategories
}

配置 api

// routes\product.js
...

const {
  create,
  getProductById,
  read,
  update,
  remove,
  list,
  listRelated,
  listCategories
} = require('../controllers/product')

...

// 根据 id 查询相同分类的产品
router.get('/products/categories', listCategories)

...

api-产品管理-根据 id 获取产品封面

api 设计

基本信息

Path: /products/photo/:productId

Method: GET

返回数据

返回产品图片的文件流

定义 api

// controllers\product.js

...

// 根据 id 获取产品封面
const photo = (req, res, next) => {
  const product = req.product
  if (product.photo.data) {
    res.set('Content-Type', product.photo.contentType)
    res.send(product.photo.data)
  }
  res.send('产品封面不存在')
}

module.exports = {
  create,
  getProductById,
  read,
  update,
  remove,
  list,
  listRelated,
  listCategories,
  photo
}

配置 api

// routes\product.js
...

const {
  create,
  getProductById,
  read,
  update,
  remove,
  list,
  listRelated,
  listCategories,
  photo
} = require('../controllers/product')

...

// 根据 id 获取产品封面
router.get('/product/photo/:productId', photo)

...

api-分类管理-根据分类 id 删除分类

api 设计

基本信息

Path: /category/:categoryId/:userId

Method: DELETE

请求参数

Headers

参数名称是否必须备注
Authorization认证token,格式 Bearer <JSON WEB TOKEN>

返回数据

字段名称说明
message结果提示

定义 api

// controllers\category.js
const Category = require('../models/category')
const { errorHandler } = require('../helpers/dbErrorHandler')
const Product = require('../models/product')

...

// 根据 id 删除分类
const remove = (req, res) => {
  const category = req.category

  // 查询是否有产品正在使用该分类
  Product.find({ category: category._id }, (err, products) => {
    if (err) {
      return res.status(400).json(errorHandler(err))
    }

    if (products.length > 0) {
      return res
        .status(400)
        .json(errorHandler(`抱歉。不能删除 ${category.name} 分类,此分类中还有 ${products.length} 条相关产品`))
    }

    // 删除分类
    category.remove((error, result) => {
      if (error) {
        return res.status(400).json(errorHandler(error))
      }
      res.json({ message: '分类删除成功' })
    })
  })
}

module.exports = {
  create,
  getCategoryById,
  read,
  update,
  list,
  remove
}

配置 api

// routes\category.js

...

const { create, read, getCategoryById, update, list, remove } = require('../controllers/category')

...

// 根据 id 删除分类
router.delete('/category/:categoryId/:userId', remove)

...

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值