仿12306项目(4)

打印 上一主题 下一主题

主题 978|帖子 978|积分 2934

基本预定车票功能的开发

        对于乘客购票来说,必要有每一个车次的余票信息,展示给乘客,供乘客选择,因此首个功能是余票的初始化,之后是余票查询,这两个都是控台端。对于会员端的购票,必要有余票查询以及乘客的选择,不仅仅支持给自己买票,还可以给其他人买票,而且还可以选择座位类型,是一等座还是二等座,可以选择座位,最后是下单购票。
余票信息表

对于购票表来说,最为重要的字段是售卖字段,对于这个字段来说,将经过的车站用0和1拼接,如果是0表示可卖,1表示不可卖。比方有ABCD四个站,那么000表示这四个站都可以买,最终是可以通过售卖信息来盘算出余票的信息。余票查询会体现还有多少张票,票数如果通过实时盘算,会影响性能,所以应该别的做张表(余票信息表),直接存储余票数,这张表通过购票表的售卖字段定时的更新此表的信息。这张表是火车的一个子表,可以看作用余票的角度观察火车,因此必要包含id,日期,车次以及出发站和到达站的信息。对于出发站和到达站来说,重要的是这个站在整个车次是第几站,以及每一个站站记录它的余票信息。唯一键是日期,车次,出发站和尽头站。
        为什么时日期,车次,出发站和尽头站呢?起首是日期,同一个车次每天会运行一次,余票必要按天来分别,车次是列车的唯一标识,不同的车次余票应该进行隔离,对于出发站和尽头站,举个例子,现在有100张票,有5个区间A->B->C->D->E,现在小刚买了A->C的票,他影响了A->C,A->B,B->C这三个区间,进行库存扣减,但是对于D->E并不影响,还是100张,这中间有座位复用的题目,因此必要加上出发站和尽头站座位唯一键。
        构建余票表完成后,有两个题目,这张表应该如何初始化?初始化的数据从何而来?起首第一个题目,什么时间初始化?当一辆火车预备开始卖票时,就可以初始化了。对于车站数据来说,是一个嵌套循环,比方ABCD四个车站,用户可以查AB,AC,AD,BC,BD,CD,如许子就可以得到车次全部的出发站和到达站的站站组合。对于余票信息来说,可以查询座位数这张表,查询车次以及座位类型就可以得到余票的信息。
        落实到详细的代码上,起首是删除要生成某日车次的余票信息,使其支持重复生成,之后是查询这个车次的全部车站信息,根据车站的信息进行嵌套循环,先生成一个余票对象,然后根据车站进行数据的添补,最后将实体保存到数据库
  1. @Transactional
  2.     public void genDaily(Date date, String trainCode) {
  3.         LOG.info("生成日期【{}】车次【{}】的余票信息开始", DateUtil.formatDate(date), trainCode);
  4.         // 删除某日某车次的余票信息
  5.         DailyTrainTicketExample dailyTrainTicketExample = new DailyTrainTicketExample();
  6.         dailyTrainTicketExample.createCriteria()
  7.                 .andDateEqualTo(date)
  8.                 .andTrainCodeEqualTo(trainCode);
  9.         dailyTrainTicketMapper.deleteByExample(dailyTrainTicketExample);
  10.         // 查出某车次的所有车站信息
  11.         List<TrainStation> stationList = trainStationService.selectByTrainCode(trainCode);
  12.         if(CollUtil.isEmpty(stationList)) {
  13.             LOG.info("该车次信息没有车站基础数据,生成该车次的余票信息失败");
  14.             return;
  15.         }
  16.         DateTime now = DateTime.now();
  17.         for (int i = 0; i < stationList.size(); i++) {
  18.             // 得到出发站
  19.             TrainStation trainStationStart = stationList.get(i);
  20.             for (int j = i + 1; j < stationList.size(); j++) {
  21.                 TrainStation trainStationEnd = stationList.get(j);
  22.                 DailyTrainTicket dailyTrainTicket = new DailyTrainTicket();
  23.                 dailyTrainTicket.setId(SnowUtil.getSnowflakeNextId());
  24.                 dailyTrainTicket.setDate(date);
  25.                 dailyTrainTicket.setTrainCode(trainCode);
  26.                 dailyTrainTicket.setStart(trainStationStart.getName());
  27.                 dailyTrainTicket.setStartPinyin(trainStationStart.getNamePinyin());
  28.                 dailyTrainTicket.setStartTime(trainStationStart.getOutTime());
  29.                 dailyTrainTicket.setStartIndex(trainStationStart.getIndex());
  30.                 dailyTrainTicket.setEnd(trainStationEnd.getName());
  31.                 dailyTrainTicket.setEndPinyin(trainStationEnd.getNamePinyin());
  32.                 dailyTrainTicket.setEndTime(trainStationEnd.getInTime());
  33.                 dailyTrainTicket.setEndIndex(trainStationEnd.getIndex());
  34.                 dailyTrainTicket.setYdz(0);
  35.                 dailyTrainTicket.setYdzPrice(BigDecimal.ZERO);
  36.                 dailyTrainTicket.setEdz(0);
  37.                 dailyTrainTicket.setEdzPrice(BigDecimal.ZERO);
  38.                 dailyTrainTicket.setRw(0);
  39.                 dailyTrainTicket.setRwPrice(BigDecimal.ZERO);
  40.                 dailyTrainTicket.setYw(0);
  41.                 dailyTrainTicket.setYwPrice(BigDecimal.ZERO);
  42.                 dailyTrainTicket.setCreateTime(now);
  43.                 dailyTrainTicket.setUpdateTime(now);
  44.                 dailyTrainTicketMapper.insert(dailyTrainTicket);
  45.             }
  46.         }
  47.         LOG.info("生成日期【{}】车次【{}】的余票信息结束", DateUtil.formatDate(date), trainCode);
  48.     }
