简介:一套完整的微信小程序上门维修系统毕业设计源码,面向高校计算机专业学生或开发者,包含用户、维修员、管理员三类角色,覆盖维修信息发布、记录跟踪、评价收藏以及后台综合管理等业务模块。项目基于Java、小程序和MySQL构建,提供完整前后端源码与数据库文件,可直接导入开发工具运行,也适合作为毕业设计或课程设计的参考。压缩包内共1249个文件,大小约26MB,主要类型包括Vue页面、Java后端、小程序JavaScript、WXML/WXSS样式及数据库SQL脚本等,目录结构清晰便于检索。已有87人学习下载,按环境说明配置JDK1.8、Tomcat7、Maven3.3及微信开发者工具即可启动。除了可直接运行的系统,还能学习多角色权限设计、前后端接口交互与移动端页面适配方法,对快速搭建同类管理类小程序项目具有参考价值。
1. 微信小程序毕业设计选上门维修系统:一个能跑通完整闭环的务实题目
拿到“上门维修系统”这个毕设题目,你真正要做的是把一条业务线从用户端打通到师傅端:用户在微信里发维修单,师傅在小程序里接单、上门、完工,管理员在后台处理审核和异常状态。这个题目比纯展示类的商城或公告栏更适合练手,因为它的核心是一张维修订单,围绕这张单子天然长出用户、师傅、服务分类、地址、评价几张关联表,业务不算复杂,但闭环完整。技术栈则非常贴近招聘市场常见组合:Java 做后端接口,MySQL 存业务数据,微信小程序做两端界面,再加上一篇 LW(毕设圈子里通常指论文,取“论文”二字拼音首字母)作为文档材料,正好凑齐毕业设计要的“系统 + 数据库 + 论文 + 演示”四件套。
我见过不少同学解压这种源码包之后,第一步就是照着 README 敲启动命令,结果卡在环境变量、依赖版本和端口冲突上,一下午没跑起来。这篇笔记按“包结构—数据库—后端接口—小程序联动—踩坑—演示验证”的顺序拆,目标是让你拿到这个题目后,能把它变成一套能在答辩现场流畅演示的系统,同时在被老师追问“为什么这样设计”时,能说出每个模块的真实理由。
2. 先看懂包里有什么:源码结构、LW 文档和运行前置条件
2.1 LW 是论文而不是某个框架:zip 交付物的通用组成
很多第一次接触毕设源码包的同学会把“LW”当成一个技术名词去搜,结果什么都搜不到。实际上在毕业设计交易和资源分享场景里,LW 就是“论文”的拼音首字母缩写,它不是一个运行组件,而是一份 Word 文档,通常包括开题报告、任务书、中期检查、论文正文和答辩 PPT。拿到压缩包后,第一件事不是急着启动项目,而是先理清目录,否则很容易把论文目录里的截图误当成运行说明。
一个规范的毕设源码包通常包含四块:后端工程(一个 Maven 项目)、小程序前端工程(一个微信开发者工具项目)、SQL 脚本(建库建表语句)、LW 文档目录。有些还会附带 README 或部署说明。先做一次完整解压,把目录树列出来,确认后端、小程序、数据库脚本三样都在,再开始配置环境。如果压缩包里有子目录被解压套了两层,别忘了把里层目录提到外层,否则后面导入 IDE 时路径全错。
提示:拿到包后先解压,再核对目录。后端的 application.yml、小程序的 utils/request.js、SQL 脚本是三个最先要看的关键文件,它们决定了整个系统怎么连起来。
2.2 后端工程结构:一个典型的 Spring Boot 分层
上门维修系统的后端在毕设级别几乎都是 Spring Boot 单体工程,很少拆微服务。常见结构是 controller、service、mapper、entity、config 五个包,用 MyBatis Plus 或原生 MyBatis 操作 MySQL。拿到手先看 pom.xml,确认依赖里有没有 MyBatis Plus、MySQL 驱动、Lombok、JWT 相关库,这些决定了你接下来改代码的方式。
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>这段 pom 是毕设后端最常见的依赖组合。spring-boot-starter-web 提供 REST 接口能力,MyBatis Plus 省去大量手写 SQL 的重复劳动,Lombok 让实体类不用写 getter/setter。这三个依赖几乎是这类系统的基本盘,如果包里的项目用的是别的持久层框架,比如 Spring Data JPA,也不奇怪,但 MyBatis Plus 在中文毕设项目里出现频率更高,因为它的代码生成器能快速生成 mapper 层。
src/main/java/com/repair ├── controller # 接口入口,接收小程序请求 ├── service # 业务逻辑,订单状态流转都在这里 ├── mapper # MyBatis Plus 数据访问层 ├── entity # 数据库实体类 ├── config # 拦截器、跨域、MyBatis 配置 ├── common # 统一返回结果、异常处理、工具类 src/main/resources ├── application.yml # 数据源、端口、微信小程序配置 └── mapper # XML 文件,写复杂 SQL 时使用这个分层的好处是边界清楚。controller 只做参数接收和结果返回,service 里写订单状态判断、支付回调处理、并发接单这类业务逻辑,mapper 只管数据库操作。答辩时老师问“某个功能在哪一层实现”,你能直接指到对应文件,比把所有逻辑堆在 controller 里要加分不少。接下来打开 application.yml,确认数据库名、端口、小程序 appid 和 secret 是否和你本地环境一致。
2.3 小程序端目录:页面、公共方法和服务封装
小程序端目录结构比后端直观。app.js 负责全局生命周期,app.json 配置页面路由和窗口样式,utils/request.js 封装 wx.request,pages 下面按功能分页面目录。上门维修系统的小程序通常有用户端和师傅端两个身份入口,页面大概包括首页(服务分类和推荐师傅)、下单页、订单列表、订单详情、我的页面、师傅任务页。
miniprogram/ ├── app.js ├── app.json ├── utils/ │ ├── request.js # 统一封装 wx.request │ └── util.js # 时间格式化等工具 ├── pages/ │ ├── index/ # 首页,服务分类和 banner │ ├── order/ # 下单页,填写地址与故障描述 │ ├── orderList/ # 订单列表,区分用户/师傅视角 │ ├── orderDetail/ # 订单详情,状态操作按钮 │ ├── profile/ # 我的,个人信息与消息 │ └── worker/ # 师傅端任务列表与接单看小程序工程时先看 app.json 里的 pages 字段,它决定哪些页面会出现在正式包里。再看 utils/request.js,确认后端接口地址是写死的 IP 还是相对路径,这直接影响你本机调试能不能通。多数毕设项目会在这里定义一个 BASE_URL,比如 http://localhost:8080,你本机跑微信开发者工具时,要把 localhost 改成自己电脑的局域网 IP,否则真机预览时手机会连不到电脑上的后端服务。
2.4 环境清单与启动顺序:先把依赖排干净
不要一上来就双击 IDEA 导入,先列一个环境对照表,让所有软件版本对齐。JDK 用 1.8 最稳妥,高版本 JDK 遇到旧项目可能出现反射和依赖兼容问题;Maven 3.6 左右即可,太新的 Maven 对旧仓库源也可能有兼容问题。MySQL 用 5.7 或 8.0 都可以,但要注意 8.0 的认证插件和连接串参数与 5.7 不一样,后面会专门讲这个坑。
| 软件 | 推荐版本 | 用途 | 注意点 |
|---|---|---|---|
| JDK | 1.8 | 运行 Spring Boot | 高版本可能导致 Lombok 或旧依赖失效 |
| Maven | 3.6.3 | 管理后端依赖 | 用阿里云镜像加速依赖下载 |
| MySQL | 5.7 或 8.0 | 存储订单与用户数据 | 8.0 需配置 allowPublicKeyRetrieval |
| 微信开发者工具 | 最新稳定版 | 导入小程序工程 | 调试需开启“不校验合法域名” |
| IDEA | 2023 或更新 | 导入 Maven 后端 | 配置 Lombok 插件 |
启动顺序也有讲究。先建数据库并执行 SQL 脚本,再改 application.yml 里的数据库密码,然后启动后端,最后打开微信开发者工具导入小程序。颠倒顺序最常见的后果是:后端起来了但报找不到表,或者小程序先打开后因为后端没起而白屏。执行 SQL 时建议用 Navicat 或命令行 source 命令整段导入,不要只复制其中一部分,否则缺表或缺外键,后面接口一调就报错。
3. 数据库建模:维修工单状态机与三张核心表
3.1 工单状态机:一张表看懂订单走到哪一步
上门维修系统的业务核心不是用户表,也不是师傅表,而是维修订单。订单表里最重要的字段就是状态 status,它决定了用户端显示什么按钮、师傅端能做什么操作、管理员在后台能看到哪些待处理数据。常见状态设计是 0 到 6 的数字枚举,而不是直接存中文,这样做既省空间又方便在代码里做条件判断。
| status | 含义 | 用户端按钮 | 师傅端操作 |
|---|---|---|---|
| 0 | 待接单 | 等待师傅接单,可取消 | 看到抢单按钮 |
| 1 | 已接单待上门 | 显示师傅联系方式 | 联系用户,标记已出发 |
| 2 | 维修中 | 等待维修完成 | 提交维修内容和费用 |
| 3 | 待支付 | 支付维修费用 | 等待用户支付 |
| 4 | 已完成 | 查看订单详情 | 查看订单详情 |
| 5 | 已取消 | 无操作 | 无操作 |
状态机的设计直接决定 service 层代码怎么写。凡是涉及状态变更的接口,比如接单、开工、完工、支付,都要在 SQL 条件里带上“当前状态必须是某个值”,否则会出现师傅一边维修,用户一边取消订单的并发冲突。状态设计成数字而不是字符串,还有一个好处是 switch 判断比字符串匹配更快,而状态流转的合法性校验也更容易写。
3.2 用户、师傅、订单三张表怎么建
用户和师傅是两类不同角色。常见的做法是拆成两张表:users 放微信用户基础信息,worker 放师傅的审核状态、技能分类和服务区域。这样设计的好处是师傅的信息字段如审核状态、评分、接单数,不会污染用户表;用户和师傅的关联通过 user_id 完成。订单表则关联用户、师傅、服务分类和地址快照。
CREATE TABLE `user` ( `id` bigint NOT NULL AUTO_INCREMENT, `openid` varchar(64) NOT NULL COMMENT '微信 openid', `nickname` varchar(64) DEFAULT '', `avatar` varchar(255) DEFAULT '', `phone` varchar(20) DEFAULT '', `user_type` tinyint DEFAULT 0 COMMENT '0用户 1师傅', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_openid` (`openid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';这张用户表做了两个关键设计:openid 加了唯一索引,保证同一个微信用户只能有一条记录;user_type 字段区分角色,但不在表结构上做硬隔离,因为一个用户既可以是下单者,也可以申请成为师傅。nickname 和 avatar 是微信授权拿到的展示信息,phone 则是在下单前由用户主动填写的联系号码,这个字段在实际业务里通常不做强校验,给正则校验就行。
CREATE TABLE `repair_order` ( `id` bigint NOT NULL AUTO_INCREMENT, `order_no` varchar(32) NOT NULL COMMENT '业务订单号', `user_id` bigint NOT NULL COMMENT '下单用户', `worker_id` bigint DEFAULT NULL COMMENT '接单师傅', `category_id` bigint DEFAULT NULL COMMENT '服务分类', `description` text COMMENT '故障描述', `address` varchar(255) NOT NULL COMMENT '上门地址', `latitude` decimal(10,7) DEFAULT NULL, `longitude` decimal(10,7) DEFAULT NULL, `appointment_time` datetime DEFAULT NULL COMMENT '预约时间', `status` tinyint DEFAULT 0 COMMENT '状态:0待接单', `amount` decimal(10,2) DEFAULT NULL COMMENT '维修费用', `accept_time` datetime DEFAULT NULL, `finish_time` datetime DEFAULT NULL, `remark` varchar(255) DEFAULT '', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_order_no` (`order_no`), KEY `idx_user_id` (`user_id`), KEY `idx_worker_id` (`worker_id`), KEY `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='维修订单表';订单表把地址直接冗余在表里,而不是通过地址 ID 去关联一张地址表,这是上门服务类业务的特点:订单里的地址是下单那一刻的快照,之后用户改住址不影响历史订单。经纬度用 decimal(10,7) 存,用于按距离推荐师傅。order_no 与自增主键 id 分开,业务上展示订单号,逻辑上用 id 做关联,这样既避免在 URL 里暴露真实数据量,也为将来分库分表留下空间。
3.3 按距离找师傅:经纬度查询是毕设里的加分项
上门维修区别于普通电商的一个点,是用户希望尽快找到附近的人。很多毕设只做到按分类筛选师傅,如果能在下单页根据用户定位,按距离排序推荐师傅,这就是一个亮点。MySQL 里算距离有两种常见方式:5.7 及以下用 Haversine 公式,8.0 可以用内置的 ST_Distance_Sphere 函数。考虑到 SQL 脚本的兼容性,用 Haversine 公式更稳妥。
SELECT w.id, w.real_name, w.skill, w.score, (6371 * acos( cos(radians(#{lat})) * cos(radians(w.latitude)) * cos(radians(w.longitude) - radians(#{lng})) + sin(radians(#{lat})) * sin(radians(w.latitude)) )) AS distance FROM worker w HAVING distance < 5 ORDER BY distance ASC LIMIT 10;这条 SQL 把用户当前经纬度 #{lat} 和 #{lng} 传进去,算出每位师傅与用户的球面距离,过滤出半径 5 公里内的师傅并按距离排序。6371 是地球半径,单位是公里;弧度转换是公式的必要步骤,数据库里存的经纬度是角度,三角函数的参数是弧度,不转算出来的距离会离谱。这个查询很适合放到 mapper XML 里,配合 MyBatis 的 @Param 注解传参。请注意表结构里如果师傅没有存经纬度,就需要在 worker 表增加 latitude 和 longitude 两列。
3.4 订单号生成:别用自增 ID 当业务单号
把自增 id 展示给用户看起来省事,但有两个问题:一是通过订单号能猜出平台单量,数据敏感;二是不同表各自有自增 id,如果以后把订单表拆分,单号会冲突。毕设里不用引入雪花算法那么重的方案,但至少要用时间戳加随机数的组合生成可读的订单号。
public String generateOrderNo() { SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss"); String time = sdf.format(new Date()); int random = (int) ((Math.random() * 9 + 1) * 1000); return time + random; }这段代码生成的是 18 位订单号:14 位时间戳加 4 位随机数,足够在单机毕业设计场景下避免重复。如果担心同一秒内并发两次下单撞随机数,可以把随机数换成 AtomicInteger 自增,或者利用 Redis INCR 生成每日自增序列。放在 service 层创建订单时调用,不要在 controller 里生成,这样业务逻辑内聚更好。
4. 后端接口与小程序页面:从下单到完工的完整请求链
4.1 登录:wx.login 换 openid,再换 token
小程序不像网页有用户名密码,它依赖微信的 wx.login 拿到临时 code,再把 code 发给后端,由后端调用微信接口换取 openid。openid 是用户在某个小程序里的唯一身份标识,同一个微信号在不同小程序里 openid 不同,所以它非常适合做主键关联用户表。拿到 openid 后查用户表,不存在就自动注册,存在就直接登录。
@PostMapping("/login") public Result login(@RequestBody WxLoginRequest req) { String url = "https://api.weixin.qq.com/sns/jscode2session" + "?appid=" + appid + "&secret=" + secret + "&js_code=" + req.getCode() + "&grant_type=authorization_code"; String result = restTemplate.getForObject(url, String.class); JSONObject json = JSONObject.parseObject(result); String openid = json.getString("openid"); User user = userMapper.selectOne( new LambdaQueryWrapper<User>().eq(User::getOpenid, openid)); if (user == null) { user = new User(); user.setOpenid(openid); user.setUserType(0); userMapper.insert(user); } String token = JwtUtil.createToken(user.getId(), user.getUserType()); return Result.success(token); }这段登录接口做了三件事:拿小程序传来的 code 去微信服务器换 openid,查用户表决定登录还是注册,最后签发 JWT token 给小程序端。后面所有需要身份的接口,都在请求头里带这个 token,由拦截器解析出用户 id 和角色。JWT 的好处是后端不用存 session,适合小程序这种多端场景。代码里的 restTemplate 是 Spring 自带的 HTTP 客户端,毕设里直接用它调微信接口就行,不用再引其他依赖。
小程序端对应的代码是 app.js 或者登录页里调用 wx.login,拿到 code 后请求后端地址。
wx.login({ success: (res) => { if (res.code) { request('/user/login', 'POST', { code: res.code }) .then(data => { wx.setStorageSync('token', data.token); wx.setStorageSync('userType', data.userType); wx.switchTab({ url: '/pages/index/index' }); }); } } });这段代码把后端返回的 token 和 userType 存到本地缓存,后续每个请求从缓存里取 token 放在 header 中。userType 也需要存一份,因为用户端和师傅端看到的页面入口完全不同。如果登录后跳转不正确,优先检查后端返回里有没有 userType 字段,很多毕设项目只返回 token 不返回角色,导致小程序端无法区分身份。
4.2 用户下单:一条 POST 把订单写进去
下单页收集的信息通常包括服务分类、故障描述、上门地址、联系方式和预约时间。后端下单接口要做的事是补全订单号、初始状态、下单用户 id,然后插入订单表。这里有一个容易忽略的点:地址信息应该直接保存用户填写的快照,而不是在详情页通过地址 id 再查一次。
@PostMapping("/order/create") public Result createOrder(@RequestBody RepairOrder order) { if (order.getAddress() == null || order.getAddress().trim().isEmpty()) { return Result.error("上门地址不能为空"); } order.setOrderNo(generateOrderNo()); order.setStatus(0); order.setUserId(currentUserId()); orderMapper.insert(order); return Result.success(order.getOrderNo()); }下单接口的校验不能只靠前端,前端可以绕过,所以后端至少做两个校验:地址非空、服务分类合法。status 强制设为 0 而不是信任前端传值,是为了防止别人通过抓包伪造一个已接单的订单。订单号在 service 里生成,controller 层只负责参数接收。这里没有做金额校验,因为维修费用通常由师傅维修完成后报价,而不是下单时确定,这也符合上门维修的真实流程。
4.3 师傅接单:用 UPDATE 加状态条件防并发抢单
抢单是上门维修系统里最有技术含量的点之一。同一个订单,如果两个师傅同时点击接单,后端的查询接口在瞬间都查到 status=0,如果都直接执行 UPDATE 设置 status=1,就会出现两个师傅同时接同一单。解决办法不是加锁,而是在 UPDATE 语句里带上状态条件,让数据库自己保证只有一个更新成功。
UPDATE repair_order SET status = 1, worker_id = #{workerId}, accept_time = NOW() WHERE id = #{orderId} AND status = 0这行 SQL 是防并发接单的关键:只有当前订单状态还是 0 时,接单更新才会生效。MyBatis 的 update 方法返回影响行数,如果返回 1 说明抢单成功,返回 0 说明订单已经被别人接走,直接返回“手慢了,订单已被接”。这种写法叫作乐观锁,不用数据库行锁,性能好,而且逻辑简单。很多同学在这个地方用 select 先查再 update,中间没有事务隔离,并发下必翻车。
4.4 订单列表:小程序页面列表加载更多的标准配合
订单列表是用户端和师傅端最常见的页面。小程序的列表交互有两种:下拉刷新和触底加载更多。下拉刷新用 onPullDownRefresh,触底加载用 onReachBottom。后端接口必须支持分页参数,否则本地开发时数据少看不出问题,数据一多页面就会卡。
@GetMapping("/order/list") public Result list(@RequestParam Integer page, @RequestParam Integer size, @RequestParam(required = false) Integer status) { PageHelper.startPage(page, size); List<RepairOrder> orders = orderMapper.selectByStatus(status); return Result.success(orders); }PageHelper 是 MyBatis 的分页插件,调用 startPage 之后,接下来第一条查询会自动拼接 LIMIT。这里的 page 是页码从 1 开始,size 是每页条数。接口返回里应该有 total 字段让前端判断是否还有下一页。小程序端的 onReachBottom 则负责在页面触底时累加页码并重新请求。
onReachBottom() { const list = this.data.list; if (list.length >= this.data.total) { return; } const page = this.data.page + 1; this.setData({ page: page, loading: true }); this.loadOrders(); }这段代码先判断当前已加载条数是否已经等于总数,如果相等就停止加载,避免做无用请求。loadOrders 里按 this.data.page 请求列表,拿到数据后用 concat 追加到已有数组,而不是整体覆盖。页面底部通常会显示“加载中”或“已经到底了”,这个小细节在演示时很加分,因为老师很可能直接上手滑动列表。
4.5 进度反馈与完工:状态机走到哪一步,接口就开放哪些动作
第 3 章的状态机设计在这里真正发挥作用。当订单处于“维修中”时,师傅端提交完工接口,后端先校验当前状态是 2,再更新为确认费用状态,同时写入完工时间。如果用户此时想取消订单,后端校验发现 status 已经是 2,就拒绝取消并提示“维修已开始,请联系师傅协商”。状态校验统一放到 service 层,做一个独立方法,避免每个接口重复写 if 判断。
public boolean checkStatus(Long orderId, int expectStatus) { RepairOrder order = orderMapper.selectById(orderId); return order != null && order.getStatus() == expectStatus; }调用方写起来就是:接单前 checkStatus(orderId, 0),开工前 checkStatus(1),完工前 checkStatus(2)。这个简单封装让状态流转逻辑集中在一起,老师问起状态机设计时,你可以直接把这张状态迁移表拿出来讲。如果源码包里的逻辑不是这样组织的,建议你按这个思路重构一遍,代码量不大,但对答辩很有帮助。
5. 跑通系统时最常见的 5 个坑:环境、端口、字段和中文乱码排查
5.1 MySQL 8 连接报 Public Key Retrieval is not allowed
现象:后端启动时控制台报错,提示 Public Key Retrieval is not allowed,数据库无法连接。这个错误只出现在 MySQL 8.0,原因是默认认证插件 caching_sha2_password 在非 SSL 连接下需要先获取公钥,而 JDBC 驱动默认不允许自动获取公钥。
原因:项目里 JDBC URL 缺少 allowPublicKeyRetrieval 参数,或者用的是旧的 mysql-connector-java 5.x 驱动连接 MySQL 8。解决方式是两条路:要么在连接串里加上 allowPublicKeyRetrieval=true,要么把 MySQL 用户的认证方式改回 mysql_native_password。毕设里改连接串最快。
spring: datasource: url: jdbc:mysql://localhost:3306/repair?useSSL=false&allowPublicKeyRetrieval=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456这段连接串里同时处理了三个隐患:useSSL=false 避免本地开发时的 SSL 警告;allowPublicKeyRetrieval=true 解决 8.0 认证问题;characterEncoding=utf8 保证中文写入;serverTimezone=Asia/Shanghai 避免时区错误导致时间差 8 小时。如果是 MySQL 5.7,最后两个参数仍然有用,第一个参数不影响。
5.2 真机调试请求失败:域名校验和局域网 IP 的坑
现象:在微信开发者工具里接口调通,预览到手机后,所有请求全部 fail,页面白屏。这是小程序开发最经典的一个坑,原因是开发者工具有一个默认设置“不校验合法域名”,工具本地调试时自动绕过域名校验,但真机上微信小程序运行时强制要求所有请求域名是 HTTPS 且在后台配置过。
原因:后端跑在 http://localhost:8080,这个地址手机根本访问不了,而且 http 接口在真机上默认被拦截。解决方式分两层:开发调试阶段,在后端启动的电脑上查局域网 IP(ipconfig 或 ifconfig),把小程序请求的 BASE_URL 改成 http://192.168.x.x:8080;在微信开发者工具里勾选“不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书”。注意这个勾选只是开发阶段的后门,演示时如果必须用真机,确保手机和电脑连同一个 WiFi,并且电脑防火墙放行 8080 端口。
5.3 中文写库变成问号:连接串和表字符集
现象:下单成功后,数据库里故障描述和地址全部是问号,英文数字正常。这种问题几乎都出在字符集不一致,而不是代码逻辑。常见情况是 MySQL 表默认字符集是 latin1,或者 JDBC 连接串没指定 utf8。
原因:MySQL 连接时客户端、连接层、表结构三层字符集不一致。解决方式是一层层对齐:建表语句里显式 CHARSET=utf8mb4;连接串加 characterEncoding=utf8;还可以在 MySQL 命令行执行 SET NAMES utf8mb4 让当前会话生效。注意 utf8mb4 和 utf8 的区别:utf8mb4 是 MySQL 8 和 5.7 都推荐的完整 UTF-8 实现,支持 emoji 和生僻字,utf8 在 MySQL 里是 utf8mb3 的别名,遇到特殊字符可能报错。检查时用 SHOW CREATE TABLE repair_order,直接看表定义里的 DEFAULT CHARSET 是什么。
5.4 后端端口被占用与 SQL 脚本执行一半
现象:后端启动时报端口占用,或者执行 SQL 脚本时报错中断,导致部分表存在部分表缺失。端口问题非常好排查,启动日志里写着 Port 8080 was already in use,说明有别的进程占着 8080。
原因:电脑上其他 Java 项目或服务占用端口,也可能是上次启动的后端没停干净。解决方式一是在 application.yml 里把 server.port 改成 8081,同时记得改小程序端的 BASE_URL;方式二是找到占用进程然后终止,Windows 下用 netstat -ano | findstr 8080 查 PID,再用 taskkill /PID 进程号 /F。SQL 脚本执行一半通常是脚本里有重复语句,或者某个表的字符集兼容问题,Navicat 会停在出错的语句上,把那条语句单独拿出来分析。如果是 utf8mb4_0900_ai_ci 报错,说明脚本是 MySQL 8 生成的,要在 5.7 里把排序规则改成 utf8mb4_general_ci。
5.5 登录接口通了,却分不清用户和师傅
现象:小程序登录成功,跳转页面却不对,用户端能看到师傅的操作按钮,或者师傅端看不到待接单列表。原因大多是后端签发的 token 里没有包含角色信息,前端拿到 token 但不知道当前登录者是用户还是师傅。
解决方式是在登录接口返回里显式带上 userType,前端存储后判断跳转;更严格的做法是后端写一个拦截器,解析 token 后根据 userType 拦截师端接口。毕设项目里常见的是前者,只做前端路由跳转判断,虽然安全性一般,但演示效果足够。如果你想加分,可以在后端加一个注解 @RequireRole(1) 来标记师傅端接口,拦截器里校验 userType,这是比较完整的权限设计方案。
6. 演示前必做的验证动作:让答辩演示不翻车的检查清单
6.1 最小演示路径:一张单从下单到评价跑一遍
演示不要从头开始讲代码,而是按业务顺序走一条最短路径:用户登录小程序,发布一个维修订单,切到师傅端登录,抢单,更新为维修中,提交完工费用,再切回用户端支付并评价。这条路径走完,系统最重要的功能点全部展示到了。演示前至少完整跑三遍,第一遍按正常节奏,第二遍故意快速操作看状态是否错乱,第三遍清掉数据后重新初始化,确保不是依赖上一次的残留数据才能跑通。
6.2 预置数据与快照:给演示留后悔药
现场演示最怕的是数据库被误删或状态改乱。提前用 mysqldump 导出一份包含预置账号、服务分类、几条待接单订单的快照脚本,比现场手动重来可靠得多。
mysqldump -u root -p repair > demo_backup.sql这段命令导出的备份里包含全部表结构加已插入的数据。提前把这份 SQL 放进项目目录,万一演示中途状态改乱,直接执行 source demo_backup.sql 就能恢复到初始状态。我的习惯是准备两个管理员账号和两个测试用户,并记录在小纸条上,避免现场临时翻手机找密码。
6.3 现场兜底:接口报错时如何快速恢复到可用状态
答辩现场网络环境不可控,最稳妥的演示方式是提前打开好后端、数据库和开发者工具,不要在评委面前重新启动整个链路。如果真机联调失败,立刻切到开发者工具演示,工具里不受真机网络限制。如果接口报错但页面还在,就用浏览器或 Postman 直接打接口,把返回 JSON 展示给老师看,至少证明后端逻辑没问题。开发阶段我吃过一次亏,演示前一晚为了调样式把小程序 appid 换成自己测试号,第二天登录全部失效,从那以后我在演示前一天绝对不动环境配置,只改数据内容。这些细节看着不起眼,却决定了你半年的代码在十分钟里是被认可还是被质疑。希望这篇笔记能帮你把这个毕设题目变成真正属于自己的作品。
本文还有配套的精品资源,点击获取