创建订单 Schema
// models\order.js
const mongoose = require('mongoose')
const Product = require('./product')
const Category = require('./category')
// 创建快照类型
// 克隆产品 Schema
const snapshotProductSchema = Product.schema.clone()
// 克隆分类 Schema
const snapshopCategorySchema = Category.schema.clone()
// 删除分类名称的唯一设置,否则保存相同 products[n].snapshot.category.name 时会失败
snapshopCategorySchema.path('name', String)
// 将类型属性由 ObjectId 变更为 Category.schema 用于存放类型快照
snapshotProductSchema.path('category', snapshopCategorySchema)
const orderSchema = new mongoose.Schema(
{
// 产品信息
products: [
{
// 产品关联 id
product: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Product'
},
// 产品快照信息(避免产品被删除后无法从订单查看产品信息)
snapshot: snapshotProductSchema,
// 产品购买数量
count: Number
}
],
// 商户订单 ID (alipay 异步通知返回)
out_trade_no: String,
// 支付宝交易 ID(alipay 异步通知返回的支付宝生成的 ID)
trade_no: String,
// 订单总金额
amount: Number,
// 收货地址
address: String,
// 订单状态
// Unpaid => 未付款
// Paid => 已付款
// Shipped => 运输中
// Completed => 已完成
// Cancelled => 已取消
status: {
type: String,
default: 'Unpaid',
// enum 使用异常,用 validate 代替
validate: {
validator(v) {
return ['Unpaid', 'Paid', 'Shipped', 'Delivered', 'Cancelled'].includes(v)
},
message: '{VALUE} 不是有效的订单状态'
}
},
// 订单所属用户
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
},
{
timestamps: true
}
)
module.exports = mongoose.model('Order', orderSchema)
api-订单管理-创建订单
创建订单
定义一个创建订单的 api,用于跳过支付直接测试,之后将其转化成方法,仅在 api/verifySignature
接口中被调用。
// controllers\order.js
const Order = require('../models/order')
const Product = require('../models/product')
// 创建订单
const create = async (req, res) => {
// order => {
// trade_no,
// out_trade_no,
// amount,
// address,
// products,
// user,
// result
// }
const order = req.body
// 添加订单状态
order.status = order.result ? 'Paid' : 'Unpaid'
// 删除验签结果
delete order.result
// 获取产品快照信息
const snapshotObject = {}
await Product.find({
_id: {
$in: order.products.map(({ product }) => product)
}
})
.populate({
path: 'category',
select: '_id name'
})
.exec()
.then(products => {
products.forEach(product => {
snapshotObject[product._id] = product
})
})
// 填充产品快照信息
order.products.forEach(item => {
item.snapshot = snapshotObject[item.product]
})
// 保存订单
// MyModel.create() 与 new MyModel().save() 一样
// 区别是 MyModel.create() 可以保存多个 document
const data = await Order.create(order)
// 更改产品库存和销量
res.json({ message: '订单创建成功' })
}
module.exports = {
create
}
// routes\order.js
const express = require('express')
const { create } = require('../controllers/order')
const router = express.Router()
// 创建订单(跳过支付)
router.post('/order/create', create)
module.exports = router
更改产品库存和销量
// controllers\product.js
const Order = require('../models/order')
...
// 更新订单下的产品的库存和销量
const updateProductQuantityAndSold = orderId => {
Order.findById(orderId, (error, order) => {
if (error || !order) {
console.error('更新产库存和销量时获取订单信息失败', error)
return
}
// 生成 updateOne 命令
const bulkOps = order.products.map(item => ({
updateOne: {
filter: {
_id: item.product
},
update: {
// $inc 将指定的值增加指定的量
$inc: {
quantity: -item.count,
sold: +item.count
}
}
}
}))
// 一次性执行多个命令
Product.bulkWrite(bulkOps, {}, error => {
if (error) {
console.error('产品库存和销量更新失败', error)
}
})
})
}
module.exports = {
create,
getProductById,
read,
update,
remove,
list,
listRelated,
listCategories,
photo,
updateProductQuantityAndSold
}
// controllers\order.js
const { updateProductQuantityAndSold } = require('./product')
// 更改产品库存和销量
updateProductQuantityAndSold(data._id)
将 api 转化成方法并调用
// controllers\order.js
const Order = require('../models/order')
const Product = require('../models/product')
const { updateProductQuantityAndSold } = require('./product')
// 创建订单(用于跳过支付测试)
const create = async (req, res) => {
const order = req.body
try {
await createOrder(order)
res.json({ message: '订单创建成功' })
} catch (e) {
res.status(400).json({ message: '订单创建失败', errors: e })
}
}
// 创建订单(用于支付成功后被调用)
const createOrder = async order => {
// order => {
// trade_no,
// out_trade_no,
// amount,
// address,
// products,
// user,
// result
// }
// 添加订单状态
order.status = order.result ? 'Paid' : 'Unpaid'
// 删除验签结果
delete order.result
// 获取产品快照信息
const snapshotObject = {}
await Product.find({
_id: {
$in: order.products.map(({ product }) => product)
}
})
.populate({
path: 'category',
select: '_id name'
})
.exec()
.then(products => {
products.forEach(product => {
snapshotObject[product._id] = product
})
})
// 填充产品快照信息
order.products.forEach(item => {
item.snapshot = snapshotObject[item.product]
})
// 保存订单
// MyModel.create() 与 new MyModel().save() 一样
// 区别是 MyModel.create() 可以保存多个 document
const data = await Order.create(order)
// 更改产品库存和销量
updateProductQuantityAndSold(data._id)
}
module.exports = {
create,
createOrder
}
异步通知后创建订单
// controllers\alipay.js
...
const { createOrder } = require('../controllers/order')
...
// 支付成功回调
const alipayNotifyUrl = async (req, res) => {
// 1. 验签
// 创建 SDK 实例
const alipaySdk = new AlipaySdk(alipaySdkConfig)
try {
// result 为布尔值,表示是否验签通过
const result = await alipaySdk.checkNotifySign(req.body)
// 获取回传参数
const passback = JSON.parse(req.body.passback_params)
// 创建订单
createOrder({
out_trade_no: req.body.out_trade_no,
trade_no: req.body.trade_no,
amount: req.body.total_amount,
address: passback.address,
products: passback.products,
user: passback.userId,
result
})
} catch (e) {
console.error('验签异常:', e)
res.status(400).json(e)
}
}
module.exports = {
getPayUrl,
alipayNotifyUrl
}
api-用户管理-根据 id 获取用户历史订单
api 设计
基本信息
Path: /user/orders/:userId
Method: GET
请求参数
Headers
参数名称 | 是否必须 | 备注 |
---|---|---|
Authorization | 是 | 认证token,格式 Bearer <JSON WEB TOKEN> |
返回数据
字段名称 | 说明 |
---|---|
Response | 返回的集合对象 |
├─ status | 订单状态 |
├─ out_trade_no | 支付宝交易 ID |
├─ trade_no | 商户订单 ID |
├─ amount | 订单总金额 |
├─ address | 收货地址 |
├─ products | 订单中的产品集合 |
├─── _id | mongoose 默认分配的 id 字段 |
├─── count | 产品购买数量 |
├─── product | 关联产品信息(同产品详情返回对象) |
├─── snapshot | 产品快照信息(同产品详情返回对象,并填充了产品分类的信息) |
├─ user | 订单所属用户信息 |
├─── _id | mongoose 默认分配的 id 字段 |
├─── name | 用户昵称 |
├─ createdAt | 创建 Schema 时指定分配的自动管理的 createdAt 字段 |
├─ updatedAt | 创建 Schema 时指定分配的自动管理的 updatedAt 字段 |
├─ _id | mongoose 默认分配的 id 字段 |
├─ _v | mongoose 默认分配的 versionKey 字段 |
定义 api
// controllers\user.js
...
const Order = require('../models/order')
...
// 根据 id 获取用户历史订单
const getOrderHistory = (req, res) => {
Order.find({ user: req.profile._id })
.select('-products.snapshot.photo')
.populate([
{
path: 'user',
select: '_id name'
},
{
path: 'products.product',
select: '-photo'
}
])
.sort('-createdAt')
.exec((err, orders) => {
if (err) {
return res.status(400).json(errorHandler(err))
}
res.json(orders)
})
}
module.exports = {
signup,
signin,
getUserById,
read,
update,
getOrderHistory
}
// routes\user.js
const { signup, signin, getUserById, read, update, getOrderHistory } = require('../controllers/user')
...
// 根据 id 获取用户历史订单
router.get('/user/orders/:userId', [tokenParser, authValidator], getOrderHistory)
...
api-订单管理-根据 id 获取订单中产品快照的封面图片
api 设计
基本信息
Path: /order/snapshotPhotp/:orderId/:productId
Method: GET
返回数据
返回产品快照图片的文件流
定义 api
// controllers\order.js
const { errorHandler } = require('../helpers/dbErrorHandler')
...
// 根据 id 获取订单中产品快照的封面图片
const getSnapshopPhoto = (req, res) => {
const { orderId, productId } = req.params
Order.findById(orderId)
.select('products.snapshot')
.exec((err, data) => {
if (err || !data) {
return res.status(400).json(errorHandler(err))
}
const productItem = data.products.find(item => item.snapshot._id.toString() === productId)
if (!productItem) {
return res.status(400).json(errorHandler('未找到产品对应的快照信息'))
}
res.set('Content-Type', productItem.snapshot.photo.contentType)
res.send(productItem.snapshot.photo.data)
})
}
module.exports = {
create,
createOrder,
getSnapshopPhoto
}
// routes\order.js
const express = require('express')
const { create, getSnapshopPhoto } = require('../controllers/order')
const router = express.Router()
// 创建订单(跳过支付)
router.post('/order/create', create)
// 根据 id 获取订单中产品快照的封面图片
router.get('/order/snapshotPhotp/:orderId/:productId', getSnapshopPhoto)
module.exports = router
api-订单管理-管理员获取订单列表
api 设计
基本信息
Path: /orders/:userId
Method: GET
请求参数
Headers
参数名称 | 是否必须 | 备注 |
---|---|---|
Authorization | 是 | 认证token,格式 Bearer <JSON WEB TOKEN> |
返回数据
返回一个订单信息组成的数组。
定义 api
// controllers\order.js
...
// 获取所有订单列表
const list = (req, res) => {
Order.find()
.select('-products.snapshot.photo')
.populate([
{
path: 'user',
select: '_id name'
},
{
path: 'products.product',
select: '-photo'
}
])
.sort('-createdAt')
.exec((err, data) => {
if (err || !data) {
return res.status(400).json(errorHandler(err))
}
res.json(data)
})
}
module.exports = {
create,
createOrder,
getSnapshopPhoto,
list
}
配置 api
// routes\order.js
const express = require('express')
const { create, getSnapshopPhoto, list } = require('../controllers/order')
const { tokenParser, authValidator, adminValidator } = require('../validator/user')
const { getUserById } = require('../controllers/user')
const router = express.Router()
// 创建订单(跳过支付)
router.post('/order/create', create)
// 根据 id 获取订单中产品快照的封面图片
router.get('/order/snapshotPhotp/:orderId/:productId', getSnapshopPhoto)
// 管理员获取所有订单列表
router.get('/orders/:userId', [tokenParser, authValidator, adminValidator], list)
// 监听路由参数
// 根据 id 获取用户信息
router.param('userId', getUserById)
module.exports = router
api-订单管理-管理员修改订单状态
api 设计
基本信息
Path: /order/updateStatus/:userId
Method: PUT
请求参数
Headers
参数名称 | 是否必须 | 备注 |
---|---|---|
Authorization | 是 | 认证token,格式 Bearer <JSON WEB TOKEN> |
Body
参数名称 | 是否必传 | 类型 | 说明 |
---|---|---|---|
orderId | 是 | String | 订单 ID |
status | 是 | String | 订单状态 |
返回数据
返回一个成功提示
定义 api
// 修改订单状态
const updateStatus = (req, res) => {
const { orderId, status } = req.body
if (!orderId) {
return res.status(400).json(errorHandler('请传入订单 ID'))
}
// 只有 save 方法会触发校验器
// 为了校验 status 是否属于指定的枚举范围 这里使用 save 而不是 updateOne
Order.findById(orderId, (err, order) => {
if (err || !order) {
return res.status(400).json(errorHandler(err))
}
order.status = status
order.save((error, updateOrder) => {
if (error) {
return res.status(400).json(errorHandler(error))
}
res.json(data)
})
})
}
module.exports = {
create,
createOrder,
getSnapshopPhoto,
list,
updateStatus
}
// routes\order.js
const express = require('express')
const { create, getSnapshopPhoto, list, updateStatus } = require('../controllers/order')
const { tokenParser, authValidator, adminValidator } = require('../validator/user')
const { getUserById } = require('../controllers/user')
const router = express.Router()
// 创建订单(跳过支付)
router.post('/order/create', create)
// 根据 id 获取订单中产品快照的封面图片
router.get('/order/snapshotPhotp/:orderId/:productId', getSnapshopPhoto)
// 管理员获取所有订单列表
router.get('/orders/:userId', [tokenParser, authValidator, adminValidator], list)
// 管理员修改订单状态
router.put('/order/updateStatus/:userId', [tokenParser, authValidator, adminValidator], updateStatus)
// 监听路由参数
// 根据 id 获取用户信息
router.param('userId', getUserById)
module.exports = router
结束
接着就可以开发客户端应用使用这些接口了。