复制代码
现在发现,对于座位类型的个数和票价还是未知,因此接下来解决这个方面的信息。求某个车次的某类型的票数,必要知道的是座位数。因此应该在每日座位表中查某个日期,某个车次,某个座位类型的票数。由于在每日座位表已经有了这些信息,因此只必要填写好信息去查询就可以得到初始化的余票信息,对于无票的时间,如果设置为0,用户可以以为卖光了,实在想要表达的是改车次没有这种类型的票,因此可以设置为-1.
  1. public int countSeat(Date date, String trainCode,String seatType){
  2.         DailyTrainSeatExample example = new DailyTrainSeatExample();
  3.         example.createCriteria()
  4.                 .andDateEqualTo(date)
  5.                 .andTrainCodeEqualTo(trainCode)
  6.                 .andSeatTypeEqualTo(seatType);
  7.         long l = dailyTrainSeatMapper.countByExample(example);
  8.         if(l == 0L) {
  9.             return -1;
  10.         }
  11.         return (int)l;
  12.     }
复制代码
接下来是盘算票价,票价和火车类型以及座位类型有关:票价=里程之和*座位类型的票价*车次类型系数。盘算里程时从初始站加上每一次到达站的距离,即
  1. sumKM = sumKM.add(trainStationEnd.getKm());
复制代码
最终的总盘算公式如下:
  1. // 票价=里程之和*座位类型的票价*车次类型系数
  2. String trainType = dailyTrain.getType();
  3. // 计算票价系数:TrainTypeEnum.priceRate
  4. BigDecimal priceRate = EnumUtil.getFieldBy(TrainTypeEnum::getPriceRate,TrainTypeEnum::getCode,trainType);
复制代码
余票查询

对于余票的查询,设置查询条件为日期,火车车次,起始站和尽头站。对于会员端的车票界面来说,它不支持增加和修改和删除,只是支持查询,因今后端对于会员端增加车票查询的接口。
  1. @RestController
  2. @RequestMapping("/daily-train-ticket")
  3. public class DailyTrainTicketController {
  4.     @Resource
  5.     private DailyTrainTicketService dailyTrainTicketService;
  6.     @GetMapping("/query-list")
  7.     public CommonResp<PageResp<DailyTrainTicketQueryResp>> queryList(@Valid DailyTrainTicketQueryReq req) {
  8.         PageResp<DailyTrainTicketQueryResp> list = dailyTrainTicketService.queryList(req);
  9.         return new CommonResp<>(list);
  10.     }
  11. }
