本文参考自
Springboot3+微服务实战12306高性能售票系统 - 慕课网 (imooc.com)
本文是仿12306项目实战第(二)章——项目实现 的第五篇,本篇讲解该项目的核心功能——余票查询、车票预定功能的基础版开发,以及讲解项目与Nacos的集成
本章目录
- 一、核心功能介绍
- 二、增加余票信息表,生成代码
- 三、生成车次时初始化余票信息
- 四、生成车次时初始化各种座位的余票数量
- 五、为余票信息页面增加查询条件
- 六、为会员端增余票查询功能
- 七、增加订票页面并且实现车次信息传递
- 1.增加预订按钮,点击预订时,跳转到下单页面,并使用sessionStorage传递参数
- 2.为余票查询页面缓存查询参数,方便用户使用;将session key写成常量,方便统一维护,可以避免多个功能使用同一个key
- 3.美化车次信息的显示
- 4.订单页面显示座位信息
- 八、订票页面勾选乘客并显示购票列表
- 1.订票页面,查询我的所有的乘客(可以在新增乘客的时候,增加一个校验:超过50个乘客,就不能再新增了)
- 2.订票页面,显示我的乘客复选框
- 3.订票页面,为勾选的乘客构造购票数据
- 4.订票页面,优化购票列表的展示
- 5.订票页面,勾选乘客后提交,显示购票列表确认框
- 九、分解选座购票功能的前后端逻辑
- 十、订票页面增加选座效果
- 1.勾选乘客后,提交时,校验余票是否足够(前端校验不一定准,但前端校验可以减轻后端很多压力)
- 2.根据购票列表,计算出是否支持选座
- 3.根据购票列表,展示选座按钮
- 4.余票小于20张时,不允许选座
- 5.确认提交时,计算出最终每个乘客所选的座位
- 6.chooseSeatObj先清空,再初始化,保证两排座位是有序的
- 十、增加确认订单表并生成前后端代码
- 十一、后端增加确认下单购票接口
- 十二、确认下单接口数据初始化
- 十三、预扣减库存并判断余票是否足够
- 十四、计算多个选座之间的偏移值
一、核心功能介绍
二、增加余票信息表,生成代码
-
business.sql
drop table if exists `daily_train_ticket`; create table `daily_train_ticket` ( `id` bigint not null comment 'id', `date` date not null comment '日期', `train_code` varchar(20) not null comment '车次编号', `start` varchar(20) not null comment '出发站', `start_pinyin` varchar(50) not null comment '出发站拼音', `start_time` time not null comment '出发时间', `start_index` tinyint not null comment '出发站序|本站是整个车次的第几站', `end` varchar(20) not null comment '到达站', `end_pinyin` varchar(50) not null comment '到达站拼音', `end_time` time not null comment '到站时间', `end_index` tinyint not null comment '到站站序|本站是整个车次的第几站', `ydz` int not null comment '一等座余票', `ydz_price` decimal(8, 2) not null comment '一等座票价', `edz` int not null comment '二等座余票', `edz_price` decimal(8, 2) not null comment '二等座票价', `rw` int not null comment '软卧余票', `rw_price` decimal(8, 2) not null comment '软卧票价', `yw` int not null comment '硬卧余票', `yw_price` decimal(8, 2) not null comment '硬卧票价', `create_time` datetime(3) comment '新增时间', `update_time` datetime(3) comment '修改时间', primary key (`id`), unique key `date_train_code_start_end_unique` (`date`, `train_code`, `start`, `end`) ) engine=innodb default charset=utf8mb4 comment='余票信息';
实际12306用的是商业缓存软件来做的余票信息缓存,本项目用mysql新建临时表来代替缓存演示业务实现
-
修改generator-config-business.xml,生成持久层、前后端代码
<table tableName="daily_train_ticket" domainObjectName="DailyTrainTicket"/>
操作同之前
-
修改admin/package.json,加规则去除ESLint报错
"rules": { "vue/multi-word-component-names": 0, "no-undef": 0, "vue/no-unused-vars": 0 }
-
修改路由、侧边栏
操作同之前
-
测试
数据后续由定时任务填充
三、生成车次时初始化余票信息
-
给批量方法都加上@Transactional
虽然由于事务的传递性,外层有事务,内层方法也会是事务,但是规范点还是都加上事务注解
-
DailyTrainTicketService.java
逻辑:比如车站1——车站2——车站3,站站组合情况就有 1、2;1、3;2、3 三种,需要分别生成每种组合的余票信息
这里第一版 先解决站站组合逻辑,具体余票信息后面完善
package com.neilxu.train.business.service; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.ObjectUtil; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.neilxu.train.business.domain.DailyTrainTicket; import com.neilxu.train.business.domain.DailyTrainTicketExample; import com.neilxu.train.business.domain.TrainStation; import com.neilxu.train.business.mapper.DailyTrainTicketMapper; import com.neilxu.train.business.req.DailyTrainTicketQueryReq; import com.neilxu.train.business.req.DailyTrainTicketSaveReq; import com.neilxu.train.business.resp.DailyTrainTicketQueryResp; import com.neilxu.train.common.resp.PageResp; import com.neilxu.train.common.util.SnowUtil; import jakarta.annotation.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.util.Date; import java.util.List; @Service public class DailyTrainTicketService { private static final Logger LOG = LoggerFactory.getLogger(DailyTrainTicketService.class); @Resource private DailyTrainTicketMapper dailyTrainTicketMapper; @Resource private TrainStationService trainStationService; public void save(DailyTrainTicketSaveReq req) { DateTime now = DateTime.now(); DailyTrainTicket dailyTrainTicket = BeanUtil.copyProperties(req, DailyTrainTicket.class); if (ObjectUtil.isNull(dailyTrainTicket.getId())) { dailyTrainTicket.setId(SnowUtil.getSnowflakeNextId()); dailyTrainTicket.setCreateTime(now); dailyTrainTicket.setUpdateTime(now); dailyTrainTicketMapper.insert(dailyTrainTicket); } else { dailyTrainTicket.setUpdateTime(now); dailyTrainTicketMapper.updateByPrimaryKey(dailyTrainTicket); } } public PageResp<DailyTrainTicketQueryResp> queryList(DailyTrainTicketQueryReq req) { DailyTrainTicketExample dailyTrainTicketExample = new DailyTrainTicketExample(); dailyTrainTicketExample.setOrderByClause("id desc"); DailyTrainTicketExample.Criteria criteria = dailyTrainTicketExample.createCriteria(); LOG.info("查询页码:{}", req.getPage()); LOG.info("每页条数:{}", req.getSize()); PageHelper.startPage(req.getPage(), req.getSize()); List<DailyTrainTicket> dailyTrainTicketList = dailyTrainTicketMapper.selectByExample(dailyTrainTicketExample); PageInfo<DailyTrainTicket> pageInfo = new PageInfo<>(dailyTrainTicketList); LOG.info("总行数:{}", pageInfo.getTotal()); LOG.info("总页数:{}", pageInfo.getPages()); List<DailyTrainTicketQueryResp> list = BeanUtil.copyToList(dailyTrainTicketList, DailyTrainTicketQueryResp.class); PageResp<DailyTrainTicketQueryResp> pageResp = new PageResp<>(); pageResp.setTotal(pageInfo.getTotal()); pageResp.setList(list); return pageResp; } public void delete(Long id) { dailyTrainTicketMapper.deleteByPrimaryKey(id); } @Transactional public void genDaily(Date date, String trainCode) { LOG.info("生成日期【{}】车次【{}】的余票信息开始", DateUtil.formatDate(date), trainCode); // 删除某日某车次的余票信息 DailyTrainTicketExample dailyTrainTicketExample = new DailyTrainTicketExample(); dailyTrainTicketExample.createCriteria() .andDateEqualTo(date) .andTrainCodeEqualTo(trainCode); dailyTrainTicketMapper.deleteByExample(dailyTrainTicketExample); // 查出某车次的所有的车站信息 List<TrainStation> stationList = trainStationService.selectByTrainCode(trainCode); if (CollUtil.isEmpty(stationList)) { LOG.info("该车次没有车站基础数据,生成该车次的余票信息结束"); return; } DateTime now = DateTime.now(); for (int i = 0; i < stationList.size(); i++) { // 得到出发站 TrainStation trainStationStart = stationList.get(i); for (int j = (i + 1); j < stationList.size(); j++) { TrainStation trainStationEnd = stationList.get(j); DailyTrainTicket dailyTrainTicket = new DailyTrainTicket(); dailyTrainTicket.setId(SnowUtil.getSnowflakeNextId()); dailyTrainTicket.setDate(date); dailyTrainTicket.setTrainCode(trainCode); dailyTrainTicket.setStart(trainStationStart.getName()); dailyTrainTicket.setStartPinyin(trainStationStart.getNamePinyin()); dailyTrainTicket.setStartTime(trainStationStart.getOutTime()); dailyTrainTicket.setStartIndex(trainStationStart.getIndex()); dailyTrainTicket.setEnd(trainStationEnd.getName()); dailyTrainTicket.setEndPinyin(trainStationEnd.getNamePinyin()); dailyTrainTicket.setEndTime(trainStationEnd.getInTime()); dailyTrainTicket.setEndIndex(trainStationEnd.getIndex()); dailyTrainTicket.setYdz(0); dailyTrainTicket.setYdzPrice(BigDecimal.ZERO); dailyTrainTicket.setEdz(0); dailyTrainTicket.setEdzPrice(BigDecimal.ZERO); dailyTrainTicket.setRw(0); dailyTrainTicket.setRwPrice(BigDecimal.ZERO); dailyTrainTicket.setYw(0); dailyTrainTicket.setYwPrice(BigDecimal.ZERO); dailyTrainTicket.setCreateTime(now); dailyTrainTicket.setUpdateTime(now); dailyTrainTicketMapper.insert(dailyTrainTicket); } } LOG.info("生成日期【{}】车次【{}】的余票信息结束", DateUtil.formatDate(date), trainCode); } }
-
DailyTrainService.java
package com.neilxu.train.business.service; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.ObjectUtil; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.neilxu.train.business.domain.DailyTrain; import com.neilxu.train.business.domain.DailyTrainExample; import com.neilxu.train.business.domain.Train; import com.neilxu.train.business.mapper.DailyTrainMapper; import com.neilxu.train.business.req.DailyTrainQueryReq; import com.neilxu.train.business.req.DailyTrainSaveReq; import com.neilxu.train.business.resp.DailyTrainQueryResp; import com.neilxu.train.common.resp.PageResp; import com.neilxu.train.common.util.SnowUtil; import jakarta.annotation.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.Date; import java.util.List; @Service public class DailyTrainService