简介:面向计算机专业毕业设计/课程设计场景的微信小程序上门维修系统源码,采用Java+小程序+MySQL架构,涵盖用户、维修员、管理员三个角色。用户端可查看首页、广告、新闻资讯,并管理维修信息、维修记录、评价与收藏;维修员端可处理维修信息、维护记录与评价;管理员后台则提供用户管理、维修员管理、维修信息管理、评价管理、广告管理和系统管理等完整功能。压缩包共1249个文件,约26.02MB,以Java源码、Vue页面、JS脚本、WXML/WXSS小程序页面、PNG图片和SQL数据库文件为主,前后端代码与数据库脚本齐全,目录清晰,便于定位与二次开发。目前已有87人学习,适合需要快速搭建同类课题或参考完整项目结构的同学使用。
1. 上门维修系统毕业设计源码包:java+小程序+mysql+LW 组合到底在解决什么问题
每年毕设季,标题「上门维修系统源码(java+小程序+mysql+LW).zip」的压缩包在班群里被转来转去,下载的人多,真正能跑起来并讲明白的人连一半都不到。不是代码难,而是很多人拿到压缩包之后不知道先改哪里、后启动什么;也有人拿了源码直接交,老师一问到底层就卡壳。这套源码对应的是一条「用户小程序下单 + Java 后端派单 + MySQL 存订单」的完整业务闭环,LW 目录一般就是答辩要用的论文或文档。它适合两类人:软件工程、计算机相关专业的应届生,需要一套能现场演示、能二次开发的毕业设计;以及刚学过 java 基础和 mysql 入门,想找个微信小程序项目实例练手的初学者。我的建议是别把它只当交差工具,而是当成一个带需求、带页面、带状态机的全栈训练场,后面你会省下大量返工时间。
2. 先盘清架构再动手:三层结构怎么分工,本地从零跑通需要几步
2.1 为什么是 Spring Boot + 原生小程序 + MySQL,而不是 uniapp 或前端分离
先回答一个高频疑问:标题里写了「小程序」,为什么不用 uniapp 一套代码跑三端?因为这个项目大概率是原生的微信小程序工程,目录里应该是 app.json、pages、components 这种结构,而不是 uniapp 的项目配置。原生和 uniapp 在毕业设计里最大的差别是:原生小程序加载更快,微信开发者工具里的报错更直接;uniapp 的优势在多端复用,但你还得额外维护一套编译链,对毕设来说性价比不高。如果手里源码用的是原生,就干脆死磕原生,改起来反而省心。
后端选 java 几乎是这类课程的默认选择:不是它最优雅,而是资料最多、面试最好解释。常见组合是 Spring Boot + MyBatis-Plus + MySQL。MyBatis-Plus 的好处是单表增删改查基本不用手写 SQL,可以把注意力放在订单状态机这样的核心逻辑上。「java 面试题」里常考的依赖注入、事务、数据一致性,在这个项目里都能找到对应落点。别的不说,光看 mysql 安装配置教程这个关键词每年有多少人搜,就知道环境问题才是第一道坎。
2.2 本地跑通最小环境清单:JDK、Maven、MySQL、开发者工具
我一般先把环境列成一张表,缺哪个补哪个,不要一上来就解压源码。
| 组件 | 推荐版本 | 用途 |
|---|---|---|
| JDK | 8 或 11 | 运行 Spring Boot 后端 |
| Maven | 3.6+ | 拉依赖、打包 |
| IDEA / Eclipse | 近两年版本即可 | 打开 java 工程 |
| MySQL | 5.7 或 8.0 | 建库导入 SQL |
| Navicat for MySQL | 任意 | 可视化导入、查表 |
| 微信开发者工具 | 稳定版 | 运行小程序端 |
关于版本有个血泪经验:MySQL 8.0 默认认证方式和 5.7 不一样,连接串少写两个参数就会报 SSL 相关错误;JDK 太新(比如 17)和某些老的 Spring Boot 2 组合也会出现反射警告,但一般能跑。如果导师没有硬性要求,我建议用 JDK 8 + MySQL 8.0,这是当前资料最齐、踩坑记录也最全的组合。
2.3 导入源码的四步操作:建库、改配置、起后端、开小程序
把压缩包解开,常见结构是一个后端目录放 java 工程,一个小程序目录放前端,外加 LW 文档目录。第一次启动别急着翻源码,按下面四步走。
第一步,建库并导入 SQL。用 Navicat 新建一个数据库,名字随意,比如repair,然后右键运行 SQL 文件。导入时注意字符集选utf8mb4,不然之后插入中文会变成问号。命令行也可以:
mysql -uroot -p CREATE DATABASE IF NOT EXISTS repair DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin; USE repair; SOURCE /path/repair.sql;COLLATE utf8mb4_bin按需使用,不关心大小写排序的话默认的utf8mb4_general_ci也够用。SOURCE后面要写 SQL 文件的实际绝对路径,导入完成后在 Navicat 里刷新,看到 t_user、t_order 这类表就算成功。
提示:Maven 首次拉依赖可能很慢,在
settings.xml里配阿里云镜像能省下大量等待时间。
第二步,改后端数据源配置。用 IDEA 打开后端目录,等 Maven 把依赖拉完,先不急着点启动,打开src/main/resources/application.yml:
server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/repair?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true username: root password: "123456" driver-class-name: com.mysql.cj.jdbc.Driver这里重点说三个参数:useSSL=false是本地开发开关,MySQL 8 默认开 SSL,不加它可能出现自签名证书报错;serverTimezone=Asia/Shanghai解决日期相差 8 小时;allowPublicKeyRetrieval=true为了配合caching_sha2_password认证插件,不加容易报Public Key Retrieval is not allowed。端口 8080 被占就改成 8081,但后面小程序端 BASE_URL 也要同步改。
第三步,启动后端。在 IDEA 里直接点运行类,看到启动日志里有 Tomcat started on port 8080 就算起来。也可以用命令行:
mvn spring-boot:run第四步,打开微信开发者工具,导入小程序目录。AppID 选「测试号」就行,开发阶段不需要注册。关键是在「详情-本地设置」里勾选「不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书」,否则后面 wx.request 会直接报域名校验失败。
四步走完,正常情况下能看到小程序首页打开,后端日志出现请求记录。如果卡在某一环,先别急着怀疑源码,按第 5 章的排查表一条条对。很多「跑不起来」其实都是环境没配对。
3. Java 后端核心模块:维修订单状态机、接口设计与数据一致性
3.1 t_user 与 t_order 的表结构设计:字段默认值怎么设才不挨答辩骂
答辩老师最爱问的一句就是「你这个表为什么这样设计」。源码包里的表结构不一定最优,但你要在跑通之后能说出理由。以用户表为例:
CREATE TABLE t_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID', openid VARCHAR(64) NOT NULL COMMENT '微信openid,唯一标识', nickname VARCHAR(32) DEFAULT '' COMMENT '昵称', avatar VARCHAR(255) DEFAULT '' COMMENT '头像URL', role TINYINT NOT NULL DEFAULT 1 COMMENT '1-客户 2-维修工 3-管理员', phone VARCHAR(20) DEFAULT '' COMMENT '手机号', create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', UNIQUE KEY uk_openid (openid) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';openid 是用户在微信生态里的唯一标识,所以必填且加唯一索引;role 用 TINYINT 存数字而不是字符串,为了查询快、占空间小,这是回答「为什么不用枚举」的常见说法。create_time 交给数据库默认值,保证后端代码漏填时也不至于落空值。
订单表是核心,字段多,状态尤其关键:
CREATE TABLE t_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL COMMENT '订单号,展示用', user_id BIGINT NOT NULL COMMENT '下单用户ID', worker_id BIGINT DEFAULT NULL COMMENT '接单维修工ID', category VARCHAR(32) NOT NULL COMMENT '故障类型:水管/电路/家电等', address VARCHAR(255) NOT NULL COMMENT '上门地址', description TEXT COMMENT '故障描述', status TINYINT NOT NULL DEFAULT 1 COMMENT '1待接单 2已接单 3维修中 4待支付 5已完成 6已取消 7退款中', appoint_time DATETIME DEFAULT NULL COMMENT '预约上门时间', price DECIMAL(10,2) DEFAULT NULL COMMENT '维修报价', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_status (status), KEY idx_user (user_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='维修订单表';注意status默认值设置成 1,对应「待接单」。这是毕设项目里非常典型的默认值,也对应一道经典 mysql 面试题:状态字段用数字类型存储,默认值必须和状态枚举的初始态对齐,否则新订单落库后查不到。另一个高频追问是worker_id为什么允许 NULL:因为订单创建时还没人接单,这个值本来就是空的,等接单接口再回填。
3.2 登录接口:小程序 wx.login 的 code 换 openid,再签发 token
小程序端没有 cookie,常见做法是用wx.login拿到一次性 code,发给 Java 后端,后端再拿 code 向微信服务器换 openid。注意方向:不是小程序直接拿 openid,而是后端去换,这样 appid 和 secret 才不会暴露在小程序代码里。接口大概长这样:
@PostMapping("/api/user/login") public Result login(@RequestBody LoginRequest request) { // 1. 用 code 调微信 jscode2session 接口,拿到 openid 和 session_key Map<String, Object> session = wxService.jscode2session(request.getCode()); // 2. 根据 openid 查用户表,查不到就说明是第一次使用,自动注册 User user = userMapper.selectOne(new LambdaQueryWrapper<User>() .eq(User::getOpenid, session.get("openid"))); if (user == null) { user = new User(); user.setOpenid((String) session.get("openid")); userMapper.insert(user); // 默认 role = 1,即普通客户 } // 3. 签发本地 token,后续接口都靠它识别身份 String token = JwtUtil.createToken(user.getId(), user.getRole()); return Result.ok(token); }三个细节值得记。第一,jscode2session必须放在后端,因为微信接口要求 appid+secret 一起传,泄露到前端等于裸奔。第二,selectOne查不到再 insert 在高并发下可能重复注册,但因为 user 表有openid唯一索引,第二次 insert 会抛DuplicateKeyException,catch 后再查一次即可,这是回答「java 怎么保证数据一致性」的一个实证。第三,token 里只放 userId 和 role,过期时间一般设 7 天。
3.3 下单与派单接口:状态机约束非法流转,防止同单双接
上门维修的订单生命周期是「待接单 → 已接单 → 维修中 → 待支付 → 已完成」,中间还可能被客户取消。最怕前后端各自为政:前端隐藏按钮、后端不校验,结果出现「已取消的订单还能被接单」这种答辩现场翻车。所以后端必须做状态机校验。接单接口核心逻辑如下:
@PostMapping("/api/order/accept") public Result accept(@RequestBody AcceptDTO dto, @RequestHeader("token") String token) { Long workerId = JwtUtil.parseToken(token).getUserId(); Order order = orderMapper.selectById(dto.getOrderId()); if (order == null) { return Result.error(404, "订单不存在"); } // 状态机约束:只有 status=1(待接单)时才允许转成 2(已接单) if (order.getStatus() != 1) { return Result.error(403, "订单当前状态不可接单"); } // 用 update 条件写死 status=1,防止两个维修工同时接同一单 int rows = orderMapper.updateStatus( dto.getOrderId(), workerId, 1, 2); if (rows == 0) { return Result.error(403, "手慢了,订单已被其他维修工接走"); } return Result.ok(); }这段代码的精髓是用一条 update 解决「并发双接」:SQL 里WHERE id=? AND status=1,只有恰好读到 status=1 的请求能成功,另一个 update 影响行数为 0,直接提示失败。这比先 select 再 update 稳得多,也是我反复强调的「状态机放后端而不是前端」。实际源码可能没这么严谨,但你答辩时能背着这一段讲出来,比照本宣科强很多。
另外,如果订单有支付环节,支付回调接口必须设计成幂等:同一订单的支付结果可能被微信服务端推送多次,后端要先按order_no查状态,只有待支付才更新为已完成,否则会把已完成订单覆盖成别的状态。多数毕设只接模拟支付,但这个思路一定要有。
4. 小程序端从登录到下单:wx.request 联调、顶部导航栏与缓存时间
4.1 小程序页面与 tabBar 配置:符合微信小程序项目实例的常见写法
小程序端打开后先看app.json,它是整个小程序的地基。常见配置长这样:
{ "pages": [ "pages/index/index", "pages/order/create", "pages/order/list", "pages/order/detail", "pages/mine/mine" ], "window": { "navigationBarTitleText": "上门维修", "navigationBarBackgroundColor": "#2f54eb", "navigationBarTextStyle": "white" }, "tabBar": { "list": [ { "pagePath": "pages/index/index", "text": "首页" }, { "pagePath": "pages/order/list", "text": "工单" }, { "pagePath": "pages/mine/mine", "text": "我的" } ] } }提示:tabBar 页面路径不能带参数,需要跳转的详情页要作为普通页面用 wx.navigateTo 打开。
tabBar里的路径必须在pages数组里存在,tabBar 页面不能带参数跳转。如果你想从首页跳到一个带orderId的详情页,详情页不能放进 tabBar,只能用wx.navigateTo打开。pages 第一项就是启动页,一般放首页。
关于「微信小程序顶部导航栏高度」,自定义导航栏时最容易翻车。默认 iOS 导航栏 44px,Android 是 48px,但胶囊按钮位置在不同机型上不一样。沿用默认 window 配置就不用管高度;如果源码用了自定义导航组件,一定要用wx.getMenuButtonBoundingClientRect()动态计算胶囊位置,再定 header 高度。写死 44px 在安卓上会明显偏上,这种玄学 bug 改了半天其实就一行代码的事。
4.2 登录态与缓存时间:带时间戳的 wx.setStorage 实现
小程序登录态不能只存一个 token,因为 token 过期后用户还在页面上,一请求就失败。常见做法是把 token 和过期时间一起存,请求前先判断:
const BASE_URL = 'http://localhost:8080' function authRequest(path, method, data) { const cache = wx.getStorageSync('loginCache') // 如果缓存存在但已过期,先清理本地登录态 if (cache && cache.expireAt && cache.expireAt < Date.now()) { wx.removeStorageSync('loginCache') wx.navigateTo({ url: '/pages/mine/mine' }) return } wx.request({ url: BASE_URL + path, method: method || 'POST', data: data || {}, header: { token: cache ? cache.token : '' }, success: (res) => { if (res.statusCode === 401) { // token 失效,重新走 wx.login login() } } }) }把过期时间一起存,就是「微信小程序设置缓存时间」的经典解法:不依赖 wx.setStorage 的默认永久生效,而是自己控制有效期。每次读取先看expireAt,过了就清理,避免用户卡在半登录状态。实际源码里可能只是简单wx.setStorageSync('token', ...),但你可以改成这种结构,答辩时多一个可讲的点。
登录入口一般放在onLaunch或自定义login()函数里:
function login() { wx.login({ success: (res) => { wx.request({ url: BASE_URL + '/api/user/login', method: 'POST', data: { code: res.code }, success: (r) => { wx.setStorageSync('loginCache', { token: r.data.data.token, expireAt: Date.now() + 7 * 24 * 3600 * 1000 }) } }) } }) }注意wx.login的 code 是一次性的,后端换完 openid 后立刻失效,所以不要把这个 code 存下来重复用,这是第 5 章会提到的坑。调试时,小程序端 Network 面板能看到每个 wx.request 的完整请求和响应,后端控制台也有日志,两头对一下时间戳,基本能定位大部分联调问题。
4.3 故障上报与图片上传:wx.chooseMedia 联调 Java 接口
上门维修系统里用户最少要传一段故障描述,好一点的产品还会让用户拍照。小程序端上传图片现在推荐wx.chooseMedia,而不是老接口wx.chooseImage:
wx.chooseMedia({ count: 3, mediaType: ['image'], sizeType: ['compressed'], success: (res) => { const tasks = res.tempFiles.map((item) => { return new Promise((resolve) => { wx.uploadFile({ url: BASE_URL + '/api/file/upload', filePath: item.tempFilePath, name: 'file', success: (r) => resolve(JSON.parse(r.data)) }) }) }) Promise.all(tasks).then((files) => { // 拿到后端返回的文件url后,和表单一起提交 this.setData({ imageUrls: files.map(f => f.data.url) }) }) } })count: 3限制一次最多选 3 张,sizeType: ['compressed']让微信压缩后再上传,省流量也省后端存储。Promise.all等三张图都传完再提交表单,避免图片地址还没写完就点提交。后端对应的@PostMapping("/api/file/upload")接口一般接收MultipartFile,保存到本地目录后返回可访问的 URL。这里提醒一句:生产环境小程序要求所有请求域名都是 HTTPS,不然 iOS 上经常出现「已连接但无法验证服务器身份」的报错,本地调试则靠「不校验合法域名」绕过。
5. 避坑清单:导入、联调、部署最常见的 5 个翻车现场
这一章专门写踩坑记录,每条都按「现象 → 原因 → 解决」展开,遇到问题可以直接按图索骥。
5.1 MySQL 连接翻车:error 2002 socket 与 SSL 连接错误
现象:Maven 工程启动时控制台报error 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/mysql.sock',或者连库时抛Connection refused,又或者 Navicat 能连但后端连不上。
原因:第一种是 MySQL 服务根本没起来,Mac 上通过 Homebrew 安装的 MySQL 默认 socket 在/tmp/mysql.sock,服务未启动时这个文件不存在;第二种是 MySQL 8.0 默认开启 SSL 和caching_sha2_password,连接串缺少useSSL=false和allowPublicKeyRetrieval=true时,后端会报 SSL 连接错误。
解决:先确认服务状态,再改连接串。命令行执行mysql.server start(Mac)或检查 Windows 服务里的 MySQL 是否运行。连接串统一加这段:
?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true如果还不行,用 Navicat 连一次确认账号密码没问题,再回来检查后端配置。这类问题八成是复制配置时少了某个参数,不是源码 bug。
5.2 小程序 request 域名校验:本地能通、上线必挂
现象:微信开发者工具里wx.request报url not in domain list,或者真机预览时请求一直失败,局域网 IP 地址同样连不上。
原因:小程序不像浏览器可以随意跨域,所有请求域名必须在小程序后台配置,且必须是 HTTPS。本地开发时项目默认启用域名校验,任何http://localhost或http://192.168.x.x都会被打回。
注意:真机预览时 BASE_URL 必须改成电脑的局域网 IP,不能写成 localhost。
解决:开发阶段在「详情-本地设置」勾选「不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书」。真机预览需要电脑和手机连同一局域网,BASE_URL 改成电脑局域网 IP。上线前必须买域名、配 HTTPS 证书,并在小程序管理后台「开发管理-服务器域名」里添加 request 合法域名。这个坑每届毕设都会踩,几乎成了过来人标配忠告。
5.3 wx.login 的 code 被重复使用,接口回 40029 invalid code
现象:小程序端第一次登录成功,第二次进入时后端返回40029 invalid code,或者页面里偶尔登录失败。
原因:wx.login返回的 code 只能使用一次,后端调用jscode2session之后立即作废。很多同学把 code 存到wx.setStorageSync里,下次进入页面直接拿旧 code 去换 token,自然报无效。
解决:不要缓存 code,每次需要登录或续期时重新调用wx.login()。如果发现同一页面里多次请求,确保只调一次wx.login,code 用完即弃。后端也要排查是否在调试时手动重复消费了同一次 code。
5.4 MyBatis-Plus 驼峰映射失效,接口返回全 null
现象:后端接口调用成功,但 JSON 里的createTime、userId这类驼峰字段全是 null,数据库里明明有值。
原因:表字段是下划线风格(create_time、user_id),Java 实体用的是驼峰(createTime、userId),MyBatis-Plus 的驼峰映射开关没生效,或实体类上少了@TableField注解。
解决:确认application.yml里有下面这一行,它在 MyBatis-Plus 控制下划线转驼峰:
mybatis-plus: configuration: map-underscore-to-camel-case: true如果开了还不行,去实体类字段上加@TableField("create_time")强制映射。排查顺序很重要:先看全局配置,再看注解,最后才怀疑数据库字段写错。
5.5 端口被占用与多实例冲突,后端静默起不来
现象:IDEA 启动后没有报错,但访问接口迟迟连不上;或者mvn spring-boot:run启动到一半卡住,日志里有Port already in use。
原因:8080 端口被上一个残留进程占住,Spring Boot 默认端口不可用就报异常退出。还有同学一台电脑开了多个后端实例,导致数据库连接池耗尽,表现为请求超时而不是拒绝连接。
解决:先用命令查端口占用:
lsof -i :8080 kill -9 <PID>Windows 上用netstat -ano | findstr 8080。想换端口就把application.yml里的server.port改成 8081,再同步改小程序端 BASE_URL。建议先在后端启动日志里确认实际监听的端口,再决定要不要排查,避免对着 8080 找半天的无效劳动。
排查时可以按这个顺序:后端起没起 → 数据库连没连 → 小程序本地设置 → 接口入参 → 日志。不要在第一步还没确认时就盯着小程序端代码反复看,那是毕业设计季最常见的无效劳动。
6. 答辩前加一个「维修评价」闭环,顺手做接口自检
如果时间有余,我建议在这个毕业设计上加一个「评价」功能:订单完成后,客户对维修工评分和留言。一个功能同时补齐业务闭环和数据库设计两个答辩加分点,而且改动不大。
先建评价表:
CREATE TABLE t_review ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_id BIGINT NOT NULL COMMENT '订单ID,一个订单只能评一次', user_id BIGINT NOT NULL COMMENT '评分人', worker_id BIGINT NOT NULL COMMENT '被评维修工', rating TINYINT NOT NULL DEFAULT 5 COMMENT '1-5星', content VARCHAR(500) DEFAULT '' COMMENT '评价内容', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_order (order_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='维修评价表';uk_order唯一索引是业务约束:一单只能评一次。后端新增接口时,必须先在 service 里校验订单状态必须是「已完成」,再校验操作人就是订单下单人,最后用order_id执行 insert,碰到DuplicateKeyException就提示已评价。这一步和 3.3 节是同一件事:把约束写在数据库和业务层,而不是只靠前端按钮。
写完后做一轮自检,我习惯用 curl 打三个路径:登录、创建工单、评价。
curl -X POST http://localhost:8080/api/user/login \ -H "Content-Type: application/json" \ -d '{"code":"test_code"}' curl -X POST http://localhost:8080/api/review/add \ -H "token: <上面返回的token>" \ -H "Content-Type: application/json" \ -d '{"orderId":3,"rating":5,"content":"上门快,修得好"}'如果登录接口依赖微信 code,本地测试时可以临时在接口里放一个测试分支,或者直接用数据库里现成订单的 userId 构造一个 token,目标只是验证状态机和权限判断是否生效。再加一步慢 SQL 检查:在 MySQL 里执行EXPLAIN SELECT * FROM t_order WHERE status = 2,能走到idx_status索引,就可以顺手写进论文的性能分析小节。
我自己的习惯是拿到任何毕设源码,第一件事保留一份原始 SQL 备份,改一张表就重新导出一遍。这个习惯救过我很多次,至少比在答辩前花一整晚恢复数据库心态要稳。这个方向投入不多,看源码、改状态机、加评价功能,几晚就能做完,但它会让你的项目从「能运行」变成「能讲清楚、经得起追问」,希望帮到你。
本文还有配套的精品资源,点击获取