复制代码
由于进行查询时必要选择车站以及火车车次,而且为了进行分离,之前在控制台界面统一增加了admin,为了使会员端实现相同的查询,因此必要重新写controller层的车次和车票查询,它和控台的功能相同,只是url不同。至此,会员端和控制台界面开发完毕。
选座功能

        起首是预定的按钮,在点击按钮之后必要跳转到order界面,因此必要在router增加一个路由,那用什么取通报参数呢?session就是一个很好的选择,在order界面打开时实行setup(),定义一个参数dailyTrainTicket从缓存中获取dailyTrainTicket,如果没有,给一个空对象,避免空指针非常,之后进行返回,在html部分进行体现出来。在点击时,利用自定义的toOrder,起首是把record放入Session中,之后进行路由跳转。现在是每次查询之后返回,选择框不会保存之前选择的值,为了增强用户体验,可以为余票查询页面缓存查询参数,方便用户使用,将session key写成常量,方便统一维护,可以避免多个功能使用同一个key。当用户选择之后,将用户的选择缓存到一个session key中,然后在公共区添加不同的session key,避免混用。之后修改onMounted(),它表示页面打开,先进性缓存的获取,之后不为空是进行查询。
  1. // order.vue
  2. <template>
  3.   <div>{{dailyTrainTicket}}</div>
  4. </template>
  5. <script>
  6. import {defineComponent} from 'vue';
  7. export default defineComponent({
  8.   name: "order-view",
  9.   setup() {
  10.     const dailyTrainTicket = SessionStorage.get("dailyTrainTicket") || {};
  11.     console.log("下单的车次信息", dailyTrainTicket);
  12.     return {
  13.       dailyTrainTicket,
  14.     };
  15.   },
  16. });
  17. </script>
  18. // ticket.vue
  19. // 保存查询参数
  20. SessionStorage.set(SESSION_TICKET_PARAMS, params.value);
  21. onMounted(() => {
  22.       params.value = SessionStorage.get(SESSION_TICKET_PARAMS) || {};
  23.       if(Tool.isNotEmpty(params.value)) {
  24.         handleQuery({
  25.           page: 1,
  26.           pageSize: pagination.value.pageSize
  27.         });
  28.       }
  29.     });
复制代码
最后的是在order界面的展示优化,得到从出发站到尽头站以及座位类型和价格以及票数的展示。
        接下来是真正的选择座位功能,起首是后端去查找我的全部乘客接口,在order界面调用接口,在搜刮的service层,必要获取当前登录者的id,然后根据登录者的id去库中搜刮出为那些乘客购票,如果乘客太多,可以增加一个功能,当乘客数目大于50时就不拿增加乘客了,controller直接增加接口查询即可。对于前端,增加一个相应式变量passenger,增加一个handleQueryPassen-ger,方法,这个方法调用后端接口,得到后给相应式变量赋值,即初始化时直接查询。
        对于选择乘客,在js部分增加了const passengerOptions = ref([]); const passengerChecks = ref([]);表示选项和选择,由于乘客带有的属性过多,因此可以在handleQueryPassenger方法中增加lable和value,分别表示看到的值以及实际操作的值。勾选完乘客后,必要为乘客构造购票数据。由于一次不仅仅勾选一个乘客,因此可以引入watch,实时监控勾选的变化,用来体现购票的界面。
  1. // 购票列表,用于界面展示,并传递到后端接口,用来描述:哪个乘客购买什么座位的票
  2.     const tickets = ref([]);
  3.     // 勾选或去掉某个乘客时,在购票列表中加上或去掉一张表
  4.     watch(() => passengerChecks.value, (newVal, oldVal)=>{
  5.       console.log("勾选乘客发生变化", newVal, oldVal)
  6.       // 每次有变化时,把购票列表清空,重新构造列表
  7.       tickets.value = [];
  8.       passengerChecks.value.forEach((item) => tickets.value.push({
  9.         passengerId: item.id,
  10.         passengerType: item.type,
  11.         seatTypeCode: seatTypes[0].code,
  12.         passengerName: item.name,
  13.         passengerIdCard: item.idCard
  14.       }))
  15.     }, {immediate: true});
