仿12306项目代码解析--乘车人购票模块

本文参考自
Springboot3+微服务实战12306高性能售票系统 - 慕课网 (imooc.com)

本文是对仿12306项目进行的代码分析,主要集中在购票操作这一个最难的模块

1. controller 层

首先前端的请求会进入ConfirmOrderController类中的doConfirm方法。

1.1 ConfirmOrderController类

doConfirm方法
@SentinelResource(value = "confirmOrderDo", blockHandler = "doConfirmBlock")
@PostMapping("/do")
public CommonResp<Object> doConfirm(@Valid @RequestBody ConfirmOrderDoReq req) {
   if (!env.equals("dev")) {
       // 图形验证码校验
       .....
   }
	Long id = beforeConfirmOrderService.beforeDoConfirm(req);
	return new CommonResp<>(String.valueOf(id));
    }

我们来看一下传入doConfirm方法的请求参数req的结构

会员id	private Long memberId;
日期	private Date date;
车次编号 private String trainCode;
出发站	private String start;
到达站 	private String end;
余票ID	private Long dailyTrainTicketId;
车票	private List<ConfirmOrderTicketReq> tickets;
验证码	private String imageCode;
图片验证码token	private String imageCodeToken;
日志跟踪号	private String logId;
加入排队人数,用于体验排队功能	private int lineNumber;

2. service层

2.1 BeforeConfirmOrderService类

beforeDoConfirm方法
public Long beforeDoConfirm(ConfirmOrderDoReq req)
Long id = null;
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(req.getMemberId());
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);

看一下ConfirmOrder 这个类的参数

private Long id;
private Long memberId;
private Date date;
private String trainCode;
private String start;
private String end;
private Long dailyTrainTicketId;
private String status;
private Date createTime;
private Date updateTime;
private String tickets;

接下来,发送MQ排队购票

ConfirmOrderMQDto confirmOrderMQDto = new ConfirmOrderMQDto();
confirmOrderMQDto.setDate(req.getDate());
confirmOrderMQDto.setTrainCode(req.getTrainCode());
confirmOrderMQDto.setLogId(MDC.get("LOG_ID"));
String reqJson = JSON.toJSONString(confirmOrderMQDto);
LOG.info("排队购票,发送mq开始,消息:{}", reqJson);
rocketMQTemplate.convertAndSend(RocketMQTopicEnum.CONFIRM_ORDER.getCode(), reqJson);
LOG.info("排队购票,发送mq结束");
id = confirmOrder.getId();

return id;

看一下ConfirmOrderMQDto这个类的参数

日志流程号,用于同转异时,用同一个流水号	private String logId;
日期	private Date date;
车次编号	private String trainCode;

2.2 ConfirmOrderConsumer 类

onMessage 方法

接收消息队列的消息,调用doConfirm方法进行具体的购票逻辑

@Override
 public void onMessage(MessageExt messageExt) {
     byte[] body = messageExt.getBody();
     ConfirmOrderMQDto dto = JSON.parseObject(new String(body), ConfirmOrderMQDto.class);
     MDC.put("LOG_ID", dto.getLogId());
     LOG.info("ROCKETMQ收到消息:{}", new String(body));
     confirmOrderService.doConfirm(dto);
 }

2.3 ConfirmOrderService 类

doConfirm 方法
@Async
@SentinelResource(value = "doConfirm", blockHandler = "doConfirmBlock")
public void doConfirm(ConfirmOrderMQDto dto)

将日志 ID 放入日志上下文中,便于跟踪日志。
代码使用 Redis 的 setIfAbsent(相当于 Redis 的 SETNX 命令)尝试获取分布式锁,防止多个线程同时处理同一个订单。
构建分布式锁的键值,锁定的维度是日期和列车代码。
尝试获取锁,如果获取成功,则进入锁定状态,否则返回。

MDC.put("LOG_ID", dto.getLogId());
LOG.info("异步出票开始:{}", dto);

