和拓者设计吧类似的网站,修改Wordpress账号密码,个人网站怎么建立流程,win7怎么建设网站目录
验证数据
加载城市数据
查询列车站点信息
查询列车余票信息
构建列车返回数据 12306 项目中列车数据检索接口路径 #xfeff; TicketController的pageListTicketQuery#xfeff;。
GetMapping(/api/ticket-service/ticket/query)public ResultT…
目录
验证数据
加载城市数据
查询列车站点信息
查询列车余票信息
构建列车返回数据 12306 项目中列车数据检索接口路径 TicketController的pageListTicketQuery。
GetMapping(/api/ticket-service/ticket/query)public ResultTicketPageQueryRespDTO pageListTicketQuery(TicketPageQueryReqDTO requestParam) {return Results.success(ticketService.pageListTicketQueryV1(requestParam));}
验证数据
查询列车数据 Service 实现层接口第一行代码就是通过责任链模式验证数据是否必填以及城市
数据是否存在等执行逻辑。
ticketPageQueryAbstractChainContext.handler(TicketChainMarkEnum.TRAIN_QUERY_FILTER.name(), requestParam);
handler具体代码如下
1. 检查相关数据是否为空或空的字符串这个是最先被执行的
/*** 查询列车车票流程过滤器之验证数据是否为空或空的字符串** */
Component
public class TrainTicketQueryParamNotNullChainFilter implements TrainTicketQueryChainFilterTicketPageQueryReqDTO {Overridepublic void handler(TicketPageQueryReqDTO requestParam) {if (StrUtil.isBlank(requestParam.getFromStation())) {throw new ClientException(出发地不能为空);}if (StrUtil.isBlank(requestParam.getToStation())) {throw new ClientException(目的地不能为空);}if (requestParam.getDepartureDate() null) {throw new ClientException(出发日期不能为空);}}Overridepublic int getOrder() {return 0;}
}
2. 检查数据是否正确
/*** 查询列车车票流程过滤器之验证数据是否正确** */
Component
RequiredArgsConstructor
public class TrainTicketQueryParamVerifyChainFilter implements TrainTicketQueryChainFilterTicketPageQueryReqDTO {private final RegionMapper regionMapper;private final StationMapper stationMapper;private final DistributedCache distributedCache;private final RedissonClient redissonClient;/*** 缓存数据为空并且已经加载过标识*/private static boolean CACHE_DATA_ISNULL_AND_LOAD_FLAG false;Overridepublic void handler(TicketPageQueryReqDTO requestParam) {StringRedisTemplate stringRedisTemplate (StringRedisTemplate) distributedCache.getInstance();HashOperationsString, Object, Object hashOperations stringRedisTemplate.opsForHash();ListObject actualExistList hashOperations.multiGet(QUERY_ALL_REGION_LIST,ListUtil.toList(requestParam.getFromStation(), requestParam.getToStation()));long emptyCount actualExistList.stream().filter(Objects::isNull).count();if (emptyCount 0L) {return;}if (emptyCount 1L || (emptyCount 2L CACHE_DATA_ISNULL_AND_LOAD_FLAG distributedCache.hasKey(QUERY_ALL_REGION_LIST))) {throw new ClientException(出发地或目的地不存在);}RLock lock redissonClient.getLock(LOCK_QUERY_ALL_REGION_LIST);lock.lock();try {if (distributedCache.hasKey(QUERY_ALL_REGION_LIST)) {actualExistList hashOperations.multiGet(QUERY_ALL_REGION_LIST,ListUtil.toList(requestParam.getFromStation(), requestParam.getToStation()));emptyCount actualExistList.stream().filter(Objects::nonNull).count();if (emptyCount ! 2L) {throw new ClientException(出发地或目的地不存在);}return;}ListRegionDO regionDOList regionMapper.selectList(Wrappers.emptyWrapper());ListStationDO stationDOList stationMapper.selectList(Wrappers.emptyWrapper());HashMapObject, Object regionValueMap Maps.newHashMap();for (RegionDO each : regionDOList) {regionValueMap.put(each.getCode(), each.getName());}for (StationDO each : stationDOList) {regionValueMap.put(each.getCode(), each.getName());}hashOperations.putAll(QUERY_ALL_REGION_LIST, regionValueMap);CACHE_DATA_ISNULL_AND_LOAD_FLAG true;emptyCount regionValueMap.keySet().stream().filter(each - StrUtil.equalsAny(each.toString(), requestParam.getFromStation(), requestParam.getToStation())).count();if (emptyCount ! 2L) {throw new ClientException(出发地或目的地不存在);}} finally {lock.unlock();}}Overridepublic int getOrder() {return 20;}
}
加载城市数据
12306 站点查询实际功能中比如你搜索了北京南到杭州东的搜索条件它会帮你列出北京到杭州所有的列车车次。这个很好实现直接通过站点关联到城市通过城市查询列车即可。我们在缓存中有一个 Hash 结构数据专门负责保存列车站点 Code 值与城市之间的关联关系。
// 列车查询逻辑较为复杂详细解析文章查看 https://nageoffer.com/12306/question// v1 版本存在严重的性能深渊问题v2 版本完美的解决了该问题。通过 Jmeter 压测聚合报告得知性能提升在 300% - 500%ListObject stationDetails stringRedisTemplate.opsForHash().multiGet(REGION_TRAIN_STATION_MAPPING, Lists.newArrayList(requestParam.getFromStation(), requestParam.getToStation()));long count stationDetails.stream().filter(Objects::isNull).count();if (count 0) {RLock lock redissonClient.getLock(LOCK_REGION_TRAIN_STATION_MAPPING);lock.lock();try {stationDetails stringRedisTemplate.opsForHash().multiGet(REGION_TRAIN_STATION_MAPPING, Lists.newArrayList(requestParam.getFromStation(), requestParam.getToStation()));count stationDetails.stream().filter(Objects::isNull).count();if (count 0) {ListStationDO stationDOList stationMapper.selectList(Wrappers.emptyWrapper());MapString, String regionTrainStationMap new HashMap();stationDOList.forEach(each - regionTrainStationMap.put(each.getCode(), each.getRegionName()));stringRedisTemplate.opsForHash().putAll(REGION_TRAIN_STATION_MAPPING, regionTrainStationMap);stationDetails new ArrayList();stationDetails.add(regionTrainStationMap.get(requestParam.getFromStation()));stationDetails.add(regionTrainStationMap.get(requestParam.getToStation()));}} finally {lock.unlock();}}
查询列车站点信息
采用Redis而不是Elasticsearch因为搜索只允许选择一天的出发日期。
同时在12306网站上尝试虽然页面上有很多查询条件但大多数条件都是由前端进行筛选实际上并没有触发后端的请求发现只有在点击“查询”按钮时才会真正触发后端的请求而点击页面上的其他筛选条件并不会向后端发出请求。 列车站点数据存入 Redis 中结构如下 具体查询代码
ListTicketListDTO seatResults new ArrayList();String buildRegionTrainStationHashKey String.format(REGION_TRAIN_STATION, stationDetails.get(0), stationDetails.get(1));MapObject, Object regionTrainStationAllMap stringRedisTemplate.opsForHash().entries(buildRegionTrainStationHashKey);if (MapUtil.isEmpty(regionTrainStationAllMap)) {RLock lock redissonClient.getLock(LOCK_REGION_TRAIN_STATION);lock.lock();try {regionTrainStationAllMap stringRedisTemplate.opsForHash().entries(buildRegionTrainStationHashKey);if (MapUtil.isEmpty(regionTrainStationAllMap)) {LambdaQueryWrapperTrainStationRelationDO queryWrapper Wrappers.lambdaQuery(TrainStationRelationDO.class).eq(TrainStationRelationDO::getStartRegion, stationDetails.get(0)).eq(TrainStationRelationDO::getEndRegion, stationDetails.get(1));ListTrainStationRelationDO trainStationRelationList trainStationRelationMapper.selectList(queryWrapper);for (TrainStationRelationDO each : trainStationRelationList) {TrainDO trainDO distributedCache.safeGet(TRAIN_INFO each.getTrainId(),TrainDO.class,() - trainMapper.selectById(each.getTrainId()),ADVANCE_TICKET_DAY,TimeUnit.DAYS);TicketListDTO result new TicketListDTO();result.setTrainId(String.valueOf(trainDO.getId()));result.setTrainNumber(trainDO.getTrainNumber());result.setDepartureTime(convertDateToLocalTime(each.getDepartureTime(), HH:mm));result.setArrivalTime(convertDateToLocalTime(each.getArrivalTime(), HH:mm));result.setDuration(DateUtil.calculateHourDifference(each.getDepartureTime(), each.getArrivalTime()));result.setDeparture(each.getDeparture());result.setArrival(each.getArrival());result.setDepartureFlag(each.getDepartureFlag());result.setArrivalFlag(each.getArrivalFlag());result.setTrainType(trainDO.getTrainType());result.setTrainBrand(trainDO.getTrainBrand());if (StrUtil.isNotBlank(trainDO.getTrainTag())) {result.setTrainTags(StrUtil.split(trainDO.getTrainTag(), ,));}long betweenDay cn.hutool.core.date.DateUtil.betweenDay(each.getDepartureTime(), each.getArrivalTime(), false);result.setDaysArrived((int) betweenDay);result.setSaleStatus(new Date().after(trainDO.getSaleTime()) ? 0 : 1);result.setSaleTime(convertDateToLocalTime(trainDO.getSaleTime(), MM-dd HH:mm));seatResults.add(result);regionTrainStationAllMap.put(CacheUtil.buildKey(String.valueOf(each.getTrainId()), each.getDeparture(), each.getArrival()), JSON.toJSONString(result));}stringRedisTemplate.opsForHash().putAll(buildRegionTrainStationHashKey, regionTrainStationAllMap);}} finally {lock.unlock();}}
查询出来列车基本信息后开始对列车按照出发时间进行排序。
seatResults CollUtil.isEmpty(seatResults)? regionTrainStationAllMap.values().stream().map(each - JSON.parseObject(each.toString(), TicketListDTO.class)).toList(): seatResults;
seatResults seatResults.stream().sorted(new TimeStringComparator()).toList();
查询列车余票信息
列车基本信息已经全部填充完毕了接下来就是查询列车余票信息并填充到基本信息中。
列车余票数据是实时变更的如果在存储到基本信息中就没办法变更了所以单独存储。
for (TicketListDTO each : seatResults) {String trainStationPriceStr distributedCache.safeGet(String.format(TRAIN_STATION_PRICE, each.getTrainId(), each.getDeparture(), each.getArrival()),String.class,() - {LambdaQueryWrapperTrainStationPriceDO trainStationPriceQueryWrapper Wrappers.lambdaQuery(TrainStationPriceDO.class).eq(TrainStationPriceDO::getDeparture, each.getDeparture()).eq(TrainStationPriceDO::getArrival, each.getArrival()).eq(TrainStationPriceDO::getTrainId, each.getTrainId());return JSON.toJSONString(trainStationPriceMapper.selectList(trainStationPriceQueryWrapper));},ADVANCE_TICKET_DAY,TimeUnit.DAYS);ListTrainStationPriceDO trainStationPriceDOList JSON.parseArray(trainStationPriceStr, TrainStationPriceDO.class);ListSeatClassDTO seatClassList new ArrayList();trainStationPriceDOList.forEach(item - {String seatType String.valueOf(item.getSeatType());String keySuffix StrUtil.join(_, each.getTrainId(), item.getDeparture(), item.getArrival());Object quantityObj stringRedisTemplate.opsForHash().get(TRAIN_STATION_REMAINING_TICKET keySuffix, seatType);int quantity Optional.ofNullable(quantityObj).map(Object::toString).map(Integer::parseInt).orElseGet(() - {MapString, String seatMarginMap seatMarginCacheLoader.load(String.valueOf(each.getTrainId()), seatType, item.getDeparture(), item.getArrival());return Optional.ofNullable(seatMarginMap.get(String.valueOf(item.getSeatType()))).map(Integer::parseInt).orElse(0);});seatClassList.add(new SeatClassDTO(item.getSeatType(), quantity, new BigDecimal(item.getPrice()).divide(new BigDecimal(100), 1, RoundingMode.HALF_UP), false));});each.setSeatClassList(seatClassList);}
构建列车返回数据
查看 12306 列车查询页可知会存在不同的查询条件这些查询条件都是通过本次查询所有列车数据构建出来的。不同地区的不同查询列车数据车次类型、出发车站、到达车站以及车次席别都不同。 接下来就是通过构建者模式构建列车查询返回数据。
return TicketPageQueryRespDTO.builder().trainList(seatResults).departureStationList(buildDepartureStationList(seatResults)).arrivalStationList(buildArrivalStationList(seatResults)).trainBrandList(buildTrainBrandList(seatResults)).seatClassTypeList(buildSeatClassList(seatResults)).build();