自助洗车三端合一源码拆解:Java后台、Vue管理端与微信小程序全栈实现

📅 发布时间:2026/10/18 17:50:14
自助洗车三端合一源码拆解:Java后台、Vue管理端与微信小程序全栈实现
简介这是一套面向自助洗车行业开发者与创业团队的三端合一数字化运营系统源码后端采用Spring BootJava构建RESTful接口前端覆盖Vue 2.x Element UI的Web管理后台与原生微信小程序可支撑会员等级管理、车牌识别对接、洗车机状态监控、预约扫码启动、微信支付及订单消费统计等核心业务适合需要快速搭建洗车店运营平台的中级Java与前端开发者。资源包共1048个文件约14.38MB包含131个Java类、151个Vue组件、45组小程序页面文件wxml/wxss以及JSON、XML、properties等配置文件和PNG、SVG静态资源另附Maven wrapper与pom.xml等构建脚本便于本地部署调试。目前已有26人学习下载。整体目录结构清晰三端代码与配置齐全读者可据此理解洗车硬件对接逻辑、支付集成与预约流程的完整实现快速二次开发或用于课程设计参考。1. 自助洗车三端合一源码拆解Java后台、Vue管理端与微信小程序怎么串起来自助洗车这个场景看着简单——扫码、付款、开设备、计时扣费但真要把后台、管理端、小程序三端跑通涉及的东西一点不少。我手上这份 Java 源码包就是一套三端合一的完整实现Java 后台负责设备通信、订单、计费、车牌识别对接Vue 管理端给运营人员看数据、配价格、管设备微信小程序是用户侧入口扫码启动、充值、查订单。三端共用一套后端 API数据口径统一不会出现小程序显示的价格和管理端配置对不上的情况。这套东西适合谁一是想快速搭一套自助洗车 SaaS 的团队二是手里有洗车设备需要做软件配套的集成商三是拿它当 Java Vue 小程序全栈练手项目的开发者。源码结构清晰模块划分合理不是那种堆在一起的“能跑就行”的代码。下面我按实际拆解和复现的顺序把关键环节一个个讲透。2. 后端架构与核心模块从设备通信到订单计费的链路2.1 技术栈选型与模块划分这套后端用的是 Spring Boot MyBatis-Plus MySQL Redis 的组合。Spring Boot 版本是 2.x 系列MyBatis-Plus 做 ORM 省去了大量 XMLRedis 主要扛三件事设备状态缓存、订单号生成、小程序登录态。模块划分上我拆出来大致是这几块设备接入层负责和洗车机主板通信常见做法是走 MQTT 或 TCP 长连接源码里用的是 Netty 做 TCP 服务端设备主动上报心跳和状态。订单与计费模块核心逻辑在这里按时间计费还是按水量计费套餐怎么扣优惠券怎么叠都在这一层。车牌识别对接不是自己训练模型而是对接第三方识别服务源码里预留了 HTTP 接口传图片 base64 过去拿车牌号。用户与账户体系小程序用户 openid 绑定、余额充值、消费流水。管理端 API给 Vue 前端提供数据接口权限用 Spring Security JWT 控制。为什么这么分因为设备通信和业务逻辑必须解耦。设备掉线是常态不能让设备层的异常把订单事务拖垮。源码里设备消息进来先落 Redis 队列业务层异步消费这个设计在实际部署时能救命。2.2 设备通信与状态同步的实现设备通信这块源码里 Netty 服务端的启动类大致长这样Component public class DeviceServer { Value(${device.tcp.port}) private int port; PostConstruct public void start() throws InterruptedException { EventLoopGroup boss new NioEventLoopGroup(1); EventLoopGroup worker new NioEventLoopGroup(); ServerBootstrap b new ServerBootstrap(); b.group(boss, worker) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializerSocketChannel() { Override protected void initChannel(SocketChannel ch) { // 心跳超时 60 秒超时判定设备离线 ch.pipeline().addLast(new IdleStateHandler(60, 0, 0)); ch.pipeline().addLast(new DeviceDecoder()); ch.pipeline().addLast(new DeviceHandler()); } }); b.bind(port).sync(); } }这段代码的关键参数是IdleStateHandler(60, 0, 0)读空闲 60 秒就触发事件DeviceHandler里捕获后把设备标记为离线。实际部署时这个值要根据设备心跳间隔调设备 30 秒发一次心跳那 60 秒是合理的如果设备 10 秒一次可以缩到 30 秒离线判定更灵敏。DeviceDecoder负责把设备上报的二进制协议转成 Java 对象源码里协议格式是「帧头 设备号 指令 数据长度 数据 校验」解码时先读帧头对齐再按长度读数据最后校验。这里有个坑TCP 粘包。源码里用LengthFieldBasedFrameDecoder处理指定长度字段偏移和长度不然后面解析全乱。设备状态同步到 Redis 的 key 设计是device:status:{deviceNo}存一个 Hash字段包括在线状态、当前订单号、剩余时间。小程序端查设备是否可用直接读这个 key不走数据库响应快。2.3 订单计费逻辑与车牌识别对接计费逻辑是这套源码里最值得细看的部分。订单表设计上order表有billing_type字段区分计费模式1 按时间、2 按水量、3 套餐。按时间计费时启动设备写一条订单start_time记录开始设备停止时回调更新end_time然后计算时长乘以单价。单价不是写死的从price_config表读管理端可以改。public BigDecimal calculateFee(Order order) { PriceConfig config priceConfigMapper.selectByDeviceType(order.getDeviceType()); if (order.getBillingType() 1) { long minutes Duration.between(order.getStartTime(), order.getEndTime()).toMinutes(); // 不足一分钟按一分钟算向上取整 minutes minutes 0 ? 1 : minutes; return config.getUnitPrice().multiply(BigDecimal.valueOf(minutes)); } // 其他计费模式省略 }注意minutes 0 ? 1 : minutes这行用户洗了 30 秒也得按一分钟收这是行业惯例源码里考虑到了。单价从配置表读不要硬编码不然改价得重新部署。车牌识别对接部分源码里是一个PlateRecognitionService调用第三方 HTTP 接口public String recognize(String imageBase64) { // 第三方接口地址和 key 从配置文件读 HttpHeaders headers new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); MapString, String body new HashMap(); body.put(image, imageBase64); body.put(key, apiKey); ResponseEntityString resp restTemplate.postForEntity(apiUrl, new HttpEntity(body, headers), String.class); // 解析返回 JSON 取车牌号 return JSON.parseObject(resp.getBody()).getString(plateNo); }这里的关键是超时设置。第三方识别服务偶尔会慢RestTemplate必须配 connectTimeout 和 readTimeout源码里默认没配我建议加上 3 秒超时超时就走人工输入车牌兜底不然用户扫码后卡在那里体验极差。3. Vue 管理端与小程序端数据看板、设备管理与扫码启动3.1 Vue 管理端的页面结构与权限控制Vue 管理端用的是 Vue 2 Element UI Vuex Vue Router 的经典组合。页面结构上我数了一下大概有这些模块登录页、数据看板、设备管理、订单管理、用户管理、价格配置、优惠券管理、系统设置。数据看板用 ECharts 画营收趋势和设备使用率设备管理页是一个表格加状态标签绿色在线、灰色离线、红色故障。权限控制这块源码里用的是动态路由。登录后后端返回该用户的角色和权限码前端根据权限码过滤路由表没权限的页面直接不出现在菜单里。路由守卫里判断 token 是否存在不存在跳登录页。// router/index.js 路由守卫片段 router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.path /login) { next() } else if (!token) { next(/login) } else { // 已登录检查是否已拉取用户信息 if (store.state.user.roles.length 0) { store.dispatch(GetUserInfo).then(() { // 动态添加可访问路由 store.dispatch(GenerateRoutes).then(accessRoutes { router.addRoutes(accessRoutes) next({ ...to, replace: true }) }) }) } else { next() } } })这段逻辑里next({ ...to, replace: true })是必须的动态添加路由后要重新导航一次不然刷新页面会白屏。这是 Vue 动态路由的经典坑源码里处理对了。3.2 小程序扫码启动与支付流程小程序端用的是原生开发没有上 uni-app 或 Taro。核心流程是用户扫码 → 进入小程序对应页面 → 调后端接口获取设备信息 → 判断设备是否可用 → 可用则调起支付 → 支付成功后后端下发启动指令给设备。扫码进入的页面路径带参数deviceNo小程序onLoad里拿到后请求后端onLoad(options) { const deviceNo options.deviceNo // 查询设备状态 wx.request({ url: ${baseUrl}/api/device/status, data: { deviceNo }, success: (res) { if (res.data.code 200 res.data.data.online) { this.setData({ device: res.data.data, canStart: true }) } else { wx.showToast({ title: 设备离线, icon: none }) } } }) }支付流程走的是微信支付 JSAPI后端统一下单返回支付参数小程序调wx.requestPayment。支付成功后的回调里后端更新订单状态并给设备发启动指令。这里有个关键点支付回调必须做幂等微信可能重复通知源码里用订单号加 Redis 锁处理同一订单号只处理一次。3.3 三端数据一致性怎么保证三端数据一致性靠的是后端统一 API 和 Redis 缓存策略。管理端改价格写数据库的同时删掉 Redis 里对应的价格缓存 key小程序下次读价格时缓存未命中从数据库读新值再写回缓存。设备状态变化Netty 服务端更新 Redis小程序和管理端都读同一个 key不会出现一端显示在线另一端显示离线的情况。订单数据是强一致的走数据库事务。支付回调更新订单状态和用户余额在同一个事务里要么都成功要么都回滚。源码里用Transactional注解注意默认只回滚 RuntimeException如果抛的是 checked exception 要手动指定rollbackFor Exception.class这个坑源码里踩过注释里写了。4. 部署与联调避坑从本地跑通到设备对接的五个翻车点4.1 数据库脚本导入与初始配置源码包里sql目录下有建表脚本和初始数据。导入时注意字符集用utf8mb4不然车牌号里的特殊字符或者用户昵称里的 emoji 会报错。初始数据里有一个默认管理员账号密码是加密存的第一次登录后赶紧改。配置文件application.yml里要改的地方数据库连接、Redis 地址、微信小程序的 appid 和 secret、第三方车牌识别接口的 url 和 key、设备 TCP 服务端口。这些不改本地跑不起来。4.2 常见问题排查现象一小程序扫码后提示“设备不存在”。原因通常是设备号没在数据库里注册或者扫码传的 deviceNo 和管理端录入的不一致。解决管理端先添加设备确认设备号一致再扫码测试。现象二支付成功但设备不启动。先看后端日志有没有收到微信回调再看给设备发指令的代码有没有执行。常见原因是回调地址配错了微信通知发到了旧地址。解决检查微信支付配置里的 notify_url必须是公网可访问的地址。现象三管理端页面刷新后 404。Vue 是单页应用路由用的是 history 模式Nginx 没配 try_files 就会 404。解决Nginx 配置里加try_files $uri $uri/ /index.html;。现象四设备频繁上下线。心跳超时时间设太短网络稍微抖动就判定离线。解决把IdleStateHandler的读空闲时间调大或者设备端加心跳重试。现象五订单金额算出来是负数。结束时间早于开始时间通常是设备时钟不同步或者回调乱序。解决计算前校验时间结束时间早于开始时间时取当前时间兜底同时检查设备 NTP 同步。提示本地联调设备通信时没有真实洗车机可以用 Netty 写一个模拟客户端定时发心跳和状态上报源码包里test目录下有一个简易模拟器改改设备号就能用。5. 二次开发与压力测试把源码改成自己能用的版本5.1 计费规则扩展与设备协议适配这套源码的计费逻辑是写死在calculateFee方法里的如果要加新的计费模式比如“前 5 分钟免费之后按秒计费”直接改这个方法就行。但更好的做法是抽一个BillingStrategy接口不同模式实现不同策略类用工厂模式根据billing_type拿策略。这样加新模式不用动老代码。设备协议适配也是同理。源码里DeviceDecoder是按一种协议写的如果换一个品牌的洗车机协议不一样就新写一个 Decoder在ChannelInitializer里根据设备号前缀或者端口区分加载。我一般会留一个protocol字段在设备表里初始化 pipeline 时查一下设备用的哪种协议。5.2 用 JMeter 做接口压测的配置要点上线前压测是必须的。用 JMeter 压订单创建和支付回调这两个接口因为它们是并发的核心。线程组设 100 并发循环 10 次看响应时间和错误率。重点观察 Redis 连接池和数据库连接池有没有被打满。# JMeter 命令行压测示例 jmeter -n -t order_test.jmx -l result.jtl -e -o report/-n是非 GUI 模式-t指定测试计划-l输出结果文件-e -o生成 HTML 报告。压测时把日志级别调到 WARN不然日志 IO 会成为瓶颈。如果错误率超过 1%先看数据库慢查询订单表的user_id和device_no字段必须建索引。5.3 一个验证三端数据一致性的小技巧改完代码后怎么快速验证三端数据一致我的习惯是管理端改一个价格然后小程序端下拉刷新看价格变没变同时用 Redis 客户端查一下缓存 key 是不是被删了。如果小程序还是旧价格说明缓存没删干净检查删除逻辑是不是漏了某个 key 前缀。还有订单状态支付成功后管理端订单列表和小程序订单详情必须同时变成“已完成”。如果一端变了另一端没变大概率是更新数据库后缓存没失效或者一端读的是从库有延迟。源码里读订单走的是主库这个问题不存在但如果自己改成读写分离就要注意。从那以后我每次部署完这套源码都强制走一遍「管理端改价 → 小程序下单 → 支付 → 设备启动 → 订单完成」的全链路确认三端数据对得上才敢交给运营用。希望帮到你。本文还有配套的精品资源点击获取