适用场景:UniApp 商城 / 点餐小程序 / 电商购物车
核心设计:插件化架构 + 方法重写 + 前置拦截 + 自动校验 + DDD 无侵入扩展
🔖 目录
1. 前言:购物车业务痛点
在商城、外卖、点餐类小程序中,添加商品是高频核心逻辑。
随着业务迭代,会出现各种校验逻辑:
- 必须选择 SKU 规格才能加购
- 库存不足不能加购
- 单品限购、活动限购
- VIP 价格自动替换
- 区域限售、年龄限制、状态校验
传统写法会导致:
addGoodsItem方法越来越臃肿- 大量 if / else 堆砌
- 业务逻辑耦合严重,违反开闭原则
- 新增校验必须修改购物车源码
- 难以维护、难以测试、难以关闭某条规则
基于此,我们设计一套无侵入、可插拔、可卸载、可自动扫描的购物车前置拦截插件体系。
2. 设计思路:插件化 + 前置拦截机制
整体设计遵循三点:
- 不修改 ICart 原有代码,通过动态扩展实现
- 保存原始 addGoodsItem,重写方法执行拦截链
- 插件可安装、可卸载、可注册多个 拦截器
- 拦截器 支持中断流程、异常捕获、日志打印
执行流程:
调用 cart.addGoodsItem()
↓
依次执行所有前置 preparer 拦截器
↓
任意一个返回 false → 终止添加
↓
全部通过 → 执行原始添加逻辑
3. 完整实现:商品添加前置处理器插件
以下为可直接上线的企业级插件实现:
import { logger } from "@/th4/mall/core/ILogger";
/**
* 商品添加前置处理器插件
* 支持:拦截、校验、修改参数、中断流程
*/
export const GoodsItemPreparer = {
/**
* 插件ID
*/
id: "GoodsItemPreparer",
/**
* 安装插件
* @param cart
*/
install(cart: mall.Cart) {
// 类型断言,扩展临时属性
const $cart = cart as mall.Cart & {
__goodsItemPreparers: Array<(goodsItem: any, num: number, options?: any) => boolean>
__addGoodsItem: typeof cart.addGoodsItem
addGoodsItemPreparer: (goodsItemPreparer: (goodsItem: any, num: number, options?: any) => boolean) => void
removeGoodsItemPreparer: (goodsItemPreparer: (goodsItem: any, num: number, options?: any) => boolean) => void
}
// 保存原始方法
$cart.__addGoodsItem = cart.addGoodsItem;
// 初始化拦截器队列
$cart.__goodsItemPreparers = [];
/**
* 注册商品添加前置处理器
*/
$cart.addGoodsItemPreparer = (goodsItemPreparer) => {
if (!$cart.__goodsItemPreparers.includes(goodsItemPreparer)) {
$cart.__goodsItemPreparers.push(goodsItemPreparer);
logger.debug(`${this.id} 插件已添加该商品添加前置处理器`);
} else {
logger.warn(`${this.id} 插件已存在该处理器,请勿重复添加`);
}
}
/**
* 移除前置处理器
*/
$cart.removeGoodsItemPreparer = (goodsItemPreparer) => {
$cart.__goodsItemPreparers = $cart.__goodsItemPreparers.filter(item => item !== goodsItemPreparer);
}
/**
* 重写添加商品方法,插入拦截逻辑
*/
$cart.addGoodsItem = (goodsItem: any, num: number, options?: any) => {
for (let goodsItemPreparer of $cart.__goodsItemPreparers) {
try {
// 返回 false 则中断添加
if (!goodsItemPreparer(goodsItem, num, options)) {
logger.warn(`${this.id} 插件已取消添加商品`);
return;
}
} catch (e) {
logger.error(`${this.id} 插件执行处理器异常`, e);
return;
}
}
// 所有校验通过,执行原始添加
$cart.__addGoodsItem(goodsItem, num, options);
}
},
/**
* 卸载插件,还原购物车
*/
uninstall(cart: mall.Cart) {
const $cart = cart as mall.Cart & {
__goodsItemPreparers?: any
__addGoodsItem?: any
}
// 还原方法
if ($cart.__addGoodsItem) {
$cart.addGoodsItem = $cart.__addGoodsItem;
delete $cart.__addGoodsItem;
}
// 清理注入属性
if ($cart.__goodsItemPreparers) {
delete $cart.__goodsItemPreparers;
}
delete $cart.addGoodsItemPreparer;
delete $cart.removeGoodsItemPreparer;
logger.info(`${this.id} 插件已卸载`)
}
}
/**
* TS 类型扩展(可选属性,不强制实现)
*/
declare global {
namespace mall {
interface Cart {
/** 注册商品添加前置处理器 */
addGoodsItemPreparer?(goodsItemPreparer: (goodsItem: any, num: number, options?: any) => boolean): void
/** 移除商品添加前置处理器 */
removeGoodsItemPreparer?(goodsItemPreparer: (goodsItem: any, num: number, options?: any) => boolean): void
}
}
}
4. 关键细节与 TS 类型扩展
4.1 为什么用 __ 开头的属性?
- 表示内部临时属性
- 避免污染业务领域模型
- 卸载时可完整清理
4.2 TS 接口扩展必须加 ?
addGoodsItemPreparer?(...) => void
不加 ? 会导致 ICart implements mall.Cart 时报错,必须实现该方法。
加 ? 表示可选方法,由插件动态注入。
4.3 异常安全
- 每个拦截器独立 try/catch
- 单个拦截器报错不会导致整个购物车崩溃
- 日志统一输出,便于排查
5. 拦截器实战用法(四大常用场景)
5.1 安装插件
const cart = new ICart()
GoodsItemPreparer.install(cart)
5.2 SKU 规格必须选择拦截
cart.addGoodsItemPreparer((goods, num, options) => {
if (goods.isSku && !options) {
uni.showToast({ title: '请选择商品规格', icon: 'none' })
return false
}
return true
})
5.3 库存不足拦截
cart.addGoodsItemPreparer((goods, num) => {
if (goods.stock < num) {
uni.showToast({ title: '库存不足', icon: 'none' })
return false
}
return true
})
5.4 商品限购拦截
cart.addGoodsItemPreparer((goods, num) => {
const current = cart.getCartItems().find(i => i.id === goods.id)?.num ?? 0
if (current + num > goods.limitCount) {
uni.showToast({ title: `限购${goods.limitCount}件`, icon: 'none' })
return false
}
return true
})
5.5 VIP 自动改价
cart.addGoodsItemPreparer((goods) => {
if (goods.isVip) {
goods.price = goods.vipPrice
}
return true
})
6. 架构优势总结
✅ 完全无侵入:不修改 ICart 任何代码
✅ 插件化设计:可安装、可卸载、可开关
✅ 支持多 拦截器:按顺序执行,任意可中断
✅ 异常安全:单个拦截器异常不崩溃全局
✅ 日志完善:便于线上排查与监控
✅ 遵循 开闭原则:新增规则无需改动购物车
✅ TS 类型安全:智能提示、无编译报错
✅ 适用于复杂业务:点餐、商城、零售、连锁门店通用
7. 常见问题 FAQ
❓ 1. 为什么 ICart 不需要实现 addGoodsItemPreparer?
因为在类型扩展中使用了 ? 可选标记,属于动态注入方法。
❓ 2. 重复安装插件会怎样?
内部已做重复判断,不会重复注册拦截器,不会多次重写方法。
❓ 3. 如何关闭某条校验规则?
cart.removeGoodsItemPreparer(handler)
❓ 4. 如何完全关闭整个插件?
GoodsItemPreparer.uninstall(cart)
❓ 5. 能否支持异步拦截?
可以,下一讲我会带来 异步 拦截器 + 接口校验 版本。
📢 文末互动
本文基于 DDD ****领域驱动设计 + 插件化架构,在实际生产项目中已稳定运行。
✅ 点赞 + 收藏 + 关注,持续更新 UniApp 实战架构
💬 评论区留下你的业务场景,我帮你设计专属拦截器