String lockKey = RedisKeyPreEnum.CONFIRM_ORDER + "-" + DateUtil.formatDate(dto.getDate()) + "-" + dto.getTrainCode();
// setIfAbsent就是对应redis的setnx
Boolean setIfAbsent = redisTemplate.opsForValue().setIfAbsent(lockKey, lockKey, 10, TimeUnit.SECONDS);
if (Boolean.TRUE.equals(setIfAbsent)) {
    LOG.info("恭喜,抢到锁了!lockKey:{}", lockKey);
} else {
    // 只是没抢到锁,并不知道票抢完了没,所以提示稍候再试
    // LOG.info("很遗憾,没抢到锁!lockKey:{}", lockKey);
    // throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_LOCK_FAIL);

    LOG.info("没抢到锁,有其它消费线程正在出票,不做任何处理");
    return;
}

创建一个 ConfirmOrderExample 对象,用于构建查询条件
如果查询到的订单列表为空,则输出日志并退出循环。
如果不为空,则输出日志并继续处理查询到的订单。
使用 forEach 遍历订单列表,对每个订单调用 sell(confirmOrder) 方法进行售票处理
在 finally 块中记录日志表示购票流程结束,并删除 Redis 中的锁(lockKey),释放锁资源

try {
	while (true) {
                // 取确认订单表的记录,同日期车次,状态是I,分页处理,每次取N条
                ConfirmOrderExample confirmOrderExample = new ConfirmOrderExample();
                confirmOrderExample.setOrderByClause("id asc");
                ConfirmOrderExample.Criteria criteria = confirmOrderExample.createCriteria();
                criteria.andDateEqualTo(dto.getDate())
                        .andTrainCodeEqualTo(dto.getTrainCode())
                        .andStatusEqualTo(ConfirmOrderStatusEnum.INIT.getCode());
                PageHelper.startPage(1, 5);
                List<ConfirmOrder> list = confirmOrderMapper.selectByExampleWithBLOBs(confirmOrderExample);

                if (CollUtil.isEmpty(list)) {
                    LOG.info("没有需要处理的订单,结束循环");
                    break;
                } else {
                    LOG.info("本次处理{}条订单", list.size());
                }

                // 一条一条的卖
                list.forEach(confirmOrder -> {
                    try {
                        sell(confirmOrder);
                    } catch (BusinessException e) {
                        if (e.getE().equals(BusinessExceptionEnum.CONFIRM_ORDER_TICKET_COUNT_ERROR)) {
                            LOG.info("本订单余票不足,继续售卖下一个订单");
                            confirmOrder.setStatus(ConfirmOrderStatusEnum.EMPTY.getCode());
                            updateStatus(confirmOrder);
                        } else {
                            throw e;
                        }
                    }
                });
            }
        } finally {
            // try finally不能包含加锁的那段代码,否则加锁失败会走到finally里,从而释放别的线程的锁
            LOG.info("购票流程结束,释放锁!lockKey:{}", lockKey);
            redisTemplate.delete(lockKey);
            // LOG.info("购票流程结束,释放锁!");
            // if (null != lock && lock.isHeldByCurrentThread()) {
            //     lock.unlock();
            // }
        }

    }
sell 方法

首先来看一下sell方法的传入参数 ConfirmOrder,详见 beforeDoConfirm 方法

private void sell(ConfirmOrder confirmOrder)

构建 ConfirmOrderDoReq 对象,将 confirmOrder 中的相关信息复制到请求对象中

 ConfirmOrderDoReq req = new ConfirmOrderDoReq();
 req.setMemberId(confirmOrder.getMemberId());
 req.setDate(confirmOrder.getDate());
 req.setTrainCode(confirmOrder.getTrainCode());
 req.setStart(confirmOrder.getStart());
 req.setEnd(confirmOrder.getEnd());
 req.setDailyTrainTicketId(confirmOrder.getDailyTrainTicketId());
 req.setTickets(JSON.parseArray(confirmOrder.getTickets(), ConfirmOrderTicketReq.class));
 req.setImageCode("");
 req.setImageCodeToken("");
 req.setLogId("");

将订单状态更新为 PENDING(处理中),以避免该订单被重复处理

LOG.info("将确认订单更新成处理中,避免重复处理,confirm_order.id: {}", confirmOrder.getId());
confirmOrder.setStatus(ConfirmOrderStatusEnum.PENDING.getCode());
updateStatus(confirmOrder);

根据订单的日期、车次、起始站和终点站,从数据库中查询相应的余票记录

