Java项目实战笔记--基于SpringBoot3.0开发仿12306高并发售票系统--(二)项目实现-第五篇-核心功能车票预定开发及nacos集成

本文参考自

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

本文是仿12306项目实战第(二)章——项目实现 的第五篇,本篇讲解该项目的核心功能——余票查询、车票预定功能的基础版开发,以及讲解项目与Nacos的集成

本章目录

  • 一、核心功能介绍
  • 二、增加余票信息表,生成代码
  • 三、生成车次时初始化余票信息
  • 四、生成车次时初始化各种座位的余票数量
  • 五、为余票信息页面增加查询条件
  • 六、为会员端增余票查询功能
  • 七、增加订票页面并且实现车次信息传递
    • 1.增加预订按钮,点击预订时,跳转到下单页面,并使用sessionStorage传递参数
    • 2.为余票查询页面缓存查询参数,方便用户使用;将session key写成常量,方便统一维护,可以避免多个功能使用同一个key
    • 3.美化车次信息的显示
    • 4.订单页面显示座位信息
  • 八、订票页面勾选乘客并显示购票列表
    • 1.订票页面,查询我的所有的乘客(可以在新增乘客的时候,增加一个校验:超过50个乘客,就不能再新增了)
    • 2.订票页面,显示我的乘客复选框
    • 3.订票页面,为勾选的乘客构造购票数据
    • 4.订票页面,优化购票列表的展示
    • 5.订票页面,勾选乘客后提交,显示购票列表确认框
  • 九、分解选座购票功能的前后端逻辑
  • 十、订票页面增加选座效果
    • 1.勾选乘客后,提交时,校验余票是否足够(前端校验不一定准,但前端校验可以减轻后端很多压力)
    • 2.根据购票列表,计算出是否支持选座
    • 3.根据购票列表,展示选座按钮
    • 4.余票小于20张时,不允许选座
    • 5.确认提交时,计算出最终每个乘客所选的座位
    • 6.chooseSeatObj先清空,再初始化,保证两排座位是有序的
  • 十、增加确认订单表并生成前后端代码
  • 十一、后端增加确认下单购票接口
  • 十二、确认下单接口数据初始化
  • 十三、预扣减库存并判断余票是否足够
  • 十四、计算多个选座之间的偏移值

一、核心功能介绍

在这里插入图片描述

二、增加余票信息表,生成代码

  • business.sql

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

    实际12306用的是商业缓存软件来做的余票信息缓存,本项目用mysql新建临时表来代替缓存演示业务实现

  • 修改generator-config-business.xml,生成持久层、前后端代码

    <table tableName="daily_train_ticket" domainObjectName="DailyTrainTicket"/>
    

    操作同之前

  • 修改admin/package.json,加规则去除ESLint报错

    "rules": {
      "vue/multi-word-component-names": 0,
      "no-undef": 0,
      "vue/no-unused-vars": 0
    }
    
  • 修改路由、侧边栏

    操作同之前

  • 测试

在这里插入图片描述