复制代码
最后选择是勾选乘客后提交,体现购票列表确认框进行最后的核对。此时,购票的选座展示效果完毕。

选座规则


  • 只有全部是一等座或全部是二等座才支持选座
  • 余票小于20张时,不答应选座
选座效果
体现两排,一等座每排4个,二等座每排5个,为什么是两排,只是一个自定义的规则,可以3排进行体现,由自己规定。每排的座位是由枚举座位类型得到的,对于1,2等座的分别,根据枚举中的type值即可进行得到。之后构造两个相应式变量chooseSeatType和chooseSeatObj,其中chooseSeatType是表示是否支持选座以及选择的类型,chooseSeatObj表示用户选择的座位是那些,默认为false,选择之后为true,通过读这个对象就知道用户选择了什么座位。经过选座,就可以得到tickets,其中有乘客id,乘客类型,座位类型,乘客姓名,身份证,实际座位。当没有选座时,实际座位为空,由系统来分配,从一号开始找,未被购买,就选座。选座,以购买两张一等座AC为例:遍历一等座车厢,每个车厢从1号座位开始找A列座位,未被购买的,就预选中它;再挑它旁边的C,如果也未被购买,则最终选中这两个座位,如果B已被购买,则回到第一步,继承找未被购买的A座。再挑它旁边的C,这个应该怎么写?可以从第二个座位开始,必要盘算和第一个座位的偏移值,不必要再从1位置开始找,进步选座效率。
前端的选座效果

起首是要考虑这个车票能不能选,比方现在还有5张票,共有7个人来买票,这肯定是不行的,因此可以在前端增加一层校验,来检验余票是否富足,可以减小后端的压力。这步是预扣减库存,只是用来校验,全部拷贝出暂时变量来扣减,即点击提交是预扣减库存,实际提交才是真正扣减库存
  1.       // 校验余票是否充足,购票列表中的每个座位类型,都去车次座位余票信息中,看余票是否充足
  2.       // 前端校验不一定准,但前端校验可以减轻后端很多压力
  3.       // 注意:这段只是校验,必须copy出seatTypesTemp变量来扣减,用原始的seatTypes去扣减,会影响真实的库存
  4.       let seatTypesTemp = Tool.copy(seatTypes);
  5.       for (let i = 0; i < tickets.value.length; i++) {
  6.         let ticket = tickets.value[i];
  7.         for (let j = 0; j < seatTypesTemp.length; j++) {
  8.           let seatType = seatTypesTemp[j];
  9.           // 同类型座位余票-1,这里扣减的是临时copy出来的库存,不是真正的库存,只是为了校验
  10.           if (ticket.seatTypeCode === seatType.code) {
  11.             seatType.count--;
  12.             if (seatType.count < 0) {
  13.               notification.error({description: seatType.desc + '余票不足'});
  14.               return;
  15.             }
  16.           }
  17.         }
  18.       }
  19.       console.log("前端余票校验通过");
复制代码
开始选座

