如果你正在为计算机专业的毕业设计发愁,不知道如何选择一个既有技术含量、又能完整展示自己能力的项目,那么这篇文章就是为你准备的。
每年都有大量同学在选题上陷入困境:想做点有实际意义的,但担心技术栈太复杂;想用流行的前后端分离架构,又怕时间不够、代码跑不通。最终,要么选择一个过于简单的管理系统草草了事,要么硬着头皮啃一个远超自己能力的项目,导致答辩时漏洞百出。
今天要拆解的这个“汽车票网上预订系统”,就是一个非常典型的、**“刚刚好”**的毕业设计选题。它涵盖了Java后端、Vue前端、SpringBoot框架和MySQL数据库,技术栈主流且完整,业务逻辑清晰但又不至于过于复杂。更重要的是,这类项目有成熟的开发模式和大量可借鉴的代码,能让你把精力从“从零造轮子”转移到“理解架构、实现业务、优化细节”上,这才是毕业设计真正要考察的能力。
本文将为你提供一份从零到一的实战指南。我们不止会告诉你这个系统“是什么”,更会深入分析它“为什么”适合作为毕业设计,并手把手带你拆解技术选型、环境搭建、核心模块开发、前后端联调以及部署上线的完整流程。你会发现,一个合格的毕业设计项目,其价值不在于用了多少炫酷的新技术,而在于你是否能系统性地运用所学知识,解决一个真实的业务问题。
1. 为什么“汽车票网上预订系统”是一个优秀的毕业设计选题?
在深入代码之前,我们首先要建立一个清晰的认知:一个好的毕业设计选题,应该像一座精心设计的桥梁,连接着你的知识储备和实际工程能力。“汽车票网上预订系统”恰好就是这样一座桥。
首先,它的业务场景具有普适性和完整性。几乎每个人都接触过在线购票,这意味着你不需要花费大量时间去理解一个生僻的业务领域。系统核心功能明确:用户管理、车次查询、余票显示、选座购票、订单支付、订单管理。这些功能模块清晰划分了前后端的职责,也天然地引入了数据库设计中的关键概念,如事务(保证扣款和出票的一致性)、锁(防止超卖)和关联查询(用户-订单-车次)。
其次,技术栈组合经典且实用。Java + SpringBoot + Vue + MySQL这个组合,是目前企业级Web开发中最主流、最稳定的技术方案之一。选择它意味着:
- 学习资源极其丰富:无论是SpringBoot的自动配置、MyBatis的ORM映射,还是Vue的组件化开发,你遇到的几乎所有问题都能在网上找到成熟的解决方案和社区讨论。
- 技术深度可控:你可以根据自身水平调整难度。基础层面,实现CRUD(增删改查)和页面跳转即可达标;进阶层面,你可以深入探讨JWT令牌认证、Redis缓存车次信息、RabbitMQ处理订单异步通知、使用Elasticsearch实现复杂车次搜索等,充分展示你的技术视野。
- 易于演示和答辩:系统有直观的前端界面,评委老师可以一目了然地看到你的工作成果。后端清晰的API设计和数据库表结构,也能在答辩时很好地体现你的设计思路。
最后,它完美匹配了毕业设计的核心要求。毕业设计本质上是一个微型软件工程项目,它考察的是你分析问题、设计系统、编写代码、测试调试和文档撰写这一整套能力。这个项目规模适中,足以让你经历一个完整的开发周期,但又不会庞大到让你迷失在细节中。完成它,你收获的不仅仅是一个能运行的系统和一份论文,更是一套可复用的、贴近企业实践的开发方法论。
2. 技术栈选型与核心概念解析
明确了项目价值,我们来拆解其技术构成。这个“四件套”组合各有其职,理解它们的分工与协作是项目成功的基础。
后端技术栈 (Java + SpringBoot)
- Java: 项目的基石语言。它的稳定性、丰富的生态(各种库和框架)和面向对象特性,非常适合构建复杂的业务后端。
- SpringBoot: 本项目的“加速器”。它基于Spring框架,通过“约定大于配置”的理念和自动配置,极大地简化了Spring应用的初始搭建和开发过程。你不再需要编写繁琐的XML配置文件,一个
main方法就能启动一个内嵌Tomcat的Web服务器。- 核心作用:依赖管理、自动配置、提供Web MVC框架、集成MyBatis/JPA等数据层框架。
- MyBatis: 一个优秀的持久层框架,它封装了JDBC操作,让你能通过简单的XML或注解来配置和映射原生SQL,将Java对象和数据库记录灵活地关联起来。
- Maven/Gradle: 项目构建和依赖管理工具。它们帮你从仓库自动下载项目所需的Jar包,管理项目结构,是现代化Java项目的标配。
前端技术栈 (Vue.js)
- Vue.js: 一套用于构建用户界面的渐进式JavaScript框架。它的核心是响应式数据绑定和组件化系统。数据变了,视图自动更新;页面被拆分成一个个独立、可复用的组件(如导航栏、车次列表、订单卡片),使得开发和维护变得非常清晰。
- Vue Router: 官方路由管理器。负责根据不同的URL路径,切换显示不同的Vue组件,实现单页面应用(SPA)的效果,比如从“首页”跳转到“我的订单”。
- Axios: 一个基于Promise的HTTP客户端。前端通过它向后端SpringBoot提供的RESTful API发送请求(GET/POST/PUT/DELETE),并处理返回的JSON数据。
- Element Plus / Ant Design Vue: 流行的Vue UI组件库。它们提供了现成、美观的按钮、表格、表单、对话框等组件,能极大提升前端开发效率,让界面快速达到可演示的水平。
数据层 (MySQL)
- MySQL: 最流行的开源关系型数据库之一。它将数据存储在结构化的表中,通过SQL语言进行管理。
- 核心概念在本项目中的应用:
- 表设计:你需要设计
user(用户)、bus_schedule(车次)、ticket(车票)、order(订单)等核心表。 - 索引:在
bus_schedule表的departure_time(发车时间)、start_station(始发站)等字段建立索引,能大幅提升查询速度。 - 事务:在用户支付订单时,必须保证“扣减余额”和“生成订单、锁定座位”这两个操作要么全部成功,要么全部失败,这就需要数据库事务来保障。
- 表设计:你需要设计
前后端分离架构这是本项目的关键架构思想。前端(Vue)和后端(SpringBoot)完全独立开发、独立部署,通过HTTP API(通常是JSON格式)进行通信。
- 优势:
- 职责清晰:前端专注交互和展示,后端专注业务逻辑和数据。
- 并行开发:前后端开发者可以同时工作,只需提前定义好API接口文档。
- 技术栈灵活:后端可以替换为其他语言,前端可以替换为React等,只要API不变。
- 通信流程:用户在前端点击“查询” -> Vue通过Axios发送GET请求到
/api/schedules-> SpringBoot控制器接收请求,调用Service处理业务,通过MyBatis查询数据库 -> 将结果封装成JSON返回 -> Vue接收到数据,更新页面组件。
3. 开发环境与工具准备
工欲善其事,必先利其器。一个顺畅的开发环境能避免很多不必要的麻烦。以下是推荐的开发环境清单:
后端环境 (Windows/macOS/Linux均可)
- JDK: 版本 8 或 11 (LTS长期支持版)。推荐JDK 11。
- 安装后配置
JAVA_HOME环境变量。 - 验证:
java -version
- 安装后配置
- IDE: IntelliJ IDEA (社区版或旗舰版)。它对SpringBoot的支持是最好的。
- 构建工具: Apache Maven 3.6+。
- 安装后配置
MAVEN_HOME,并将bin目录加入PATH。 - 验证:
mvn -v
- 安装后配置
- 数据库: MySQL 5.7 或 8.0。
- 安装MySQL Server,记住root密码。
- 安装图形化管理工具:MySQL Workbench或Navicat,用于直观地操作数据库。
前端环境
- Node.js: 版本 14+。它包含了npm包管理工具。
- 验证:
node -v,npm -v
- 验证:
- Vue CLI: Vue的官方脚手架工具,用于快速创建项目骨架。
- 安装:
npm install -g @vue/cli - 验证:
vue --version
- 安装:
- IDE: Visual Studio Code (VSCode)。轻量且插件生态丰富,对Vue开发支持极佳。
- 必装插件:Volar (Vue语言支持)、ESLint、Prettier (代码格式化)。
版本控制
- Git: 必须安装。用于代码版本管理。
- 代码托管平台: 在GitHub或Gitee上创建一个仓库,将本地代码定期推送上去。这不仅是备份,也能清晰展示你的开发历程。
4. 项目初始化与数据库设计
让我们从零开始,搭建项目的骨架。
4.1 创建SpringBoot后端项目在IDEA中,使用 Spring Initializr 创建项目是最快的方式。
- 选择:
Spring Boot版本(如2.7.x或3.x),Java版本,Maven。 - 依赖:勾选
Spring Web(用于构建Web API)、MyBatis Framework、MySQL Driver、Lombok(简化Java Bean代码)。 - 生成项目并导入IDEA。
4.2 创建Vue前端项目打开终端,使用Vue CLI创建项目。
vue create bus-ticket-frontend在创建过程中,你可以手动选择特性:
- Vue 3
- 选择
Router和Vuex(状态管理,本项目可选) - 选择
CSS Pre-processors(如Sass/SCSS) - 选择
ESLint + Prettier(代码规范)
创建完成后,进入项目目录并安装一个UI库,例如Element Plus:
cd bus-ticket-frontend npm install element-plus npm install axios4.3 设计核心数据库表在MySQL中创建一个名为bus_booking的数据库,然后设计以下核心表结构:
-- 用户表 CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `username` varchar(50) NOT NULL COMMENT '用户名', `password` varchar(100) NOT NULL COMMENT '密码(加密存储)', `phone` varchar(20) DEFAULT NULL COMMENT '手机号', `id_card` varchar(20) DEFAULT NULL COMMENT '身份证号', `balance` decimal(10,2) DEFAULT '0.00' COMMENT '账户余额', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; -- 车次表 CREATE TABLE `bus_schedule` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `bus_number` varchar(20) NOT NULL COMMENT '班次号', `start_station` varchar(50) NOT NULL COMMENT '始发站', `end_station` varchar(50) NOT NULL COMMENT '终点站', `departure_time` datetime NOT NULL COMMENT '发车时间', `estimated_arrival_time` datetime NOT NULL COMMENT '预计到达时间', `total_seats` int(11) NOT NULL COMMENT '总座位数', `available_seats` int(11) NOT NULL COMMENT '可用座位数', `price` decimal(10,2) NOT NULL COMMENT '票价', PRIMARY KEY (`id`), KEY `idx_departure` (`departure_time`), KEY `idx_station` (`start_station`, `end_station`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='车次表'; -- 订单表 CREATE TABLE `order` ( `id` varchar(32) NOT NULL COMMENT '订单号(可自定义规则生成)', `user_id` bigint(20) NOT NULL COMMENT '用户ID', `schedule_id` bigint(20) NOT NULL COMMENT '车次ID', `passenger_name` varchar(50) NOT NULL COMMENT '乘客姓名', `passenger_id_card` varchar(20) NOT NULL COMMENT '乘客身份证', `seat_number` varchar(10) NOT NULL COMMENT '座位号', `amount` decimal(10,2) NOT NULL COMMENT '订单金额', `status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '状态:0-待支付,1-已支付,2-已取消', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `pay_time` datetime DEFAULT NULL COMMENT '支付时间', PRIMARY KEY (`id`), KEY `idx_user` (`user_id`), KEY `idx_schedule` (`schedule_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';这个设计包含了最核心的实体和关系。available_seats字段是余票管理的关键,对其的更新操作需要在高并发场景下特别注意。
5. 后端核心模块开发与API实现
后端是业务逻辑的核心。我们将按照分层架构(Controller -> Service -> Mapper)来开发。
5.1 配置数据库连接在src/main/resources/application.yml中配置:
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/bus_booking?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai username: root password: your_password mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true # 自动将下划线字段映射为驼峰属性5.2 创建实体类 (Entity)使用Lombok简化代码。
// 文件路径:src/main/java/com/busticket/entity/User.java package com.busticket.entity; import lombok.Data; import java.math.BigDecimal; import java.util.Date; @Data public class User { private Long id; private String username; private String password; private String phone; private String idCard; private BigDecimal balance; private Date createTime; }同理,创建BusSchedule和Order实体类。
5.3 创建数据访问层 (Mapper)先定义接口,再编写XML映射文件。
// 文件路径:src/main/java/com/busticket/mapper/BusScheduleMapper.java package com.busticket.mapper; import com.busticket.entity.BusSchedule; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import java.util.List; @Mapper public interface BusScheduleMapper { // 根据条件查询车次 List<BusSchedule> selectByCondition(@Param("startStation") String startStation, @Param("endStation") String endStation, @Param("departureDate") String departureDate); // 根据ID查询车次(用于购票时检查余票) BusSchedule selectByIdForUpdate(Long id); // FOR UPDATE 用于悲观锁 // 更新余票 int updateAvailableSeats(@Param("id") Long id, @Param("version") Integer version, @Param("seats") Integer seats); }对应的XML映射文件BusScheduleMapper.xml:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.busticket.mapper.BusScheduleMapper"> <select id="selectByCondition" resultType="BusSchedule"> SELECT * FROM bus_schedule WHERE 1=1 <if test="startStation != null and startStation != ''"> AND start_station LIKE CONCAT('%', #{startStation}, '%') </if> <if test="endStation != null and endStation != ''"> AND end_station LIKE CONCAT('%', #{endStation}, '%') </if> <if test="departureDate != null and departureDate != ''"> AND DATE(departure_time) = #{departureDate} </if> ORDER BY departure_time ASC </select> <select id="selectByIdForUpdate" resultType="BusSchedule"> SELECT * FROM bus_schedule WHERE id = #{id} FOR UPDATE </select> <update id="updateAvailableSeats"> UPDATE bus_schedule SET available_seats = available_seats + #{seats} WHERE id = #{id} </update> </mapper>5.4 创建业务逻辑层 (Service)这里是核心业务逻辑所在,例如购票,必须考虑并发下的余票问题。
// 文件路径:src/main/java/com/busticket/service/impl/TicketServiceImpl.java package com.busticket.service.impl; import com.busticket.entity.BusSchedule; import com.busticket.entity.Order; import com.busticket.mapper.BusScheduleMapper; import com.busticket.mapper.OrderMapper; import com.busticket.service.TicketService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.util.UUID; @Service public class TicketServiceImpl implements TicketService { @Autowired private BusScheduleMapper busScheduleMapper; @Autowired private OrderMapper orderMapper; @Override @Transactional(rollbackFor = Exception.class) // 声明式事务管理 public Order createOrder(Long scheduleId, Long userId, String passengerName, String passengerIdCard) { // 1. 查询车次并锁定行(悲观锁),防止其他事务同时修改 BusSchedule schedule = busScheduleMapper.selectByIdForUpdate(scheduleId); if (schedule == null) { throw new RuntimeException("车次不存在"); } if (schedule.getAvailableSeats() <= 0) { throw new RuntimeException("余票不足"); } // 2. 生成订单 Order order = new Order(); order.setId(generateOrderId()); // 自定义订单号生成规则 order.setUserId(userId); order.setScheduleId(scheduleId); order.setPassengerName(passengerName); order.setPassengerIdCard(passengerIdCard); order.setSeatNumber("A" + schedule.getTotalSeats() - schedule.getAvailableSeats() + 1); // 简单模拟座位号 order.setAmount(schedule.getPrice()); order.setStatus(0); // 待支付 // 3. 插入订单 orderMapper.insert(order); // 4. 扣减余票 int updateCount = busScheduleMapper.updateAvailableSeats(scheduleId, -1); if (updateCount != 1) { throw new RuntimeException("更新余票失败,可能已被其他用户购买"); } return order; } private String generateOrderId() { // 简单示例:时间戳+随机数 return System.currentTimeMillis() + UUID.randomUUID().toString().substring(0, 8).toUpperCase(); } }5.5 创建Web控制层 (Controller)提供RESTful API给前端调用。
// 文件路径:src/main/java/com/busticket/controller/BusScheduleController.java package com.busticket.controller; import com.busticket.entity.BusSchedule; import com.busticket.service.BusScheduleService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/api/schedules") public class BusScheduleController { @Autowired private BusScheduleService busScheduleService; @GetMapping public Result<List<BusSchedule>> querySchedules( @RequestParam(required = false) String startStation, @RequestParam(required = false) String endStation, @RequestParam(required = false) String departureDate) { List<BusSchedule> list = busScheduleService.queryByCondition(startStation, endStation, departureDate); return Result.success(list); } @GetMapping("/{id}") public Result<BusSchedule> getDetail(@PathVariable Long id) { BusSchedule schedule = busScheduleService.getById(id); return Result.success(schedule); } } // 统一返回结果封装类 @Data class Result<T> { private int code; private String msg; private T data; public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.setCode(200); result.setMsg("success"); result.setData(data); return result; } }6. 前端核心页面与组件开发
前端负责将后端的数据和功能以友好的界面呈现出来。
6.1 配置跨域与Axios全局设置在Vue项目的src目录下创建utils/request.js文件,封装Axios。
// src/utils/request.js import axios from 'axios'; const service = axios.create({ baseURL: 'http://localhost:8080/api', // 后端API基础地址 timeout: 10000 // 请求超时时间 }); // 请求拦截器(可在此处添加token) service.interceptors.request.use( config => { // 从localStorage或Vuex获取token const token = localStorage.getItem('token'); if (token) { config.headers['Authorization'] = 'Bearer ' + token; } return config; }, error => { console.error('Request Error:', error); return Promise.reject(error); } ); // 响应拦截器 service.interceptors.response.use( response => { const res = response.data; if (res.code === 200) { return res.data; } else { // 处理业务错误 console.error('API Error:', res.msg); return Promise.reject(new Error(res.msg || 'Error')); } }, error => { console.error('Response Error:', error); return Promise.reject(error); } ); export default service;6.2 开发车次查询页面创建src/views/ScheduleQuery.vue组件。
<template> <div class="schedule-query"> <el-card> <el-form :model="queryForm" :inline="true"> <el-form-item label="出发地"> <el-input v-model="queryForm.startStation" placeholder="请输入出发地"></el-input> </el-form-item> <el-form-item label="目的地"> <el-input v-model="queryForm.endStation" placeholder="请输入目的地"></el-input> </el-form-item> <el-form-item label="出发日期"> <el-date-picker v-model="queryForm.departureDate" type="date" placeholder="选择日期" value-format="YYYY-MM-DD"> </el-date-picker> </el-form-item> <el-form-item> <el-button type="primary" @click="handleQuery">查询</el-button> <el-button @click="resetQuery">重置</el-button> </el-form-item> </el-form> </el-card> <el-card style="margin-top: 20px;"> <el-table :data="scheduleList" stripe style="width: 100%"> <el-table-column prop="busNumber" label="班次号" width="120"></el-table-column> <el-table-column prop="startStation" label="始发站"></el-table-column> <el-table-column prop="endStation" label="终点站"></el-table-column> <el-table-column prop="departureTime" label="发车时间" width="180"> <template #default="scope"> {{ formatDate(scope.row.departureTime) }} </template> </el-table-column> <el-table-column prop="availableSeats" label="余票" width="80"></el-table-column> <el-table-column prop="price" label="票价" width="100"> <template #default="scope"> ¥{{ scope.row.price }} </template> </el-table-column> <el-table-column label="操作" width="120"> <template #default="scope"> <el-button type="primary" size="small" @click="goToBooking(scope.row.id)">预订</el-button> </template> </el-table-column> </el-table> <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="currentPage" :page-sizes="[10, 20, 50]" :page-size="pageSize" layout="total, sizes, prev, pager, next, jumper" :total="total"> </el-pagination> </el-card> </div> </template> <script setup> import { ref, onMounted } from 'vue'; import { useRouter } from 'vue-router'; import { ElMessage } from 'element-plus'; import request from '@/utils/request'; const router = useRouter(); const queryForm = ref({ startStation: '', endStation: '', departureDate: '' }); const scheduleList = ref([]); const currentPage = ref(1); const pageSize = ref(10); const total = ref(0); const handleQuery = async () => { try { const params = { ...queryForm.value, page: currentPage.value, size: pageSize.value }; const data = await request.get('/schedules', { params }); scheduleList.value = data.list || data; // 根据后端返回结构调整 total.value = data.total || data.length; } catch (error) { ElMessage.error('查询失败:' + error.message); } }; const resetQuery = () => { queryForm.value = { startStation: '', endStation: '', departureDate: '' }; handleQuery(); }; const goToBooking = (scheduleId) => { router.push({ path: '/booking', query: { scheduleId } }); }; const formatDate = (timestamp) => { // 简单格式化日期,可使用dayjs或moment库 return new Date(timestamp).toLocaleString(); }; onMounted(() => { handleQuery(); // 页面加载时默认查询一次 }); </script>6.3 配置Vue Router在src/router/index.js中配置路由。
import { createRouter, createWebHistory } from 'vue-router'; import ScheduleQuery from '../views/ScheduleQuery.vue'; import BookingPage from '../views/BookingPage.vue'; import UserLogin from '../views/UserLogin.vue'; const routes = [ { path: '/', redirect: '/schedules' }, { path: '/schedules', name: 'ScheduleQuery', component: ScheduleQuery }, { path: '/booking', name: 'Booking', component: BookingPage, meta: { requiresAuth: true } // 需要登录 }, { path: '/login', name: 'Login', component: UserLogin } ]; const router = createRouter({ history: createWebHistory(), routes }); // 路由守卫:检查是否需要登录 router.beforeEach((to, from, next) => { const isAuthenticated = localStorage.getItem('token'); // 简单判断 if (to.meta.requiresAuth && !isAuthenticated) { next('/login'); } else { next(); } }); export default router;7. 前后端联调与功能测试
开发完成后,最关键的一步是让前后端协同工作。
7.1 启动后端服务在IDEA中,找到包含@SpringBootApplication注解的主类,运行它的main方法。控制台输出类似Tomcat started on port(s): 8080的信息,表示启动成功。
7.2 启动前端开发服务器在终端中,进入前端项目目录,运行:
npm run serve访问控制台提示的地址,通常是http://localhost:8081。
7.3 联调测试流程
- 测试API连通性:打开浏览器开发者工具(F12),切换到Network(网络)标签页。在前端页面进行查询操作,查看是否有请求发送到
http://localhost:8080/api/schedules,以及响应状态码和返回的JSON数据是否正确。 - 测试登录与认证:实现一个简单的登录接口(返回一个模拟的token),前端登录后将token存入
localStorage。在访问需要认证的页面(如/booking)时,通过Axios拦截器将token添加到请求头。后端可以编写一个简单的过滤器来验证token。 - 测试购票流程:
- 前端:在车次列表点击“预订”,跳转到预订页面,填写乘客信息,点击提交。
- 后端:接收请求,执行
TicketService.createOrder方法,操作数据库。 - 验证:查看数据库
order表和bus_schedule表的available_seats字段是否按预期变化。
- 测试异常情况:
- 余票不足:手动将某个车次的
available_seats设为0,尝试预订,前端应收到明确的错误提示。 - 重复提交:快速双击“提交订单”按钮,后端应通过数据库锁或分布式锁机制防止生成多个订单。
- 余票不足:手动将某个车次的
8. 项目部署与上线准备
毕业设计答辩通常需要现场演示,因此将项目部署到一台本地或云服务器上,比在开发环境运行更显专业。
8.1 后端打包与部署
- 打包:在SpringBoot项目根目录下执行Maven命令,生成可执行的Jar包。
生成的Jar包位于mvn clean package -DskipTeststarget目录下,如bus-ticket-backend-0.0.1-SNAPSHOT.jar。 - 上传服务器:使用FTP或SCP工具将Jar包上传到Linux服务器。
- 运行:在服务器上运行(确保已安装JDK)。
nohup java -jar bus-ticket-backend-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod > app.log 2>&1 &--spring.profiles.active=prod用于激活生产环境配置文件(application-prod.yml),其中可以配置生产环境的数据库地址、日志级别等。
8.2 前端打包与部署
- 打包:在前端项目目录下执行构建命令,生成静态文件。
生成的静态文件位于npm run builddist目录。 - 部署:将
dist目录下的所有文件上传到Web服务器(如Nginx)的静态资源目录。 - 配置Nginx:编辑Nginx配置文件,将请求代理到后端API,并处理前端路由。
server { listen 80; server_name your-domain.com; # 或服务器IP # 前端静态文件 location / { root /path/to/your/dist; index index.html; try_files $uri $uri/ /index.html; # 支持Vue Router的history模式 } # 后端API代理 location /api/ { proxy_pass http://localhost:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } } - 重启Nginx服务。
9. 毕业设计文档与答辩要点
一个出色的项目需要清晰的文档来支撑。你的毕业设计论文和答辩PPT应围绕以下核心展开:
9.1 论文结构建议
- 绪论:阐述选题背景(传统购票痛点)、意义、国内外研究现状、本文主要工作。
- 相关技术介绍:对SpringBoot、Vue、MyBatis、MySQL等技术进行概述,说明选型理由。
- 系统需求分析:功能性需求(用例图)、非功能性需求(性能、安全性、易用性)。
- 系统设计:
- 架构设计:前后端分离架构图。
- 功能模块设计:用户模块、车次模块、订单模块等。
- 数据库设计:ER图、核心表结构说明(就是你之前设计的表)。
- 接口设计:列出核心API,如
GET /api/schedules,说明其请求和响应格式。
- 系统实现:展示关键代码片段(如购票事务处理、前端组件),并配以说明。
- 系统测试:描述测试环境、测试用例(如查询、登录、购票成功/失败)、测试结果。
- 总结与展望:总结项目成果、个人收获,指出系统不足(如未做高并发优化、界面可美化)和未来改进方向(如引入Redis缓存、微信支付、微服务化)。
9.2 答辩演示核心
- 演示流程要流畅:提前准备好测试数据,确保演示时查询、登录、购票、查看订单一气呵成。避免在现场调试代码。
- 突出重点和难点:不要平铺直叙地展示所有功能。重点讲解:
- 如何解决“超卖”问题?(悲观锁/乐观锁)
- 前后端如何通信?(Axios + RESTful API)
- 事务是如何管理的?(
@Transactional注解) - 遇到了什么困难,如何解决的?(如跨域问题、日期格式化问题)
- 准备好问答:提前思考评委可能问的问题,例如:
- 如果多人同时抢最后一张票怎么办?(深入讲锁机制)
- 用户密码怎么存储?(MD5/SHA-256加盐哈希)
- 系统能承受多大的并发?(根据你的实现回答,可提及未来可用Redis缓存车次信息、消息队列削峰等优化思路)
- 有没有考虑分布式部署?(可简要谈谈SpringCloud或Docker化的想法)
完成这个项目,你收获的将不仅仅是一个能跑通的系统。你完整地实践了软件开发的生命周期,理解了主流技术栈如何协同工作,并具备了解决典型业务问题的能力。这才是毕业设计对你未来职业生涯最大的价值。建议你将代码托管到Git平台,整理好文档,这本身就是一个出色的作品集。