1. 为什么毕设选中“javaweb影院订票系统”这个题目:这是一个很聪明的折中
如果让我给毕业设计选题打分,“基于javaweb的影院订票系统的设计与实现”这个题目我愿意给到80分以上。它不像社交类APP那样容易把项目做成一个空壳,也不像纯后台管理系统那样演示时缺乏亮点,更不像算法类题目那样短时间内难以出成果。电影排片、场次管理、座位选择、订单生成、支付模拟、超时取消,表面看是一个订票网站,实质是一套完整的信息管理系统加电商交易流程,正好卡在“有难度但不至于做不完”的甜区。
很多同学担心这个题目太老,其实老题目恰恰意味着资料齐全、踩坑记录多、答辩老师熟悉整个业务闭环,反而不容易翻车。真正拉开差距的是谁把细节做得更完整、逻辑更自洽。我复刻这套系统的最终交付物包括毕业论文、答辩PPT、完整源代码和演示视频,整套东西是真正能跑通的,不是那种页面截图拼出来的“假项目”。下面把几个关键决策和实操路径拆开讲,想照着做的同学可以直接套用。
1.1 “很像电商但又没那么复杂”的绝佳折中
影院订票系统骨子里就是一个简化版电商系统:有商品(电影票)、有库存(座位余量)、有订单、有支付、有取消和退款逻辑。但它的商品结构比真正的电商简单很多,不需要SKU、购物车、优惠券、物流、售后这些复杂概念,核心链路只有“选场次、选座位、下单、支付”四步。
这个特性对毕业设计极其友好。你可以把电商领域最核心的并发扣库存、订单状态流转、超时关单问题都讲清楚,但不需要处理电商系统里那些庞杂的周边模块。系统规模控制在合理范围,一个人花两个月左右做完核心功能,再用一个月完善论文和演示材料,时间上是来得及的。
也正是因为业务边界清晰,平时完全没接触过JavaWeb开发的初学者也能快速上手。只要把Servlet、JSP、JDBC、MySQL这几样基础内容吃透,再配合一套现成的源码对照着改,基本不会出现“代码看懂了但不知道怎么组合”的情况。项目里最复杂的一块是选座和订单联动,这部分我会在后面单独展开,它也是答辩时最容易给老师留下好印象的点。
1.2 一套项目能带出的技术点清单
把题目里涉及的技术点列出来,你会发现它足以覆盖绝大多数学校的毕设验收标准和技术考核点:
- 后端语言与运行环境:Java、Servlet、JSP、JDBC、Tomcat容器
- 数据持久层:MySQL数据库,JDBC或MyBatis二选一
- 前端技术:HTML、CSS、JavaScript、JSP页面,配合layui或Bootstrap做界面
- 关键机制:Session会话管理、过滤器拦截登录权限、JDBC事务控制、乐观锁或行锁处理并发
- 业务设计:订单状态机、超时释放座位、定时任务、分页查询、多条件检索
这些考点单看每一项都不算高深,但合在一起就是一个完整的“JavaWeb工程师”基本技能组合。更难得的是,它比理论题更容易在答辩现场演示:你设置两个浏览器窗口同时买同一个座位,用数据库查询结果说话,比任何口头解释都有效。
2. 功能边界先画清楚:用户端和管理端的完整功能清单
很多项目做砸的第一步,不是代码写得烂,而是功能清单没想清楚,做着做着给自己加需求,最后页面一堆但每个都半成品。影院订票系统最合理的功能边界是三段式:用户端完整的购票闭环,管理端对数据源的日常维护,加上贯穿两端的订单与状态流转。
2.1 用户端一条主流程串起九个功能点
用户端的核心逻辑就是“从登录到下单再到查订单”的一条链路,我建议按这条链路来规划功能,别按页面来规划:
- 注册与登录:注册时校验用户名唯一性,登录采用Session记录用户状态,配一个过滤器拦截未登录访问。
- 首页电影列表:区分“正在热映”和“即将上映”两个Tab,展示海报、评分、简介摘要。
- 电影详情页:展示完整信息,包括演职人员、剧情简介、片长、上映时间,同时给出该电影在近期所有可订场次。
- 场次列表:按日期和影厅展示排片,显示开始时间、影厅名称、语言版本、票价和当前剩余座位数。
- 选座页:用座位图表单展示,灰色代表已售,绿色代表可售,点击可选中多个座位,再次点击取消。
- 确认订单页:汇总场次信息、所选座位、单价与总价,显示支付剩余时间倒计时。
- 支付模拟:为避免实际接入第三方支付,用“用户余额扣款”模拟,充值入口放在个人中心。
- 我的订单:按状态分类展示全部订单,支持待支付订单继续支付、已支付订单退票。
- 个人中心:修改资料、查看余额、查看历史订单记录。
没做的是收藏功能、评论影评、会员等级、积分这些花哨扩展。毕业设计不追求大而全,把主流程做扎实比做什么都强,这些扩展点放到“未来展望”里写一句就行。
2.2 管理端不是凑数的后台,是数据来源
管理端有六个功能模块,全部围绕“数据从哪里来、怎么维护”展开:
- 管理员登录:独立于用户表的管理员表,登录后跳转到后台首页。
- 电影管理:新增、修改、上下架、删除电影,支持上传海报图片路径和填写影片信息。
- 影厅管理:维护影厅名称和基础座位排布,比如一个影厅有8排、每排10座,可以整体定义座位矩阵。
- 场次排片:这是管理端最核心的功能,选择电影、影厅、开场时间,设置票价,系统自动生成一个场次,场次关联的座位状态初始化为全部可售。
- 订单管理:查看所有订单,支持搜索用户、状态筛选;对已完成支付但用户申请退票的订单执行退款。
- 用户管理:查看用户列表、封禁用户,给用户手动调整余额(方便测试支付流程)。
管理端的界面可以做得朴素,关键是功能完整。很多同学喜欢把精力花在后台的酷炫图表上,但答辩老师更关心的往往是“管理员改了一场电影信息后,用户端能不能立刻看到变化”,这类前后端联动的效果比花哨图表直观得多。
2.3 容易被忽略的非功能需求
功能清单之外,还要考虑三个非功能需求:
第一是权限控制。普通用户只能访问用户端页面,管理员页面必须经过角色校验,不能仅仅靠“隐藏入口”来防访问,需要用过滤器统一拦截。
第二是数据安全基础动作。用户密码使用MD5加盐存储,防止数据库泄露后密码明文暴露;SQL操作全部使用预编译参数,防止SQL注入注入攻击。
第三是操作反馈。所有的增删改查操作,无论成功还是失败,都要有明确的提示消息,不能让用户点了按钮之后毫无反应。这个小细节在答辩演示时特别加分,因为很多项目一演示就“静默失败”。
3. 数据库设计是第一个隐藏答辩题:五张核心表和座位数据模型
数据库设计在我见过的答辩中几乎是必问环节,大概率会从“表结构怎么设计、为什么这么拆分”切入。这块如果含糊其辞,后面全盘被动。我建议从一开始就按五张核心表的思路来设计,不要图省事把多个概念塞到一张表里。
3.1 用户、电影、影厅、排片、订单五张表怎么拆
五张核心表分别是用户表、电影表、影厅表、场次表(排片表)、订单表,它们的关系非常清晰,我也顺便把关键字段写出来:
| 表名 | 关键字段 | 说明 |
|---|---|---|
| user | id, username, password, nickname, balance, status | 保存用户和管理员,用role字段区分身份 |
| movie | id, title, poster, duration, type, description, publish_date, status | status表示上映或下架 |
| hall | id, name, row_count, col_count | 影厅基础信息,座位矩阵由行列数决定 |
| schedule | id, movie_id, hall_id, start_time, price, remain_seats | 排片表,每一条代表一场电影 |
| orders | id, user_id, schedule_id, seat_ids, total_price, status, create_time, pay_time | 订单表,seat_ids存所选座位的标识列表 |
订单表里有一列seat_ids,存储的是“排片+座位坐标”的组合标识,比如“3排5座,3排6座”。有人会问这违反了第一范式吗?严格来说这是一个可以商量的设计,但对于毕业设计来说,这种冗余存储方式极大简化了订单与座位的关联逻辑,同时只要配合“座位状态表”做并发校验,就不会产生脏数据。数据库设计追求的是业务场景下的合适,不是理论上的绝对完美。
3.2 座位为什么不存一整张座位图
最容易踩坑的设计是:在影厅表里放一个类似“0000000000/0000000000/...”的字符串或者JSON,直接表示整张座位图的售出状态。这个方案看起来简单,实际上会在选座、退票、释放座位时造成极大的解析和更新麻烦,而且很难保证并发下数据一致。
推荐的做法是单独设计一张场次座位的状态表,我通常叫它seat_status表:
| 字段 | 说明 |
|---|---|
| id | 主键 |
| schedule_id | 关联场次 |
| row_no | 排号 |
| col_no | 列号 |
| status | 0可售、1锁定、2已售 |
| order_id | 当订单关联时记录订单号 |
每次开一个场次时,用Java循环生成影厅行列数对应的所有座位记录,每张记录状态为0。用户选座时,后台就把选中的座位状态从0改成1,并写入order_id;支付成功后把状态从1改成2;订单超时或退票时把状态回退为0。这个表虽然数据量会随场次增多而膨胀,但对于毕设规模的系统完全不是问题,而且思路极其清晰,答辩解释起来也顺。
3.3 订单状态用状态机管理,而不是简单设一个“未支付/已支付”
订单状态是另一个值得在答辩时主动展开的设计点,它体现的是工程化思维。
订单的状态按流程推进设计为:待支付、已支付、已取消、已退票、已完成。
- 待支付:用户提交订单但未在限定时间内完成支付。
- 已支付:支付成功且没有退票。
- 已取消:待支付订单超过15分钟未支付,系统自动取消,座位恢复可售。
- 已退票:已支付订单由用户申请退票,管理员审核后退款,座位释放。
- 已完成:电影放映结束且订单未退票,系统或手动将其置为完成。
整条状态线是单向的,但“待支付到已取消”“已支付到已退票”这两个分支最容易出错,因为都涉及到座位释放。很多同学做退票时只改订单状态,忘记释放座位,导致明明退掉的电影座位仍然显示已售。所以,任何涉及座位回退的操作都必须同时更新订单状态和座位状态,并且要放在同一个数据库事务里,这个点我会在下一章详细展开。
4. 并发选座与支付核心:不会超卖的演示才拿得上台面
影院订票系统的高频答辩问题几乎都集中在这儿:同一场电影同一个座位,两个用户同时选,你怎么保证不会超卖?这个问题如果答不好,之前的所有工作都会被扣分。更现实的是,如果不做并发控制,演示时真的有人同时点就会出bug。
4.1 演示现场最容易翻车的一个动作
我见过好几个项目演示的翻车现场:两个浏览器窗口同时登录两个账号,选同一个座位的不同时间点提交订单,系统居然都显示“选座成功”。最后数据库里同一场次同一座位出现了两条订单,两个人都认为票已经买到。原因很简单,代码里只做了这样的逻辑:先查一下座位状态是否可售,如果可售就插入订单把座位改成占用。
“先查询再更新”的模式在单线程下没有问题,但并发场景下查询和更新之间插入了其他操作,就出现了所谓的时间差问题。更准确地说,两笔事务同时读到同一个可用座位,然后各自认为自己拥有它。这个场景必须用数据库层面的机制去解决,而不是靠Java代码里加一个普通的if判断。
4.2 行锁与剩余票数校验:两条实现路线
我给这套系统设计了两条保险线,第一条是依靠数据库行级锁,第二条是依靠更新语句的原子性校验。
核心逻辑可以写成这样的伪代码:
public OrderResult submitOrder(Long scheduleId, List<Long> seatIds, Long userId) { // 进入事务 // 1. 对场次记录加行锁,同一场次的并发请求会在这里排队 Schedule schedule = scheduleDao.selectByIdForUpdate(scheduleId); if (schedule.getRemainSeats() < seatIds.size()) { return OrderResult.fail("余票不足"); } // 2. 校验选中的座位是否仍然全部可售 List<SeatStatus> occupiedSeats = seatStatusDao.selectOccupied(scheduleId, seatIds); if (!occupiedSeats.isEmpty()) { return OrderResult.fail("座位刚刚被选走,请重新选择"); } // 3. 置为锁定并关联订单 // 4. 扣减余票 scheduleDao.decreaseRemainSeats(scheduleId, seatIds.size()); // 5. 提交事务 }第一步的select for update就是对数据库中的某一行加锁,它保证同一时刻只有一个线程能读取并修改这个场次的记录。其他用户同时操作同一场次时,只能排队等待,这样“查询余量”和“扣减余量”就变成了一个原子操作序列。
第二条保险线是在扣减余票时使用带条件的更新语句:
update schedule set remain_seats = remain_seats - #{num}, version = version + 1 where id = #{scheduleId} and remain_seats >= #{num}这条SQL的巧妙之处在于,如果剩余票数不够,更新语句影响的行数是0,Java层就能据此判断操作失败,避免把余票扣成负数。这两条线缺一不可:行锁解决同一场次的并发串行化,条件更新解决极端情况下的余票保护。
4.3 座位唯一性约束再加一道硬保险
除了代码层控制,我强烈建议你们在座位状态表上再加一个唯一索引:
alter table seat_status add unique index uk_schedule_seat (schedule_id, row_no, col_no);只要场次和座位坐标组合重复,数据库就会报错,这条约束是最后一道硬保险。即便代码逻辑偶尔漏判,数据库层面也能拦住重复选座。答辩时把这个设计讲出来,老师一听就知道你理解“数据库约束优先于应用层判断”的原理。
4.4 订单超时释放:定时任务与状态机联动
待支付订单15分钟后自动取消,需要在后端做一个定时扫描。用JavaWeb原生的定时器即可,不用上消息队列:
- 每隔一分钟扫描一次订单表,找出所有“待支付”且创建时间超过15分钟的订单。
- 将订单状态改为“已取消”。
- 释放该订单关联的所有座位,状态从1改为0。
- 恢复场次的余票数量。
这里的关键点在于,扫描、改状态、释放座位必须在一个事务里完成,不能分多次操作,否则会出现“订单取消成功但座位还占着”的不一致情况。我写代码的时候把第三步和第四步封装成同一个服务方法,并且用事务传播保证原子性。视频演示中我会专门做这样一个演示:创建订单不支付,等15分钟后刷新页面,看到订单自动变成已取消,同时去选座页看座位已经重新变成可售。
5. 前端功能怎么做出“能打”的质感:座位图、倒计时和状态反馈
前端部分不一定需要多花哨,但至少不能让答辩老师觉得“这个页面像是2005年写的”。我总结出三个投入产出比最高的前端设计点,它们都不需要复杂的前端框架,纯JSP加JavaScript就能实现,但视觉效果和交互体验都会明显上一个台阶。
5.1 排期列表与电影详情的数据组织方式
电影详情页和场次列表之间有一个数据传递逻辑,很多人在开发时容易弄混。我推荐的做法是:电影详情页只负责展示电影本身的资料,然后根据电影ID在前端发起一次查询,获取该电影的有效场次列表,按日期分组展示。
当用户点击某个场次进入选座页时,URL中带三个关键参数:场次ID、电影ID、影厅ID。选座页通过场次ID查出排片信息,通过电影ID查出电影名称和海报,通过影厅ID查出座位矩阵的行列数,然后动态生成座位图。这种按需查询的方式让页面职责非常清晰,数据流向也不会乱。
值得注意的细节是场次列表的排序规则。不要只按开始时间排,最好先按日期分组,再按“场次开始时间排序”,同时把“已经开映超过15分钟”的场次置灰,点击时给出“该场次已开映,无法购票”的提示。这个小细节虽然不起眼,但能让你的系统看起来是真的被人用过,而不是为了演示而搭的空壳。
5.2 座位图组件:HTML表格加二维数组就够了
座位图是所有页面里最核心的功能,也是最具展示效果的一块。没有必要用Canvas或第三方组件库,一个HTML表格完全可以实现:
<table id="seatMap"></table>JavaScript端根据影厅行列数据动态生成行和列。每个单元格用一个div或button表示一个座位,自定义属性存放排号列号:
for (let row = 1; row <= rowCount; row++) { for (let col = 1; col <= colCount; col++) { const seat = document.createElement('div'); seat.dataset.row = row; seat.dataset.col = col; seat.className = 'seat-available'; seat.onclick = toggleSeat(seat); seatMap.appendChild(seat); } }座位有三种视觉状态:灰色代表已售,绿色代表可售,用户点击选中后变成红色。选中座位的逻辑需要维护一个已选列表,每次点击判断状态,如果座位状态是可售并且不在已选列表中就加入,如果在列表中则移除,每操作一次都重新计算总价,显示在页面下方的结算栏中。
这里有一个常见的坑:某些同学直接在页面上通过判断样式类来处理逻辑,比如拿到“灰色座位”会去阻止点击,但是座位状态是后端返回的,用户刷选座页的时候如果座位已经被别人锁定,前端可能还是绿色。所以我建议前端初始渲染时直接使用后端返回的status值,并且提交订单时后端必须再次校验状态,而不是信任前端传给后端的“我已经检查过”这个信号。
5.3 支付倒计时和订单状态实时刷新
倒计时的实现并不难,但细节决定成败。核心思路是:后端返回订单的截止时间,前端用当前时间与截止时间做差值,用setInterval每秒更新一次显示:
const endTime = new Date(order.expireTime).getTime(); const timer = setInterval(() => { const diff = endTime - Date.now(); if (diff <= 0) { clearInterval(timer); alert('订单已超时,正在刷新...'); window.location.reload(); } else { const minutes = Math.floor(diff / 60000); const seconds = Math.floor((diff % 60000) / 1000); document.getElementById('countdown').textContent = minutes + '分' + seconds + '秒'; } }, 1000);这里要注意,不要在前端用“设置一个15分钟的定时器”这种方式,因为页面一旦刷新,定时器就丢了。所以必须从后端拿截止时间,用时间戳计算差值,这样即使页面刷新,倒计时也不会重置。
我的订单页面也做了类似机制:每隔一段时间自动刷新一次订单状态,确保用户看到“待支付变成已取消”是自动发生的,而不是手动刷新后才看到。这种实时感在后端开发里其实很简单,就是AJAX轮询,但它能让整个项目的交互体验非常接近真实商业系统。
6. 论文、PPT、演示视频的节奏安排:可以直接套用的框架
毕业设计不只是写代码,论文、PPT和演示视频的质量同样决定最终成绩。很多同学程序做得不错,但论文写得像流水账,PPT密密麻麻全是代码,视频演示操作顺序混乱,最后分数不理想。这里我整理出一份可以直接改造使用的材料框架。
6.1 论文的章节骨架
一篇标准的毕设论文,我建议按照下面这个骨架展开,大概在五章到七章之间,每章给足内容,避免出现“某章几乎全是截图”的情况:
| 章节 | 核心内容 | 写作思路 |
|---|---|---|
| 绪论 | 背景、意义、国内外现状、研究内容 | 把“为什么做影院订票系统”讲清楚,不要大段复制百科 |
| 相关技术介绍 | Java、Servlet、JSP、MySQL、Tomcat | 重点是讲这些技术为什么适合这个场景,不要堆名词 |
| 需求分析 | 可行性分析、功能需求、用例图、数据字典 | 对照功能清单逐条写,用例图不要画得太复杂 |
| 系统设计 | 总体架构、模块设计、数据库设计 | 画清晰的分层架构图和E-R图,数据库设计放核心表说明 |
| 系统实现 | 各模块实现过程与核心代码截图 | 每段讲清“怎么做的”和“为什么这么做”,选核心代码即可 |
| 系统测试 | 测试环境、功能测试用例、测试结果 | 用表格写测试用例,含输入输出和预期结果 |
| 总结与展望 | 完成内容、不足、后期改进方向 | 少写空洞展望,多写实际收获 |
论文最忌讳的是把代码全文贴上去,这是所有老师都反感的行为。我建议在系统实现章节里,每个模块贴不超过15行核心代码,配三四行解释,再贴一张运行界面截图就够了。更多篇幅留给需求分析和设计思路,这部分才真正反映你“为什么会做这个系统”。
6.2 PPT怎么做到“演示型”而不是“朗读型”
答辩PPT通常是10到15页,我采用的是这个结构:
- 第1页:题目、姓名、指导教师、日期
- 第2页:选题背景与研究意义
- 第3页:系统需求与功能概览
- 第4页:系统架构图
- 第5页:核心功能模块说明
- 第6页:数据库设计
- 第7页:核心业务流程图
- 第8页:系统界面展示
- 第9页:关键问题解决,这里放并发控制、超时释放
- 第10页:系统测试结果
- 第11页:总结与不足
PPT每一个页面的文字量控制在两三行以内,多用架构图、截图和表格。答辩时口述的内容不是念PPT,而是围绕每个页面展开讲。特别是第9页“关键问题解决”,这是全场最重点的页面,我会现场打开代码和数据库表,先讲问题现象,再讲解决方案,最后演示结果。这种“问题导向”的讲述方式比按页面顺序平铺直叙要抓人得多。
6.3 演示视频的脚本设计与录制顺序
演示视频需要录成一个流畅的连贯流程,而不是一堆片段拼凑。我的建议是按照“用户端完整购票流程、订单管理、管理端维护、难点演示”的顺序来录:
- 注册一个账号并登录,演示电影列表和详情。
- 选择一个场次,演示选座和提交订单,现场完成支付模拟。
- 马上回到我的订单页面,展示待支付和已支付两种状态。
- 创建一笔订单不支付,快进或等待超时,展示系统自动取消和座位释放。
- 切换管理员账号,演示新增电影、创建场次、处理退票。
- 最后专门演示并发场景:开两个浏览器抢同一个座位,一个成功一个失败。
录制前要把数据库恢复到初始数据状态,保证操作过程中不会因为数据不完整而卡壳。屏幕分辨率建议设为1920宽度,字体别开太小,视频控制在8到12分钟之间,重点看核心流程,不要把所有页面都点一遍。如果网站运行时有报错,不要中断录制,先从容关闭页面再重新进入,现场急救能力反而更能体现你对系统的熟悉程度。
7. 实测部署中的高发问题与排查记录
代码写完只是开始,真正跑到别人机器上还能正常工作才是真本事。我在部署和测试这套影院订票系统的过程中遇到过不少问题,挑几个出现频率最高、也最影响体验的写在这里,你们照着排查可以省很多时间。
7.1 环境配置三件套:编码、时区、连接参数
中文乱码是JavaWeb项目最常见的面试题,同时也是实际部署最常见的坑。解决方法是三层同时设置:
第一,数据库连接URL加上编码参数和时区参数:
jdbc:mysql://localhost:3306/cinema?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai这里我建议显式写明serverTimezone,否则高版本MySQL驱动会因为时区参数缺失而报错。第二,MySQL表结构统一使用utf8mb4字符集,比utf8更稳,可以覆盖特殊字符。第三,Tomcat的server.xml里连接器配置加上URIEncoding参数,新手最容易漏掉这一层。
如果三层全部设置好之后页面仍然乱码,那基本可以确定是JSP页面文件本身的编码没有统一,确保每个JSP页面头部都声明UTF-8编码即可。
7.2 启动与访问的几个经典异常排查
“Tomcat能启动但页面一访问就404或500”是高发问题,通常有三个原因:
- 项目没有正确部署到Tomcat的webapps目录下。用IDEA内置Tomcat运行的时候,要注意在Artifacts配置中添加“lib”目录,否则依赖的JAR包不会被打进部署产物,运行时会报找不到类。
- 静态资源路径写错。JSP页面里的CSS、JS、图片尽量用绝对路径,比如${pageContext.request.contextPath}/css/style.css,不要用相对路径,否则在不同层级页面跳转时资源会丢失。
- 数据库连接池初始化失败。很多框架的配置在本地开发环境没问题,换到另一台机器上跑就报连接超时,多为MySQL没启动或密码不对,先启动MySQL并用命令行登录验证,再去排查代码问题。
如果出现500错误,第一时间去Tomcat的logs目录查看localhost日期.log文件,尾部就是真正的异常堆栈。不要盯着页面上的抽象错误信息猜,看堆栈永远比猜快得多。
7.3 这套源码后续还能怎么改
项目做完之后如果想提升,我建议按下面的优先级顺序去扩展:第一优先级是引入MyBatis替换JDBC,然后在此基础上把Spring和SpringMVC搭进来,这样项目就能从“传统JavaWeb”升级成“SSM架构”,论文里的技术描述也会更漂亮。第二优先级是增加查询分页和搜索条件,尤其是管理端的订单分页和用户分页。第三优先级是增加一些展示性的功能,比如电影评论、评分、热度排行,这些功能开发量不大但视觉效果好。
如果时间和精力允许,可以继续把“首页缓存片单”用定时任务做一下,或者把支付模拟页改成二维码样式,让演示更接近真实商用场景。但不管怎么扩展,核心的主流程事务、并发控制、订单状态流转不要动,这些才是整个项目的灵魂,也是你在答辩时能从容讲清楚的东西。