news 2026/10/11 15:12:36

Spring Boot + Vue在线电影购票系统:技术选型与并发锁座实战解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Boot + Vue在线电影购票系统:技术选型与并发锁座实战解析

1. 项目概览与技术选型思路

第一次看到“基于Spring Boot + Vue在线电影购票系统”这个名字的时候,我脑子里浮现的其实不只是"又一个管理系统"。在线购票这个场景,比常见的增删改查项目复杂的地方在于:它涉及到电影排片、影厅座位、订单状态、支付流程以及并发锁座这一整条业务链。可以说,它是一个非常典型的"前后端分离 + 复杂业务状态流转"的教学级项目,同时又具备直接改造成商业项目的骨架潜力。

我当时接手这个项目时,手头拿到的是一份完整的源码包,附带数据库脚本和项目文档。整个项目跑起来之后,第一感觉是:结构清晰,模块划分干净,没有把一堆乱七八糟的逻辑堆在Controller里。这一点对于学习者和二次开发者来说,比功能多少更重要。因为能从中学到的不仅仅是"怎么实现一个购票系统",更是"怎么组织一个稍微有点复杂的业务系统"。

这个项目适合谁?我认为主要适合三类人:第一类是正在做毕业设计或者课程设计的在校生,这个项目的完整度和文档质量可以直接作为参考;第二类是准备找Java全栈开发相关工作的人,用它作为简历上的项目,面试时有很多可以深挖的点;第三类是刚接触前后端分离开发模式的初级开发者,想找一个完整的案例来理解Vue和Spring Boot到底是怎么配合的。如果你正处于其中任何一个阶段,这篇文章都值得你花点时间好好看看。

技术选型方面,后端用的是Spring Boot 2.x系列,配合MyBatis Plus做持久层操作,安全认证采用JWT方案;前端使用的是Vue 2 + Element UI,构建工具是Vue CLI;数据库使用MySQL 5.7以上版本。这套组合在目前国内的中小型项目里非常主流。Spring Boot负责把后端服务"跑起来"的复杂度降到最低,MyBatis Plus则把单表CRUD操作简化到几乎不用写XML,而Vue 2 + Element UI的组合在国内的社区生态非常成熟,遇到任何问题都能很快搜索到解决方案。

这里我特别想聊一下为什么选择JWT而不是传统的Session方案。在线购票系统的前端是独立部署的Vue应用,和后端是分离的,这种情况下Session的跨域处理和集群共享都比较麻烦。JWT的无状态特性天然适合这种场景,用户登录成功后拿到一个令牌,后续所有请求都在Header里带上这个令牌,后端只需要验签即可。当然JWT也有它的缺点,比如无法主动失效,但在这个量级的项目中,利远大于弊。

2. 后端设计与核心业务逻辑实现

2.1 从三张核心表出发理解数据模型

在线电影购票系统看起来功能不少,但核心的数据模型其实可以浓缩成三条主线:用户线、影片线和订单线。用户线主要涉及用户表和管理员表,影片线涉及电影表、影厅表、排片表,订单线则涉及订单表和座位表。这三条线之间通过外键关联,构成了整个系统的心脏。

我第一次看这套项目的数据库脚本时,特别留意了排片表和订单表的设计。排片表是整个系统的枢纽,它把电影、影厅、放映时间三者绑定在一起。字段上除了电影ID、影厅ID、开始时间这些常规字段外,还单独设计了一个座位状态字段。这个字段一开始我以为是冗余存储,后来仔细看才发现这是为了锁座功能服务的。订单表则在记录用户购票信息的同时,直接存了座位编号的字符串,用逗号分隔。这个设计不算最优,但在中小型项目里非常实用——查询订单详情时,不需要再额外查座位表,直接解析字符串就能拿到座位信息。

用户表的设计就比较常规了,包含用户名、密码、手机号、邮箱等基础字段。密码存储采用的是加密后的密文,不是明文。这一点我特别想强调一下,很多初学者在做项目时容易忽略密码安全,直接把明文存进数据库里,这在正式项目中是非常严重的安全隐患。这个项目的密码加密策略虽然不算复杂,但对教学来说是合格的。

