当前位置: 首页 > news >正文

基于Spring Boot+Vue+MySQL的汽车票预订系统全栈开发实战

最近在辅导学生毕业设计时,发现很多同学对如何整合前后端技术栈完成一个完整的Web系统感到无从下手,尤其是涉及Java后端、Vue前端、Spring Boot框架和MySQL数据库的协同开发。本文将围绕一个“汽车票网上预订系统”的毕业设计项目,手把手带你从零开始,完成一个功能完整、代码规范、可直接运行和答辩的实战项目。无论你是正在寻找毕业设计课题的计算机专业学生,还是希望学习全栈开发技术的Java开发者,都能通过本文掌握从环境搭建、数据库设计、接口开发到前端联调的全流程。

1. 项目背景与核心价值

1.1 项目简介

汽车票网上预订系统是一个典型的B/S架构电子商务应用,旨在模拟现实中的长途汽车票务服务。系统核心功能包括用户注册登录、车次查询、在线选座购票、订单管理、后台车次与线路维护等。这类系统业务逻辑清晰,技术栈覆盖全面,是检验学生综合运用Java Web开发、数据库设计、前端交互等能力的绝佳课题。

1.2 技术选型与优势

本系统采用当前企业级开发中最主流、最成熟的“前后端分离”架构。

  • 后端 (Java + Spring Boot): Spring Boot极大地简化了Spring应用的初始搭建和开发过程,通过自动配置和起步依赖,我们可以快速构建出稳定、可扩展的RESTful API服务。它负责处理核心业务逻辑、数据持久化和安全性。
  • 前端 (Vue.js): Vue是一套用于构建用户界面的渐进式JavaScript框架,以其轻量、易上手和强大的响应式数据绑定著称。采用Vue可以高效地开发出交互体验良好的单页面应用(SPA)。
  • 数据库 (MySQL): MySQL作为最流行的开源关系型数据库之一,性能稳定、社区活跃,完全能够满足本系统在数据存储、事务处理方面的需求。
  • 项目构建与管理 (Maven): Maven用于统一管理项目依赖、构建生命周期,确保团队协作和项目部署的一致性。

这种技术组合的优势在于:架构清晰、职责分离、便于团队协作、前后端可以并行开发,并且拥有极其丰富的社区资源和开源组件支持,能有效降低学习和开发成本。

2. 开发环境与工具准备

在开始编码之前,请确保你的本地开发环境已就绪。以下版本为本文示例环境,你可以根据实际情况调整。

2.1 后端环境

  • JDK: 版本 8 或 11 (推荐11,长期支持版本)。安装后配置JAVA_HOME环境变量。
  • IDE: IntelliJ IDEA (社区版或旗舰版) 或 Eclipse (需安装Spring Tools插件)。本文以IDEA为例。
  • 构建工具: Apache Maven 3.6+。安装后配置MAVEN_HOME,并确保mvn -v命令能正确执行。
  • 数据库: MySQL 5.7 或 8.0。确保MySQL服务已启动,并记住root用户密码。

2.2 前端环境

  • Node.js: 版本 14.x 或 16.x (LTS版本)。安装Node.js会自带包管理工具npm。
  • 包管理器: 可以使用npm,但更推荐yarnpnpm,速度更快。本文使用npm。
  • IDE: Visual Studio Code (VSCode) 或 WebStorm。VSCode轻量且插件丰富,是前端开发的热门选择。

2.3 辅助工具

  • API测试: Postman 或 Insomnia,用于测试后端接口。
  • 数据库管理: Navicat for MySQL, MySQL Workbench 或 DBeaver,用于直观地操作数据库。
  • 版本控制: Git,用于代码版本管理。可搭配Gitee或GitHub使用。

环境验证命令:

# 检查Java java -version # 检查Maven mvn -v # 检查Node.js和npm node -v npm -v # 检查MySQL客户端(需在PATH中) mysql --version

3. 数据库设计与建模

