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 会根据 _id
和 ref
查询对应的 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
参数名称 | 是否必传 | 类型 | 说明 |
---|---|---|---|
name | 是 | String | 产品名称 |
description | 是 | String | 产品描述 |
price | 是 | Number | 产品单价 |
category | 是 | String | 产品分类 id |
quantity | 是 | Number | 库存 |
shipping | 是 | Boolean | 是否需要运输 |
photo | 否 | Buffer | 产品图片 |
返回数据
字段名称 | 说明 |
---|---|
sold | 销量 |
name | 产品名称 |
description | 产品描述 |
price | 产品单价 |
category | 产品分类 id |
quantity | 库存 |
shipping | 是否需要运输 |
createdAt | 创建 Schema 时指定分配的自动管理的 createdAt 属性 |
updatedAt | 创建 Schema 时指定分配的自动管理的 updatedAt 属性 |
_id | mongoose 默认分配的 id 属性,当访问 id 时就是获取的它 |
_v | mongoose 默认分配的 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.
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 | 分类名称 |
├─ _id | mongoose 默认分配的 id 属性,当访问 id 时就是获取的它 |
├─ _v | mongoose 默认分配的 versionKey 属性 |
├─ createdAt | 创建 Schema 时指定分配的自动管理的 createdAt 属性 |
├─ updatedAt | 创建 Schema 时指定分配的自动管理的 updatedAt 属性 |
quantity | 库存 |
shipping | 是否需要运输 |
createdAt | 创建 Schema 时指定分配的自动管理的 createdAt 属性 |
updatedAt | 创建 Schema 时指定分配的自动管理的 updatedAt 属性 |
_id | mongoose 默认分配的 id 属性,当访问 id 时就是获取的它 |
_v | mongoose 默认分配的 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
参数名称 | 是否必传 | 类型 | 默认值 | 说明 |
---|---|---|---|---|
sortBy | 否 | String | _id | 排序字段 |
order | 否 | String | desc | 排序方式 asc | desc |
limit | 否 | Number | 6 | 查询文档数量上限 |
skpi | 否 | Number | 0 | 跳过文档数量 |
search | 否 | String | 搜索字段:产品名称模糊匹配关键字 | |
filters | 否 | Object | 过滤选项,格式:{price: [50, 100], sold: 10} | |
├─ <字段名> | 否 | Array | key 是过滤查询的字段名,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
参数名称 | 是否必传 | 类型 | 默认值 | 说明 |
---|---|---|---|---|
limit | 否 | Number | 6 | 查询文档数量上限 |
skpi | 否 | Number | 0 | 跳过文档数量 |
返回数据
返回一个产品信息组成的数组。
定义 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)
...