管理员表就比较简单了,主要是后台登录使用,和用户表分开存储,避免权限混淆。如果你的二开计划里包括后台管理增强,可以考虑引入更完善的RBAC权限模型,把管理员表扩展为角色表和权限表的关联结构。但就当前这个项目的需求而言,单独一张管理员表已经足够。

2.2 排片与锁座的并发处理

购票系统最重要的技术难点不是CRUD,而是并发情况下座位不能超卖。这个项目在锁座处理上做了一层相当不错的方案,值得拿出来单独讲讲。

方案的核心思路是"预占座位 + 订单超时释放"。用户在前端选完座位后,系统会先把座位标记为"已锁"状态,同时创建一个待支付订单,并给这个订单设置一个有效期,比如15分钟。如果用户在有效期内完成支付,订单状态变为已支付,座位状态变为已售出;如果超时未支付,定时任务会把订单置为已取消,同时释放座位。这个设计规避了最让人头疼的问题——用户选好座位后没付款,座位却被一直占着不放。

具体的实现上,关键点在于更新座位状态的SQL语句必须带条件判断,不能直接无条件更新。比如更新座位状态的语句应该判断"当前状态为可售"时才执行更新,而不是直接改成已锁。这样即使多个用户同时抢同一个座位,数据库层面的条件更新也会保证只有一个请求能成功。这个思路在技术上叫"乐观锁",核心就是通过版本字段或者状态条件来避免并发冲突。

不过这个方案也有一个明显的短板:如果用户选了座位但不支付,也不关闭页面,座位就会被占用15分钟。这个时间窗口内,其他用户没法购买同一个座位。在实际项目中,可以通过引入Redis来设置更精准的锁座过期时间,或者使用消息队列做延迟任务来优化。但作为单体项目,这个实现方式已经能够很好地演示"如何解决并发下的数据一致性问题",面试时把这个逻辑讲清楚,非常加分。

2.3 接口设计与JWT认证的落地细节

接口设计上,这个项目遵循了RESTful风格的基本规范。资源路径用名词复数,不同的操作通过HTTP方法来区分。比如获取电影列表是GET /api/movies,创建订单是POST /api/orders,取消订单是DELETE /api/orders/{id}。这样的接口设计虽然不算特别惊艳,但胜在规范,对于前后端分离项目的沟通协作非常有帮助。

我特别留意了接口的统一返回结构。所有接口都返回一个统一格式的JSON对象,包含状态码、消息和数据三个字段。这个看似简单的封装,实际上解决了前后端协作中最大的痛点——前端不用为每个接口单独处理异常情况,只需要在axios的响应拦截器里判断状态码,统一处理业务错误和Token过期,代码会清爽很多。

JWT认证的落地细节,这里给一个关键代码示例,方便你理解整个流程:

// JWT工具类核心逻辑 public class JwtUtil { // 生成Token public static String generateToken(String userId, String role) { return Jwts.builder() .setSubject(userId) .claim("role", role) .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); } // 解析Token public static Claims parseToken(String token) { return Jwts.parser() .setSigningKey(SECRET_KEY) .parseClaimsJws(token) .getBody(); } }

在拦截器层面,项目定义了一个JWT拦截器,对所有需要登录才能访问的接口进行Token校验。校验通过后将用户信息放入请求上下文,业务层通过上下文获取当前用户ID,而不用在每个接口里手动传递用户ID参数。这个设计非常巧妙,一方面减少了重复代码,另一方面避免了用户越权操作——比如A用户尝试修改B用户的订单,系统通过上下文里的用户ID做归属校验,直接拒绝。

当然,JWT方案也有一些需要注意的细节。比如密钥的管理,开发环境可以写死在配置文件中,但生产环境必须通过环境变量或配置中心注入,防止密钥泄露。还有Token过期时间的设置——这个项目设置为24小时,对于购票类场景来说略长,可以根据实际业务调整为2小时加Refresh Token机制。

