基于SpringBoot的展会门票系统设计与实现:从架构到高并发实战
2026/9/16 7:37:30 网站建设 项目流程

每年到了毕设选题的节点,总有一批同学在“做什么题目”上反复横跳。我的建议一直很明确:如果你想要业务场景完整、技术栈有含金量、演示效果好、答辩有的聊,又不想去卷那些烂大街的“图书管理系统”,基于 SpringBoot 的展会门票系统是一个非常稳妥的选择。这个题目做出来的东西叫“会展电子票务管理平台”,从用户注册登录、展会浏览、在线购票、预约入场到后台票务统计,一整条业务链路全都覆盖到了,天然就是一个能拿得出手的完整项目。这篇文章我就把这个系统从设计到落地的完整思路扒开来讲,包括技术选型、核心模块、数据表设计、关键功能实现,外加我自己在这类项目里踩过的坑和答辩时容易被追问的问题,希望能帮你少走弯路。

1. 项目整体设计与需求拆解

1.1 这个系统到底解决什么现实问题

传统线下展会买票,基本还是人工窗口排队、纸质票验票那套流程。我自己去逛过几次展会,印象最深的就是入场高峰期,人工核验一张纸质票得十几秒,队伍能弯好几道弯。而对主办方来说,纸质票存在黄牛囤票、假票泛滥、入场数据无法实时统计这些老大难问题。展会门票系统的核心价值,就是把这些线下流程搬到线上,做成“在线购票 + 电子票二维码 + 分时段预约入场 + 现场扫码核销”的闭环。

拆解一下,这套系统要解决的业务痛点其实很清晰:

  • 买票难:用户不用再去现场排队,在线就能选展会、选票种、支付拿电子票。
  • 入场慢:通过分时段预约,把观众的入场时间错峰分散,减轻现场排队压力;现场扫码核销,单人次核销时间能压缩到两三秒。
  • 管理乱:主办方在后台可以实时查看各时段预约人数、各票种售出情况、每天的入场核销数据,方便提前安排安保和运营资源。
  • 防假防黄牛:电子票绑定用户身份和订单信息,配合二维码一单一码,基本杜绝纸质假票;配合每人限购规则,也能在一定程度上抑制囤票行为。

这些痛点就是整个系统的需求来源。做毕设的时候,不要只是抄一个项目功能列表,先想清楚每个功能是解决什么问题的,写文档、画原型、做答辩 PPT 的时候才知道怎么把故事讲圆。

1.2 为什么这个题适合作为计算机毕业设计选题

计算机毕业设计选题最怕两件事:一是题目太泛,不知道从哪里下手;二是题目太窄,技术含量不够,撑不起一篇论文。展会门票系统恰恰避开了这两个极端。

先说业务完整性。这个系统天然分为用户端和管理端两个视角,用户端涉及注册、登录、检索、下单、支付、预约、证照展示,管理端涉及会展管理、票种配置、订单处理、核销终端、数据报表。两端加起来十几个功能模块,论文的“需求分析”“系统设计”“功能实现”章节根本不愁没东西写。

再看技术覆盖面。一个合格的 SpringBoot 项目需要用到的东西,这个系统几乎都能沾上:SpringBoot 做后端框架、MyBatis-Plus 或 Spring Data JPA 做持久层、MySQL 存业务数据、Redis 做缓存和分布式锁、JWT 做用户认证、ZXing 生成二维码、定时任务处理超时订单、WebSocket 做入场通知,如果再把前端用 Vue 搭一套出来,前后端分离的项目经验也有了。这些技术点写进简历,每个都能在面试时展开聊几句。

最后是演示效果好。毕设答辩的时候,系统能跑出什么效果直接影响老师的第一印象。这类票务系统演示起来非常直观:用户端买一张票,生成带二维码的电子票,然后到“模拟入场”页面扫码核销,后台大屏数据实时变。评委老师一眼就能看懂你在做什么、做完了没有。

2. 技术选型与核心方案设计

2.1 后端框架:为什么非 SpringBoot 不可

现在做 Java 方向的毕设,SpringBoot 几乎是默认选项,但很多人只是跟着教程建了个工程,并没有想清楚它到底解决了什么问题。理解这一点,不光是为了写论文,更是为了应付答辩时老师的追问。

