简介压缩包内是一套基于Spring Boot与Vue.js开发的废品回收系统小程序完整源码面向Web前后端开发者及相关课程设计、毕业设计人群覆盖从源码到部署说明的完整链路。系统以后端Java处理业务逻辑、前端Vue组件提供交互界面涵盖用户身份验证、废品类型分类、回收订单处理与管理等功能模块适合作为课设项目或前后端整合的入门实践。资源共37个文件包含10个Java后端源文件、4个CSS样式、4个JS脚本及4个map调试映射以及HTML页面、xml/yml工程配置、字体图标、JPG图片和docx/pdf说明文档压缩包大小约2.87MB。其中配置说明PDF与必读推荐文档配合src、pom.xml、main等目录结构可帮助读者快速掌握Spring Boot项目的组织方式和启动配置减少环境搭建踩坑。目前已有20人学习下载既可学习Vue与Spring Boot的协同开发也可借鉴废品回收业务流程与接口设计适合需要完整源码参考的开发者。1. 废品回收系统小程序下载源码不等于能跑先看懂这套三端闭环做废品回收这行难点从来不在“收废品”本身而在预约、称重、结算、提现这条链路的线上化。这套源码给你的是微信小程序端 Vue 管理后台 SpringBoot 后端的三端工程跑起来就是一个最小可用的回收平台用户在小程序里预约上门回收骑手端接单、上门称重、录入品类和重量系统按单价自动算金额进用户余额用户提现走后台审核。适合两类人一是手里有回收业务、想低成本验证线上化流程的从业者二是想拿“小程序 Vue SpringBoot”完整项目练手或做毕设的开发者。但我不建议你拿到 zip 就急着解压导入 IDE先把三端关系和数据表看懂后面联调能少踩一半的坑。2. 工程拆包三端怎么组织、数据表怎么设计、启动顺序谁先谁后2.1 解压后的目录结构与三端职责先别管代码细节把 zip 解压后先看一眼顶层目录。正常这类工程会拆成三个子工程加一份数据库脚本我拿到手的这个包结构基本是recycling-miniapp微信小程序、recycling-adminVue 管理后台、recycling-serverSpringBoot 后端外加一个sql目录存放初始化脚本。有些打包的人习惯把前后端塞进同一个目录但只要代码不是乱到连pom.xml都找不到这个结构就算正常。三个端的分工很清晰小程序端面向 C 端用户承担注册登录、预约回收、订单跟踪、余额提现这些操作管理后台面向运营和骑手处理品类管理、订单派单、称重录入、提现审核SpringBoot 后端是唯一的数据读写入口所有业务规则都收敛在 service 层。别想着在小程序里直接改数据库这套架构里小程序和后端之间只走 HTTPS JSON管理后台也一样没有任何一端能绕过后端直连数据库。启动顺序建议是先导入数据库脚本再起后端确认 8080 端口能通最后跑小程序和后台。你要是先把小程序跑起来会发现所有请求都在报request:fail这是正常的因为后端还没起。小程序开发工具里那个“不校验合法域名”的开关记得打开否则连本地后端都会因为域名白名单被拦。2.2 核心数据表用户、订单、品类、提现四张表足够撑起业务数据库脚本是整套系统的地基我建议你打开 SQL 文件重点看这四张表其他的表大多是扩展或日志不影响主线。用户表user至少得有openid、nickname、phone、balance字段其中openid是微信登录引起来的唯一标识订单表recycle_order要有关键的order_status、user_id、courier_id、category_id、weight、amount品类表category存回收品类名称和单价提现表withdraw记录用户提现申请和审核状态。订单状态是整套系统的核心状态机一般用数字表示0待接单1已接单/待上门2已完成待确认骑手称重后3已确认入账用户确认金额后4已取消。状态流转靠后端 service 方法强校验比如只有状态为 1 的订单才能执行“录入称重”操作只有状态为 2 的订单用户端才能确认金额。你不要小看这个状态机后期所有的 bug 排查几乎都在围绕 state 字段做文章。品类表里单价字段要注意精度问题建议用DECIMAL(10,2)别用FLOAT否则金额计算会出现 0.1 0.2 不等于 0.3 这种经典翻车。用户余额字段同理涉及钱的字段全部用 decimalJava 侧对应BigDecimal这一点你能少不少麻烦。2.3 三端如何联调接口前缀、Token 传递与本地网络联调时最容易懵的是三端之间地址怎么配。后端接口统一走/api前缀小程序端在request/request.js里配baseUrl管理后台在.env.development里配VUE_APP_BASE_URL。本地联调时管理后台跑在 9527 端口Vue 默认后端跑 8080中间没有代理的话管理后台请求后端是跨域的。解决跨域有两条路后端加CrossOrigin或者全局WebMvcConfigurer配置 CORS另一个思路是管理后台用 devServer 的 proxy 把/api代理到http://localhost:8080。我一般用后者因为上线后 Nginx 也要做同样的反向代理本地和线上行为一致不用改代码。小程序端没有跨域问题但本地联调时开发工具必须勾选“不校验合法域名”而且baseUrl里的 IP 得是你电脑在局域网里的 IP别写localhost手机预览时localhost指向的是手机自己连不通。Token 传递链路是这样的用户在小程序端通过 wx.login 拿到 code后端拿 code 换 openid生成 JWT 返回给小程序小程序端把 token 存到wx.setStorageSync每次请求在 header 里带Authorization: Bearer token管理后台是账号密码登录后端生成另一套 token同样放在请求头。两套 token 不要混用后端拦截器按接口前缀区分/api/wx/**走小程序 token 校验/api/admin/**走后台 token 校验。3. SpringBoot 后端实战微信登录、订单状态机与金额计算的正确写法3.1 微信登录接口code2Session 与 JWT 签发小程序登录不走账号密码而是用微信的wx.login()获取临时 code后端拿着 code 去微信接口换 openid 和 session_key。这是小程序登录的政治正确做法后端不能自己伪造 openid。PostMapping(/api/wx/login) public Result login(RequestBody LoginRequest req) { // 1. 用 code 换 openid注意这里必须走微信官方接口 String url https://api.weixin.qq.com/sns/jscode2session; MapString, String params new HashMap(); params.put(appid, appId); params.put(secret, appSecret); params.put(js_code, req.getCode()); params.put(grant_type, authorization_code); String resp HttpUtil.get(url, params); JSONObject json JSONUtil.parseObj(resp); String openid json.getStr(openid); // 2. 查库没有就自动注册 User user userMapper.selectOne(new LambdaQueryWrapperUser() .eq(User::getOpenid, openid)); if (user null) { user new User(); user.setOpenid(openid); user.setBalance(new BigDecimal(0)); userMapper.insert(user); } // 3. 签发 JWT有效期建议 7 天 String token JwtUtil.createToken(user.getId(), user.getOpenid(), 7 * 24 * 3600); return Result.success(new LoginResponse(token, user.getId())); }这里我直接用了 Hutool 的HttpUtil.get和JSONUtil.parseObj省去写原生 HttpURLConnection 的模板代码。微信返回的openid是用户唯一标识同一个用户每次登录 openid 不变所以你不用反复注册新用户。appId和appSecret放到application.yml里配置千万别写死在代码里上传到公开仓库那等于把用户登录接口交给别人了。JWT 生成后一般放在请求头Authorization里后端写一个拦截器解析 token 并把 userId 放到 ThreadLocal 里供后续业务使用。拦截器里要处理 token 过期建议统一返回 401 状态码小程序端收到 401 后在请求封装里统一跳转登录页不在业务代码里一个个处理。3.2 订单状态机防止用户和骑手同时改单订单是整个系统里并发风险最高的地方。用户端有“取消订单”骑手端有“接单”“录入重量”这些操作可能发生在同一秒。后端不能只靠 if 判断当前状态必须加乐观锁或状态条件更新。Transactional public boolean confirmOrder(Long orderId, Long userId) { // 1. 条件更新只有 status 2 的订单才能被用户确认 int updated orderMapper.update(null, new LambdaUpdateWrapperRecycleOrder() .eq(RecycleOrder::getId, orderId) .eq(RecycleOrder::getStatus, 2) .set(RecycleOrder::getStatus, 3) .set(RecycleOrder::getConfirmTime, LocalDateTime.now())); if (updated 0) { throw new BizException(订单状态异常刷新后重试); } // 2. 订单金额入用户余额 RecycleOrder order orderMapper.selectById(orderId); userMapper.update(null, new LambdaUpdateWrapperUser() .eq(User::getId, userId) .setSql(balance balance order.getAmount())); return true; }关键在第一步的update返回条数更新 0 条说明当前状态不是 2直接抛异常。这就是乐观锁的思路——用状态字段作为版本条件而不是先 select 再 update。这样即使骑手和用户同时操作数据库层面也只可能有一个成功。入余额那里用setSql(balance balance ...)做的是数据库原子自增避免你先查余额、加算、再更新的中间态丢数据。这里金额虽然是从订单里读出来的但你得明白真正可信的金额是订单表里的amount不是骑手录入的重量重新算的。3.3 金额计算重量、单价、精度三位一体的坑金额计算逻辑在“骑手录入称重数据”这个接口里前端传入categoryId和weight后端根据品类单价算出预估金额。但有一点值得注意单价不是从数据库随便捞一个就完事回收行业的价格波动很大所以这套系统的单价应该走“今日价”逻辑——比如品类表里有个current_price字段后台可以每天调整。public BigDecimal calcAmount(Long categoryId, BigDecimal weight) { Category category categoryMapper.selectById(categoryId); if (category null) { throw new BizException(品类不存在); } BigDecimal price category.getCurrentPrice(); BigDecimal amount price.multiply(weight).setScale(2, RoundingMode.HALF_UP); return amount; }这里有两个隐蔽坑。第一个是BigDecimal必须用String构造new BigDecimal(0.1f)会有浮点误差数据库设计时单价字段要是DECIMALMyBatis-Plus 映射成 BigDecimal 一般没问题但你自己写测试用例时别用 double 直接怼。第二个是重量单位小程序端录入的 weight 是公斤还是斤必须前后端统一这个系统的约定是公斤但骑手端录入时容易拿市斤习惯来填后台展示和结算就会差一倍这种问题属于业务约定问题不是技术问题。multiply之后必须setScale(2)保留两位小数四舍五入规则用HALF_UP这样用户看到的金额和数据库存的金额一致不会出现 9.999 这种显示问题。建议把这段计算逻辑单独抽成 service 方法因为后面订单确认、对账单导出可能都要复用同一套计算。4. 前端双端落地小程序预约流与 Vue 管理后台的骑手派单4.1 小程序端页面结构与预约逻辑小程序前端一般基于微信原生框架开发也可能用 uni-app这个包的小程序端是原生写法页面放在pages目录下pages/index/index是首页展示回收品类和预约入口pages/order/confirm是预约确认页选品类、填地址、约时间pages/order/list是订单列表pages/user/index是个人中心含余额和提现入口。预约流程的核心是把三个输入串起来品类、地址、时间。品类来自后端接口/api/category/list预约提交走POST /api/order/create。预约提交前一定要校验地址是否完整小程序端用wx.chooseLocation获取经纬度和地址文本后端再结合地图逆地理编码校验地址真实有效——如果只是存一个用户手打的地址骑手上门找不到地方订单就会在待接单状态卡死。// pages/order/confirm.js 中的提交预约逻辑 submitOrder() { const category this.data.selectedCategory const address this.data.address const appointTime this.data.appointTime if (!category.id || !address.detail || !appointTime) { wx.showToast({ title: 请补全回收信息, icon: none }) return } wx.request({ url: ${app.baseUrl}/api/order/create, method: POST, header: { Authorization: Bearer ${wx.getStorageSync(token)} }, data: { categoryId: category.id, address: address.detail, latitude: address.latitude, longitude: address.longitude, appointTime }, success: (res) { if (res.data.code 0) { wx.redirectTo({ url: /pages/order/detail?id${res.data.data.id} }) } else { wx.showToast({ title: res.data.msg, icon: none }) } } }) }这里注意提交按钮的 loading 状态一定要做防止用户重复点击生成两笔相同预约。我当时踩过这个坑用户连续点两次“提交”结果订单表里出现两笔一模一样的记录骑手只接了一单另一单在“待接单”里躺到超时。后来在data里加了一个submitting布尔位提交时直接 return这才解决。时间选择器建议用picker modedate和picker modetime组合后端在创建订单时校验appointTime必须大于当前时间防止预约过去的时间。4.2 管理后台Vue Element Plus 的订单派单与看板管理后台基于 Vue 2 或 Vue 3 Element Plus核心页面有三个订单管理、品类管理、提现审核。订单管理页的关键操作是派单管理员把待接单的订单分配给某个骑手。派单的本质就是更新订单表的courier_id和状态从 0 到 1。// views/order/index.vue 派单方法 async assignOrder(row) { const courierId await this.selectCourier() // 弹窗选择骑手 if (!courierId) return const res await api.assignOrder({ orderId: row.id, courierId: courierId }) if (res.data.code 0) { this.$message.success(派单成功) this.fetchOrderList() } else { this.$message.error(res.data.msg) } }派单接口在后端对应的逻辑是校验订单状态为 0校验骑手存在且非禁用更新courier_id和status1。有个细节骑手侧也要能在小程序端看到“我的待办订单”所以courier_id对骑手端必须可见派单后骑手端的订单列表要能自动刷新。品类管理页就是一个标准 CRUD新增品类时设名称和单价编辑时改单价。这里单价会直接影响用户端预估金额所以后台修改品类价格时要记录价格变更日志运营同学可能需要追溯“上周报纸多少钱一斤”。日志表可以设计成category_price_log字段包括category_id、old_price、new_price、operator_id、create_time不算复杂但实际运营中非常有用。4.3 前后端接口联调字段命名与时间格式化联调阶段最烦的问题不是功能不通而是字段对不上。后端实体类字段一般是驼峰命名appointTime小程序端 JS 也习惯用驼峰这个问题不大但如果是后端返回create_time这种下划线格式小程序端就只能用res.data.data.create_time访问代码风格会很割裂。这套系统里 MyBatis-Plus 开了map-underscore-to-camel-case: true返回 JSON 会转成驼峰你拿到手后别去改这个配置保持前后端都用驼峰最省事。时间格式化是另一个高频翻车点。Java 后端返回LocalDateTime默认序列化成2024-01-15T10:30:00中间带个 T小程序端展示的时候不方便。建议在application.yml里配spring.jackson.date-format: yyyy-MM-dd HH:mm:ss和time-zone: GMT8让后端统一输出格式化字符串前端不用做二次处理。联调时我习惯用 Charles 抓包看真实请求和响应。小程序开发工具自带 Network 面板能看到请求但手机真机预览时看不到所以抓包是排查线上问题的传统手艺。装好 Charles、配置好手机代理后最关键一步是安装 Charles 的 SSL 证书到手机并信任它否则只能看到加密流量看不到具体报文。5. 避坑指南这份源码跑起来会遇到的 6 个经典问题5.1 微信登录报errcode: 40163现象小程序调wx.login拿 code后端调微信接口返回errcode: 40163code 已被使用。原因code 是一次性的用一次就失效。前端在并发场景下重复调用登录接口或者后端登录接口里面重复消费了同一个 code。解决后端登录接口用 code 换 openid 后立即把 code 标记为已使用可以用 Redis 或 DB 记录前端也要做防重入登录按钮 loading 期间禁止二次点击。我处理时是后端加了wxLoginCache同一 code 只能成功消费一次第二次直接报“请重新登录”。5.2 小程序请求报url not in domain list现象开发工具点预览时请求报错提示xxx not in domain list。原因小程序正式环境的 request 域名必须在微信公众平台配置白名单本地联调时 IP 或 localhost 不在白名单里。解决开发阶段在微信开发者工具右上角“详情-本地设置”勾选“不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书”。真机预览时也要保持这个选项或者用“真机调试”模式。上线前再把baseUrl改成正式 HTTPS 域名并去公众平台配置 request 合法域名。5.3 用户提现成功后余额没减少现象提现审核通过后用户提现金额已经从银行卡扣走了但小程序端余额没变。原因提现审核通过和余额扣减不在同一个事务里审核更新了withdraw.status但没同步执行user.balance的扣减或者两个操作之间有异常被吞掉。解决后端提现审核通过方法上加Transactional把“更新提现状态”和“扣减用户余额”放在同一个事务。但注意扣减余额必须用余额充足校验否则用户余额为负数还能继续提现。顺序是查余额 → 校验 → 扣减 → 更新提现单状态 → 提交事务。5.4 骑手录入重量后用户金额没刷新现象骑手在后台录入重量和品类后用户端订单详情页显示的金额还是 0 或旧金额。原因金额是后端计算并存入订单表的骑手录入后订单详情接口返回的是新值但用户端订单详情页上次请求结果被缓存在小程序全局数据或 Storage 里没有强制刷新。解决小程序端订单详情页onShow里重新请求getOrderDetail不要依赖onLoad只请求一次。onShow每次从别的页面返回都会触发能保证数据最新。再加一个下拉刷新动作让用户手动刷新兜底。5.5 管理后台接口 404现象管理后台登录后所有业务接口都返回 404。原因大概率是后端接口路径前缀不匹配。管理后台请求发到/api/admin/order/list后端 Controller 的 RequestMapping 写成了/api/order/list路径没对上。解决方案统一后端所有 admin 接口的RequestMapping前缀为/api/admin小程序端接口前缀为/api/wx公共接口放/api/common。顺手在管理后台的request.js里把 baseURL 和后端 context-path 核对一遍这种问题在前后端分离项目里属于最常见的低级错误。5.6 Vue 打包后放到 SpringBoot 出现白屏现象把管理后台npm run build的产物到 SpringBoot 的static目录下访问首页白屏。原因Vue 打包后的资源路径是绝对路径/assets/...SpringBoot 的静态资源根路径不一定匹配。另外 Vue Router 如果是 history 模式后端没有配置 404 fallback 到index.html刷新或直接访问子路由就白屏。解决Vue 项目vue.config.js里publicPath设置为./相对路径Router 切回hash模式SpringBoot 加一个WebMvcConfigurer把非静态资源的请求转发到index.html。这三个都做了打包部署才算真正闭环。6. 进阶技巧搞定支付回调与假支付开关这套系统才真正能上线小程序端预约回收按理说应该是“完成后结算”而不是预付所以支付功能在这套系统里不是核心链路。但用户余额提现是涉及钱的而余额的充值和回收结算之间必须有支付回调来确认资金到账否则账对不上。这套系统通常会预留一个mockPay开关在application.yml里配置pay.mock: true打开后用户发起支付时后端直接返回支付成功并触发模拟回调。这个开关的目的不是偷懒而是让你在不接真实微信支付商户号的情况下先把整条用户流程跑通——下单、确认、余额入账、提现申请。等真要上线再把开关关掉替换成真实的wx.requestPayment参数签名和回调验签。验证这套系统是否正常我一般会走一遍完整链路小程序登录 → 创建预约单 → 管理后台派单 → 骑手录入重量 → 用户确认金额 → 余额变多 → 发起提现 → 后台审核通过 → 余额扣减。中间任何一环状态不对就去看订单表的status字段落在哪配合日志里打印的参数定位。从那以后我每次接手这类三端项目都先做两件事第一打开数据库脚本和状态机定义把状态流转画成一张草表贴在手边第二确认pay.mock开关和微信 appid 配置是分开的防止有人把测试开关带上生产。这套废品回收系统小程序源码的边界很清晰业务闭环完整、接口规范、代码量不大适合作为你改造自己业务的起点也适合第一次完整跑通“小程序 Vue SpringBoot”三端流程的人。希望帮到你。本文还有配套的精品资源点击获取