基于微信小程序的智慧家政系统:Java毕业设计实战指南

📅 发布时间:2026/9/28 13:55:49
基于微信小程序的智慧家政系统:Java毕业设计实战指南
简介基于微信小程序的智慧家政系统是一套面向Java毕业设计的完整工程采用VueSpringBootMySQL架构供家政管理员、工作人员与消费者三类角色使用。系统除地址管理、订单管理、家政分类管理、家政服务管理、用户反馈管理等核心业务模块外还内置用户管理、部门管理、角色管理、菜单管理、日志管理、数据字典管理、文件管理、图表展示等基础功能并支持基于角色的访问控制可将权限精确到按钮级别适合精细权限约束需求的课题。资源包共409个文件以Java后端、Vue前端、JS脚本为主辅以SQL数据库脚本、YML配置、WXML/WXSS小程序页面等整体约1.9MB目录清晰便于按模块检索与二次开发。目前已有543人学习浏览适合筹备家政类或小程序方向毕业设计的计算机专业学生。1. 基于微信小程序的智慧家政系统为什么说它是最适合练手的 Java 毕业设计做毕业设计最怕两件事一是题目太虚答辩时说不清自己写了什么二是技术栈太老写完自己都不想再看第二眼。基于微信小程序的智慧家政系统恰好踩在中间前端用 Vue 构建微信小程序后端用 SpringBoot 提供接口数据落在 MySQL四个技术点全是招聘要求里高频出现的关键词而且彼此之间边界清晰——小程序只管展示和交互后端只管业务逻辑和鉴权MySQL 只存订单、用户、阿姨、评价这些实体。你不需要像做电商那样处理复杂的库存和支付对账家政系统的核心就是用户下单—平台派单—阿姨接单—服务完成—双方评价这一条主链路表结构和服务接口都能在一张纸上画完。这个方向适合两类人一类是 Java 基础尚可、但没完整做过前后端分离项目的同学可以用它把 Vue 和 SpringBoot 的联调流程走通另一类是打算找工作、需要把项目写进简历的同学家政系统的角色权限、订单状态机、微信登录鉴权这几个点面试时都能展开讲出细节。我见过太多人拿着商城项目去面试结果被问到订单超时未支付你怎么处理就卡住而家政系统的订单流转比商城简单得多反而更容易讲透。这篇笔记我不讲虚的直接按我平时带项目的思路从表结构设计、后端接口、小程序端实现到部署避坑一步步给你能照着敲的方案。2. 先想清楚再写代码角色权限与订单状态机的设计2.1 三种角色的权限边界普通用户、阿姨、管理员智慧家政系统的核心不是下单这个动作而是谁能在什么状态下做什么事。我见过很多毕设把用户和阿姨放在同一张表里用一个 role 字段区分结果后续扩展时到处写 if (role 1) 的判断代码又臭又难维护。常见做法是设计三张独立的业务表用户表、阿姨表、管理员表各自有独立的登录入口和资料字段。用户表存昵称、头像、手机号、默认地址阿姨表存姓名、身份证号、服务技能标签、服务区域、评分、接单状态管理员表最简单账号密码加角色标识就行。微信小程序端的登录天然区分角色用户端和阿姨端是两个不同的登录入口虽然都走 wx.login 换取 openid但后端要根据传入的 userType 参数决定去查哪张表。这样做的最大好处是权限判断可以收敛到后端拦截器里而不是散落在各个 controller 方法里。我一般会定义一个 AuthRequired(role admin) 这样的注解配合 SpringMVC 的 HandlerInterceptor 做统一鉴权比在每个方法里手写判断干净得多。2.2 订单状态机从待接单到已完成每个状态怎么流转订单表是整个系统的事实来源状态字段 design 得不好后面统计报表和消息推送都会翻车。我建议用 tinyint 存状态码而不是用 varchar 直接存待接单这样的中文因为中文值一旦在代码里写死改起来就是全局替换而且数据库索引对数字更友好。家政订单我一般拆成六个状态0 待支付、1 待接单、2 已接单服务中、3 待评价、4 已完成、5 已取消。状态流转的规则要写在后端 Service 层不要在小程序端做任何状态变更。用户下单后生成待支付订单支付成功进入待接单阿姨在阿姨端看到待接单列表点击接单后状态变已接单同时把阿姨 ID 回写到订单表的 worker_id 字段服务完成后阿姨点击完成服务状态变待评价用户评价后变已完成。这里最容易漏掉的是支付超时处理小程序端的 wx.requestPayment 只是发起支付真正确认支付结果要在后端接收微信支付回调回调里更新订单状态。如果毕设不做真实支付可以用一个模拟支付接口替代但状态机里一定要给支付成功留一个明确的回调入口否则后面接真实支付时订单流程会乱。2.3 MySQL 表结构用户、阿姨、订单、评价、服务项五张核心表表结构设计我直接给出最小可用版本字段不多但每个都能在答辩时说出设计理由。用户表 user 包含 id、openid、nickname、avatar、phone、address、create_time阿姨表 worker 包含 id、openid、name、phone、id_card、skill_tags、service_area、score、status、create_time。skill_tags 建议用逗号分隔的字符串存比如保洁,做饭,养老护理查询时用 FIND_IN_SET 或者直接在服务端做拆分不必为这个单独建关联表毕设规模用不到那么重的设计。订单表 order 是核心字段包括 id、order_no、user_id、worker_id、service_item_id、service_date、service_address、amount、status、pay_time、start_time、finish_time、remark、create_time。order_no 要自己生成格式建议用时间戳加随机数避免用自增 ID 直接暴露订单量。评价表 evaluation 关联订单 ID 和用户 ID包含 rating、content、create_time。服务项表 service_item 存服务名称、价格、单位、描述用户下单时选择的就是这个表里的数据。这五张表的关系是用户和订单一对多阿姨和订单一对多订单和评价一对一服务项和订单一对多。在 Navicat 里画 ER 图时把这些外键关系标清楚答辩 PPT 里直接截这张图就是很好的设计说明。3. 用 SpringBoot 把后端搭起来从项目初始化到微信登录鉴权3.1 项目初始化的最小配置pom.xml 和 application.yml 该怎么写创建一个 SpringBoot 项目Java 版本建议 8 或 11不要一上来就 Java 17因为很多老版本的 MyBatis 和 Lombok 在 Java 17 下会出模块化相关的报错毕设没必要在这个地方浪费时间。pom.xml 里依赖控制在六个以内spring-boot-starter-web、mybatis-spring-boot-starter、mysql-connector-java、lombok、fastjson或 Jackson但 fastjson 对微信返回的 JSON 解析更省事、hutool用于生成订单号和加密。如果要做 JWT再加一个 java-jwt。application.yml 里重点关注三个配置块数据源、MyBatis 映射、微信小程序参数。数据源用 druid 连接池配置 initialSize 为 5maxActive 为 20maxWait 为 60000这些参数不要随意加大毕设的并发量根本用不到反而会让连接池初始化变慢。MyBatis 配置 mapper-locations 指向 classpath:mapper/*.xmlmap-underscore-to-camel-case 设为 true这样数据库里的 create_time 能自动映射成实体类的 createTime。微信参数 wx.appid 和 wx.secret 放配置里千万别写死在代码里否则以后换小程序 AppID 时要重新编译。spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/housekeeping?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 type: com.alibaba.druid.pool.DruidDataSource mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true wx: appid: your_wx_appid secret: your_wx_secret这里要特别注意 serverTimezone 必须显式声明MySQL 8.x 默认时区是 UTC不声明的话 Java 里 new Date() 写入数据库会差八个小时。连接串里用 Asia/Shanghai 比用 UTC 更符合国内项目习惯省得在代码里做时区转换。3.2 编写微信登录接口code2session 换取 openid 的完整流程小程序端调用 wx.login 拿到一个临时 code这个 code 只有五分钟有效期而且只能用一次。后端拿到 code 后请求微信的 code2session 接口用 appid 和 secret 换取 openid 和 session_key。openid 是用户在当前小程序下的唯一标识我们需要把它存到用户表里作为登录凭证。常见做法是首次登录时查不到 openid就自动注册新用户并返回用户 ID再次登录时直接返回已有用户信息。这个接口是后端第一个要写的接口因为它决定了后续所有请求怎么识别用户。我一般不用传统 session而是用 JWT 生成 token 返回给小程序端小程序端把 token 存到 storage 里后续请求在 header 里带 Authorization 字段。JWT 的有效期设置为两天过期后小程序端重新调用 wx.login 换取新 token。这样设计的好处是后端服务可以无状态部署以后加一台服务器做负载均衡也不用处理 session 同步问题。PostMapping(/api/auth/login) public Result login(RequestBody LoginRequest request) { String url https://api.weixin.qq.com/sns/jscode2session?appid appid secret secret js_code request.getCode() grant_typeauthorization_code; String response HttpUtil.get(url); JSONObject json JSONObject.parseObject(response); String openid json.getString(openid); User user userMapper.findByOpenid(openid); if (user null) { user new User(); user.setOpenid(openid); user.setNickname(微信用户_ openid.substring(openid.length() - 6)); user.setCreateTime(new Date()); userMapper.insert(user); } String token JWT.create() .withClaim(userId, user.getId()) .withClaim(role, user) .withExpiresAt(new Date(System.currentTimeMillis() 2 * 24 * 3600 * 1000)) .sign(Algorithm.HMAC256(your_secret_key)); return Result.success(token); }这段代码里有两个容易踩的坑。第一个是 openid 为空时说明 code 已失效要直接返回错误信息不要继续往下走。第二个是 JWT 的 secret 密钥不要硬编码在代码里从配置文件读取否则代码传到 GitHub 上密钥就泄露了。请求微信接口用 Hutool 的 HttpUtil 是最省事的比 HttpClient 写起来短很多而且自带超时控制。3.3 用拦截器统一处理 token 校验避免每个接口重复写鉴权逻辑没有拦截器的项目你会看到每个 controller 方法第一行都在写 token 解析和用户查询这是最常见的坏味道。正确做法是写一个 AuthInterceptor 实现 HandlerInterceptor在 preHandle 里从 header 取出 token解析出 userId 和 role放进 request attribute 里然后放行。对于需要管理员权限的接口再定义一个 AdminInterceptor继承同一个 base 逻辑额外检查 role 是否为 admin。配置拦截器时要注意放行路径登录接口和微信支付回调接口不需要鉴权但其他所有 /api/** 下的接口都要走拦截器。我见过有人把静态资源路径也拦截了导致小程序端上传图片的头像接口 401排查半天发现是拦截器把 multipart 请求拦了。小程序端的请求 header 是自定义的你在拦截器里取 header 时要用前端实际传的那个 key前后端约定好比如统一用 Authorization不要出现前端传 token、后端取 Authorization 的情况。Component public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { if (!(handler instanceof HandlerMethod)) { return true; } String token request.getHeader(Authorization); if (StringUtils.isBlank(token)) { response.setStatus(401); return false; } try { DecodedJWT jwt JWT.require(Algorithm.HMAC256(secret)).build().verify(token); request.setAttribute(userId, jwt.getClaim(userId).asInt()); request.setAttribute(role, jwt.getClaim(role).asString()); return true; } catch (Exception e) { response.setStatus(401); return false; } } }注意到这里对非 HandlerMethod 的请求直接放行这是避免拦截器对静态资源和错误转发路径产生误伤。JWT 解析失败统一返回 401前端拿到 401 后清理本地 token 并跳转登录页这是前后端联调时的标准约定。拦截器注册时用 WebMvcConfigurer 的 addInterceptors 方法指定 /api/** 路径模式不要用 /**否则小程序端的资源请求也会被拦截。4. Vue 小程序端开发页面结构、请求封装与订单流程实现4.1 用 uni-app 还是原生小程序选型决定开发效率小程序端我用的是 uni-app基于 Vue 语法编写可以一套代码编译到微信小程序、H5 和 App。如果你只做微信小程序原生小程序语法也没问题但 uni-app 对 Vue 开发者更友好而且有丰富的组件库可以直接用。智慧家政系统这种偏业务表单的项目uni-app 的模板语法和 Vue 的 v-for、v-if 直接复用写起来比原生小程序的 setData 体验好很多。项目结构上我在 src 下分 pages、components、api、utils、store 五个目录。pages 放页面每个页面一个文件夹包含 vue 文件和配置api 目录按业务模块拆分文件比如 order.js、user.js、worker.js每个文件导出接口方法utils 放 request.js 公共请求封装store 用 vuex 管理用户状态token 和用户信息存这里刷新页面时从 storage 恢复。这个结构不是必须的但它能让答辩时的代码讲解有层次面试官问到项目结构时你能讲清楚每个目录的职责。4.2 封装 request.js挂载 token、统一处理 401 和错误提示小程序端的网络请求不能直接用 axios因为 axios 依赖 XMLHttpRequest小程序环境里没有。uni-app 提供了 uni.request 方法我们需要封装一层把所有接口调用统一走同一个入口。封装的核心逻辑是每次请求前从 storage 取 token拼到 header 里响应回来后先判断状态码401 时清除本地登录态并跳转登录页其他错误码统一弹出提示。import { getToken, clearToken } from /utils/auth.js const BASE_URL http://localhost:8080 export function request({ url, method GET, data {} }) { return new Promise((resolve, reject) { uni.request({ url: BASE_URL url, method, data, header: { Content-Type: application/json, Authorization: getToken() }, success: (res) { if (res.data.code 200) { resolve(res.data.data) } else if (res.statusCode 401) { clearToken() uni.navigateTo({ url: /pages/login/login }) reject(res.data) } else { uni.showToast({ title: res.data.msg || 请求失败, icon: none }) reject(res.data) } }, fail: (err) { uni.showToast({ title: 网络异常, icon: none }) reject(err) } }) }) }BASE_URL 这个地址在真机上要换成你电脑的局域网 IP模拟器可以直接用 localhost但手机预览时请求的是手机自身的 localhost会连不上后端。这个问题每年答辩前都会坑一批人解决办法是在 manifest.json 里配置 h5 和微信小程序的 devServer 代理或者直接写死电脑的局域网 IP。另外要注意微信小程序后台需要配置合法域名开发时可以在开发者工具里勾选不校验合法域名但体验版和正式版必须配 HTTPS 域名。4.3 用户下单页面选择服务项、填写地址、提交订单的完整交互下单页面是用户端交互最多的页面包含服务项列表、日期选择、时间选择、地址填写、金额展示和提交按钮。服务项列表从后端接口拉取渲染成卡片样式点击选中后高亮金额根据所选服务项计算。日期选择用微信原生的 picker 组件选择范围限制为今天之后七天避免用户选择过去时间。地址字段默认带出用户表里保存的地址允许修改后提交。提交订单时前端需要组装的数据结构是serviceItemId、serviceDate、serviceTime、serviceAddress、remark。注意不要把用户 ID 和订单金额传上来用户 ID 从 token 里解析金额由后端根据服务项价格计算。前端传金额的话用户改一下请求参数就能改价格这是安全漏洞。后端接口收到请求后校验服务项是否存在生成 order_no插入订单表状态置为待支付。如果是模拟支付直接在后端把状态置为待接单并返回订单 ID如果是真实支付返回预支付参数让前端调 wx.requestPayment。template view classorder-page view classservice-list view v-foritem in serviceList :keyitem.id classservice-card clickselectService(item) text{{ item.name }}/text text{{ item.price }}/{{ item.unit }}/text /view /view picker modedate :startstartDate changeonDateChange view{{ selectedDate || 请选择服务日期 }}/view /picker input v-modeladdress placeholder请输入服务地址 / button typeprimary clicksubmitOrder提交订单/button /view /template服务列表用 v-for 渲染点击时通过 selectService 方法把选中项存入 data。日期选择器用 picker 的 modedatestart 属性传今天往后推七天的日期。提交按钮的 loading 状态要注意防止用户连续点击导致重复下单我一般会在 submitOrder 里加一个 isSubmitting 标志请求完成后才重置。4.4 阿姨端接单页面轮询待接单列表与状态更新阿姨端是家政系统有别于普通商城的地方它的核心场景是阿姨进入小程序后看到一个待接单列表点击接单订单状态更新之后在我的订单里看到已接单的任务和服务地址。待接单列表的数据来源是订单表里 status 1 且服务区域匹配的订单。区域匹配可以在 SQL 里做也可以用服务区域字段直接相等判断毕设规模不需要复杂的 LBS 地理位置计算。轮询接单是个值得注意的设计点。因为小程序没有后台推送能力阿姨端刷新列表要么靠手动下拉要么靠 setInterval 定时拉取。我建议用下拉刷新加进入页面时主动拉取不要用 setInterval 每三秒请求一次因为多个阿姨同时轮询会给后端造成不必要的压力而且小程序在后台时定时器会被冻结。接单操作要做并发控制两个阿姨同时点击同一个订单只有第一个能成功。实现方式是在后端更新订单状态的 SQL 里加一个条件 status 1更新成功影响行数为 1 的才返回成功否则提示订单已被抢。UPDATE order SET worker_id #{workerId}, status 2, start_time NOW() WHERE id #{orderId} AND status 1这条 SQL 是接单接口的核心WHERE 条件里的 status 1 就是乐观锁的思路。高并发下第二条更新语句因为匹配不到记录而影响行数为 0从而避免重复接单。这个点在答辩时一定要能讲出来面试官问你怎么解决并发下的资源竞争时这就是一个很好的回答素材。5. 订单、评价与数据统计业务闭环里的四个后端接口实现5.1 订单列表接口按角色区分查询条件用 Map 接收查询参数订单列表是用户端和阿姨端共用的接口但查询条件完全不同。用户端查自己的订单按状态筛选阿姨端查自己接的订单也按状态筛选。同一张订单表两个角色查出来的字段还不一样用户端需要显示阿姨昵称和头像阿姨端需要显示用户地址和联系方式。所以这个接口的参数里必须带 role 和 userId后端根据角色拼接不同的 SQL。select idselectOrderList resultTypecom.example.entity.OrderVO SELECT o.*, u.nickname AS user_nickname, u.phone AS user_phone, w.name AS worker_name, w.avatar AS worker_avatar FROM order o LEFT JOIN user u ON o.user_id u.id LEFT JOIN worker w ON o.worker_id w.id where if testrole user AND o.user_id #{userId} /if if testrole worker AND o.worker_id #{userId} /if if teststatus ! null AND o.status #{status} /if /where ORDER BY o.create_time DESC /select这里用 LEFT JOIN 而不是 INNER JOIN是因为未接单的订单 worker_id 为空INNER JOIN 会把这些订单过滤掉。OrderVO 是个视图对象额外包含 user_nickname、worker_name 这些展示字段不要直接在 Order 实体类里加否则职责混乱。分页用 MyBatis 的分页插件 PageHelper配置一个拦截器就能用不需要手写 LIMIT。5.2 评价接口事务保证订单状态和评价数据的一致性评价接口涉及两张表评价表插入评价记录订单表更新状态为已完成。这两个操作必须在一个事务里否则会出现评价成功了但订单还停在待评价的情况。SpringBoot 里给 Service 方法加 Transactional 注解就能实现但要注意事务失效的几个场景方法被同类内部调用时事务不生效因为是通过 this 调用而不是代理对象异常被 try-catch 吞掉时事务不会回滚非运行时异常默认不回滚。Transactional Override public void evaluate(Integer orderId, Integer userId, Integer rating, String content) { Order order orderMapper.selectById(orderId); if (order null || !order.getUserId().equals(userId)) { throw new BusinessException(订单不存在或无权评价); } if (order.getStatus() ! 3) { throw new BusinessException(当前状态不可评价); } Evaluation evaluation new Evaluation(); evaluation.setOrderId(orderId); evaluation.setUserId(userId); evaluation.setRating(rating); evaluation.setContent(content); evaluation.setCreateTime(new Date()); evaluationMapper.insert(evaluation); order.setStatus(4); order.setFinishTime(new Date()); orderMapper.updateById(order); // 重新计算阿姨的加权平均分 BigDecimal avgScore evaluationMapper.selectAvgScoreByWorkerId(order.getWorkerId()); workerMapper.updateScore(order.getWorkerId(), avgScore); }事务方法里先做合法性校验再插入评价再更新订单最后算阿姨平均分。注意更新阿姨评分是额外操作如果不想让评分误差影响主要流程可以用 try-catch 包住评分更新并单独标记或者把评分更新放到评价查询时动态计算。但毕设规模直接同步更新也没问题数据量小不用考虑性能。5.3 数据统计接口用 GROUP BY 实现管理员首页的图表数据管理员端首页通常要展示三块数据今日订单量和销售额、近七天订单趋势、服务项销售排行。这些数据不需要单独建统计表直接对订单表做聚合查询就行。今日订单量用 WHERE create_time 今天零点 的 COUNT 查询近七天趋势用 GROUP BY DATE(create_time) 统计每天的订单数服务项排行用 GROUP BY service_item_id 再 JOIN 服务项表取出名称。SELECT DATE(create_time) AS day, COUNT(*) AS order_count FROM order WHERE create_time DATE_SUB(CURDATE(), INTERVAL 6 DAY) AND status IN (2, 3, 4) GROUP BY DATE(create_time) ORDER BY day ASC这个查询要把已取消的订单排除掉否则管理员看到的数据会虚高。统计接口返回的格式要配合 ECharts 或小程序端的手写图表组件一般返回一个数组每个元素包含日期和数值。小程序端画图表可以用 uCharts 插件但答辩时如果只是展示几个数字用简单的纯 CSS 柱状图就行省去引入第三方库的复杂度。注意 MySQL 的 DATE_FORMAT 和 DATE 函数的时区问题如果你在 3.1 里没配置 serverTimezone这里可能出现统计日期偏移一天的情况。6. 前后端联调与部署避坑真机预览、微信登录、请求失败的排查清单6.1 模拟器正常、真机请求失败90% 是 IP 和域名配置问题小程序开发时最常见的现象是模拟器里接口全部正常一真机预览就全部超时。原因只有一个BASE_URL 写的是 localhost。真机上 localhost 指向手机自己当然连不上你电脑上的后端。解决办法是把 BASE_URL 改成电脑的局域网 IP比如 192.168.1.100:8080。如果改了还连不上先确认手机和电脑连的是同一个 WiFi再确认 Windows 防火墙有没有放通 8080 端口。还有一种情况是请求发出去了但报 401这在真机上往往是因为 token 没存进去。小程序的 storage 是异步读取的你在 request.js 里同步读取 getToken() 时可能拿到的是空值。我建议在小程序启动时先 uni.getStorageSync 读取 token 存入 vuex后续请求从 vuex 读取避免异步时序问题。调试时打开开发者工具的 Network 面板能看到请求头里 Authorization 是否真的带上了。6.2 微信登录一直报 openid 为空appid 和 secret 是否匹配、code 是否重复使用微信登录接口是另一个重灾区。最常见的问题是小程序后台的 AppID 和你代码里配置的不一致。很多同学注册了小程序账号但在测试时用的是测试号测试号的 AppID 和 code2session 接口要求的 AppID 对不上导致返回的 openid 为空。排查思路是先看一眼 application.yml 里的 wx.appid 是不是当前小程序后台的 AppID再检查 wx.secret 是不是重置过。第二个常见问题是 code 被重复使用。wx.login 返回的 code 只能用一次你如果在前端调了 wx.login 后又手动触发了一次第二次的后端请求拿到的 code 已经失效。前端代码里确保 wx.login 只执行一次且配合 setTimeout 防抖避免用户在登录页面快速点击。另外回调里要判断 json.get(errcode)微信返回错误码时不要只取 openid否则空指针异常会直接 500。6.3 数据库连接报错时区、驱动版本、编码三个角度的排查顺序启动 SpringBoot 项目时数据库连接失败按时区→驱动→编码顺序排查能省很多时间。第一步看连接串里有没有 serverTimezoneMySQL 8 以上不配必报异常。第二步看 mysql-connector-java 版本5.1.x 和 8.0.x 的驱动类名不一样5.1 用 com.mysql.jdbc.Driver8.0 用 com.mysql.cj.jdbc.Driver在 application.yml 里配错就直接启动失败。第三步看数据库编码如果表结构在建库时没指定 utf8mb4微信用户的昵称里有 emoji 表情时插入会报 Incorrect string value。utf8mb4 的问题是微信小程序场景特有的因为微信昵称允许包含 emoji而 MySQL 的 utf8 字符集存不下四个字节的 emoji。建库语句要写成 CREATE DATABASE housekeeping DEFAULT CHARACTER SET utf8mb4同时表的字段也要确认是 utf8mb4。如果你用的是 Navicat 建表创建时选字符集 utf8mb4排序规则选 utf8mb4_general_ci。6.4 订单状态不更新先查接口入参再查 SQL 影响行数最后看事务线上排查订单状态问题按数据流方向走前端传的 orderId 对不对接口接收后有没有校验SQL 的 WHERE 条件是不是把记录过滤掉了。很多次我以为代码写错了最后发现是前端传的 status 类型不对后端用 Integer 接收但前端传了字符串 2MyBatis 自动转换偶尔会出问题。排查时在 controller 入口打印日志把入参全部记下来一目了然。SQL 影响行数为 0 也是高频原因。比如接单操作如果订单已经被别人接走WHERE status 1 就匹配不到影响行数为 0但代码里没有判断影响行数直接返回成功前端就以为接单成功了。正确做法是 update 后检查 int result orderMapper.updateStatus(...)result 为 0 时抛出订单已被接走的异常。事务方法里抛出的异常会被回滚但要注意 BusinessException 需要继承 RuntimeException 才会触发回滚否则事务默默提交了订单状态就脏了。6.5 答辩演示前必须验证的三个场景登录态恢复、订单全流程、断网重连答辩前的自测清单我固定在三个场景。第一个是冷启动登录态杀掉小程序进程后重新打开能不能自动恢复登录态不用重新授权。这依赖 token 是否持久化到 storage以及 token 过期后的重新登录逻辑是否顺畅。第二个是订单全流程用两个手机或者一台电脑模拟器加一个真机分别在用户端下单、在阿姨端接单、完成服务、评价全程不要断尤其注意从用户端切换到阿姨端时的登录状态隔离。第三个是断网重连真机开飞行模式再关掉重新打开小程序请求能不能正常回来还是卡在 loading 状态不动。我习惯在答辩前一周就锁定代码后面只做数据清理和演示数据准备。家政系统的演示数据不要用测试这种名字管理员账号、阿姨账号、用户账号分别造好每个账号下预置几条真实感的订单记录评价内容写阿姨很准时做饭好吃这样的话比空表格更有说服力。数据库里记得清掉调试时的脏数据把订单表的 status 字段重新梳理到初始状态保证演示时每个流程都能从头走完。7. 从能跑到能用给智慧家政系统加一个 Gitee 备份和接口文档的进阶习惯开发到中期第一件我建议你做的事是把代码推到 Gitee 私有仓库。这不是形式主义我见过太多人电脑硬盘坏了或者误删了整个项目文件夹一个月的代码全没了。Gitee 私有仓库免费git init 之后把代码推上去每次完成一个功能点就 commit 一次commit message 写清楚完成订单列表分页还是修复阿姨接单并发问题。答辩时如果评委问版本管理你把提交记录展示出来比口头说我用了 git有说服力得多。第二件事是接口文档。SpringBoot 项目可以引入 springfox 或 knife4j 生成 Swagger 文档但毕设项目我更建议手写一个 Markdown 文档把每个接口的 URL、请求参数、返回示例、错误码列清楚。原因有二Swagger 生成的文档在答辩演示时看起来很专业但配置略显繁琐而且版本兼容性问题多手写文档的过程能强迫你重新审视每个接口的参数设计我去年带的一个学生就在写文档时发现自己订单接口的 serviceDate 参数没有做非空校验这种问题走一遍文档就能暴露。文档放在项目的 docs/ 目录里和代码一起提交以后自己回看也方便。最后一个习惯是给核心接口写单元测试。我知道毕设阶段写测试的人不多但至少给两个关键 Service 方法写测试登录接口的 openid 返回值正常接单接口的并发场景下只有一次更新成功。SpringBoot 的测试不需要启动整个项目用 SpringBootTest 加 MockMvc 就能测 controller。测试代码能帮你确认改完数据库表结构后没把老接口弄坏答辩时提到我用 JUnit 测过核心接口的并发场景面试官对你的印象会完全不一样。开发这件事代码只是前半场让代码可维护、可验证才是后半场希望这些习惯能帮你在毕设和第一份工作之间少走一段弯路。本文还有配套的精品资源点击获取