Java Web 开发在 SpringBoot 出现之前,SSM(Spring + SpringMVC + MyBatis)是绝对的主流。用 SSM 建一个工程,你得自己配置 web.xml、Spring 容器、SpringMVC 的 DispatcherServlet、数据源、事务管理器、MyBatis 的 SqlSessionFactory,一堆 XML 配置文件能让新手写到怀疑人生。SpringBoot 的核心思路是“约定大于配置”,它通过自动装配机制把那些繁琐的配置全部封装好了。

以我们项目为例,引入spring-boot-starter-web后,内嵌的 Tomcat、SpringMVC 的自动配置、JSON 序列化这些全部都给安排好了。我再也不用去想“DispatcherServlet 怎么映射”这种问题,直接写 Controller 就能跑起来。这就是 SpringBoot 对生产力的最大解放——框架的配置交给框架,业务的代码留给开发者。

这里多说一句自动装配的原理,因为这是 SpringBoot 面试题里的高频考点,答辩时也十有八九会问到。SpringBoot 在启动时会通过@EnableAutoConfiguration注解,结合META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件(2.7 之前是spring.factories),加载所有候选的自动配置类。每个配置类上都有@ConditionalOnClass@ConditionalOnProperty这类条件注解,意思是“类路径下有这个依赖我才配置,配置项里开了这个开关我才生效”。比如你引入了spring-boot-starter-data-redis,类路径下有了RedisTemplate相关的类,RedisAutoConfiguration 才会生效,帮你自动创建一个连接工厂和操作模板。理解了这个机制,后面排查“为什么我的配置没生效”这类问题,思路会清晰很多。

2.2 持久层和数据存储选型

数据存储层面,这个项目的标配组合是 MySQL + Redis。

MySQL 负责所有结构化业务数据的持久化存储:用户信息、展会信息、票种、订单、电子票、核销记录,这些都是典型的关系型数据,用 MySQL 的表来管理最合适。ORM 框架我推荐 MyBatis-Plus,理由很直接:单表 CRUD 不用写 SQL,自带分页插件,对于毕设这种规模的项目开发效率极高。如果你对 JPA 更熟也用 JPA,但 MyBatis-Plus 的代码生成器可以一键生成 entity、mapper、service、controller,做毕设能省下大量重复劳动。不过有一点要注意,MyBatis-Plus 只是帮你省了单表 CRUD 的代码,多表关联查询(比如查某笔订单的详情,关联展会、票种、用户三个表)还是要手写 SQL,而且这时候手写 SQL 反而更清晰。所以我的建议是:简单查询用 MyBatis-Plus 的QueryWrapper,复杂统计和关联查询用 XML 里写的自定义 SQL,两个结合着来。

Redis 在这个系统里有三个用途。第一个是缓存热点数据,比如展会的详情信息、票种的剩余库存,这些数据被访问的频率非常高,每次都查 MySQL 压力太大,缓存到 Redis 里能显著提升接口响应速度。第二个是存储验证码和登录 Token(如果用 JWT 的话,Token 本身可以由客户端持有,但服务端可以用 Redis 做登录状态管理或黑名单控制)。第三个是解决高并发场景下的库存扣减问题,后面第四章会详细展开。另外,很多同学做毕设时不装 Redis,代码里也绕开它,这样其实是把项目最大的一个亮点给砍掉了,不太建议这么干。

2.3 前后端交互与认证方案

前后端交互方式,我建议直接采用前后端分离的 Restful API 设计。后端只提供 JSON 接口,前端工程单独用 Vue 3 + Element Plus 搭一套管理后台。这么做有两个好处:一是技术栈覆盖面更广,简历上能同时写后端开发和前端开发的经验;二是答辩演示时可以直接用浏览器访问前端页面,视觉效果比 Postman 里点接口美观得多。

关于用户认证,方案上强烈建议用 JWT,而不是传统的 Session。原因有两个。第一,前后端分离架构下,前端可能部署在一台服务器,后端部署在另一台,Session 天然存在跨域共享的问题,而 JWT 是无状态的,Token 由客户端保存,服务端只需要验签就能确认用户身份,天然适合这种场景。第二,JWT 是当前企业级项目的主流方案,面试聊到“登录认证怎么做”时,JWT 几乎是必考题。