数据后续由定时任务填充

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

  • 给批量方法都加上@Transactional

    虽然由于事务的传递性,外层有事务,内层方法也会是事务,但是规范点还是都加上事务注解

  • DailyTrainTicketService.java

    逻辑:比如车站1——车站2——车站3,站站组合情况就有 1、2;1、3;2、3 三种,需要分别生成每种组合的余票信息

    这里第一版 先解决站站组合逻辑,具体余票信息后面完善

    package com.neilxu.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.date.DateUtil;
    import cn.hutool.core.util.ObjectUtil;
    import com.github.pagehelper.PageHelper;
    import com.github.pagehelper.PageInfo;
    import com.neilxu.train.business.domain.DailyTrainTicket;
    import com.neilxu.train.business.domain.DailyTrainTicketExample;
    import com.neilxu.train.business.domain.TrainStation;
    import com.neilxu.train.business.mapper.DailyTrainTicketMapper;
    import com.neilxu.train.business.req.DailyTrainTicketQueryReq;
    import com.neilxu.train.business.req.DailyTrainTicketSaveReq;
    import com.neilxu.train.business.resp.DailyTrainTicketQueryResp;
    import com.neilxu.train.common.resp.PageResp;
    import com.neilxu.train.common.util.SnowUtil;
    import jakarta.annotation.Resource;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    import java.math.BigDecimal;
    import java.util.Date;
    import java.util.List;
    
    @Service
    public class DailyTrainTicketService {
    
        private static final Logger LOG = LoggerFactory.getLogger(DailyTrainTicketService.class);
    
        @Resource
        private DailyTrainTicketMapper dailyTrainTicketMapper;
    
        @Resource
        private TrainStationService trainStationService;
    
        public void save(DailyTrainTicketSaveReq req) {
            DateTime now = DateTime.now();
            DailyTrainTicket dailyTrainTicket = BeanUtil.copyProperties(req, DailyTrainTicket.class);
            if (ObjectUtil.isNull(dailyTrainTicket.getId())) {
                dailyTrainTicket.setId(SnowUtil.getSnowflakeNextId());
                dailyTrainTicket.setCreateTime(now);
                dailyTrainTicket.setUpdateTime(now);
                dailyTrainTicketMapper.insert(dailyTrainTicket);
            } else {
                dailyTrainTicket.setUpdateTime(now);
                dailyTrainTicketMapper.updateByPrimaryKey(dailyTrainTicket);
            }
        }
    
        public PageResp<DailyTrainTicketQueryResp> queryList(DailyTrainTicketQueryReq req) {
            DailyTrainTicketExample dailyTrainTicketExample = new DailyTrainTicketExample();
            dailyTrainTicketExample.setOrderByClause("id desc");
            DailyTrainTicketExample.Criteria criteria = dailyTrainTicketExample.createCriteria();
    
            LOG.info("查询页码:{}", req.getPage());
            LOG.info("每页条数:{}", req.getSize());
            PageHelper.startPage(req.getPage(), req.getSize());
            List<DailyTrainTicket> dailyTrainTicketList = dailyTrainTicketMapper.selectByExample(dailyTrainTicketExample);
    
            PageInfo<DailyTrainTicket> pageInfo = new PageInfo<>(dailyTrainTicketList);
            LOG.info("总行数:{}", pageInfo.getTotal());
            LOG.info("总页数:{}", pageInfo.getPages());
    
            List<DailyTrainTicketQueryResp> list = BeanUtil.copyToList(dailyTrainTicketList, DailyTrainTicketQueryResp.class);
    
            PageResp<DailyTrainTicketQueryResp> pageResp = new PageResp<>();
            pageResp.setTotal(pageInfo.getTotal());
            pageResp.setList(list);
            return pageResp;
        }
    
        public void delete(Long id) {
            dailyTrainTicketMapper.deleteByPrimaryKey(id);
        }
    
        @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);
    
        }
    }
    
  • DailyTrainService.java

    package com.neilxu.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.date.DateUtil;
    import cn.hutool.core.util.ObjectUtil;
    import com.github.pagehelper.PageHelper;
    import com.github.pagehelper.PageInfo;
    import com.neilxu.train.business.domain.DailyTrain;
    import com.neilxu.train.business.domain.DailyTrainExample;
    import com.neilxu.train.business.domain.Train;
    import com.neilxu.train.business.mapper.DailyTrainMapper;
    import com.neilxu.train.business.req.DailyTrainQueryReq;
    import com.neilxu.train.business.req.DailyTrainSaveReq;
    import com.neilxu.train.business.resp.DailyTrainQueryResp;
    import com.neilxu.train.common.resp.PageResp;
    import com.neilxu.train.common.util.SnowUtil;
    import jakarta.annotation.Resource;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    import java.util.Date;
    import java.util.List;
    
    @Service
    public class DailyTrainService 
    上一篇:视觉Transformer和Swin Transformer


    下一篇:Centos7.X服务器搭建VOS系统的REC录音转换MP3,并支持外呼系统wav转换MP3