九车票预定功能开发

Posted 夏雪冬蝉

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了九车票预定功能开发相关的知识,希望对你有一定的参考价值。

内容

  • 余票查询(控台端)

    余票初始化、余票查询

  • 选座购票(会员端)

    余票查询、选择乘客、选择座位类型、选择座位、下单购票

增加余票信息表以提高余票查询性能

第一步:建表

 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` int not null comment \'出发站序|本站是整个车次的第几站\',
                                       `end` varchar(20) not null comment \'到达站\',
                                       `end_pinyin` varchar(50) not null comment \'到达站拼音\',
                                       `end_time` time not null comment \'到站时间\',
                                       `end_index` int 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=\'余票信息\';

比如说有5个座位ABCDE,那么可售区间有4个,1111就是所有区间已售空。0000就是所有区间未售。如果想要买A-C的座位,如果A-C的售票区间中包含1,那么则不可售(A-B为1,B-C为0,那么A-C肯定不可售)。

余票查询会显示还有多少张票,票数如果实时通过每日座位表的sell来计算,会影响性能,所以要另外做张表,直接存储余票数。

一个火车经过5个站,就能生成10个余票信息(4+3+2+1),会影响10个余票记录。

难点:如何把售票信息转换为单表的信息

第二步:用代码生成器生成dao、service、controller、请求和响应、前端页面。

生成车次时初始化余票信息

问题:

  • 表的数据什么时候初始化

    车次生成,该表应该也生成,也就是每日自动生成。

  • 数据是怎么来的(出发站、到达站怎么形成?不同座位类型车票数怎么来)

    G1   ABCDE

    G2   CD

    此时用户查找CD会出现G1、G2。

    车站是一个嵌套循环

    AB    BC    CD    DE

    AC    BD    CE

    AD    BE

    AE

    车票就把每日座位的记录copy一下

DailyTrainTicketService需要增加genDaily方法,用来创建哪一天哪个车次的数据,用来初始化车站。

 @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);
 
     
DailyTrainTicketService.java

一定要加事务@TranSaction,防止生成失败。

下面生成余票数量

只要计算在每日车次表中每一类座位的数量,因此在DailyTrainSeatService中添加countSeat函数,如果没有该座位类型,则返回-1。

 public int countSeat(Date date, String trainCode, String seatType) 
         DailyTrainSeatExample example = new DailyTrainSeatExample();
         example.createCriteria()
                 .andDateEqualTo(date)
                 .andTrainCodeEqualTo(trainCode)
                 .andSeatTypeEqualTo(seatType);
         long l = dailyTrainSeatMapper.countByExample(example);
         if (l == 0L) 
             return -1;
         
         return (int) l;
     
DailyTrainTicketService.java

DailyTrainTicketService就可以调用该方法。

                 int ydz = dailyTrainSeatService.countSeat(date, trainCode, SeatTypeEnum.YDZ.getCode());
                 int edz = dailyTrainSeatService.countSeat(date, trainCode, SeatTypeEnum.EDZ.getCode());
                 int rw = dailyTrainSeatService.countSeat(date, trainCode, SeatTypeEnum.RW.getCode());
                 int yw = dailyTrainSeatService.countSeat(date, trainCode, SeatTypeEnum.YW.getCode());
                 // 票价 = 里程之和 * 座位单价 * 车次类型系数
                 String trainType = dailyTrain.getType();
                 // 计算票价系数:TrainTypeEnum.priceRate
                 BigDecimal priceRate = EnumUtil.getFieldBy(TrainTypeEnum::getPriceRate, TrainTypeEnum::getCode, trainType);
                 BigDecimal ydzPrice = sumKM.multiply(SeatTypeEnum.YDZ.getPrice()).multiply(priceRate).setScale(2, RoundingMode.HALF_UP);
                 BigDecimal edzPrice = sumKM.multiply(SeatTypeEnum.EDZ.getPrice()).multiply(priceRate).setScale(2, RoundingMode.HALF_UP);
                 BigDecimal rwPrice = sumKM.multiply(SeatTypeEnum.RW.getPrice()).multiply(priceRate).setScale(2, RoundingMode.HALF_UP);
                 BigDecimal ywPrice = sumKM.multiply(SeatTypeEnum.YW.getPrice()).multiply(priceRate).setScale(2, RoundingMode.HALF_UP);

将dailyTrainTicketService.genDaily(dailyTrain, date, train.getCode())方法注入DailyTrainService.java。

对于车票查询,增加四个判断

         if (ObjectUtil.isNotNull(req.getDate())) 
             criteria.andDateEqualTo(req.getDate());
         
         if (ObjectUtil.isNotEmpty(req.getTrainCode())) 
             criteria.andTrainCodeEqualTo(req.getTrainCode());
         
         if (ObjectUtil.isNotEmpty(req.getStart())) 
             criteria.andStartEqualTo(req.getStart());
         
         if (ObjectUtil.isNotEmpty(req.getEnd())) 
             criteria.andEndEqualTo(req.getEnd());
         

为会员端增加余票查询功能

businiss的controller增加一个admin包,用来给web访问。

会员端只能通过日期、始发站、终点站查询。查询功能和上面差不多,只是要通过三个条件查询。

 package com.zihans.train.business.controller;
 
 import com.zihans.train.business.req.DailyTrainTicketQueryReq;
 import com.zihans.train.business.resp.DailyTrainTicketQueryResp;
 import com.zihans.train.business.service.DailyTrainTicketService;
 import com.zihans.train.common.resp.CommonResp;
 import com.zihans.train.common.resp.PageResp;
 import jakarta.annotation.Resource;
 import jakarta.validation.Valid;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
 @RestController
 @RequestMapping("/daily-train-ticket")
 public class DailyTrainTicketController 
     @Resource
     private DailyTrainTicketService dailyTrainTicketService;
 
     @GetMapping("/query-list")
     public CommonResp<PageResp<DailyTrainTicketQueryResp>> queryList(@Valid DailyTrainTicketQueryReq req) 
         PageResp<DailyTrainTicketQueryResp> list = dailyTrainTicketService.queryList(req);
         return new CommonResp<>(list);
     
 
DailyTrainTicketController.java

订票页面

可以查询所有乘客

     /**
      * 查询我的所有乘客
      */
     public List<PassengerQueryResp> queryMine() 
         PassengerExample passengerExample = new PassengerExample();
         passengerExample.setOrderByClause("name asc");
         PassengerExample.Criteria criteria = passengerExample.createCriteria();
         criteria.andMemberIdEqualTo(LoginMemberContext.getId());
         List<Passenger> list = passengerMapper.selectByExample(passengerExample);
         return BeanUtil.copyToList(list, PassengerQueryResp.class);
     
PassengerService.java
     @GetMapping("/query-mine")
     public CommonResp<List<PassengerQueryResp>> queryMine() 
         List<PassengerQueryResp> list = passengerService.queryMine();
         return new CommonResp<>(list);
     
PassengerController.java

分解选座购票功能的前后端逻辑

12306规则:

  只有全部是一等座或全部是二等座才支持选座

  余票小于一定数量时,不允许选座(以20为例)

选座效果

  A    B    C    D    F

  A    B    C    D    F

后端购票逻辑

1、不选座,遍历一等座车厢,每个车厢从1号座位找,未购买就选中。

2、选座,以购买两张一等座为例,先遍历一遍一等座车箱,每个车厢从第一个座位开始找A,未被购买就预定;再根据B相对于A的偏移值找B。

售卖情况:如果有ABCDE五个站,sell=0110,则AB可买,AC不可买。

增加确认订单表并生成前后端代码

 drop table if exists `confirm_order`;
 create table `confirm_order` (
                                  `id` bigint not null comment \'id\',
                                  `member_id` bigint not null comment \'会员id\',
                                  `date` date not null comment \'日期\',
                                  `train_code` varchar(20) not null comment \'车次编号\',
                                  `start` varchar(20) not null comment \'出发站\',
                                  `end` varchar(20) not null comment \'到达站\',
                                  `daily_train_ticket_id` bigint not null comment \'余票ID\',
                                  `tickets` json not null comment \'车票\',
                                  `status` char(1) not null comment \'订单状态|枚举[ConfirmOrderStatusEnum]\',
                                  `create_time` datetime(3) comment \'新增时间\',
                                  `update_time` datetime(3) comment \'修改时间\',
                                  primary key (`id`),
                                  index `date_train_code_index` (`date`, `train_code`)
 ) engine=innodb default charset=utf8mb4 comment=\'确认订单\';

对于重要的功能,要在接口入口落库,留下痕迹。可以方便统计买票高峰,购买率等。

增加确认下单购票接口

 package com.zihans.train.business.req;
 
 import jakarta.validation.constraints.NotBlank;
 import jakarta.validation.constraints.NotNull;
 
 public class ConfirmOrderTicketReq 
 
     /**
      * 乘客ID
      */
     @NotNull(message = "【乘客ID】不能为空")
     private Long passengerId;
 
     /**
      * 乘客票种
      */
     @NotBlank(message = "【乘客票种】不能为空")
     private String passengerType;
 
     /**
      * 乘客名称
      */
     @NotBlank(message = "【乘客名称】不能为空")
     private String passengerName;
 
     /**
      * 乘客身份证
      */
     @NotBlank(message = "【乘客身份证】不能为空")
     private String passengerIdCard;
 
     /**
      * 座位类型code
      */
     @NotBlank(message = "【座位类型code】不能为空")
     private String seatTypeCode;
 
     /**
      * 选座,可空,值示例:A1
      */
     private String seat;
 
     public Long getPassengerId() 
         return passengerId;
     
 
     public void setPassengerId(Long passengerId) 
         this.passengerId = passengerId;
     
 
     public String getPassengerType() 
         return passengerType;
     
 
     public void setPassengerType(String passengerType) 
         this.passengerType = passengerType;
     
 
     public String getPassengerName() 
         return passengerName;
     
 
     public void setPassengerName(String passengerName) 
         this.passengerName = passengerName;
     
 
     public String getPassengerIdCard() 
         return passengerIdCard;
     
 
     public void setPassengerIdCard(String passengerIdCard) 
         this.passengerIdCard = passengerIdCard;
     
 
     public String getSeatTypeCode() 
         return seatTypeCode;
     
 
     public void setSeatTypeCode(String seatTypeCode) 
         this.seatTypeCode = seatTypeCode;
     
 
     public String getSeat() 
         return seat;
     
 
     public void setSeat(String seat) 
         this.seat = seat;
     
 
     @Override
     public String toString() 
         final StringBuilder sb = new StringBuilder("ConfirmOrderTicketReq");
         sb.append("passengerId=").append(passengerId);
         sb.append(", passengerType=\'").append(passengerType).append(\'\\\'\');
         sb.append(", passengerName=\'").append(passengerName).append(\'\\\'\');
         sb.append(", passengerIdCard=\'").append(passengerIdCard).append(\'\\\'\');
         sb.append(", seatTypeCode=\'").append(seatTypeCode).append(\'\\\'\');
         sb.append(", seat=\'").append(seat).append(\'\\\'\');
         sb.append(\'\');
         return sb.toString();
     
 
ConfirmOrderTicketReq.java
 package com.zihans.train.business.req;
 
 import com.fasterxml.jackson.annotation.JsonFormat;
 import jakarta.validation.constraints.NotBlank;
 import jakarta.validation.constraints.NotNull;
 
 import java.util.Date;
 import java.util.List;
 
 public class ConfirmOrderDoReq 
 
     /**
      * 会员id
      */
     @NotNull(message = "【会员id】不能为空")
     private Long memberId;
 
     /**
      * 日期
      */
     @JsonFormat(pattern = "yyyy-MM-dd",timezone = "GMT+8")
     @NotNull(message = "【日期】不能为空")
     private Date date;
 
     /**
      * 车次编号
      */
     @NotBlank(message = "【车次编号】不能为空")
     private String trainCode;
 
     /**
      * 出发站
      */
     @NotBlank(message = "【出发站】不能为空")
     private String start;
 
     /**
      * 到达站
      */
     @NotBlank(message = "【到达站】不能为空")
     private String end;
 
     /**
      * 余票ID
      */
     @NotNull(message = "【余票ID】不能为空")
     private Long dailyTrainTicketId;
 
     /**
      * 车票
      */
     @NotBlank(message = "【车票】不能为空")
     private List<ConfirmOrderTicketReq> tickets;
 
 
     public Long getMemberId() 
         return memberId;
     
 
     public void setMemberId(Long memberId) 
         this.memberId = memberId;
     
 
     public Date getDate() 
         return date;
     
 
     public void setDate(Date date) 
         this.date = date;
     
 
     public String getTrainCode() 
         return trainCode;
     
 
     public void setTrainCode(String trainCode) 
         this.trainCode = trainCode;
     
 
     public String getStart() 
         return start;
     
 
     public void setStart(String start) 
         this.start = start;
     
 
     public String getEnd() 
         return end;
     
 
     public void setEnd(String end) 
         this.end = end;
     
 
     public Long getDailyTrainTicketId() 
         return dailyTrainTicketId;
     
 
     public void setDailyTrainTicketId(Long dailyTrainTicketId) 
         this.dailyTrainTicketId = dailyTrainTicketId;
     
 
 
     @Override
     public String toString() 
         return "ConfirmOrderDoReq" +
                 "memberId=" + memberId +
                 ", date=" + date +
                 ", trainCode=\'" + trainCode + \'\\\'\' +
                 ", start=\'" + start + \'\\\'\' +
                 ", end=\'" + end + \'\\\'\' +
                 ", dailyTrainTicketId=" + dailyTrainTicketId +
                 ", tickets=" + tickets +
                 \'\';
     
 
     public List<ConfirmOrderTicketReq> getTickets() 
         return tickets;
     
 
     public void setTickets(List<ConfirmOrderTicketReq> tickets) 
         this.tickets = tickets;
     
 
 
ConfirmOrderDoReq.java

校验订单

 package com.zihans.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.util.EnumUtil;
 import cn.hutool.core.util.NumberUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
 import com.alibaba.fastjson.JSON;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
 import com.zihans.train.business.domain.*;
 import com.zihans.train.business.enums.ConfirmOrderStatusEnum;
 import com.zihans.train.business.enums.SeatColEnum;
 import com.zihans.train.business.enums.SeatTypeEnum;
 import com.zihans.train.business.mapper.ConfirmOrderMapper;
 import com.zihans.train.business.req.ConfirmOrderDoReq;
 import com.zihans.train.business.req.ConfirmOrderQueryReq;
 import com.zihans.train.business.req.ConfirmOrderTicketReq;
 import com.zihans.train.business.resp.ConfirmOrderQueryResp;
 import com.zihans.train.common.context.LoginMemberContext;
 import com.zihans.train.common.exception.BusinessException;
 import com.zihans.train.common.exception.BusinessExceptionEnum;
 import com.zihans.train.common.resp.PageResp;
 import com.zihans.train.common.util.SnowUtil;
 import jakarta.annotation.Resource;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Service;
 
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 
 @Service
 public class ConfirmOrderService 
 
     private static final Logger LOG = LoggerFactory.getLogger(ConfirmOrderService.class);
 
     @Resource
     private ConfirmOrderMapper confirmOrderMapper;
 
     @Resource
     private DailyTrainTicketService dailyTrainTicketService;
 
     @Resource
     private DailyTrainCarriageService dailyTrainCarriageService;
 
     @Resource
     private DailyTrainSeatService dailyTrainSeatService;
 
     @Resource
     private AfterConfirmOrderService afterConfirmOrderService;
 
     public void save(ConfirmOrderDoReq req) 
         DateTime now = DateTime.now();
         ConfirmOrder confirmOrder = BeanUtil.copyProperties(req, ConfirmOrder.class);
         if (ObjectUtil.isNull(confirmOrder.getId())) 
             confirmOrder.setId(SnowUtil.getSnowflakeNextId());
             confirmOrder.setCreateTime(now);
             confirmOrder.setUpdateTime(now);
             confirmOrderMapper.insert(confirmOrder);
          else 
             confirmOrder.setUpdateTime(now);
             confirmOrderMapper.updateByPrimaryKey(confirmOrder);
         
     
 
     public PageResp<ConfirmOrderQueryResp> queryList(ConfirmOrderQueryReq req) 
         ConfirmOrderExample confirmOrderExample = new ConfirmOrderExample();
         confirmOrderExample.setOrderByClause("id desc");
         ConfirmOrderExample.Criteria criteria = confirmOrderExample.createCriteria();
 
         LOG.info("查询页码:", req.getPage());
         LOG.info("每页条数:", req.getSize());
         PageHelper.startPage(req.getPage(), req.getSize());
         List<ConfirmOrder> confirmOrderList = confirmOrderMapper.selectByExample(confirmOrderExample);
 
         PageInfo<ConfirmOrder> pageInfo = new PageInfo<>(confirmOrderList);
         LOG.info("总行数:", pageInfo.getTotal());
         LOG.info("总页数:", pageInfo.getPages());
 
         List<ConfirmOrderQueryResp> list = BeanUtil.copyToList(confirmOrderList, ConfirmOrderQueryResp.class);
 
         PageResp<ConfirmOrderQueryResp> pageResp = new PageResp<>();
         pageResp.setTotal(pageInfo.getTotal());
         pageResp.setList(list);
         return pageResp;
     
 
     public void delete(Long id) 
         confirmOrderMapper.deleteByPrimaryKey(id);
     
 
     public void doConfirm(ConfirmOrderDoReq req) 
         // 省略业务数据校验,如:车次是否存在,余票是否存在,车次是否在有效期内,tickets条数>0,同乘客同车次是否已买过
 
         Date date = req.getDate();
         String trainCode = req.getTrainCode();
         String start = req.getStart();
         String end = req.getEnd();
         List<ConfirmOrderTicketReq> tickets = req.getTickets();
 
         // 保存确认订单表,状态初始
         DateTime now = DateTime.now();
         ConfirmOrder confirmOrder = new ConfirmOrder();
         confirmOrder.setId(SnowUtil.getSnowflakeNextId());
         confirmOrder.setCreateTime(now);
         confirmOrder.setUpdateTime(now);
         confirmOrder.setMemberId(LoginMemberContext.getId());
         confirmOrder.setDate(date);
         confirmOrder.setTrainCode(trainCode);
         confirmOrder.setStart(start);
         confirmOrder.setEnd(end);
         confirmOrder.setDailyTrainTicketId(req.getDailyTrainTicketId());
         confirmOrder.setStatus(ConfirmOrderStatusEnum.INIT.getCode());
         confirmOrder.setTickets(JSON.toJSONString(tickets));
         confirmOrderMapper.insert(confirmOrder);
 
         // 查出余票记录,需要得到真实的库存
         DailyTrainTicket dailyTrainTicket = dailyTrainTicketService.selectByUnique(date, trainCode, start, end);
         LOG.info("查出余票记录:", dailyTrainTicket);
 
         // 预扣减余票数量,并判断余票是否足够
         reduceTickets(req, dailyTrainTicket);
 
         // 最终的选座结果
         List<DailyTrainSeat> finalSeatList = new ArrayList<>();
         // 计算相对第一个座位的偏移值
         // 比如选择的是C1,D2,则偏移值是:[0,5]
         // 比如选择的是A1,B1,C1,则偏移值是:[0,1,2]
         ConfirmOrderTicketReq ticketReq0 = tickets.get(0);
         if(StrUtil.isNotBlank(ticketReq0.getSeat())) 
             LOG.info("本次购票有选座");
             // 查出本次选座的座位类型都有哪些列,用于计算所选座位与第一个座位的偏离值
             List<SeatColEnum> colEnumList = SeatColEnum.getColsByType(ticketReq0.getSeatTypeCode());
             LOG.info("本次选座的座位类型包含的列:", colEnumList);
 
             // 组成和前端两排选座一样的列表,用于作参照的座位列表,例:referSeatList = A1, C1, D1, F1, A2, C2, D2, F2
             List<String> referSeatList = new ArrayList<>();
             for (int i = 1; i <= 2; i++) 
                 for (SeatColEnum seatColEnum : colEnumList) 
                     referSeatList.add(seatColEnum.getCode() + i);
                 
             
             LOG.info("用于作参照的两排座位:", referSeatList);
 
             List<Integer> offsetList = new ArrayList<>();
             // 绝对偏移值,即:在参照座位列表中的位置
             List<Integer> aboluteOffsetList = new ArrayList<>();
             for (ConfirmOrderTicketReq ticketReq : tickets) 
                 int index = referSeatList.indexOf(ticketReq.getSeat());
                 aboluteOffsetList.add(index);
             
             LOG.info("计算得到所有座位的绝对偏移值:", aboluteOffsetList);
             for (Integer index : aboluteOffsetList) 
                 int offset = index - aboluteOffsetList.get(0);
                 offsetList.add(offset);
             
             LOG.info("计算得到所有座位的相对第一个座位的偏移值:", offsetList);
 
             getSeat(finalSeatList,
                     date,
                     trainCode,
                     ticketReq0.getSeatTypeCode(),
                     ticketReq0.getSeat().split("")[0], // 从A1得到A
                     offsetList,
                     dailyTrainTicket.getStartIndex(),
                     dailyTrainTicket.getEndIndex()
             );
 
          else 
             LOG.info("本次购票没有选座");
             for (ConfirmOrderTicketReq ticketReq : tickets) 
                 getSeat(finalSeatList,
                         date,
                         trainCode,
                         ticketReq.getSeatTypeCode(),
                         null,
                         null,
                         dailyTrainTicket.getStartIndex(),
                         dailyTrainTicket.getEndIndex()
                 );
             
         
 
         LOG.info("最终选座:", finalSeatList);
 
         // 选中座位后事务处理:
         // 座位表修改售卖情况sell;
         // 余票详情表修改余票;
         // 为会员增加购票记录
         // 更新确认订单为成功
         afterConfirmOrderService.afterDoConfirm(dailyTrainTicket, finalSeatList, tickets, confirmOrder);
 
 
 
     
 
     /**
      * 挑座位,如果有选座,则一次性挑完,如果无选座,则一个一个挑
      * @param date
      * @param trainCode
      * @param seatType
      * @param column
      * @param offsetList
      */
     private void getSeat(List<DailyTrainSeat> finalSeatList, Date date, String trainCode, String seatType, String column, List<Integer> offsetList, Integer startIndex, Integer endIndex) 
         List<DailyTrainSeat> getSeatList = new ArrayList<>();
         List<DailyTrainCarriage> carriageList = dailyTrainCarriageService.selectBySeatType(date, trainCode, seatType);
         LOG.info("共查出个符合条件的车厢", carriageList.size());
 
         // 一个车箱一个车箱的获取座位数据
         for (DailyTrainCarriage dailyTrainCarriage : carriageList) 
             LOG.info("开始从车厢选座", dailyTrainCarriage.getIndex());
             getSeatList = new ArrayList<>();
             List<DailyTrainSeat> seatList = dailyTrainSeatService.selectByCarriage(date, trainCode, dailyTrainCarriage.getIndex());
             LOG.info("车厢的座位数:", dailyTrainCarriage.getIndex(), seatList.size());
             for (int i = 0; i < seatList.size(); i++) 
                 DailyTrainSeat dailyTrainSeat = seatList.get(i);
                 Integer seatIndex = dailyTrainSeat.getCarriageSeatIndex();
                 String col = dailyTrainSeat.getCol();
 
                 // 判断当前座位不能被选中过
                 boolean alreadyChooseFlag = false;
                 for (DailyTrainSeat finalSeat : finalSeatList)
                     if (finalSeat.getId().equals(dailyTrainSeat.getId())) 
                         alreadyChooseFlag = true;
                         break;
                     
                 
                 if (alreadyChooseFlag) 
                     LOG.info("座位被选中过,不能重复选中,继续判断下一个座位", seatIndex);
                     continue;
                 
 
                 // 判断column,有值的话要比对列号
                 if (StrUtil.isBlank(column)) 
                     LOG.info("无选座");
                  else 
                     if (!column.equals(col)) 
                         LOG.info("座位列值不对,继续判断下一个座位,当前列值:,目标列值:", seatIndex, col, column);
                         continue;
                     
                 
 
                 boolean isChoose = calSell(dailyTrainSeat, startIndex, endIndex);
                 if (isChoose) 
                     LOG.info("选中座位");
                     getSeatList.add(dailyTrainSeat);
                  else 
                     continue;
                 
 
                 // 根据offset选剩下的座位
                 boolean isGetAllOffsetSeat = true;
                 if (CollUtil.isNotEmpty(offsetList)) 
                     LOG.info("有偏移值:,校验偏移的座位是否可选", offsetList);
                     // 从索引1开始,索引0就是当前已选中的票
                     for (int j = 1; j < offsetList.size(); j++) 
                         Integer offset = offsetList.get(j);
                         // 座位在库的索引是从1开始
                         // int nextIndex = seatIndex + offset - 1;
                         int nextIndex = i + offset;
 
                         // 有选座时,一定是在同一个车箱
                         if (nextIndex >= seatList.size()) 
                             LOG.info("座位不可选,偏移后的索引超出了这个车箱的座位数", nextIndex);
                             isGetAllOffsetSeat = false;
                             break;
                         
 
                         DailyTrainSeat nextDailyTrainSeat = seatList.get(nextIndex);
                         boolean isChooseNext = calSell(nextDailyTrainSeat, startIndex, endIndex);
                         if (isChooseNext) 
                             LOG.info("座位被选中", nextDailyTrainSeat.getCarriageSeatIndex());
                             getSeatList.add(nextDailyTrainSeat);
                          else 
                             LOG.info("座位不可选", nextDailyTrainSeat.getCarriageSeatIndex());
                             isGetAllOffsetSeat = false;
                             break;
                         
                     
                 
                 if (!isGetAllOffsetSeat) 
                     getSeatList = new ArrayList<>();
                     continue;
                 
 
                 // 保存选好的座位
                 finalSeatList.addAll(getSeatList);
                 return;
             
         
     
 
     /**
      * 计算某座位在区间内是否可卖
      * 例:sell=10001,本次购买区间站1~4,则区间已售000
      * 全部是0,表示这个区间可买;只要有1,就表示区间内已售过票
      *
      * 选中后,要计算购票后的sell,比如原来是10001,本次购买区间站1~4
      * 方案:构造本次购票造成的售卖信息01110,和原sell 10001按位与,最终得到11111
      */
     private boolean calSell(DailyTrainSeat dailyTrainSeat, Integer startIndex, Integer endIndex) 
         // 00001, 00000
         String sell = dailyTrainSeat.getSell();
         //  000, 000
         String sellPart = sell.substring(startIndex, endIndex);
         if (Integer.parseInt(sellPart) > 0) 
             LOG.info("座位在本次车站区间~已售过票,不可选中该座位", dailyTrainSeat.getCarriageSeatIndex(), startIndex, endIndex);
             return false;
          else 
             LOG.info("座位在本次车站区间~未售过票,可选中该座位", dailyTrainSeat.getCarriageSeatIndex(), startIndex, endIndex);
             //  111,   111
             String curSell = sellPart.replace(\'0\', \'1\');
             // 0111,  0111
             curSell = StrUtil.fillBefore(curSell, \'0\', endIndex);
             // 01110, 01110
             curSell = StrUtil.fillAfter(curSell, \'0\', sell.length());
 
             // 当前区间售票信息curSell 01110与库里的已售信息sell 00001按位与,即可得到该座位卖出此票后的售票详情
             // 15(01111), 14(01110 = 01110|00000)
             int newSellInt = NumberUtil.binaryToInt(curSell) | NumberUtil.binaryToInt(sell);
             //  1111,  1110
             String newSell = NumberUtil.getBinaryStr(newSellInt);
             // 01111, 01110
             newSell = StrUtil.fillBefore(newSell, \'0\', sell.length());
             LOG.info("座位被选中,原售票信息:,车站区间:~,即:,最终售票信息:"
                     , dailyTrainSeat.getCarriageSeatIndex(), sell, startIndex, endIndex, curSell, newSell);
             dailyTrainSeat.setSell(newSell);
             return true;
 
         
     
 
     private static void reduceTickets(ConfirmOrderDoReq req, DailyTrainTicket dailyTrainTicket) 
         for (ConfirmOrderTicketReq ticketReq : req.getTickets()) 
             String seatTypeCode = ticketReq.getSeatTypeCode();
             SeatTypeEnum seatTypeEnum = EnumUtil.getBy(SeatTypeEnum::getCode, seatTypeCode);
             switch (seatTypeEnum) 
                 case YDZ -> 
                     int countLeft = dailyTrainTicket.getYdz() - 1;
                     if (countLeft < 0) 
                         throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_COUNT_ERROR);
                     
                     dailyTrainTicket.setYdz(countLeft);
                 
                 case EDZ -> 
                     int countLeft = dailyTrainTicket.getEdz() - 1;
                     if (countLeft < 0) 
                         throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_COUNT_ERROR);
                     
                     dailyTrainTicket.setEdz(countLeft);
                 
                 case RW -> 
                     int countLeft = dailyTrainTicket.getRw() - 1;
                     if (countLeft < 0) 
                         throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_COUNT_ERROR);
                     
                     dailyTrainTicket.setRw(countLeft);
                 
                 case YW -> 
                     int countLeft = dailyTrainTicket.getYw() - 1;
                     if (countLeft < 0) 
                         throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_COUNT_ERROR);
                     
                     dailyTrainTicket.setYw(countLeft);
                 
             
         
     
 
ConfirmOrderService.java

 

Smobiler实现录音和录音播放调用通讯录功能(开发日志九)

一、录音和播放
技术分享

record000~2.jpg (190.05 KB, 下载次数: 3)

下载附件

2015-12-23 17:41 上传



详细步骤:

1,  添加VoiceRecorderButton控件↓
技术分享

201.png (2.44 KB, 下载次数: 11)

下载附件

2015-10-12 16:55 上传



2,  选择VoiceRecorderButton为对象→事件→双击RecorderAudio↓
技术分享

202.png (2.24 KB, 下载次数: 11)

下载附件

2015-10-12 16:55 上传



3,  写代码↓
技术分享

203.png (3.66 KB, 下载次数: 11)

下载附件

2015-10-12 16:55 上传



4,  插入ImageButton,作为播放录音的按钮↓
技术分享

204.png (760 Bytes, 下载次数: 11)

下载附件

2015-10-12 16:55 上传

技术分享

205.png (1.27 KB, 下载次数: 11)

下载附件

2015-10-12 16:55 上传



可将TEXT改为“play” (也可不改)↓
技术分享

206.png (858 Bytes, 下载次数: 11)

下载附件

2015-10-12 16:56 上传

技术分享

207.png (745 Bytes, 下载次数: 11)

下载附件

2015-10-12 16:56 上传



将Image ID改为image文件夹下的同名文件作为按钮图片↓
技术分享

208.png (67.53 KB, 下载次数: 11)

下载附件

2015-10-12 16:56 上传


技术分享

209.png (907 Bytes, 下载次数: 11)

下载附件

2015-10-12 16:56 上传



5,  选中ImageButton为对象,选择事件→双击Click↓
技术分享

210.png (3.7 KB, 下载次数: 11)

下载附件

2015-10-12 16:56 上传



6,  写代码↓
技术分享

211.png (3.77 KB, 下载次数: 11)

下载附件

2015-10-12 16:56 上传






二、调用手机通讯录里的联系人
技术分享

phonenumber000~2.jpg (245.5 KB, 下载次数: 3)

下载附件

2015-12-23 17:41 上传



详细步骤:

1,  添加PhoneButton控件↓
技术分享

301.png (1.22 KB, 下载次数: 11)

下载附件

2015-10-12 16:59 上传



2,  不用写代码




























以上是关于九车票预定功能开发的主要内容,如果未能解决你的问题,请参考以下文章

Smobiler实现录音和录音播放调用通讯录功能(开发日志九)

基于Springboot+SSM框架旅游系统项目开发与设计(附源码资料)-毕业设计

实验七实验八实验九

买火车票

UML五种视图九种图+包图

练习九:time.sleep方法