温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片!
1. 项目背景与意义
随着互联网技术的快速发展,传统线下购票方式存在排队时间长、信息不透明、票务管理效率低等问题。尤其在演出、电影、交通出行等场景中,用户往往需要到现场或通过电话查询余票信息,购票体验较差。与此同时,票务管理方也面临库存统计滞后、退换票流程繁琐、数据难以沉淀等痛点。
基于SpringBoot和Vue的前后端分离购票系统,正是为了解决上述问题而设计。系统将票务信息数字化、购票流程线上化,用户可随时随地浏览场次、选择座位、在线支付并获取电子凭证;管理端则可实时维护场次、票价、库存和订单状态。该系统的设计与实现具有以下意义:
- 提升购票效率:用户无需到现场排队,在线即可完成选座、支付和出票全流程。
- 降低运营成本:票务库存、订单和财务数据自动联动,减少人工核对工作量。
- 改善用户体验:支持余票实时查询、订单状态跟踪和电子票核验,信息透明可追溯。
- 技术实践价值:前后端分离架构、RESTful API 设计、JWT 身份认证等均为企业级开发常用技术,具有较好的工程参考意义。
2. 技术栈
2.1 后端技术栈
| 技术 | 版本 | 说明 |
|---|---|---|
| SpringBoot | 2.7.x | 核心框架,提供自动配置与快速开发能力 |
| MyBatis-Plus | 3.5.x | ORM 框架,简化数据库操作 |
| MySQL | 8.0 | 关系型数据库,存储业务数据 |
| Redis | 6.x | 缓存热点数据与分布式锁 |
| JWT | 0.9.1 | 无状态身份认证令牌 |
| Maven | 3.8.x | 项目构建与依赖管理 |
2.2 前端技术栈
| 技术 | 版本 | 说明 |
|---|---|---|
| Vue | 2.6.x | 前端核心框架,组件化开发 |
| Vue Router | 3.x | 前端路由管理 |
| Vuex | 3.x | 全局状态管理 |
| Element UI | 2.15.x | 桌面端组件库 |
| Axios | 0.21.x | HTTP 请求库,与后端交互 |
| Node.js | 14.x | 前端构建运行环境 |
3. 系统架构设计
系统采用前后端分离架构,前端通过 Axios 调用后端 RESTful API,后端按业务模块分层组织,整体架构如下:
flowchart TD A[Vue前端] -->|Axios / RESTful API| B[Nginx反向代理] B --> C[SpringBoot后端] C --> D[Controller层] D --> E[Service层] E --> F[Mapper层] F --> G[(MySQL)] E --> H[(Redis缓存)] C --> I[JWT认证拦截器] A --> J[Element UI组件]后端采用经典三层架构:Controller 负责接收请求与参数校验,Service 负责业务逻辑处理,Mapper 负责数据库持久化操作。前端按视图组件划分模块,通过 Vuex 管理用户登录状态和全局数据。
4. 数据库设计
系统核心数据表包括用户表、场次表、座位表、订单表和支付记录表,主要表结构如下:
4.1 用户表 t_user
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键,自增 |
| username | varchar(50) | 用户名,唯一 |
| password | varchar(100) | 密码,BCrypt 加密存储 |
| phone | varchar(20) | 手机号 |
| role | tinyint | 角色:0 用户,1 管理员 |
| create_time | datetime | 创建时间 |
4.2 场次表 t_session
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键,自增 |
| title | varchar(100) | 演出/影片名称 |
| start_time | datetime | 开场时间 |
| venue | varchar(100) | 场馆地址 |
| total_seats | int | 总座位数 |
| sold_seats | int | 已售座位数 |
| price | decimal(10,2) | 票价 |
4.3 订单表 t_order
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键,自增 |
| order_no | varchar(32) | 订单编号,唯一 |
| user_id | bigint | 下单用户 ID |
| session_id | bigint | 场次 ID |
| seat_ids | varchar(200) | 座位 ID 集合,逗号分隔 |
| amount | decimal(10,2) | 订单金额 |
| status | tinyint | 状态:0 待支付,1 已支付,2 已取消 |
| create_time | datetime | 下单时间 |
5. 核心功能实现
5.1 用户登录与 JWT 认证
用户登录成功后,后端生成 JWT 令牌返回给前端,前端将令牌存入 localStorage,并在后续请求中通过 Authorization 请求头携带。后端通过拦截器统一校验令牌有效性。
@RestController @RequestMapping("/api/auth") public class AuthController { @Autowired private UserService userService; @Autowired private JwtUtil jwtUtil; @PostMapping("/login") public Result login(@RequestBody LoginDTO dto) { User user = userService.login(dto.getUsername(), dto.getPassword()); if (user == null) { return Result.error("用户名或密码错误"); } String token = jwtUtil.generateToken(user.getId(), user.getUsername(), user.getRole()); return Result.success(new LoginVO(token, user.getUsername(), user.getRole())); } }5.2 场次查询与余票展示
场次列表支持按日期和关键词筛选,查询结果实时计算余票数量。热点场次数据通过 Redis 缓存,降低数据库压力。
@Service public class SessionServiceImpl implements SessionService { @Autowired private SessionMapper sessionMapper; @Autowired private StringRedisTemplate redisTemplate; @Override public List<SessionVO> listSessions(SessionQuery query) { String cacheKey = "session:list:" + query.getDate(); String cached = redisTemplate.opsForValue().get(cacheKey); if (cached != null) { return JSON.parseArray(cached, SessionVO.class); } List<SessionVO> list = sessionMapper.selectByCondition(query); redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(list), 10, TimeUnit.MINUTES); return list; } }5.3 选座与下单
用户选择场次后进入选座页面,前端展示座位图,已售座位置灰不可选。提交订单时后端使用 Redis 分布式锁防止同一座位被重复购买。
@Transactional(rollbackFor = Exception.class) public Order createOrder(Long userId, Long sessionId, List<Long> seatIds) { String lockKey = "lock:session:" + sessionId; boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 5, TimeUnit.SECONDS); if (!locked) { throw new BusinessException("系统繁忙,请稍后重试"); } try { Session session = sessionMapper.selectById(sessionId); int count = seatMapper.countSold(sessionId, seatIds); if (count > 0) { throw new BusinessException("所选座位已被购买"); } String orderNo = generateOrderNo(); Order order = new Order(); order.setOrderNo(orderNo); order.setUserId(userId); order.setSessionId(sessionId); order.setSeatIds(String.join(",", seatIds.stream().map(String::valueOf).collect(Collectors.toList()))); order.setAmount(session.getPrice().multiply(BigDecimal.valueOf(seatIds.size()))); order.setStatus(0); orderMapper.insert(order); seatMapper.batchUpdateStatus(seatIds, 1); sessionMapper.increaseSold(sessionId, seatIds.size()); return order; } finally { redisTemplate.delete(lockKey); } }5.4 前端页面实现
前端使用 Vue 组件化开发,以场次列表页为例,通过 Axios 请求后端接口并渲染数据。
<template> <div class="session-list"> <el-card v-for="item in sessions" :key="item.id" class="session-card"> <h3>{{ item.title }}</h3> <p>时间:{{ item.startTime }}</p> <p>场馆:{{ item.venue }}</p> <p>余票:{{ item.totalSeats - item.soldSeats }}</p> <el-button type="primary" @click="goBuy(item.id)">立即购票</el-button> </el-card> </div> </template> <script> import { listSessions } from "@/api/session"; export default { data() { return { sessions: [] }; }, created() { this.fetchSessions(); }, methods: { async fetchSessions() { const res = await listSessions({ date: "" }); this.sessions = res.data; }, goBuy(id) { this.$router.push({ path: "/buy", query: { sessionId: id } }); } } }; </script>6. 系统测试与总结
系统完成后,对用户注册登录、场次查询、选座下单、订单支付和后台管理等核心模块进行了功能测试,并对高并发下单场景进行了简单压测。测试结果表明,系统各模块功能正常,接口响应稳定,基本满足购票业务需求。
本文设计并实现了一个基于SpringBoot和Vue的前后端分离购票系统,覆盖了用户端购票和管理端维护的核心流程。系统采用 JWT 实现无状态认证,使用 Redis 缓存和分布式锁保障高并发场景下的数据一致性,整体架构清晰、扩展性良好。后续可在此基础上进一步引入消息队列削峰、支付回调对账以及座位分区定价等能力,持续完善系统功能。