每年三四月份,“计算机毕业设计源码”这几个字就成了搜索框里的高频词,热搜词里它和“springboot”几乎绑定出现。我当初选题目的时候,也在十几个备选里翻来覆去,最后锁定了这个“springboot艺术展览票务预订系统”。乍一看,它也就是个增删改查:展览列表、下单、支付,听起来比电商、外卖、社交平台轻巧不少。等代码真正到手、跑起来、再拆开理解每一处设计,我才发现这题目表面平静,水下全是细节——库存怎么防超卖,订单状态怎么流转,支付回调怎么保证幂等,票档和观展日期怎么关联,哪一样都是答辩现场会被刨根问底的硬骨头。
这篇文章不打算逐行注释代码,我想把从选题、架构、数据库、核心业务逻辑到部署跑通的全过程拆开讲一遍,把我踩过的坑、答辩时被追问的点、以及源码里值得重点讲给评审老师的逻辑都整理出来。无论你是刚拿到源码准备改造,还是正在纠结毕业设计选什么方向,这篇都能给你一个完整参照。
1. 为什么我最终选了“艺术展览票务”这个毕设题
1.1 选题逻辑:什么样的题目才是合格的毕设
我见过太多人选“XX管理系统”然后做到一半发现没什么可写的。判断一个题目合不合格,我习惯用一个三角模型:业务够不够完整、技术有没有讨论空间、工作量是否可控。
业务完整,指的是这个系统能讲出一条清晰的用户价值链路,而不是把几个页面堆在一起。艺术展览票务天然是一条“浏览展览—选择日期—选购票档—支付—生成电子票—入场核销”的闭环,无论放在美术馆、博物馆还是商业沉浸式展览场景,都能让答辩老师三秒钟get到它在解决什么问题。
技术有讨论空间,指的是里面必须有几个不是“一把梭”就能糊弄过去的设计点。图书商城类的项目最容易做成商品表加购物车,从头到尾没有一处能引发深度提问。票务系统不一样,它自带库存并发、超时关单、支付幂等、票券核销这些关键词,每个都能延伸成一串专业追问,而这恰恰是你展示功底的机会。
工作量可控就更直接了。一个展览模块加订单模块,前端页面数量适中,数据库不超过十张表,一个人一个月内从读懂到二次开发完全够用,不会出现那种写到一半发现模块多到收不了场的情况。
1.2 艺术展览票务和图书商城到底差在哪
同样是交易类系统,票务和普通商品交易在核心约束上有本质区别。说出来你可能觉得我在夸张,但这两个系统的设计难度根本不在一个量级。
先看图书商城:一本书库存1000本,用户下单你扣库存,用户取消你加回来,逻辑直来直去。票务系统多了一层“日期库存”的维度——同一个展览,每天能放多少张票是不一样的。热门周末可能放300张,工作日可能放80张,每个票档每天还要单独限量。这意味着你的库存模型不是一张表一个字段那么简单,而是“展览 + 观展日期 + 票档”三维度联合决定。
再看订单状态,商城订单通常有待支付、已支付、已发货、已收货、已取消,状态之间基本是线性推进。票务订单除此之外还要处理超时未支付的自动关闭与库存回补,处理用户下单后改变主意取消,处理入场后的票券状态变更。更重要的是,一张订单可以对应多张票,票的维度还要独立记录核销情况。
电子票也是商城没有的概念。下单支付成功之后,系统要生成二维码,用户在入场口出示,管理员扫码核销,核销之后票就作废。这里面牵扯到生成、传输展示、验签、防复用一整条链路。正因为这些差异,艺术展览票务这个题目在答辩时能讲的东西比普通商城多得多,老师问你也问得有层次。
2. 技术栈与整体架构设计:前后端分离到底怎么拆
2.1 技术选型:为什么是SpringBoot + MyBatis-Plus + Vue
这套源码后端主体是SpringBoot,持久层用MyBatis-Plus,前端是Vue,典型的Java毕业设计组合。技术选型有一定合理性,我实际跑下来之后的评价是:它把“稳妥”和“可讲”兼顾得比较好。
SpringBoot不必多说,自动配置和起步依赖让项目搭建成本极低,这决定了你不需要在环境上浪费大量精力。MyBatis-Plus解决的是单表CRUD的重复劳动,内置的BaseMapper让你少写大量XML,这一点在赶毕设的时候非常重要。前端用Vue做前后端分离开发,页面路由、组件化、Axios请求拦截这些都是现成的工程化实践,写在论文里也是一整章内容。
这里有一个经验要提醒。热词里常年有人搜“springboot版本太高”怎么处理,我见过不少拿到源码的同学,把项目直接往IDEA里拖,发现新版SpringBoot要求JDK17甚至更高,本地装的是JDK8,一堆依赖报错。这套项目如果主版本是2.7.x,配JDK8最稳,千万别图新升级到3.x,除非你连MyBatis-Plus和部分兼容配置一起换掉,否则纯属给自己加戏。
| 技术项 | 选型 | 说明 |
|---|---|---|
| 后端框架 | SpringBoot 2.7.x | 稳定、资料多,天然适配JDK8 |
| 持久层 | MyBatis-Plus | 减少单表CRUD代码,内置分页插件 |
| 数据库 | MySQL 5.7 / 8.0 | 存储全部业务数据 |
| 前端 | Vue3 + Element Plus | 组件化开发,后台管理界面成型快 |
| 认证鉴权 | JWT + 拦截器 | 无状态认证,前后端分离环境友好 |
| 构建工具 | Maven | 主流标配,IDEA直接支持 |
2.2 后端分层与前、后端目录结构
后端的包结构是这个项目的骨架,建议拿到源码先看包名,再顺着包名读代码。通常会有这样几层:
- controller:接收前端请求,做参数校验,返回统一结果
- service:业务逻辑核心,下单、支付、退款都在这一层
- mapper:继承BaseMapper的接口,配合注解SQL或XML使用
- entity:数据库表对应的实体类
- dto:前端传入和返回的数据对象
- config:配置类,包含拦截器、跨域、全局异常等
- common:统一返回、自定义异常、JWT工具等公共模块
这种分层不是摆设,它决定了答辩时老师问“你这个业务逻辑放在哪一层”时你能不能答得干净。比如下单逻辑如果散落在Controller里,老师顺着代码一查,印象分马上打折。
前端目录一般按业务模块划分,admin端和user端分开,api目录统一管理请求函数,router里配置页面路由。我建议你把重点放在api封装和路由守卫这两块,一个是所有请求的出口,一个是登录态控制的入口,都属于“一看就知道你懂工程化”的点。
2.3 核心业务流程的文字推演
不要一上来就盯代码,先闭上眼睛把业务在脑子里过一遍。这套系统的主流程是这样走的:
用户注册登录进入首页,看到展览列表,点进详情后选择观展日期,系统根据日期展示可用票档和余量。用户选好票档、输入数量,后端校验日期是否在展期内、限购数量是否超限、余票是否充足,通过后生成待支付订单。用户跳转支付页面,完成模拟支付,后端收到支付请求后更新订单状态,同时生成对应数量的电子票,每张票一个唯一票号。用户到现场出示二维码,管理员扫描核销,票状态从“未使用”变成“已使用”,整个闭环结束。
管理端的流程是另一条线:管理员发布展览,配置展期,为展览创建票档,再按天设置每日库存。用户买票后,管理员在订单管理里查看记录,在核销台完成入场验票,在统计页面看到销售额和热门展览排行。
把这两条线讲清楚,你对整个系统的理解就已经超过了绝大多数只关注页面样式的同学。
3. 数据库设计与防超卖:评审老师最爱问的两块
3.1 核心表结构:从展览到电子票的建模
数据库设计是整个项目的地基。表结构如果设计得乱,前端再好看也是空中楼阁。这套系统里,我认为最核心的表应该是这些:
| 表名 | 核心字段 | 作用 |
|---|---|---|
| sys_user | id, username, password, phone, role | 用户与管理员账户 |
| exhibition | id, title, cover, location, start_date, end_date, status | 展览基础信息 |
| ticket_type | id, exhibition_id, name, price, limit_per_user | 票档信息,如早鸟票、标准票 |
| daily_quota | id, exhibition_id, ticket_type_id, visit_date, total_stock, sold_stock | 日期库存,防超卖的落点 |
| orders | id, order_no, user_id, exhibition_id, ticket_type_id, visit_date, quantity, amount, status, expire_time | 订单主表 |
| ticket | id, ticket_no, order_id, user_id, exhibition_id, qr_code, status | 电子票明细,一单一票 |
| payment_record | id, pay_no, order_id, amount, status, callback_time | 支付流水,对账用 |
重点说一下daily_quota这张表。很多人做票务会把库存直接放在ticket_type表的stock字段里,这是不严谨的。同一个展览,不同日期的热度不同,如果只有一个总库存,就会出现“9月20日卖爆了,9月21日还有大量余票”却无法精细化控制的问题。按日期拆成一条条配额记录,才能支持“周末放票多、工作日放票少”的真实运营策略。
订单表里的order_no一定要加唯一索引,这既是订单号本身的业务约束,也是防止并发重复插入的兜底手段。ticket表里的ticket_no也需要唯一索引,每张票在系统中只对应一条记录,核销时才能唯一定位。
3.2 库存扣减方案:乐观锁SQL与唯一索引的搭配
防超卖是票务系统最核心的问题。艺术展览暑期热门场次一天几百张票,理论上完全可能多个用户同时抢同一张余票。如果代码写成“先查余票,再判断足够,再执行扣减”,在高并发下会有严重超卖风险,因为两个事务可能同时读到同一个余量数字。
这套源码采用的思路是可取的——用数据库的单行原子更新来扣减库存,本质上是乐观锁。核心SQL长这样:
UPDATE daily_quota SET sold_stock = sold_stock + #{num} WHERE id = #{quotaId} AND sold_stock + #{num} <= total_stock对应的Mapper接口方法:
@Update("UPDATE daily_quota SET sold_stock = sold_stock + #{num} " + "WHERE id = #{quotaId} AND sold_stock + #{num} <= total_stock") int deductStock(@Param("quotaId") Long quotaId, @Param("num") Integer num);这个方法返回受影响的行数,等于0就说明余票不足或配额不存在。这种做法的巧妙之处在于:扣减和判断在同一个原子SQL里完成,不需要先查再改,也避免了并发下的经典超卖问题。拿到代码后你可以重点给老师画一下这条语句的执行路径,我敢说这比你在答辩时扯一堆Redis分布式锁更能让人信服。
选这个方案不选悲观锁,原因也是实际层面的考量。悲观锁用for update实现,代码简单但会把数据行锁住直到事务结束,持锁时间不可控。真实抢票场景里这个方案会放大数据库压力,毕设里用乐观SQL更能体现你对并发控制的理解深度。
3.3 订单状态机与超时自动关单
订单状态我梳理成五个:待支付、已支付、已使用、已取消、已退款。状态机不算复杂,但它决定了整个系统的行为边界。
待支付状态下,用户取消订单或者超时未支付,都要走“关单回补库存”的逻辑。这里有一个常见错误:取消订单时直接把库存加回去,不考虑此时是否已经超时被系统自动关闭,结果出现库存双倍回补。正确做法是先更新订单状态,用一个带条件、的判断语句确保只有“待支付”状态才能被改成“已取消”,同时把回补库存放在同一个事务里。
@Transactional public void cancelExpiredOrder(Order order) { int rows = orderMapper.cancelIfPending(order.getOrderNo()); if (rows == 0) { return; // 订单已经不是待支付状态,避免重复回补 } dailyQuotaMapper.restoreStock(order.getQuotaId(), order.getQuantity()); }超时关单可以用Spring自带的定时任务,在启动类上加@EnableScheduling,然后在Service里写一个@Scheduled方法,每5分钟扫描一次待支付且过期时间早于当前时间的订单,挨个关单回补。很多同学的源码其实已经带了这段逻辑,你读的时候要专门留意一下,因为这是答辩时的经典问题“如果用户下单后不支付怎么办”。
4. 关键代码实现走查:从下单到支付回调,每一步都在卡什么
4.1 下单接口的前置校验与库存锁定
下单接口是整个后端最值得细读的一段。它表面上是插入一条订单记录,实际上在插入之前做了四件关键事情。
第一步是鉴权和参数校验。从JWT里解析出当前用户ID,校验用户是否存在,校验前端传来的票档ID、数量、观展日期是否合法。第二步是业务时限校验,判断所选日期是否在展览的起始日期和结束日期之间,这是很多人会忽略的点。第三步是限购校验,从sys_user和ticket_type里查当前用户对这个票档已经买过多少张,加上本次数量是否超过limit_per_user。
第四步才是库存锁定。调用dailyQuotaMapper的deductStock方法扣减对应日期配额,如果返回0,直接抛业务异常提示“余票不足”。在扣库存成功之后再插入订单记录,所有操作加在同一事务中。这样设计的好处是:库存和订单永远不会出现一边扣了另一边没生成的状态,也就是你常听说的“事务一致性”。
生成订单号的方式也值得看一眼。好的订单号要全局唯一且带一定业务辨识度,通常用时间戳加随机数实现。你可以看到这类代码:
String orderNo = "AT" + System.currentTimeMillis() + String.format("%04d", new Random().nextInt(10000));格式不唯一,但“前缀 + 时间戳 + 随机数”是毕设阶段最稳妥的写法。真要追求更专业的方案,可以用雪花算法生成分布式ID,但你需要在答辩时讲清楚它解决的是ID唯一性和趋势递增问题,同时也要能扛住“为什么不用自增主键”的追问。
4.2 支付回调的幂等处理与状态流转
支付环节在真实项目里是要对接微信或支付宝的,但毕设通常用模拟支付来替代。模拟支付的核心思路是:前端点击“立即支付”,请求后端一个Mock支付接口,后端校验订单属于当前用户且状态为待支付,然后把订单置为已支付。
这里最容易被忽略但含金量最高的是幂等处理。真实支付网关的回调可能会因为网络超时重复发起,如果回调里不判断订单当前状态,就可能把同一笔订单处理两次,造成重复充值或者重复出票。模拟支付接口同样要防御这个问题。
@PostMapping("/payment/mock") public Result<String> mockPay(@RequestParam String orderNo) { Order order = orderMapper.selectByOrderNo(orderNo); if (order == null) { throw new BizException("订单不存在"); } // 幂等判断:只有待支付订单才能支付成功 if (!OrderStatus.PENDING_PAY.equals(order.getStatus())) { return Result.success("订单已处理,请勿重复支付"); } // 更新订单状态并生成电子票,同一事务 orderMapper.markPaid(orderNo); ticketService.generateTickets(order); return Result.success("支付成功"); }这段伪代码里的幂等判断,回答的是一个非常高频的面试和答辩问题:“支付回调重复收到怎么办?”你只要能像上面这样分层作答——先查订单,判断状态,只有特定状态才执行后续流程,就已经踩中了关键点。生成电子票的逻辑同样放在同一事务里,保证支付成功必有票。
4.3 电子票生成与入场核销
支付成功后的产物是若干张电子票。我在读源码时特别注意了ticketService.generateTickets方法,它做的事情是:根据订单信息批量创建Ticket记录,每张票生成唯一ticket_no,再生成一个带有签名信息的二维码。
二维码生成在毕设里最常用的方案是ZXing库,后端把ticket_no、展览ID、观展日期拼成一个字符串,用工具类生成Base64编码的二维码图片返回给前端。前端在“我的票夹”里展示二维码,用户入场时出示给管理员。
核销接口的逻辑核心是防复用。管理员用扫码枪或者前端页面输入ticket_no,后端查出对应Ticket记录,判断状态是否为“未使用”,如果是,则更新为“已使用”,同时记录核销时间;如果不是,返回提示“该票已核销,请勿重复使用”。这个校验和支付幂等是同一个思想,防止同一张票被反复入场。
如果你想把项目做得更出彩,可以尝试给ticket_no加一个验签字段或者用HMAC对票号签名,核销时先验签再查库。这样即使被人恶意伪造票号,没有签名也过不了验签关。这是一个很有展示价值的扩展点,而且实现成本不高。
5. 管理端与用户端的功能清单及接口设计
5.1 用户端:从浏览展览到电子票展示的完整链路
用户端的功能紧密围绕购票旅程展开。我按页面流转顺序梳理一份清单,这套清单同时也是你写论文功能需求章节的素材:
- 注册登录:手机号或用户名注册,密码加密存储,登录后发放JWT
- 首页展览推荐:按热度或时间排序,展示封面、时间和价格
- 展览详情:介绍、展期、可选日期列表、每个日期下的票档余量
- 下单页:选择日期、票档、数量,实时显示总价
- 订单确认与支付:未支付订单列表,点击支付跳转模拟支付
- 我的订单:查看全部订单,按状态筛选,未支付订单可取消
- 我的票夹:已支付订单下的电子票列表,展示二维码
- 个人中心:修改资料、查看限购信息
每个功能背后都对应接口,例如“首页展览推荐”对应GET /api/exhibition/hot,“选择日期后展示票档余量”对应GET /api/quota/list?exhibitionId=&date=。前端拿到数据后渲染,后端统一返回Result结构,里面包含code、message、data三个字段。我看过不少毕业设计的接口返回,状态码和业务码混在一起,而统一返回结构能让前端处理逻辑非常干净。
5.2 管理端:从发布展览到数据统计的运营视角
管理端功能比用户端更考验对业务的理解。发布一个展览不是简单填个标题,而是连带着配置票档和每日库存:
- 仪表盘:今日订单量、今日销售额、总用户数、热展排行
- 展览管理:新增展览、编辑信息、上架下架、查看展期
- 票档管理:为展览添加早鸟票、标准票、VIP票,设置价格和限购数
- 日期库存管理:按展览设置某天某票档的总库存,查看已售数量
- 订单管理:所有订单列表、按状态筛选、手动退款
- 验票核销:输入票号或扫描二维码,完成核销
- 用户管理:查看用户列表,禁用异常账户
这里我要特别提一句日期库存管理,它是这个项目和普通商城的最大区别所在。很多人的源码实现里,是直接在一个表格里列出“日期-票档-总库存-已售”,管理员可以随时调整总数但不能低于已售数。这个调整逻辑需要校验:新设置的总库存必须大于等于当前已售数量,否则会出现负数库存的数据脏状态。
5.3 接口规范、JWT鉴权与全局异常处理
接口设计上要遵循REST风格,资源用名词表达,动作由HTTP方法体现。比如创建订单用POST /api/order,取消订单用PUT /api/order/{orderNo}/cancel,核销用POST /api/ticket/verify。这样的接口设计本身就能在论文或者答辩PPT里单独列一节。
权限控制是另一大得分点。用户端接口一般要求“登录即可”,管理端接口要求“必须是管理员”。实现上,后端用拦截器统一从请求头取出Authorization字段,解析JWT后把用户信息放入ThreadLocal,再通过自定义注解配合拦截器做角色校验。比如管理员接口可以加@RequireRole("ADMIN"),拦截器里判断当前用户角色,不匹配直接返回403。
全局异常处理也值得专门看一眼。统一用@RestControllerAdvice捕获业务异常和系统异常,业务异常返回业务码,系统异常返回500并记录日志。前端只需要判断code是不是200,不用把各种错误堆成一团。这部分代码量不大,但答辩老师非常喜欢问,因为它体现的是你是否有工程化思维。
6. 源码到手后怎么跑通:环境配置与避坑实录
6.1 环境准备清单
跑通一个SpringBoot前后端分离项目,最理想的环境是这样一套:
| 工具 | 推荐版本 | 说明 |
|---|---|---|
| JDK | 1.8 | 和SpringBoot 2.7.x搭配最稳,别一上来就搞17 |
| Maven | 3.6+ | IDEA自带可用,注意镜像源 |
| MySQL | 5.7或8.0 | 8.0需要改driver和url参数 |
| Node.js | 16以上 | 前端构建和安装依赖用 |
| IDEA | 2022+ | 社区版够用,专业版更好 |
| Postman / 浏览器 | 任意 | 接口调试与页面验证 |
装环境时最容易翻车的点是MySQL版本。如果本地是8.0,连接的驱动名要用com.mysql.cj.jdbc.Driver,URL里要带serverTimezone=Asia/Shanghai,否则启动会报时区错误。如果本地是5.7,驱动用com.mysql.jdbc.Driver即可。拿到源码后先检查application.yml里的数据库配置,把我说的这两点确认好再启动。
6.2 从源码到可运行的六步流程
我按实际操作顺序给你一套可直接照做的步骤,每一步都对应常见报错场景。
第一步,导入数据库。用Navicat或者命令行执行项目根目录下的art_ticket.sql脚本,确认生成所有表和初始数据。注意脚本字符集要是utf8mb4,否则中文会出现乱码。
第二步,修改后端配置。打开application.yml,把数据库地址、账号、密码改成你自己的。确认启动端口,常见是8080。
第三步,导入后端项目到IDEA。用IDEA的Open方式选择后端根目录,等待Maven自动下载依赖。如果依赖下载很慢,把Maven的settings.xml里配置阿里云镜像,这一步能节约大量时间。
第四步,启动后端。直接运行主类上的main方法,看到Spring Boot启动成功的日志,说明后端已经起来了。用浏览器访问本机8080端口,能出现接口文档或者错误返回都说明服务在线。
第五步,启动前端。用终端进入前端目录,依次执行npm install和npm run dev,控制台会输出访问地址,默认一般是localhost:9527。第一次npm install可能需要几分钟,耐心等。
第六步,联调测试。浏览器打开前端地址,注册一个用户,走一遍浏览展览、下单、支付、查看票夹的完整流程,再用管理员账号登录后台,查看订单和核销。能用浏览器把全流程点通,项目就算跑起来了。
6.3 我实际遇到过的坑和复盘
依赖版本冲突是最大的坑。热词里常年有人搜“springboot版本太高”和“maven项目构建方法”,就是因为pom.xml里的依赖版本和本地环境不匹配。我的建议是:拿到源码先看pom.xml,确认SpringBoot父依赖版本,再确认JDK版本,两者必须兼容。2.7.x配JDK8,3.x配JDK17,这是铁律。
第二个坑是前端请求跨域。前后端分离下,前端在9527端口请求后端8080端口,必然跨域。后端需要配置CorsFilter放行前端地址,或者在拦截器里设置允许跨域的响应头。你自己新写接口时也要记得继承同一套跨域配置,否则页面白屏、请求发不出去,很多人会被卡在这里查半天的错。
第三个坑是图片上传后的静态资源映射。展览封面通常会上传图片,如果上传后无法访问,多半是SpringBoot没有把本地上传目录映射为静态资源路径。需要在WebMvc配置里加一个addResourceHandlers,把磁盘路径映射到/upload/**。这一步不解决,后台传图成功但前台显示裂图,很影响答辩展示效果。
第四个坑是IDEA不识别SpringBoot项目。点鼠标右键没有Run,原因是IDEA没有把项目识别为Maven工程。解决方法是右键pom.xml,选择Add as Maven Project,再执行一次clean和reimport。
7. 把项目讲出亮点:答辩展示与高频追问应答
7.1 展示顺序这样设计,老师更容易给高分
答辩时不要一上来就打开页面翻来翻去,那是“用户视角”,老师想看的是“设计视角”。我建议的展示顺序是:先用一分钟讲清楚系统解决了什么业务问题,再画出核心业务流程图,把展览、票档、日期配额、订单、电子票之间的关系说清楚,接着讲数据库设计里最巧妙的一两个点,比如日期配额表的设计动机,最后做系统演示。
演示环节也有讲究。不要先登管理端一顿增删改查,而是走一遍真实用户路径:打开首页看展览,进入详情选日期,下单支付,打开票夹展示二维码,然后切换管理员账号核销这张票。这条路径走完,老师对整个系统的业务闭环就有了感性认识,你再回头把订单状态、库存扣减这些技术点一讲,印象自然就立体了。
强烈建议你在答辩前准备两张图,一张是系统模块图,一张是核心业务时序图。画图的时候注意干净清晰,直接放进PPT里,这是你专业度的第一印象。老师很多问题都是顺着你的图问出来的,图上有的内容你有准备,图上没有的我建议别硬画。
7.2 老师最爱追问的几个问题和应答思路
票务系统有一些命中率极高的问题,提前准备好应答思路,现场就不会卡壳。
“库存扣减为什么不先查余票再判断?”回答的核心是并发安全。先查后改在并发下会读到旧值,导致超卖;用一条带条件的UPDATE语句把判断和扣减合并成原子操作,数据库行锁保证同一时刻只有一个事务能修改该行配额。如果老师追问“那你的意思是并发全靠数据库?”你可以补一句:还可以引入Redis预扣库存提升吞吐,但会带来缓存一致性问题,毕设场景下数据库原子扣减更稳妥。
“超时订单怎么处理的?”关键是定时任务加状态条件更新。每5分钟扫描待支付且超过30分钟的订单,在事务中先把订单状态从待支付更新为已取消,只有更新成功说明这张单还没被用户手动取消,才能执行库存回补。这样设计的好处是不会重复回补库存。
“支付回调重复到达怎么办?”直接答幂等。回调处理逻辑第一步查订单,判断当前状态是不是待支付,不是就直接返回成功;只有待支付状态才继续更新订单并生成电子票。老师如果继续追问“为什么这样就能防重复”,可以解释为:状态判断相当于乐观锁的版本控制,已经流转走的状态不可能被二次执行。
“为什么每一张票要单独建表而不是只存一个数量?”答案是票据和订单是两个领域概念。订单是一笔交易的凭证,票据是入场凭证。一单可能买了三张票,三张票可能分批入场,每张票要有独立的核销状态,所以必须拆表。回答这个问题时,你顺手把ticket表的qr_code和status字段指给老师看,说服力会非常强。
最后一个容易被问的是“你自己加了什么功能”。如果你的源码是在原项目基础上改造的,一定要把这个功能讲清楚,哪怕它很小。比如我给自己加了一个“热门展览浏览量统计”或者“订单导出Excel”,成本不高但能证明你独立完成过设计和编码。我不建议只照着源码原样读,任何一个老师连续听五个同学讲同一个项目都会审美疲劳,做出一个让别人能记住的差异点很重要。
源码跑到线上只是起点,真正拉开差距的是你能不能把里面每一个“为什么”讲清楚。这套SpringBoot艺术展览票务系统,代码吃透之后,你会发现自己对订单状态、库存并发、支付回调、权限设计这些概念的理解,比刷十篇教程都管用。把最后一章那些问题翻来覆去模拟几遍,你会发现答辩那天的底气完全不一样。