Date date = req.getDate();
String trainCode = req.getTrainCode();
String start = req.getStart();
String end = req.getEnd();
List<ConfirmOrderTicketReq> tickets = req.getTickets();
DailyTrainTicket dailyTrainTicket = dailyTrainTicketService.selectByUnique(date, trainCode, start, end);
LOG.info("查出余票记录:{}", dailyTrainTicket);

DailyTrainTicket 的结构是

private Long id;
private Date date;
private String trainCode;
private String start;
private String startPinyin;
private Date startTime;
private Integer startIndex;
private String end;
private String endPinyin;
private Date endTime;
private Integer endIndex;
private Integer ydz;
private BigDecimal ydzPrice;
private Integer edz;
private BigDecimal edzPrice;
private Integer rw;
private BigDecimal rwPrice;
private Integer yw;
private BigDecimal ywPrice;
private Date createTime;
private Date updateTime;

预先扣减余票数量,并检查余票是否足够

reduceTickets(req, dailyTrainTicket);

创建一个空的 finalSeatList 列表,用于存储最终选中的座位信息
获取订单中第一张票的座位信息。如果第一张票中包含座位信息(即用户指定了座位),进入选座逻辑。

List<DailyTrainSeat> finalSeatList = new ArrayList<>();
// 计算相对第一个座位的偏移值
// 比如选择的是C1,D2,则偏移值是:[0,5]
// 比如选择的是A1,B1,C1,则偏移值是:[0,1,2]
ConfirmOrderTicketReq ticketReq0 = tickets.get(0);

看看ConfirmOrderTicketReq的结构

乘客ID	private Long passengerId;
乘客票种	private String passengerType;
乘客名称	private String passengerName;
乘客身份证	private String passengerIdCard;
座位类型code	private String seatTypeCode;
选座,可空,值示例:A1	private String seat;

根据座位类型(如经济舱、一等座等),获取对应的座位列信息
生成两排参照座位列表,作为选座的参考。比如,对于A、C、D、F列,参照列表可能是 {A, C, D, F, A, C, D, F}

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);

计算每个选定座位在参照列表中的位置(即绝对偏移值)。比如,C1在列表中第2个位置,D2在第5个位置。
计算每个选定座位相对于第一个座位的偏移值。例如,如果选择了C1和D2,则相对偏移值为 [0, 5]。

	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。

		getSeat(finalSeatList,
		                    date,
		                    trainCode,
		                    ticketReq0.getSeatTypeCode(),
		                    ticketReq0.getSeat().split("")[0], // 从A1得到A
		                    offsetList,
		                    dailyTrainTicket.getStartIndex(),
		                    dailyTrainTicket.getEndIndex()
		            );

如果用户没有指定座位,则为每张票调用 getSeat 方法进行自动选座。

} else {
            LOG.info("本次购票没有选座");
            for (ConfirmOrderTicketReq ticketReq : tickets) {
                getSeat(finalSeatList,
                        date,
                        trainCode,
                        ticketReq.getSeatTypeCode(),
                        null,
                        null,
                        dailyTrainTicket.getStartIndex(),
                        dailyTrainTicket.getEndIndex()
                );
            }
        }

        LOG.info("最终选座:{}", finalSeatList);

在选座成功后,执行后续的事务处理,包括:
- 更新座位表中的售票信息。
- 更新余票详情表中的余票信息。
- 为会员增加购票记录。
- 更新确认订单状态为成功。

try {
        afterConfirmOrderService.afterDoConfirm(dailyTrainTicket, finalSeatList, tickets, confirmOrder);
        } catch (Exception e) {
            LOG.error("保存购票信息失败", e);
            throw new BusinessException(BusinessExceptionEnum.CONFIRM_ORDER_EXCEPTION);
        }
getSeat 方法
private void getSeat(List<DailyTrainSeat> finalSeatList, Date date, String trainCode, String seatType, String column, List<Integer> offsetList, Integer startIndex, Integer endIndex)

/**
     * 挑座位,如果有选座,则一次性挑完,如果无选座,则一个一个挑
     * @param date
     * @param trainCode
     * @param seatType
     * @param column
     * @param offsetList
     */

从数据库中查找符合指定日期、车次和座位类型的所有车厢,并保存到 carriageList 中

