Skip to content

Commit 819af52

Browse files
author
LIANGLINJIANG888
committed
docs: 更新12306专栏内容和新增md文件
1 parent 2cfcec4 commit 819af52

File tree

5 files changed

+275
-23
lines changed

5 files changed

+275
-23
lines changed

docs/.vuepress/config.js

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -373,41 +373,27 @@ module.exports = {
373373
},
374374
{
375375
text: '12306',
376-
items: [{
376+
items: [
377+
{
377378
text: '项目介绍',
378379
link: '/md/12306/项目介绍.md'
379380
},
380381
{
381382
text: '快速开始',
382-
items: [{
383+
items: [
384+
{
383385
text: '环境搭建',
384386
link: '/md/12306/环境搭建.md'
385387
}
386-
387388
]
388389
},
389390
{
390391
text: '核心技术文档',
391-
items: [{
392+
items: [
393+
{
392394
text: '如何生成分布式ID',
393395
link: '/md/12306/如何生成分布式ID.md'
394396
},
395-
{
396-
text: '详解雪花算法',
397-
link: '/md/12306/详解雪花算法.md'
398-
},
399-
{
400-
text: '责任链模式重构复杂业务场景',
401-
link: '/md/12306/责任链模式重构复杂业务场景.md'
402-
},
403-
{
404-
text: '死磕设计模式之抽象责任链模式',
405-
link: '/md/12306/死磕设计模式之抽象责任链模式.md'
406-
},
407-
{
408-
text: '策略模式在项目设计中的应用',
409-
link: '/md/12306/策略模式在项目设计中的应用.md'
410-
},
411397
]
412398
}
413399
]
@@ -732,7 +718,8 @@ module.exports = {
732718
"04-Redisson读写锁加锁机制分析.md"
733719
]
734720
}],
735-
"/md/12306/": [{
721+
"/md/12306/": [
722+
{
736723
title: "项目介绍",
737724
collapsable: false,
738725
sidebarDepth: 0,
@@ -758,6 +745,8 @@ module.exports = {
758745
"责任链模式重构复杂业务场景.md",
759746
"死磕设计模式之抽象责任链模式.md",
760747
"策略模式在项目设计中的应用.md",
748+
"死磕设计模式之抽象策略模式.md",
749+
761750
]
762751
}
763752
],

docs/md/12306/如何生成分布式ID.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# **分布式ID选型**
1+
# **如何生成分布式ID**
22
随着12306开源项目的不断发展,后端服务需要处理来自全国乃至全球的海量请求。在这样的分布式系统中,生成全局唯一且高效的ID是保障数据一致性和服务可靠性的关键。以下是针对12306开源项目的分布式ID选型分析与实践。
33

44
## **项目需求分析**
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# **死磕设计模式之抽象策略模式**
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
# **详解雪花算法**
1+
# **详解雪花算法**
Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,264 @@
11
# **责任链模式重构复杂业务场景**
22

3+
## 引言:
4+
5+
在软件设计中,我们经常会遇到复杂的业务场景,这些场景往往涉及多个处理对象和一系列操作。为了提高代码的可维护性和扩展性,我们可以采用设计模式来优化结构。其中,责任链模式(Chain of Responsibility)是一种有效的解决方案,它通过将请求沿着链传递来处理复杂的业务逻辑。
6+
7+
责任链模式定义了一系列处理器对象,每个处理器都有机会处理请求。请求会沿着链传递,直到被某个处理器处理或达到链的末端。这种模式降低了发送者和接收者之间的耦合度,使得新增或修改处理器更加灵活。
8+
9+
想要深入了解责任链设计模式的可直接访问<a href ="#jump">#</a>
10+
11+
## 应用场景:
12+
13+
在12306項目中,列车购买车票需要对前端传过来的购票参数进行一系列的校验,包括必填参数不可为空、参数是否满足要求、验证当前车次票数是否充足、乘客是否已购买当前车次等,如果通过if-else处理,虽然也能实现逻辑,但不利于扩展,如果要新增一个校验规则,就得修改当前代码,这样不符合设计模式的开闭原则。
14+
因此,为了代码的健壮和扩展性,采用了责任链模式进行代码解耦,减少if-else的模板代码出现,提高代码的阅读性和可扩展性。
15+
16+
校验规则主要有如下:
17+
- 验证参数必填。
18+
- 检查参数是否有效。
19+
- 验证列车站点库存是否充足。
20+
21+
每个环节都可以视为一个处理器,它们按照一定的顺序组成了处理链。
22+
23+
## 责任链模式实现:
24+
25+
首先,定义一个全局抽象的处理器接口:
26+
27+
```java
28+
public interface AbstractChainHandler<T> extends Ordered {
29+
/**
30+
* 执行责任链逻辑
31+
*
32+
* @param requestParam 责任链执行入参
33+
*/
34+
void handler(T requestParam);
35+
36+
/**
37+
* @return 责任链组件标识
38+
*/
39+
String mark();
40+
}
41+
```
42+
43+
接着,定义每个处理环节的处理器接口,用于区分不同环节的处理器,如下是列车购买车票的校验过滤器
44+
45+
```java
46+
/**
47+
* 列车购买车票过滤器
48+
*/
49+
public interface TrainPurchaseTicketChainFilter<T extends PurchaseTicketReqDTO> extends AbstractChainHandler<PurchaseTicketReqDTO> {
50+
51+
@Override
52+
default String mark() {
53+
return TicketChainMarkEnum.TRAIN_PURCHASE_TICKET_FILTER.name();
54+
}
55+
}
56+
```
57+
具体的列车购票校验器实现类
58+
59+
```java
60+
/**
61+
* 购票流程过滤器之验证参数必填
62+
*
63+
*/
64+
@Component
65+
public class TrainPurchaseTicketParamNotNullChainHandler implements TrainPurchaseTicketChainFilter<PurchaseTicketReqDTO> {
66+
67+
@Override
68+
public void handler(PurchaseTicketReqDTO requestParam) {
69+
if (StrUtil.isBlank(requestParam.getTrainId())) {
70+
throw new ClientException("列车标识不能为空");
71+
}
72+
if (StrUtil.isBlank(requestParam.getDeparture())) {
73+
throw new ClientException("出发站点不能为空");
74+
}
75+
if (StrUtil.isBlank(requestParam.getArrival())) {
76+
throw new ClientException("到达站点不能为空");
77+
}
78+
if (CollUtil.isEmpty(requestParam.getPassengers())) {
79+
throw new ClientException("乘车人至少选择一位");
80+
}
81+
for (PurchaseTicketPassengerDetailDTO each : requestParam.getPassengers()) {
82+
if (StrUtil.isBlank(each.getPassengerId())) {
83+
throw new ClientException("乘车人不能为空");
84+
}
85+
if (Objects.isNull(each.getSeatType())) {
86+
throw new ClientException("座位类型不能为空");
87+
}
88+
}
89+
}
90+
91+
@Override
92+
public int getOrder() {
93+
return 0;
94+
}
95+
}
96+
97+
/**
98+
* 购票流程过滤器之验证参数是否有效
99+
*/
100+
@Component
101+
@RequiredArgsConstructor
102+
public class TrainPurchaseTicketParamVerifyChainHandler implements TrainPurchaseTicketChainFilter<PurchaseTicketReqDTO> {
103+
104+
private final TrainMapper trainMapper;
105+
private final TrainStationMapper trainStationMapper;
106+
private final DistributedCache distributedCache;
107+
108+
@Override
109+
public void handler(PurchaseTicketReqDTO requestParam) {
110+
// 查询会员购票车次是否存在,通过封装后安全的 Get 方法
111+
TrainDO trainDO = distributedCache.safeGet(
112+
TRAIN_INFO + requestParam.getTrainId(),
113+
TrainDO.class,
114+
() -> trainMapper.selectById(requestParam.getTrainId()),
115+
ADVANCE_TICKET_DAY,
116+
TimeUnit.DAYS);
117+
if (Objects.isNull(trainDO)) {
118+
// 如果按照严谨逻辑,类似异常应该记录当前用户的 userid 并发送到风控中心
119+
// 如果一段时间有过几次的异常,直接封号处理。下述异常同理
120+
throw new ClientException("请检查车次是否存在");
121+
}
122+
// TODO,当前列车数据并没有通过定时任务每天生成最新的,所以需要隔离这个拦截。后期定时生成数据后删除该判断
123+
if (!EnvironmentUtil.isDevEnvironment()) {
124+
// 查询车次是否已经发售
125+
if (new Date().before(trainDO.getSaleTime())) {
126+
throw new ClientException("列车车次暂未发售");
127+
}
128+
// 查询车次是否在有效期内
129+
if (new Date().after(trainDO.getDepartureTime())) {
130+
throw new ClientException("列车车次已出发禁止购票");
131+
}
132+
}
133+
// 车站是否存在车次中,以及车站的顺序是否正确
134+
String trainStationStopoverDetailStr = distributedCache.safeGet(
135+
TRAIN_STATION_STOPOVER_DETAIL + requestParam.getTrainId(),
136+
String.class,
137+
() -> {
138+
LambdaQueryWrapper<TrainStationDO> queryWrapper = Wrappers.lambdaQuery(TrainStationDO.class)
139+
.eq(TrainStationDO::getTrainId, requestParam.getTrainId())
140+
.select(TrainStationDO::getDeparture);
141+
List<TrainStationDO> actualTrainStationList = trainStationMapper.selectList(queryWrapper);
142+
return CollUtil.isNotEmpty(actualTrainStationList) ? JSON.toJSONString(actualTrainStationList) : null;
143+
},
144+
Index12306Constant.ADVANCE_TICKET_DAY,
145+
TimeUnit.DAYS
146+
);
147+
List<TrainStationDO> trainDOList = JSON.parseArray(trainStationStopoverDetailStr, TrainStationDO.class);
148+
boolean validateStation = validateStation(
149+
trainDOList.stream().map(TrainStationDO::getDeparture).toList(),
150+
requestParam.getDeparture(),
151+
requestParam.getArrival()
152+
);
153+
if (!validateStation) {
154+
throw new ClientException("列车车站数据错误");
155+
}
156+
}
157+
158+
@Override
159+
public int getOrder() {
160+
return 10;
161+
}
162+
163+
public boolean validateStation(List<String> stationList, String startStation, String endStation) {
164+
int index1 = stationList.indexOf(startStation);
165+
int index2 = stationList.indexOf(endStation);
166+
if (index1 == -1 || index2 == -1) {
167+
return false;
168+
}
169+
return index2 >= index1;
170+
}
171+
}
172+
173+
/**
174+
* 购票流程过滤器之验证列车站点库存是否充足
175+
*/
176+
@Component
177+
@RequiredArgsConstructor
178+
public class TrainPurchaseTicketParamStockChainHandler implements TrainPurchaseTicketChainFilter<PurchaseTicketReqDTO> {
179+
180+
private final SeatMarginCacheLoader seatMarginCacheLoader;
181+
private final DistributedCache distributedCache;
182+
183+
@Override
184+
public void handler(PurchaseTicketReqDTO requestParam) {
185+
// 车次站点是否还有余票。如果用户提交多个乘车人非同一座位类型,拆分验证
186+
String keySuffix = StrUtil.join("_", requestParam.getTrainId(), requestParam.getDeparture(), requestParam.getArrival());
187+
StringRedisTemplate stringRedisTemplate = (StringRedisTemplate) distributedCache.getInstance();
188+
List<PurchaseTicketPassengerDetailDTO> passengerDetails = requestParam.getPassengers();
189+
Map<Integer, List<PurchaseTicketPassengerDetailDTO>> seatTypeMap = passengerDetails.stream()
190+
.collect(Collectors.groupingBy(PurchaseTicketPassengerDetailDTO::getSeatType));
191+
seatTypeMap.forEach((seatType, passengerSeatDetails) -> {
192+
Object stockObj = stringRedisTemplate.opsForHash().get(TRAIN_STATION_REMAINING_TICKET + keySuffix, String.valueOf(seatType));
193+
int stock = Optional.ofNullable(stockObj).map(each -> Integer.parseInt(each.toString())).orElseGet(() -> {
194+
Map<String, String> seatMarginMap = seatMarginCacheLoader.load(String.valueOf(requestParam.getTrainId()), String.valueOf(seatType), requestParam.getDeparture(), requestParam.getArrival());
195+
return Optional.ofNullable(seatMarginMap.get(String.valueOf(seatType))).map(Integer::parseInt).orElse(0);
196+
});
197+
if (stock >= passengerSeatDetails.size()) {
198+
return;
199+
}
200+
throw new ClientException("列车站点已无余票");
201+
});
202+
}
203+
204+
@Override
205+
public int getOrder() {
206+
return 20;
207+
}
208+
}
209+
```
210+
将上述的列车购票校验处理器组装成一条链:
211+
212+
```java
213+
/**
214+
* 抽象责任链上下文
215+
*/
216+
public final class AbstractChainContext<T> implements CommandLineRunner {
217+
218+
private final Map<String, List<AbstractChainHandler>> abstractChainHandlerContainer = new HashMap<>();
219+
220+
/**
221+
* 责任链组件执行
222+
*
223+
* @param mark 责任链组件标识
224+
* @param requestParam 请求参数
225+
*/
226+
public void handler(String mark, T requestParam) {
227+
List<AbstractChainHandler> abstractChainHandlers = abstractChainHandlerContainer.get(mark);
228+
if (CollectionUtils.isEmpty(abstractChainHandlers)) {
229+
throw new RuntimeException(String.format("[%s] Chain of Responsibility ID is undefined.", mark));
230+
}
231+
abstractChainHandlers.forEach(each -> each.handler(requestParam));
232+
}
233+
234+
@Override
235+
public void run(String... args) throws Exception {
236+
Map<String, AbstractChainHandler> chainFilterMap = ApplicationContextHolder
237+
.getBeansOfType(AbstractChainHandler.class);
238+
chainFilterMap.forEach((beanName, bean) -> {
239+
List<AbstractChainHandler> abstractChainHandlers = abstractChainHandlerContainer.get(bean.mark());
240+
if (CollectionUtils.isEmpty(abstractChainHandlers)) {
241+
abstractChainHandlers = new ArrayList();
242+
}
243+
abstractChainHandlers.add(bean);
244+
List<AbstractChainHandler> actualAbstractChainHandlers = abstractChainHandlers.stream()
245+
.sorted(Comparator.comparing(Ordered::getOrder))
246+
.collect(Collectors.toList());
247+
abstractChainHandlerContainer.put(bean.mark(), actualAbstractChainHandlers);
248+
});
249+
}
250+
}
251+
```
252+
最后,在列车购票服务中使用。
253+
```java
254+
@Override
255+
public TicketPurchaseRespDTO purchaseTicketsV1(PurchaseTicketReqDTO requestParam) {
256+
// 责任链模式,验证 1:参数必填 2:参数正确性 3:乘客是否已买当前车次等...
257+
purchaseTicketAbstractChainContext.handler(TicketChainMarkEnum.TRAIN_PURCHASE_TICKET_FILTER.name(), requestParam);
258+
// other logic 省略
259+
}
260+
```
261+
262+
## 总结:
263+
264+
通过使用责任链模式,我们成功地将复杂的列购票处理流程分解成了独立、可管理的处理器链。这种设计提高了代码的模块化程度,降低了各个处理器之间的耦合性,同时也便于后续的功能扩展和维护。

0 commit comments

Comments
 (0)