相应式变量chooseSeatType起首是0,表示不支持选座,然后根据座位类型选择对应的列,赋值给SEAT_COL_ARRAY,之后对两排的座位进行初始化,赋值为false;由于规定不能同时选择1和2等座,全部开始选座之前先进行去重,如果多于1中返回选座不乐成,否则的话根据类型进行选座。最后进行界面优化,增加选座的按钮,这里留意,如果是选择一个人进行购票,这里采用只体现一排按钮。回到选择的函数中来,增加一个约定,余票小于20张时,不答应选座。最后提交时,盘算出每个用户的座位选择,代码如下:
  1. const handleOk = () => {
  2.       console.log("选好的座位:", chooseSeatObj.value);
  3.       // 设置每张票的座位
  4.       // 先清空购票列表的座位,有可能之前选了并设置座位了,但选座数不对被拦截了,又重新选一遍
  5.       for (let i = 0; i < tickets.value.length; i++) {
  6.         tickets.value[i].seat = null;
  7.       }
  8.       let i = -1;
  9.       // 要么不选座位,要么所选座位应该等于购票数,即i === (tickets.value.length - 1)
  10.       for (let key in chooseSeatObj.value) {
  11.         if (chooseSeatObj.value[key]) {
  12.           i++;
  13.           if (i > tickets.value.length - 1) {
  14.             notification.error({description: '所选座位数大于购票数'});
  15.             return;
  16.           }
  17.           // 实际的赋值
  18.           tickets.value[i].seat = key;
  19.         }
  20.       }
  21.       if (i > -1 && i < (tickets.value.length - 1)) {
  22.         notification.error({description: '所选座位数小于购票数'});
  23.         return;
  24.       }
  25.       console.log("最终购票:", tickets.value);
  26.     }
