资讯详情

废品回收小程序全栈开发:Spring Boot订单状态机与微信端避坑实践

📅 2026/10/7 1:39:12 | 华诺云谱 👁 阅读
废品回收小程序全栈开发:Spring Boot订单状态机与微信端避坑实践
简介这是一份基于 Spring Boot 与 Vue.js 的废品回收系统小程序源码面向希望学习前后端分离开发或进行回收业务二次开发的开发者。压缩包共 37 个文件大小 2.87MB包含 Java 后端业务逻辑、HTML/CSS/JS 前端页面、yml/xml 配置、图片字体资源以及 docx 必读说明和 pdf 配置说明目录清晰便于快速定位。项目覆盖用户认证、废品价格查询、回收订单处理、废品分类与回收流程管理等典型模块展示了 Spring Boot 快速构建接口与 Vue 数据驱动视图的协作方式对理解系统设计和前后端联调很有帮助源码中的 Java 算法文件和配置文件还能帮助读者掌握回收定价或订单分配相关的实现思路。当前已有 20 人学习适合开发者从源码入手结合文档分析模块实现理解核心功能的完整链路并在此基础上升级功能或替换业务流程。1. 废品回收系统小程序到底在做什么一个被低估的全栈样本废品回收系统小程序本质上是一套「用户端下单预约回收 回收员接单上门称重 管理员后台定价审核」的三端闭环项目技术栈是微信小程序原生 Vue 管理后台 Spring Boot 后端。我见过太多人把这个项目当成简单的 CRUD 课设实际动手才发现运费模板计算、订单状态机、微信手机号授权、小程序真机调试这几块才是真正卡人的地方。不论你是想把它当作毕设、接单练手还是给社区做个再生资源回收的 demo这套源码能让你在一天内摸清前后端分离项目的骨架和坑。下面我按自己做过的落地路径把选型理由、接口设计、踩坑记录一次讲清楚。2. 先把业务边界画清楚三个端各管什么事表怎么设计2.1 角色拆解与权限模型别一上来就写代码拿到「废品回收系统」这类项目标题第一反应不该是建工程而是先问谁在用这个系统我一般梳理成三类角色。C 端用户通过微信小程序提交回收预约选择地址、回收品类、预计重量和上门时间回收员在小程序端或专用的小程序窗口接单、上门、称重、结算管理员在 Vue 后台维护回收品类、运费模板、审核回收员资质、查看订单统计。权限模型不一定要引入 Spring Security 那一套重东西。常见做法是用户表里加一个role字段取值USER、COLLECTOR、ADMIN配合 JWT 拦截器做接口级鉴权。小项目这么做完全够用等到真的需要细粒度权限比如区域回收员只能看自己片区的订单再上 Sa-Token 或 Spring Security 也不迟。2.2 核心表设计订单表是心脏运费模板表是良心我把表结构画出来再动手写代码一般至少需要六张表user用户、recycle_category回收品类、order回收订单、order_item订单明细记录每类废品的重量和金额、freight_template运费模板、collector_apply回收员入驻申请。订单表字段里除了常见的外键和状态有两个字段建议从一开始就留好。一个是order_no订单编号别用自增 ID 直接暴露给用户用时间戳 随机数生成个业务号另一个是coupon_amount优惠金额哪怕第一期不做优惠券也先把字段埋上。返工改表最烦尤其是订单这种核心表。品类表和运费模板表的关联方式是每个品类挂一个freight_template_id管理员调整价格时只改模板不动订单。2.3 订单状态机从预约到结算一共七个节点废品回收和普通电商最大的区别是有一个「上门称重」的线下环节所以订单状态不能只靠支付驱动。我设计的状态流是这个顺序待接单 → 已接单 → 待上门 → 已称重 → 待结算 → 已完成外加一个终态已取消。每一笔订单在状态流转时后端必须记录状态变更日志否则用户投诉「我的订单怎么从待接单变成已取消了」时你没证据可查。状态机的实现不建议用一堆if/else在 Controller 里硬写。我习惯在每个状态节点上写清楚「允许从哪些状态进入、由谁触发、需要哪些前置条件」。比如「已接单」只能由待接单状态进入触发人必须是回收员角色前置条件是该订单没有被其他回收员抢先接走。这种约束写在后端 Service 里比放前端控制可靠得多。后面第 3 章我会给出具体的代码模板。3. Spring Boot 后端落地订单状态机、运费计算与权限拦截3.1 工程结构与依赖选型Java 8 Spring Boot 2.7.x 最稳废品回收系统这类中小型项目我一般用 Java 8 配 Spring Boot 2.7.x别追新。原因很现实很多教程、开源组件、云服务器上的 JDK 环境还在 Java 8Spring Boot 3.x 强制要求 JDK 17 且javax.servlet包名改为jakarta.servlet一旦选型失误你会发现网上搜到的老代码全跑不起来——这个坑在第 5 章我会展开讲。基础依赖就四个spring-boot-starter-web、mybatis-plus数据库操作、jjwt生成和解析 JWT、lombok省略 getter/setter。MyBatis-Plus 在这个项目里比 JPA 顺手因为它自带分页插件和条件构造器写订单查询这类多条件过滤时能少写很多 XML。工程结构按功能分包不按技术层次分包。也就是说不要建controller、service、mapper三个平级包然后互相引用而是每个业务模块一个包order、user、recycle、freight。这样后期维护时改一个订单功能只需要进order包不会牵连别的模块。3.2 订单状态机的代码实现状态流转写在 Service 里接单接口的核心代码我一般这么写// OrderService.java Transactional(rollbackFor Exception.class) public boolean acceptOrder(Long orderId, Long collectorId) { // 1. 查询订单并加锁防止两个回收员同时接同一单 Order order orderMapper.selectByIdForUpdate(orderId); if (order null) { throw new BizException(订单不存在); } // 2. 状态机校验只有待接单状态才能被接单 if (!OrderStatus.PENDING_ACCEPT.getCode().equals(order.getStatus())) { throw new BizException(该订单已被接走或已取消); } // 3. 业务校验回收员必须已通过资质审核 User collector userMapper.selectById(collectorId); if (collector null || !APPROVED.equals(collector.getCollectorStatus())) { throw new BizException(回收员资质未通过审核); } // 4. 更新订单状态和回收员信息 order.setStatus(OrderStatus.ACCEPTED.getCode()); order.setCollectorId(collectorId); order.setAcceptTime(LocalDateTime.now()); orderMapper.updateById(order); // 5. 写状态变更日志 orderLogMapper.insert(OrderLog.build(order.getId(), OrderStatus.PENDING_ACCEPT.getCode(), OrderStatus.ACCEPTED.getCode(), collectorId)); return true; }逻辑说明第一步用了selectByIdForUpdate行级锁这是避免并发接单的关键。两个回收员同时点接单时数据库层面会串行化后执行的那个事务读到的是加了锁之后的最新状态状态机校验就会失败接口直接抛出「该订单已被接走」。第二步到第四步是状态机的核心约束状态必须按预设方向流转不允许跳状态或回退。第五步写日志属于锦上添花但生产环境排查问题时就靠它。参数说明OrderStatus是枚举类PENDING_ACCEPT 表示「待接单」ACCEPTED 表示「已接单」BizException是自定义业务异常由全局异常处理器统一捕获并返回{ code: 400, msg: ... }格式给前端。事务注解Transactional保证了状态更新和日志写入在同一次事务里要么都成功要么都回滚不会出现订单状态改了但日志丢了的情况。3.3 运费模板计算重量阶梯计价金额一律用分存储废品回收的计价规则我遇到过三类按品类单价乘重量、按重量区间给不同单价、上门服务费按距离算。最省事的方案是「重量阶梯计价」比如纸壳 10 公斤以内每公斤 0.8 元超过 10 公斤部分每公斤 0.6 元。实现方式放在FreightTemplateService里// FreightTemplateService.java public BigDecimal calcAmount(Long categoryId, BigDecimal weightKg) { FreightTemplate template freightTemplateMapper.selectByCategoryId(categoryId); if (template null) { throw new BizException(该品类暂未开通回收服务); } // 阶梯计价第一阶梯 thresholdKg 内按 priceFirst超出部分按 priceSecond if (weightKg.compareTo(template.getThresholdKg()) 0) { return weightKg.multiply(template.getPriceFirst()) .setScale(0, RoundingMode.HALF_UP); // 金额转分为整数 } BigDecimal within template.getThresholdKg().multiply(template.getPriceFirst()); BigDecimal excess weightKg.subtract(template.getThresholdKg()) .multiply(template.getPriceSecond()); return within.add(excess).setScale(0, RoundingMode.HALF_UP); }逻辑说明金额存储一律用「分」这个整数单位避免浮点数运算带来的 0.1 0.2 不等于 0.3 的问题。数据库字段类型用BIGINT实体里用Long或BigDecimal都行但计算时必须用BigDecimal而不是double。最后setScale(0, RoundingMode.HALF_UP)是把计算结果四舍五入到整数分。参数说明thresholdKg是阶梯分界重量priceFirst是第一阶梯单价分/公斤priceSecond是超出部分的单价。这两个字段由管理员在 Vue 后台维护修改后立即生效。这里有坑如果前端展示的单价是元后端接收时必须先转换成分为整数再入库我见过太多人因为单位不统一导致结算金额差一百倍——第 5 章避坑部分还要再提一次。3.4 权限拦截JWT 拦截器与注解鉴权接口鉴权我用 JWT HandlerInterceptor 实现。登录成功后签发 token后续请求在 Header 里带Authorization: Bearer token拦截器解析 token 并提取用户 ID 和角色// JwtInterceptor.java Component public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (token ! null token.startsWith(Bearer )) { token token.substring(7); try { Claims claims Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody(); request.setAttribute(userId, claims.get(userId)); request.setAttribute(role, claims.get(role)); return true; } catch (Exception e) { // token 过期或非法统一返回 401 } } response.setStatus(401); response.setContentType(application/json;charsetUTF-8); response.getWriter().write({\code\:401,\msg\:\登录已过期请重新登录\}); return false; } }逻辑说明preHandle 里先取 Header 中的 token解析成功后把用户信息塞进 request attributeController 里就能通过RequestAttribute直接拿。解析失败统一返回 401 JSON不让请求继续往下走。这个拦截器只做身份认证角色鉴权放 Controller 层用RequireRole(COLLECTOR)这类自定义注解做会更清晰。参数说明secretKey是签名密钥至少 32 位字符串生产环境必须放在配置中心或环境变量里不能写死在代码里。token 有效期一般设 2 小时小程序端每次启动时静默调用刷新接口续期。注意Forbidden 不等于 Unauthorized角色不匹配返回 403登录过期返回 401很多新手把这两个状态码混用前端拿到后不好判断该跳登录页还是弹「无权限」提示。4. 微信小程序端实现手机号登录、接单推送与地图选点4.1 微信小程序登录获取手机号不是调一个接口那么简单小程序端最重要的登录链路是「微信登录获取手机号」。很多新手以为前端拿到wx.login的 code 传给后端就能直接换手机号——这是错的。正确流程分三步wx.login拿 code 换 openid用户点击授权弹窗触发e.detail.code拿到手机号动态令牌后端拿这个动态令牌调微信接口换真实手机号。第一步wx.login的代码长这样// pages/login/login.js wx.login({ success: async (res) { if (res.code) { // 将 code 传给后端后端用 code appid secret 换 openid 和 session_key const loginRes await wx.request({ url: https://your-server.com/api/auth/wx-login, method: POST, data: { code: res.code } }); // 后端返回的自定义登录态 token后续所有请求都带上 wx.setStorageSync(token, loginRes.data.data.token); } } });逻辑说明wx.login获取的 code 只能使用一次有效期五分钟。后端拿这个 code 调用微信的jscode2session接口拿到 openid 和 session_key。openid 是用户在当前小程序下的唯一标识拿它查数据库有记录就直接登录没记录就自动注册一个账号。这里不建议直接拿 openid 当登录凭证返给前端而是后端自己签发 JWT token这样 token 过期时间、角色信息都能自定义控制。参数说明wx.request的 URL 不能是 IP 或 localhost必须是小程序后台配置的合法域名且必须是 HTTPS。开发阶段可以在开发者工具里勾选「不校验合法域名」但真机预览时这个开关不生效——这是新手最常翻车的地方第 5 章细说。第二步手机号授权代码是// pages/profile/profile.js async handleGetPhoneNumber(e) { if (e.detail.code) { // code 是动态令牌5 分钟内有效且只能用一次 const res await wx.request({ url: https://your-server.com/api/auth/phone, method: POST, data: { code: e.detail.code, token: wx.getStorageSync(token) } }); // 后端拿 code 调微信接口换手机号 if (res.data.code 0) { wx.showToast({ title: 手机号绑定成功 }); } } else { // 用户拒绝了授权 wx.showToast({ title: 不授权无法下单, icon: none }); } }逻辑说明从基础库 2.21.2 开始open-typegetPhoneNumber的按钮返回的是e.detail.code不是手机号明文。后端要用这个 code 调微信的接口换手机号。这个 code 是一次性的有效期为五分钟所以拿到后要立刻传给后端。用户拒绝授权时e.detail.code是空的——此时不能弹个 toast 就了事业务上要引导用户手动填写手机号作为兜底方案。4.2 接单消息的实时性WebSocket 还是轮询废品回收订单的时效性很强用户预约了明天上午九点上门回收员得尽快接单。消息推送有两条路WebSocket 长连接或者小程序端定时轮询。我建议小项目先用轮询——微信小程序的 WebSocket 在切后台时有可能会被系统断开重新建立连接的逻辑复杂度远超你的预期。我用轮询的方案是回收员端小程序每隔 15 秒请求一次/api/order/pending-list?statusPENDING_ACCEPT有新订单就震动 弹提示。接口里用updatedAt做增量查询只返回最近更新的订单减少流量浪费。等日活上千、回收员数量超过五十人再换 WebSocket 不迟。这里有一句话要说接单页面的「立即接单」按钮一定要做防重复点击。用户手快了连点两次后端有状态机拦着不会出现两张单但前端要提示「接单成功」否则回收员以为没点上又点一次后端返回订单已接走用户就会觉得系统有 bug。4.3 地图选点与地址管理腾讯位置服务的小程序适配用户预约回收时得选上门地址地图选点我推荐用腾讯位置服务微信小程序 SDK因为微信内置了wx.chooseLocation不需要额外申请地图 SDK key拿到的经纬度和地址名可以直接存库// pages/order/create.js async chooseAddress() { const res await wx.chooseLocation({ success: (res) { this.setData({ address: res.address, latitude: res.latitude, longitude: res.longitude }); } }); }逻辑说明wx.chooseLocation需要在app.json里声明permission和requiredPrivateInfos否则真机调用会直接失败。声明方式如下{ permission: { scope.userLocation: { desc: 你的位置信息将用于选择回收上门地址 } }, requiredPrivateInfos: [getLocation, chooseLocation] }参数说明如果不加requiredPrivateInfos开发工具里可能正常但真机预览时接口返回fail: no permission——这是 2022 年微信新增的隐私接口管控属于高频翻车点。另外经纬度坐标一定要用小程序返回的原始值不要前端自己转换坐标系后端对接第三方地图比如高德时再统一转换 GCJ-02 与 WGS-84否则位置会偏几百米。5. 部署与联调避坑springboot 版本、小程序抓包、合法域名这些我踩过的坑5.1 Spring Boot 版本太高javax 改 jakarta 导致老代码全废现象用 Spring Boot 3.x 创建工程引入网上找的 MyBatis-Plus 或其他第三方依赖启动报ClassNotFoundException: javax.servlet.Filter或编译错误cannot find symbol javax.servlet.http.HttpServletRequest。原因Spring Boot 3.0 从 Java EE 迁移到 Jakarta EE所有javax.*包名变成jakarta.*。老教程、老代码、相当一部分第三方 starter 都还停留在 javax。如果你照着一个 2.x 写的教程搭 3.x 的工程十有八九会对不上。解决废品回收这种项目直接用 Java 8 Spring Boot 2.7.x。两个版本的功能差异对你这个项目没有任何影响但网上 90% 的教程和问答都能直接照抄。如果你已经建了 3.x 工程要么全局替换javax.为jakarta.只影响少量 import要么把 spring-boot-starter-parent 版本降到 2.7.18。别在这上面较劲没有意义。5.2 微信小程序请求接口报「不在以下 request 合法域名列表中」现象开发者工具里勾选了「不校验合法域名」一切正常一真机预览所有wx.request全部失败报url not in domain list。原因小程序真机环境强制校验合法域名开发者工具的开关只在本地调试时生效。你把后端跑在http://localhost:8080或局域网 IP 上真机当然访问不了。解决两个办法。第一部署阶段花钱或找一台云服务器把 Spring Boot 服务通过 Nginx 代理成 HTTPS 域名域名在小程序后台配到合法域名列表里。第二开发阶段用微信开发者工具的「真机调试」功能它允许你填入本机局域网 IP——注意手机和电脑必须在同一 Wi-Fi 下且电脑防火墙要放行 8080 端口。我见过最离谱的情况是后端服务正常、手机也能 ping 通电脑但就是连不上最后发现是 Windows 防火墙默认拦截了 Java 进程。5.3 小程序抓包Charles 配好也抓不到 HTTPS 流量现象用 Charles 抓小程序请求看到一堆CONNECT请求但没有具体接口内容或直接显示SSL Handshake Failed。原因小程序对 HTTPS 证书有完整性校验Charles 的 CA 证书默认不被信任。你以为装了 Charles 根证书就行实际上微信对本地证书有额外的校验逻辑。解决我常用的方案有三个按省事程度排序。第一用微信开发者工具自带的 Network 面板——它内置抓包不用任何代理配置这是最推荐的。第二在装有 Charles 的电脑上启动 Android 模拟器模拟器里安装 Charles 证书并把代理指向电脑 IP大部分调试场景够用。第三如果你需要看加密后的请求体那就别抓包了直接在后端接口打日志打印请求参数和响应结果反而最快。抓包的目的是确认数据对不对后端日志同样能达到这个目的。5.4 Vue 打包放进 Spring Boot 的静态目录路由刷新 404现象Vue 管理后台执行npm run build后把 dist 文件复制到 Spring Boot 的src/main/resources/static目录打开首页正常但点开「订单管理」页后刷新浏览器出现 404。原因Vue Router 默认用 HTML5 History 模式路由路径是/order/list这种伪路径不存在的物理文件。Spring Boot 的静态资源处理找不到对应路径就返回 404。解决两种办法。第一种Vue Router 改用 hash 模式URL 变成/index.html#/order/list刷新不会 404但 URL 不够好看。第二种Spring Boot 里加一个路由转发配置把所有非 API 请求转发到index.html。我建议用第二种配置如下Configuration public class WebConfig implements WebMvcConfigurer { Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController(/{path:[^\\.]*}) .setViewName(forward:/index.html); } }逻辑说明这段配置把所有不带文件扩展名的路径都转发到/index.html由 Vue Router 接管渲染。注意[^\\.]*正则排除了带点号的路径避免静态资源.js、.css也被转发。另外这个方案只在后端部署静态资源时用开发阶段请保持前后端分离Vue 起npm run dev在 5173 端口后端接口在 8080 端口通过 Vite 的代理转发接口请求。这样前端改动即时生效后端也能正常断点调试。5.5 zip 解压后中文路径乱码项目启动直接失败现象从网上下载的「废品回收系统小程序源码.zip」解压后目录名是锟斤拷或液体这类乱码IDE 打开代码文件也乱码Maven 编译报符号错误。原因压缩包创建时用的是非 UTF-8 编码的压缩软件常见于 Windows 默认的 GBK 编码而你的解压环境默认 UTF-8。VSCode 和 IntelliJ IDEA 默认读取 UTF-8两者不一致就出乱码。解决Windows 上装 7-Zip用它打开 zip 后手动把文件名复制走或者在解压时设置编码为 GBK。macOS 上可以用命令ditto -V -x -k 源码.zip 目标目录处理。代码文件如果已经乱码用 VSCode 打开文件后点右下角编码选「通过编码重新打开」再选 GBK 或 GB18030 恢复。总之后端代码统一 UTF-8前端小程序也是 UTF-8只有压缩包本身可能不是先排查这一点。6. 从源码到能演示的完整验证链路四个端一次跑通拿到源码后别急着改代码先按顺序验证四件事确保这套东西能「跑起来给你看」。后端先启动用 IDEA 打开 Spring Boot 工程确认 JDK 8 和 Maven 配置对等依赖下载完后直接运行Application.java——这期间你会等 3 到 10 分钟不等期间最可能挂的是依赖下载超时把 Maven 源换成阿里云镜像就快了。看到Started Application in xx seconds后用浏览器访问http://localhost:8080/api/health检查服务是否存活。后端起来后再动小程序端用微信开发者工具打开小程序目录本地调试时勾选「不校验合法域名」把utils/request.js里的基础 URL 改成http://localhost:8080。正常启动后你会看到登录页、首页的回收品类列表能拉到数据这一步通了说明前后端联调已经走通一个问题链路。最后打开 Vue 管理后台的工程npm install安装依赖后npm run dev登录管理员账号进后台试着改一个回收品类价格并创建一个运费模板再回到小程序端刷新看价格是否更新——到这里三个端的数据流动闭环就跑通了。我每次接到这类项目的第一天都强迫自己走完这条链路再睡。因为源码本身能不能编译、数据库脚本能不能执行、接口能不能通这三件事任何一个出问题都会让你后面的定制无从下手。记得把数据库连接配置application.yml里的url、username、password和自己的 MySQL 环境对齐用 Navicat 或命令导入项目里附带的sql脚本。最怕的是你项目启动成功但查不到数据——大概率是连了别的库或者初始化脚本没跑别问我怎么知道的都是血泪经验。废品回收系统小程序这个方向技术上最大的价值不是功能新颖而是把「线下服务 微信生态 管理后台」这一整套闭环走完整了。你把它跑通一次以后再做同城跑腿、上门维修、家政预约都只是在订单状态机上加节点的问题。希望帮到你——按上面链路走一遍遇到卡住的地方优先查日志而不是盲改代码。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