1. 选题复盘:一个“看起来普通”的购物网站,为什么能成毕业设计的好牌
每年毕业设计选题那阵子,都会有学弟学妹跑来问我:“师兄,做什么题不容易翻车?”我的答案通常很简单:做个像样的中小型Web项目,比选题看起来高大上但最后跑不通的更稳妥。飘香水果购物网站这个题目,就是典型代表。
先说结论:这个项目属于“一听就知道要做什么”的经典题目。但经典不等于水。它完整覆盖了一个真实业务系统的所有环节——游客浏览、用户注册登录、商品搜索与分页、购物车管理、下单支付、后台管理、数据统计。换句话说,它把前后端数据交互的逻辑全走了一遍,而且业务量适中,本科生在两个月内完全可以独立做完。
1.1 为什么水果购物网站比“XX管理系统”更适合当毕设
很多同学第一反应是做图书管理系统、宿舍管理系统,这类题目确实好写,但存在一个硬伤:业务场景太扁平。管理系统的核心无非是增删改查,老师一眼就能看穿工作量。购物网站不一样,它含有一条完整的用户行为链路:
- 用户注册登录后才能下单,这涉及身份认证。
- 商品有分类、有上下架状态,涉及复杂查询条件。
- 购物车和订单有关联,涉及数据一致性。
- 下单时要从购物车生成订单、扣减库存、清空购物车,这是一个典型的事务场景。
- 后台还要有商品、订单、用户、公告等管理模块,能体现完整的RBAC权限思想。
选题时我给自己定了三条标准:业务能讲清楚、技术能体现难度、答辩能演示出来。水果购物网站三条全中。更妙的是,水果这个主题自带演示效果——数据库里塞满苹果、香蕉、车厘子的图片,页面做出来色彩鲜艳,答辩时比一堆灰蒙蒙的管理界面好看得多。
1.2 从题目拆出来的完整需求清单
拿到题目先别急着敲代码,第一步一定是做需求拆解。我当时的拆法是分成“前台用户端”和“后台管理端”两条线,每条线再按“游客、用户、管理员”三类角色去梳理功能点:
前台用户端:
- 游客:浏览首页轮播图、查看商品列表、按分类查看商品、查看商品详情、搜索商品。
- 注册用户:登录注册、修改个人信息、管理收货地址、加入购物车、修改购物车数量、删除购物车商品、订单结算、模拟支付、查看订单状态、取消订单、对已购商品发表评论。
- 所有用户:查看公告、使用快捷搜索。
后台管理端:
- 管理员登录。
- 商品管理:新增商品、编辑商品、上下架、设置库存、上传商品图片。
- 分类管理:商品分类的增删改。
- 订单管理:查看订单列表、订单详情、订单发货、退款处理。
- 用户管理:查看用户列表、禁用用户账号。
- 公告管理:发布公告、编辑公告。
- 数据统计:用图表展示商品销量和用户增长趋势。
这个清单在写论文时也有大用。第三章需求分析的用例图、第四章的功能模块图,都可以直接从这张清单生成。所以选题之后的第一个动作永远是“把需求写满一页纸”,而不是去新建Spring Boot工程。
2. 技术选型的几个岔路口:Spring Boot版本、前端模板、中间件怎么定
技术选型这块,我的原则是“不炫技,但要讲得出为什么”。毕设答辩老师不会要求你用最新的技术栈,但一定会问“你为什么选它”。所以选型的每一环都要有一个能自圆其说的理由。
2.1 后端框架为什么是Spring Boot而不是传统SSM
这个题目直接点名要求Spring Boot,原因其实很直白:它已经是当前Java服务端开发的绝对主流。我在对比时特意去看了Spring Boot和SSM的差异:
- SSM(Spring + Spring MVC + MyBatis)需要手动配置web.xml、spring-mvc.xml、mybatis-config.xml等一堆XML文件,光是配置就能耗掉几天,而且配置错误很难排查。
- Spring Boot的核心是自动配置和约定优于配置,内嵌Tomcat,一个main方法就能启动,极大降低了环境搭建成本。
- 毕设的核心时间应该花在业务逻辑上,而不是花在“让Tomcat不报404”上。
所以用Spring Boot不是跟风,而是把省下来的配置时间投入到业务代码和论文撰写里。这里提一句:如果你在网上下载的参考工程用的是Spring Boot,注意看它的版本。Spring Boot 3.x要求JDK 17以上,而很多学校实验室的电脑还停留在JDK 8,这种情况下老老实实选Spring Boot 2.7.x,避免到演示前一天才发现环境起不来。
2.2 前端方案的取舍:Thymeleaf服务端渲染还是Vue前后端分离
前端是很多Java方向学生的痛点。我当时的决策过程是这样的:
方案一:前后端分离,Vue3 + Element Plus写界面,通过Axios调REST接口。好处是界面现代、代码结构清晰,答辩时“前后端分离”本身就是加分项。坏处是工程复杂度翻倍,需要安装Node.js、使用npm构建,如果本地环境不熟悉,联调阶段非常痛苦。
方案二:Thymeleaf模板引擎 + Bootstrap + 少量原生JavaScript。后端接口直接返回页面,数据通过模型对象渲染到HTML。好处是只有一个Spring Boot工程,打包成一个jar包就能运行,部署演示极其稳定;坏处是页面交互逻辑没那么炫。
我最后选了方案二,并做了一点升级:前台用户页面用Thymeleaf + Bootstrap,走服务端渲染;后台管理页面用Layui的表格和表单组件,提升开发效率。选择这个方案最现实的理由是——答辩环境不可控。机房可能没有Node环境,网络可能不稳定,前后端分离项目一旦前端构建不出来,整个演示就凉了。而一个jar包加一个MySQL脚本,在任何一台电脑上都能五分钟跑起来。
2.3 辅助组件的配套选择
技术栈不只是Spring Boot本身,我在这个项目里还搭配了以下组件,每个都对应一个明确痛点:
| 组件 | 解决的问题 | 替代方案 |
|---|---|---|
| MyBatis Plus | 简化单表CRUD,内置分页插件,减少手写XML | 原生MyBatis、Spring Data JPA |
| MySQL 8.0 | 数据存储,开箱即用 | 其他关系型数据库均可 |
| Redis | 缓存首页公告和验证码,提升访问速度 | 可省略,但加上能体现技术深度 |
| JWT(jjwt) | 无状态登录认证,用户每次请求携带token | Session、Spring Security |
| Lombok | 减少实体类的getter/setter样板代码 | 手动生成 |
| Hutool | 验证码生成、文件上传等工具方法 | Google Guava |
| ECharts | 后台商品销量统计图表 | Chart.js |
有人会问:毕设有必要上Redis吗?我的看法是,如果时间充裕就加。因为论文的技术介绍章节总要写点东西,Redis缓存是个很容易讲清楚的亮点:首页公告高频读取,放入Redis可以减少数据库压力。但要注意,用了Redis就必须保证功能演示前也把Redis服务启动,否则代码里一调用缓存就报错,这是很常见的翻车点。
3. 数据库设计的关键点:订单快照、状态机、库存扣减,一个都不能少
数据库设计是这篇毕设真正的重头戏。很多同学数据库就建三四张表,硬把所有信息塞在一起,结果写订单功能时才发现字段不够用。我的建议是第一版就按12张表来设计,后面实现每个功能都会顺利很多。
3.1 核心表结构清单
我按业务模块,把表拆成用户域、商品域、交易域、内容域四组。下面这张表是当时的完整清单,字段名属于“可直接抄作业”级别:
| 表名 | 主要字段 | 说明 |
|---|---|---|
| t_user | id, username, password, nickname, phone, avatar, status | 用户表,status控制账号是否禁用 |
| t_admin | id, username, password, last_login_time | 管理员表 |
| t_category | id, name, parent_id, sort, status | 商品分类表,支持两级分类 |
| t_product | id, category_id, name, subtitle, main_image, detail, price, stock, sales, status | 商品表,status=1为上架,0为下架 |
| t_banner | id, image, link, sort, status | 首页轮播图 |
| t_address | id, user_id, consignee, phone, province, city, district, detail, is_default | 收货地址 |
| t_cart | id, user_id, product_id, quantity, checked | 购物车,建议加唯一索引(user_id, product_id) |
| t_order | id, order_no, user_id, total_amount, pay_type, status, create_time, pay_time, deliver_time, finish_time | 订单主表 |
| t_order_item | id, order_id, product_id, product_name, product_image, price, quantity, subtotal | 订单明细表,商品信息为下单时快照 |
| t_comment | id, user_id, product_id, order_id, content, rating, images, status | 商品评价 |
| t_notice | id, title, content, create_time, status | 公告表 |
| t_favorite | id, user_id, product_id, create_time | 商品收藏,可选模块 |
这里重点提醒三个设计细节。
第一,用户表的密码字段不要存明文。我用的是MD5加盐的方式,虽然业界现在更推荐BCrypt,但毕设阶段写清楚“加盐哈希”已经比一片乌云的明文存储强太多了。答辩老师问密码安全时就讲这个。
第二,商品表和订单明细表之间的关联不要做成强依赖。订单明细里必须冗余product_name、product_image、price这几个字段,因为商品价格随时可能调整,而订单一旦生成就是历史事实。现在改分了只能影响以后的新订单,不能把用户半年前买水果的记录也改了。这就是“订单快照”的用意。
第三,轮播图、公告这类内容表一定要带一个sort排序字段和status启用状态。否则后台上传了一张新图,却没法手动控制它在首页的顺序和展示状态,管理端就形同虚设。
3.2 订单状态机的流转设计
订单是这个项目的数据核心,我把它单独画了一张状态流转表,论文和答辩都直接复用:
| 状态值 | 状态含义 | 触发动作 | 记录字段 |
|---|---|---|---|
| 0 | 待支付 | 用户提交订单 | create_time |
| 1 | 已支付 | 模拟支付成功 | pay_time |
| 2 | 已发货 | 后台点击发货 | deliver_time |
| 3 | 已完成 | 用户确认收货或超时自动完成 | finish_time |
| -1 | 已取消 | 待支付时用户取消 | update_time |
| 4 | 已退款 | 后台处理退款申请 | update_time |
订单状态机的意义在于,系统里任何一张订单在任何时刻都落在某一个明确状态,管理后台的订单列表也靠它过滤。答辩时老师如果问“取消订单后库存怎么办”,你直接回答“取消且未支付时把商品库存回补”——这背后就是状态机加上一次库存更新操作。
3.3 库存扣减的防超卖处理
库存扣减虽然只是update语句,但在设计上必须防住“超卖”。所谓超卖,就是两个人同时各买10个苹果,但库存只剩15个,结果两个订单都成功了,库存变成-5。
我当时的实现是在SQL层面做条件更新:
Integer count = productMapper.deductStock(productId, quantity); // 对应SQL: // UPDATE t_product SET stock = stock - #{quantity} // WHERE id = #{productId} AND stock >= #{quantity}这行SQL的意思是:只有库存大于等于购买数量时才执行扣减,受影响行数count为0就说明库存不够,抛出业务异常。这个写法不复杂,但它是真正的“工程级”处理思路,比先查再改的方式可靠,而且能在论文里明确写出“利用数据库行锁保证了库存一致性”。
4. 主干功能怎么落地:从登录到下单,核心代码这样写
这个项目最需要动手的核心模块有四个:登录认证、商品分页搜索、购物车、下单事务。我按照实际开发中碰到问题的顺序来讲,这些代码片段都经过了完整测试,可以直接参考改造。
4.1 JWT登录认证的实现要点
登录流程不复杂:用户提交用户名密码,校验通过后签发一个JWT令牌返回给前端,前端将它存到localStorage。之后每次请求在Header里带上Authorization: token,后端通过拦截器解析。核心代码分两段。
签发token的逻辑:
String token = JWT.create() .withClaim("userId", user.getId()) .withClaim("username", user.getUsername()) .withExpiresAt(new Date(System.currentTimeMillis() + 2 * 60 * 60 * 1000)) .sign(Algorithm.HMAC256("your-secret-key"));拦截器里解析token:
String token = request.getHeader("Authorization"); try { DecodedJWT jwt = JWT.require(Algorithm.HMAC256("your-secret-key")) .build() .verify(token); request.setAttribute("userId", jwt.getClaim("userId").asInt()); } catch (Exception e) { response.setStatus(401); response.getWriter().write("未登录或登录已过期"); return false; }这里有个容易踩的坑:拦截器会拦截所有请求,但登录接口、注册接口、商品列表、商品详情这些游客也能访问的接口必须放行。我直接在拦截器配置里维护了一个白名单列表,凡是需要登录的功能才进入鉴权,这样既安全又不会把游客挡在门外。
4.2 商品分页和关键字搜索
商品列表是前台访问量最大的页面,必须支持分类筛选、关键字模糊搜索和分页。我的实现思路是用MyBatis Plus的LambdaQueryWrapper,避免手写复杂XML:
LambdaQueryWrapper<Product> wrapper = new LambdaQueryWrapper<>(); if (StringUtils.hasText(keyword)) { wrapper.like(Product::getName, keyword) .or().like(Product::getSubtitle, keyword); } if (categoryId != null) { wrapper.eq(Product::getCategoryId, categoryId); } wrapper.eq(Product::getStatus, 1) .orderByDesc(Product::getSales); Page<Product> page = productMapper.selectPage( new Page<>(pageNum, pageSize), wrapper);这里的keyword来自前端搜索框,categoryId来自侧边栏分类点击。整套下来整个前端只需要传pageNum、pageSize、keyword、categoryId四个参数,后端返回分页对象包含records、total、current等字段,前端用Thymeleaf的th:each遍历列表,再用th:href拼接下一页的分页链接,逻辑非常直白。
4.3 购物车的增删改逻辑
购物车的设计重点在“加入购物车”这一步。一个用户点击很多次“加入购物车”,不应该产生多条同商品的记录,而是把数量累加。所以核心逻辑是先查再增改:
Cart cart = cartMapper.findByUserIdAndProductId(userId, productId); if (cart == null) { Cart newCart = new Cart(); newCart.setUserId(userId); newCart.setProductId(productId); newCart.setQuantity(quantity); cartMapper.insert(newCart); } else { cart.setQuantity(cart.getQuantity() + quantity); cartMapper.updateById(cart); }修改数量时前端传新数量和购物车项id,后端直接update。删除购物车项就一行delete。这里有一个体验细节:购物车列表接口要join商品表查出商品名称、当前价格、主图,这样前端才能展示完整的商品信息,而且价格展示用商品表的实时价格,不要用购物车表里冗余的旧价格。
4.4 下单事务与库存扣减的完整链路
下单是最能体现功力的部分,因为多个数据操作必须保证“要么全成功,要么全失败”。比如用户下单时库存扣了、订单生成了,但清空购物车失败,这就会造成数据不一致,所以整体必须加事务。
@Transactional(rollbackFor = Exception.class) public Result createOrder(OrderCreateRequest request) { // 1. 创建主订单,状态设为待支付 Order order = buildOrder(request); orderMapper.insert(order); // 2. 遍历购物车选中的商品,写入订单明细 for (CartItem item : request.getItems()) { OrderItem orderItem = new OrderItem(); orderItem.setOrderId(order.getId()); orderItem.setProductId(item.getProductId()); orderItem.setProductName(item.getProductName()); orderItem.setPrice(item.getPrice()); orderItem.setQuantity(item.getQuantity()); orderItemMapper.insert(orderItem); // 3. 扣减库存,数量不足直接抛异常触发回滚 int rows = productMapper.deductStock(item.getProductId(), item.getQuantity()); if (rows == 0) { throw new BizException("商品库存不足"); } } // 4. 清空购物车中已下单的商品 cartMapper.deleteBatchIds(request.getItemIds()); return Result.success(order); }这段代码里我最想强调的就是@Transactional注解。很多参考工程里写了这个注解但没注意rollbackFor = Exception.class,结果业务异常抛出后事务没回滚。原因在于Spring默认只对RuntimeException回滚,自定义的BizException如果不继承RuntimeException,就不会触发回滚。这个细节在答辩时讲出来,老师会认为你真懂事务机制。
下单完成后的模拟支付更简单:前端跳到一个收银台页面,展示订单号和金额,点击“确认支付”,后端执行UPDATE t_order SET status = 1, pay_time = NOW() WHERE order_no = ?。我没有接真实支付,论文里明确写“为保障安全,支付功能为模拟实现,可扩展接入支付宝沙箱环境”,这就够了。
4.5 统一结果返回和全局异常处理
为了让前后端交互整洁,我加了一个Result类,所有接口都返回统一格式:
public class Result<T> { private Integer code; // 200成功,其他失败 private String msg; private T data; }再用@RestControllerAdvice做全局异常处理,业务异常统一返回Result.fail(e.getMessage()),像库存不足这种提示就能直接展示到前端页面。这个设计在论文的“系统实现特色”部分是个不错的亮点,篇幅不大但显得项目规范。
5. 论文和答辩怎么讲清楚这个项目:从目录到高频问答的完整套路
很多人代码写完了,论文却不知道怎么下笔。我的经验是:论文不要最后突击,每完成一个模块立刻写对应章节。代码还在脑子里热乎时写出来的设计思路,比事后对着代码回忆要流畅得多,查重风险也低。
5.1 论文目录框架
我的论文目录大致如下,这个结构基本是所有“设计与实现”类毕设的标准模板:
- 第一章 绪论:选题背景与意义、国内外研究现状、论文组织结构。
- 第二章 相关技术介绍:Spring Boot、MyBatis Plus、MySQL、Redis、JWT、Thymeleaf。
- 第三章 需求分析:可行性分析(技术、经济、操作)、功能性需求(用例图+用例描述)、非功能性需求(性能、安全、易用性)。
- 第四章 系统设计:总体架构设计、功能模块设计、数据库设计(ER图、表结构)、下单流程时序图。
- 第五章 系统实现:按前台用户模块、后台管理模块逐一截图并搭配核心代码说明。
- 第六章 系统测试:测试环境、功能测试用例表、测试结果分析。
- 第七章 总结与展望:项目完成情况、存在的不足、后续改进方向。
这里要特别提醒两个地方。
一个是“国内外研究现状”写得别太像百度百科。我的处理是分两条线:国外线侧重生鲜电商平台的技术模式,国内线写京东到家、盒马等平台的发展脉络,最后收一句“本项目正是在此背景下,通过Spring Boot实现一个功能完整的水果购物网站”。这段落在查重环节风险极高,一定要用自己的话改写到不像任何百科原文。
另一个是第三章不能只放一张用例图。老师更看重你能否把“用户下单”这种用例拆成前置条件、基本流程、异常流程。我写用例描述时基本是按这个格式:前置条件(用户已登录、购物车有商品)、基本流(提交订单→生成待支付订单→支付→状态改为已支付)、异常流(库存不足→提示并中断)。这样写出来的需求分析,含金量立马上来了。
5.2 答辩PPT的页面安排
PPT我控制在12页左右,再多老师就没耐心看了。页面顺序如下:
- 封面:题目、姓名、学号、指导老师。
- 目录。
- 选题背景与意义:带一两句时代背景,快速过。
- 需求分析:放功能模块总览图,强调三类角色。
- 技术选型:列出技术栈表格,每个技术给一句话理由。
- 系统总体设计:放架构图(浏览器→Controller→Service→Mapper→MySQL/Redis)。
- 数据库设计:放精简版ER图,只展示用户、商品、订单、订单明细的关系。
- 核心功能实现:讲两个亮点,一是JWT登录认证,二是下单事务与库存扣减。
- 系统演示:截图放几个重点页面,现场再演示一遍。
- 系统测试:展示测试用例表,说明覆盖率。
- 总结与展望:一句话概括成果,提两三个未来方向。
- 致谢页:请各位老师批评指正。
5.3 老师最爱问的高频问题及回答思路
我把答辩准备的重点押在下面这些问题上,每个都准备了“一句话核心+两句展开”的答法:
- 为什么选择Spring Boot?答:简化配置,内嵌容器,自动装配,是目前Java主流的开发框架,生态完善。
- 密码怎么存储的?答:使用MD5加盐哈希,避免明文存储;若加强安全可换成BCrypt。
- 购物车和订单的区别?答:购物车是预下单状态,订单是用户明确购买意愿后的业务单据,生成订单时会对商品信息做快照。
- 库存并发超卖怎么办?答:用
UPDATE ... WHERE stock >= quantity条件扣减,受影响行数为0则中断,防止超卖。 - 支付是真实的吗?答:模拟支付,出于安全考虑不接入真实渠道,但预留了支付接口,可扩展支付宝沙箱。
- 用户禁用了还能登录吗?答:登录校验时会检查status字段,为0则拒绝登录并提示账号异常。
- 订单超时未支付怎么处理?答:可以在后续扩展中使用定时任务扫描超过30分钟未支付订单,自动将状态改为已取消并回补库存。
- 商品删除时分类下有商品怎么办?答:分类删除前会统计该分类下商品数,非空则禁止删除,保证数据完整性。
- 这个项目的难点在哪?答:难点集中在订单事务的一致性控制和库存扣减的并发安全。
- 测试是怎么做的?答:功能测试按模块设计测试用例,接口层面用Postman验证,并记录测试结果。
答辩的核心是自信,但自信的前提是真懂自己写的代码。哪怕源码是从别处参考来的,只要把上面这些问题吃透,老师问任何细节你都能接住。
6. 手头有参考源码时,怎么改造成“自己的作品”
最后一块内容纯粹是经验之谈。每年这个时候,很多人手里都会有一份“参考源码”,我的态度是:参考完全没问题,但绝对不能原封不动提交。拿源码当脚手架,把它改造成真正属于你自己的项目,既省时间又安全。下面这几步是我觉得最关键的。
6.1 跑通之前不要改代码
拿到一份源码,第一件事不是打开IDEA看代码,而是先把环境和运行步骤搞清楚。务必确认三件事:
- JDK版本是否匹配。编译报错先看Project Structure里的SDK版本,Spring Boot 3.x配JDK 8必炸。
- MySQL版本和连接串。很多工程用的是MySQL 8,驱动和连接参数是
useSSL=false&serverTimezone=Asia/Shanghai,版本不对会连不上。 - Redis是否需要启动。代码里凡是用了RedisTemplate的地方,Redis没启动,接口直接报错。如果演示环境实在不方便跑Redis,干脆把缓存相关代码注释掉,把原本走缓存的地方改成直接查数据库,先把系统跑稳。
顺序一定是先跑通,再动刀。跑都没跑通的源码,改起来你根本不知道是自己改坏了还是原来就坏了。
6.2 改包名、改信息、换皮肤
跑通之后,用IDEA的全局替换把所有涉及原作者信息的痕迹清掉:
- 包名重命名:右键包名→Refactor→Rename,把
com.xxx.fruit改成你自己的项目包名,比如com.student.freshfruit。 - 数据库名替换:把application.yml里的数据库名改掉,重新执行SQL脚本。
- 前端页面文案:首页标题、页脚版权、网页图标全部替换成“飘香水果购物网站”的对应内容。
- 管理端Logo和登录页背景换成自己的图片。
这些操作技术含量不高,但能极大提升项目的“个人感”。答辩时老师打开页面看到的是你的信息,而不是某个陌生人的ID,第一印象就完全不同。
6.3 至少新增一个原创功能模块
要真正体现工作量,最好在源码基础上新增一个独立功能。我推荐几个容易实现又出效果的方向:
- 优惠券模块:后台发放优惠券,前台下单时选择使用,涉及订单金额重新计算。
- 商品收藏模块:用户点击收藏,个人中心查看收藏列表,这是逻辑最简单的新功能。
- 我常推荐签到积分模块:每日签到获得积分,积分可在下单时抵扣。这个功能既有业务闭环,还能在论文里写成一个完整的子模块。
加一个自己独立设计的功能,论文的第五章就能名正言顺地写“本人在参考现有系统基础上,重点实现了XX模块”,既诚实又加分。
6.4 六个可以立刻落地的扩展方向
如果学分和精力都允许,这些扩展方向随便挑一个做成“系统展望”,或者干脆作为期末加分项:
- 增加微信小程序端:复用后端接口,前端用uniapp或者原生小程序实现。
- 接入支付宝沙箱支付:把模拟支付流程替换成真实沙箱调用。
- 用Redis缓存热点商品详情,降低数据库查询压力。
- 引入Elasticsearch做全文搜索,提升商品搜索体验。
- 基于用户购买记录做简单推荐:同分类商品、购买组合推荐。
- 增加数据可视化大屏:前端用ECharts,把后台统计页面做成大屏效果。
这些方向在论文第七章“展望”部分写出来,能让老师觉得你具备前瞻意识,而不只是完成任务。
说实话,这个项目做完之后,我最大的收获并不是学会了Spring Boot的某个注解,而是把一个模糊的“做个水果网站”拆解成了需求、表结构、接口、页面、论文、答辩一系列可执行的任务。尤其是订单和库存那套一致性保障的思路,等以后去公司做真实业务系统时依然用得上。最后提醒一句:演示前一天把数据库时间和系统时间对好,把浏览器缓存清干净,把Redis和MySQL按正确顺序启动,现场别慌,你就能稳稳收场。