JWT 的完整链路过一遍:用户提交用户名密码,后端校验通过后生成一个 Token(包含用户 ID、过期时间,用密钥签名),返回给前端;前端在后续请求的 Header 里带上Authorization: Bearer <token>;后端用一个拦截器或 Spring Security 过滤器解析 Token,校验签名和过期时间,把用户信息塞到请求上下文里供 Controller 使用。这个流程代码量不大,但涉及拦截器、注解、工具类等多个环节,建议自己动手写一遍,答辩时能把每个环节的职责讲清楚是非常加分的。

3. 核心功能模块与数据表设计

3.1 用户端和管理端的功能全景

先梳理一下整个系统的功能菜单,方便后续做模块拆分和数据表设计时有全局视角。

用户端(普通观众使用):

  • 用户注册与登录(支持手机号 + 密码方式,可选短信验证码登录)
  • 展会列表浏览与关键词搜索,展会详情页展示主办方信息、举办时间、场地、票种列表
  • 在线选票与下单,支持多张票一次购买,生成订单
  • 模拟支付(对接真实的支付宝/微信支付需要企业资质,毕设项目一般做成模拟支付,点击按钮直接回调成功)
  • 电子票夹,展示已购票的二维码,支持下载和查看票面信息
  • 入场预约,选择具体的入场日期和时段(分上午场、下午场之类)
  • 个人中心维护个人信息和修改密码

管理端(展会主办方工作人员使用):

  • 仪表盘数据概览,包括总销售额、总订单量、今日入场人数、各展会门票销量排行
  • 会展管理,维护展会基本信息、上线下线状态
  • 票种管理,维护标准票、早鸟票、VIP 票等票种的价格、库存、售卖时间
  • 订单管理,查询所有订单,处理退款
  • 核销管理,通过扫码枪或手机摄像头扫描观众电子票二维码完成入场核销
  • 用户管理,查看注册用户列表,禁用异常账号

这个功能列表本身并不复杂,我现在把用户端和管理端拆开列出来,是为了让你清楚两端的职责边界。很多同学做一个模块做一个模块,做着做着就忘了哪个接口是给谁用的,导致权限乱成一锅粥。建议在需求分析阶段就把两端的功能清单定下来,再往下做接口设计和数据库设计就有的放矢了。

3.2 数据库表设计,七张核心表一次讲清

数据库设计是毕业设计论文的重要章节,也是评审老师重点看的部分。这个系统的数据表我建议至少设计以下七张:

表名核心字段设计要点说明
user(用户表)id, username, password, phone, nickname, avatar, status, create_timepassword 必须加密存储,推荐 BCrypt;status 用于账号禁用,值为 0 或 1
exhibition(展会表)id, title, cover, address, start_date, end_date, start_time, end_time, organizer, description, status, create_timestatus 表示展会上线下线状态;时间和日期字段要分清,同一个展会每天有不同的开始与结束时间
ticket_type(票种表)id, exhibition_id, name, price, stock, limit_per_user, sale_start, sale_end, statusexhibition_id 关联展会;stock 是库存;limit_per_user 表示单人限购张数,这是防囤票的抓手
orders(订单表)id, order_no, user_id, exhibition_id, ticket_type_id, quantity, total_amount, status, pay_time, create_timeorder_no 唯一,建议用“日期 + 随机数”生成;status 用数字表示状态,建议状态机设计:0 待支付、1 已支付、2 已取消、3 已退款
e_ticket(电子票表)id, order_id, user_id, exhibition_id, ticket_code, status, entry_timeticket_code 是每张独立的电子票编码,同一个订单下买三张票就对应三条电子票记录,每条记录一个唯一二维码
reservation(预约记录表)id, ticket_id, user_id, exhibition_id, reservation_date, time_slot, status同一张票只能有一条有效预约记录;time_slot 用 0 表示上午场 1 表示下午场这类枚举值
checkin_record(核销记录表)id, ticket_id, user_id, exhibition_id, checkin_time, operator_id核销后插入一条记录,同时回写电子票表的 status 和 entry_time,两张表配合防止一张票多次入场