3. 前端Vue页面结构与交互实现

3.1 页面划分与路由设计

前端部分采用的是Vue 2 + Element UI,整体页面结构可以分为用户端和管理端两条线。用户端包括首页电影列表、电影详情页、选座购票页、订单列表页和登录注册页;管理端包括电影管理、排片管理、影厅管理和订单管理四个主要模块。

路由设计上,用户端和管理端通过不同的路由前缀区分,比如用户端是/home、/detail、/buy等路径,管理端统一挂在/admin前缀下。这样的设计在权限控制上非常清晰——前端通过路由守卫判断当前用户的角色,如果是管理员且访问的是用户端页面,可以正常放行;如果普通用户尝试访问管理端页面,会被重定向到首页。

我比较欣赏的是这个项目在路由懒加载上的处理。所有页面组件都使用了函数形式的import,即component: () => import('@/views/MovieList')这种方式。这样做的好处是首屏加载时不会一次性加载所有页面的JS文件,而是按需加载,显著提升了首屏渲染速度。很多初学者容易忽略这个细节,但实际体验差别非常大。

3.2 列表、选座、下单这条核心链路的交互

用户端的核心操作链路是:浏览电影列表 -> 查看电影详情 -> 选择场次 -> 选择座位 -> 提交订单 -> 支付。这条链路里,选座页面的交互是这个项目前端部分最复杂的模块。

选座页面的实现思路是这样的:后端在返回排片详情时,同时返回该影厅的座位布局和已售/已锁座位列表。前端拿到数据后,通过CSS Grid或者Flex布局渲染一个座位矩阵,不同状态的座位用不同颜色区分——可售座位是绿色,已锁和已售出的是灰色,当前正在选择的座位高亮成橙色。

选座交互的关键在于状态管理。这个项目使用的方案是维护一个本地数组来记录已选座位,同时通过计算属性判断当前选中数量是否超过限购数量(通常是6张)。超过限购数量时,继续点击座位会弹出提示,并且新点击的座位不会加入选座列表。这个小细节虽然不起眼,但非常影响用户体验,我在实测过程中专门试过连续快速点击座位的情况,状态更新没有出现错乱。

下单流程的交互处理也很有代表性。用户确认座位后点击提交订单,前端会先发送一个创建订单的请求,后端在创建订单的同时锁座。这里有一个非常关键的交互设计——创建订单成功后,前端应该立即跳转到支付页面,而不是停留在选座页面等待用户确认。这样可以最大程度减少锁座但未支付的时间窗口,降低座位被无效占用的概率。

代码实现上,选座页面的核心逻辑大致是这样的:

// 选座组件核心逻辑 export default { data() { return { selectedSeats: [], // 已选座位列表 soldSeats: [], // 已售/已锁座位列表 hallLayout: [] // 影厅座位布局 }; }, computed: { // 计算属性:判断当前是否已达到限购数量 isLimitExceeded() { return this.selectedSeats.length >= 6; } }, methods: { // 点击座位的处理函数 handleSeatClick(seat) { const seatId = seat.id; // 已售或已锁的座位禁止点击 if (this.soldSeats.includes(seatId)) { return; } // 判断座位是否已经在选中列表中 const index = this.selectedSeats.indexOf(seatId); if (index >= 0) { // 取消选中 this.selectedSeats.splice(index, 1); } else { // 达到限购数量时阻止继续选座 if (this.isLimitExceeded) { this.$message.warning('单次最多购买6张电影票'); return; } // 加入选中列表 this.selectedSeats.push(seatId); } }, // 提交订单 async handleSubmit() { if (this.selectedSeats.length === 0) { this.$message.warning('请先选择座位'); return; } const res = await createOrder({ screeningId: this.screeningId, seatIds: this.selectedSeats }); // 跳转支付页 this.$router.push(`/pay/${res.data.orderId}`); } } };

