基本預定車票功能的開發
? ? ? ? 對于乘客購票來說,需要有每一個車次的余票信息,展示給乘客,供乘客選擇,因此首個功能是余票的初始化,之后是余票查詢,這兩個都是控臺端。對于會員端的購票,需要有余票查詢以及乘客的選擇,不僅僅支持給自己買票,還可以給其他人買票,而且還可以選擇座位類型,是一等座還是二等座,可以選擇座位,最后是下單購票。
余票信息表
對于購票表來說,最為重要的字段是售賣字段,對于這個字段來說,將經過的車站用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,這樣子就可以得到車次所有的出發站和到達站的站站組合。對于余票信息來說,可以查詢座位數這張表,查詢車次以及座位類型就可以得到余票的信息。
? ? ? ? 落實到具體的代碼上,首先是刪除要生成某日車次的余票信息,使其支持重復生成,之后是查詢這個車次的所有車站信息,根據車站的信息進行嵌套循環,先生成一個余票對象,然后根據車站進行數據的填充,最后將實體保存到數據庫
@Transactionalpublic 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);}
現在發現,對于座位類型的個數和票價還是未知,因此接下來解決這個方面的信息。求某個車次的某類型的票數,需要知道的是座位數。因此應該在每日座位表中查某個日期,某個車次,某個座位類型的票數。由于在每日座位表已經有了這些信息,因此只需要填寫好信息去查詢就可以得到初始化的余票信息,對于無票的時候,如果設置為0,用戶可以以為賣光了,其實想要表達的是改車次沒有這種類型的票,因此可以設置為-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;}
接下來是計算票價,票價和火車類型以及座位類型有關:票價=里程之和*座位類型的票價*車次類型系數。計算里程時從初始站加上每一次到達站的距離,即
sumKM = sumKM.add(trainStationEnd.getKm());
最終的總計算公式如下:
// 票價=里程之和*座位類型的票價*車次類型系數 String trainType = dailyTrain.getType(); // 計算票價系數:TrainTypeEnum.priceRate BigDecimal priceRate = EnumUtil.getFieldBy(TrainTypeEnum::getPriceRate,TrainTypeEnum::getCode,trainType);
余票查詢
對于余票的查詢,設置查詢條件為日期,火車車次,起始站和終點站。對于會員端的車票界面來說,它不支持增加和修改和刪除,只是支持查詢,因此后端對于會員端增加車票查詢的接口。
@RestController
@RequestMapping("/daily-train-ticket")
public class DailyTrainTicketController {@Resourceprivate DailyTrainTicketService dailyTrainTicketService;@GetMapping("/query-list")public CommonResp<PageResp<DailyTrainTicketQueryResp>> queryList(@Valid DailyTrainTicketQueryReq req) {PageResp<DailyTrainTicketQueryResp> list = dailyTrainTicketService.queryList(req);return new CommonResp<>(list);}
}
由于進行查詢時需要選擇車站以及火車車次,而且為了進行分離,之前在控制臺界面統一增加了admin,為了使會員端實現相同的查詢,因此需要重新寫controller層的車次和車票查詢,它和控臺的功能相同,只是url不同。至此,會員端和控制臺界面開發完畢。
選座功能
????????首先是預定的按鈕,在點擊按鈕之后需要跳轉到order界面,因此需要在router增加一個路由,那用什么取傳遞參數呢?session就是一個很好的選擇,在order界面打開時執行setup(),定義一個參數dailyTrainTicket從緩存中獲取dailyTrainTicket,如果沒有,給一個空對象,避免空指針異常,之后進行返回,在html部分進行顯示出來。在點擊時,利用自定義的toOrder,首先是把record放入Session中,之后進行路由跳轉。現在是每次查詢之后返回,選擇框不會保存之前選擇的值,為了增強用戶體驗,可以為余票查詢頁面緩存查詢參數,方便用戶使用,將session key寫成常量,方便統一維護,可以避免多個功能使用同一個key。當用戶選擇之后,將用戶的選擇緩存到一個session key中,然后在公共區添加不同的session key,避免混用。之后修改onMounted(),它表示頁面打開,先進性緩存的獲取,之后不為空是進行查詢。
// order.vue<template><div>{{dailyTrainTicket}}</div>
</template><script>import {defineComponent} from 'vue';export default defineComponent({name: "order-view",setup() {const dailyTrainTicket = SessionStorage.get("dailyTrainTicket") || {};console.log("下單的車次信息", dailyTrainTicket);return {dailyTrainTicket,};},
});
</script>// ticket.vue// 保存查詢參數
SessionStorage.set(SESSION_TICKET_PARAMS, params.value);onMounted(() => {params.value = SessionStorage.get(SESSION_TICKET_PARAMS) || {};if(Tool.isNotEmpty(params.value)) {handleQuery({page: 1,pageSize: pagination.value.pageSize});}});
最后的是在order界面的展示優化,得到從出發站到終點站以及座位類型和價格以及票數的展示。
????????接下來是真正的選擇座位功能,首先是后端去查找我的所有乘客接口,在order界面調用接口,在搜索的service層,需要獲取當前登錄者的id,然后根據登錄者的id去庫中搜索出為那些乘客購票,如果乘客太多,可以增加一個功能,當乘客數量大于50時就不拿增加乘客了,controller直接增加接口查詢即可。對于前端,增加一個響應式變量passenger,增加一個handleQueryPassen-ger,方法,這個方法調用后端接口,得到后給響應式變量賦值,即初始化時直接查詢。
? ? ? ? 對于選擇乘客,在js部分增加了const passengerOptions = ref([]); const passengerChecks = ref([]);表示選項和選擇,由于乘客帶有的屬性過多,因此可以在handleQueryPassenger方法中增加lable和value,分別表示看到的值以及實際操作的值。勾選完乘客后,需要為乘客構造購票數據。由于一次不僅僅勾選一個乘客,因此可以引入watch,實時監控勾選的變化,用來顯示購票的界面。
// 購票列表,用于界面展示,并傳遞到后端接口,用來描述:哪個乘客購買什么座位的票const tickets = ref([]);// 勾選或去掉某個乘客時,在購票列表中加上或去掉一張表watch(() => passengerChecks.value, (newVal, oldVal)=>{console.log("勾選乘客發生變化", newVal, oldVal)// 每次有變化時,把購票列表清空,重新構造列表tickets.value = [];passengerChecks.value.forEach((item) => tickets.value.push({passengerId: item.id,passengerType: item.type,seatTypeCode: seatTypes[0].code,passengerName: item.name,passengerIdCard: item.idCard}))}, {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個人來買票,這肯定是不行的,因此可以在前端增加一層校驗,來檢驗余票是否足夠,可以減小后端的壓力。這步是預扣減庫存,只是用來校驗,所有拷貝出臨時變量來扣減,即點擊提交是預扣減庫存,實際提交才是真正扣減庫存
// 校驗余票是否充足,購票列表中的每個座位類型,都去車次座位余票信息中,看余票是否充足// 前端校驗不一定準,但前端校驗可以減輕后端很多壓力// 注意:這段只是校驗,必須copy出seatTypesTemp變量來扣減,用原始的seatTypes去扣減,會影響真實的庫存let seatTypesTemp = Tool.copy(seatTypes);for (let i = 0; i < tickets.value.length; i++) {let ticket = tickets.value[i];for (let j = 0; j < seatTypesTemp.length; j++) {let seatType = seatTypesTemp[j];// 同類型座位余票-1,這里扣減的是臨時copy出來的庫存,不是真正的庫存,只是為了校驗if (ticket.seatTypeCode === seatType.code) {seatType.count--;if (seatType.count < 0) {notification.error({description: seatType.desc + '余票不足'});return;}}}}console.log("前端余票校驗通過");
開始選座
響應式變量chooseSeatType首先是0,表示不支持選座,然后根據座位類型選擇對應的列,賦值給SEAT_COL_ARRAY,之后對兩排的座位進行初始化,賦值為false;由于規定不能同時選擇1和2等座,所有開始選座之前先進行去重,如果多于1中返回選座不成功,否則的話根據類型進行選座。最后進行界面優化,增加選座的按鈕,這里注意,如果是選擇一個人進行購票,這里采用只顯示一排按鈕。回到選擇的函數中來,增加一個約定,余票小于20張時,不允許選座。最后提交時,計算出每個用戶的座位選擇,代碼如下:
const handleOk = () => {console.log("選好的座位:", chooseSeatObj.value);// 設置每張票的座位// 先清空購票列表的座位,有可能之前選了并設置座位了,但選座數不對被攔截了,又重新選一遍for (let i = 0; i < tickets.value.length; i++) {tickets.value[i].seat = null;}let i = -1;// 要么不選座位,要么所選座位應該等于購票數,即i === (tickets.value.length - 1)for (let key in chooseSeatObj.value) {if (chooseSeatObj.value[key]) {i++;if (i > tickets.value.length - 1) {notification.error({description: '所選座位數大于購票數'});return;}// 實際的賦值tickets.value[i].seat = key;}}if (i > -1 && i < (tickets.value.length - 1)) {notification.error({description: '所選座位數小于購票數'});return;}console.log("最終購票:", tickets.value);}
后端選座接口
????????前面的余票信息表是根據購票表來進行得到的,但是,購買完成票之后還需要進行落表,無論是否成功。需要哪一個人購的票,表示當前訪問這個接口的是哪一個會員,還需要日期,車次,出發站和到達站以及這些基礎信息,這些信息就可以唯一定位到余票信息這張表,就可以判斷它的一等座,二等座等的余票信息了。余票id字段也可以和上面的余票表進行關聯。由于訂單狀態不一定成功,因此需要訂單的狀態,車票可以做成json,也可以做成子表。
? ? ? ? 開發接口的話,那么傳入的參數是什么,可以參考設計的表,接口進來之后,就應該數據落表,那么表中的數據如何來?其中member_id可以從線程本地變量獲取,日期,車次,出發站,到達站和車票都可以從前端獲取,其中車票可以把json映射成Java類,這樣子操作更加的方便。因為需要車票進行選座,用json操作不太方便。訂單狀態是由程序根據不同的步驟進行落庫,因此不用管。首先要做的是添加車票類(ConfrimOrderTicketReq),即購買車票的信息,接受前端傳來的對象。之后要構造一個訂單類,方便入庫。之后在controller層增加doConfirm接口,最后在service層增加相關保存購票信息的方法。最后前端調用后端接口。
? ? ? ? 重點的話是會員模塊的service層的保存訂單方法doConfirm如何實現。
- 保存確認訂單表,狀態初始,對于id,直接使用雪花算法,時間使用當前的時間,memberid使用登錄人的id,像traincode,date,start和end一次ticket都是從前端獲取的,之前保存到了ConfirmOrderReq,從這里直接獲取即可,注意,對于ticket來說,需要將json字符串轉化為車票類
- 查出余票記錄,得到真實的庫存。由于唯一鍵是日期,車次,起始和終點站,這樣子構造號條件,進行查詢。
public DailyTrainTicket selectByUnique(Date date, String trainCode,String start,String end) {DailyTrainTicketExample dailyTrainTicketExample = new DailyTrainTicketExample();dailyTrainTicketExample.createCriteria().andDateEqualTo(date).andTrainCodeEqualTo(trainCode).andStartEqualTo(start).andEndEqualTo(end);List<DailyTrainTicket> list = dailyTrainTicketMapper.selectByExample(dailyTrainTicketExample);if(CollUtil.isNotEmpty(list)) {return list.get(0);}else {return null;}}
- 進行票數的預扣減,由于前端的是實時顯示到界面上,因此需要一個變量,而這里只要不更新到數據庫,怎么扣減都可以,因此可以之間操作查出的庫存記錄。一張票一張票的循環進行扣減,由于選擇的可能是不同的座位類型(不同的人),因此不能按照列表進行扣票,應該按照不同的座位類型進行扣票。
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);}}}}
- 偏移值的計算,這里的偏移值指的是相對于第一個選座位置的偏移值,這樣子可以一次循環找到所有位置的值(假設一個車廂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。
private boolean calSell(DailyTrainSeat dailyTrainSeat,Integer startIndex,Integer endIndex) {String sell = dailyTrainSeat.getSell();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);// 111String curSell = sellPart.replace('0', '1');// 0111curSell = StrUtil.fillBefore(curSell,'0', endIndex);curSell = StrUtil.fillAfter(curSell,'0',sell.length());// 當前區間售票信息curSell與庫里的已售信息sell按位與,即可得到該座位賣出此票后的售票詳情// 32int newSellInt = NumberUtil.binaryToInt(curSell) | NumberUtil.binaryToInt(sell);// 11111String newSell = NumberUtil.getBinaryStr(newSellInt);newSell = StrUtil.fillBefore(newSell,'0',sell.length());LOG.info("座位{}在本車站區間{}--{}賣出該票后,最終售票詳情:{}",dailyTrainSeat.getCarriageSeatIndex(),startIndex,endIndex,newSell);dailyTrainSeat.setSell(newSell);return true;}
-
優化getSeat方法:首先判斷傳入的column和拿到的col,進行比較,如果無值,表示無選座,有值需要判斷是否可以匹配,不匹配的話跳過。現在選完第一個座位后,接下來根據偏移值選則后面幾人的座位,偏移值可能有多個,從索引1開始(0是以及選完的第一個座位)進行循環,下一個位置是索引+偏移量,然后根據是否超賣判斷是否可選。還要注意,選座是在同一個車廂,根據下一個索引是否大于車廂座位數
-
保存最終的選座結果,但不是更新到數據庫中。此時用一個臨時變量保存選擇的座位,當下一個選座時,應該看這個選座的結果以及最終的選座結果(這個最終結果是之前的結果,如果此次選座成功,那么就更新最終選座),否則就出現挑選同一個座位的情況。例如系統分配座位的時候,先分配座位3,由于同一個人為不同人買多張票,在進行第二張票購買前,沒有更新到數據庫中,導致下一個分配的還是3號座位,這顯然是不合理的,因此需要對每一次選擇的座位進行保存,為了讓下一次正確的挑選座位。對于選座的情況,由于第一次選座成功的情況下,可能之后根據偏移值選擇的其他座位不成功,如果直接保存的最終結果,可能導致結果和符合的不一致,因此也是需要保存到臨時變量中,注意沒有成功選座時清空臨時變量,當兩種情況都成功選座后,才真正的保存到最終選座的變量中。
-
根據售賣信息更新座位售賣情況,就是更新數據庫的售賣信息,例如從000更新到101.就是選好座位后,將座位信息更新到日常座位表中。由于后面還需要涉及到修改余票,為會員增加購票記錄,更新訂單狀態,因此可以把他們稱為選座后的事務處理。可以在最后修改數據庫的時候增加事務,這樣子占用事務的時間較少,否則的話占用大量的數據庫資源。由于本壘方法間的調用,事務不生效,因此增加一個類,專門進行處理。根據最終選票結果來處理。此時需要更新的是id以及售賣票的信息還要更新時間,不必要更新表中所有的字段。
@Transactionalpublic void afterDoConfirm(List<DailyTrainSeat> finalSeatList) {for (DailyTrainSeat dailyTrainSeat: finalSeatList){DailyTrainSeat seatForUpdate = new DailyTrainSeat();seatForUpdate.setId(dailyTrainSeat.getId());seatForUpdate.setSell(dailyTrainSeat.getSell());seatForUpdate.setUpdateTime(new Date());dailyTrainSeatMapper.updateByPrimaryKeySelective(seatForUpdate);}}
-
扣減庫存(很重要):售賣一張票影響的是多個區間的庫存,就是本次選座之前沒賣過票的,并且本次購買的區間有交集的區間,怎么理解,如下圖:
由于座位區間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]這個區間結束都會影響庫存。
Integer startIndex = dailyTrainTicket.getStartIndex();
Integer endIndex = dailyTrainTicket.getEndIndex();
char[] chars = seatForUpdate.getSell().toCharArray();
Integer maxStartIndex = endIndex - 1;
Integer minEndIndex = startIndex + 1;
Integer minStartIndex = 0;
for (int i = startIndex - 1; i >= 0; i--) {char aChar = chars[i];if(aChar == '1') {minStartIndex = i + 1;break;}
}
LOG.info("影響出發站區間:"+minStartIndex+"-"+maxStartIndex);
Integer maxEndIndex = seatForUpdate.getSell().length();
for (int i = endIndex; i < seatForUpdate.getSell().length(); i++) {char aChar = chars[i];if(aChar == '1') {maxEndIndex = i;break;}
}
LOG.info("影響結束站區間:"+minEndIndex+"-"+maxEndIndex);
dailyTrainTicketMapperCust.updateCountBySell(dailyTrainSeat.getDate(),dailyTrainSeat.getTrainCode(),dailyTrainSeat.getSeatType(),minStartIndex,maxStartIndex,minEndIndex,maxEndIndex);
之后根據這四個值進行庫存的更新。
- 對乘客增加車票表,由于之前的購票表訂單狀態由可能為失敗,而且信息不容易搜索,因此新建一張表,用來保存乘客購買車票成功的信息,由于每個會員經常查自己購買的車票,因此可以把會員id當作索引。由于生成的表在member模塊,購票業務在business模塊,當business模塊購票成功后調用member模塊的接口,把數據傳入,這里使用的了feign。對于會員車票參數來說,在business模塊需要調用它進行車票的構造,number模塊需要調用它進行入庫,因此可以放到common模塊。
- 要啟用feign,需要在調用方buiness增加依賴,之后在啟動類配置路徑,表示哪個包是屬于feign的,之后在相關的路徑增加一個接口,路徑配置是調用的哪個包下的接口。
之后在business模塊就可以調用member的接口,為會員(乘客)增加一張票。之后可以為會員段增加我的車票,方便查看車票。但這里需要根據會員的id查看,只能夠查看自己買的車票。
最后更新訂單狀態為成功,根據id更新?更新時間以及狀態