这张设计里有两个地方值得特别注意。第一,订单表和电子票表是“一对多”的关系,一个订单可以包含多张票,这更贴近现实中“一次性买三张票、三个人一起去”的场景,也符合数据库第三范式的设计规范。第二,预约记录和核销记录单独建表,而不是在电子票表上直接加两个字段,好处是可扩展性——假如未来要支持一次预约改签,有独立表记录历史变更就方便多了。

对于毕设项目用这些表,字段不算多,关联关系清晰,画 ER 图也好看。不过我只列了核心字段,实际开发中还要根据业务补充字段,比如订单表可以加一个 address 字段用于邮寄实体票(如果支持的话)。安全方面,我自己的习惯是给每张表都加上 create_time 和 update_time 两个审计字段,后面排查数据问题时会救命。

3.3 订单状态机与流程闭环

数据库表设计完之后,非常重要的一步是把订单状态流转图画清楚。这不仅是论文里的图,更是代码里状态判断的依据。

订单的状态机我是这么设计的。用户提交订单后,订单落到“待支付”;用户完成支付后,系统回调修改为“已支付”,同时生成对应的电子票,此时用户的电子票夹里就能看到票了;如果用户在 15 分钟内没有支付,系统通过定时任务把订单自动置为“已取消”,同时把锁定的库存加回去;已支付的订单在未核销前,用户可申请退款,管理员通过后订单变为“已退款”,对应电子票作废;如果票已经核销过了,订单就不允许再退。

这个流程用文字描述很简单,但代码里涉及到分布式事务的配合要小心。这个项目里一个比较典型的场景是:支付成功回调 → 修改订单状态 → 生成电子票 → 扣减库存,这几个操作在同一个事务里完成是没问题的,因为票务库存扣减发生在购买时,而不是支付时才扣。所以正确做法是:用户提交订单时先锁定(扣减)库存,支付超时取消时再把库存加回去,支付成功后不再动库存。如果你在支付回调时才去扣库存,就会出现超卖——两个人同时下单,都以为自己是最后一个成功买家。这个逻辑我想特别提示一下,因为不少同学在这个环节会绕进去。

4. 关键功能实现要点与难点攻关

4.1 在线购票与模拟支付回调实现

在线购票是用户端的核心流程,涉及交易的敏感操作,代码实现上要按“创建订单 → 支付 → 回调通知 → 发放电子票”四个环节去做,每一步都有自己的特殊性。

创建订单接口做的事情比较机械:校验用户登录态、查票种信息、校验库存是否充足、计算总价、生成唯一订单号、插入订单记录。唯一一个需要动脑子的是库存扣减,这个放在本章第三节专门讲。

模拟支付这里要做一个“回调”的动作。因为毕设没有条件接微信支付宝官方支付,所以我们约定一个通用方案:前端在订单详情页点“立即支付”,后端收到请求后直接调用一个“支付成功回调函数”,把订单状态从待支付改为已支付,然后生成电子票。这个回调函数实际上是模拟了真实支付网关向你系统发起的异步通知——真实的支付流程中,支付平台扣款成功后会向你的回调地址发一个 HTTP 请求,告诉你“这笔订单已经支付成功了”,你的系统收到通知后才会把订单状态改掉。

我建议把模拟支付的代码放在一个独立的 Service 方法里,并加上@Transactional注解,方法里依次执行:更新订单状态为已支付、为订单里每张票生成唯一的 ticket_code 和二维码图片、更新票种表的已售数量。这里的事务性非常重要——要么全部成功,要么全部回滚,绝不能出现“钱扣了票没发”或者“票发了钱没扣”的中间状态。

4.2 电子票二维码生成与扫码核销

电子票是这个项目的脸面,做成什么效果直接影响演示体验。二维码我用的是 ZXing 库生成,核心代码就几行:

// 依赖: com.google.zxing:core 和 javase BitMatrix bitMatrix = new QRCodeWriter().encode(ticketCode, BarcodeFormat.QR_CODE, 300, 300); MatrixToImageWriter.writeToStream(bitMatrix, "png", outputStream);

