
不用开场白直接进正文。1. 洗衣店订单系统一个被低估的毕业设计选题每年到了毕设季总有同学在群里问有没有简单又不容易挂的题目。我见过太多人一上来就选商城、选社交平台结果前端要接支付、后端要搞IM进度一拖再拖最后答辩前一周还在补核心功能。如果你正在为选什么题目发愁我的建议很直接试试洗衣店订单管理系统。这个选题的好处在于——它足够生活化。洗衣店的核心业务无非就是顾客送衣、店员登记、安排清洗、通知取衣、结算走人这套流程每个人都见过甚至用过业务逻辑不需要额外向答辩老师解释半天。但你别小看它真要把这套流程做成一个前后端分离的系统里面涉及的东西一点都不少订单状态流转、会员折扣、洗衣项目定价、取衣提醒、营业统计该有的复杂度全都有而且每一块都能在答辩时讲得清清楚楚。更关键的是它的功能边界非常清晰。你不必像做商城那样为了凑功能硬塞购物车和秒杀洗衣店系统的功能天然收敛在订单这条主线上。做出来之后演示效果非常直观创建一个订单、状态一步步往后走、最终结算完成整个流程在页面上看得见摸得着。这种过程可见的项目答辩时老师一眼就能看懂你做了什么提的问题也不会跑偏。技术栈上SpringBootVueMySQL这套组合也是当前最主流的选择。SpringBoot负责后端接口Vue负责前端页面MySQL存数据三个角色分工明确任何一个部分出问题都好排查。项目源码我放在文末跟着文章一步步来从环境搭建到答辩准备都能走通。2. 技术选型不要跟风为什么是SpringBootVueMySQL现在互联网上铺天盖地都是微服务、容器化、分布式但我要泼一盆冷水毕设项目用不上这些硬上反而会把自己坑了。SpringBootVueMySQL这组搭配是这个体量项目里最平衡的方案接下来逐个拆解选型背后的逻辑。2.1 SpringBoot把繁琐的配置收起来SpringBoot最核心的价值是约定大于配置。以前用SSH或SSM那套光XML配置文件就够写半天现在SpringBoot一个注解就能把Spring、SpringMVC、MyBatis等组件全部集成好内置Tomcat打个Jar包丢到服务器上就能跑。洗衣店订单系统这种量级的项目用SpringBoot是最舒服的写一个Controller启动类扫描一下就能跑起来写一个Service加一个事务注解回滚逻辑就齐了写一个Mapper接口继承MyBatis-Plus的BaseMapper增删改查都不用自己写SQL。整个后端代码量能控制在两千行以内维护起来不累这对接下来的Debug和答辩都有很大的正向帮助。2.2 Vue前后端分离的展示利器Vue在国内前端圈的地位不用多说了。它的响应式数据绑定让页面状态和视图自动同步这在订单管理这种交互频繁的场景里非常省事——订单列表刷新、状态标签切换、弹窗表单校验全部靠数据驱动不需要手动操作DOM。选Vue还有一个现实原因组件生态和资料极多。Element UI或者Element Plus提供了现成的表格、表单、弹窗、日期选择器洗衣店系统的绝大多数页面都能用组件拼出来不需要从头写CSS。对于平时前端接触不多的同学这是实打实地缩短开发周期。2.3 MySQL够用且好讲订单数据是典型的强关系型数据——顾客、订单、订单明细、洗衣项目、结算记录之间都有外键关联。这种场景用MySQL再合适不过。更重要的是MySQL的SQL语句大家都熟答辩时老师随便问一句某个表怎么查询你都能现场写出来不会因为技术太偏说不上来。有人可能会说PostgreSQL也很强但注意这是毕设不是生产系统。MySQL的安装、配置、可视化工具Navicat、DataGrip在国内有海量教程遇到问题搜一下就有答案这个优势在赶工的时候非常关键。2.4 MyBatis-Plus为什么加的第三个组件很多教程喜欢在这套架构里加MyBatis-Plus强烈建议你加。它并不是什么花哨的技术就是MyBatis的增强工具但能省掉大量重复劳动。比如订单明细表要按订单ID查询传统MyBatis要写一个selectByOrderId的XML和接口方法而MyBatis-Plus直接让你用LambdaQueryWrapper一行搞定ListOrderDetail details orderDetailMapper.selectList( new LambdaQueryWrapperOrderDetail() .eq(OrderDetail::getOrderId, orderId) );这种写法的好处是你在答辩时可以坦然地说我用了MyBatis-Plus来提高开发效率老师不会觉得你在炫技反而会觉得你了解常见的工程实践。3. 数据库设计一张订单表如何撑起整个系统很多人在做毕设时有个坏习惯——先写代码后建表结果写到一半发现字段不够用又回头改表改来改去代码和表结构对不上最后越写越乱。数据库设计应该是整个项目的第一步而且要把核心表的结构想清楚再动工。3.1 核心表结构从订单出发逐层拆解洗衣店订单管理系统的核心业务围绕订单展开我建议至少设计五张表用户表customer、洗衣项目表laundry_item、订单表orders、订单明细表order_detail、结算记录表payment_record。这里重点说说订单表因为它是整个系统的心脏。我的订单一版设计如下字段名类型说明idbigint主键自增order_novarchar(32)订单编号展示用customer_idbigint关联用户表statustinyint订单状态1待取衣 2清洗中 3待取回 4已完成 5已取消total_amountdecimal(10,2)订单总金额discount_amountdecimal(10,2)优惠金额actual_amountdecimal(10,2)实付金额pickup_timedatetime预计取衣时间remarkvarchar(255)备注create_timedatetime创建时间update_timedatetime更新时间有两点需要特别说明。第一状态字段用数字而不是字符串。不要直接在表里存待取衣清洗中这种中文因为后续状态判断用数字比字符串更快而且改状态名称不需要动表数据前端映射一下就能显示。第二金额字段用decimal而不是float。float在计算金额时会出现精度丢失洗衣店虽然单笔金额小但累计统计时误差会被放大用decimal(10,2)坚决没错。3.2 订单明细表让订单可以扩展订单表只记录了汇总信息具体洗了什么衣服、每件多少钱需要放在明细表里CREATE TABLE order_detail ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_id BIGINT NOT NULL, item_name VARCHAR(50) NOT NULL, quantity INT NOT NULL DEFAULT 1, unit_price DECIMAL(10,2) NOT NULL, subtotal DECIMAL(10,2) NOT NULL );为什么要单独拆一张明细表因为一个订单可能包含多件衣物——一件羽绒服干洗、两件衬衫水洗、一条裤子熨烫。如果直接在订单表里加衣物名称字段那同一订单的多件衣物只能拼成一行非常不优雅。拆出明细表后订单表管状态和汇总明细表管具体项目各司其职。3.3 状态设计用常量类统一管理状态字段是数字但代码里不能直接写死数字否则时间一长自己都忘了1代表什么。我建议在Java里建一个状态常量类public class OrderStatus { public static final int WAITING_PICKUP 1; // 待取衣 public static final int WASHING 2; // 清洗中 public static final int READY 3; // 待取回 public static final int COMPLETED 4; // 已完成 public static final int CANCELLED 5; // 已取消 }前端Vue里也可以做一个对应的映射配置把数字翻译成展示文本和标签颜色。这样订单状态一目了然点开订单列表看到标签颜色就知道目前进行到哪一步。4. 订单状态机比想象中更重要的核心逻辑洗衣店系统的功能看起来是增删改查但最核心的难点在于订单状态的流转控制。如果这个逻辑做不好系统就是一个普通的数据管理工具答辩时很容易被问住。4.1 状态流转规则不是所有跳转都允许订单状态不能随意跳转。比如一个已完成的订单不应该能改回清洗中一个已取消的订单也不应该能变成待取衣。所以我在实现状态变更时先定义了一张合法的流转表当前状态允许跳转到的状态待取衣清洗中、已取消清洗中待取回待取回已完成已完成无已取消无这个规则在代码里怎么体现最简单的做法是在Service层做一个校验方法Override public void changeStatus(Long orderId, int newStatus) { Orders order orderMapper.selectById(orderId); if (order null) { throw new RuntimeException(订单不存在); } // 校验状态合法跳转 if (!validTransition(order.getStatus(), newStatus)) { throw new RuntimeException(非法状态变更 order.getStatus() - newStatus); } // 更新状态 order.setStatus(newStatus); orderMapper.updateById(order); } private boolean validTransition(int current, int target) { switch (current) { case OrderStatus.WAITING_PICKUP: return target OrderStatus.WASHING || target OrderStatus.CANCELLED; case OrderStatus.WASHING: return target OrderStatus.READY; case OrderStatus.READY: return target OrderStatus.COMPLETED; default: return false; } }这段代码翻译成人话就是订单必须按接单 - 清洗 - 可取 - 完成这个流程往下走中间不能跳步也不能倒带。这样实现之后业务上不会出现衣服还没洗就通知取货这种低级错误答辩时把这张流转表画出来老师一眼就能明白你考虑到了业务的完整性。4.2 接口设计前端只提交目标状态状态变更的接口我推荐用PUT请求路径设计为/api/order/{orderId}/status请求体携带目标状态即可。前端不需要知道当前状态后端自己会判断合法不合法。PUT /api/order/12/status Content-Type: application/json {status: 2}如果后端返回非法状态变更异常前端弹窗提示当前订单状态不允许此操作用户体验和系统健壮性都有保障。顺带一提这个异常处理逻辑要统一——建议全局用RestControllerAdvice处理统一返回JSON格式的错误信息不要每次手动try-catch。4.3 并发问题毕设阶段最容易忽略的坑毕设系统一般没有高并发但并发这个话题答辩时很可能被问到。比如两个员工同时点击确认取衣会不会出现状态覆盖这个问题其实很好回答在更新状态时使用乐观锁机制。在订单表里加一个version字段更新时带上版本号UPDATE orders SET status #{newStatus}, version version 1 WHERE id #{orderId} AND version #{oldVersion}如果更新受影响行数为0说明数据被其他人改过了重新查询后再处理。这行SQL写起来不复杂却是一个很高级的思考老师提问时能从容作答。5. 前端不将就Vue页面规划与交互细节后端接口设计好了前端就是把这些接口一个一个串起来。很多人觉得前端只是展示数据但订单管理系统做得好不好用前端至少占一半功劳。5.1 页面结构四个页面撑起全部功能洗衣店管理系统不需要花哨的多页面我建议核心页面控制在四个以内首页/仪表盘展示今日订单数、待取衣物数、今日营收等统计卡片订单管理订单列表增删改查状态筛选、状态变更按钮洗衣项目管理维护洗衣项目和价格的CRUD页面会员管理顾客信息列表可查看会员历史订单先说订单管理页面这是整个系统的主战场。页面布局用左侧筛选区右侧表格的方式筛选条件包括状态下拉框、日期范围日期选择器、订单号输入框。表格列固定为订单号、顾客名、项目数、总金额、状态、创建时间、操作操作列放两个按钮编辑、变更状态。状态变更弹窗里放一个下拉框选项就是当前允许跳转到的状态后端校验失败则在弹窗中提示错误。5.2 API封装别把axios写满每个组件这是我见过太多人犯的错——每个Vue组件里直接引用axios请求路径散落在各个页面改一个端口要全局搜索替换。正确的做法是在src/api目录下统一管理// src/api/order.js import request from /utils/request export function getOrderList(params) { return request({ url: /order/list, method: get, params }) } export function changeOrderStatus(orderId, status) { return request({ url: /order/${orderId}/status, method: put, data: { status } }) }页面组件只负责调用这些封装好的函数不改路径、不写具体URL。这样以后后端接口调整只需改api目录下对应的文件维护成本大大降低。这个习惯虽然简单但在代码评审时是一个妥妥的加分项。5.3 状态标签和汇总统计前端状态展示有一些提升观感的细节。比如订单状态不要用纯文本用不同颜色的标签待取衣是蓝色、清洗中是橙色、待取回是绿色、已完成是灰色、已取消是红色。Vue里实现起来很简单给el-tag的type属性绑一个计算函数就行。首页统计卡片也别只做加法。除了总数还可以按状态分组展示——待取衣8单清洗中3单待取回5单每张卡片点击后跳转到订单列表并自动带出该状态的筛选条件。这种交互细节做出来演示的时候效果立竿见影老师会觉得你考虑到了实际使用场景。6. 从0到1跑通整个项目环境配置与启动步骤这部分是给准备照着源码复现的同学准备的实操流程。很多人在这一步卡住并不是代码问题而是环境问题。下面按我的操作顺序一步步来尽量做到零基础也能跑通。6.1 环境准备版本一定不要乱装首先是JDK。注意SpringBoot 2.x官方最低要求JDK 8但推荐用JDK 8或11这两个版本最稳定。JDK 17也能跑SpringBoot 2.x但有些老版本依赖会出兼容性问题没必要冒险。装好之后在命令行验证一下java -version # 出现了类似 java version 1.8.0_301 的输出就对了Maven用3.6.3或3.8.x都可以配置阿里云镜像仓库否则依赖下载速度会让人崩溃。然后是数据库。MySQL建议5.7或8.0不要装最新版的开发版稳定版即可。装完之后用Navicat或DataGrip建一个数据库名字叫laundry然后把项目里提供的laundry.sql导入表结构和初始数据就都有了。前端环境需要Node.js建议14.x或16.x不要一上来装最新版Node 20部分老依赖可能不兼容。装好后执行npm config set registry https://registry.npmmirror.com换淘宝镜像否则npm install能卡到你怀疑人生。6.2 后端启动流程项目导入IDE推荐IDEA等Maven把依赖下载完找到application.yml把数据库账号密码改成你自己的spring: datasource: url: jdbc:mysql://localhost:3306/laundry?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 你改成自己的密码然后直接运行启动类里的main方法。看到Tomcat started on port 8080的日志出来后端就启动成功了。可以用Postman或者直接用浏览器访问Swagger地址如果项目集成了SpringDoc或Knife4j验证接口是否正常。如果启动报端口被占用在配置里改掉server.port就行比如改成8081记得前端请求的baseURL也要跟着改。6.3 前端启动流程进入前端目录执行npm install npm run dev启动成功后命令行会显示dev server地址一般是 http://localhost:5173 。打开浏览器先确认登录页能不能出来然后用项目的演示账号登录。如果页面能打开但列表数据为空优先检查后端是否启动、前端src/utils/request.js里的baseURL是否指向后端端口。这个问题我踩过不止一次80%的联调失败都是端口没对齐。前端与后端联调时还有一个Vite配置要注意。开发环境下建议配置代理而不是在request.js里直接写http://localhost:8080。代理配置在vite.config.js里server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }这样前端代码里请求的都是/api/order/list这种相对路径部署上线时不需要改动任何业务代码。7. 答辩前的准备高频问题与加分技巧项目跑通之后还有一件重要的事——准备答辩。很多同学项目做得不错但到了答辩环节不知道该说什么或者被老师一个问题问住后开始慌张。这一章把常见问题整理出来照着准备就好。7.1 老师最爱问的四个问题第一个问题几乎必问这个系统的核心业务流程是什么回答思路从顾客进店到取衣离店完整走一遍流程。顾客提交衣物店员创建订单状态设为待取衣衣物进入清洗区改为清洗中清洗完成改为待取回顾客来取衣结算付款状态改为已完成。中间涉及订单明细的录入、金额自动计算、支付记录生成。把这个流程讲清楚老师马上知道你清楚自己在做什么。第二个问题项目的难点在哪里你是如何解决的回答思路不要回避亮点说你解决过的问题。参考话术系统的难点在于订单状态的流转控制。最初我想让前端直接修改状态后来发现会产生非法跳转比如待取回的订单被误改为清洗中。之后我在后端增加状态机校验定义了合法流转表所有状态变更必须通过Service接口进行合法性校验同时在更新时加上乐观锁防止并发冲突。这让我在代码中能清晰控制所有状态的走向。第三个问题为什么要用Redis存用户的登录状态如果项目用了JWTRedis即便项目里没有Redis也要准备好没用Redis的原因的回答本系统用户量不大JWT无状态token完全够用省去了Redis部署成本降低了系统复杂度。这样回答既诚实又合理。第四个问题这个系统可以怎么扩展回答思路往业务和架构两个方向说。业务上可以增加会员积分、优惠券、预约取送衣功能架构上如果用户量起来可以把订单服务拆分出来独立部署引入MQ削峰填谷。不用真做说的是思路体现你思考过。7.2 让项目在演示时更出彩的小技巧演示的时候别傻乎乎地只录屏。提前准备几条测试数据状态覆盖各个阶段——有待取衣的、有清洗中的、有已完成的演示时先展示列表整体然后挑一条状态是待取衣的走一遍变更到清洗中的流程界面上的状态标签颜色变化会给老师留下印象。再准备一个异常演示把一条已完成订单尝试变更为清洗中页面弹出非法状态变更的提示。这个反例演示反而比正例更有说服力证明系统有完整的校验逻辑。最后录制演示视频时不要一上来就打开IDE。先启动项目再用浏览器操作最后再补充讲解技术细节。那种上来先展示代码的演示老师很容易注意力涣散。8. 项目目录结构与源码说明很多同学拿到源码后喜欢直接双击启动启动不了就发消息问为什么报错。坦白说大部分启动失败都不是代码问题而是环境问题。这里先贴一下项目的目录结构让你心里有数。laundry-system/ ├── backend/ # SpringBoot 后端 │ ├── src/main/java/com/laundry/ │ │ ├── controller/ # 前端接口层 │ │ ├── service/ # 业务逻辑层 │ │ ├── mapper/ # MyBatis-Plus 数据访问层 │ │ ├── entity/ # 实体类 │ │ ├── config/ # 配置类 │ │ └── common/ # 通用工具、结果封装 │ ├── src/main/resources/ │ │ ├── application.yml # 全局配置 │ │ ├── mapper/ # XML文件如需 │ │ └── db/laundry.sql # 数据库初始化脚本 │ └── pom.xml ├── frontend/ # Vue 前端 │ ├── src/ │ │ ├── api/ # 接口请求封装 │ │ ├── assets/ # 静态资源 │ │ ├── components/ # 复用组件 │ │ ├── router/ # 路由配置 │ │ ├── store/ # 状态管理 │ │ ├── views/ # 页面组件 │ │ └── utils/request.js # axios 封装 │ ├── vite.config.js # Vite 配置 │ └── package.json └── README.md # 项目说明文档源码文件的使用逻辑是先导入后端的pom.xml等IDEA下载依赖再导入laundry.sql到MySQL然后启动后端启动类最后进入frontend目录执行npm install和npm run dev。如果某一步卡住优先检查的是版本匹配问题而不是代码逻辑——先看JDK是不是8或11再看MySQL端口是不是3306再看Node是不是16.x这三个点收拾明白了项目基本就活了。还有件事多说一句README.md一定要仔细看一遍。里面通常包含环境要求、账号密码、初始化说明、常见报错解决办法。很多人项目拿到手先双击启动报一堆错才想起看文档顺序反了。我顺手整理了一张快速排查表贴在下面供参考报错现象最可能的源头Maven依赖下载失败未配置阿里云镜像后端启动提示数据库连接失败账号密码或库名不对前端npm install卡住未配置淘宝镜像页面能打开但接口报404代理端口和后端端口不一致中文变成乱码数据库连接URL缺少characterEncodingutf89. 结合实际经验做完这个项目我的一点体会整个项目从建表到跑通我前前后后用了大概四天每天稳定推进六到八小时。第一天花在数据库设计和搭建框架上第二天走后端接口第三天写前端页面第四天联调和修bug。理论上按我的步骤走零基础的同学一周内也能跑通。我最想强调的还是数据库设计。做这种管理系统表结构想清楚后面写的每一行代码都会很顺表结构改来改去每个接口都可能出问题。先花时间把订单表、明细表、用户表之间的关系画清楚再用代码把业务串起来这才是做项目的正确顺序。最后分享一个应对答辩的小心得不要只背代码逻辑要会讲故事。把系统当成一条流水线按顾客进店—创建订单—衣物清洗—通知取衣—结算完成这个顺序去讲老师在听的时候会自动构建出画面感。再结合状态机校验和乐观锁这两个技术细节做重点解释整个答辩二十到三十分钟就稳稳拿下了。如果后续你想在功能上继续扩展加一个会员积分模块或者预约取衣模块都是承上启下的好方向。