复制代码
后端选座接口

        前面的余票信息表是根据购票表来进行得到的,但是,购买完成票之后还必要进行落表,无论是否乐成。必要哪一个人购的票,表示当前访问这个接口的是哪一个会员,还必要日期,车次,出发站和到达站以及这些底子信息,这些信息就可以唯肯定位到余票信息这张表,就可以判断它的一等座,二等座等的余票信息了。余票id字段也可以和上面的余票表进行关联。由于订单状态不肯定乐成,因此必要订单的状态,车票可以做成json,也可以做成子表。
        开发接口的话,那么传入的参数是什么,可以参考计划的表,接口进来之后,就应该数据落表,那么表中的数据如何来?其中member_id可以从线程当地变量获取,日期,车次,出发站,到达站和车票都可以从前端获取,其中车票可以把json映射成Java类,如许子操作更加的方便。因为必要车票进行选座,用json操作不太方便。订单状态是由步调根据不同的步骤进行落库,因此不用管。起首要做的是添加车票类(ConfrimOrderTicketReq),即购买车票的信息,担当前端传来的对象。之后要构造一个订单类,方便入库。之后在controller层增加doConfirm接口,最后在service层增加相关保存购票信息的方法。最后前端调用后端接口。
        重点的话是会员模块的service层的保存订单方法doConfirm如何实现。


  • 保存确认订单表,状态初始,对于id,直接使用雪花算法,时间使用当前的时间,memberid使用登录人的id,像traincode,date,start和end一次ticket都是从前端获取的,之前保存到了ConfirmOrderReq,从这里直接获取即可,留意,对于ticket来说,必要将json字符串转化为车票类
  • 查出余票记录,得到真实的库存。由于唯一键是日期,车次,起始和尽头站,如许子构造号条件,进行查询。
    1. public DailyTrainTicket selectByUnique(Date date, String trainCode,String start,String end) {
    2.         DailyTrainTicketExample dailyTrainTicketExample = new DailyTrainTicketExample();
    3.         dailyTrainTicketExample.createCriteria()
    4.                 .andDateEqualTo(date)
    5.                 .andTrainCodeEqualTo(trainCode)
    6.                 .andStartEqualTo(start)
    7.                 .andEndEqualTo(end);
    8.         List<DailyTrainTicket> list = dailyTrainTicketMapper.selectByExample(dailyTrainTicketExample);
    9.         if(CollUtil.isNotEmpty(list)) {
    10.             return list.get(0);
    11.         }else {
    12.             return null;
    13.         }
    14.     }
    复制代码
  • 进行票数的预扣减,由于前端的是实时体现到界面上,因此必要一个变量,而这里只要不更新到数据库,怎么扣减都可以,因此可以之间操作查出的库存记录。一张票一张票的循环进行扣减,由于选择的大概是不同的座位类型(不同的人),因此不能按照列表进行扣票,应该按照不同的座位类型进行扣票。
    1. private static void reduceTickets(ConfirmOrderDoReq req, DailyTrainTicket dailyTrainTicket) {
    2.         for (ConfirmOrderTicketReq ticketReq : req.getTickets()) {
    3.             String seatTypeCode = ticketReq.getSeatTypeCode();
    4.             SeatTypeEnum seatTypeEnum = EnumUtil.getBy(SeatTypeEnum::getCode, seatTypeCode);
    5.             switch (seatTypeEnum) {
    6.                 case YDZ -> {
    7.                     int countLeft = dailyTrainTicket.getYdz() - 1;
    8.                     if(countLeft < 0) {
    9.                         throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_COUNT_ERROR);
    10.                     }
    11.                     dailyTrainTicket.setYdz(countLeft);
    12.                 }
    13.                 case EDZ -> {
    14.                     int countLeft = dailyTrainTicket.getEdz() - 1;
    15.                     if(countLeft < 0) {
    16.                         throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_COUNT_ERROR);
    17.                     }
    18.                     dailyTrainTicket.setEdz(countLeft);
    19.                 }
    20.                 case RW -> {
    21.                     int countLeft = dailyTrainTicket.getRw() - 1;
    22.                     if(countLeft < 0) {
    23.                         throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_COUNT_ERROR);
    24.                     }
    25.                     dailyTrainTicket.setRw(countLeft);
    26.                 }
    27.                 case YW -> {
    28.                     int countLeft = dailyTrainTicket.getYw() - 1;
    29.                     if(countLeft < 0) {
    30.                         throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_COUNT_ERROR);
    31.                     }
    32.                     dailyTrainTicket.setYw(countLeft);
    33.                 }
    34.             }
    35.         }
    36.     }
    复制代码
  • 偏移值的盘算,这里的偏移值指的是相对于第一个选座位置的偏移值,如许子可以一次循环找到全部位置的值(假设一个车厢50个座位,A,B,C,D,E各10个,现在A全部被买了,如果现在选座A,C那么不会选座乐成)。对于第一个购票来说,要么是乐成,要么不乐成,因此就可以根据第一个张票乐成与否的情况判断后面的票是否能选,即本次购票是否有选座。对于有选座的功能,还必要偏移值进行盘算,得到的效果有空位算是选座乐成。由于一等座和二等座每排位置不同,还必要知道座位的类型,之后组成和前端两排选座一样的列表,用于作参照的座位列表,必要两次循环,因为有两排,这一步就是座位的初始化。之后盘算绝对的偏移值,再根据第一个位置盘算相对的偏移值
  • 挑车厢:对于购票没有选座的来说,比较简朴,只要这个座位是可选的即可,必要一张票一张票的去挑选。在买票之前,起首要知道购买票的类型在哪一个车厢中,因此必要是要写一个探求车厢的方法,根据日期,车次,和车票类型,然后把上面的看成传入的参数就可以得到符合条件的车厢了,之后在得到的车厢列表中一个个的探求。
  • 根据车厢挑座位:根据获取车厢的方法获得符合条件的车厢之后,现在得到的车厢都是一种票型了,先获取起始车厢,以及进入车厢前的座位列表,座位列表可以根据日期,车次以及车厢位置来获取,因为一个车厢的座位类型是相同的,然后就一个车厢一个车厢的探求。下面的选座就是调用了本段写的getSeat方法,在有选座的情况下,必要知道第一个选座的实际列,以及得到的偏移值,根据这两个参数进行选座。在没有选座的情况,并没有特定的列号,也没有偏移值,那就传null,只是一个座位一个座位的选座。对于座位还应该进行排序,根据座位索引进行排序。现如今插入车厢的座位后,第一次必要一个座位一个座位的挑选,写个循环,看每个座位是否可卖,可卖的话之间返回,否则跳过。
  • 判断是否可卖:1表示在这个区间买过票了,就不可以或许售票了。0表示在这个给区间没有卖票。比方:sell=10001,本次购买的区间是1~4,则区间应该售000,这里是10001中间的三个0,000要变成111,那么之后可以根据或运算变成11111,即10001|01110==11111,如果sell=00001,那么按位或得到01111,之后转为数字是15,但再转成二进制是1111,不是01111,因此必要补0。
    1. private boolean calSell(DailyTrainSeat dailyTrainSeat,Integer startIndex,
    2.                          Integer endIndex) {
    3.         String sell = dailyTrainSeat.getSell();
    4.         String sellPart = sell.substring(startIndex, endIndex);
    5.         if(Integer.parseInt(sellPart) > 0) {
    6.             LOG.info("座位{}在本车站区间{}--{}已售过票,不可选中该座位",dailyTrainSeat.getCarriageSeatIndex(),
    7.                     startIndex,endIndex);
    8.             return false;
    9.         }else {
    10.             LOG.info("座位{}在本车站区间{}--{}未售过票,可选中该座位",dailyTrainSeat.getCarriageSeatIndex(),
    11.                     startIndex,endIndex);
    12.             // 111
    13.             String curSell = sellPart.replace('0', '1');
    14.             // 0111
    15.             curSell = StrUtil.fillBefore(curSell,'0', endIndex);
    16.             curSell = StrUtil.fillAfter(curSell,'0',sell.length());
    17.             // 当前区间售票信息curSell与库里的已售信息sell按位与,即可得到该座位卖出此票后的售票详情
    18.             // 32
    19.             int newSellInt = NumberUtil.binaryToInt(curSell) | NumberUtil.binaryToInt(sell);
    20.             // 11111
    21.             String newSell = NumberUtil.getBinaryStr(newSellInt);
    22.             newSell = StrUtil.fillBefore(newSell,'0',sell.length());
    23.             LOG.info("座位{}在本车站区间{}--{}卖出该票后,最终售票详情:{}",dailyTrainSeat.getCarriageSeatIndex(),
    24.                     startIndex,endIndex,newSell);
    25.             dailyTrainSeat.setSell(newSell);
    26.             return true;
    27.         }
    复制代码
  • 优化getSeat方法:起首判断传入的column和拿到的col,进行比较,如果无值,表示无选座,有值必要判断是否可以匹配,不匹配的话跳过。现在选完第一个座位后,接下来根据偏移值选则后面几人的座位,偏移值大概有多个,从索引1开始(0是以及选完的第一个座位)进行循环,下一个位置是索引+偏移量,然后根据是否超卖判断是否可选。还要留意,选座是在同一个车厢,根据下一个索引是否大于车厢座位数
  • 保存最终的选座效果,但不是更新到数据库中。此时用一个暂时变量保存选择的座位,当下一个选座时,应该看这个选座的效果以及最终的选座效果(这个最闭幕果是之前的效果,如果此次选座乐成,那么就更新最终选座),否则就出现挑选同一个座位的情况。比方系统分配座位的时间,先分配座位3,由于同一个人为不同人买多张票,在进行第二张票购买前,没有更新到数据库中,导致下一个分配的还是3号座位,这显然是不合理的,因此必要对每一次选择的座位进行保存,为了让下一次正确的挑选座位。对于选座的情况,由于第一次选座乐成的情况下,大概之后根据偏移值选择的其他座位不乐成,如果直接保存的最闭幕果,大概导致效果和符合的不一致,因此也是必要保存到暂时变量中,留意没有乐成选座时清空暂时变量,当两种情况都乐成选座后,才真正的保存到最终选座的变量中。
  • 根据售卖信息更新座位售卖情况,就是更新数据库的售卖信息,比方从000更新到101.就是选好座位后,将座位信息更新到日常座位表中。由于后面还必要涉及到修改余票,为会员增加购票记录,更新订单状态,因此可以把他们称为选座后的事件处理。可以在最后修改数据库的时间增加事件,如许子占用事件的时间较少,否则的话占用大量的数据库资源。由于本垒方法间的调用,事件不生效,因此增加一个类,专门进行处理。根据最终选票效果来处理。此时必要更新的是id以及售卖票的信息还要更新时间,不必要更新表中全部的字段。
    1.     @Transactional
    2.     public void afterDoConfirm(List<DailyTrainSeat> finalSeatList) {
    3.         for (DailyTrainSeat dailyTrainSeat: finalSeatList){
    4.             DailyTrainSeat seatForUpdate = new DailyTrainSeat();
    5.             seatForUpdate.setId(dailyTrainSeat.getId());
    6.             seatForUpdate.setSell(dailyTrainSeat.getSell());
    7.             seatForUpdate.setUpdateTime(new Date());
    8.             dailyTrainSeatMapper.updateByPrimaryKeySelective(seatForUpdate);
    9.         }
    10.     }
    复制代码
  • 扣减库存(很重要):售卖一张票影响的是多个区间的库存,就是本次选座之前没卖过票的,而且本次购买的区间有交集的区间,怎么明白,如下图:


由于座位区间2没有卖过票,而且AD和AE与购买的CD区间有交集,因此减库存。 


由于 座位区间1已经卖过票了,因此不必要减库存。
因此先盘算区间,然后根据区间来进行扣减库存。必要盘算出最大最小开始竣事的影响区间(4个参数)。

 这里买了4(startIndex)到7(endIndex)站的票,购买区间下标7不更新是因为在第七站下车,不会影响第七站的购票,起首看最小开始影响的下标,这里是3,因此minStartIndex = startIndex - 往前碰0到的最后一个0,由于这里购买7今后不会前面的影响库存,因此maxStratIndex = endIndex - 1,表示在[minStartIndex,maxStartIndex]这个区间开始的都会影响其他库存。如果是在3下标竣事不会影响其他库存,但是4下标开始有影响,即minEndIndex= startIndex+1,同理maxEndIndex=endIndex+ 今后碰0到的最后一个0,表示在[minEndIndex,maxEndIndex]这个区间竣事都会影响库存。
  1. Integer startIndex = dailyTrainTicket.getStartIndex();
  2. Integer endIndex = dailyTrainTicket.getEndIndex();
  3. char[] chars = seatForUpdate.getSell().toCharArray();
  4. Integer maxStartIndex = endIndex - 1;
  5. Integer minEndIndex = startIndex + 1;
  6. Integer minStartIndex = 0;
  7. for (int i = startIndex - 1; i >= 0; i--) {
  8.     char aChar = chars[i];
  9.     if(aChar == '1') {
  10.        minStartIndex = i + 1;
  11.        break;
  12.     }
  13. }
  14. LOG.info("影响出发站区间:"+minStartIndex+"-"+maxStartIndex);
  15. Integer maxEndIndex = seatForUpdate.getSell().length();
  16. for (int i = endIndex; i < seatForUpdate.getSell().length(); i++) {
  17.     char aChar = chars[i];
  18.     if(aChar == '1') {
  19.        maxEndIndex = i;
  20.        break;
  21.     }
  22. }
  23. LOG.info("影响结束站区间:"+minEndIndex+"-"+maxEndIndex);
  24. dailyTrainTicketMapperCust.updateCountBySell(
  25.    dailyTrainSeat.getDate(),
  26.    dailyTrainSeat.getTrainCode(),
  27.    dailyTrainSeat.getSeatType(),
  28.    minStartIndex,
  29.    maxStartIndex,
  30.    minEndIndex,
  31.    maxEndIndex);
复制代码
之后根据这四个值进行库存的更新。


  • 对乘客增加车票表,由于之前的购票表订单状态由大概为失败,而且信息不容易搜刮,因此新建一张表,用来保存乘客购买车票乐成的信息,由于每个会员常常查自己购买的车票,因此可以把会员id看成索引。由于生成的表在member模块,购票业务在business模块,当business模块购票乐成后调用member模块的接口,把数据传入,这里使用的了feign。对于会员车票参数来说,在business模块必要调用它进行车票的构造,number模块必要调用它进行入库,因此可以放到common模块。
  • 要启用feign,必要在调用方buiness增加依靠,之后在启动类设置路径,表示哪个包是属于feign的,之后在相关的路径增加一个接口,路径设置是调用的哪个包下的接口。

之后在business模块就可以调用member的接口,为会员(乘客)增加一张票。之后可以为会员段增加我的车票,方便查看车票。但这里必要根据会员的id查看,只可以或许查看自己买的车票。
最后更新订单状态为乐成,根据id更新 更新时间以及状态

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

忿忿的泥巴坨

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表