List<DailyTrainSeat> getSeatList = new ArrayList<>();
List<DailyTrainCarriage> carriageList = dailyTrainCarriageService.selectBySeatType(date, trainCode, seatType);
LOG.info("共查出{}个符合条件的车厢", carriageList.size());

遍历每一个符合条件的车厢,并从数据库中获取该车厢的所有座位信息,存储在 seatList 中。

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;
		}

如果有指定列号(如A、B、C等),则检查当前座位的列号是否匹配。如果不匹配,则继续判断下一个座位。

		if (StrUtil.isBlank(column)) {
		    LOG.info("无选座");
		} else {
		    if (!column.equals(col)) {
		        LOG.info("座位{}列值不对,继续判断下一个座位,当前列值:{},目标列值:{}", seatIndex, col, column);
		        continue;
		    }
		}

调用 calSell 方法,判断当前座位在指定区间内是否可以选。如果可以选中,则将其加入 getSeatList。

		boolean isChoose = calSell(dailyTrainSeat, startIndex, endIndex);
		if (isChoose) {
		    LOG.info("选中座位");
		    getSeatList.add(dailyTrainSeat);
		} else {
		    continue;
		}

如果有偏移值(即用户选了多个连续座位),则逐个验证偏移位置的座位是否可选。如果有任何一个偏移位置不可选,则放弃当前车厢中的所有已选座位,继续选择下一个车厢。

		if (CollUtil.isNotEmpty(offsetList)) {
		    LOG.info("有偏移值:{},校验偏移的座位是否可选", offsetList);
		    for (int j = 1; j < offsetList.size(); j++) {
		        Integer offset = offsetList.get(j);
		        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;
		        }
		    }
		}

如果所有偏移值对应的座位都成功选定,则将这些座位添加到最终的选座列表 finalSeatList 中,并结束当前车厢的遍历,直接返回。

		if (!isGetAllOffsetSeat) {
		    getSeatList = new ArrayList<>();
		    continue;
		}
		
		finalSeatList.addAll(getSeatList);
		return;
calSell 方法
private boolean calSell(DailyTrainSeat dailyTrainSeat, Integer startIndex, Integer endIndex)

/**
     * 计算某座位在区间内是否可卖
     * 例:sell=10001,本次购买区间站1~4,则区间已售000
     * 全部是0,表示这个区间可买;只要有1,就表示区间内已售过票
     *
     * 选中后,要计算购票后的sell,比如原来是10001,本次购买区间站1~4
     * 方案:构造本次购票造成的售卖信息01110,和原sell 10001按位或,最终得到11111
     */

sell 是一个字符串,表示座位的售票信息。每个字符(0或1)代表座位在某个车站区间的售出状态,0表示未售出,1表示已售出。

String sell = dailyTrainSeat.getSell();

sellPart 提取了从 startIndex 到 endIndex 之间的售票信息,这部分表示在用户当前选择的起始站到终点站之间,这个座位是否已经售出。

String sellPart = sell.substring(startIndex, endIndex);

如果 sellPart 的数值大于0,说明该区间内的座位已经被售出,返回 false 表示座位不可选。

if (Integer.parseInt(sellPart) > 0) {
    LOG.info("座位{}在本次车站区间{}~{}已售过票,不可选中该座位", dailyTrainSeat.getCarriageSeatIndex(), startIndex, endIndex);
    return false;
}

如果 sellPart 的数值为0,说明该区间内的座位尚未售出。
将 sellPart 中的所有 ‘0’ 替换为 ‘1’,表示在这个区间内座位被选中售出。
使用 StrUtil.fillBefore 和 StrUtil.fillAfter 对 curSell 进行填充,以确保长度与原始 sell 字符串相同。

LOG.info("座位{}在本次车站区间{}~{}未售过票,可选中该座位", dailyTrainSeat.getCarriageSeatIndex(), startIndex, endIndex);
String curSell = sellPart.replace('0', '1');
curSell = StrUtil.fillBefore(curSell, '0', endIndex);
curSell = StrUtil.fillAfter(curSell, '0', sell.length());

使用按位或(|)操作将当前区间的售票信息(curSell)与之前的售票信息(sell)合并,得到新的售票状态 newSell。
newSell 被设置为座位的最新售票信息。
返回 true 表示座位在当前区间内可选,并且售票信息已更新。