这个实现有一个值得注意的点:创建订单时,前端传递的是座位ID列表,而不是座位编码。这样设计的好处是后端可以根据座位ID直接锁定对应的数据库记录,避免通过"排片ID + 座位编码"组合条件查询时可能出现的索引失效问题。虽然代码上只差了一点,但对性能和准确性都有影响。

3.3 axios封装与接口层解耦

这个项目在前端接口封装上做得比较规范,api目录下面按照业务模块拆分了多个文件,比如movie.js、order.js、user.js等。每个文件只负责对应的接口调用,组件内部不直接写axios请求,而是通过import这些模块中的方法来完成数据交互。这样的分层设计,在后期维护时优势明显——接口地址变了只需要改一个文件,组件里完全不用动。

axios的封装方面,项目在request.js里做了统一处理。请求拦截器负责在每次请求的Header中注入Token,响应拦截器负责统一处理返回数据。特别是Token过期的情况,响应拦截器里会判断HTTP状态码,如果是401,直接清除本地存储的用户信息并跳转到登录页。这个逻辑虽然简单,但避免了在每个页面里重复处理"登录状态已失效"的尴尬情况。

我自己实测下来的感觉是,这套接口封装思路非常值得初学者学习。很多自己写项目的朋友,习惯于在组件里直接写this.$http.get('/api/xxx'),短平快,但项目一复杂起来,接口散落在各个组件里,改一个接口地址就要全局搜索,效率极低。通过api目录统一管理接口,配合Vuex或者Pinia管理跨页面共享的数据,项目的可维护性会提升一个档次。

4. 数据库设计要点、数据脚本与性能优化

4.1 建表脚本与字段设计详解

打开项目附带的数据库脚本文件,你会看到整个系统的表结构。我按照业务关系的紧密程度,把这些表分成了三组来理解:

第一组是基础的用户信息表,包含用户表和管理员表。用户表的核心字段包括用户名、密码密文、手机号、邮箱和创建时间。这里有一个好习惯——所有表都包含create_time和update_time两个时间字段,create_time在插入数据时自动填充,update_time在记录更新时自动刷新,这个用MyBatis Plus的自动填充功能就能实现,非常方便。

第二组是影片资源表,包含电影表、影厅表和排片表。电影表字段包括片名、海报URL、导演、主演、类型、上映日期、片长和剧情简介。影厅表则包括影厅名称、座位行数、座位列数、座位总数等字段。排片表的设计是整个系统的亮点,它通过movie_id、hall_id、start_time三个字段建立了影片与场次的对应关系。

第三组是交易类表,包含订单表和座位表。座位表和排片表是一对多的关系,每个排片场次下有一组座位记录,每条记录用seat_row、seat_col来标记具体的座位位置,用status字段标记状态。这里我要特别说明一下,有些项目会把座位设计成"影厅下的一组固定座位",场次出来时再复制一份。但更常见的设计是"座位属于某一场次",即排片创建时同时生成该影厅布局下的全部座位记录。两种方案各有优劣,但就这个项目而言,直接关联场次的方式在锁座和查询时性能更好,因为不需要做影厅座位和场次座位的关联映射。

订单表的核心字段包括订单号、用户ID、排片ID、座位ID列表(逗号分隔)、总金额、状态和创建时间。订单状态一般包括待支付、已支付、已取消和已退款。我用一个表格来总结核心表的关系:

数据表核心作用关联关系
movie(电影表)存储影片基础信息被排片表引用
hall(影厅表)存储影厅布局参数被排片表引用
screening(排片表)将电影与影厅、场次时间绑定关联movie和hall
seat(座位表)存储某场次下的座位状态关联screening
orders(订单表)存储购票订单信息关联user和screening

这个表结构设计有一个优点:查询一张订单的完整信息时,可以通过订单表里保存的排片ID和座位ID列表,一次性查出电影信息、影厅信息、场次时间和座位区域,不需要做太复杂的多表关联。虽然看起来有点反范式,但在业务查询上非常高效。

