
每年到了毕设选题的节点总有一批同学在“做什么题目”上反复横跳。我的建议一直很明确如果你想要业务场景完整、技术栈有含金量、演示效果好、答辩有的聊又不想去卷那些烂大街的“图书管理系统”基于 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 出现之前SSMSpring 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 必须加密存储推荐 BCryptstatus 用于账号禁用值为 0 或 1exhibition展会表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 去掉横线 随机字母数字串比如8f3a2c9e6b1d4ef185d2074f9a3c1b7e32 位长度全局唯一。不要用自增 ID 去生成二维码因为那会被人轻易伪造扫一下就进不来了。然后是扫码核销接口的设计。场馆入口的核销设备对着观众手机上的二维码扫一下后端接收两个参数ticket_code 和操作员 ID。核销逻辑拆成下面几步根据 ticket_code 查电子票记录查不到直接返回“无效票”检查电子票状态如果 status 已经是 1已入场返回“该票已使用”校验当前时间是否在展会入场时间段内非入场时间返回提示校验该票是否有当天的入场预约记录无预约不允许入场这是“预约入场”模式的核心规则以上全部通过将电子票状态置为已入场写入核销记录表这里有一个高并发的隐患两个闸机同时扫同一张票可能同时读到“未使用”状态然后都执行入场成功。实际项目中我的处理方案是给核销加一个 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.persistence变jakarta.persistence、javax.servlet变jakarta.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-jsr310SpringBoot 2.x 默认自带然后在 application.yml 里配置spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8这样全局生效前端拿到的就是标准格式时间字符串。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-size和max-request-size6. 项目亮点、答辩要点与扩展思路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 的程度已经绰绰有余千万别为了炫技把项目复杂度拉到自己控制不住的高度。我个人做毕设有个执念项目规模不一定要大但一定要把一个完整的业务场景做透。功能可以只有十几个但用户从注册到买票再到进场的每一环都要走得通技术栈可以不用最前沿但用到的每个框架和中间件都要能讲清楚它的角色和工作原理。展会门票系统恰好给了我这样一个载体——它不只是一个能跑起来的代码仓库更是一个能让我在论文和答辩中有话可说、有理可讲的完整项目。你跟着这篇文章的思路走一遍把核心流程跑通把几个关键的设计决策想明白最后呈现出来的项目绝对会比绝大多数同题目的作品要扎实。