拓冰建站拓冰建站
首页 / 资讯中心 / 正文

基于SpringBoot+Vue的旧物回收管理系统:从架构设计到部署实战

最近刚好把一个旧物回收管理系统的项目从零到一完整重做了一遍正好有朋友在问这类基于SpringBootVue的毕设项目怎么做、里面的核心难点在哪里趁这个机会把整个项目的设计思路、技术方案、关键实现和踩坑记录整理出来给正在做同类系统的同学一份可以直接参考的实操手册。基于SpringBootVue的旧物回收管理系统本质上就是一个典型的前后端分离Web应用核心场景是解决闲置物品回收过程中的信息不通、流程混乱、定价不透明、回收效率低等问题把用户提交回收申请、回收员上门收件、质检定价、订单流转、积分/金额结算等环节全部线上化。无论你是用来做毕业设计、课程设计还是想在企业内部搭建一套轻量级的回收管理后台这套系统的架构思路和关键代码都值得花点时间看完。这类项目最容易被忽略的其实是需求分析很多人一上来就建表写接口结果做到一半发现不知道订单状态该怎么流转、回收员端和用户端的数据权限边界在哪。所以我建议先别急着敲代码把整个业务流程拉通一遍再动手后面会省掉大量返工的痛苦。1. 内容整体设计与思路拆解1.1 用户、回收员、管理员三角色的核心诉求在动手之前我先花了三四天把需求盘了一遍。旧物回收管理系统不是简单的增删改查它绕不开三角色配合的问题普通用户C端提交回收申请、查看回收订单进度、确认完成、查看积分或余额、参与社区互动。回收员B端接单、上门回收、物品初检、提交质检结果、维护回收记录。管理员后台运营配置回收类型、回收价格策略、用户管理、回收员审核、订单调度/仲裁、数据报表查看。这里最关键的是“订单”这条主线所有角色动作都围绕订单状态推进。我最终把核心表设计也围绕它们展开这个思路几乎决定了整个项目能不能顺利收官。1.2 前后端分离架构的取舍与理由技术栈选型上我坚持用了SpringBoot Vue。为什么不用传统Thymeleaf模板因为旧物回收管理系统天然带“多端联动”的场景——用户可能用手机网页发起回收请求管理员在PC后台处理业务。如果用服务端渲染前后端代码揉在一起回收员端和用户端如果要拆成不同入口就得在Controller层费劲地做视图切换非常痛苦。前后端分离之后后端只提供纯JSON接口前端可以独立部署在Nginx上后端接口跑在JVM里两边各自横向扩展。开发阶段用Vite代理即可解决跨域生产环境直接反代体感很好。另外Vue生态对移动端适配很好如果你后续要套一个H5或者小程序壳复用度也高。1.3 业务流程闭环设计核心业务流程我画了好几版才定稿。这里用文字描述一遍方便大家理解用户提交回收申请选择物品类型、预约上门时间、填写期望价格管理员或回收员在后台/移动端查看待审核订单回收员接单系统锁定该订单避免多人抢单回收员上门现场初检上传照片和质检说明给出回收预估价用户确认回收价格订单进入结算环节系统计算积分/金额用户可以在提现或商城兑换中使用管理员定时查看回收价格波动、热门类目等报表。围绕这条主线我还加了公告资讯、在线留言、积分商城兑换等辅助功能让系统演示时更有完整度也方便在答辩或汇报时展示“用户留存”方面的思考。2. 核心细节解析与实操要点2.1 SpringBoot后端如何分层最稳一个看似简单的SpringBoot项目如果包结构乱掉后面全是坑。我的习惯是严格按“Controller → Service → Mapper”三层来拆然后额外加一层dto专门放参数接收对象不让前端传参直接绑定实体类。我实际最终采用controller只负责参数校验、调用Service、封装返回结果service业务逻辑层事务注解都加在这里mapperMyBatis-Plus的BaseMapperextendsentity数据库表对应的实体类dto接收前端参数避免直接暴露数据库字段vo返回给前端的视图对象隐藏敏感字段比如密码config放WebMvc配置、跨域配置、MyBatis-Plus分页插件配置等。比如用户提交回收订单时前端传过来的不只是一个订单主表数据还包含物品明细和图片路径。如果在Controller里直接接收entity就得用JsonIgnore来处理密码等字段很容易漏。用DTO接收直接指定Long[] imageIds、ListItemSkuReq items参数结构清晰得多也方便做Validated分组校验。2.2 数据库表设计的关键细节这个项目的表不算特别复杂但有几张表需要仔细思考。我把核心表结构列一下省去冗余字段大家理解设计意图即可。用户表useruser_id、nickname、phone、password、points当前积分、balance当前金额、role区分用户/回收员/管理员、status账号状态、create_time、update_time、deleted关键点role我直接用int存1用户2回收员3管理员。没有设计单独的角色表因为系统体量不大用PreAuthorize(hasRole(ADMIN))就能控制权限不必引入Spring Security的复杂RBAC。回收物品/订单表recycle_orderorder_no业务编号展示用随机生成、user_id、recycler_id、category_id、appointment_time、address、contact_name、contact_phone、estimated_price估价、final_price结算价、status订单状态、remark这里要特别注意订单状态流转要有状态机思想。我是用Integer存状态码然后统一在Service层做一个statusFlow()方法校验是否允许从状态A切到状态B。比如待审核状态不能直接跳到已完成必须经过待上门→已上门→质检中→待确认→已完成。这样即使在极端并发场景下订单状态也不会产生逻辑矛盾。回收品明细/图片存储表因为一个订单可能有多个物品每个物品可能有多张图片所以我把图片独立成表recycle_order_item订单ID、物品名称、分类、预估重量、成色描述、现场照片URL。资讯公告/社区表article用户端动态资讯、公告内容、发布人、状态。这块相对简单但是要注意富文本图片入库前的XSS过滤。有些系统直接用前端富文本提交HTML后端不做清理就存库导致XSS攻击这也是个常考的安全加分点。2.3 权限控制如何利用Spring Security或Interceptor我调研后在项目中最终没有引入完整Spring Security而是基于HandlerInterceptor JWT做了一个轻量级拦截器方案。原因很简单这个项目角色固定、接口数量有限用Spring Security OAuth2那套反而增加学习和部署成本。做法是登录成功后发放JWT token含userId、role默认有效期2小时设置一个较长的refresh token前端请求头带Authorization: Bearer token后端拦截器解析token绑定当前用户到ThreadLocal基于自定义注解RequireRole做接口级权限校验。这里有一个实操教训拦截器里解析token时一定要检查token过期、token被篡改以及数据库里用户是否被禁用这三层检查缺一不可。哪怕JWT本身签名没问题用户被管理员封号后token依然能通过JWT校验所以必须加一层用户状态查询。2.4 价格策略与积分结算的设计旧物回收系统的“价格”不是定死的回收价格会随品类、重量、成色浮动。我是把“回收价格字典”和“订单实际结算价”分开recycle_category表存基础价位比如图书按公斤单价、手机按型号基础价订单提交时前端展示的是预估价由后端根据字典计算回收员上门初检后提交的final_price需要后端二次确认不能只是前端传个数字否则用户随便传个1分钱就能成交这里必须做内部校验比如换新、翻新补贴计算等。结算逻辑上我设计成积分余额双轨确认回收后先把积分发到用户账户积分可以在商城兑换环保用品同时计算一版现金余额用户可以申请提现到微信。这样做的好处是用户在平台内部有消费闭环不至于回收完立即流失。3. 实操过程与核心环节实现3.1 环境准备与项目初始化如果你是从零开始建议环境和版本对齐技术栈推荐版本说明JDK1.8/11如果是SpringBoot 2.xJDK8足够SpringBoot 3.x则必须JDK17SpringBoot2.7.182.x稳定教程多毕设足够用MyBatis-Plus3.5.x简化CRUD神器自带分页插件MySQL5.7 / 8.05.7最兼容8.0注意字符集排序规则Redis5.0用于缓存分类字典、热点数据、验证码存储Vue3.4.x组合式API script setupVite5.x构建工具不要在2025年还用webpack起Vue新项目Element Plus2.x中后台UI框架管理后台可快速搭页面Pinia2.x状态管理替代Vuex初始化时我踩的最大的坑在网络依赖下载。国内环境一定要用镜像源settings.xml或者package.json里配置阿里云镜像/淘宝镜像否则等依赖下载能怀疑人生这里单独提醒一下。3.2 后端关键接口与核心代码实现我把“用户发起回收请求”这个核心接口的代码结构展示一下这是整个系统的业务心脏。Controller层RestController RequestMapping(/api/order) public class RecycleOrderController { Resource private RecycleOrderService recycleOrderService; PostMapping(/submit) public RLong submit(RequestBody Validated RecycleOrderReq req, HttpServletRequest request) { Long userId UserContext.getUserId(); Long orderId recycleOrderService.submitOrder(userId, req); return R.ok(orderId); } }Service层关键业务逻辑Transactional(rollbackFor Exception.class) public Long submitOrder(Long userId, RecycleOrderReq req) { // 1. 生成唯一业务编号 String orderNo generateOrderNo(); // 2. 保存订单主表 RecycleOrder order new RecycleOrder(); order.setOrderNo(orderNo); order.setUserId(userId); order.setCategoryId(req.getCategoryId()); order.setAppointmentTime(req.getAppointmentTime()); order.setAddress(req.getAddress()); order.setContactName(req.getContactName()); order.setContactPhone(req.getContactPhone()); // 预估价计算防止前端乱传 BigDecimal estimate priceService.calcEstimatePrice(req.getCategoryId(), req.getWeight()); order.setEstimatedPrice(estimate); order.setStatus(OrderStatusEnum.WAIT_AUDIT.getCode()); recycleOrderMapper.insert(order); // 3. 保存物品明细 // 4. 保存图片关联 return order.getOrderId(); }关于分布式事务因为单体内项目没有跨服务调用用Transactional就能保证订单主表、明细表、图片关系表要么都成功要么都回滚。这里特别提醒不要在一个事务里做远程调用或者耗时IO否则会锁表。注意上面代码中我用BigDecimal来存金额。这是财务系统的铁律绝不能用double或float否则出现0.10.20.30000000000000004的精度问题结算就乱套了。3.3 前端核心页面与接口联调Vue3前端我采用了如下页面结构views/pc/login.vue登录页views/pc/order/list.vue用户订单列表views/pc/order/apply.vue提交回收申请views/admin/dashboard.vue数据统计views/admin/orderManage.vue订单管理views/recycler/task.vue回收员任务接口封装上我在src/utils/request.js里封装axios实例import axios from axios import { ElMessage } from element-plus import router from /router const request axios.create({ baseURL: import.meta.env.VITE_APP_BASE_API, timeout: 10000, }) request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) request.interceptors.response.use( response { const res response.data if (res.code ! 200) { ElMessage.error(res.msg || 请求失败) return Promise.reject(new Error(res.msg)) } return res }, error { if (error.response?.status 401) { // token失效清空用户信息并跳转登录 localStorage.clear() router.push(/login) } ElMessage.error(error.message || 网络异常) return Promise.reject(error) } ) export default request提示环境变量.env.development和.env.production分别配置VITE_APP_BASE_API开发环境为/api通过Vite proxy代理到后端8080端口。不要在代码里写死IP否则打包后换地址你还要重新改代码发布。3.4 跨域和生产环境部署实操开发环境的跨域在vite.config.js里配置代理即可server: { port: 3000, proxy: { /api: { target: http://localhost:8080, changeOrigin: true, } } }生产环境我会把前端打包后的dist/目录放到Nginx下并配置反向代理server { listen 80; server_name yourdomain.com; root /opt/web/dist; index index.html; location /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; } location / { try_files $uri $uri/ /index.html; } }这就让Vue Router的history模式去掉#号在刷新时不会404后端接口也顺带解决了跨域问题。注意如果后端配置了Spring Security的CorsConfigurationSource要确认没有和Nginx代理起冲突。3.5 回收员端H5页面适配要点系统里回收员的动作非常依赖手机端我直接用Vue3写了一套移动端适配页面。移动端采用viewport适配并配合postcss-px-to-viewport插件自动把px转成vw。注意按钮点击区域不要小于44px否则指纹/手指操作很难受图片上传组件要压缩后上传避免在4G/5G网络下上传慢、失败。图片上传我集成的是阿里云OSS直传模式前端先请求后端获取STS临时凭证然后直接上传到OSS上传完成后把URL回传。这样做的好处是图片扛得住高并发不占后端带宽不过如果你只是毕设/演示用途放在本地磁盘或FastDFS也完全够用不需要额外花钱。3.6 定时清理和过期订单处理这里分享一个容易被忽略的问题用户提交回收申请后如果一直没有人接单预约时间过去之后怎么办我增加了一个定时任务每5分钟扫描一次待审核且超过预约时间2小时的订单自动取消并把库存/额度释放。Component public class OrderTimeoutJob { Scheduled(cron 0 */5 * * * ?) public void timeoutOrder() { // 查询超时订单 // 执行取消动作 // 记录操作日志 } }开启定时任务别忘了在启动类或配置类上加EnableScheduling很多新手在这里漏配导致定时器不触发。4. 常见问题与排查技巧实录4.1 前后端联调时Token失效或用户信息错乱现象用户A登录后访问接口偶尔返回B用户的数据。排查大部分情况是拦截器把ThreadLocal里存的用户信息没有正确清理或清理时机不对。我用的是Servlet的afterCompletion方法里强制remove()同时确认拦截器注册顺序避免被其他过滤器拦截后提前返回。public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { UserContext.clear(); }另外并发场景下ThreadLocal在Tomcat线程池复用时会串数据所以在finally里面清理是铁律。4.2 Vue打包后首页白屏或路由404这个问题我在部署时也遇到过。现象是本地nginx跑npm run dev一切正常但生产环境npm run build之后访问首页白屏或刷新404。排查顺序检查router是不是history模式如果是nginx必须有try_files回退到index.html的逻辑F12看Console如果报Failed to fetch dynamically imported module说明Vue打包后异步组件加载路径不对需要在.env.production设置VITE_BASE_PATH或在vite.config.js里设置base: /如果白屏且没报错检查是否引用了不兼容的浏览器API如Array.prototype.at低版本浏览器不支持也会白屏。4.3 订单状态并发操作怎么防比如回收员在手机端点了“完成回收”用户同时点了“取消订单”两个请求后端的执行顺序不确定就可能出现脏状态。我采用的方法在updateStatus的SQL里加条件UPDATE recycle_order SET status #{newStatus} WHERE order_id #{orderId} AND status #{oldStatus}受影响行数为0说明状态已被其他请求修改此时抛异常提示“订单状态已变更请刷新后重试”。在关键业务操作上加Redis分布式锁如setIfAbsent带过期时间确保同一订单同一时刻只能有一个操作在跑。4.4 Redis缓存穿透的简易方案系统首页需要展示回收品类和基础价格字典。如果每次都查MySQL量不大也还行但如果有并发缓存又会击穿。我的做法查询时先查Redis如果Redis没有但DB有回填缓存如果DB也没有就缓存一个空值并设置短过期时间60秒避免恶意请求打爆数据库。这是一个经典的“缓存穿透”前置处理简洁有效。4.5 打包体积太大首屏加载慢Vue3 Element Plus全量引入的话打包后chunk可能超过1MB秒开就变成白日梦。建议开启按需自动导入。例如Unplugin Vue Components插件结合Element Plus按需引入// vite.config.js import Components from unplugin-vue-components/vite import { ElementPlusResolver } from unplugin-vue-components/resolvers export default { plugins: [ Components({ resolvers: [ElementPlusResolver()] }) ] }这样打包后Vendor会减小到300KB左右配合gzip在Nginx开启后可以降到100KB以内实际体验会好很多。4.6 数据库字段命名踩坑我在做表设计时把description定义成describe结果和MySQL8.0的保留字冲突建表时直接报语法错误。老老实实改用description之后问题解除。建议所有字段尽量避开order、describe、condition、group这类的SQL关键字即使能加反引号也不要自找麻烦。5. 多端接入扩展Web端能放视频、App端适配在这个项目里用户提交图文说明时如果物品比较贵重比如手机、电脑回收平台往往需要视频验机。所以我还做了个简单的视频上传预览功能。这里结合你看到的热搜词“vue播放m3u8”额外分享一下通用经验很多回收管理系统在管理后台都会上传视频培训内容或者用户端会上传验机视频最终播放采用m3u8流媒体格式。前端可以使用hls.js播放video idvideo controls autoplay muted/video script import Hls from hls.js const video document.getElementById(video) if (Hls.isSupported()) { const hls new Hls() hls.loadSource(/test/video/index.m3u8) hls.attachMedia(video) } /script如果你的业务不需要这么重用传统mp4也行。关键还是把视频和图片上传走OSS直传、权限控制好避免用户刷流量。6. 部署上线与答辩亮点整理6.1 最小化部署方案如果你只是毕设演示不搞K8s最稳妥的部署方式是云服务器1台2核4G即可JDK MySQL Redis Nginx后端打成jar包用systemd或nohup守护运行前端npm run build后dist目录扔到Nginx。具体启动命令示例nohup java -jar recycle-admin.jar --spring.profiles.activeprod /data/logs/recycle.log 21 注意给JVM设置初始堆和最大堆比如-Xms256m -Xmx512m避免在2G内存的机器上默认堆大小导致启动时内存溢出。6.2 项目汇报时的加分细节如果是答辩或内部汇报建议重点讲清楚以下几点为什么用BigDecimal而不用double处理金额订单状态机设计如何避免非法流转JWT无状态认证和Spring Security的取舍缓存穿透/击穿的预防方案图片上传OSS直传的架构优势定时取消超时订单的兜底策略。这些点每一条都可能成为评委追问的对象提前写好说明比你临场发挥稳得多。6.3 为什么这个系统很值得做深旧物回收本身是一个很“接地气”的领域但它涉及的业务逻辑并不肤浅。从用户提交回收申请、回收员抢单、物品质检、价格计算、积分发放、后台数据统计形成一个完整的闭环。在这个项目里你可以同时练到SpringBoot全家桶整合、Vue3vite工程化、权限设计、状态机、缓存、定时任务、文件上传、服务器部署。这些技能点几乎是JavaWeb岗位的核心要求做完这个项目简历上可以写的东西会非常扎实。最后再分享一个我的心法不要只把项目当成“作业”做完就放手。哪怕就是这样一个回收系统你在实现过程中遇到的每一个“状态不一致”的问题背后都涉及分布式系统、并发控制、缓存一致性这些大话题。把这些问题一个个弄明白才是做项目最大的收获。我自己做完这个系统之后对SpringBoot自动装配原理、拦截器执行顺序、Nginx部署细节的掌握程度比起光看面试题要深刻得多。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门