数据库设计是系统的基石,良好的设计能保证数据一致性、完整性和查询性能。

3.1 数据库概念模型 (E-R图)

核心实体包括:

  1. 用户 (user): 系统使用者,分为普通用户和管理员。
  2. 汽车班次 (schedule): 具体的发车班次,包含发车时间、起始站、终点站、车型、票价等。
  3. 座位 (seat): 属于某个班次,有座位号、类型(如普通座、商务座)和状态(可售、已售)。
  4. 订单 (order): 用户购买车票后生成的凭证,关联用户、班次和具体座位。
  5. 支付记录 (payment): 记录订单的支付状态和详情。

实体间关系:一个用户可以下多个订单,一个订单对应一个班次和一个座位,一个班次拥有多个座位。

3.2 物理表结构SQL

在MySQL中创建数据库bus_ticket_db,并执行以下SQL语句建表。

-- 创建数据库 CREATE DATABASE IF NOT EXISTS `bus_ticket_db` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE `bus_ticket_db`; -- 用户表 CREATE TABLE `sys_user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `username` varchar(50) NOT NULL COMMENT '用户名(登录用)', `password` varchar(255) NOT NULL COMMENT '密码(加密存储)', `real_name` varchar(50) DEFAULT NULL COMMENT '真实姓名', `phone` varchar(20) DEFAULT NULL COMMENT '手机号', `id_card` varchar(20) DEFAULT NULL COMMENT '身份证号', `avatar` varchar(500) DEFAULT NULL COMMENT '头像URL', `role` varchar(20) NOT NULL DEFAULT 'USER' COMMENT '角色:USER-普通用户,ADMIN-管理员', `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态:0-禁用,1-正常', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`), UNIQUE KEY `uk_phone` (`phone`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统用户表'; -- 插入初始管理员用户 (密码明文为admin123,实际应为BCrypt加密后的值) INSERT INTO `sys_user` (`username`, `password`, `real_name`, `role`) VALUES ('admin', '$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTV5UiC', '系统管理员', 'ADMIN'); -- 汽车班次表 CREATE TABLE `bus_schedule` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '班次ID', `bus_number` varchar(50) NOT NULL COMMENT '车牌号/班次号', `departure_station` varchar(100) NOT NULL COMMENT '始发站', `arrival_station` varchar(100) 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 '票价', `bus_type` varchar(50) DEFAULT '普通大巴' COMMENT '车型', `status` varchar(20) NOT NULL DEFAULT 'NORMAL' COMMENT '状态:NORMAL-正常,CANCELLED-已取消', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_departure_time` (`departure_time`), KEY `idx_route` (`departure_station`, `arrival_station`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='汽车班次表'; -- 座位表 CREATE TABLE `bus_seat` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '座位ID', `schedule_id` bigint(20) NOT NULL COMMENT '所属班次ID', `seat_number` varchar(10) NOT NULL COMMENT '座位号,如:A01, B12', `seat_type` varchar(20) DEFAULT 'NORMAL' COMMENT '座位类型:NORMAL-普通,VIP-商务', `status` varchar(20) NOT NULL DEFAULT 'AVAILABLE' COMMENT '状态:AVAILABLE-可售,LOCKED-锁定(选座中),SOLD-已售', `lock_expire_time` datetime DEFAULT NULL COMMENT '锁定过期时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_schedule_seat` (`schedule_id`, `seat_number`), KEY `idx_schedule_status` (`schedule_id`, `status`), CONSTRAINT `fk_seat_schedule` FOREIGN KEY (`schedule_id`) REFERENCES `bus_schedule` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='座位表'; -- 订单表 CREATE TABLE `bus_order` ( `id` varchar(32) NOT NULL COMMENT '订单号(业务主键)', `user_id` bigint(20) NOT NULL COMMENT '用户ID', `schedule_id` bigint(20) NOT NULL COMMENT '班次ID', `seat_id` bigint(20) NOT NULL COMMENT '座位ID', `passenger_name` varchar(50) NOT NULL COMMENT '乘客姓名', `passenger_id_card` varchar(20) NOT NULL COMMENT '乘客身份证', `total_amount` decimal(10,2) NOT NULL COMMENT '订单总金额', `order_status` varchar(20) NOT NULL DEFAULT 'UNPAID' COMMENT '订单状态:UNPAID-待支付,PAID-已支付,CANCELLED-已取消,REFUNDED-已退款', `pay_time` datetime DEFAULT NULL COMMENT '支付时间', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), KEY `idx_user_id` (`user_id`), KEY `idx_create_time` (`create_time`), CONSTRAINT `fk_order_user` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`id`), CONSTRAINT `fk_order_schedule` FOREIGN KEY (`schedule_id`) REFERENCES `bus_schedule` (`id`), CONSTRAINT `fk_order_seat` FOREIGN KEY (`seat_id`) REFERENCES `bus_seat` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';