4.2 索引策略与常见SQL性能陷阱

数据库性能问题在这个项目中主要集中在一类场景:热门电影的排片查询和座位查询。以选座请求为例,当用户点击某个场次准备选座时,后端要查出该场次下的全部座位信息并标示状态。如果座位表数据量较大,没有合适的索引,这个查询会非常慢。

项目中主要的查询场景有三种。第一种是根据电影ID查排片列表,这种情况应该在screening表的movie_id字段上建索引。第二种是根据排片ID查座位列表,screening_id字段就是关键的索引列。第三种是查询用户的历史订单,user_id字段需要建立索引。把这三个索引加上之后,大部分查询都能走索引,性能问题基本就解决了。

但仅仅建了索引还不够,我在实际测试中发现一个比较隐蔽的性能陷阱。项目的座位查询SQL如果写成SELECT * FROM seat WHERE screening_id = ?这种形式,在座位数据量增长到一定规模后,即使走了索引,也会因为返回的字段数量过多而导致网络传输时间变长。正确的做法是只返回需要的字段:座位ID、排片ID、座位行号、座位列号和状态,海报和电影名称这些信息完全可以在列表查询时通过关联查出来,不需要在座位表里冗余存储。

另外,项目的订单查询里有一个常见问题——订单号字段存储的是字符串类型,高频地用在WHERE条件中。这里我建议即使订单号已经创建了唯一索引,也要避免在订单号前面使用前缀模糊查询,比如WHERE order_no LIKE '%abc%'这种写法是走不了索引的。如果确实需要模糊搜索订单号,可以考虑使用全文索引或者分词搜索引擎,但在当前项目的体量下,普通单表查询加合理索引已经足够了。

定时任务释放过期未支付订单时,项目用的是定时查询WHERE status = 'TO_PAY' AND create_time < NOW() - INTERVAL 15 MINUTE的方式。这种方式在小数据量下没问题,但这种全表扫描加时间比较的方式在高并发场景下会引发死锁问题。改进方案是先查询出需要取消的订单ID列表,再通过主键批量更新,而不是一个UPDATE语句直接更新所有符合条件的记录。虽然代码上多了两步,但大大降低了锁冲突的概率。

5. 部署运行步骤与前端联调要点

5.1 本地环境准备与初始化

我要先说明一下,这个项目的运行环境我在本地搭建时使用的是Windows 10系统,搭配JDK 1.8、Maven 3.6、MySQL 5.7和Node.js 14.x。如果你的环境和这个不完全一致也不用担心,这套技术栈的兼容性很好,只要版本差不太多都能正常跑起来。

第一步是准备数据库。拿到项目的SQL脚本文件后,我建议你使用Navicat或者命令行工具执行脚本。执行前需要注意,脚本文件里如果有创建数据库的语句,你要确保MySQL当前的账号有相应权限。执行成功后,你会看到整个数据库的完整表结构和初始数据。这里我特别提醒一下,初始数据非常关键——没有预设的电影、影厅和用户数据,前端页面显示出来就是空的,你还需要手动录入,这必然会影响你第一时间体验完整流程。

接下来是启动后端服务。用IDE打开Spring Boot后端项目,等待Maven自动下载依赖,然后修改application.yml配置文件中的数据库连接信息。这里常见的一个坑是数据库账号密码不一致,导致启动时报错。启动成功的标志是控制台输出Spring Boot的启动日志,并显示服务端口号,默认是8080。

然后是前端部分。命令行进入前端项目目录,执行npm install安装依赖。这一步如果网络不好,可能需要较长时间,建议配置国内npm镜像源来加速。依赖安装完成后,执行npm run serve启动开发服务器。默认端口通常是8081——这个端口和后端的8080需要区分开来,因为前端配置了代理转发,所有/api开头的请求都会被转发到后端8080端口。

Vue项目的代理配置在vue.config.js文件中。核心配置代码如下:

