29 KiB
设计模式
单例模式
懒汉
public class LazySingleton {
private static volatile LazySingleton instance;
private LazySingleton() {}
public static LazySingleton getInstance() {
if (instance == null) { // 第一次检查
synchronized (LazySingleton.class) {
if (instance == null) { // 第二次检查
instance = new LazySingleton();
}
}
}
return instance;
}
}
第一次检查:防止重复实例化、以及进行synchronized同步块。
第二次检查:防止有多个线程同时通过第一次检查,然后依次进入同步块后,创建N个实例。
volatile:防止指令重排序,instance = new LazySingleton(); 正确顺序是:
1.分配内存
2.调用构造函数,初始化对象
3.把引用赋给 instance
饿汉
public class EagerSingleton {
// 类加载时就初始化实例
private static final EagerSingleton INSTANCE = new EagerSingleton();
// 私有构造函数
private EagerSingleton() {
// 防止反射创建实例
if (INSTANCE != null) {
throw new IllegalStateException("Singleton already initialized");
}
}
// 全局访问点
public static EagerSingleton getInstance() {
return INSTANCE;
}
// 防止反序列化破坏单例
private Object readResolve() {
return INSTANCE;
}
}
工厂模式
简单工厂
// 产品接口
interface Product {
void use();
}
// 具体产品A
class ConcreteProductA implements Product {
@Override
public void use() {
System.out.println("使用产品A");
}
}
// 具体产品B
class ConcreteProductB implements Product {
@Override
public void use() {
System.out.println("使用产品B");
}
}
class SimpleFactory {
// 根据参数创建不同的产品
public static Product createProduct(String type) {
switch (type) {
case "A":
return new ConcreteProductA();
case "B":
return new ConcreteProductB();
default:
throw new IllegalArgumentException("未知产品类型");
}
}
}
public class Client {
public static void main(String[] args) {
// 通过工厂创建产品
Product productA = SimpleFactory.createProduct("A");
productA.use(); // 输出: 使用产品A
Product productB = SimpleFactory.createProduct("B");
productB.use(); // 输出: 使用产品B
}
}
缺点:添加新产品需要修改工厂类(违反开闭原则)
抽象工厂
抽象工厂模式是一种创建型设计模式,它提供一个接口用于创建相关或依赖对象的家族,而不需要明确指定具体类。
// 抽象产品接口
interface Button {
void render();
}
interface Checkbox {
void render();
}
// 具体产品实现 - Windows 风格
class WindowsButton implements Button {
@Override
public void render() {
System.out.println("渲染一个 Windows 风格的按钮");
}
}
class WindowsCheckbox implements Checkbox {
@Override
public void render() {
System.out.println("渲染一个 Windows 风格的复选框");
}
}
// 具体产品实现 - MacOS 风格
class MacOSButton implements Button {
@Override
public void render() {
System.out.println("渲染一个 MacOS 风格的按钮");
}
}
class MacOSCheckbox implements Checkbox {
@Override
public void render() {
System.out.println("渲染一个 MacOS 风格的复选框");
}
}
// 抽象工厂接口
interface GUIFactory {
Button createButton();
Checkbox createCheckbox();
}
// 具体工厂实现 - Windows
class WindowsFactory implements GUIFactory {
@Override
public Button createButton() {
return new WindowsButton();
}
@Override
public Checkbox createCheckbox() {
return new WindowsCheckbox();
}
}
// 具体工厂实现 - MacOS
class MacOSFactory implements GUIFactory {
@Override
public Button createButton() {
return new MacOSButton();
}
@Override
public Checkbox createCheckbox() {
return new MacOSCheckbox();
}
}
// 客户端代码
public class Application {
private Button button;
private Checkbox checkbox;
public Application(GUIFactory factory) {
button = factory.createButton();
checkbox = factory.createCheckbox();
}
public void render() {
button.render();
checkbox.render();
}
public static void main(String[] args) {
// 根据配置或环境选择工厂
GUIFactory factory;
String osName = System.getProperty("os.name").toLowerCase();
if (osName.contains("win")) {
factory = new WindowsFactory();
} else {
factory = new MacOSFactory();
}
Application app = new Application(factory);
app.render();
}
}
模板方法
核心思想:
在抽象父类中定义算法骨架(固定执行顺序),把某些可变步骤留给子类重写;调用方只用模板方法,保证流程一致。
如果仅仅是把重复的方法抽取成公共函数,不叫模板方法!模板方法要设计算法骨架!!!
Client ───▶ AbstractClass
├─ templateMethod() ←—— 固定流程
│ step1()
│ step2() ←—— 抽象,可变
│ step3()
└─ hookMethod() ←—— 可选覆盖
▲
│ extends
┌──────────┴──────────┐
│ ConcreteClassA/B… │
示例:
// 1. 抽象模板
public abstract class AbstractDialog {
// 模板方法:固定调用顺序,设为 final 防止子类改流程
public final void show() {
initLayout();
bindEvent();
beforeDisplay(); // 钩子,可选
display();
afterDisplay(); // 钩子,可选
}
// 具体公共步骤
private void initLayout() {
System.out.println("加载通用布局文件");
}
// 需要子类实现的抽象步骤
protected abstract void bindEvent();
// 钩子方法,默认空实现
protected void beforeDisplay() {}
protected void afterDisplay() {}
private void display() {
System.out.println("弹出对话框");
}
}
// 2. 子类:登录对话框
public class LoginDialog extends AbstractDialog {
@Override
protected void bindEvent() {
System.out.println("绑定登录按钮事件");
}
@Override
protected void afterDisplay() {
System.out.println("focus 到用户名输入框");
}
}
// 3. 调用
public class Demo {
public static void main(String[] args) {
AbstractDialog dialog = new LoginDialog();
dialog.show();
/* 输出:
加载通用布局文件
绑定登录按钮事件
弹出对话框
focus 到用户名输入框
*/
}
}
要点
- 复用公共流程:
initLayout()
、display()
写一次即可。 - 限制流程顺序:
show()
定为final
,防止子类乱改步骤。 - 钩子方法:子类可选择性覆盖(如
beforeDisplay
)。
策略模式
核心思想:
将可以互换的算法或行为抽象为独立的策略类,运行时由**上下文类(Context)**选择合适的策略对象去执行。调用方(Client)只依赖统一的接口,不关心具体实现。
┌───────────────┐
│ Client │
└─────▲─────────┘
│ has-a
┌─────┴─────────┐ implements
│ Context │────────────┐ ┌──────────────┐
│ (使用者) │ strategy └─▶│ Strategy A │
└───────────────┘ ├──────────────┤
│ Strategy B │
└──────────────┘
// 策略接口
public interface PaymentStrategy {
void pay(int amount);
}
// 策略A:微信支付
@Service("wechat")
public class WechatPay implements PaymentStrategy {
public void pay(int amount) {
System.out.println("使用微信支付 " + amount + " 元");
}
}
// 策略B:支付宝支付
@Service("alipay")
public class Alipay implements PaymentStrategy {
public void pay(int amount) {
System.out.println("使用支付宝支付 " + amount + " 元");
}
}
// 上下文类
public class PaymentContext {
private PaymentStrategy strategy;
public PaymentContext(PaymentStrategy strategy) {
this.strategy = strategy;
}
public void execute(int amount) {
strategy.pay(amount);
}
}
// 调用方
public class Main {
public static void main(String[] args) {
PaymentContext ctx = new PaymentContext(new WechatPay());
ctx.execute(100);
ctx = new PaymentContext(new Alipay());
ctx.execute(200);
}
}
下面有更优雅的策略选择方式!
Spring集合自动注入
在策略、工厂、插件等模式中,经常需要维护**“策略名 → 策略对象”**的映射。Spring 可以通过 Map<String, 接口类型>
一次性注入所有实现类。
@Resource
private Map<String, IDiscountCalculateService> discountCalculateServiceMap;
字段类型:Map<String, IDiscountCalculateService>
- key—— Bean 的名字
- 默认是类名首字母小写 (
mjCalculateService
) - 或者你在实现类上显式写的
@Service("MJ")
- 默认是类名首字母小写 (
- value —— 那个实现类对应的实例
- Spring 机制:
- 启动时扫描所有实现
IDiscountCalculateService
的 Bean。 - 把它们按 “BeanName → Bean 实例” 的映射注入到这张
Map
里。 - 你一次性就拿到了“策略字典”。
- 启动时扫描所有实现
示例:
// 上下文类:自动注入所有策略 Bean
@Component
@RequiredArgsConstructor
public class PaymentContext {
// key 为 Bean 名(如 "wechat"、"alipay"),value 为策略实例
private final Map<String, PaymentStrategy> paymentStrategyMap;
public void pay(String strategyKey, int amount) {
PaymentStrategy strategy = paymentStrategyMap.get(strategyKey);
if (strategy == null) {
throw new IllegalArgumentException("无匹配支付方式: " + strategyKey);
}
strategy.pay(amount);
}
}
// 调用方示例
@Component
@RequiredArgsConstructor
public class PaymentService {
private final PaymentContext paymentContext;
public void process() {
paymentContext.pay("wechat", 100); // 输出:使用微信支付 100 元
paymentContext.pay("alipay", 200); // 输出:使用支付宝支付 200 元
}
}
模板方法+策略模式
本项目的价格试算同时用了策略模式 + 模板方法模式:
策略模式(Strategy):
IDiscountCalculateService
是策略接口;ZKCalculateService
、ZJCalculateService
...是可替换的折扣策略(@Service("ZK") / @Service("ZJ") 作为选择键)。外部可以根据活动配置里的类型码选哪个实现来算价——这就是“运行时可切换算法”。
模板方法模式(Template Method):
AbstractDiscountCalculateService#calculate(...)
把共同流程固定下来(先进行人群校验 → 计算优惠后价格),并把“真正的计算”这一步延迟到子类通过 doCalculate(...)
实现。
责任链
应用场景:日志系统、审批流程、权限校验——任何需要将请求按阶段传递、并由某一环节决定是否继续或终止处理的地方,都非常适合责链模式。
典型的责任链模式要点:
- 解耦请求发送者和处理者:调用者只持有链头,不关心中间环节。
- 动态组装:通过
appendNext
可以灵活地增加、删除或重排链上的节点。 - 可扩展:新增处理逻辑只需继承
AbstractLogicLink
并实现apply
,不用改动已有代码。
单实例链
可以理解成“单向、单链表式的链条”:每个节点只知道自己的下一个节点(next
),链头只有一个入口。
你可以在启动或运行时动态组装:head.appendNext(a).appendNext(b).appendNext(c);
T / D / R 是啥?
T
:请求的静态入参(本次请求的主要数据)。D
:动态上下文(链路里各节点共享、可读写的状态容器,比如日志收集、校验中间结果)。R
:最终返回结果类型。
1)接口定义:ILogicChainArmory<T, D, R>
提供添加节点方法和获取节点
// 定义了“链条组装”的最小能力:能拿到下一个节点、也能把下一个节点接上去
public interface ILogicChainArmory<T, D, R> {
// 获取当前节点的“下一个”处理者
ILogicLink<T, D, R> next();
// 把新的处理者挂到当前节点后面,并返回它(方便链式 append)
ILogicLink<T, D, R> appendNext(ILogicLink<T, D, R> next);
}
2)ILogicLink<T, D, R>
继承自 ILogicChainArmory<T, D, R>
,并额外声明了核心方法 apply
// 真正的“处理节点”接口:在具备链条组装能力的基础上,还要能“处理请求”
public interface ILogicLink<T, D, R> extends ILogicChainArmory<T, D, R> {
R apply(T requestParameter, D dynamicContext) throws Exception;
}
3)抽象基类:AbstractLogicLink
,提供了责任链节点的通用骨架,(保存 next
、实现 appendNext
/next()
、以及一个便捷的 protected next(...)
,这样具体的节点类就不用重复这些代码,真正的业务处理逻辑仍然交由子类去实现 apply(...)
。
// 抽象基类:大多数节点都可以继承它,避免重复写“组装链”的样板代码
public abstract class AbstractLogicLink<T, D, R> implements ILogicLink<T, D, R> {
// 指向“下一个处理者”的引用
private ILogicLink<T, D, R> next;
@Override
public ILogicLink<T, D, R> next() {
return next;
}
@Override
public ILogicLink<T, D, R> appendNext(ILogicLink<T, D, R> next) {
this.next = next;
return next; // 返回 next 以便连续 append,类似 builder
}
/**
* 便捷方法:当前节点决定“交给下一个处理者”
*/
protected R next(T requestParameter, D dynamicContext) throws Exception {
// 直接把请求丢给下一个节点继续处理
// 注意:这里假设 next 一定存在;实际项目里建议判空以免 NPE(见下文改进建议)
return next.apply(requestParameter, dynamicContext);
}
}
子类只需要继承 AbstractLogicLink
并实现 apply(...)
:
- 能处理就处理(并可选择直接返回,终止链条)。
- 不处理或处理后仍需后续动作,就
return next(requestParameter, dynamicContext)
继续传递。
4)实现子类
@Component
public class AuthLink extends AbstractLogicLink<Request, Context, Response> {
@Override
public Response apply(Request req, Context ctx) throws Exception {
if (!ctx.isAuthenticated()) {
// 未认证:立刻终止;也可以在这里构造一个标准错误响应返回
throw new UnauthorizedException();
}
// 认证通过,继续下一个环节
return next(req, ctx);
}
}
@Component
public class LoggingLink extends AbstractLogicLink<Request, Context, Response> {
@Override
public Response apply(Request req, Context ctx) throws Exception {
System.out.println("Request received: " + req);
return next(req, ctx);
}
}
@Component
public class BusinessLogicLink extends AbstractLogicLink<Request, Context, Response> {
@Override
public Response apply(Request req, Context ctx) throws Exception {
// 业务逻辑...
return new Response(...);
}
}
5)组装链
@Configuration
@RequiredArgsConstructor
public class LogicChainFactory {
private final AuthLink authLink;
private final LoggingLink loggingLink;
private final BusinessLogicLink businessLogicLink;
@Bean
public ILogicLink<Request, Context, Response> logicChain() {
return authLink
.appendNext(loggingLink)
.appendNext(businessLogicLink);
}
}
示例图:
AuthLink.apply
└─▶ LoggingLink.apply
└─▶ BusinessLogicLink.apply
└─▶ 返回 Response
这种模式链上的每个节点都手动 next()
到下一节点。
多实例链1
以上是单例链,即只能创建一条链;比如A->B->C,不能创建别的链,因为节点Bean是单例的,如果创别的链会导致指针引用错误!!!
如果想变成多例链:
1)节点由默认的单例模式改为原型模式:
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class A extends AbstractLogicLink<Req, Ctx, Resp> { ... }
2)组装链的时候注明不同链的bean名称:
/** 全局唯一链:A -> B -> C */
@Bean("chainABC")
public ILogicLink<Req, Ctx, Resp> chainABC() {
A a = aProvider.getObject();
B b = bProvider.getObject();
C c = cProvider.getObject();
return a.appendNext(b).appendNext(c); // 返回链头 a
}
/** 全局唯一链:A -> C */
@Bean("chainAC")
public ILogicLink<Req, Ctx, Resp> chainAC() {
A a = aProvider.getObject();
C c = cProvider.getObject();
return a.appendNext(c); // 返回链头 a(另一套实例)
}
多实例链2
/**
* 通用逻辑处理器接口 —— 责任链中的「节点」要实现的核心契约。
*/
public interface ILogicHandler<T, D, R> {
/**
* 默认的 next占位实现,方便节点若不需要向后传递时直接返回 null。
*/
default R next(T requestParameter, D dynamicContext) {
return null;
}
/**
* 节点的核心处理方法。
*/
R apply(T requestParameter, D dynamicContext) throws Exception;
}
/**
* 业务链路容器 —— 双向链表实现,同时实现 ILogicHandler,从而可以被当作单个节点使用。
*/
public class BusinessLinkedList<T, D, R> extends LinkedList<ILogicHandler<T, D, R>> implements ILogicHandler<T, D, R>{
public BusinessLinkedList(String name) {
super(name);
}
/**
* BusinessLinkedList是头节点,它的apply方法就是循环调用后面的节点,直至返回。
* 遍历并执行链路。
*/
@Override
public R apply(T requestParameter, D dynamicContext) throws Exception {
Node<ILogicHandler<T, D, R>> current = this.first;
// 顺序执行,直到链尾或返回结果
while (current != null) {
ILogicHandler<T, D, R> handler = current.item;
R result = handler.apply(requestParameter, dynamicContext);
if (result != null) {
// 节点命中,立即返回
return result;
}
//result==null,则交给那一节点继续处理
current = current.next;
}
// 全链未命中
return null;
}
}
/**
* 链路装配工厂 —— 负责把一组 ILogicHandler 顺序注册到 BusinessLinkedList 中。
*/
public class LinkArmory<T, D, R> {
private final BusinessLinkedList<T, D, R> logicLink;
/**
* @param linkName 链路名称,便于日志排查
* @param logicHandlers 节点列表,按传入顺序链接
*/
@SafeVarargs
public LinkArmory(String linkName, ILogicHandler<T, D, R>... logicHandlers) {
logicLink = new BusinessLinkedList<>(linkName);
for (ILogicHandler<T, D, R> logicHandler: logicHandlers){
logicLink.add(logicHandler);
}
}
/** 返回组装完成的链路 */
public BusinessLinkedList<T, D, R> getLogicLink() {
return logicLink;
}
}
//工厂类,可以定义多条责任链,每条有自己的Bean名称区分。
@Bean("tradeRuleFilter")
public BusinessLinkedList<TradeRuleCommandEntity, DynamicContext, TradeRuleFilterBackEntity> tradeRuleFilter(ActivityUsabilityRuleFilter activityUsabilityRuleFilter, UserTakeLimitRuleFilter userTakeLimitRuleFilter) {
// 1. 组装链
LinkArmory<TradeRuleCommandEntity, DynamicContext, TradeRuleFilterBackEntity> linkArmory =
new LinkArmory<>("交易规则过滤链", activityUsabilityRuleFilter, userTakeLimitRuleFilter);
// 2. 返回链容器(即可作为责任链使用)
return linkArmory.getLogicLink();
}
示例图:
BusinessLinkedList.apply ←─ 只有这一层在栈里
while 循环:
├─▶ 调用 ActivityUsability.apply → 返回 null → 继续
├─▶ 调用 UserTakeLimit.apply → 返回 null → 继续
└─▶ 调用 ... → 返回 Result → break
链头拿着“游标”一个个跑,节点只告诉“命中 / 未命中”。
这里无需把节点改为原型模式,也可以实现多例链,因为由双向链表BusinessLinkedList
负责保存链路关系和推进执行,而ILogicHandler
节点本身不再保存 next
指针,所以它们之间没有共享可变状态。
本项目中使用多实例链2,有以下场景:
一、拼团「锁单前」校验链
目标:在真正锁单前把“活动有效性 / 用户参与资格 / 可用库存”一口气校清楚,避免后续回滚。
1.活动有效性校验 ActivityUsability
(当前时间是否早于活动截止时间)
2.用户可参与活动次数校验 UserTakeLimitRuleFilter
(默认用户只可参与一次拼团)
3.剩余库存校验 TeamStockOccupyRuleFilter
(可能同时有多人点击参与当前拼团,尝试抢占库存,仅部分人可通过校验。)
校验通过方可进行真正的锁单。
二、交易结算校验链
1.渠道黑名单校验 SCRuleFilter
:某签约渠道下架/风控拦截,禁止结算。
2.外部单号校验 OutTradeNoRuleFilter
:查营销订单;不存在或已退单(CLOSE
)→ 不结算。
3.可结算时间校验 SettableRuleFilter
:结算时间必须在拼团有效期内(outTradeTime < team.validEndTime
),比如发起
拼团一个小时之内要结算完毕。
4.结束节点EndRuleFilter
:整理上下文到返回对象,作为结算规则校验的产出。
检验通过方可进入真正的结算。
三、交易退单执行链
1.数据加载 DataNodeFilter
:按 userId + outTradeNo
查询营销订单与拼团信息,写入上下文。
2.重复退单检查 UniqueRefundNodeFilter
:订单已是 CLOSE
→ 视为幂等重复,直接返回。
3.退单策略执行 RefundOrderNodeFilter
:依据“拼团态 + 订单态”选用具体退单策略 IRefundOrderStrategy
,执行退款/解锁/改库并返回成功结果。
本身就是完整的退单流程。
规则树流程
整体分层思路
分层 | 作用 | 关键对象 |
---|---|---|
通用模板层 | 抽象出与具体业务无关的「规则树」骨架,解决 如何找到并执行策略 的共性问题 | StrategyMapper 、StrategyHandler 、AbstractStrategyRouter<T,D,R> |
业务装配层 | 基于模板,自由拼装出 一棵 贴合业务流程的策略树 | RootNode / SwitchNode / MarketNode / EndNode … |
对外暴露层 | 通过 工厂 + 服务支持类 将整棵树封装成一个可直接调用的 StrategyHandler ,并交给 Spring 整体托管 |
DefaultActivityStrategyFactory 、AbstractGroupBuyMarketSupport |
通用模板层:规则树的“骨架”
角色 | 职责 | 关系 |
---|---|---|
StrategyMapper |
映射器:依据 requestParameter + dynamicContext 选出 下一个 策略节点 |
被 AbstractStrategyRouter 调用 |
StrategyHandler |
处理器:真正执行业务逻辑;apply 结束后可返回结果或继续路由 |
节点本身 / 路由器本身都是它的实现 |
AbstractStrategyRouter<T,D,R> |
路由模板:① 调用 get(...) 找到合适的 StrategyHandler ;② 调用该 handler 的 apply(...) ;③ 若未命中则走 defaultStrategyHandler |
同时实现 StrategyMapper 与 StrategyHandler ,但自身保持 抽象,把细节延迟到子类 |
业务装配层:一棵可编排的策略树
RootNode -> SwitchNode -> MarketNode -> EndNode
↘︎ OtherNode ...
-
每个节点
继承
AbstractGroupBuyMarketSupport
(业务基类)- 实现
get(...)
:决定当前节点的下一跳是哪一个节点 - 实现
apply(...)
:实现节点自身应做的业务动作(或继续下钻)
- 实现
-
组合方式
- 路由是“数据驱动”的:并非工厂把链写死,而是节点在运行期根据
request + context
决定下一跳(可能是ERROR_NODE或END_NODE),灵活插拔。
- 路由是“数据驱动”的:并非工厂把链写死,而是节点在运行期根据
对外暴露层:工厂 + 服务支持类
组件 | 主要职责 |
---|---|
DefaultActivityStrategyFactory (@Service ) |
仅负责把 RootNode 暴露为 StrategyHandler 入口(交由 Spring 管理,方便注入)。 |
AbstractGroupBuyMarketSupport |
业务服务基类:封装拼团场景下共用的查询、工具方法;供每个节点继承使用 |
本项目执行总览:
调用入口:factory.strategyHandler()
→ 返回 RootNode
(实现了 StrategyHandler
)。
执行流程:
apply(...)
:模板入口,先跑 multiThread(...)
预取/并发任务,再跑 doApply(...)
。
doApply(...)
:每个节点自己的业务;通常在末尾调用 router(...)
继续下一个节点(你现在就是这样写的:return router(request, ctx);
)。也可以在某些节点“短路返回”,不再路由。
router(...)
:内部调用当前节点的 get(...)
来挑选下一节点next
,若存在就调用 next.apply(...)
递归推进;若不存在(或是到达 EndNode
),则收束返回。
RootNode
- 校验必填:
userId/goodsId/source/channel
。 - 合法则路由到
SwitchNode
;非法直接抛ILLEGAL_PARAMETER
。
SwitchNode(总开关、不区分活动,做总体的降级限流)
- 调用
repository.downgradeSwitch()
判断是否降级;是则抛E0003
。 - 调用
repository.cutRange(userId)
做切量;不在范围抛E0004
。 - 通过后路由到
MarketNode
。
MarketNode
- multiThread(...) 中并发拉取:
- 拼团活动配置
GroupBuyActivityDiscountVO
- 商品信息
SkuVO
- 写入
DynamicContext
- 拼团活动配置
- doApply(...)
- 读取配置 + SKU,按
marketPlan
选IDiscountCalculateService
,计算payPrice
/deductionPrice
并写回上下文。
- 读取配置 + SKU,按
- 路由判定:
- 若配置/商品/
deductionPrice
有缺失 →ErrorNode
- 否则 →
TagNode
- 若配置/商品/
TagNode(业务相关,部分人不在本次活动范围内!)
- 若活动没配置
tagId
→ 视为不限定人群:visible=true
、enable=true
。 - 否则通过
repository.isTagCrowdRange(tagId, userId)
判断是否在人群内,并据此更新visible/enable
。 - 路由到
EndNode
。
EndNode
- 从
DynamicContext
读取:skuVO / payPrice / deductionPrice / groupBuyActivityDiscountVO / visible / enable
; - 构建并返回最终的
TrialBalanceEntity
,链路终止。
ErrorNode
- 统一异常出口;若无配置/无商品,抛
E0002
;否则可返回空结果作为兜底; - 返回后走
defaultStrategyHandler
(结束)。