九车票预定功能开发
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);
一定要加事务@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就可以调用该方法。
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);
订票页面
可以查询所有乘客
/** * 查询我的所有乘客 */ 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);
@GetMapping("/query-mine") public CommonResp<List<PassengerQueryResp>> queryMine() List<PassengerQueryResp> list = passengerService.queryMine(); return new CommonResp<>(list);
分解选座购票功能的前后端逻辑
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();
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;
校验订单
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);
Smobiler实现录音和录音播放调用通讯录功能(开发日志九)
以上是关于九车票预定功能开发的主要内容,如果未能解决你的问题,请参考以下文章
Smobiler实现录音和录音播放调用通讯录功能(开发日志九)