场景 1:订单模块 - “能否取消订单”的复杂业务规则判断
业务背景: 用户下单后,想取消订单。但取消不是随时都能取消的,得满足一堆业务规则:
- 订单状态必须是“已支付”或“待发货”
- 未超过可取消时间窗口(比如支付后7天内)
- 如果是虚拟商品(如门票),超过活动开始前24小时就不能取消
- 如果订单包含“赠品”,且赠品已发货,就不能取消
- VIP用户有特权,可以延长取消窗口到15天
不用 Specification 的传统写法(痛点):
Java
public boolean canCancel(Order order, User user) {
if (order.getStatus() != PAID && order.getStatus() != PENDING_SHIP) {
return false;
}
if (order.getCreateTime().plusDays(7).isBefore(now())) {
return false;
}
if (order.isVirtual() && order.getActivityStartTime().minusHours(24).isBefore(now())) {
return false;
}
if (order.hasGift() && order.getGiftShipped()) {
return false;
}
if (user.isVip()) {
// VIP 特权逻辑
if (order.getCreateTime().plusDays(15).isBefore(now())) {
return false;
}
}
return true;
}痛点:
- 规则全堆在一个大 if-else 方法里,代码越来越长,可读性差
- 新增一个规则(如“节日活动订单不可取消”),就要改这个方法,容易漏判
- 想复用“VIP可延长取消时间”这个子规则,在其他地方(如退款判断)用不了
- 测试麻烦:要测所有组合,得写一大堆测试用例覆盖分支
用 Specification 的优雅写法:
java
// 原子规则(每个规则独立、可复用、可单独测试)
public class PaidOrPendingShipSpec implements Specification<Order> {
public boolean isSatisfiedBy(Order order) {
return order.getStatus() == PAID || order.getStatus() == PENDING_SHIP;
}
}
public class WithinCancelWindowSpec implements Specification<Order> {
private final int days;
public WithinCancelWindowSpec(int days) { this.days = days; }
public boolean isSatisfiedBy(Order order) {
return order.getCreateTime().plusDays(days).isAfter(now());
}
}
public class NotStartedVirtualActivitySpec implements Specification<Order> {
public boolean isSatisfiedBy(Order order) {
if (!order.isVirtual()) return true;
return order.getActivityStartTime().minusHours(24).isAfter(now());
}
}
public class GiftNotShippedSpec implements Specification<Order> {
public boolean isSatisfiedBy(Order order) {
return !order.hasGift() || !order.getGiftShipped();
}
}
public class VipExtendedCancelSpec implements Specification<Order> {
public boolean isSatisfiedBy(Order order, User user) { // 可传入上下文
if (!user.isVip()) return true; // 非VIP不影响
return order.getCreateTime().plusDays(15).isAfter(now());
}
}
// 使用时动态组合(超级清晰!)
Specification<Order> cancelSpec = new PaidOrPendingShipSpec()
.and(new WithinCancelWindowSpec(7))
.and(new NotStartedVirtualActivitySpec())
.and(new GiftNotShippedSpec());
if (user.isVip()) {
cancelSpec = cancelSpec.and(new VipExtendedCancelSpec());
}
if (cancelSpec.isSatisfiedBy(order)) {
order.cancel();
}好处:
- 每个规则独立成类,命名就是业务语言(通用语言)
- 新增规则只需加一个新类,不会改动原有代码(开闭原则)
- 子规则可复用(比如 WithinCancelWindowSpec 还能用于退款判断)
- 测试简单:每个 Spec 可以单独单元测试
场景 2:商品模块 - “商品能否发布/上架”的多规则校验
业务背景: 商家发布商品,上架前要通过一系列审核规则:
- 主图必须上传
- 商品标题不能含敏感词
- 价格必须合理(不能低于成本价的80%)
- 库存必须大于0
- 必须选择正确的类目
- 如果是食品类,必须上传质检报告
- 管理员手动审核通过
不用 Specification:一个巨大的 validate() 方法,里面一堆 if,维护噩梦。
用 Specification:
java
Specification<Product> publishSpec = new HasMainImageSpec()
.and(new NoSensitiveWordsInTitleSpec())
.and(new ReasonablePriceSpec(0.8)) // 价格不低于成本80%
.and(new HasStockSpec())
.and(new ValidCategorySpec());
if (product.isFood()) {
publishSpec = publishSpec.and(new HasQualityReportSpec());
}
publishSpec = publishSpec.and(new AdminApprovedSpec());
if (!publishSpec.isSatisfiedBy(product)) {
throw new BusinessException("商品不满足上架条件: " + publishSpec.getFailedReasons());
}好处:规则清晰可组合,食品类目自动加额外规则,超级灵活。
场景 3:权限模块 - 复杂权限判断
业务背景: 用户访问某个功能,需要判断是否有权限。权限规则可能很复杂:
- 有角色“ADMIN”就能访问
- 或者属于某个组织
- 或者是被邀请的嘉宾
- 或者付费开通了高级功能
用 Specification:
java
Specification<User> accessSpec = new HasRoleSpec("ADMIN")
.or(new InOrganizationSpec(orgId))
.or(new IsInvitedGuestSpec(eventId))
.or(new HasPaidFeatureSpec("PREMIUM"));
if (accessSpec.isSatisfiedBy(currentUser)) {
grantAccess();
}好处:OR 组合非常自然,新增一种权限方式只需加一个 Spec。
场景 4:风控模块 - 用户注册/登录风控拦截
业务背景: 防止刷号、恶意注册。规则:
- 同IP 1小时内注册不能超过3个
- 同设备ID 24小时内不能超过5个
- 手机号不能是黑名单
- 不能短时间内频繁尝试登录
用 Specification(内存执行,速度快):
java
Specification<RegisterRequest> riskSpec = new NotBlacklistedPhoneSpec()
.and(new IpRegisterFrequencySpec(3, Duration.ofHours(1)))
.and(new DeviceRegisterFrequencySpec(5, Duration.ofDays(1)));
if (riskSpec.isSatisfiedBy(request)) {
allowRegister();
} else {
reject("风控拦截");
}好处:风控规则经常调整,用 Specification 改起来特别方便。
场景 5:报表/统计模块 - 数据过滤(结合 JPA Specification)
业务背景: 后台运营看报表,想按多种条件过滤订单数据:时间段、订单状态、支付方式、用户类型、金额范围等。
这其实和你现在的活动搜索一模一样! 用 Specification:完全复用你现在的 Criteria + JPA Specification 模式,在数据库层面过滤,性能最优。
总结:Specification 到底解决了什么业务痛点?
| 痛点 | 传统写法 | Specification 解决方式 |
|---|---|---|
| 规则越来越多,代码膨胀 | 一个大方法堆满 if-else | 每个规则一个独立类 |
| 新增/修改规则要改核心代码 | 改动风险高 | 新增类或组合方式,不改旧代码 |
| 规则难以复用 | 复制粘贴代码 | 原子 Spec 可跨场景复用 |
| 可读性差 | 一长串条件判断 | 组合后像自然语言:paid.and(within7Days).and(notVirtualStarted) |
| 测试困难 | 覆盖所有分支难 | 每个 Spec 单独单元测试 |
核心价值:把“业务规则”从“过程式代码”变成“一等公民的可组合对象”。
你现在的活动搜索是用 Specification 的“数据库动态查询”能力, 而上面这些场景是用它的“纯业务规则校验 + 可组合”能力。
两者都是 Specification 的正确用法,只是执行位置不同(内存 vs 数据库)。
明白了吗?如果还有哪个场景不清晰,或者想看某个具体业务的完整代码示例,我可以再详细展开!🚀