int newSellInt = NumberUtil.binaryToInt(curSell) | NumberUtil.binaryToInt(sell);
String newSell = NumberUtil.getBinaryStr(newSellInt);
newSell = StrUtil.fillBefore(newSell, '0', sell.length());
LOG.info("座位{}被选中,原售票信息:{},车站区间:{}~{},即:{},最终售票信息:{}"
        , dailyTrainSeat.getCarriageSeatIndex(), sell, startIndex, endIndex, curSell, newSell);
dailyTrainSeat.setSell(newSell);
return true;

2.4 AfterConfirmOrderService类

afterDoConfirm 方法
public void afterDoConfirm(DailyTrainTicket dailyTrainTicket, List<DailyTrainSeat> finalSeatList, List<ConfirmOrderTicketReq> tickets, ConfirmOrder confirmOrder) throws Exception
/**
     * 选中座位后事务处理:
     *  座位表修改售卖情况sell;
     *  余票详情表修改余票;
     *  为会员增加购票记录
     *  更新确认订单为成功
     */

遍历最终选定的座位列表 finalSeatList,更新每个座位的售票信息 sell 以及更新时间。

for (int j = 0; j < finalSeatList.size(); j++) {
    DailyTrainSeat dailyTrainSeat = finalSeatList.get(j);
    DailyTrainSeat seatForUpdate = new DailyTrainSeat();
    seatForUpdate.setId(dailyTrainSeat.getId());
    seatForUpdate.setSell(dailyTrainSeat.getSell());
    seatForUpdate.setUpdateTime(new Date());
    dailyTrainSeatMapper.updateByPrimaryKeySelective(seatForUpdate);
}

计算购买座位后的影响区间。这里的逻辑是确定座位售票信息中,哪些区间的售票信息受到了本次购买的影响。
计算这个站卖出去后,影响了哪些站的余票库存

  // 影响的库存:本次选座之前没卖过票的,和本次购买的区间有交集的区间
  // 假设10个站,本次买4~7站
  // 原售:001000001
  // 购买:000011100
  // 新售:001011101
  // 影响:XXX11111X

然后调用 dailyTrainTicketMapperCust.updateCountBySell 方法,更新受影响区间的余票信息。

	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;
	    }
	}
	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("影响出发站区间:" + minStartIndex + "-" + maxStartIndex);
	LOG.info("影响到达站区间:" + minEndIndex + "-" + maxEndIndex);
	
	dailyTrainTicketMapperCust.updateCountBySell(
	    dailyTrainSeat.getDate(),
	    dailyTrainSeat.getTrainCode(),
	    dailyTrainSeat.getSeatType(),
	    minStartIndex,
	    maxStartIndex,
	    minEndIndex,
	    maxEndIndex);

创建 MemberTicketReq 对象,并填充相关购票信息。
调用会员服务接口 memberFeign.save(memberTicketReq),为会员增加一张车票。

	MemberTicketReq memberTicketReq = new MemberTicketReq();
	memberTicketReq.setMemberId(confirmOrder.getMemberId());
	memberTicketReq.setPassengerId(tickets.get(j).getPassengerId());
	memberTicketReq.setPassengerName(tickets.get(j).getPassengerName());
	memberTicketReq.setTrainDate(dailyTrainTicket.getDate());
	memberTicketReq.setTrainCode(dailyTrainTicket.getTrainCode());
	memberTicketReq.setCarriageIndex(dailyTrainSeat.getCarriageIndex());
	memberTicketReq.setSeatRow(dailyTrainSeat.getRow());
	memberTicketReq.setSeatCol(dailyTrainSeat.getCol());
	memberTicketReq.setStartStation(dailyTrainTicket.getStart());
	memberTicketReq.setStartTime(dailyTrainTicket.getStartTime());
	memberTicketReq.setEndStation(dailyTrainTicket.getEnd());
	memberTicketReq.setEndTime(dailyTrainTicket.getEndTime());
	memberTicketReq.setSeatType(dailyTrainSeat.getSeatType());
	CommonResp<Object> commonResp = memberFeign.save(memberTicketReq);
	LOG.info("调用member接口,返回:{}", commonResp);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值