// vue.config.js 中的代理配置 module.exports = { devServer: { port: 8081, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } };

这个代理配置解决了开发环境下的跨域问题。前端项目运行在8081端口,后端接口在8080端口,浏览器直接跨端口请求会被浏览器拦截,但通过代理转发,所有请求先到达8081的dev server,再由dev server转发到8080,浏览器只跟8081通信,跨域问题就自然解决了。

5.2 生产环境部署的正确姿势

如果要把项目部署到一台正式服务器上,就不能使用npm run serve这种方式了。前端需要执行npm run build进行打包,生成静态文件到dist目录,然后把dist目录里的全部文件拷贝到Nginx的html目录下,同时配置Nginx把/api路径的请求反向代理到后端端口。

Nginx的配置大概长这样:

server { listen 80; server_name your-domain.com; # 前端静态资源 location / { root /usr/share/nginx/html; index index.html; # 前端路由使用history模式时需要此配置 try_files $uri $uri/ /index.html; } # 后端接口反向代理 location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }

这里面try_files $uri $uri/ /index.html;这行配置非常关键。Vue Router如果使用了history模式(即URL路径不包含#号),刷新页面时Nginx会直接按照URL路径去找对应的静态文件,结果找不到,返回404。加入这行配置后,Nginx会在找不到文件时回退到index.html,由前端路由接管URL,问题就解决了。如果你看到部署后刷新页面报404,几乎都是因为缺少这个配置。

后端部署时有一个细节要注意:Spring Boot项目打成jar包后,配置文件里的数据库连接地址需要改成服务器的真实IP,同时要将数据库账号密码配置在环境变量中而不是写死在配置文件里,这样更安全,也方便不同环境间切换。项目还有一个需要留意的地方——文件上传路径。电影海报的保存地址在生产环境里应该配置成服务器上的绝对路径,同时通过Nginx映射成可访问的URL,否则前端页面上的海报图会加载失败。

6. 部署上线后踩过的典型问题与排查思路

6.1 下单并发导致座位超卖问题

第一次部署上线后,我找了几位朋友帮忙并发测试,结果还真测出了超卖问题。两个用户同时点击同一个场次的同一个座位,系统居然都显示下单成功了。排查后发现,问题出在座位状态的更新逻辑上。

我们来看这段有问题的伪代码:

// 错误示例:先查询再更新,存在竞态条件 Seat seat = seatMapper.selectById(seatId); if ("AVAILABLE".equals(seat.getStatus())) { seat.setStatus("LOCKED"); seatMapper.updateById(seat); }

代码的逻辑是先查询座位状态,判断是否可售,然后更新。问题是判断和更新之间有一个时间窗口,两个并发请求同时执行到if判断这一步时,都会看到座位状态是"可售",于是两个请求都成功更新了座位。解决方法是把判断放进SQL语句里,让数据库保证原子性:

// 正确写法:条件更新,数据库原子操作 int count = seatMapper.updateSeatStatus(seatId, "LOCKED", "AVAILABLE"); if (count == 0) { throw new SeatLockException("座位已被锁定"); }

updateSeatStatus方法的SQL类似UPDATE seat SET status = 'LOCKED' WHERE id = ? AND status = 'AVAILABLE',只有当座位当前状态确实是"可售"时,更新才会成功。数据库层面的行锁保证了这个操作是原子的,不会出现两个请求同时成功的情况。排查这个问题时,我在日志里详细记录了两个请求的时间戳和SQL执行情况,最终确认了竞态条件的存在。后来修复后再次压测,超卖问题不再出现。

6.2 前端历史模式刷新404问题

这个问题我们前面提到过,这里详细说一下具体的排查过程。前端部署到Nginx后,用户在首页点击进入电影详情页,再刷新页面,结果直接显示404。出现这个问题的原因很简单:Vue Router的history模式下,URL路径是真实存在的"假"路径,比如/detail/123,刷新时浏览器会向Nginx请求/detail/123这个路径对应的资源,而Nginx的静态文件目录里根本没有这个路径,自然就404了。

排查方法很简单,先在浏览器开发者工具的Network面板里查看页面刷新时请求了哪个URL,再检查Nginx的error.log日志,确认请求被打到了哪个location。定位问题后,在Nginx的配置中加上try_files $uri $uri/ /index.html;就可以了。

这个问题的本质是"前端路由与后端静态资源服务的冲突"。解决方案不仅限于Nginx配置,如果使用Tomcat部署前端资源,也需要在Tomcat的web.xml里配置类似的错误页面转发。对于这个问题,我还想多说一句:开发环境下不会出现这个情况,因为Vue CLI的dev server已经内置了history模式的回退处理,所以很多开发者直到部署到生产环境才发现这个坑。建议你从项目一开始就把部署问题纳入考虑,不要在最后一刻才发现。

6.3 数据库连接池耗尽导致系统假死

这个问题是在我连续多天没有重启服务后遇到的。某天早上打开系统,首页加载非常慢,部分接口直接报超时错误。查看日志后发现,大量线程阻塞在获取数据库连接的操作上,连接池连接全部耗尽。

产生这个问题的原因有几个可能的方向。首先检查了是否有慢SQL——某些SQL执行时间过长,占用了连接不释放。其次检查了是否有连接泄漏——代码中某些查询路径没有正确关闭连接。在Spring Boot + MyBatis Plus的组合中,连接通常由连接池统一管理,框架会自动回收,但如果使用了多线程并手动获取连接,就很容易出现连接不归还的情况。

排查时我用了几个方法。首先在数据库里执行SHOW FULL PROCESSLIST,查看当前被占用的连接都在执行什么SQL。如果看到大量长时间挂起的查询,优先排查慢SQL;如果连接全部处于Sleep状态,就要怀疑是业务代码里的连接泄漏。然后在Spring Boot的配置文件中调整连接池参数:

spring: datasource: hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 30000 idle-timeout: 600000

这些参数的调整思路是:maximum-pool-size控制最大连接数,不宜过大,MySQL默认最大连接数是151,如果连接池设置成100,再加上后台任务的连接,很容易把数据库连接耗尽;connection-timeout控制获取连接的等待时间,如果超过30秒还没拿到连接,直接报错比无限阻塞更好,可以快速暴露问题。修复了连接泄漏的代码后,这个问题就消失了。后来我把连接池参数调整到合理的范围,并在代码中加入了连接使用后的归还逻辑,系统一直稳定运行。

6.4 常见问题速查表

为了让你后续排查问题时更方便,我把项目中比较常见的几个问题整理成一个速查表:

问题现象可能原因排查手段与解决方案
前端页面能打开,接口全部报网络错误代理配置不正确,请求未转发到后端检查vue.config.js里的proxy配置和target地址
接口返回401 UnauthorizedToken过期或未在请求头中携带Token检查request.js的请求拦截器和本地存储的Token是否有效
图片加载失败文件上传路径与实际保存路径不一致检查配置文件中的文件目录和Nginx静态资源映射
下单后座位仍然显示可售座位状态更新逻辑存在竞态条件改用条件更新SQL,把状态判断放在UPDATE语句里
页面刷新后404Nginx缺少history模式回退配置在location /中添加try_files回退到index.html
系统运行数天后接口响应变慢数据库连接池耗尽或存在连接泄漏使用SHOW FULL PROCESSLIST查看连接状态,调整连接池参数
控制台报端口被占用错误8080或8081端口已被其他程序使用使用命令查看并更换占用进程的端口
数据库乱码,中文显示为问号连接字符集配置不正确检查JDBC连接URL是否包含characterEncoding=utf8参数

这八个问题覆盖了从开发到部署的常见场景。如果你想把这个项目作为面试项目来准备,我强烈建议你亲手去制造并解决其中至少两个问题,比如故意去掉条件更新SQL,然后写一个并发测试脚本,观察超卖现象;或者把Nginx的try_files注释掉,刷新页面看404报错。把这些问题的排查过程讲清楚,面试官会觉得你是一个真正写过项目、有实战经验的人,而不是停留在"能跑通教程"的水平上。

根据我个人的实际体会,这个购票系统项目是在课程设计和毕设选题中可塑性比较高的方向。核心业务逻辑的复杂度恰到好处,既不会因为太简单而显得没有含金量,也不会因为过于复杂而超出学习范围。你在学习时可以将它视为一个微缩版的商业项目,用生产标准要求自己,从数据库设计到接口规范,从前端交互到部署策略,每一层都能学到实战经验。如果时间允许,我推荐你在此基础上尝试一些扩展,比如引入Redis做分布式锁、引入消息队列处理订单超时通知、增加座位偏好选座等功能,每增加一项,你对这个项目的理解就会深一层,这个项目给你带来的回报也会翻一倍。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/11 15:12:26

江苏全合成水基水溶性切削液制造厂家 南通城炜金属处理剂行业现状与选择指南

江苏全合成水基水溶性切削液制造厂家南通城炜金属处理剂行业现状与选择指南对于长三角的机加工企业而言&#xff0c;选对一款切削液&#xff0c;往往意味着刀具成本、废液处置、人工换液三大开支的同时优化。近年来&#xff0c;江苏全合成水基水溶性切削液制造厂家数量持续增长…

作者头像 李华
网站建设 2026/10/11 15:10:40

Excel转Lua工具实战:从配置表映射到构建流程的避坑指南

简介&#xff1a;Excel转Lua工具包是一套面向游戏开发与配置管理场景的实用转换方案&#xff0c;能把结构化的Excel表格批量导出为Lua脚本&#xff0c;减少手动整理数据的重复劳动&#xff0c;尤其适合数据驱动且以Lua为主要脚本语言的中小型项目。压缩包内共4个文件&#xff0…

作者头像 李华
网站建设 2026/10/11 15:10:08

PSO优化CNN超参数:粒子群算法实战指南

简介&#xff1a;这份资源围绕PSO优化卷积神经网络模型参数展开&#xff0c;面向深度学习入门者与需要调参实践的开发者&#xff0c;针对CNN收敛速度较慢、易过拟合以及超参数依赖人工经验等问题&#xff0c;给出用粒子群算法自动寻优的完整实现思路。包内共12个文件&#xff0…

作者头像 李华
网站建设 2026/10/11 15:09:15

RAR归档实战:分卷、校验与增量更新方案

简介&#xff1a;这份资源面向GIS从业者、水文研究者及地理信息相关专业学生&#xff0c;提供黄河流域河网水系的矢量数据&#xff0c;可用于流域分级分析、水文建模与空间制图等场景。压缩包共49个文件&#xff0c;以shp矢量文件为核心&#xff0c;配套dbf属性表、shx索引、pr…

作者头像 李华
网站建设 2026/10/11 15:08:02

2026陇南景区古建牌坊检测排名 TOP5 CMA 资质机构提供牌坊裂缝检测、牌坊倾斜检测、老化检测 联系方式推荐

在众多本地古建牌坊检测机构中&#xff0c;陇南古坊文保结构检测有限公司综合实力拔群出众&#xff0c;其检测报告精准可靠&#xff0c;深受住建与文物部门信赖。紧随其后的陇南宸古石牌楼安全研究院&#xff0c;在石质牌坊材质风化专项检测领域独树一帜&#xff0c;技术底蕴深…

作者头像 李华
网站建设 2026/10/11 15:07:38

造价软件加密锁驱动590/592与S4型2.4写锁工具安装避坑指南

简介&#xff1a;面向广联达深思S4 2.4软件用户的写锁工具包&#xff0c;主要针对安装590/592驱动、需在较长时间内稳定使用广联达与广材助手的工程造价、招投标及项目管理相关人员。工具通过写锁与锁号生成机制&#xff0c;可延长软件授权至2040年&#xff0c;适配老版本驱动环…

作者头像 李华