设计要点说明:

  1. 密码安全: 用户密码必须加密存储,示例中使用的是BCrypt加密后的密文。绝对禁止明文存储。
  2. 索引优化: 在departure_time,departure_station等查询频繁的字段上创建索引,提升查询性能。
  3. 外键约束: 使用外键保证数据引用完整性,例如订单必须关联有效的用户、班次和座位。
  4. 业务主键: 订单号id使用字符串(如UUID或雪花算法ID),便于业务展示和传递。
  5. 数据字典: 状态字段(如order_status,seat_status)使用明确的字符串常量,提高可读性。

4. Spring Boot后端项目搭建与核心开发

4.1 创建Spring Boot项目

使用Spring Initializr (https://start.spring.io) 或IDEA内置的Spring Initializr创建项目。

  • Project: Maven Project
  • Language: Java
  • Spring Boot: 2.7.x (稳定版本)
  • Group: com.busticket
  • Artifact: bus-ticket-system
  • Packaging: Jar
  • Java: 11
  • Dependencies: 选择Spring Web,Spring Data JPA,MySQL Driver,Lombok

生成项目后,用IDEA打开。

4.2 项目结构与关键配置

项目标准Maven结构如下:

bus-ticket-system ├── src/main/java/com/busticket │ ├── BusTicketSystemApplication.java // 启动类 │ ├── config/ // 配置类(如WebConfig, SecurityConfig) │ ├── controller/ // 控制器层 │ ├── service/ // 业务逻辑层 │ │ └── impl/ // 业务实现类 │ ├── repository/ // 数据访问层 (JPA) │ ├── model/entity/ // 实体类 │ ├── model/dto/ // 数据传输对象 │ ├── model/vo/ // 视图对象 │ └── utils/ // 工具类 ├── src/main/resources │ ├── application.yml // 主配置文件 │ └── static/ // 静态资源 └── pom.xml // Maven依赖管理

配置文件application.yml

server: port: 8080 servlet: context-path: /api spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/bus_ticket_db?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai username: root password: your_password_here # 替换为你的MySQL密码 jpa: hibernate: ddl-auto: update # 开发环境可用update,生产环境用validate或none show-sql: true properties: hibernate: format_sql: true database-platform: org.hibernate.dialect.MySQL8Dialect # 自定义配置 app: jwt: secret: yourSuperSecretKeyForJWT123!@# # JWT密钥,务必修改并保密 expiration: 86400000 # token有效期,单位毫秒 (24小时)

4.3 核心实体类与JPA映射

使用JPA注解将数据库表映射为Java实体类。

用户实体 (User.java):

package com.busticket.model.entity; import lombok.Data; import javax.persistence.*; import java.time.LocalDateTime; @Entity @Table(name = "sys_user") @Data public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(unique = true, nullable = false, length = 50) private String username; @Column(nullable = false) private String password; private String realName; private String phone; private String idCard; private String avatar; @Column(nullable = false) private String role = "USER"; // USER, ADMIN @Column(nullable = false) private Boolean status = true; @Column(updatable = false) private LocalDateTime createTime; private LocalDateTime updateTime; @PrePersist protected void onCreate() { createTime = LocalDateTime.now(); updateTime = LocalDateTime.now(); } @PreUpdate protected void onUpdate() { updateTime = LocalDateTime.now(); } }

班次实体 (Schedule.java) 和 订单实体 (Order.java)依此类推,使用@OneToMany,@ManyToOne等注解定义关联关系。

4.4 实现用户登录与JWT认证

在Web系统中,认证和授权是首要安全环节。我们采用无状态的JWT (JSON Web Token) 方案。

1. 添加JWT依赖 (pom.xml):

<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency>

2. 创建JWT工具类 (JwtUtil.java):

package com.busticket.utils; import io.jsonwebtoken.*; import io.jsonwebtoken.security.Keys; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.security.Key; import java.util.Date; @Component public class JwtUtil { @Value("${app.jwt.secret}") private String secret; @Value("${app.jwt.expiration}") private Long expiration; private Key getSigningKey() { return Keys.hmacShaKeyFor(secret.getBytes()); } public String generateToken(String username, String role) { Date now = new Date(); Date expiryDate = new Date(now.getTime() + expiration); return Jwts.builder() .setSubject(username) .claim("role", role) .setIssuedAt(now) .setExpiration(expiryDate) .signWith(getSigningKey(), SignatureAlgorithm.HS256) .compact(); } public String getUsernameFromToken(String token) { Claims claims = Jwts.parserBuilder() .setSigningKey(getSigningKey()) .build() .parseClaimsJws(token) .getBody(); return claims.getSubject(); } public boolean validateToken(String token) { try { Jwts.parserBuilder().setSigningKey(getSigningKey()).build().parseClaimsJws(token); return true; } catch (JwtException | IllegalArgumentException e) { // 日志记录异常 return false; } } }

3. 创建登录接口 (AuthController.java):

package com.busticket.controller; import com.busticket.model.dto.LoginRequest; import com.busticket.model.dto.LoginResponse; import com.busticket.model.entity.User; import com.busticket.repository.UserRepository; import com.busticket.utils.JwtUtil; import lombok.RequiredArgsConstructor; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; import java.util.Optional; @RestController @RequestMapping("/auth") @RequiredArgsConstructor public class AuthController { private final UserRepository userRepository; private final BCryptPasswordEncoder passwordEncoder; private final JwtUtil jwtUtil; @PostMapping("/login") public LoginResponse login(@Valid @RequestBody LoginRequest request) { Optional<User> userOpt = userRepository.findByUsername(request.getUsername()); if (userOpt.isEmpty()) { throw new RuntimeException("用户名或密码错误"); } User user = userOpt.get(); if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) { throw new RuntimeException("用户名或密码错误"); } if (!user.getStatus()) { throw new RuntimeException("用户已被禁用"); } String token = jwtUtil.generateToken(user.getUsername(), user.getRole()); return new LoginResponse(token, user.getUsername(), user.getRole()); } }

4. 配置Spring Security (简化版):创建一个配置类SecurityConfig.java,配置密码编码器、放行登录接口、对其他接口进行JWT令牌验证。由于篇幅限制,这里不展开完整代码,核心是自定义一个JwtAuthenticationFilter来拦截请求并验证Token。

4.5 实现车次查询与购票业务

这是系统的核心业务逻辑,涉及事务管理和并发控制。

1. 车次查询接口 (ScheduleController.java):

@RestController @RequestMapping("/schedules") @RequiredArgsConstructor public class ScheduleController { private final ScheduleService scheduleService; @GetMapping public PageResult<ScheduleVO> querySchedules( @RequestParam String departure, @RequestParam String arrival, @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate date, @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer size) { return scheduleService.querySchedules(departure, arrival, date, page, size); } }

2. 购票接口 (OrderController.java) - 重点注意并发问题:购票是典型的高并发场景,需要防止超卖(同一座位被多人购买)。

@PostMapping("/create") public Result createOrder(@Valid @RequestBody CreateOrderDTO dto, @RequestHeader("Authorization") String token) { // 1. 验证用户身份 (从token中解析userId) Long userId = getUserIdFromToken(token); // 2. 调用服务层方法,该方法必须加 @Transactional 和锁机制 String orderId = orderService.createOrder(userId, dto); return Result.success("下单成功", orderId); }

服务层购票方法 (OrderServiceImpl.java):

@Override @Transactional(rollbackFor = Exception.class) public String createOrder(Long userId, CreateOrderDTO dto) { // 1. 查询班次和座位信息,并加锁(悲观锁或乐观锁) Schedule schedule = scheduleRepository.findByIdWithLock(dto.getScheduleId()) .orElseThrow(() -> new RuntimeException("班次不存在")); Seat seat = seatRepository.findByIdWithLock(dto.getSeatId()) .orElseThrow(() -> new RuntimeException("座位不存在")); // 2. 校验:班次状态、座位状态、余票 if (!"NORMAL".equals(schedule.getStatus())) { throw new RuntimeException("该班次已取消"); } if (!"AVAILABLE".equals(seat.getStatus())) { throw new RuntimeException("该座位已被预订或售出"); } if (schedule.getAvailableSeats() <= 0) { throw new RuntimeException("余票不足"); } // 3. 扣减余票,更新座位状态(在同一个事务内) schedule.setAvailableSeats(schedule.getAvailableSeats() - 1); scheduleRepository.save(schedule); seat.setStatus("SOLD"); seatRepository.save(seat); // 4. 生成订单 Order order = new Order(); order.setId(generateOrderId()); // 使用雪花算法生成订单号 order.setUserId(userId); order.setScheduleId(schedule.getId()); order.setSeatId(seat.getId()); order.setPassengerName(dto.getPassengerName()); order.setPassengerIdCard(dto.getPassengerIdCard()); order.setTotalAmount(schedule.getPrice()); order.setOrderStatus("UNPAID"); orderRepository.save(order); // 5. (可选) 发送创建订单成功消息到消息队列,触发后续支付流程 // rabbitTemplate.convertAndSend("order.exchange", "order.created", order.getId()); return order.getId(); }

关键点:这里使用了findByIdWithLock(自定义方法,使用SELECT ... FOR UPDATE)对记录加悲观锁,确保在事务提交前,其他线程无法修改这条记录,这是防止超卖的一种有效方式。在高并发场景下,也可以考虑使用乐观锁(版本号)或分布式锁(如Redis)。

5. Vue前端项目开发与联调

5.1 创建Vue项目

使用Vue CLI快速搭建项目。

# 全局安装Vue CLI(如果未安装) npm install -g @vue/cli # 创建项目 vue create bus-ticket-frontend # 选择 Manually select features # 勾选 Babel, Router, Vuex, CSS Pre-processors (如Sass), Linter/Formatter # 选择 Vue 2 或 Vue 3 (本文以Vue 3 + Composition API为例) # 其余选项按需选择或默认 cd bus-ticket-frontend npm install axios element-plus --save # 安装HTTP库和UI组件库

5.2 项目结构规划

src/ ├── api/ // 所有后端接口请求封装 │ └── index.js ├── assets/ // 静态资源 ├── components/ // 公共组件 ├── router/ // 路由配置 ├── store/ // Vuex状态管理 ├── utils/ // 工具函数(如request拦截器) ├── views/ // 页面组件 │ ├── Login.vue │ ├── Home.vue │ ├── ScheduleQuery.vue │ ├── SeatSelection.vue │ └── OrderList.vue └── App.vue

5.3 封装Axios与API请求

src/utils/request.js中创建axios实例,配置请求/响应拦截器,用于自动添加JWT Token和处理通用错误。

import axios from 'axios'; import { ElMessage } from 'element-plus'; import router from '@/router'; const service = axios.create({ baseURL: process.env.VUE_APP_BASE_API || 'http://localhost:8080/api', timeout: 10000, }); // 请求拦截器 service.interceptors.request.use( config => { 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; // 假设后端统一返回格式为 { code: 200, message: 'success', data: {} } if (res.code !== 200) { ElMessage.error(res.message || 'Error'); // 如果是401未授权,跳转到登录页 if (res.code === 401) { localStorage.removeItem('token'); router.push('/login'); } return Promise.reject(new Error(res.message || 'Error')); } else { return res.data; // 直接返回业务数据 } }, error => { console.error('Response Error:', error); ElMessage.error(error.message || '网络请求失败'); return Promise.reject(error); } ); export default service;

src/api/index.js中封装具体的业务接口。

import request from '@/utils/request'; export function login(data) { return request({ url: '/auth/login', method: 'post', data }); } export function getScheduleList(params) { return request({ url: '/schedules', method: 'get', params }); } export function createOrder(data) { return request({ url: '/orders/create', method: 'post', data }); }

5.4 实现车次查询与选座页面

车次查询页面 (ScheduleQuery.vue) 核心代码片段:

<template> <div class="schedule-query"> <el-form :model="queryForm" inline @submit.prevent="handleQuery"> <el-form-item label="出发城市"> <el-input v-model="queryForm.departure" placeholder="如:北京"></el-input> </el-form-item> <el-form-item label="到达城市"> <el-input v-model="queryForm.arrival" placeholder="如:上海"></el-input> </el-form-item> <el-form-item label="出发日期"> <el-date-picker v-model="queryForm.date" type="date" value-format="yyyy-MM-dd"></el-date-picker> </el-form-item> <el-form-item> <el-button type="primary" @click="handleQuery">查询</el-button> </el-form-item> </el-form> <el-table :data="scheduleList" v-loading="loading"> <el-table-column prop="busNumber" label="班次号"></el-table-column> <el-table-column prop="departureStation" label="始发站"></el-table-column> <el-table-column prop="arrivalStation" label="终点站"></el-table-column> <el-table-column prop="departureTime" label="发车时间"> <template #default="{ row }"> {{ formatDateTime(row.departureTime) }} </template> </el-table-column> <el-table-column prop="price" label="票价(元)"></el-table-column> <el-table-column prop="availableSeats" label="余票"></el-table-column> <el-table-column label="操作"> <template #default="{ row }"> <el-button size="small" @click="goToSeatSelection(row.id)">选座购票</el-button> </template> </el-table-column> </el-table> <el-pagination @current-change="handlePageChange" :current-page="currentPage" :page-size="pageSize" :total="total" layout="prev, pager, next"> </el-pagination> </div> </template> <script setup> import { ref, onMounted } from 'vue'; import { getScheduleList } from '@/api'; import { useRouter } from 'vue-router'; import { ElMessage } from 'element-plus'; const router = useRouter(); const queryForm = ref({ departure: '', arrival: '', date: new Date().toISOString().split('T')[0] // 默认今天 }); const scheduleList = ref([]); const loading = ref(false); const currentPage = ref(1); const pageSize = 10; const total = ref(0); const handleQuery = async () => { if (!queryForm.value.departure || !queryForm.value.arrival) { ElMessage.warning('请输入出发地和目的地'); return; } loading.value = true; try { const params = { ...queryForm.value, page: currentPage.value, size: pageSize }; const res = await getScheduleList(params); scheduleList.value = res.list; total.value = res.total; } catch (error) { console.error(error); } finally { loading.value = false; } }; const goToSeatSelection = (scheduleId) => { router.push({ path: '/seat-selection', query: { scheduleId } }); }; onMounted(() => { handleQuery(); // 页面加载时默认查询一次 }); </script>

选座页面 (SeatSelection.vue)则需要通过scheduleId查询该班次的所有座位状态,并用一个网格(Grid)或表格渲染出来,可用不同颜色区分“可售”、“已售”状态。用户点击可售座位后,弹出表单填写乘客信息并调用创建订单接口。

6. 系统部署与运行

6.1 后端打包与运行

  1. 打包: 在项目根目录执行mvn clean package -DskipTests,会在target目录生成bus-ticket-system-0.0.1-SNAPSHOT.jar
  2. 运行:java -jar bus-ticket-system-0.0.1-SNAPSHOT.jar。确保MySQL服务已启动且配置文件中的数据库连接信息正确。
  3. 验证: 浏览器访问http://localhost:8080/api/auth/login(POST请求需用Postman测试),或访问http://localhost:8080/api/schedules?departure=北京&arrival=上海&date=2023-10-27看是否能返回数据。

6.2 前端打包与运行

  1. 开发环境运行:npm run serve,访问http://localhost:8081(端口可能不同)。
  2. 生产打包: 修改.env.production文件,设置VUE_APP_BASE_API为你的后端生产环境地址。然后执行npm run build,生成静态文件在dist目录。
  3. 部署: 可以将dist目录下的文件部署到Nginx、Apache等Web服务器,或使用Docker容器化部署。

6.3 前后端联调

  1. 启动后端Spring Boot应用。
  2. 启动前端开发服务器 (npm run serve)。
  3. 在前端页面进行登录、查询、购票等操作。
  4. 使用浏览器开发者工具的Network面板查看所有HTTP请求和响应,这是调试前后端交互问题的关键。

7. 常见问题与解决方案 (FAQ)

问题现象可能原因解决方案
前端访问后端接口出现CORS错误浏览器同源策略限制,前端(localhost:8081)访问后端(localhost:8080)端口不同。在后端Spring Boot中配置CORS。添加@CrossOrigin注解或全局CORS配置类。
启动Spring Boot报java.net.ConnectException: Connection refusedMySQL数据库服务未启动,或连接URL、用户名、密码错误。检查MySQL服务状态,确认application.yml中的数据库配置正确。
Vue项目运行时报Error: Cannot find module ‘xxx’依赖未安装或node_modules损坏。删除node_modulespackage-lock.json,重新执行npm install
JPA执行更新操作,字段未更新实体类未提供setter方法,或使用了@Data但其他注解冲突。确保实体类有正确的setter方法,或检查是否在非事务方法中执行了更新。
购票时出现“座位已售出”但查询显示有余票高并发下,多个请求同时通过了余票检查。使用数据库悲观锁(SELECT ... FOR UPDATE)、乐观锁(版本号)或在应用层使用分布式锁。
前端页面刷新后Vuex状态丢失Vuex状态存储在内存中,页面刷新会重置。将需要持久化的状态(如用户token、基本信息)同步存储到localStoragesessionStorage
打包后的前端页面访问后端API 404生产环境API地址配置错误,或Nginx未正确代理API请求。检查打包时设置的VUE_APP_BASE_API,并配置Nginx将/api路径的请求反向代理到后端服务。

8. 项目扩展与优化建议 (毕业设计加分项)

一个基础的CRUD系统只能算及格,要获得高分,需要在以下方面进行深化:

  1. 支付集成: 模拟支付流程。集成支付宝/微信支付沙箱环境,实现从创建订单、调用支付接口、处理支付回调、更新订单状态的完整闭环。
  2. 短信/邮件通知: 使用阿里云短信服务或邮件服务,在用户注册、购票成功、发车前提醒等环节发送通知。
  3. Redis缓存: 将热门线路的车次信息、站点信息缓存到Redis中,大幅减轻数据库压力,提升查询性能。
  4. Elasticsearch搜索: 如果班次数据量大,查询条件复杂,可以引入Elasticsearch,实现更高效、更灵活的车次搜索功能。
  5. 后台管理系统: 为管理员开发一个独立的Vue后台管理项目,实现班次管理、用户管理、订单统计、数据可视化(使用ECharts)等功能。
  6. Docker容器化部署: 编写Dockerfiledocker-compose.yml,将MySQL、Redis、后端应用、前端应用全部容器化,实现一键部署,体现运维能力。
  7. API文档: 使用Swagger/OpenAPI生成在线API文档,方便前端对接和答辩演示。
  8. 单元测试: 为Service层关键方法编写JUnit单元测试,保证核心业务逻辑的正确性。
  9. 压力测试: 使用JMeter对购票接口进行压力测试,分析系统瓶颈并提出优化方案,这在答辩时是很大的亮点。

完成这个项目后,你不仅能够提交一份高质量的毕业设计,更能系统地掌握从前端到后端、从数据库到部署的完整全栈开发技能。建议你在GitHub或Gitee上创建仓库来管理代码,良好的提交记录和README文档也是加分项。

http://www.jsqmd.com/news/1319655/

相关文章:

  • Unity3D与Pico VR开发环境配置全攻略:从零搭建到性能优化
  • 2026年8月广东省惠州市电信融合宽带攻略与避坑指南 - 领卡园地
  • 告别命令行烦恼:TegraRcmGUI让Switch注入Payload变得如此简单
  • Django开发校园兼职管理系统:技术选型与实现
  • VLDB 2020女性数据创新技术解析与应用实践
  • 数据网格架构:金融行业大数据平台改造实践与挑战
  • Windows任务栏美化终极指南:用TranslucentTB轻松打造透明桌面
  • Impala字符串函数全解析:从基础操作到性能调优实战指南
  • 十堰市天车龙门吊行车吊机起重机采购销售维修安装维保改造本地厂家推荐指南 - 便民获客通
  • 2026 沅江装修口碑榜单|深耕12年,沅江意诚装饰凭精工与诚信服务收获沅江业主一致好评 - 推途云
  • 零基础UE5 VR开发入门:从蓝图交互到场景优化的完整实践指南
  • 终极指南:使用LeetDown为老旧iPhone/iPad降级iOS系统
  • 暗黑3自动化辅助工具终极指南:如何用D3keyHelper彻底告别操作疲劳
  • 长期到店真实体验纪实:黛妮雅卡斯造型(肥西店)消费客观评价 - 米諾
  • 3ds Max与Unity贴图错乱终极解决:UVW通道映射原理与实战
  • 3分钟精通Blender 3MF插件:打通3D打印工作流的终极解决方案
  • 3分钟掌握QKeyMapper:解决游戏手柄与键鼠互转的终极指南
  • 海诺大招vs刘禅强推:机制拆解与高地攻防战术分析
  • 西南优质种子技术服务供应商推荐,四川成都泥美人一站式农业方案助力种植增收 - 十大品牌榜
  • qBittorrent搜索插件终极指南:5分钟开启一站式种子搜索
  • Trino与Paimon元数据整合优化实践
  • Wand-Enhancer:3步永久解锁游戏修改器专业版完整功能
  • 2026 推荐:石家庄新房装修后除甲醛避坑,靠谱公司全攻略 - 专注室内空气检测治理
  • QKeyMapper终极指南:Windows免费开源按键映射工具,游戏手柄键鼠全能转换
  • Legacy iOS Kit深度解析:SSH Ramdisk模式的技术架构与应用实践
  • Unity可视化脚本实战:基于XNode与Odin构建可运行流程图系统
  • 武汉科谷技工学校 2026 年招生代码 - 升学择校早知道
  • 如何用gofile-downloader彻底解决Gofile下载速度慢的问题:完整免费指南
  • 网盘直链下载助手完整指南:5分钟掌握免费高速下载技巧
  • 外贸长账期订单越来越多:BBWEYY GEO与独立站如何平衡增长与回款安全,含零代码SAAS、AI编程、源码定制交付