1.营销活动需求
2.责任链模式介绍
3.用责任链模式编写营销活动的构思
4.编码
5.回顾与总结
1.营销活动需求
在一个电商项目中,营销活动是一个绕不开的功能。
在最简单的情况下,一般都是由业务方提出一个活动,然后程序员开发一个活动。但是随着项目的迭代,单个活动内的业务逻辑会愈来愈复杂,可能会出现如下问题
- 在一个活动中添加一个条件会就会导致巨大的改动,也不符合程序开发过程中的开闭原则。
- 来一种活动就要开发一种活动,工作量巨大。
- 得注意活动与活动之间的影响,代码混乱不堪。
- 活动比较不灵活,配置复杂,编码复杂,业务逻辑运行复杂。
为了解决上述这些问题,我们可以使用责任链模式,将所有活动条件(活动时间,适用门店,指定商品等)做成可配置项,使用拖拉拽的方式自定义每一种活动中的所有条件,而不用每次来一种活动就开发一种活动。
2.责任链模式介绍
责任链模式,旨在将一系列的责任对象串联成一个处理链(如同链表一样),将请求沿着这条处理链从上游到下游传递,直到处理完毕或者不能请求为止。责任链模式能够使用多个对象都有机会处理请求,避免请求的发送者和接受者之间的耦合关系。
- Handler抽象责任角色:负责定义具体的处理方法,并且要在该角色中定义nextHandler(下一个处理节点),责任链就如同链表,链表就含有next属性。同时我们要根据用户的配置方式,将多个责任类定义到一个责任链处理链条当中。
- ConcreteHandler具体责任角色:实现抽象责任角色的具体类,在这里就是所有活动的条件,比如按商品所选择的责任类,按门店所选择的责任类,按会员所选择的责任类等等。
- Client责任链的装配、使用者:Client再进行责任链的调用前,需要对责任链进行装配,就是将上述的多个责任类,通过nextHandler属性进行链接,因此Client角色的作用有两个:装配责任链,调用责任链头结点。
3.用责任链模式编写营销活动的构思
要用责任链模式编写营销活动,我们需要准备的工作如下:
- 编写每一个营销活动条件的责任类,里面有每一个责任类的具体业务处理逻辑。(活动时间责任类,活动适用门店责任类,活动范围责任类。)
活动表,里面包含这个营销活动的id,活动名称折扣值状态等基本信息。
CREATE TABLE `activity` ( `id` int NOT NULL AUTO_INCREMENT, `activity_name` varchar(30) COLLATE utf8_bin DEFAULT NULL COMMENT '活动名称', `discount` decimal(10,4) DEFAULT NULL COMMENT '折扣', `status` int DEFAULT NULL COMMENT '状态0未开始 1进行中 2已结束', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime DEFAULT NULL COMMENT '更新时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='活动表';
活动明细类,返回这个活动对应的商品id,指定日期,指定门店等等
CREATE TABLE `activity_time_detail` ( `id` int NOT NULL AUTO_INCREMENT, `activity_id` int DEFAULT NULL COMMENT '活动id', `start_time` datetime DEFAULT NULL COMMENT '活动开始时间', `end_time` datetime DEFAULT NULL COMMENT '活动结束时间', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime DEFAULT NULL COMMENT '更新时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='活动时间表'; CREATE TABLE `activity_product_detail` ( `id` int NOT NULL AUTO_INCREMENT, `activity_id` int DEFAULT NULL COMMENT '活动id', `product_id` int DEFAULT NULL COMMENT '商品id', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime DEFAULT NULL COMMENT '更新时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='活动商品表'; CREATE TABLE `activity_store_detail` ( `id` int NOT NULL AUTO_INCREMENT, `activity_id` int DEFAULT NULL COMMENT '活动id', `store_id` int DEFAULT NULL COMMENT '活动匹配门店', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime DEFAULT NULL COMMENT '更新时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='活动门店表';
活动责任类明细表,包含这个活动拥有哪几个责任类。
CREATE TABLE `activity_condition_detail` ( `id` int NOT NULL AUTO_INCREMENT, `activity_id` int DEFAULT NULL COMMENT '活动id', `activity_condition_id` int DEFAULT NULL COMMENT '活动责任类id', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime DEFAULT NULL COMMENT '更新时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='活动条件明细表';
责任类表,通过活动明细查询责任类,再根据反射创建好责任类,方便调用。
CREATE TABLE `activity_condition_class` ( `id` int NOT NULL AUTO_INCREMENT, `condition_class` varchar(255) COLLATE utf8_bin DEFAULT NULL COMMENT '条件class全类名,反射用', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime DEFAULT NULL COMMENT '更新时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='活动责任类表';
以上全都用代码生成器生成对应service。
4.编码
抽象责任类:
package com.example.activity.service;
import com.example.activity.vo.HandlerConditionVo;
import java.math.BigDecimal;
public abstract class IActivityHandler {
public IActivityHandler nextHandler;
public boolean hasNext() {
return this.nextHandler != null;
}
/**
* 查询这个商品符不符合这个活动并且返回折扣率
*/
protected abstract BigDecimal handle(HandlerConditionVo handlerConditionVo);
}
创建具体责任类:
ActivityProductHandler按商品id的筛选责任类:
@Service public class ActivityProductHandler extends IActivityHandler { @Autowired private ActivityProductDetailService activityProductDetailService; @Autowired private ActivityService activityService; @Override public BigDecimal handle(HandlerConditionVo handlerConditionVo) { List<ActivityProductDetail> productDetails = activityProductDetailService.list(Wrappers.<ActivityProductDetail>lambdaQuery() .eq(ActivityProductDetail::getActivityId, handlerConditionVo.getActivityId()) .eq(ActivityProductDetail::getProductId, handlerConditionVo.getProductId())); //如果符合活动条件,则继续向下搜索 if (CollectionUtil.isNotEmpty(productDetails)) { if (hasNext()) { return nextHandler.handle(handlerConditionVo); } else { return activityService.getById(handlerConditionVo.getActivityId()).getDiscount(); } } //如果不符合条件,返回该商品折扣率为100% return new BigDecimal("100"); } }
ActivityStoreHandler按门店id的筛选责任类:
@Service public class ActivityStoreHandler extends IActivityHandler { @Autowired private ActivityStoreDetailService storeDetailService; @Autowired private ActivityService activityService; @Override public BigDecimal handle(HandlerConditionVo handlerConditionVo) { ActivityStoreDetail storeDetails = storeDetailService.getOne(Wrappers.<ActivityStoreDetail>lambdaQuery() .eq(ActivityStoreDetail::getStoreId, handlerConditionVo.getStoreId()) .eq(ActivityStoreDetail::getActivityId, handlerConditionVo.getActivityId()).last("limit 0,1")); //如果符合活动条件,则继续向下搜索 if (storeDetails != null) { if (hasNext()) { return nextHandler.handle(handlerConditionVo); } else { return activityService.getById(handlerConditionVo.getActivityId()).getDiscount(); } } //如果不符合条件,返回该商品折扣率为100% return new BigDecimal("100"); } }
ActivityTimeHandler按活动时间的筛选责任类:
@Service public class ActivityTimeHandler extends IActivityHandler { @Autowired private ActivityTimeDetailService timeDetailService; @Autowired private ActivityService activityService; @Override public BigDecimal handle(HandlerConditionVo handlerConditionVo) { List<ActivityTimeDetail> timeDetails = timeDetailService.list(Wrappers.<ActivityTimeDetail>lambdaQuery() .eq(ActivityTimeDetail::getActivityId, handlerConditionVo.getActivityId()) .ge(ActivityTimeDetail::getStartTime, LocalDateTime.now()) .le(ActivityTimeDetail::getEndTime, LocalDateTime.now())); //如果符合活动条件,则继续向下搜索 if (CollectionUtil.isNotEmpty(timeDetails)) { if (hasNext()) { return nextHandler.handle(handlerConditionVo); } else { return activityService.getById(handlerConditionVo.getActivityId()).getDiscount(); } } //如果不符合条件,返回该商品折扣率为100% return new BigDecimal("100"); } }
- client的具体初始化流程和统一调用入口
我们往数据库插入这三条数据:
INSERT INTO `test`.`activity_condition_class`(`id`, `condition_class`, `create_time`, `update_time`) VALUES (1, 'com.example.activity.service.impl.ActivityTimeHandler', '2024-07-10 19:48:03', NULL);
INSERT INTO `test`.`activity_condition_class`(`id`, `condition_class`, `create_time`, `update_time`) VALUES (2, 'com.example.activity.service.impl.ActivityStoreHandler', '2024-07-10 19:48:22', NULL);
INSERT INTO `test`.`activity_condition_class`(`id`, `condition_class`, `create_time`, `update_time`) VALUES (3, 'com.example.activity.service.impl.ActivityProductHandler', '2024-07-10 19:49:08', NULL);
@Service
public class ActivityClient {
/**
* 活动缓存
*/
Cache<Integer, IActivityHandler> activityCache = Caffeine.newBuilder()
.maximumSize(100)
.expireAfterAccess(600L, TimeUnit.SECONDS)
.build();
@Autowired
private ActivityConditionClassService conditionClassService;
@Autowired
private ActivityConditionDetailService conditionDetailService;
public BigDecimal clientHandler(HandlerConditionVo handlerConditionVo) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
IActivityHandler activityHandler = activityCache.getIfPresent(handlerConditionVo.getActivityId());
//查询缓存,如果没有就初始化
if (activityHandler == null) {
//查询活动条件
List<ActivityConditionDetail> conditionDetails = conditionDetailService.list(Wrappers.<ActivityConditionDetail>lambdaQuery()
.eq(ActivityConditionDetail::getActivityId, handlerConditionVo.getActivityId()));
List<Integer> conditionIds = conditionDetails.stream().map(ActivityConditionDetail::getActivityConditionId).collect(Collectors.toList());
//查询活动条件对应的类
List<ActivityConditionClass> activityConditionClasses = conditionClassService.listByIds(conditionIds);
//装配这些活动
//创建哑结点,找第0个节点创建
IActivityHandler headHandler = (IActivityHandler) Class.forName(activityConditionClasses.get(0).getConditionClass()).newInstance();
//创建前置节点,轮询用
IActivityHandler preHandler = headHandler;
for (int i = 1; i < activityConditionClasses.size(); i++) {
IActivityHandler handler = (IActivityHandler) Class.forName(activityConditionClasses.get(i).getConditionClass()).newInstance();
preHandler.nextHandler = handler;
preHandler = preHandler.nextHandler;
//重新赋值
activityCache.put(handlerConditionVo.getActivityId(), activityHandler);
activityHandler = headHandler;
}
}
return activityHandler.handle(handlerConditionVo);
}
}
5.回顾与总结
通过使用责任链设计模式,完成了营销活动的配置,包括责任链表的组装方式,简化开发流程,使代码的设计更加抽象,扩展性更强。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。