React+Redux+Ant Design+TypeScript 电子商务实战-服务端应用 06 订单管理api和其它api

创建订单 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订单中的产品集合
├─── _idmongoose 默认分配的 id 字段
├─── count产品购买数量
├─── product关联产品信息(同产品详情返回对象)
├─── snapshot产品快照信息(同产品详情返回对象,并填充了产品分类的信息)
├─ user订单所属用户信息
├─── _idmongoose 默认分配的 id 字段
├─── name用户昵称
├─ createdAt创建 Schema 时指定分配的自动管理的 createdAt 字段
├─ updatedAt创建 Schema 时指定分配的自动管理的 updatedAt 字段
├─ _idmongoose 默认分配的 id 字段
├─ _vmongoose 默认分配的 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

参数名称是否必传类型说明
orderIdString订单 ID
statusString订单状态

返回数据

返回一个成功提示

定义 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

结束

接着就可以开发客户端应用使用这些接口了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值