内容摘自:https://bugstack.cn/md/develop/design-pattern/2020-06-18-重学 Java 设计模式《实战责任链模式》.html#重学-java-设计模式-实战责任链模式「模拟618电商大促期间-项目上线流程多级负责人审批场景」
击鼓传雷,看上图你是否想起周星驰有一个电影,大家坐在海边围成一个圈,拿着一个点燃的炸弹,互相传递。
责任链模式的核心是解决一组服务中的先后执行处理关系,就有点像你没钱花了,需要家庭财务支出审批,10块钱以下找闺女审批,100块钱先闺女审批在媳妇审批。你可以理解想象成当你要跳槽的时候被安排的明明白白的被各个领导签字放行。
在本案例中我们模拟在618大促期间的业务系统上线审批流程场景
像是这些一线电商类的互联网公司,阿里、京东、拼多多等,在618期间都会做一些运营活动场景以及提供的扩容备战,就像过年期间百度的红包一样。但是所有开发的这些系统都需要陆续的上线,因为临近618有时候也有一些紧急的调整的需要上线,但为了保障线上系统的稳定性是尽可能的减少上线的,也会相应的增强审批力度。就像一级响应、二级响应一样。
而这审批的过程在随着特定时间点会增加不同级别的负责人加入,每个人就像责任链模式中的每一个核心点。对于研发小伙伴并不需要关心具体的审批流程处理细节,只需要知道这个上线更严格,级别也更高,但对于研发人员来说同样是点击相同的提审按钮,等待审核。
接下来我们就模拟这样一个业务诉求场景,使用责任链的设计模式来实现此功能。
itstack-demo-design-13-00
└── src
└── main
└── java
└── org.itstack.demo.design
└── AuthService.java
public class AuthService {
private static Map<String, Date> authMap = new ConcurrentHashMap<String, Date>();
public static Date queryAuthInfo(String uId, String orderId) {
return authMap.get(uId.concat(orderId));
}
public static void auth(String uId, String orderId) {
authMap.put(uId.concat(orderId), new Date());
}
}
queryAuthInfo
)、另外一个是处理审核(auth
)。接下来使用装饰器模式来进行代码优化,也算是一次很小的重构。
责任链模式可以让各个服务模块更加清晰,而每一个模块间可以通过next
的方式进行获取。而每一个next
是由继承的统一抽象类实现的。最终所有类的职责可以动态的进行编排使用,编排的过程可以做成可配置化。
itstack-demo-design-13-02
└── src
└── main
└── java
└── org.itstack.demo.design
├── impl
│ ├── Level1AuthLink.java
│ ├── Level2AuthLink.java
│ └── Level3AuthLink.java
├── AuthInfo.java
└── AuthLink.java
责任链类图
责任链模式模型结构
AuthLink
的不同规则,再进行责任编排模拟出一条链路。这个链路就是业务中的责任链。public class AuthInfo {
private String code;
private String info = "";
public AuthInfo(String code, String ...infos) {
this.code = code;
for (String str:infos){
this.info = this.info.concat(str);
}
}
// ...get/set
}
public abstract class AuthLink {
protected Logger logger = LoggerFactory.getLogger(AuthLink.class);
protected SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// 时间格式化
protected String levelUserId; // 级别人员ID
protected String levelUserName; // 级别人员姓名
private AuthLink next; // 责任链
public AuthLink(String levelUserId, String levelUserName) {
this.levelUserId = levelUserId;
this.levelUserName = levelUserName;
}
public AuthLink next() {
return next;
}
public AuthLink appendNext(AuthLink next) {
this.next = next;
return this;
}
public abstract AuthInfo doAuth(String uId, String orderId, Date authDate);
}
AuthLink next
,重点在于可以通过next
方式获取下一个链路需要处理的节点。levelUserId
、levelUserName
,是责任链中的公用信息,标记每一个审核节点的人员信息。abstract AuthInfo doAuth
,这是每一个实现者必须实现的类,不同的审核级别处理不同的业务。Level1AuthLink
public class Level1AuthLink extends AuthLink {
public Level1AuthLink(String levelUserId, String levelUserName) {
super(levelUserId, levelUserName);
}
public AuthInfo doAuth(String uId, String orderId, Date authDate) {
Date date = AuthService.queryAuthInfo(levelUserId, orderId);
if (null == date) {
return new AuthInfo("0001", "单号:", orderId, " 状态:待一级审批负责人 ", levelUserName);
}
AuthLink next = super.next();
if (null == next) {
return new AuthInfo("0000", "单号:", orderId, " 状态:一级审批完成负责人", " 时间:", f.format(date), " 审批人:", levelUserName);
}
return next.doAuth(uId, orderId, authDate);
}
}
Level2AuthLink
public class Level2AuthLink extends AuthLink {
private Date beginDate = f.parse("2020-06-11 00:00:00");
private Date endDate = f.parse("2020-06-20 23:59:59");
public Level2AuthLink(String levelUserId, String levelUserName) throws ParseException {
super(levelUserId, levelUserName);
}
public AuthInfo doAuth(String uId, String orderId, Date authDate) {
Date date = AuthService.queryAuthInfo(levelUserId, orderId);
if (null == date) {
return new AuthInfo("0001", "单号:", orderId, " 状态:待二级审批负责人 ", levelUserName);
}
AuthLink next = super.next();
if (null == next) {
return new AuthInfo("0000", "单号:", orderId, " 状态:二级审批完成负责人", " 时间:", f.format(date), " 审批人:", levelUserName);
}
if (authDate.before(beginDate) || authDate.after(endDate)) {
return new AuthInfo("0000", "单号:", orderId, " 状态:二级审批完成负责人", " 时间:", f.format(date), " 审批人:", levelUserName);
}
return next.doAuth(uId, orderId, authDate);
}
}
Level3AuthLink
public class Level3AuthLink extends AuthLink {
private Date beginDate = f.parse("2020-06-01 00:00:00");
private Date endDate = f.parse("2020-06-25 23:59:59");
public Level3AuthLink(String levelUserId, String levelUserName) throws ParseException {
super(levelUserId, levelUserName);
}
public AuthInfo doAuth(String uId, String orderId, Date authDate) {
Date date = AuthService.queryAuthInfo(levelUserId, orderId);
if (null == date) {
return new AuthInfo("0001", "单号:", orderId, " 状态:待三级审批负责人 ", levelUserName);
}
AuthLink next = super.next();
if (null == next) {
return new AuthInfo("0000", "单号:", orderId, " 状态:三级审批负责人完成", " 时间:", f.format(date), " 审批人:", levelUserName);
}
if (authDate.before(beginDate) || authDate.after(endDate)) {
return new AuthInfo("0000", "单号:", orderId, " 状态:三级审批负责人完成", " 时间:", f.format(date), " 审批人:", levelUserName);
}
return next.doAuth(uId, orderId, authDate);
}
}
Level1AuthLink
、Level2AuthLink
、Level3AuthLink
,实现了不同的审核级别处理的简单逻辑。super.next();
,如果不存在下一个节点,则直接返回结果。next.doAuth(uId, orderId, authDate);
,有点像递归调用。@Test
public void test_AuthLink() throws ParseException {
AuthLink authLink = new Level3AuthLink("1000013", "王工")
.appendNext(new Level2AuthLink("1000012", "张经理")
.appendNext(new Level1AuthLink("1000011", "段总")));
logger.info("测试结果:{}", JSON.toJSONString(authLink.doAuth("小傅哥", "1000998004813441", new Date())));
// 模拟三级负责人审批
AuthService.auth("1000013", "1000998004813441");
logger.info("测试结果:{}", "模拟三级负责人审批,王工");
logger.info("测试结果:{}", JSON.toJSONString(authLink.doAuth("小傅哥", "1000998004813441", new Date())));
// 模拟二级负责人审批
AuthService.auth("1000012", "1000998004813441");
logger.info("测试结果:{}", "模拟二级负责人审批,张经理");
logger.info("测试结果:{}", JSON.toJSONString(authLink.doAuth("小傅哥", "1000998004813441", new Date())));
// 模拟一级负责人审批
AuthService.auth("1000011", "1000998004813441");
logger.info("测试结果:{}", "模拟一级负责人审批,段总");
logger.info("测试结果:{}", JSON.toJSONString(authLink.doAuth("小傅哥", "1000998004813441", new Date())));
}
AuthLink authLink = new Level3AuthLink("1000013", "王工") .appendNext(new Level2AuthLink("1000012", "张经理") .appendNext(new Level1AuthLink("1000011", "段总")));
通过把不同的责任节点进行组装,构成一条完整业务的责任链。authLink.doAuth(...)
,通过返回结果对数据进行3、2、1级负责人审核,直至最后审核全部完成。23:49:46.585 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果:{"code":"0001","info":"单号:1000998004813441 状态:待三级审批负责人 王工"}
23:49:46.590 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果:模拟三级负责人审批,王工
23:49:46.590 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果:{"code":"0001","info":"单号:1000998004813441 状态:待二级审批负责人 张经理"}
23:49:46.590 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果:模拟二级负责人审批,张经理
23:49:46.590 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果:{"code":"0001","info":"单号:1000998004813441 状态:待一级审批负责人 段总"}
23:49:46.590 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果:模拟一级负责人审批,段总
23:49:46.590 [main] INFO org.itstack.demo.design.test.ApiTest - 测试结果:{"code":"0000","info":"单号:1000998004813441 状态:一级审批完成负责人 时间:2020-06-18 23:49:46 审批人:段总"}
Process finished with exit code 0