
不知道你有没有答过这类题电脑蓝屏了维修师傅问你故障描述你只会说“就是开不了机啊”。而对面报修平台还要你填品牌、型号、购买日期、是否在保……用户烦、客服也烦。去年我做阳光电脑公司的维修服务小程序时核心目标就是把“报修”这件事做成微信里点几下就能发起、工程师手机端抢单、后台全程跟踪的一套闭环。项目用的是微信小程序 SSMSpring、SpringMVC、MyBatis前后端分离又不完全分离——小程序端负责用户和工程师操作管理后台用传统网页SSM负责所有接口和业务逻辑。这篇文章就把整个项目从需求拆解、技术选型、数据库设计到接口实现和踩坑记录完整讲一遍给正在做同类毕设或者接私活的朋友做一个参考。很多同学一听到“SSM”就觉得是老古董但真做下来你会发现SSM 的配置虽然是硬骨头恰恰是这部分最锻炼人。小程序那边同样有不少坑手机号获取新规、订阅消息授权、request 合法域名、自定义导航栏高度……我把这些坑都填平了接下来按实际开发顺序给你捋一遍。1. 项目定位与需求拆解1.1 电脑维修服务场景的三个角色阳光电脑公司本质上是一个本地化的 3C 维修服务商业务包括台式机故障检修、笔记本清灰、系统重装、数据恢复、屏幕更换这类常见项目。这类公司做维修服务小程序和纯电商、纯内容类小程序的差异非常大——它是“服务预约支付”的混合形态。先说角色。系统里有三类人用户普通客户、维修工程师接单员、管理员公司运营人员。用户在小程序端提交故障描述、选服务类型、预约上门时间工程师在小程序端查看工单池、抢单、更新维修进度、填写维修结果管理员在公司后台维护服务项目、管理工程师、审核订单、看营收统计。这个三角色模型听起来普通但真正的业务难点在“工单状态流转”。维修订单不是电商订单那样“下单→付款→发货→收货”一气呵成它中间有冗长的人工环节待接单、待上门、维修中、待支付、已完成、已取消、退款中。状态一多接口设计就容易失控。1.2 功能需求清单拆解按模块细分项目主要包含以下功能点用户端微信登录、获取手机号、服务分类浏览、提交报修单、查看订单进度、在线支付/模拟支付、评价、取消订单、个人中心。工程师端抢单大厅、我的工单、工单状态流转接单、开始维修、完成、查看订单详情、修改个人信息。管理后台服务项目管理、工程师账号管理、订单管理、统计报表、公告管理。这些功能里最容易做糊的是“订单进度”和“工程师抢单”。很多同学把订单表当成一个静态表只在第一次写入时记录数据后续所有变更都靠 update 硬改最后页面呈现就变得一塌糊涂。我建议从一开始就引入“状态机思维”后面接口设计部分会细讲。2. 技术选型为什么还是微信小程序 SSM2.1 小程序端选型的现实理由前端选微信小程序而不是 H5、App原因很直接客群在线上的时间已经锁定在微信里维修这种低频刚需服务让人下载一个 App 基本不可能。小程序扫码即用、用完即走还自带微信支付和订阅消息能力省去了一堆账号体系的建设成本。小程序端技术栈就是原生微信小程序加 WeUI 风格组件没有引入 Vant Weapp 这类第三方库。为什么不用一个是项目工期紧原生组件在真机上的稳定性足够另一个是毕设或课程设计场景下原生小程序的代码结构更容易向面试官讲清楚页面、组件、自定义事件、数据绑定这些概念都一目了然。后面如果要用 uni-app 搞多端再迁移也不晚。2.2 SSM 是不是已经过时了我的看法后端选 SSM确实有“教学惯性”的因素。Spring SpringMVC MyBatis 这套组合在 2015 年前后基本是国内 Java 后端事实标准但现在新项目大多是 Spring Boot。那为什么还要用第一SSM 的配置方式是理解 Spring IoC 和 AOP 的最佳教材。你手动维护 applicationContext.xml、spring-mvc.xml手动配数据源、事务管理器、Mapper 扫描路径整个过程会逼你把“容器”这个概念彻底搞明白。Spring Boot 的自动配置把这些都藏起来了你反而容易变成一个只会写注解的工具人。第二很多高校的毕设题目还停留在 SSM 阶段这类项目文档、代码查重、可解释性都做得更成熟。你已经会 SSM 了再转 Spring Boot 只需要半个月的适应期反过来先学 Boot 再回去啃 SSM 反而更痛苦。第三实际开发中我仍然用 SSM 打底只不过做了一点“现代化”改造Mapper 层用 MyBatis 注解 XML 结合Controller 层统一返回 JSON数据库连接池换成 Druid。这套组合在本地 Tomcat 上跑得非常稳内存占用小启动快适合这种中小型服务系统。2.3 微信登录与手机号获取的新规则这个必须单独说因为 2023 年到 2024 年微信侧更新特别频繁网上很多旧教程还在用老代码直接抄会翻车。现在标准流程是小程序端wx.login()拿 code后端拿 code 调code2Session接口换取 openid 和 session_key用户手机号必须通过“手机号快速验证组件”获取也就是button open-typegetPhoneNumber bindgetphonenumbergetPhoneNumber这种方式后端收到 code 后再调用接口换取真实手机号。这里有几个致命细节使用手机号快速验证组件要求小程序主体为企业、政府或其他组织个人开发者小程序是不能用的如果只是毕设演示官方推荐用测试号但测试号没有 real 手机号能力只能用固定测试手机号模拟。我项目里的处理方式是正常走完整流程同时写了一个“演示模式开关”后端配置常量控制返回模拟手机号这样就不至于在答辩现场被微信的资质校验卡住。3. 数据库设计与接口实现3.1 核心表结构设计这个项目的表不复杂但有几张表必须设计对否则后面业务逻辑全是补丁。我的主要表如下user用户表字段包括 openid、unionid、nickname、avatar、phone、status。engineer工程师表包含 name、phone、service_area、work_status空闲/忙碌、score、order_count。service_item服务项目表包含 item_name、price、price_unit、cover_url、description、sort。repair_order维修工单主表这是核心中的核心。order_status_log订单状态流转日志。comment评价表。admin后台管理员表。repair_order 表的字段我列一下重点order_no业务单号、user_id、engineer_id、service_item_id、fault_description、address、contact_name、contact_phone、expect_time、status、pay_status、pay_amount、cancel_reason、create_time、update_time、finish_time。order_status_log 很多人容易漏掉。有了这张表你可以随时追溯一个工单从“待接单”到“已完成”全部节点的时间线后台展示和故障排查都方便得多。强烈建议任何状态流转业务都配一张日志表别嫌麻烦。3.2 统一返回结构和接口设计SSM 项目接口设计最忌讳的就是 Controller 里到处MapString, Object乱塞数据。我这里定义了一个通用结果类Result字段就三个code、msg、data。约定 code200 为成功400 参数错误401 未登录500 系统异常。public class ResultT { private Integer code; private String msg; private T data; // 省略 getter/setter 和静态工厂方法 success()/error() }前端小程序里封装了一个request函数所有请求统一走它成功时判断 code 再取 data失败弹 toast。这样就把重复的错误处理收敛到了一处后续新增接口时 Controller 层代码量非常小。对于列表接口统一用 PageHelper 做分页。小程序端做“加载更多”时页码从 0 开始每次传 pageSize一般 10 条后端返回PageInfo包含 list、total、pageNum、pages。有一个小坑PageHelper 的分页参数是线程绑定的用完后一定要在执行 SQL 前设置而且不能用它包非查询语句否则分页会串台。3.3 工单状态机的设计工单状态我一开始是用硬编码Service 层写一堆if (order.getStatus() 1) { ... }后面发现根本维护不了。后来改成“状态机校验”。状态机的逻辑是每个状态只允许跳到特定的下一个状态其他跳转直接抛异常。比如待接单只能跳“已取消”或“已接单”维修中只能跳“待支付”待支付只能跳“已完成”或“退款中取消”。public enum OrderStatus { WAITING_ACCEPT(0, 待接单), ACCEPTED(1, 待上门), REPAIRING(2, 维修中), WAITING_PAY(3, 待支付), FINISHED(4, 已完成), CANCELLED(5, 已取消), REFUNDING(6, 退款中); }每次状态变更就是一个独立的方法比如acceptOrder(Long orderId, Long engineerId)、startRepair(...)、finishRepair(...)。方法内部第一步锁行第二步校验当前状态是否等于允许的前置状态第三步更新状态并写日志。这套设计让整个系统的状态流转可控也不容易出脏数据。4. 关键功能实现从用户报修到订单闭环4.1 用户端报修流程与页面数据绑定用户报修是整个流程的入口体验必须顺。报修页分了三个区块服务类型选择、故障描述和照片上传、联系方式和上门地址。服务类型从后端接口动态加载渲染成单选框列表避免因为新增服务项目改代码。故障照片上传用的是微信小程序的wx.chooseMedia拿到临时路径后先调wx.uploadFile传给后端后端用 MultipartFile 接收并写入服务器本地目录返回一个 URL 存到订单表里。这里要注意本地存储文件有个问题没有做域名绑定生产环境图片 URL 访问的是 IP 加端口小程序端image组件的 src 必须用后台配置的域名做合法域名绑定后才能正常显示。如果不处理就会出现“后台能看见图、小程序里图片裂开”的诡异现象。地址那块为了让用户少打字直接接了wx.chooseLocation选择位置反填经纬度和详细地址。这个 API 需要在小程序后台申请“地理位置”接口权限属于用户隐私接口提交代码审核时要在后台填清楚使用场景说明否则会被驳回。这是很多新手容易卡住的地方。4.2 工程师抢单与并发控制抢单是高频并发场景多工程师同时点“接单”时一个工单不能被两个人抢走。最简单可靠的方案是用条件更新配合影响行数判断UPDATE repair_order SET engineer_id #{engineerId}, status 1 WHERE id #{orderId} AND status 0 AND engineer_id IS NULL用 MyBatis 执行这条 SQL 后如果返回影响行数为 1表示抢单成功返回 0说明订单已被抢走或者状态已经变化直接提示“手慢了”。这个方案足够应对中小型公司的实际并发量不需要引入 Redis 分布式锁。如果你觉得还不够可以在 Service 层方法加Transactional配合SELECT ... FOR UPDATE锁行但要注意锁的粒度别把整个表锁了。抢单成功后要把工程师的work_status改成忙碌并推送一条订阅消息给用户告诉 TA 已经有人接单。4.3 微信支付与订阅消息的取舍微信支付这一块很多毕设项目都容易掉进坑里个人主体的小程序根本没有开通微信支付的资质。即使公司主体能开通签约、提现、证书配置的流程也会耗掉大把时间。我的处理是做一个可配置的“模拟支付”开关开关打开时用户点击“去支付”按钮直接调本地模拟接口后端把订单状态改成已支付、记录模拟支付流水号。答辩时可能被问到为什么不接真支付就说清楚域名证书、商户号资质限制以及模拟支付如何确保流程完整性这就够了。订阅消息则是真真切切用上了。维修服务与外卖、快递类似用户需要实时知道“工程师已接单”“工程师正在路上”所以我在关键节点嵌入了订阅消息推送。需要注意微信的规则目前订阅消息是一次性订阅也就是后端要拿到用户点击“允许订阅”后的授权记录才能向用户推送一次消息。我的做法是在用户提交报修成功后弹出订阅请求上一次授权对应一次推送绝对不能在用户没授权时硬推否则 API 会报 43101用户也可能直接投诉。5. SSM 配置与小程序对接中的常见坑5.1 合法域名与 request 请求失败小程序真机调后端接口绕不开“合法域名”问题。开发工具里可以勾选“不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书”来跳过但手机预览时必须使用 HTTPS 且域名已备案并在小程序后台配置为 request 合法域名。我用的是阿里云服务器加 Nginx 做反向代理。后端 Tomcat 跑 8080 端口Nginx 把https://api.demo.com/api/开头的请求转发到本机 8080这样小程序只和域名通信后端 IP 和端口变化也不用改小程序代码。SSL 证书用的 Let’s Encrypt三个月一续配合定时任务自动续期。如果没有域名短期在本地用花生壳做内网穿透也凑合但只适合联调。5.2 拦截器放行与 CORS 跨域问题SSM 的小程序接口用拦截器实现登录校验前端每次请求在 header 里带 token拦截器里放行/api/login、/api/sms等白名单路径其余接口取 token 校验失效抛 401。这里最容易踩的坑是静态资源和图片访问也被拦截器拦截。记得在 spring-mvc.xml 里配置mvc:resources mapping/upload/** location/upload/ /并且拦截器滤掉/upload/**和静态文件后缀。管理后台是普通网页部署在另一台端口访问后端接口必然产生跨域。我在 Controller 上用一个全局CorsFilter配置允许来源、允许方法、允许 header。注意Access-Control-Allow-Origin不要随手写*如果涉及携带 token 的请求浏览器不允许 origin 为*且携带Authorizationheader必须显式指定域名。5.3 小程序顶部导航栏高度与自定义导航这个小坑确实折磨了很多新手。微信小程序的胶囊按钮右上角那三个点高度在不同机型、不同系统版本下不一样自定义导航栏时如果硬编码一个 padding-topiPhone 和 Android 上就会错位。处理方式是读取系统信息里的statusBarHeight和menuButtonBoundingClientRect.top动态计算导航栏高度。我当时在 app.js 里封装了一个全局方法所有自定义导航栏页面统一调用const systemInfo wx.getSystemInfoSync(); const menuButton wx.getMenuButtonBoundingClientRect(); globalData.navBarHeight menuButton.bottom menuButton.top - systemInfo.statusBarHeight;这样不管刘海屏还是普通屏布局都不会乱。真机调试时多准备几台不同厂商的手机试一下你会感谢这个封装的。5.4 并发抢单时的数据库行锁测试抢单接口上线前一定要压测。我本地用 JMeter 模拟 50 个并发线程同时抢同一个订单一开始代码没加条件更新用的“先查后改”逻辑结果 50 个请求全部“抢单成功”订单被 50 个工程师同时持有。后来改成条件更新加事务行锁生效50 个请求里只有 1 个返回成功、其余都返回失败效果符合预期。这种问题在联调阶段不暴露一上线就完蛋。关于事务还有一个细节Transactional默认遇到 RuntimeException 才回滚如果 Service 方法里抛出的是普通 Exception事务不会自动回滚。抢单失败后我是主动抛RuntimeException来触发回滚这比在方法里手动transactionManager.rollback()干净得多。6. 整理文档和源码时的经验之谈这个项目的题目带“文档源码”说明交付物里不只代码还要有完整的项目文档。做过几次类似交付后我的体会是文档别最后写边做边写。项目文档至少要包含需求规格说明书、数据库设计说明书含 ER 图、系统概要设计、详细设计核心模块时序图、类图、测试计划与测试报告、操作手册。封面要写清楚“阳光电脑公司维修服务微信小程序系统”配上《软件工程》的标准模板详细设计部分把核心接口的请求/响应示例贴上去。很多同学的项目代码不错但文档只有苍白的功能列表答辩时老师翻不到关键设计印象分会拉低不少。另外源码目录结构也要整理规整前端小程序目录、后端 SSM 工程目录、数据库初始化脚本 sql、部署文档、演示视频、说明 README。数据库脚本要自己从零跑一遍确保另一台电脑上导入后不会报错。别偷懒把applicationContext.xml里的数据库密码写死成自己的本地密码交付时改成通用配置并在文档里注明修改位置。7. 几个能直接拿走的优化建议再说几个我实测下来好用的功能优化方向不涉及核心流程改动但体验提升很大。第一个是同步服务进度时间线。用户端订单详情页用order_status_log表渲染一条“历史轨迹”比如“2025-01-10 09:23 订单已提交”“09:40 工程师张三已接单”“10:15 工程师已上门”。这个功能用户非常买账因为维修这种服务等待感很强时间线能大幅缓解焦虑而且实现成本极低后端查询一条列表前端几个 view 循环就出来了。第二个是工程师“空闲”状态自动切换。原来工程师在后台手动改忙碌/空闲经常忘改导致用户约到了却没上门。我加了一个定时任务每天凌晨扫描“维修中”工单数超过 5 个的工程师自动改成忙碌工单结束且数量少于 3 个自动恢复空闲。虽然简单但比手动控制靠谱多了。第三个是评价模块的提醒机制。订单完成后用户不会主动去评价。我做了“完成订单后 24 小时未评价则系统自动好评”的规则同时在小程序首页做了“待评价”红点提醒。这个对平台积累口碑很有帮助不然评价栏一直空的新用户看着不放心。第四个是数据看板。管理后台加了一个简单的统计首页展示今日新增订单、今日营收、各服务项目占比图。技术实现上就是一个聚合查询接口后端返回几个统计值前端用 ECharts 画图。注意 ECharts 在小程序里有专门的 ec-canvas 版本不要直接在原生小程序里引入 web 版否则一堆兼容问题。以上是我个人从实际开发中提炼出的内容。联调时遇到过 web-view 上无法打开部分页面排查到最后是业务域名未配置还遇到过苹果手机上wx.chooseLocation闪退原因是基础库版本太低更新基础库后恢复正常。这些琐碎问题不记下来过半年再遇到又要重新踩一遍。项目做完了回看整个流程真正有意义的部分不是代码量有多庞大而是通过这个小而完整的系统把微信生态的账号体系、支付逻辑、消息推送和后端 MVC 框架、数据库事务、状态机设计全部打通了一遍。有了这套底子以后接手任何“小程序加后台管理”的项目你都会知道第一步该做什么第一步做完后大概率会在哪个坑里等坑。