
简介这套基于RuoYiSpringBootVue前后端分离的售货机管理系统面向Web应用开发者和后台管理框架学习者针对智能售货机的商品、库存、销售数据、设备和支付方式等管理需求提供了一个完整的模块化解决方案。系统覆盖商品档案、库存进出、销售统计、设备维护与支付渠道配置等核心模块前端Vue组件与后端接口分开组织目录结构清晰。资源共541个文件约14.81MB其中包含124个Vue页面组件、94个JavaScript逻辑文件、159个PNG与128个SVG图标资源以及CSS、SCSS样式文件和构建脚本前端界面与后端逻辑均可直接查看和修改。已有314人学习下载适合用于SpringBootVue项目实战练习、毕业设计参考或作为商城类后台系统的二次开发基础。借助RuoYi框架的前后端分离特性学习者可以快速理解商品管理、库存流转、销售统计等典型业务模块的实现思路以及MyBatis数据持久化和Spring Security安全控制的具体应用。1. 项目背景与整体架构设计思路做售货机管理系统这个项目最开始的想法其实很朴素市面上成熟方案要么太重、定制成本高要么太轻、连基本进销存都管不清楚。正好团队里对 RuoYi若依这套脚手架比较熟就想着拿它当底座把设备管理、商品管理、订单交易、补货盘点这些业务往上摞。事实也证明这个选择是对的——RuoYi 本身基于 SpringBoot Vue 前后端分离架构权限、用户、菜单、日志这些通用能力开箱即用省下来的时间全部可以投入到售货机业务本身。先说说整体架构。后端是 SpringBoot核心版本用的是 2.5.x 这个线配合 MyBatis 做数据持久层前端是 Vue 2 Element UI走的是 RuoYi 官方前后端分离那套目录结构。数据库用的是 MySQL 8.0缓存用了 Redis用来扛设备心跳和 Token 刷新这类高频读取场景。整个系统部署在阿里云 ECS 上前后端通过 Nginx 做反向代理和静态资源托管HTTPS 证书挂的是免费版够用就行。为什么选 RuoYi 而不是从零搭我个人的判断是售货机系统本质上是个典型的“管理后台 设备通讯”业务80% 的能力都是通用的——操作员账号、角色权限、操作日志、数据字典、文件上传这些 RuoYi 都有现成的。剩下 20% 才是售货机独有的业务逻辑。如果从零开始光权限这套就能折腾两周还不一定能做到若依这么完善。当然用 RuoYi 也有代价后面我会详细讲讲二次开发时踩过的坑。提示如果你所在团队对 RuoYi 不熟悉评估期建议先跑通官方的前后端分离版本确认菜单权限和代码生成器能满足基本预期再动手做业务设计否则中途换架构的成本很高。2. 售货机业务模块拆解与数据表设计2.1 核心业务链路梳理售货机管理系统的业务链路说白了是三段设备端售货机本体→服务端本系统→运营管理端管理员手机或电脑。设备端通过 HTTP 定时上报心跳、库存、故障状态服务端处理订单和支付回调运营管理端则负责上架商品、设置价格、查看销售报表、安排补货。在设计数据表时我没有一上来就建一堆表而是先把核心链路画出来。整个系统最核心的表就这么几张tb_vending_machine设备基础信息包括设备编号、所在位置、经纬度、状态在线/离线/故障、售货机类型、合作商户IDtb_product商品信息名称、图片、规格、成本价、售价、条码tb_machine_product设备与商品的关联表记录某台设备上挂了哪些商品、当前库存、补货阈值、货道编号tb_order订单主表记录用户购买行为关联设备ID、商品ID、支付方式、实付金额、订单状态tb_order_item订单明细表理论上售货机一次只出一件商品但考虑到用户可能一次买多件所以拆成主表和明细表更稳妥。这几张表是核心周边再配上补货记录表、故障工单表、对账单表、操作日志表等辅助表。RuoYi 自带的sys_user、sys_role、sys_menu等系统表不动直接复用。2.2 数据表设计的关键取舍有几个字段的设计我实际开发时是反复调过的这里重点说一下。第一个是订单号。商业场景里订单号必须唯一且趋势递增方便对账。我是直接用 Redis 自增 日期前缀的方式生成比如202406011030001234避免了数据库主键的无序性对索引的影响。RuoYi 集成了 Redis这一步实现起来很顺。第二个是库存扣减时机。售货机的库存分两层数据库库存和设备实际库存。用户下单时服务端必须先在数据库扣减库存预占然后下发指令给设备设备出货成功后再确认扣减若出货失败则回滚并退款。这里我用了一个简单的状态机待出货 → 出货中 → 已完成 / 出货失败自动退款。如果不加这个状态机高并发场景下很容易出现“数据库显示有货设备实际没货”的尴尬情况。第三个是设备心跳时间戳。设备每 30 秒上报一次心跳服务端更新 Redis 中该设备的最后心跳时间。判断设备是否在线不是查 MySQL而是查 Redis——now - lastHeartbeat 90秒就判定离线。查表太慢Redis 的 TTL 机制还能自动过期清理一举两得。注意设备表不要每次心跳都 update 整行数据否则数据库的写压力会非常大。一台设备一天心跳 2880 次1000 台设备就是 288 万次写入。把这些高频状态放 Redis数据库只存最终状态这是从实际压测中得出的结论。3. RuoYi 二次开发与核心功能落地实操3.1 代码生成器是效率利器但需要“调教”RuoYi 自带的代码生成器可以直接根据数据库表生成 Java 代码、Vue 页面、SQL 菜单这点确实香。但我的实操经验是——生成器生成的代码只能当骨架不能直接上生产。比如生成出来的列表页查询条件比较死板售货机列表要按“所属区域”“设备状态”联动筛选生成器默认只是单个字段的等于查询得手动改。我的做法是先用生成器打好基础domain、mapper、service、controller、vue 页面一锅端然后按业务需要手动调整查询逻辑。比如tb_vending_machine表的列表查询我改造成了支持多条件组合设备名称模糊查询、设备编号精确查询、所在位置模糊查询、设备状态下拉选择并且加了时间段筛选。改造过程并不复杂就是在 controller 层的list方法里接收多个参数拼进 Service 的 QueryWrapper。另外建议大家把生成代码时“去除表前缀”的选项勾上这样生成的类名比较干净比如TbVendingMachine直接生成VendingMachine实体避免后期代码里到处都是tb_前缀看着乱。3.2 设备指令下发要用异步重试售货机的核心动作是“出货”。用户在客户端下单后系统要把出货指令推送到设备。设备是走 HTTP 长轮询的服务端把指令先写到指令表设置状态为待发送设备下次轮询的时候拉走执行完再回调结果。指令这块最大的坑是重复下发。因为网络超时会导致设备端和服务端状态不一致如果不做幂等处理用户买一瓶水可能掉两瓶下来。解决办法是每条指令生成一个全局唯一的instruction_id设备端执行前检查这个 ID 是否已执行过已执行直接返回成功结果。这套方案在对接市面上主流售货机主控板时基本都通用不管硬件用的是串口电台还是 4G DTU。指令状态我设计了 4 个0 待发送、1 已发送、2 执行成功、3 执行失败。服务端有个定时任务每 10 秒扫描一次把超过 30 秒还没被设备拉取的指令标记为超时并触发告警。定时任务在 RuoYi 里配置也很简单用Scheduled注解配合cron表达式就行。3.3 支付回调的幂等与对账方案售货机支持微信、支付宝扫码支付。支付链路的经验是客户端不直接调支付接口必须由服务端统一下单。即服务端先创订单再调用微信/支付宝预下单接口拿到支付二维码用户扫码付款后支付平台异步回调服务端通知结果。回调处理有几个关键点。第一回调接口必须是外网可访问的 HTTPS 地址且要关闭 RuoYi 的登录鉴权过滤器否则支付平台回调进不来。第二必须验证回调签名防止伪造通知。第三处理回调时必须做幂等校验——同一个订单的重复回调不能重复增加余额。我是用 Redis 的分布式锁做的key 是pay_callback:{订单号}加锁后判断订单状态已处理直接返回成功。对账方案也很重要。每天凌晨跑一个定时任务拉取前一天的微信/支付宝账单和本地订单表逐笔核对找出“本地已支付但平台无记录”或“平台已扣款但本地未处理”的异常订单生成对账差异报告。这块不复杂但特别考验耐心字段映射和金额精度处理很琐碎金额一律用BigDecimal以“分”为单位存储避免 float 精度问题。3.4 权限与多角色管理的二次封装RuoYi 自带的权限模型是 RBAC用户→角色→菜单。售货机系统在这个基础上要再加一层数据权限。比如区域经理只能看自己辖区内的售货机品牌运营方只能看自己品牌下的设备不能所有运营商登录进来都看到全量数据。RuoYi 本身支持数据权限核心是DataScope注解配合sys_role_data_scope表。我根据业务需要做了扩展在tb_vending_machine表上增加dept_id字段把设备归属到部门这样就能复用若依原生的数据权限逻辑。代码层面只需要在 Mapper 查询方法上加上DataScope(deptAlias vm)注解框架会自动拼上 SQL 过滤条件省了很多事。4. 前后端联调与部署上线的实战记录4.1 本地开发环境的坑与解决前后端分离开发最烦的就是联调环境问题。前端用 Vue默认开发服务器端口是 80后端 SpringBoot 是 8080必然跨域。RuoYi 官方的前端项目里已经内置了代理配置vue.config.js里的proxy节点会把/dev-api开头的请求转发到后端。这个机制很成熟但如果是从零接手的同学最容易犯的错是直接在 axios 请求里写死了后端 IP。正确做法是环境变量区分。我在.env.development里配置了VUE_APP_BASE_API /dev-api本地请求走相对路径交给 webpack 代理转发生产环境配置VUE_APP_BASE_API /prod-api由 Nginx 统一转发。改起来就是一行配置的事但能省掉后来人无数问号。另一个容易踩坑的是依赖版本冲突。RuoYi 前端用的 Element UI 版本和 Node 版本有对应关系建议 Node 直接用 14.x LTS别用太新的 18否则node-sass编译会报错。我实测下来用sass替换node-sass可以解决绝大多数环境兼容问题。4.2 阿里云部署完整流程打包部署这一步我把流程彻底跑通后总结了一套标准的操作顺序照着做基本不会出错。后端部署# 1. Maven 打包跳过测试 mvn clean package -DskipTests # 2. 上传 jar 包到服务器 scp ruoyi-admin.jar root你的服务器IP:/opt/ruoyi/ # 3. 编写 systemd 服务文件开机自启系统服务文件的写法我贴一下这套配置实测很稳[Unit] DescriptionRuoYi Vending Machine Server Afternetwork.target [Service] Typesimple Userroot WorkingDirectory/opt/ruoyi ExecStart/usr/bin/java -server -Xms512m -Xmx1024m -jar ruoyi-admin.jar --spring.profiles.activeprod Restarton-failure RestartSec10s [Install] WantedBymulti-user.target前端部署# 1. 前端打包 npm run build:prod # 2. 将 dist 目录上传到服务器 /usr/share/nginx/html # 3. 配置 Nginx关键配置如下Nginx 配置是前后端部署的重头戏这里贴一份实际使用的精简配置server { listen 443 ssl http2; server_name 你的域名; ssl_certificate /etc/nginx/cert/fullchain.pem; ssl_certificate_key /etc/nginx/cert/privkey.pem; # 前端静态资源 location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } # 后端接口反向代理 location /prod-api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }核心就两点try_files要指向index.html保证 Vue Router 的 history 模式刷新页面不 404接口路径用/prod-api/开头并转发到后端proxy_pass后面的斜杠不要落下否则路径会多一层。注意前后端分离项目部署后最常见的报错就是前端请求接口返回 404 或 502。遇到这种问题先看 Nginx 的 error.log 确认转发地址是否正确再看后端的启动日志确认是否有接口报错两边日志一对比问题基本就能定位。4.3 上线后必做的几个稳定性配置系统上线不是跑起来就完事稳定性配置才是真正见功夫的地方。MySQL 连接池要调。RuoYi 默认用的 HikariCP默认maximum-pool-size是 10售货机设备多的时候根本不够用。我调到了 50同时设置了connection-timeout: 30000避免连接池耗尽后接口无限等待。JVM 参数要针对服务器配置调。上面 systemd 配置里给了-Xms512m -Xmx1024m这只是一个基础值。如果服务器只有 2G 内存堆别给太大要留出内存给操作系统和 Nginx。内存溢出后的重启策略靠Restarton-failure兜底。日志要持久化并定期清理。RuoYi 默认用 logback我配置了按天滚动和最大保留 30 天避免日志文件把磁盘塞满。同时加了关键业务的操作日志埋点比如“创建订单”“下发出货指令”“补货完成”以后排查问题追溯现场就靠这些日志。Redis 要做持久化。默认的 RDB 快照策略如果机器突然断电会丢数据设备心跳数据丢了还好但如果支付回调的锁数据丢了可能会引发重复回调。稳妥的做法是开启 AOF 持久化代价是写性能略降但对这个业务量级完全没影响。5. 常见问题与排查技巧速查开发到上线这几个月遇到的高频问题我整理了一张速查表基本覆盖了 RuoYi 售货机业务最常见的坑问题现象可能原因排查与解决方案前端页面白屏控制台报 Uncaught SyntaxError打包后静态资源路径不对检查vue.config.js中publicPath是否为/或改为相对路径./接口请求 401明明已经登录过Token 过期或 Redis 中 session 丢失检查 Redis 连接是否正常RuoYi 默认 token 有效期 30 分钟可调长设备上报心跳但系统显示离线Redis 中心跳时间没更新检查心跳接口是否被白名单拦截确认设备与服务器的网络连通性用户支付成功但订单状态没更新支付回调接口被权限拦截或签名校验失败确认回调地址在SecurityConfig中放行核对支付平台密钥前端打包后本地测试正常服务器上 404Nginx 没有配置 try_files按上文 Nginx 配置补上try_files $uri $uri/ /index.html;设备出货指令超时指令状态未流转或设备离线检查指令表状态用测试工具模拟设备轮询确认指令下发链路通畅高并发下单时库存超卖数据库扣减不是原子操作使用 SQLUPDATE tb_machine_product SET stock stock - 1 WHERE id ? AND stock 0保证原子性排查技巧有一个核心原则先看日志再猜原因。RuoYi 的日志体系相对完整后端日志在logs/sys-error.log里有堆栈记录前端浏览器 Network 面板能看到具体请求和响应码。把请求路径、状态码、响应体三者对起来看90% 的问题都能快速定位。另外一个我强烈建议做的接入一个简单的告警通知。设备离线超过 5 分钟、订单退款失败、对账差异超过阈值都通过钉钉机器人推送到运维群。售货机是无人值守场景设备离线不告警用户扫码买不了东西才发现那时候损失已经造成了。钉钉机器人的 webhook 对接非常简单后端封装一个 HTTP 请求工具类就行成本极低但价值极高。最后再说一个容易被忽略的小细节——时间统一用服务器时区。RuoYi 默认时区是GMT8但如果服务器时区设置不对比如默认 UTC订单创建时间和支付回调时间就会差 8 小时对账的时候全是差异查了半天最后发现只是时区没配对。部署时执行一句timedatectl set-timezone Asia/Shanghai就能避免这个坑。售货机管理系统这个项目技术上其实没有特别高深的东西真正难的是把业务流程梳理清楚再把框架的通用能力和业务需求结合好。RuoYi 帮我们解决了很多基础设施问题但具体业务的设计与实现还是得自己一步一个脚印地趟。这几百台设备跑下来从订单链路到设备运维都稳定运行也算是对前期设计的一个验证了。本文还有配套的精品资源点击获取