关键在于 ticket_code 的生成策略。我的方案是:UUID 去掉横线 + 随机字母数字串,比如8f3a2c9e6b1d4ef185d2074f9a3c1b7e,32 位长度,全局唯一。不要用自增 ID 去生成二维码,因为那会被人轻易伪造,扫一下就进不来了。

然后是扫码核销接口的设计。场馆入口的核销设备对着观众手机上的二维码扫一下,后端接收两个参数:ticket_code 和操作员 ID。核销逻辑拆成下面几步:

  1. 根据 ticket_code 查电子票记录,查不到直接返回“无效票”
  2. 检查电子票状态,如果 status 已经是 1(已入场),返回“该票已使用”
  3. 校验当前时间是否在展会入场时间段内,非入场时间返回提示
  4. 校验该票是否有当天的入场预约记录,无预约不允许入场(这是“预约入场”模式的核心规则)
  5. 以上全部通过,将电子票状态置为已入场,写入核销记录表

这里有一个高并发的隐患:两个闸机同时扫同一张票,可能同时读到“未使用”状态,然后都执行入场成功。实际项目中我的处理方案是给核销加一个 Redis 分布式锁或者使用数据库乐观锁,两者选一个实现就可以。乐观锁的实现方式是在电子票表上增加 version 字段,更新时带上WHERE status = 0 AND version = 上次查到的 version,如果更新的影响行数为 0,说明票已经被别人核销了,就返回“票已使用”。这个方法代码改动量最小,也最好在答辩时向老师解释。

4.3 高并发场景下的库存防超卖

库存超卖是电商和票务系统面试必问的高频考点,放在毕设里作为项目亮点非常加分。场景是:一个 VIP 票种只剩最后 10 张,但同时有 20 个人在抢,系统必须保证最终只有 10 个人能下单成功,其他人看到的是“库存不足”。

如果直接用代码里的先查库存再扣减这招,在高并发下会出现著名的“ABA 问题”:两个线程同时查到库存是 1,线程 A 扣减后变成 0,线程 B 不知道库存已经变了,也执行扣减,结果变成了 -1,超卖了。这个问题的根源是“查”和“扣”之间没有加锁,两个操作不是原子的。

我的方案是用 Redis 的原子操作来处理。在票种库存设计时,把可售库存同步到 Redis 的 String 类型 key 上,用户下单时执行:

Long stock = redisTemplate.opsForValue().decrement("stock:ticketTypeId:" + ticketTypeId); if (stock < 0) { // 扣减失败,说明库存不够,需要把多扣的加回来 redisTemplate.opsForValue().increment("stock:ticketTypeId:" + ticketTypeId); throw new BizException("库存不足"); }

decrement在 Redis 中是原子操作,不会出现线程并发覆盖的问题。每一个请求进来都会执行一次原子减,减出来的值如果小于 0,说明这个用户实际已经排到库存之外了,就把减掉的值加回去,然后返回“库存不足”。这样即使 100 个并发请求同时进来,最终也只有库存数量的请求能拿到大于等于 0 的结果。

当然,用 Redis 扣了库存之后,MySQL 里的 stock 也要在同一个事务里更新。这里更好的办法是把“订单表插入”和“库存扣减”放在事务里,Redis 是前置的并发过滤器,MySQL 是最终的存储事实。演示和答辩的时候把这个链路说清楚,老师基本上能确认你是真的理解了并发控制,而不是背了一段概念就上场了。

4.4 预约入场与超时关单的定时任务

“智能展会入场预约”这个功能是题目里的关键词,所以在实现上不能马虎。预约的逻辑比较简单:用户在电子票夹里点击某张票,选择一个日期和时段,后端校验该票种是否支持该时段、该时段预约人数是否已满,然后写入预约记录表。同一张票如果已经有预约记录,就提示用户先取消原预约再重新预约。

预约时段的人数控制可以做成一个参数表,后台管理员可配置每个时段的容量上限。比如上午场限制 5000 人,下午场限制 8000 人。某一时段预约满了之后,前端直接隐藏或置灰对应时段的按钮。这个功能和第二章的核销校验联动起来,“无预约不入场”的业务规则才算是真正闭环。

超时未支付关单就是定时任务的经典应用场景。SpringBoot 里用@Scheduled注解就能实现一个最简单的定时任务:

@Scheduled(cron = "0 */5 * * * ?") public void closeExpiredOrders() { LocalDateTime deadline = LocalDateTime.now().minusMinutes(15); // 查询所有创建时间早于 deadline 且 status = 0 的订单 // 批量置为已取消,并把对应票种库存加回去 }

这个方案对毕设来说够用,但要注意不是实时的,最坏情况下订单会延迟最多 5 分钟才被关闭。如果想做得更完善一点,可以引入 RabbitMQ / RocketMQ 的延迟消息队列,下单时发送一条延迟 15 分钟的检查消息,消息到期后去判断订单是否已支付,未支付则关单。这个点在论文里可以作为一个优化方向来写,答辩时主动提出来,会显得你对系统设计有深入思考。但为了省事,用 @Scheduled 也完全没问题,毕竟毕设的并发量根本没到咬着延迟不放的程度。

5. 开发环境搭建与毕设常见坑位排雷

5.1 新建一个 SpringBoot 项目的正确姿势

很多同学刚拿到一个新电脑或者换了新版的 IDEA,上来踩的第一坑就是项目根本创建不起来。我用 IDEA 新建 SpringBoot 项目的标准流程是这样的:

打开 IDEA,选择 Spring Initializr,然后在界面里配置项目基本信息(Group、Artifact、Java 版本),依赖这块勾选 Spring Web、MyBatis Framework(或者用 MyBatis-Plus 的 starter 自己手动引)、MySQL Driver、Spring Data Redis 这些。一个关键点是:如果你的网络环境访问start.spring.io很慢或者超时,可以换成阿里云的镜像源https://start.aliyun.com,这一步能节省大量等待时间。

项目创建成功后,先别急着写代码,而是先把 Maven 的配置文件settings.xml配好国内镜像,否则下载依赖的速度会让人崩溃。我用的是阿里云 Maven 镜像,配置大概是把 mirror 节点指向https://maven.aliyun.com/repository/public。这些步骤看起来零碎,但做毕设的时候,环境搭建的时间成本往往比写功能代码还要多。

5.2 版本选择是一个大坑:SpringBoot 3.x 还是 2.x

这是我在这个项目里最想提醒你的一件事:注意 SpringBoot 版本和 JDK 版本的兼容性。现在网络上能找到的教程五花八门,有人用 SpringBoot 3.2 + JDK 21,有人用 SpringBoot 2.7 + JDK 8,如果你不假思索地照着某个视频敲,很容易出现依赖冲突。

我的建议是:毕设项目选 SpringBoot 2.7.x + JDK 8(或 11),这是最稳的组合。原因有三:第一,SpringBoot 2.7 的教程和网上的资料最全,遇到问题很容易搜到对应的解决方案;第二,绝大多数学校机房或者老师要求的 Java 环境还是 JDK 8,你用 JDK 8 写的代码到哪里都能编译运行;第三,SpringBoot 2.7 是 2.x 分支的最后一个免费开源支持的版本,很多企业内部还在大量使用。

如果你非要尝试 SpringBoot 3.x + JDK 17 的组合,也不是不行,但要做好折腾的准备。SpringBoot 3 是基于 Jakarta EE 规范改版的,很多老教程里的javax.*包名要全部改成jakarta.*,比如javax.persistencejakarta.persistencejavax.servletjakarta.servlet。这看似只是简单替换,但如果用到了一些第三方兼容库,很容易出现各种幺蛾子。我见过好几个同学因为在 SpringBoot 3 和 2 的迁移上卡了一整天,最后不得不推倒重来。做毕设求的是一个“稳”字,技术新不新其实没那么重要。

另外,“springboot version 太高”这个问题还延伸到内嵌容器的选择上。很多同学不知道,SpringBoot 的内嵌 Web 容器其实是可以替换的。默认是 Tomcat,想换成 Undertow,在 pom 里排除spring-boot-starter-tomcat并引入spring-boot-starter-undertow即可。这个知识点不会影响你正常做毕设,但在面试时提出来会显得你对框架有更细致的了解。

5.3 开发中反复踩的坑和排查思路实录

这个部分是我的重点心得,每一个坑都是我实际做过类似项目后总结出来的,记录下来给你排雷。

第一个必踩的坑是 Redis 没安装或没启动。我接手的毕设项目里,十个有八个的登录功能第一天还能用,第二天突然报连接超时。原因往往就是电脑重启后 Redis 服务没有自动启动,而代码在启动时就要连接 Redis 做缓存初始化。解决思路很简单:检查本机 Redis 进程是否在运行,Windows 下用redis-cli ping测试,能返回 PONG 就说明服务正常。我这里想多啰嗦一句:别在答辩演示的时候才发现 Redis 没起来,那时候全场都在看你排错,场面会很尴尬。

第二个坑是跨域问题。前端跑在 8080 端口,后端跑在 8081 端口,浏览器控制台报错CORS policy: No 'Access-Control-Allow-Origin' header,这就是典型的跨域。解决思路是在后端加一个全局的跨域配置类,实现WebMvcConfigurer接口并注册CorsFilter,或者给每个接口加@CrossOrigin注解。注意,配置要放开的方法和头部信息要写全,否则预检请求(OPTIONS)就会把请求挡掉。

第三个坑是 MyBatis-Plus 和 JSqlParser 的版本兼容问题。这个问题在新手项目中非常常见,因为 MyBatis-Plus 的不同版本依赖的mybatis-plus-boot-starter版本差异很大,尤其当你手动引入了一个比较高版本的jsqlparser时,分页插件可能直接启动报错。解决方式也简单:用 MyBatis-Plus 官方提供的版本兼容组合,在 pom 里用mybatis-plus-boot-starter统一管理版本,不要自己单独引jsqlparser

第四个坑非常隐蔽:LocalDateTime序列化问题。SpringBoot 默认使用 Jackson 做 JSON 序列化,如果你用LocalDateTime作为接口返回值而不做配置,前端收到的是数组格式的一串数字,比如[2025, 3, 5, 14, 30, 0],而不是"2025-03-05 14:30:00"。解决方法是引入jackson-datatype-jsr310(SpringBoot 2.x 默认自带),然后在 application.yml 里配置:

spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8

这样全局生效,前端拿到的就是标准格式时间字符串。

5.4 常见问题速查表

现象大概率原因处理办法
项目启动端口被占用上一次运行没关干净,或 Redis/MySQL 占了端口杀掉占用进程,或在 yml 中改server.port
连不上数据库MySQL 服务未启动、密码错误、URL 没写对检查 MySQL 进程,核对spring.datasource.url和账号密码
前端登录报 401Token 过期或没存重新登录获取新 Token,检查前端请求拦截器是否带上了 Authorization 头
扫描二维码核销没反应核销接口路径或 ticket_code 编码问题在控制器打印参数,确认控制台收到的 ticket_code 和数据库是否一致
定时任务不触发缺少@EnableScheduling在启动类上加上@EnableScheduling注解
批量保存失败MyBatis-Plus 批量插入 SQL 超长分批插入,每批 500 条以内
上传图片文件过大报错Spring 默认单文件限制 1MB配置spring.servlet.multipart.max-file-sizemax-request-size

6. 项目亮点、答辩要点与扩展思路

6.1 给项目加分的四个优化方向

需求做完了、系统能跑了,下一步就是想办法让这个项目看起来“不廉价”。同等功能的毕设,最后评分拉开差距的往往就在这些细节优化上。

第一个加分项是引入 Redis 缓存并做好缓存一致性设计。除了库存用 Redis 扣减外,展会列表、展会详情这些读多写少的接口都可以加一层缓存。缓存过期时间设置 5 到 10 分钟,后台修改展会信息后主动删除对应缓存。这个设计写进论文和答辩 PPT 里,就是一个标准的“缓存穿透、缓存击穿、缓存雪崩”场景。你可以在答辩时讲:如果某个热门展会秒杀时大量请求同时打到后端,缓存怎么兜底,怎么防止 MySQL 被打垮。这个话题能聊的深度非常够。

第二个加分项是给系统加一个简单的接口限流。SpringBoot 集成 Resilience4j 或者直接用 Redis + Lua 脚本实现一个令牌桶限流。演示的时候,可以用 Postman 或 JMeter 发起几十个并发请求,展示限流效果——部分请求正常返回,部分请求返回“系统繁忙,请稍后再试”。这个演示效果很强的,而且它把高并发的话题从一个概念变成了一个可见的功能。

第三个加分项是把整个项目用 Docker 部署。写一个docker-compose.yml,里面定义 MySQL、Redis、后端应用三个容器,一键启动整个项目环境。这解决了很多评审老师担心的“项目换台机器还能不能跑”的问题,也让你的系统具备了微服务部署的雏形。

第四个加分项是在后台加一个数据可视化面板,用 ECharts 展示近七天的售票趋势、各票种销量占比、每日核销人数。这个功能前端工作量不大,但视觉冲击力很强,而且“用图表展示系统产生的数据”本身就是一个完整的业务闭环,论文里能从数据可视化角度分析很多结论。

6.2 答辩时最容易被追问的问题与应对思路

答辩环节的不确定因素最多,但万变不离其宗,绝大多数问题都围绕“为什么这样做”和“出了问题怎么办”两个角度展开。我把自己被问过、以及见过的同学被问到的问题整理了一下。

第一个必问题:“你的系统是怎么做登录认证的?”回答思路是先说 JWT 的无状态特性,再说完整的认证流程:登录时服务端签发 Token,客户端存储并在每次请求的 Header 中携带,服务端通过拦截器解析校验。如果老师接着问“JWT 和 Session 有什么区别”“Token 过期了怎么办”,你就把 2.3 节的内容展开说。核心是讲清楚无状态和有状态的区别,以及各自的应用场景。

第二个高频问题:“如果用户同时抢购同一张票,系统怎么防止超卖?”回答顺序:先说业务上要保证原子的库存扣减,再说 Redis 的decrement是原子操作,最后再说 MySQL 的乐观锁兜底。如果老师再深挖,你说到用 Lua 脚本保证“检查库存 + 扣减”这个复合操作的原子性,就已经超过大多数同学的深度了。

第三个常见问题:“订单一直未支付,库存什么时候释放?”回答思路是定时任务扫描超时未支付订单并回补库存,可以说当前实现是每 5 分钟扫描一次,最坏延迟不超过 5 分钟;优化方向是用延迟队列做到秒级关单。这正好对应第四章第四节的内容。

第四个问题:“你这个系统上线后,数据库表要不要加索引?加哪些列?”这个问题是考察你的数据库基本功。回答思路:订单表的 order_no 字段要加唯一索引,因为订单号经常被用来查询;电子票表的 ticket_code 要加唯一索引,因为核销时直接按 ticket_code 查;外键关联的 user_id、exhibition_id 也应该加普通索引,避免全表扫描。能把这些理由说清楚,老师对你的印象分会明显提升。

6.3 这个系统的后续扩展方向

毕设交完不是终点,如果你打算把这个项目写进简历,或者后续想拿来参加比赛,有几个方向可以继续延伸。

如果往业务复杂度方向扩展,可以为展会引入多个展商,每个展商可以在展会内发布展位和活动,观众可以收藏展商、预约展商的活动场次。这样系统就从“票务平台”升级成了“展会综合服务平台”,模块更多,讲述空间更大。

如果往技术深度方向扩展,可以拆分出独立的认证服务、订单服务、票务服务,用 Spring Cloud Alibaba 那套微服务框架来做服务治理,把用户认证、订单幂等、分布式事务这些企业级问题引进来。不过这是研究生阶段的玩法,本科毕设做到单体 + Redis 的程度已经绰绰有余,千万别为了炫技把项目复杂度拉到自己控制不住的高度。

我个人做毕设有个执念:项目规模不一定要大,但一定要把一个完整的业务场景做透。功能可以只有十几个,但用户从注册到买票再到进场的每一环都要走得通;技术栈可以不用最前沿,但用到的每个框架和中间件都要能讲清楚它的角色和工作原理。展会门票系统恰好给了我这样一个载体——它不只是一个能跑起来的代码仓库,更是一个能让我在论文和答辩中有话可说、有理可讲的完整项目。你跟着这篇文章的思路走一遍,把核心流程跑通,把几个关键的设计决策想明白,最后呈现出来的项目,绝对会比绝大多数同题目的作品要扎实。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询