12、基本的车票预定功能开发

12-1 本章介绍 (04:28)

12-2 增加余票信息表以提高余票查询效率 (18:55)

余票查询会显示还有多少张票,票数如果实时通过sell去计算,会影响性能,所以应该另外做张表,直接存储余票数

数据库设计,就是通过不同维护的表来描述一样事物

对于相对较固定的枚举类型,就可以用行转列来展示,目的就是方便查询,适合读多写少的场景

视图和存储过程在以前的项目中,经常用到,因为依靠数据库内部的算力,就可以帮我们做很多复杂的功能,但因为会占用较多的数据库资源,所以逐渐淘汰了,服务端资源不够可以加机器,数据库就不好加了,所以数据库资源很珍贵,这也是为什么会有数据缓存

12-3 生成车次时初始化余票信息 (23:17)

单向嵌套循环

for i = 0

     for j = i

批量操作都应该考虑是否要加事务,如果跑一半失败了,是全部回滚?还是跑多少算多少?

面试经常问到:事务的传播机制,其实就是事务有嵌套时,要让事务怎么做,是外层决定内层,还是内层不管外层

阶梯价格是比较常见的设计,比如:
0100公里:0.4元/公里
100
200公里:0.3元/公里
坐了一趟车,开了150公里,那么票价=100*0.4+(150-100)*0.3

12-4 生成车次时初始化各种座位的余票数量 (25:27)

12-5 为余票信息页面增加查询条件 (11:55)

12-6 为会员端增余票查询功能 (21:07)

本节分步进行,先做一个和控台一模一样的页面,再做修改,这样可以保存每步的提交记录,哪一步改砸了,可以随时回退。在工作中开发功能也是按这个流程来走,先拆分功能,再一步一步的实现

12-7 增加订票页面并实现车次信息传递 (26:10)

12-8 订票页面勾选乘客并显示购票列表 (27:17)

如果遇到【查找我的所有的XXX】这样的功能,需要考虑数据量的问题
一种是限制我的XXX的数量,在新增的时候,做校验,如果超过100,就不能再新增
一种是限制查询的数量,最多返回前100条数据

12-9 分解选座购票功能的前后端逻辑 (20:32)

12306规则:
只有全部是一等座或全部是二等座才支持选座
余票小于一定数量时,不允许选座(本项目以20为例)

选座效果:

座位类型枚举:

构造两个重要的响应式变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 0:不支持选座;1:选一等座;2:选二等座
const chooseSeatType = ref(0);
// 选择的座位
// {
//   A1: false, C1: true,D1: false, F1: false,
//   A2: false, C2: false,D2: true, F2: false
// }
const chooseSeatObj = ref({});
最终购票tickets:
// seat可选,当无选座时,seat为空
[{
  passengerId: 123,
  passengerType: "1",
  seatTypeCode: "1",
  passengerName: "张三",
  passengerIdCard: "12323132132",
  seat: "C1"
}, {
  passengerId: 123,
  passengerType: "1",
  seatTypeCode: "1",
  passengerName: "李四",
  passengerIdCard: "12323132132",
  seat: "D2"
}]

座位售卖详情,比如有ABCDE五个站,sell=0110,则AB未被购买,AC已被购买

后端购票逻辑,分成选座和不选座
不选座,以购买一等座为例:遍历一等座车厢,每个车厢从1号座位开始找,未被购买的,就选中它
选座,以购买两张一等座AB为例:遍历一等座车厢,每个车厢从1号座位开始找A列座位,未被购买的,就预选中它;再挑它旁边的B,如果也未被购买,则最终选中这两个座位,如果B已被购买,则回到第一步,继续找未被购买的A座。

从第二个座位开始,需要计算和第一个座位的偏移值,可以减少循环,提高选座效率

12-10 订票页面增加选座效果 (35:38)

前后端都得加校验,前端校验可以减轻后端压力,后端校验是真实的校验。
后端不要相信前端,比如一些非空校验,不能前端有校验了,后端就省了

关于本节的第一个提交,copy出一个临时变量用于扣减库存,大家可以试试不用临时变量,而直接用seatTypes去扣减,在存在足够时,反复提交多次订单后,会有什么效果

computed和watch类似,都可以用来响应式的计算一个值,compute可以像第二个提交这样写,直接声明出一个响应式变量

本节提到多次,常见的代码设计:如果一个操作可以反复多次,则可以清空上一次的操作,再重新赋值,避免上一次的数据对本次的操作造成影响

12-11 增加确认订单表并生成前后端代码 (13:00)

对于重要的功能,我们要在接口入口落库,留下痕迹,方便做统计。

主键一般跟业务无关,每次重新生成数据,ID都会变;唯一键一般跟业务有关,每次重新生成数据,唯一键数据不会变

12-12 后端增加确认下单购票接口 (18:44)

本节没有测试,最终的代码有问题,将在下个小节一起测试并修复

常见的安全性校验:后端需要做好参数合法性校验,防止黑客绕过界面,直接访问接口

springboot在接收参数时,可以将json数组映射成List

后端校验分为两种:

1.            数据合法性校验:必输、长度、格式

2.            业务合法性校验:表数据是否存在?日期范围是否合理?

12-13 确认下单接口数据初始化 (15:08)

MemberIntercepter:会员拦截器,用于将当前登录的会员放到线程本地变量。要想使用这个功能,需要在SpringMvcConfig里配置下,打开这个功能

12-14 预扣减库存并判断余票是否足够 (13:44)

本节的扣减余票数功能使用的是switch case,去获取一个对象的不固定的属性,就会用到反射,反射性能不如直接写分支判断

12-15 计算多个选座之间的偏移值 (23:23)

计算出offsetList,偏移值,如果只有一个座位,就是{0},如果选择多个座位,例如三个:{0, 1, 2}
所以,通过第一个座位的列号column和offsetList,就可以知道本次的选座

12-16 循环获取每个车厢的每个座位 (22:28)

12-17 根据座位销售详情判断本次是否可选(一) (14:47)

抱歉!!本节所说的按位与,说错了,应该是按位或

位运算性能较好,特殊的场景可以优先考虑位运算。
sell的设计应该反过来说,因为位运算性能好,所以才设计了存储01的sell字段
常用的程序设计:约定优于配置

12-18 根据座位销售详情判断本次是否可选(二) (19:27)

12-19 完成有选座的挑座位逻辑 (24:30)

选座只能在同一个车厢
约定:车厢号从1开始,座位号从1开始,站序从0开始

12-20 保存最终的选座结果 (17:09)

12-21 选座成功后更新各座位的销售详情 (11:31)

尽量做短事务,不要做常事务,否则会大量占用数据库资源

本类方法间的调用,事务不生效

在遇到数组、列表、日期计算的时候,要注意边界值

对于复杂的SQL,可以写自定义mapper,具体参照本节的DailyTrainTicketMapperCust

12-22 选座成功后批量扣减影响到的多个库存 (30:18)

12-23 选座成功后会会员增加车票记录 (19:24)

12-24 选座成功后更新确认订单状态为成功 (05:12)


12、基本的车票预定功能开发
http://yuanql.top/2023/07/07/00_项目经历/YuanqlProject/Springboot3+微服务实战12306高性能售票系统/12、基本的车票预定功能开发/
作者
Qingli Yuan
发布于
2023年7月7日
许可协议