1. 为什么我推荐把“鲜花预售系统”当成Spring Boot毕设选题
先说个实话:每年计算机毕设选题翻来覆去就那么几类,图书管理、在线考试、学生选课、宿舍报修……不是说这些题目不行,而是做得太多以后,答辩老师一眼就能看出你的功能模块到底是自己写的还是照着视频敲的。相比之下,鲜花预售系统是一个“看似普通,但实际很有挖掘空间”的题目,它天然带着一个普通CRUD项目没有的东西——预售这个核心业务规则。
你要知道,普通商城系统就是“上架商品→用户下单→管理员发货”线性流程,属于标准的增删改查。但预售系统里多了一个“先付定金、后付尾款”的环节,这个小小的规则改动,直接让你的项目从“管理系统”升级为“业务系统”。因为凡是涉及定金、尾款、支付状态、库存锁定、到期发货时机这些东西,就必然牵扯到状态流转、定时任务、并发扣减、订单一致性这几个让评委眼前一亮的专业话题。
另外从选题策略上讲,鲜花本身具有非常强的“时效性”和“节日爆发性”特征——情人节卖玫瑰、母亲节卖康乃馨,这种场景天然适合做预售,能讲出商业故事。你答辩的时候可以理直气壮地说:“鲜花提前预售,花店才能按单采购,避免囤货损耗。”这比“方便管理员管理图书”有说服力得多。
再说回技术层面,Spring Boot + Vue这套组合目前仍然是Java方向毕设的绝对主流。Spring Boot解决了传统SSH、SSM工程里那些繁琐的XML配置问题,约定优于配置,内置Tomcat,打jar包就能跑。你用零配置就能起一个Web服务,这对毕设选手来说意味着——你把精力花在业务逻辑上,而不是折腾配置文件。而Vue前端做起来也快,Element Plus这类组件库能让你用很短的代码搭出像样的管理后台。
所以我给这个题目的定位是:难度中等偏上一点点、技术栈主流、业务有亮点、答辩有故事可讲。适合那些Java基础和数据库操作还凑合、想做出一个“有含金量”的毕设、又不希望难度大到做不完的同学。
源码我拿到手的时候,第一反应是结构很干净,package分层清晰,没有那种为了凑代码量堆出来的冗余类。后面我会把里面值得复用的核心设计思路全部拆开讲一遍,包括怎么跑起来、怎么讲清楚、怎么避开那些你可能查两个小时都查不出原因的坑。
2. 花店经营的“预售”到底解决了什么问题:业务模型与角色划分
2.1 预售和普通电商的本质区别:库存与支付时机
很多人做毕设的时候有个通病:拿着题目就开写,连业务规则都没想清楚。但如果你真的做过开发就知道,业务规则就是这个项目的灵魂,评委问的问题基本都是围绕业务规则展开的。
鲜花预售和普通电商的核心区别在于:
- 普通电商:商品上架 → 用户付款 → 商家发货(库存即时扣减)
- 预售模式:商品预购 → 用户支付定金 → 尾款支付期开启 → 用户支付尾款 → 商家统一采购/生产 → 发货
这个差异带来两件很有意思的事。第一,库存的扣减时机变了。你在预售阶段看到的“剩余库存”,本质上是“可预购名额”,用户付完定金后名额就被锁定,这个锁定的订单不能因为用户没付尾款就立刻把名额放给别人——需要等尾款期结束后统一释放。第二,状态机变复杂了。普通订单就是“待付款/已付款/已发货/已完成”,而预售订单至少要多出“待付定金/已付定金/待付尾款/已付尾款/已取消/已关闭”这么几个状态。
源码里对这块的处理方式是设置了一个非常清晰的订单状态字段,结合支付状态、发货状态,三个字段组合起来判断当前订单处于什么阶段。这是很标准的做法,你在答辩的时候如果能把这个状态流转图画出来(用嘴讲清楚也行),就已经赢了一半的普通选手。
2.2 系统角色拆解:前台用户、后台管理员、超级管理员
这个项目分为两个端:用户端(前台)和管理端(后台)。
用户端面向的是普通买花的人,核心功能是:注册登录、浏览鲜花商品、查看预售活动、发起预购支付定金、在我的订单里查看预售进度、尾款期支付尾款、确认收货、以及个人资料的维护。
管理端面向的是花店运营人员,核心功能是:商品管理(维护花材、价格、库存)、预售活动管理(创建预售场次、设置定金比例、设定尾款支付截止时间)、订单管理(查看所有订单、按状态筛选、发货操作)、用户管理(查看注册用户、禁用异常账号)、以及数据看板(统计预售金额、订单量、热门鲜花)。
另外系统里还有个隐藏角色叫超级管理员,它的权限粒度更粗,主要管“管理员账号”本身。这其实是很多毕设里容易被忽略的点——权限模型一定要分级,不需要做到Spring Security那么复杂,但至少要有“普通用户看不到管理界面、管理员之间也有高低之分”这个层次感,否则评委一问权限怎么做你就哑了。
2.3 预售活动周期:从“上架预热”到“尾款发货”的完整链路
我拿源码里的一个具体场景给各位还原一下“一场预售是怎么跑完的”,这能帮你建立整体认知:
- 管理员在后台创建一场预售活动,选定若干鲜花商品,设置定金金额、尾款截止时间、发货时间。
- 前台用户看到“预售中”的活动卡片,点进去详情页,选择规格(比如11朵、33朵、99朵),支付定金。
- 定金支付成功后,系统生成一笔“预购订单”,该商品的预购名额减一。
- 时间来到尾款期,用户在我的订单页收到提醒,进入订单详情支付尾款。
- 用户支付尾款后,订单状态变为“待发货”,管理员后台看到满屏待发货订单,按地区、品种统一采购鲜花,然后批量发货。
- 用户收到花,点击确认收货,订单关闭,整个生命周期结束。
如果用户一直不付尾款怎么办?源码的处理是:超过尾款截止时间后,订单自动关闭,预购名额释放回库存。这个逻辑非常关键,是你在答辩时一定要讲的点。实现手段无非两种:定时任务扫描 + 用户主动支付时校验时间。源码里用的是后者搭配一个简单的定时清理任务,说实话这个设计已经很成熟了。
3. 核心依赖与配置:这套源码是怎么把Spring Boot生态串起来的
3.1 为什么拒绝SSH老古董,Spring Boot这波选型赢在哪
我看到源码的pom.xml时,心里默默点了个赞。引用的依赖没有一个是多余的,而且全部是Java生态里目前最主流、最不容易在答辩时被问倒的那一套:
| 依赖 | 作用 | 选型理由 |
|---|---|---|
| Spring Boot Starter Web | 提供Web容器、Spring MVC、Jackson等 | 零配置启动内嵌Tomcat,打jar包直接跑 |
| MyBatis-Plus | ORM层、通用Mapper、分页插件 | 比原生MyBatis少写大量XML,内置CRUD方法 |
| MySQL Connector | 数据库驱动 | 经典组合,面试也常问 |
| Lombok | 实体类自动生成getter/setter | 减少样板代码,实体类看着干净 |
| Hutool | 工具类库 | 身份证校验、日期处理、随机数生成都有现成的 |
| JWT / Spring Security相关 | 登录鉴权与权限控制 | 前后端分离场景最常用的无状态鉴权方案 |
你可能注意到这里没有用Redis。很多教程会让你强行上Redis做缓存,但说实话,一个毕设项目里如果只是把数据从MySQL搬到Redis再读回来,没有实际的高并发场景,答辩老师一眼就看穿你这是“为了用而用”。这个源码没堆没用的技术,这点很重要——技术上做“够用且合理”的选择,比盲目堆砌新技术更讨喜。不过如果你想加分,可以自己在尾款支付那块引入Redis做分布式锁防超卖,我后面会给出具体思路。
3.2 工程结构与目录规划:按业务模块分package的正确姿势
我见过太多毕设源码包结构乱七八糟,全部堆在controller/service/dao/mapper四层里,一个类五六百行,改个需求想死的心都有。这套源码的包结构比较舒服:
com.example.flower ├── controller/ // 控制层,接收前端请求 │ ├── admin/ // 后台管理接口 │ └── api/ // 用户端接口 ├── service/ // 业务层接口 │ └── impl/ // 业务层实现 ├── mapper/ // MyBatis-Plus的数据访问层 ├── entity/ // 数据库实体 ├── vo/ // 视图对象(给前端返回的封装) ├── dto/ // 数据传输对象(接收前端参数) ├── config/ // 配置类(跨域、拦截器、MybatisPlus配置) ├── common/ // 公共工具、统一返回结果、异常处理 └── utils/ // 工具类(JWT、日期处理等)vo和dto是经常被人忽略的层。很多人做项目图省事,直接把entity丢给前端,结果多查出几个敏感字段,或者字段格式不对还得临时改实体。源码里单独分了vo/dto,说明作者是有工程经验的,这点你在答辩时也可以提一嘴——“为了前后端解耦,我单独做了视图对象”这句话,含金量不低。
配置方面,application.yml里主要配置了数据源、端口、MyBatis-Plus的日志与分页插件、文件上传路径等。数据库这块值得注意的一个细节是:字符集编码一定要设置成utf8mb4,不然鲜花商品描述里出现个emoji表情符号,存进去就变乱码,这种问题排查起来能让你怀疑人生。
3.3 登录鉴权:JWT无状态方案在前后端分离里的落地细节
这个小节单独拉出来讲,是因为登录鉴权是很多毕设最容易被问倒的地方。
这套源码的鉴权方式是传统Session还是JWT?我看了代码,采用的是JWT方案。整体流程是:用户登录时校验用户名密码,成功后后端生成一个token字符串返回给前端;前端把它存在localStorage里,每次请求在请求头Authorization里带上;后端拦截器对所有/api/**接口做token校验,合法则放行、不合法则返回401。
实现的三个关键点:
- 拦截器注册:在
WebMvcConfigurer里新增一个HandlerInterceptor,通过addInterceptors方法注册,并配置excludePathPatterns放行登录、注册、商品列表这些无需鉴权的接口。 - token里只放必要信息:源码的JWT载荷里放了userId和role,没有放密码等敏感信息。过期时间一般设置为7天,过期后前端会收到401,跳回登录页。
- 权限控制:管理员接口要做拦截校验,判断token里的role是否为
ADMIN,不是就拒绝访问,这也是第一道权限防线。
如果你在答辩时被问“JWT和Session有什么区别”,核心答法是:JWT无状态、服务端不用存会话信息、天然支持分布式;Session有状态、服务端要维护会话记录,但在单个服务器场景下实现简单。知道这两句,基本不会被难住。
4. 数据库设计是这类项目的命根子:核心表结构与关键SQL
4.1 八张核心表:从用户到订单的完整数据链路
数据库设计好坏直接决定你做一个项目的体感。这套源码共设计了多少张表?我数了一下,核心的八张:用户表、管理员表、鲜花分类表、鲜花商品表、预售活动表、订单表、订单明细表、活动与商品关联表。
挑重点表来说:
用户表(user):用户ID、用户名、密码(BCrypt加密存储)、昵称、手机号、头像、余额、注册时间、状态。这里注意两点:第一,密码绝对禁止明文存储,用SecureUtil.md5或者BCryptPasswordEncoder;第二,余额字段是后面做支付逻辑的基础,虽然毕设通常用模拟支付,但用户余额这个字段一定要有。
鲜花商品表(flower):商品ID、名称、主图、轮播图、分类ID、原价、预售价、库存、销量、上架状态、描述。有一个细节处理得好——detail字段存的是富文本HTML,前端用v-html渲染,这样后台编辑商品时可以像写Word一样排版,比存纯文本体验强很多。
预售活动表(pre_sale_activity):活动ID、活动名称、开始时间、结束时间、尾款开始时间、尾款截止时间、发货时间、状态。这张表是整个系统的核心发动机,所有时间节点都在这张表里定义。
订单表(orders):订单号、用户ID、活动ID、商品ID、商品快照(名称+图片+单价,防止商品后续修改导致历史订单错乱)、定金金额、尾款金额、实付金额、订单状态、支付状态、发货状态、创建时间、支付时间、发货时间、收货地址。
订单表里的“商品快照”字段是最容易被初学者忽略的。这里强调一下:订单里保存的必须是下单那一刻的商品信息副本,而不是通过外键去关联商品表查询。如果用户下单后管理员改了鲜花图片和价格,你再去查商品表,历史订单显示就全乱了——这个坑我当年自己踩过。
4.2 订单状态机设计:定金、尾款、取消、关闭,一个都不能乱
这块是整个系统的业务核心,也是答辩时最能展现你逻辑能力的部分。源码用订单状态、支付状态、发货状态三个字段的组合来标识订单当前所处阶段。
这里笔者整理了一张订单状态流转表,建议你在答辩前背熟:
| 场景 | 订单状态 | 支付状态 | 发货状态 | 说明 |
|---|---|---|---|---|
| 用户提交预购,未付定金 | 待付定金 | 未支付 | 未发货 | 用户还可取消 |
| 用户已支付定金 | 已付定金 | 已付定金 | 未发货 | 库存已锁定 |
| 尾款期内未付尾款 | 待付尾款 | 已付定金 | 未发货 | 系统提醒用户 |
| 用户支付尾款 | 待发货 | 已付全款 | 未发货 | 等待商家发货 |
| 管理员发货 | 已发货 | 已付全款 | 已发货 | 等待用户收货 |
| 用户确认收货 | 已完成 | 已付全款 | 已发货 | 订单生命周期结束 |
| 用户取消订单 | 已取消 | 视情况退款 | 未发货 | 定金按规则退还 |
| 尾款超时未付 | 已关闭 | 已付定金(可能不退还) | 未发货 | 库存释放 |
这里有个常见问题:尾款超时前,如果用户主动取消订单,定金退不退还?不同业务规则有不同答案。源码的做法是提供“取消订单”接口,且取消后定金原路退回用户余额。至于尾款超时被强制关闭的订单,定金如何处理,一般是规则里定义好“超时关闭后定金不退”,模拟支付可以简化成退回余额,你要在答辩时明确说出你的规则。
4.3 数据库连接与初始化:拿到手第一步要改的配置
源码运行时第一步肯定是配置数据库。application.yml里核心配置就以下几行:
spring: datasource: url: jdbc:mysql://localhost:3306/flower_pre_sale?useUnicode=true&characterEncoding=utf8mb4&useSSL=false&serverTimezone=Asia/Shanghai username: root password: 你自己数据库的密码 driver-class-name: com.mysql.cj.jdbc.Driver这里有几个容易翻车的小地方,我挨个说一下:
serverTimezone=Asia/Shanghai必须加。如果你用MySQL 8.x的驱动,不指定时区很可能会报The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized这种错,看起来像乱码一样,其实只是时区问题。characterEncoding=utf8mb4一定要配utf8mb4而不是utf8。原因上面说过了,emoji字符问题。- MySQL 8.x的驱动类是
com.mysql.cj.jdbc.Driver,MySQL 5.x则是com.mysql.jdbc.Driver,如果你本机是5.7版本就要注意改。
至于建表SQL,源码里一般放在sql/目录或者doc/目录下,一个flower_pre_sale.sql文件,用Navicat或命令行source执行即可。执行前建议提前创建一个空数据库,然后导入SQL,避免表已经存在导致报错。
5. 从源码到跑通:我的启动实操记录与避坑参考
5.1 环境准备清单:JDK、Maven、Node,版本怎么选才稳
这个源码是标准的Spring Boot + Vue前后端分离项目,所以你需要准备两套环境:
后端环境:
- JDK 1.8 或 11(千万、千万、千万不要用JDK 17跑老项目,很多依赖的反射机制和模块化限制会让你踩到飞起)
- Maven 3.6+(只要不是特别老的3.2、3.3就行)
- IDEA 2020.3及以上(社区版也能跑,就是少一点Spring插件支持)
- MySQL 5.7 或 8.0(如果你的机器上同时有多个MySQL版本,注意IDE里配置的是哪个端口)
前端环境:
- Node.js 14 或 16(别装Node 20,老项目用的node-sass大概率编译不过去,装完就想砸电脑)
- npm 或 yarn(npm随Node自带,够用)
这些版本建议不是凭空说的,是这类前后端分离项目跑不通的时候,八成以上案例都死于版本。记住一句话:毕设项目跑不起来,90%是环境问题,不是代码问题。
5.2 启动流程:五步跑通,全程半小时
我实际操作了一遍,整理出最顺滑的启动顺序:
- 导入数据库:打开Navicat(或者DataGrip、命令行都行),创建数据库
flower_pre_sale,字符集选utf8mb4,然后运行源码目录下的SQL文件。 - 用IDEA打开后端项目:
File -> Open,选择源码的backend文件夹(也可能是根目录下的后端子目录),之后等待Maven自动下载依赖。这里有个技巧:如果下载特别慢,检查IDEA的Maven配置里的settings.xml是否配置了阿里云镜像仓库,配置后速度能快十倍。 - 修改
application.yml:把数据库账号密码改成你自己的,端口默认8080一般不用动。 - 启动后端:找到Spring Boot启动类(一般带
@SpringBootApplication注解,类名类似FlowerApplication),右键运行。看到类似Started FlowerApplication in 3.2 seconds的日志就是成功。 - 启动前端:用IDEA自带终端或者VSCode打开前端目录,依次执行
npm install、npm run dev,看到Local: http://localhost:8081/之类输出后,浏览器打开对应地址。
如果一切顺利,你就能看到系统的登录页了。默认管理员账号密码一般源码的README里会写,比如admin/admin123之类的组合,如果没有,去数据库admin_user表里直接插入一条记录或改一条已有记录即可。
5.3 我踩过的坑:启动报错排查与更优做法
把我实操过程中遇到的几个典型报错整理如下,前两个是我自己踩过的,后两个是帮别人排查时见过的,大概率你也会碰到:
报错一:Consider defining a bean of type 'xxxMapper' in your configuration
这个经典中的经典。原因是启动类没有被@MapperScan扫到,或者mapper接口没有加@Mapper注解。解决方法是:在启动类上加@MapperScan("com.xxx.mapper"),或者在每个Mapper接口上加@Mapper。源码里一般已经配好,但如果你自己新建了Mapper类,就会触发这个问题。
报错二:前端npm install报错node-sass相关
我看到源码前端依赖里可能有sass或者旧版本node-sass,一旦Node版本过高就会出现编译失败。最快的解决方式是卸载重装node-sass改用dart-sass,在package.json里把node-sass替换为sass,然后删掉node_modules整个文件夹重新install。如果项目不需要自定义样式覆盖,甚至可以忽略这个依赖。
报错三:前端请求后端接口404或跨域报错
前后端分离项目必备三件套:后端配置跨域(CorsConfig)、前端代理(vue.config.js里的devServer.proxy)、请求路径以/api为前缀。源码里后端和前端一般都已经配好了,但你如果改了端口,就需要同步改前端代理的target地址和后端的实际端口。
报错四:登录后刷新页面,用户信息丢失
这是前端路由守卫没有处理好的典型表现。源码里的方案比较稳妥:路由守卫里判断localStorage里有没有token,有就尝试获取用户信息,没有就跳转到登录页。如果你自己改造时发现刷新就跳登录,大概率是刷新后用户信息接口返回失败或者token已经过期,检查一下token有效期和请求拦截器的处理逻辑。
6. 把预售业务逻辑一次性讲透:模块边界、超卖与状态同步
6.1 用户从“看花”到“付定金”的完整请求链路
很多同学答辩被问倒,不是因为代码没写完,而是讲不清一次请求从前端到数据库到底走了哪些层。这里我用“用户浏览预售活动并支付定金”这个场景,手把手过一遍调用链:
- 前端Vue组件加载活动列表,向
/api/pre-sale/list发GET请求,参数带当前页和页码大小。 - 后端
PreSaleController.listPage()接收参数,调用PreSaleService。 PreSaleService层通过MyBatis-Plus的selectPage方法分页查询活动表,同时联表查出活动关联的商品列表。- 返回结果是
Result.ok(data)的统一封装,code=200表示成功,前端拿到数据后渲染卡片列表。 - 用户点击“立即预购”,进入详情页,选择商品规格,点击“支付定金”。
- 前端把
{ activityId, flowerId, userId, depositAmount }这些参数POST到/api/pre-sale/submit。 - 后端
PreSaleServiceImpl.submit()做了三件事:校验活动时间是否在预售期内、校验定金金额是否为0、然后调用订单Mapper插入一条“待付定金”的订单记录。 - 支付定金时,修改订单状态为“已付定金”,更新商品库存(预购名额减一)。
- 数据库事务提交,前端跳转至“我的订单”页面,展示这笔预购订单。
这个链路最核心的一步在第8步,库存扣减必须和订单状态更新放在同一个数据库事务里。源码里用@Transactional注解搞定,这个注解背后的原理是Spring AOP的事务管理,你答辩时可以提一下。
6.2 防超卖思路:单机和并发场景各自的处理方式
“库存超卖”是电商类项目必问的经典问题,放在鲜花预售场景里更突出:因为预售的名额是有限的,如果100个人同时抢99朵红玫瑰的最后10个预购名额,处理不好就会出现卖了12单但库存只减了10的情况。
源码里采用的方案比较基础:先查库存,判断是否大于0,大于0才执行更新SQL,同时用数据库的行锁做兜底。具体到SQL层面,其实可以做成一个原子操作来保障安全:
UPDATE flower SET stock = stock - 1 WHERE id = ? AND stock > 0这条SQL通过stock > 0条件保证不会扣成负数,是单机场景下最简单可靠的防超卖手段。如果你项目中加入了Redis,也可以把库存预扣放在Redis里用DECR原子命令来做,这个思路可以作为你答辩时的“进阶回答”。
6.3 尾款支付与订单关闭:定时任务和支付校验双保险
尾款期到了,用户没付尾款,系统要自动关闭订单并释放库存。源码的实现思路是双保险:
第一重保险:定时任务。Spring Boot里用@Scheduled注解声明一个方法,每隔5分钟扫描一次订单表,找出所有“尾款截止时间已过但仍然是待付尾款状态”的订单,批量更新为“已关闭”,同时把对应的预购名额加回库存。
第二重保险:用户支付尾款时校验。即使定时任务还没来得及跑,用户此刻刚好在支付尾款,后端会在支付接口里再次校验当前时间是否在尾款期内,如果超时直接返回“尾款期已结束,订单已关闭”的提示,阻断支付。
这种“被动校验 + 主动清理”的组合设计很适合拿来当答辩亮点。你可以说:“为了防止数据不一致,我不仅在用户触发时校验,还用了定时任务兜底清理,保证系统状态最终一致。”
定时任务本身很简单:
@Component public class OrderAutoCloseTask { @Scheduled(cron = "0 */5 * * * *") public void autoCloseExpiredOrders() { // 扫描过期待付尾款订单,更新状态、释放库存 } }注意@Scheduled默认是单线程执行,如果你的项目后续要跑多个定时任务,最好在配置类里加一个@EnableAsync并用线程池隔离,否则一个任务卡住会影响其他任务。当然,毕设阶段不用那么讲究,知道即可。
7. 管理后台实操:从创建预售活动到批量发货的完整演示
7.1 创建一场预售活动:字段、时间节点、关联商品
管理员后台的核心操作就是创建预售活动。我在源码的PreSaleActivityController里找到了相关接口,前端管理页面的操作流程如下:
- 进入“预售管理”菜单,点击“新增活动”。
- 填写活动基本信息:活动名称(如“情人节玫瑰超前预售”)、预售开始时间、预售结束时间、尾款开始时间、尾款截止时间、计划发货时间。
- 从商品列表里勾选本场活动要卖的花材,可以设置活动价(预售价)和定金金额。
- 设置活动状态为“上架”,点击保存。
- 活动创建成功后,用户端首页就能看到这场预售活动了。
这里有一个设计细节值得关注:活动时间的校验逻辑。比如尾款截止时间必须晚于尾款开始时间,发货时间必须晚于尾款截止时间,这些都应该在接口层校验,前端只管提示。源码里用的是LocalDateTime来做时间比较,比用Date类型写起来清爽得多。你要是用着不爽,可以自己在Service层加一个时间冲突校验的公共方法,顺便还能写几个单元测试。
7.2 商品管理:图片上传、规格设置、库存初始化
鲜花商品和普通商品还有一些小区别——花材有规格(朵数、包装)、有主图和详情图、库存可能不是按件算而是按“束”算。源码里的商品管理功能覆盖了这些细节点:
- 图片上传:通过
MultipartFile接收前端上传的图片,保存到服务器本地指定目录,然后返回图片访问URL,前端用它来展示。本地路径的配置在application.yml里的file.upload-path字段。 - 创建商品时指定分类,选择“玫瑰/百合/康乃馨”等花材分类,分类表是一张独立的表,支持一级分类和多级分类。
- 初始化库存时,普通电商直接填库存数字,预售场景下库存字段有两种解读:如果是“现货立即发”的商品,库存就是实际库存;如果是“预售商品”,库存实际上代表“本场可预购名额”。
- 上下架状态单独一个字段控制,上下架不影响历史订单查询,因为订单里存了商品快照。
这部分功能属于标准CRUD,难度不高,但参数校验尽量做全,尤其是金额、库存不能为负数这个常识性问题。
7.3 订单处理:发货、退款、订单导出三个高频操作
后台订单管理页面是三段式布局:顶部是状态筛选Tab(全部/待付定金/已付定金/待发货/已发货/已完成/已取消),中间是订单列表,右侧是订单详情抽屉。
发货操作是后台最频繁的操作。点击“发货”按钮,弹出填写物流单号的对话框,提交后订单状态从“待发货”变成“已发货”,用户端就能看到物流信息了。这块源码用的也是简单的状态更新,大概率没有对接真实的物流接口,如果你想加分,可以自己接入快递100的免费API。
退款操作一般针对用户取消的订单。用户在前台取消“待付定金”订单后,后台不需要操作直接完成;如果订单已经付了定金或尾款,取消后需要后台审核退款。源码里简化成了“取消后自动退款”——这也是可以接受的做法,毕竟毕设不是上线生产环境。
订单导出这块,不少毕设没有做,但这个功能很讨喜。做法不难:后端把所有订单列表用EasyExcel或POI生成Excel文件,返回二进制流,前端下载。答辩时演示一下“导出月度预售订单报表”,观感瞬间就不一样了。
8. 答辩前的准备:讲出亮点、避开雷区,让评委觉得你是真做了
8.1 这些技术点一定要会主动讲,它们是你的加分项
答辩的本质不再是“代码跑通”,而是“你讲清楚你做了什么”。我把这套源码里可以提炼的亮点打包好了,你答辩时按顺序说:
- 状态机设计:强调订单状态+支付状态+发货状态三者组合,实现了从“付定金→付尾款→发货→收货”的完整状态流转。这是任何CRUD系统都没有的复杂度。
- 事务控制:下单扣库存、支付改状态、取消退库存,凡是涉及多个写操作的方法,都用
@Transactional包起来。你可以说“为了保持数据一致性”。 - 统一返回结果与全局异常处理:源码里应该有
Result类和@RestControllerAdvice注解的全局异常处理器。主动提“所有接口返回统一json格式,异常全部走全局处理器,不会把堆栈信息暴露给前端”。 - 权限控制:JWT token + 拦截器 + 角色判断三层配合。注意要说清楚“管理员接口和用户接口是隔离验证的”。
- 预售业务的商业价值:讲清楚为什么鲜花要预售——降低库存损耗、按单采购、锁定客户。这个你从业务角度讲,评委绝对爱听。
8.2 评委大概率会问的五个问题与参考答案
提前把高频问题准备好,答辩时你就不慌:
Q1:你的库存字段扣减是如何防止超卖的?
答:我在扣减库存时使用了带条件的更新SQL:UPDATE flower SET stock = stock - 1 WHERE id = ? AND stock > 0,这样即使在高并发下,数据库本身也保证了库存不会扣成负数。如果系统量级更大,我会考虑引入Redis预扣库存来解决热点数据并发问题。
Q2:如果用户支付定金后一直不支付尾款怎么办?
答:系统里有两个保障机制:第一,用户发起尾款支付时,后端会校验当前时间是否在尾款期限内,超时直接拒绝;第二,系统配置了一个定时任务,每5分钟扫描一次所有超时未支付的预售订单,批量关闭并把预购名额释放回库存。
Q3:密码是怎么存储的?
答:用户密码不是明文存储,而是通过加盐哈希算法(比如BCrypt或MD5加盐)处理后才存入数据库。登录时也是拿输入的密码做同样的哈希运算再比对,保证数据库泄露也不会直接暴露密码。
Q4:这个系统和普通的鲜花商城有什么区别?
答:最大的区别是订单生命周期多了一个“定金+尾款”的阶段。普通商城用户付款后就等发货了,但预售系统里用户先付定金锁定名额,等尾款期开启后再付尾款,商家才统一采购和发货。这就带来了两个额外设计:预购库存管理和尾款超时释放机制。
Q5:你对这个系统未来有什么扩展想法?
答:我会从两个方向扩展。第一种是技术方向,引入Redis把热门的预售活动库存放到缓存里,用分布式锁解决并发超卖;引入消息队列做订单峰值削峰。第二种是业务方向,增加鲜花礼盒搭配推荐、生日提醒订阅、配送时间选择等功能。这样回答既展示了技术视野,也体现了商业思维。
8.3 讲解过程中的几个关键话术
最后分享几个实战技巧。用这些方式去讲,自然得像真做了几个月项目的人:
- 提到别人的设计时,用“我这里采用的方式是……”而不是“源码里是这样写的……”。
- 不要背代码,把核心业务流程画在纸上或者脑子里,讲到订单流转时用手势示意方向,会更从容。
- 遇到不会的问题,用一句“我当时实现的时候,主要考虑的是……”把话题拉到你知道的范围内,千万别当场沉默或者硬编一个答案。
9. 技术复盘:做完这个项目,你真正掌握的硬技能清单
最后这部分,我想换个角度聊。一个毕设项目做完,你不只是交了一个系统,你在这个过程里真正掌握的技能清单,才是有价值的东西。这里按经验帮大家盘一盘:
第一块,后端核心开发能力。你用Spring Boot写了完整的RESTful接口,理解了Controller-Service-Mapper三层之间的联系;学会了MyBatis-Plus怎么通过实体类+Mapper接口完成单表CRUD,怎么用LambdaQueryWrapper构造查询条件,怎么做分页查询。这些技能直接对标工作里的常规开发任务。
第二块,业务设计与建模能力。你从零设计了一个包含预售活动、订单状态流转、库存锁定与释放的数据库,画了E-R图,定义了核心表结构。这个能力比单纯的CRUD值钱得多,因为你经历了一个完整的“业务规则→数据模型→接口→实现”的过程。
第三块,常见问题的排查能力。你遇到过数据库时区报错、端口占用、Mapper扫描不到、跨域拦截、前端依赖装不上……这些问题在开发社区里天天有人问,你亲手解决一遍,下次再遇到就完全不慌了。
第四块,答辩与表达总结能力。你能把项目功能讲清楚、把业务设计讲清楚、把技术方案讲清楚,如果前面这些你都记住了,答辩现场你基本不需要临场发挥,只需要按准备内容讲。
我自己做项目这么多年,最大的体会是:一个好的毕设项目不一定用了多高深的技术,而在于你能否把一件事情从头到尾完整地做透。鲜花预售系统恰恰提供了这样一个“完整”的载体——业务不算简单到无聊,技术不算复杂到失控,做完了有得讲、有得展示、也有得扩展。
如果你正在纠结选题,或者已经选了这套源码但还在研究怎么跑通,我的建议很简单:先照着这篇内容把环境和流程跑通一遍,然后把订单状态流转和预售活动创建这两个核心流程亲自操作三遍,做到不用看笔记也能说出来每一步发生了什么。做到这个程度,你的毕设就稳了。