ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

SpringBoot+Vue3+MyBatis实战:洗衣店订单管理系统开发全解析

SpringBoot+Vue3+MyBatis实战:洗衣店订单管理系统开发全解析 最近帮朋友的小洗衣店折腾了一套订单管理系统从最开始的手工记账到后来用Excel再到最后决定自己动手写一套前后端分离的完整系统。这一路踩了不少坑也总结了一些实战经验。趁着周末空下来把整个项目的核心思路、技术选型、关键实现和调试心得都整理出来希望能给正在做类似毕业设计或者小型商业项目的朋友一点参考。这套系统用的是目前国内中小型项目里非常主流的一套组合Java SpringBoot做后端服务Vue3做前端界面MyBatis负责数据库操作MySQL作为持久化存储。四个技术栈各司其职合在一起就是一个订单从创建、洗衣、完成到取走的全流程闭环管理。相比那些什么都往单体JSP页面里塞的老项目这种前后端分离的架构至少有两个很直接的好处一个是前端开发和后端开发可以彻底并行另一个是后期就算要换Web端或者对接小程序后端接口基本不需要大动。先说一下这个系统的定位。洗衣店订单管理系统核心其实就是围绕“订单”这个聚合根来做文章。店里的顾客来了要记录他送洗了什么衣物、什么洗涤方式、什么时候要取、收多少钱衣物进车间后要标记状态是正在洗还是已经洗完顾客来取的时候要快速核销订单。再加上每件衣物还要按种类管理比如羽绒服、西装、毛毯洗法不一样价格也不一样。所以系统的数据模型其实不复杂但业务状态流转特别容易做得混乱。有些同学一上来就建了二十多张表字段多到看半天不知道哪个是主键反而把简单事情复杂化了。我这个项目的经验是先把订单主表、订单明细表、衣物分类表、客户表这四张核心表设计好其余的都是锦上添花。1. 项目整体设计与技术选型思路1.1 为什么选SpringBootVue3而不是SSH或者纯JSP很多人问一个洗衣店管理系统而已用得着前后端分离吗我的回答是如果你只想交个作业那JSPServlet确实够用但如果你想做一套真正能在店里跑起来、后续能继续扩展的系统前后端分离是值得的。SpringBoot最大的优势是内置了Tomcat打一个jar包就能跑不需要单独装服务器中间件也不要像SSH时代那样配一堆XML配置文件。Maven管理依赖也比手动拷贝jar包省心太多。Vue3这边Composition API是我特别推荐用起来的。以前写Options API数据都散在data、methods、computed里一旦页面复杂度上来逻辑跳来跳去很难维护。用了Composition API之后可以把订单列表的查询、筛选、状态更新这些逻辑全部封装到一个自定义函数里页面里只需要引用这个函数就行。这个特性在订单管理这种大量列表交互的场景里体验差距特别明显。1.2 系统模块边界划分我在设计这个项目的时候把所有功能分为四个大块。第一块是订单中心包含新建订单、订单列表、状态流转、订单核销第二块是客户管理负责会员信息的登记、累计消费次数以及联系方式快速检索第三块是衣物分类与价格管理洗衣店通常有明码标价的需求不同衣物不同类型价格不同第四块是统计报表简单统计每天、每周的单量和营业额。这个划分既能够覆盖洗衣店日常经营的绝大多数场景又不至于一开始就把权限管理、多门店、员工排班这些复杂功能塞进来。做项目最忌讳一上来就追求大而全先把核心链路跑通再迭代扩展这个原则在真实场景里非常重要。1.3 为什么选MyBatis而不是JPA其实对于这种表关系比较简单的系统Spring Data JPA也挺合适但我最后还是选了MyBatis。原因有两个。一是MyBatis对SQL的控制力更强订单列表要做多条件动态查询用Provider或者XML里的if标签都非常直观写出来的SQL我自己能完全掌控第二个原因是国内公司用MyBatis的比例实在太高了这套技术栈练熟了以后找工作或者接手别的项目会省很多力气。2. 数据库设计与服务端实现2.1 MySQL表结构设计的取舍数据库我用的MySQL 8.0字符集选了utf8mb4而不是utf8这个很重要。utf8在MySQL里其实是utf8mb3存不了emoji而且有些生僻字也会报错。客户端店名、顾客备注这些字段一旦有特殊字符utf8就容易翻车。订单主表我命名为t_order核心字段包括订单编号、客户ID、订单状态、总金额、创建时间、取衣时间、备注。订单明细表t_order_item记录每件衣物的分类ID、衣物描述、洗涤方式、单价和数量。客户表t_customer和衣物分类表t_clothes_type分别记录基本信息。四张表之间用订单ID和客户ID关联不搞多对多洗衣店业务里也没有多对多的需求。订单编号的生成方式我用的是时间戳随机数的组合类似“202502071530001234”。为什么不用数据库自增ID直接暴露给前端因为订单编号是一个业务概念很多时候要在电话里给顾客报单号太长太乱容易报错。我采取的方式是日期加四位流水号同一天内自增跨天归零。这样顾客报号码只需要报最后四位就能快速定位实测非常方便。资金相关的字段我统一用了DECIMAL(10,2)比如订单总金额、单价、应收金额。有些初学者图省事用DOUBLE存钱这是大忌。DOUBLE有精度问题0.10.2算出来可能是0.30000000000000004。一旦涉及金额计算必须用DECIMAL。2.2 MyBatis动态SQL与结果映射细节订单列表页是系统里最核心的页面搜索条件通常有订单状态、客户名称、时间段、订单编号模糊查询。这种场景用MyBatis的动态SQL来处理非常清爽。举一个例子select idpageOrder resultTypecom.laundry.entity.Order SELECT o.id, o.order_no, o.status, o.total_amount, o.create_time, c.name AS customerName FROM t_order o LEFT JOIN t_customer c ON o.customer_id c.id where if teststatus ! null AND o.status #{status} /if if testcustomerName ! null and customerName ! AND c.name LIKE CONCAT(%, #{customerName}, %) /if if teststartTime ! null AND o.create_time gt; #{startTime} /if if testendTime ! null AND o.create_time lt; #{endTime} /if /where ORDER BY o.create_time DESC /select这个SQL里有个细节很多人第一次写会踩坑resultTypecom.laundry.entity.OrderOrder类里如果只有id、orderNo、status、totalAmount、createTime那么customerName就映射不了。我以前的做法是在实体类里加一个customerName字段然后开启MyBatis的驼峰映射开关。因为数据库字段是下划线风格Java属性是驼峰风格如果不开自动映射查出来的customerName在Java里就是null。根本解决办法是在application.yml里配置mybatis: configuration: map-underscore-to-camel-case: true有了这个配置order_no、create_time这些字段才能自动赋到Order对象的orderNo、createTime属性上省掉一大串resultMap。2.3 SpringBoot接口设计与状态机流转后端接口设计遵循RESTful风格订单相关的接口这样规划POST /api/order新建订单同时插入订单主表和订单明细表在一个事务里完成。GET /api/order/page分页查询订单支持状态、客户名、时间段筛选。PUT /api/order/{id}/status更新订单状态。GET /api/order/statistics营业额统计。DELETE /api/order/{id}删除订单一般只允许删除待接收状态的数据。订单状态的流转我定义了一个枚举包括待接收(0)、洗涤中(1)、已完成(2)、已取走(3)、已取消(4)。状态只能往后流转不能乱跳比如待接收的订单不能直接改成已取走。我在Service层写了一个状态机校验方法每次更新状态前先校验当前状态和目标的组合是否合法。很多人觉得状态机是过度设计但实际运营中如果顾客衣服还在洗店员却误操作点了已取走后面排查起来非常麻烦。有了状态校验从源头上就把低级错误挡掉了。创建订单这个接口前后端联调最容易出问题的是时间格式。前端传过来的是2025-02-07 15:30:00这种字符串如果后端接收参数用的是Date类型SpringBoot默认的Jackson反序列化可能报错或者解析失败。我的做法是在application.yml里统一配置时间格式spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8因为MySQL连接串我加了一个serverTimezoneAsia/Shanghai的参数。如果不加JDBC连接时会报UTC和本地时区差8个小时的错误或者出现时间数据入库后早8小时的情况。这两处配置是前后端时间对不齐的万金油解法。2.4 MyBatis一级缓存导致的数据不一致MyBatis的一级缓存是SqlSession级别的默认开启。这在某些场景下会坑人比如连续两次查询同一条订单第一次查出来状态是洗涤中第二次查询之前MySQL里的数据已经被另一个会话改成了已完成但第二次查询因为走了一级缓存返回的结果还是洗涤中。尤其在做订单状态管理这种更新频繁的系统里一级缓存对数据一致性的影响不小。我的解决方式有两种最简单粗暴的是在需要强一致性的查询方法上加上flushCachetrue也就是这个查询执行前强制清空一级缓存。或者直接在Service层把查询方法标记为Transactional(readOnly true)。注意在实际项目中一级缓存只能保证同一个SqlSession内的数据一致跨请求根本不存在共享问题。所以只要搞清楚了作用域就不会被它坑太狠。3. 前端Vue3实现与前后端联调3.1 Vue3工程初始化和目录结构规划前端我用Vite作为构建工具。相比WebpackVite在开发环境的启动速度几乎秒开热更新的体验好一个档次。创建工程的命令很简单npm create vitelatest laundry-frontend -- --template vue-ts我选择了JavaScript而不是TypeScript不是说TS不好而是考虑到这个项目的定位是快速开发和演示JS能省掉不少类型声明的代码量。如果团队规模大、需求变化频繁那还是上TS更稳。前端的目录结构我是按业务模块划分的不是按文件类型划分。比如views目录下面就直接分OrderList.vue、CustomerList.vue、ClothesType.vue、Dashboard.vue。有人说这样复用性差但对于业务页面来说按模块维护的直观性远远重要于代码复用。组件抽取的粒度我的经验是同一个功能代码在三个以上页面出现才考虑提炼出公共组件否则硬抽组件只会让父子通信的链路变长排查问题更痛苦。3.2 订单创建页面与Composition API的实际运用订单创建页面是整个前端开发中工作量最大的部分。它有客户选择、衣物明细的动态添加、洗涤方式选择、金额自动计算、提交时校验等多个交互逻辑。如果全部堆在setup里写代码很容易变得乱糟糟。我在代码里把整个表单逻辑抽成了两个自定义组合式函数useOrderForm.ts和useOrderSubmit.ts。useOrderForm负责表单状态管理包括客户搜索选中的客户信息、衣物明细用的reactive数组、添加明细、删除明细、根据衣物分类联动计算单价、实时算合计。useOrderSubmit则负责调用后端接口处理提交中的loading状态和错误提示。页面组件只负责把这些函数返回的数据解构出来绑定到表单控件上。这样做的核心思想是状态逻辑和视图解耦。以后就算把模板从Element Plus换成别的组件库业务逻辑一行都不用改。代码的大致结构是这样的const { formData, itemList, addItem, removeItem, totalAmount } useOrderForm(); const { submitting, submitOrder } useOrderSubmit(formData, itemList);函数内部的addItem正常情况下是往一个reactive的数组里push一个空对象。这里有个很典型的Vue3响应式陷阱如果用普通数组加上改length的方式去更新视图不会刷新。reactive包裹的数组必须用push、splice这些变异方法来操作直接赋值或者直接itemList[itemList.length] {}都不会触发响应。这个坑Vue3初学者基本都会踩一次。3.3 订单列表页面的筛选、分页与联动订单列表页的筛选条件绑定了几个表单控件点击查询按钮时重新请求第一页的数据。分页组件我用的Element Plus的el-pagination一个非常典型的交互模式el-pagination v-model:current-pagequeryParams.pageNum v-model:page-sizequeryParams.pageSize :totaltotal current-changefetchOrderList size-changehandleSizeChange /后端接口返回的数据结构我统一封装成了{ code: 200, data: { records: [], total: 100 }, msg: success }。前端在request.js的axios响应拦截器里统一拦截code如果不是200就直接弹message提示业务代码里就不需要每个接口都去判断成功失败了。这个封装看起来简单但它能让之后几十个接口的开发节省大量的重复代码。订单列表每一行我放了一个“状态流转”按钮点击后弹确认框调后端的PUT接口更新状态。这类操作要防止用户连续点击导致重复请求。我的做法是在请求拦截器里给每个请求生成一个队列同一个请求如果还在pending状态就阻止第二次发送。也可以用防抖但对于状态流转这种必须保证一次成功的操作防抖不如直接加loading禁用按钮稳妥。3.4 Axios请求封装与跨域调试前后端分离开发时最大的痛点就是跨域。前端跑在5173端口后端跑在8080端口浏览器在同源策略下默认是不允许彼此通信的。开发环境里我的解决方法是利用Vite的代理在vite.config.ts里配置server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }这样前端代码里请求/api/order/pageVite开发服务器会自动把请求转发到后端的8080端口完全绕开跨域问题。生产环境部署时把前端build出来的dist目录扔到Nginx里再配置一个location /api的反向代理指向后端服务思路同开发环境完全一致。有些人在后端直接加CrossOrigin注解或者配全局CORS虽然开发时能通但生产环境如果走了Nginx这些配置反而可能造成混乱。我的原则很简单开发环境靠Vite代理生产环境靠Nginx反向代理后端代码里不做任何跨域处理。3.5 Element Plus表单校验的细节处理订单表单里客户名是必选衣物明细至少有一件洗涤方式必须选。Element Plus的表单校验用的是async-validator我直接把校验规则定义在表单规则里const rules { customerName: [{ required: true, message: 请选择客户, trigger: change }], clothesItems: { type: array, required: true, min: 1, message: 至少添加一件衣物, trigger: change } }有个细节是trigger类型。下拉选择、日期选择的校验trigger用change输入框的校验trigger用blur。如果把trigger统一写成change输入框内容清空后一失焦校验并不触发表单提交时才会提示这种交互体验非常差。我一开始就把所有trigger都设置成change后来测试发现输入框的实时校验体验反而很奇怪改成blur之后流畅多了。4. 核心功能点拆解与实现4.1 订单状态流转的前端展示后端校验订单状态的展示前端是拿一个tag标签来显示不同颜色待接收是灰色洗涤中是蓝色已完成是绿色已取走是橙色取消是红色。这个展示本身不复杂但牵扯到一个问题订单列表接口返回的状态字段是数字0到4前端不能直接显示数字需要一个映射函数转换。我自定义了一个OrderStatus.ts模块导出一个包含所有状态枚举和标签样式的常量数组前端组件里直接引用。这样修改状态文案时不需要去页面里到处找字符串集中管理非常高效。更重要的是后端对状态流转的约束。如果前端直接调PUT /api/order/{id}/status参数传一个跟当前状态毫无关联的目标状态后端必须能拦住。我在OrderServiceImpl里写了一个checkStatusTransition方法private final MapInteger, SetInteger TRANSITION_MAP new HashMap(); { TRANSITION_MAP.put(0, new HashSet(Arrays.asList(1, 4))); TRANSITION_MAP.put(1, new HashSet(Arrays.asList(2, 4))); TRANSITION_MAP.put(2, new HashSet(Collections.singletonList(3))); }意思很明白待接收只能流转到洗涤中或取消洗涤中只能到已完成或取消已完成只能到已取走。其他任何组合都直接抛业务异常。这种集中管理的好处是如果以后洗衣店有“质检不通过退回洗涤”的需求只需要在TRANSITION_MAP里加一条2到1的映射不需要去各个业务流程的代码里找。4.2 多条件动态统计报表的实现思路统计报表模块主要是两个数据今日单量和今日营业额。接口本身只有一句话但数据口径是一个容易被忽略的问题。我做过几个版本一开始直接在SQL里用WHERE create_time CURDATE()但是MySQL的CURDATE()返回的是当天0点0分0秒这个没问题。然而不同员工在晚上查询时可能会因为时区问题查出两条数据。核心还是确保整个系统统一使用Asia/Shanghai时区不然后端启动的JVM时间和数据库时间不在一个频道统计结果就全是错的。除了今日数据统计报表还要支持一个到七个自然日的数据趋势。我在前端用ECharts的折线图展示后端用日期分组查询SELECT DATE(create_time) as day, COUNT(*) as orderCount, SUM(total_amount) as amount FROM t_order WHERE create_time BETWEEN #{startTime} AND #{endTime} GROUP BY DATE(create_time)这里有个优化细节。BETWEEN的查询如果没有索引走全表扫描到数据量过万会明显变慢。我给t_order表的create_time加了一个普通索引实测在十万条数据量级的订单表上这个统计查询依然能保持在几十毫秒内返回。4.3 衣物分类管理对前端联动的影响衣物分类管理是洗衣店系统的一个特色功能。一件衣物有名称、颜色、材质、洗涤方式、单价几个属性。前端订单明细在添加衣物时会先调一个接口获取所有衣物分类渲染到一个下拉框。用户选择衣物分类后单价自动带出同时洗涤方式也根据分类里的约定自动选择。这里我用到了一个Vue3的经典写法watch监听衣物明细数组里每一项所选分类的id变化后自动去取对应分类的单价并赋给当前项的单价字段。注意vue的watch如果监听的是数组嵌套对象的属性deep选项要设为true但deep会比较消耗性能。如果明细最多十项完全可以不用deep直接给下拉框的change事件绑定处理函数在事件发生时就更新单价比watch更直接也更高效。4.4 客户管理的会员时长统计与快速检索客户模块不复杂但有一个体验非常关键的点是快速检索。店里常见的场景是顾客报手机号后四位店员在客户列表输入后四位直接定位到客户。所以客户表里我加了phone字段和索引查询做模糊匹配SELECT * FROM t_customer WHERE phone LIKE CONCAT(%, #{keyword}, %)如果数据量大这个前导通配符会导致索引失效。但洗衣店的客户量一般只有几百到几千全表扫描都毫无压力。这种场景我就没有刻意去优化索引实际运行效果很好。另外客户表里我加了累计消费金额和累计消费次数两个字段每次订单创建时在事务里同步更新。这样客户列表页面就可以看到一个客户常不常来、贡献多少流水支持店长统计老客户的回馈策略。5. 常见问题与排查技巧实录5.1 前后端联调时的CORS和404问题跨域问题的处理我在上文已经给过方案但这里要单独强调一个很多人容易犯的错误。如果前端配了Vite代理后端也配了CORS两种机制会叠加发生一些奇怪现象比如一次请求发两次OPTIONS预检浏览器控制台因为Access-Control-Allow-Origin冲突报错。我最终把后端的跨域配置全部删掉了只保留Vite代理整个世界清净了。如果你用的是Swagger测试后端接口建议在Swagger配置上也加上完整路径否则联调阶段很容易因为路径不对找不到接口。还有一个非常常见的404问题SpringBoot的接口路径和前端axios请求的URL对不上。比如后端接口写的是/api/order/page前端请求写成了/api/order/page/多一个斜杠直接404。这类问题其实很好排查打开浏览器F12Network面板看一下请求的完整URL和后端Controller里的RequestMapping值对照一遍就知道了。5.2 MyBatis返回字段为null的排查方法订单列表和客户列表都出现过某几个字段查出来是null的情况。排查思路我总结为三步。第一步SQL在数据库客户端里直接跑看有没有数据第二步如果数据库查询有值而接口返回null看看实体类字段名和查询结果的列名能不能对应上第三步检查map-underscore-to-camel-case配置是否开启。绝大部分null问题都出在这三步里。有一个特别容易忽视的是如果实体类里某个字段根本不是表字段而是联表查询出来的比如订单实体类的customerName。这种字段如果没开启驼峰映射即使数据库查出来的列别名是customer_nameJava里也赋不上值。要么把SQL里的别名改成和实体类属性一模一样的customerName要么开启全局映射。我建议就是全局开一劳永逸。5.3 跨天订单数据统计错误的案例有一次测试时发现早上九点统计昨天单量比实际少了三单。查了半天发现是时间过滤条件写得太粗糙只用了create_time BETWEEN 2025-02-06 AND 2025-02-07。因为2025-02-07这个字符串在MySQL里会被隐式转换成2025-02-07 00:00:00所以昨天最后一小时的订单全部被漏掉了。排查方法很简单统计口径改为DATE(create_time) 2025-02-06或者用 2025-02-06 00:00:00 AND 2025-02-07 00:00:00。这个坑在凌晨的时间点附近特别容易发作以后写任何带时间的条件都要问一句“这一天的结束时间到底包不包括晚上23点59分59秒”。5.4 SpringBoot版本过高导致的不兼容问题我最初创建项目时用了当时最新的SpringBoot 3.x版本结果用MyBatis-Spring-Boot-Starter时碰上了兼容性问题。很多老版本Starter是基于javax命名空间的而SpringBoot 3.x用了jakarta命名空间直接启动报错ClassNotFoundException: javax.servlet.Filter。解决办法是升级Starter到对应的3.x版本或者干脆用SpringBoot 2.7.x。作为个人项目稳定性优先我最后退回使用了2.7.x版本。现在想想这个决定是对的新版本虽有新特性但对于这种业务比较固定的中小型系统成熟稳定的生态更重要。5.5 MySQL连接配置中SSL和时区问题汇总MySQL 8.0默认开启SSL验证有些环境如果证书有问题启动时连接就会报SSL connection error。我见过大量同学卡在这里。解决方法是连接串里显式关闭spring: datasource: url: jdbc:mysql://localhost:3306/laundry?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8mb4allowPublicKeyRetrievaltrueallowPublicKeyRetrievaltrue这个参数也很关键MySQL 8.0的客户端首次连接时会要求获取服务器的公钥来加密密码如果连接串不带这个参数某些环境也会报错。这三个参数useSSL、serverTimezone、allowPublicKeyRetrieval是我见过所有MySQL连接报错里出现频率最高的坑一次配好能省一周的排查时间。5.6 前后端分离部署时history路由404前端用的是Vue Router如果启用了history模式打包之后部署到Nginx的静态目录刷新某个非首页路径时Nginx会直接404。因为Nginx尝试去找/order/list这个文件找不到就返回404。解决方法是在Nginx配置里加一个try_files指令location / { try_files $uri $uri/ /index.html; }这个配置的含义是如果请求的路径不是真实文件就回退到index.html交给Vue Router去自己处理路由。这个问题在前端打包放上线时才暴露很容易被忽略这里特意记录下来。5.7 金额精度丢失与小数展示问题洗衣店系统牵扯钱金额精度特别敏感。后端我统一用BigDecimal接收和计算前端展示金额时如果数字是35.50浏览器显示35.5看起来不整齐。我在前端封装了一个formatAmount函数用Number(amount).toFixed(2)保证始终展示两位小数。另外在订单提交时前端计算的总金额和后端重新计算的总金额做了一次比对如果不一致会拒绝提交避免因为前端浮点误差导致实际入账金额错误。6. 项目部署、打包与上线经验6.1 后端打包的细节SpringBoot项目打包成jar执行mvn clean package。有几个细节要注意。一是pom.xml里要加spring-boot-maven-plugin插件否则打出来的jar可能不是可执行jar运行时报no main manifest attribute。二是测试代码里如果有依赖数据库的集成测试打包时最好跳过用mvn clean package -DskipTests避免测试环境连不上数据库导致整个打包失败。三是数据库连接配置不要硬编码在application.yml里用环境变量覆盖比如url: ${MYSQL_URL:jdbc:mysql://localhost:3306/laundry}这样部署到不同环境只需要在启动命令里加上--MYSQL_URL或者设置环境变量代码不用重新打包。6.2 JVM启动参数和内存配置洗衣店管理系统本身并发量不高JVM参数不需要太多花哨的东西。我使用的启动命令是java -Xms256m -Xmx512m -jar laundry-server.jar --spring.profiles.activeprod内存设置256到512兆对这个系统完全够用。如果服务器内存比较紧张Xms设成128也一样跑。有些初学者喜欢不加参数直接java -jar默认堆内存会按机器物理内存的四分之一设置在4G内存的机器上就可能占用1G完全没有必要。6.3 前端打包构建与部署路径前端打包执行npm run build默认输出到dist目录。这里有一个要注意的点Vue项目的路由模式如果是history打包后如果直接双击index.html打开页面是白的因为路由用的是BrowserRouter而文件协议的url不匹配。所以前端打包后必须放在HTTP服务器里访问。我用的是Nginx配置大概是这样server { listen 80; server_name laundry.example.com; root /opt/laundry-frontend/dist; index index.html; location /api { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这里的location /api把前端的API请求转发到了后端jar包监听的8080端口。前后端分离的最终形态就是这样一个静态前端一个后端服务靠一个反代把它们粘在一起。6.4 数据库备份的常规方案系统的核心资产是数据尤其是订单和客户信息。我在服务器上写了一个简单的Shell脚本每天晚上三点自动备份MySQL数据库保留最近七天的备份文件#!/bin/bash mysqldump -uroot -p密码 laundry /backup/laundry_$(date \%Y\%m\%d).sql find /backup -name laundry_*.sql -mtime 7 -delete再配合crontab定时任务基本就完成了数据库的安全网。好记性不如烂笔头定期备份这个习惯在出问题的时候才知道有多值。7. 个人开发心得与后续扩展方向做这个系统最深的一个体会是技术栈本身不是难点真正难的是把业务流程想透。我前后改了三个版本第一个版本表结构设计得过于复杂订单状态搞了八个洗完衣服还要分待打包、待质检结果店员根本不会用每天就点两三个按钮。后来我把状态浓缩成五个页面按钮逻辑大幅简化前端代码反而少了接近四成。这说明设计阶段越克制开发阶段越轻松。整个项目对我自己来说也是一次全栈体验的锻炼。从MySQL建库建表到SpringBoot写接口再到Vue3画页面每个环节都有各自的坑但连起来想通之后它们之间的关系就非常清晰了。比如一个订单在后端怎么存、在数据库里长什么样、到前端页面渲染成什么样子一条线串下来整个系统的全貌就立体了。这种全栈视角对以后定位问题和性能优化都非常有帮助。还有一个小技巧值得一提联调阶段我会用Swagger把后端接口文档生成出来同时在前端代码里对每个接口注释写明参数格式和返回结构这样两个人对接时就不需要反复问。个人项目虽然只有我自己开发但这样的好习惯仍然能防止隔段时间再看代码时忘记接口约定。写注释这件事别偷懒项目价值的一半都藏在代码注释和文档里。这套系统目前已经在我朋友的店里跑了两个多月订单管理效率确实比手工记账提升明显。后续如果再加需求我会优先考虑两个方向。第一是增加简单的库存管理洗衣店的洗衣耗材比如袋子、衣架、洗涤剂也可以一起管起来第二是给客户增加一个取衣提醒短信功能订单状态变成已完成时自动发一条短信通知客户来取衣服。这两个需求技术上都是成熟的SpringBoot整合阿里云短信服务或者第三方短信平台都有很完善的文档等店里经营数据再多一点就安排上。
返回列表