ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Spring Boot + Vue家政服务平台源码:前后端分离与订单状态机实践

2026/9/17 0:25:38 拓冰建站 浏览量
Spring Boot + Vue家政服务平台源码:前后端分离与订单状态机实践 简介面向计算机、电子信息工程、数学等专业的毕业设计与课程设计场景这份基于Spring Boot与Vue的家政服务平台源码提供了一套可直接运行的前后端分离项目方案。代码经严格调试并通过导师认可作者为阿里云开发者社区专家博主项目实战经验丰富。资源包共947个文件以179个Java文件、61个Vue组件、164个JS文件为核心配合HTML、CSS、XML/YML等配置与图片等静态资源覆盖前端页面、后端逻辑与项目配置完整链路压缩包整体仅17.97MB并附带bat脚本便于安装与启动。已有111人学习适合缺乏完整项目经验的学生快速搭建业务框架。除源码外还能借鉴作者在实际开发中积累的结构组织、调试思路与模块划分方法对完成毕设或期末大作业具有直接参考价值。1. 家政服务平台源码先分清“业务漂亮”和“代码漂亮”同样是增删改查家政平台拿高分的关键往往不在谁能写更长的 CRUD而在订单怎么从“待指派”走到“已完成”。这套基于 Spring Boot 和 Vue 的源码恰好把前后端分离的骨架搭得完整后端按 Controller-Service-Repository-Entity 四层组织前端按 Router、Store、View 组织业务上覆盖用户登录、服务浏览、下单预约、派单、服务跟踪、评价六个闭环。对做毕业设计的学生和想快速搭一套“前台 后台”业务原型的工程师来说它的参考价值在于一个课堂项目和“可演示项目”之间差哪些东西。后面会按“读源码 → 拆后端 → 拆前端 → 排错 → 答辩”一步步展开每个环节都能直接对着操作。2. 家政服务平台源码先读目录再跑代码拿到一份源码第一件事不是启动而是看目录。目录是一个项目的地图哪里是接口入口、哪里是业务规则、哪里是页面组件在 IDE 里展开三秒钟就能判断一份源码的工程质量。家政服务平台这类前后端分离项目后端不关心页面长什么样前端不关心数据库怎么连二者之间只通过 JSON 通信目录自然可以完全独立。2.1 先读目录再跑代码一份前后端分离的项目拓扑常见的开题目录命名是backend和frontend或者server和web以项目根目录为例整体结构一般长这样home-service ├── backend # Spring Boot 后端 │ ├── src/main/java/com/example/homeservice │ │ ├── controller # 接口层只做参数接收与返回 │ │ ├── service # 业务层订单状态流、事务都在这里 │ │ ├── repository # 数据访问层JPA 接口 │ │ ├── entity # 实体类与数据表字段一一对应 │ │ ├── config # 安全配置、跨域配置、拦截器注册 │ │ └── common # 统一返回结果、异常、工具类 │ └── src/main/resources │ ├── application.yml # 数据源、JPA、端口等配置 │ └── db/init.sql # 建库建表脚本 └── frontend # Vue 前端 ├── src │ ├── api # 每个页面对应一个接口文件 │ ├── router # 路由表与登录守卫 │ ├── stores # Pinia 状态管理 │ ├── views # 页面组件 │ ├── components # 通用组件 │ └── utils # axios 实例、工具函数 ├── vite.config.js # 开发服务器与代理配置 └── package.json读目录要抓住对应关系controller里的每一个类应该能在frontend/src/api里找到同名接口封装views里的页面与router里的路径一一对应。如果api目录出现“某个页面调了十来个接口但 api 文件只有两个”的情况说明接口封装不够细后期替换域名或统一加参数时后端会很痛苦。反过来如果views里一个页面写了两千行说明组件拆分不到位。拿到任何源码先按这个标准过一遍结构再启动代码不迟。2.2 家政平台核心实体与数据表设计家政服务平台的核心不是服务列表而是“人、服务、订单、评价”四个要素。数据表设计是否合理决定了后续派单、统计、结算功能能不能做。常见核心表见下表名关键字段类型说明userid, phone, password, rolebigint, varchar, varchar, tinyintrole 区分用户/管理员/服务人员workerid, user_id, service_type, scorebigint, bigint, varchar, double服务人员档案关联 userservice_itemid, name, price, unitbigint, varchar, decimal, varchar服务项目按小时/次计价ordersid, user_id, worker_id, service_id, status, appoint_timebigint订单主表状态用枚举值存order_status_logid, order_id, from_status, to_status, created_atbigint状态流转流水evaluationid, order_id, content, starsbigint订单完成后写评价多数毕设版本会用一个role字段区分三种身份而不是建三套用户表这对课设体量是对的。需要注意orders表的status字段尽量存整数而不是字符串因为状态枚举通常定义在 Java 后端前端接受的是数字直接用“待指派”“已完成”存字符串统计时会遇到编码问题。order_status_log这张表很多初版源码没有但如果要把“订单什么时候被派单、谁改的状态”做成答辩亮点这张表是最便宜的方案——它只是把每次状态变更插入一条记录却能完整支撑追溯。2.3 Spring Boot 四层架构与 Vue 目录的真实映射源码读懂与否取决于能不能把后端的“层”映射到前端的“目录”。Spring Boot 四层架构中Entity与数据库表对齐Repository负责查询Service层写业务规则Controller只暴露接口映射到 Vue 侧api目录对应Controllerstores相当于前端的“临时 Service”views则是最终展示。一个常见的错误理解是前端的api目录只是放请求 URL 的。对于家政平台这种多人协作课设api文件应该把参数类型也写清楚。比如下单接口createOrder(userId, serviceId, appointTime)三个参数缺失任何一个后端RequestBody就会直接报 400。在api层就把参数检查做掉比如if (!appointTime) return比打到后端再被 Spring Boot 的MethodArgumentNotValidException拦下来要省事得多。看懂这层映射就不会出现“后端改了接口字段前端全局搜字符串替换”的操作。3. 后端源码的底气用 Spring Boot 把订单状态机写清楚家政平台最容易做“虚”的地方是订单模块。很多源码的订单只有status字段代码里到处setStatus(3)逻辑分散在十个地方。优秀源码与普通源码的分水岭就是是否把状态流转收敛到一个地方集中控制。3.1 Java 后端依赖组合JPA Lombok JWT 的选型与最小配置Spring Boot 后端的核心依赖一般集中在三个 starter 上Web 提供 MVC 能力Data JPA 负责数据库操作Security 做登录鉴权。为了减少冗余代码通常会再挂 Lombok。一个最小可启动的pom.xml核心片段dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-jpa/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-security/artifactId /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId version0.11.5/version /dependencyjjwt按api、impl、jackson三个包引入只用jjwt-api运行时会报NoClassDefFoundError记得把jjwt-impl和jjwt-jackson也加上。数据源方面application.yml里通常至少配置datasource.url、spring.jpa.hibernate.ddl-auto、spring.jpa.show-sql三个关键项。ddl-auto在初版演示时用update能自动建表但答辩前建议改成validate或者干脆去掉用db/init.sql手动控制表结构——否则改造数据表时 Hibernate 会按实体类自动加列出现“代码里没这个字段但数据库有”的尴尬。对于 JPA 的JpaRepository接口只用findById、findAll、save就能覆盖大部分场景但家政平台的“服务人员列表”往往会按评分排序这种查询在 Repository 层写方法签名即可public interface WorkerRepository extends JpaRepositoryWorker, Long { ListWorker findByServiceTypeAndStatusOrderByScoreDesc(String serviceType, Integer status); }findByServiceTypeAndStatusOrderByScoreDesc这段方法名会被 Spring Data JPA 解析成 SQL按service_type和status过滤按score降序排列。这种命名式查询的好处是没有手写 SQL字段改名时 IDE 能直接提示编译错误。参数上注意score降序后评分相同的情况下最好追加id asc保证排序稳定否则分页时可能出现同一页数据两次。这也是 JPA 面试常问的“方法名解析规则”的实例。3.2 订单状态机Spring Boot Service 层如何守住家政派单边界我通常会把订单状态定义成枚举而不是魔法数字。状态枚举本身很简单重点在每个状态“能去哪、不能去哪”的规则。家政订单的典型合法流转是待指派 → 已指派 → 服务中 → 已完成任何状态下用户都可以取消但“已完成”和“已取消”是终态。核心代码集中在 Service 层public class OrderService { public void changeStatus(Order order, OrderStatus target) { OrderStatus current order.getStatus(); if (!canTransit(current, target)) { throw new IllegalStateException(订单状态不允许从 current 流转到 target); } order.setStatus(target); orderRepository.save(order); orderStatusLogRepository.save(new OrderStatusLog(order.getId(), current, target)); } private boolean canTransit(OrderStatus from, OrderStatus to) { if (from OrderStatus.CANCELLED || from OrderStatus.COMPLETED) { return false; // 终态不可再变 } if (to OrderStatus.PENDING) { return false; // 不允许回退到待指派 } return to.ordinal() from.ordinal(); } }这段逻辑的核心在于to.ordinal() from.ordinal()保证状态值只能单向增长而from为终态时直接返回 false把非法流转拦截在业务入口。OrderStatus枚举的顺序本身代表业务推进方向因此枚举定义时一定要按生命周期排列不能把CANCELLED插在中间。changeStatus方法内保存了订单和日志两张表需要加Transactional保证要么都成功、要么都回滚。这样设计后Controller 层就不能直接order.setStatus(目标值)了必须调orderService.changeStatus()。如果源码里到处都是setStatus说明没有收口收口之后派单、完成、取消按钮各自调用同一个方法非法流转全部在老位置报错。答辩时能讲清楚这个设计比列十个接口价值高得多。3.3 JWT 登录拦截与跨域配置源码里的安全底线家政平台涉及用户手机号和家庭地址登录鉴权是安全底线。常见方案是用户名密码认证后发放 JWT前端每次请求在Authorization头带上Bearer token后端通过拦截器校验。核心拦截器逻辑public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String token request.getHeader(Authorization); if (token ! null token.startsWith(Bearer )) { token token.substring(7); } Long userId JwtUtil.parseToken(token); if (userId null) { response.setStatus(401); return false; } request.setAttribute(userId, userId); return true; } }parseToken返回 null 表示 token 过期或签名不对直接返回 401解析成功则把 userId 塞进 request 属性Controller 通过RequestAttribute Long userId获取避免每个接口都重复解析。拦截器注册时要注意两个例外登录接口/api/auth/login和注册接口通常放行Swagger 路径和静态资源路径也要放行否则前端联调时接口文档全被 401 挡住。跨域配置要区分开发环境和生产环境。开发环境常见做法是在 Vue 的vite.config.js里开代理生产环境则统一走 nginx 反向代理如果后端代码里写了CrossOrigin务必将allowCredentials设为true否则携带 Cookie 的请求会被浏览器拦截。一个稳妥做法是后端只允许http://localhost:5173一个来源其余环境交给 nginx 处理。4. Vue 前端源码的骨架路由守卫、状态管理和接口封装Vue 部分的重点不在页面多好看而在“数据怎么流转”。家政后台管理页面大概有十几个如果每个页面都自己发 axios 请求、自己处理过期 token代码量会爆炸。良好封装下页面代码量其实很少——大段的重复逻辑都被收进路由守卫、axios 拦截器和全局状态里了。4.1 Vue 动态路由与登录守卫订单详情页的参数怎么传路由表要定义meta信息用来区分哪些页面需要登录。订单详情页这类带参数的页面路由配置长这样{ path: /orders/:id, name: OrderDetail, component: () import(../views/order/OrderDetail.vue), meta: { requiresAuth: true } }登录守卫在每个路由跳转前判断router.beforeEach((to, from, next) { const token localStorage.getItem(token); if (to.meta.requiresAuth !token) { next({ path: /login, query: { redirect: to.fullPath } }); return; } next(); });query.redirect记录用户原本要去的地址登录成功后再router.push(route.query.redirect)跳回来这是登录跳转的常规玩法。跳转订单详情时用router.push({ name: OrderDetail, params: { id: orderId } })页面内通过route.params.id或useRoute()取得参数后调接口。这里有个易踩的坑从订单列表点进详情再切到别的路由组件实例被复用后watch(() route.params.id)必须重查数据否则会出现“路由变了页面数据没刷新”的问题。这是 Vue Router 参数响应式的高频考点。4.2 Pinia 与 Axios 拦截器把登录态和接口报错收口到一处用户信息和登录状态放进一个 store全局只有一处修改入口。下面是stores/user.js的要点export const useUserStore defineStore(user, { state: () ({ token: localStorage.getItem(token) || , userInfo: {} }), actions: { setLogin(token, info) { this.token token; this.userInfo info; localStorage.setItem(token, token); }, logout() { this.token ; this.userInfo {}; localStorage.removeItem(token); router.push(/login); } } });axios 实例放在utils/request.js拦截器统一处理 token 注入和过期跳转service.interceptors.request.use(config { const userStore useUserStore(); if (userStore.token) { config.headers.Authorization Bearer ${userStore.token}; } return config; }); service.interceptors.response.use( res { if (res.data.code 401) { ElMessage.error(登录已过期请重新登录); useUserStore().logout(); return Promise.reject(new Error(unauthorized)); } return res.data; }, err { ElMessage.error(err.response?.data?.message || 网络异常); return Promise.reject(err); } );code 401的处理逻辑放在响应成功分支里是因为部分后端错误码用 200 包装业务状态如果后端严格按 HTTP 状态码返回这里就靠错误分支来触发status 401的逻辑。无论是哪种约定核心思路相同登录过期不能只弹个提示就完事必须清空 store、清空 localStorage、跳回登录页否则用户误以为还在登录态。前端源码是否成熟看这一处就够了。4.3 Vue 业务组件拆分节奏列表页、表单页、状态卡片家政后台的页面看起来多实际可以归纳为三类列表页服务项目、订单、用户、表单页发布服务、编辑预约、状态卡片订单详情里的状态时间线。我一般建议每个view只画轮廓页面里的卡片、弹窗、表格单独抽成components。状态卡片是家政项目的特色。订单详情页通常有一段状态时间线待指派 → 已指派 → 服务中 → 已完成用el-steps或自己写四段色块实现。这种组件只接收currentStatus一个 prop内部根据状态值计算当前是第几步不同状态的颜色和图标由组件自行完成。这样做之后订单列表页、订单详情页、个人中心都要展示状态时就不存在各写一份的问题。源码里组件拆分越细越容易在两边各加功能而不互相影响。5. 联调、部署与打包三类必然遇到的故障现场毕业设计做到最后一两周问题通常不是新功能写不出来而是“明明本地好的部署上去就挂了”。这三种故障每个 Spring Boot Vue 项目都会遇到提前写好排错路径能节省大量时间。5.1 vite.config.js 代理跨域前端开发环境的唯一出口本地开发时前端跑在 5173 端口后端跑在 8080 端口直接axios.get(http://localhost:8080/api/...)会被浏览器跨域拦截。正确做法不是在后端写CrossOrigin(*)放开所有来源而是在vite.config.js里配置代理export default defineConfig({ server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true, rewrite: path path.replace(/^\/api/, ) } } } });配置的关键三个参数target指向后端真实地址changeOrigin设为true让后端以为自己收到的来源是 8080可以绕过部分严格校验rewrite决定是否去掉/api前缀。如果后端接口路径本身就是http://localhost:8080/api/...rewrite这段就不要加加了反而会把路径变成/...导致 404。判断方法很简单浏览器 Network 面板看请求路径发出去的是什么。这里常见误区是“为什么代理配了还跨域”多数因为浏览器直接访问了http://localhost:8080而不是由 5173 转发。5.2 history 路由刷新 404nginx 的 try_files 兜底Vue Router 开启history模式后URL 里没有#追求美观。但刷新/orders/12这个页面时nginx 会拿着这个路径去找真实的/orders/12文件找不到就返回 404。解决方法是把所有前端路由都指向index.html由 Vue Router 接管server { listen 80; server_name your-domain.com; root /usr/share/nginx/html; index index.html; location / { try_files $uri $uri/ /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; } }try_files $uri $uri/ /index.html的意思是先找真实文件找不到就找目录还找不到就回退到index.html。location /api/的proxy_pass后面写了http://127.0.0.1:8080而没有额外的路径是将/api/xxx原样转发到后端。如果proxy_pass写成http://127.0.0.1:8080/则/api/order/list会被转发成/order/list后端没有对应映射就直接 404。这种 nginx 配置是最常见的一种主要是要注意 root 要和vite.config.js打出的dist目录一致。location /assets/是否需要单独配置取决于vite.config.js里的base是否设置为相对路径。打包后如果刷新不报 404 但样式丢失问题多半不在这段配置而在打包资源路径。5.3 打包后布局异常publicPath 与路由模式自检清单“ Vue 打包后布局异常”是高频热搜问题根源多数是打包后的静态资源路径不对。用npm run build打出的dist目录index.html引用的 css/js 默认是/assets/xxx.js这要求部署时资源必须放在域名根路径下。如果放在子目录比如http://your-domain.com/home-service/就会因为找不到/assets/xxx.js而白屏或布局坍塌。自检按以下顺序排查症状检查项处理方案白屏控制台报Failed to load resourceindex.html 中 script src 是否以/assets/开头vite.config.js中设置base: ./重新打包页面能打开刷新 404路由模式是否为 historynginx 增加try_files或改用 hash 模式图片/字体不显示相对路径 vs 绝对路径静态资源统一走 import 或new URL(./xx, import.meta.url)样式乱了但 js 正常加载是否开启了chunkSizeWarningLimit或分包检查路由组件是否都用动态 import最容易检查的是第二点路由模式是history时必须配后端想省事就在打包前把路由改回hash。如果用base: ./注意所有路由级代码中手写的绝对路径/api/xxx不会受影响因为base只影响静态资源不影响 axios 的 URL。这些细节没有写在 Spring Boot 源码里但部署时恰是决定演示能否成功的关键。6. 毕业设计答辩与二次开发源码里被低估的加分点答辩时老师不太可能一行行读代码但会问“你觉得哪里设计得最好”。与其说“我用了 Spring Boot Vue”不如指着一处具体设计讲清楚理由能讲的点也的确很多订单状态机状态集中在 Service 层管理非法流转直接抛异常配合order_status_log表可完整追溯。全局返回与异常CommonResult统一了成功和失败结构前端拦截器只需要判断code即可不用每种报错分别处理。权限控制前端路由守卫管页面后端 JWT 拦截器管接口双端校验缺少任何一环都可通过 Postman 直接调接口绕过页面登录。分页与查询列表页用Pageable和PageHelper风格统一分页避免一次性查全表简历里写“性能优化”时没有落点。组件化程度订单状态卡片、评价弹窗是独立组件一个组件在三个页面复用你可以直接强调“开工前画了组件树避免后期拆组件影响已有页面”。最后一个可以立刻动手验证的点启动后端和前端的测试环境用无痕窗口走一遍“注册/登录 → 下单 → 模拟后台派单 → 服务完成 → 写评价”的完整流程每走一步都观察order_status_log表插入的记录。如果这张表有数据且状态不能乱跳这份源码的核心逻辑就是自洽的答辩时甚至可以打开数据库现场演示状态流转。把这一轮走通比准备十页 PPT 都有说服力。本文还有配套的精品资源点击获取