1. 先聊清楚:这个"智慧垃圾分类系统"到底在解决什么问题
1.1 毕设题目的常见误区:不是做个增删改查后台
带毕设这几年,十个人里有八个人拿到"基于SpringBoot的智慧垃圾分类与资源循环利用系统"这种题目,第一版设计都长一个样:登录注册、垃圾类别管理、投放记录管理,一共三张表,一个后台加一个简陋的前端页面。做完一看,确实能跑,但本质就是个换皮CRUD,连"智慧"两个字都没挨上。
问题出在题目没有帮你把需求翻译出来。"智慧垃圾分类"拆开看是两件事:一是怎么让垃圾识别更聪明,二是怎么让分类后的垃圾真正流转起来。如果只做信息登记,那小区门口的登记表比你更高效,系统没有任何存在价值。真正值得做的,是把居民、分类设备、回收人员、管理后台连成一条数据闭环,让每一次投放都有识别依据、有积分反馈、有回收去向。这套思路一旦想通,后面所有模块的边界就清楚了,项目本身的含金量也上来了。
所以这篇文章我不打算从一个"官方需求文档"角度讲,而是从我自己折腾这套系统的完整过程讲:需求怎么拆、技术怎么选、表怎么建、识别服务怎么接、开发期踩了哪些坑、最后答辩怎么演示。适合正在做同类毕设的同学,也适合想用SpringBoot快速搭一套带AI能力和业务闭环的管理系统的在职读者。
1.2 我把需求定成了这样:识别、引导、闭环三件事
经过几轮梳理,我把整个系统收敛成三个核心动作:
第一是识别。居民拿手机拍一张垃圾照片,系统自动判断这是什么垃圾、属于可回收物还是有害垃圾,并给出对应的投放建议。这是系统的"门面功能",也是答辩时最能打的部分。第二是引导。识别结果不能只停留在"告诉用户答案",还要落到行为上:用户按建议完成投放,系统记录投放行为、发放积分,连续分类正确还能获得额外奖励,把环保行为变成有即时反馈的激励。第三是闭环。可回收物不能只被登记,用户应当能发起回收申请,管理员接单、上门收运、按重量结算积分,形成"分类—投放—回收—积分—兑换"的资源循环利用闭环。
这三个动作对应到系统里就三块大模块:识别模块、投放与积分模块、回收流转模块,再加上用户/管理员/统计报表这些支撑性功能。答辩评分点和项目亮点也藏在这条闭环里:识别模块体现"智慧",投放与积分体现"分类",回收流转体现"资源循环利用"。
2. 技术选型复盘:SpringBoot之外的每一层为什么这么定
2.1 后端与数据库的版本取舍
后端框架没什么悬念,题目指定了SpringBoot,这本身也是当前Java生态里最适合快速交付的选择。版本上我建议老老实实用2.7.x配合JDK8,不折腾。原因很简单:绝大多数云服务器、教材、网上教程、甚至答辩评委的认知基线都停留在这一代,出了问题随便搜都能搜到解决方案。如果你用SpringBoot 3.5.x,必须配JDK17甚至21,一些第三方库(比如某些老版本的验证码组件、逆向工程插件)会直接不支持,转换成本远超那点性能提升。我自己试过把一个2.6项目升级到3.x,光是javax到jakarta的包名替换就改了几十个文件,中间还有一批反射工具类失效,完全不值得在毕设阶段硬刚。
数据库用MySQL 8.0,本地开发配数据库是刚需,关键词里经常出现"springboot yml 随机端口""springboot 数据库驱动?参数"这类问题,也都是围绕这一层出现的。ORM层我选了MyBatis-Plus而不是原生MyBatis,理由是分页、条件构造器、自动填充这些功能确实省时间。注意MyBatis-Plus的3.5.3版本之后分页插件做了一次整合,写法从new PaginationInnerInterceptor()到通过MybatisPlusInterceptor注册bean,这个后面专门说,因为踩坑概率极高。
2.2 识别能力选型:API调用还是本地模型
垃圾识别是整个系统里唯一带有"智能"字样的部分,选型路径有两条:调用现成的云API,或者本地部署一个视觉模型。我的建议很明确:毕设优先走API接口方案,本地模型作为加分项放在演示环境里。
云API方式,国内主流的云平台都有垃圾分类或通用图像识别接口,注册后拿到AppKey/SecretKey,通过HTTP调用即可。优点是接入快、识别准确率高、不需要显卡,缺点是部分接口需要实名认证和开通付费,但通常有免费调用额度,做一个演示项目绰绰有余。接入后你需要做的封装很简单:一个HTTPClient工具类负责签名与请求,一个GarbageRecognitionService负责拼接参数、解析返回的categoryName和confidence,再通过一个映射函数把模型输出映射成你数据库里的garbage_category表ID。
本地模型方式,一般是YOLOv5或YOLOv8训练的自定义数据集,识别速度不错但模型文件少说几十MB,还要GPU才跑得动推理。没有独立显卡的笔记本跑起来会卡顿,答辩演示时容易翻车。更稳妥的做法是:把模型打包成一个独立的Python FastAPI服务,SpringBoot通过RestTemplate去调用它,两边解耦,本地模型挂了系统还能走API降级逻辑。
2.3 前端形态与认证方案
前端我用的是典型的"SpringBoot + Vue前后端分离"组合,Vue3 + Element Plus + ECharts,路由用Vue Router,状态用Pinia。相比传统Thymeleaf模板方案,前后端分离的好处是业务边界清晰,接口可以直接给多个端复用;代价是跨域和登录态处理要多花点心思。跨域问题我在后端配置了一个WebMvcConfigurer,统一加CorsRegistry,其他没有任何技巧,就是别漏了allowCredentials(true)配合前端withCredentials,否则登录Cookie带不过去。
认证方案没有引入Spring Security全家桶,用的是轻量级JWT拦截器方案。原因不是Security不好,而是它那套过滤器链和配置体系对新手太不友好,出个异常都要查半天。JWT方案自己实现不过几十行代码:登录成功后签发token、前端存localStorage、后端写一个HandlerInterceptor放行登录和放行白名单路径,其余请求校验token并解析出userId放入ThreadLocal。这套方案在答辩时也更好讲,评委问"登录怎么做的"你能从token生成说到拦截验证,逻辑完整。
3. 核心模块拆解与数据库建模:回收闭环是怎么落到表上的
3.1 三个端口的边界划分
系统从使用角色上分成三个端口:居民端、管理端、识别服务端。居民端面向普通用户,功能包括垃圾拍照识别、投放记录查看、积分明细、回收申请、积分商城兑换;管理端面向环卫管理员或物业人员,功能包括垃圾类别维护、投放记录审核、回收订单派单与结算、运营数据统计;识别服务端是一个内部服务,被居民端和管理端共享调用,承载图像识别与投放建议生成。
这三个端口的边界划分很重要,因为后面所有Controller的职责都跟着它走。居民端接口命名用/api/user/**,管理端用/api/admin/**,识别服务用/api/recognition/**。管理端接口在拦截器里额外校验role字段,普通用户访问直接返回403。这样做的好处是权限逻辑集中、接口文档好写、答辩时画架构图也一目了然。
3.2 关键表设计与状态流转
数据库层面我设计了七张核心表,这里只挑关键的几张说。user表在主键、用户名、密码之外,增加了points字段存放总积分,role字段区分普通用户和管理员;garbage_category表存垃圾类别,包括category_name(可回收物/有害垃圾/厨余垃圾/其他垃圾)、is_recyclable、icon_url、description,这是识别结果映射的目标表。
recognition_record表记录每一次识别行为,是"智慧"二字的直接体现。字段有user_id、image_url、predicted_category(模型输出的类别)、actual_category(用户实际投放的类别,管理员可修正)、is_correct(是否分类正确)、confidence(识别置信度)、status(待投放/已投放/已纠正)。这张表既支撑了识别历史页面,又能统计分类准确率,一举两得。
recycling_order表承载资源循环利用流程,状态流转是这套系统的业务精髓:用户提交回收申请→状态为PENDING→管理员接单→ACCEPTED→上门收运完成→COMPLETED→按重量结算积分→SETTLED,过程中还可以有REJECTED(驳回)状态。这个状态机在代码里用常量类定义,每个状态变更都写一条point_record,保证积分流水可追溯。
3.3 资源循环利用的"积分-回收-结算"链路
资源循环利用这块,最容易做成"又一个增删改查",所以我把重点放在积分结算规则上。规则不能写得含糊,否则到时候你没法解释业务逻辑。我是这样定的:识别正确并完成投放,奖励10积分;用户通过回收服务成功交付可回收物,按重量 * 单价结算积分,比如1公斤废纸20积分、1公斤塑料瓶30积分;积分可以在商城兑换垃圾袋、洗手液等小商品,兑换操作扣减积分并生成积分明细。
这套规则落到数据库就是一张point_record表,字段包括user_id、points(正负值)、change_type(投放奖励/回收结算/商城兑换)、biz_id(关联业务ID)、create_time。所有积分变动必须在同一个事务里完成:投放记录写入成功,才发积分;回收订单状态变成SETTLED,才做积分结算,绝不允许出现"订单已完成但积分没到账"的脏数据。这也是答辩时很容易被追问的事务一致性问题,后面在踩坑部分再展开。
4. 垃圾分类识别接入实战:从上传图片到返回投放建议
4.1 识别服务的接口封装链路
识别模块的完整调用链路是这样的:前端用Element Plus的el-upload组件把图片POST到后端/api/recognition/upload接口,后端先做文件校验(大小限制5MB以内、只允许jpg/png格式),然后存储图片到本地指定目录并返回url;接着调用GarbageRecognitionService.recognize(imageUrl),内部再去请求云API或本地模型服务,拿到原始返回结果后做归一化处理。这个过程有两个细节容易踩坑:一是图片存储路径不能放在resources目录下,因为打包成jar后resources是只读的,存在本地磁盘指定目录才不会在重启后丢失;二是识别服务返回的类别名称可能是"塑料瓶""可乐罐"这类细粒度表述,要做一个映射表对应到数据库里的四大分类,这一步在CategoryMapper里用一个switch做正则归类。
识别接口的超时时间我设置的是15秒。云端API首次调用可能要建立连接、计算签名,模型服务加载也要时间,默认的几秒超时在弱网环境下几乎必挂,所以建议在RestTemplate的配置里显式设置connectTimeout和readTimeout,不能只靠前端timeout属性兜底。
4.2 同步调用还是异步任务:超时与排队问题的处理
识别接口到底同步返回还是异步轮询,是这个模块最纠结的设计点。我最终采用了"异步识别+前端轮询"方案,原因是实测发现本地YOLOv5模型首次推理需要加载权重文件,耗时可能长达3~5秒,如果让前端一直同步等待,用户会以为页面卡死了;而云API虽然通常2秒内返回,但偶尔会有网络抖动和排队延迟。异步方案的处理逻辑是:POST /api/recognition/upload接口只做图片入库与任务创建,返回一个taskId;后端在RecognitionTaskService里用Spring的@Async注解异步执行识别,完成后更新recognition_record表的status与结果字段;前端拿到taskId后每2秒调用GET /api/recognition/result?taskId=xx查询结果。
如果不想引入消息队列,这套基于数据库状态+定时轮询的方案最务实。同时我在异步任务里加了try-catch,失败时把status更新为FAILED,并记录错误原因。前端查询到FAILED就提示"识别失败,请重试"。这个容错在答辩时值得主动提,评委一听就知道你想过生产环境的问题,而不仅仅是"能跑就行"。
4.3 识别结果如何反向校正业务数据
识别完成不等于业务结束,还有一个很多人忽略的动作:让用户确认或让管理员纠正。用户上传照片识别后,页面会显示"识别为可回收物,建议投递至蓝色垃圾桶",这时候用户可以选择"确认投放"或"识别错误"。如果用户点了"识别错误",系统弹出一个垃圾类别手工选择框,把用户实际选择的类别更新到actual_category字段,同时is_correct置为0。
这个反向校正机制有三个价值:第一,它让识别准确率有了真实数据支撑,而不是只看模型测试集;第二,管理员后台能看到所有is_correct=0的记录,识别错误的图片全部拉取出来,形成一份"易混淆垃圾"清单,后续可以人工分析或重新标注训练;第三,答辩时当你展示"系统分类准确率90%+"这个指标时,你能说清楚这个统计口径是来自用户实际投放反馈,而不是模型自己的置信度,可信度高得多。我还给每天统计了一张报表,显示各类垃圾的识别正确率、失败率、平均置信度,配合ECharts折线图放在管理首页。
5. SpringBoot开发期的踩坑清单:配置、分页、安全过滤与部署
5.1 配置文件里最容易被忽略的几个点
开发这套系统的过程中,application.yml是我改得最多的文件。几个印象深刻的点值得单独说。
第一个是MyBatis-Plus的逻辑删除配置。我给大部分表设计了deleted字段,逻辑删除后不能再用@TableLogic以外的查询绕过去,否则会被"删不掉的数据"坑一晚上。第二个是日期格式化问题。后端返回的LocalDateTime默认是数组格式(yyyy,MM,dd,HH,mm,ss),前端除非做序列化处理,否则根本没法用。我在配置文件里加了一个Jackson的spring.jackson.date-format和time-zone设置,数据库时间才和本地时间对齐。第三个是随机端口问题。有些人喜欢在yml里写server.port: 0让端口随机分配,这在本地单实例没问题,但部署到服务器后你就永远不知道服务起了哪个端口,调试成本陡增。毕设项目老老实实固定一个端口,比如8080。
5.2 MyBatis分页插件与多表查询的冲突
分页这块我踩过一个非常典型的坑。我的投放记录列表需要联表查询:recognition_record表关联user表、garbage_category表,把用户名和类别名称一起查出来。第一版直接在service层用Page<UserDTO>接收,然后用自定义SQL进行LEFT JOIN,结果发现分页后的total数完全不对,明明有200条记录却只显示一页。
排查到最后,根因是MyBatis-Plus分页插件在遇到LEFT JOIN时,自动生成的count SQL把主表连带子查询一起count了,导致总数错误。解决方式是在分页插件的配置里开启optimizeJoin(旧版本叫optimizeCountSql),让插件尝试优化count语句;另外,对于这种复杂联表,我最后放弃了Page<UserDTO>接收返回结果的写法,改成分页查询主表ID集合,再用IN查询关联数据手动组装,虽然代码多一些,但结果稳定可控。这也提醒各位:分页插件不是万能的,一旦联表变复杂,老老实实分两步查。
5.3 全局过滤器处理上传文件时的XSS问题
安全这块不一定要做得多深,但XSS防护是评委和面试官很喜欢问的点。我的实现思路是:写一个全局OncePerRequestFilter,放行文件上传请求,对其他请求做参数清洗。在JSON请求体场景下,直接包装request.getInputStream()重新读一遍,用HTML标签正则匹配,把<script>等危险内容替换为转义字符。
这里有个坑:如果你不加判断,过滤器会把上传文件时的multipart/form-data请求也当文本清洗,直接导致文件内容被转义或请求体无法读取。我的解决方式是先判断Content-Type,如果是multipart/form-data就直接放行,因为文件内容的XSS风险应该由文件服务环节单独处理——图片格式校验和白名单已经能挡住绝大多数攻击。另外,过滤器里做JSON重读时,记得用ContentCachingRequestWrapper或手动缓存输入流,否则同一个输入流被读取两次后第二次就读不到数据了,接口会报"Request body missing"。
5.4 从本地到服务器:Docker部署与容器化踩坑
本地跑得再欢,部署到服务器才是一个毕设项目真正完整的最后一环。我的部署方案是三个容器:MySQL容器、后端SpringBoot容器、前端Nginx容器,用docker-compose.yml编排。后端Dockerfile用了多阶段构建,第一阶段用Maven镜像编译打包,第二阶段用openjdk:8-jdk-alpine作为运行镜像,只拷贝jar包,镜像体积能压到150MB左右。
部署阶段有个高频坑:后端容器连接MySQL时用localhost或127.0.0.1会报Connection refused,因为容器之间是独立网络,必须用docker-compose里定义的服务名(比如mysql)作为数据库地址。这个错误概率极高,我见过太多人卡在这一步。另一个坑是服务器防火墙只开了80端口但没开8080,导致后端接口外部访问不通,教训是前端请求全部走Nginx反向代理到后端的/api路径,这样只需暴露80端口就够了,也更安全。容器重启策略我设置成restart: unless-stopped,防止服务器重启后服务全挂。
6. 答辩与演示:让系统在5分钟内被看懂
6.1 演示路径:从投放到回收的完整闭环
演示环节最忌讳照着页面从上往下念功能清单,评委听三分钟就走神了。我建议按业务闭环来讲,让评委看到一条完整的故事线。我的五步演示设计供参考:第一步,打开居民端,展示垃圾拍照识别,上传一张塑料瓶图片,系统返回"可回收物"和投放建议,页面同时显示识别置信度;第二步,点击"确认投放",展示积分到账的实时反馈和积分明细页;第三步,打开垃圾回收申请页,提交1.5公斤废纸回收申请;第四步,切换到管理员端,接单、完成收运、按重量结算积分;第五步,切回统计面板,展示今日投放量、分类准确率、可回收物回收量趋势。
这条路径刚好把系统所有核心模块走了一遍,每一步都有数据变化支撑,评委能直观感受到"识别→分类→投放→积分→回收→结算"是一个有机整体,而不是五个孤立页面。
6.2 评委常问的几个问题与准备方法
有几个问题几乎必问,提前准备好答案能避免现场卡壳。最常问的是"识别准确率到底多少,数据怎么来的"——不要只说"很高",要具体回答我们后台有用户投放反馈校正机制,系统根据用户确认的正确投放记录计算实时准确率,并展示统计页面上的数据。第二个常见问题是"积分有什么用,会不会被刷分"——回答要突出两点:积分只能通过投放和回收获得,兑换商品后扣减并在积分明细中留痕;如果识别连续错误,系统限制当天投放积分奖励次数,防止恶意刷分。第三个是"为什么选这个架构"——从SpringBoot快速开发、前后端分离解耦、识别服务独立部署等角度回答,不要只说"大家都用SpringBoot"。
还有一个小技巧:答辩前预置好演示数据,别在现场临时注册账号、传图片、等识别。我在本地环境准备了一个演示账号和5张常见垃圾测试图,确保演示流程一次成功。现场翻车往往不是因为系统不行,而是流程太长容易点错。
6.3 后续扩展方向与我的个人体会
系统做到这个程度已经能应付答辩,但如果想把它做成真正的"智慧"项目,还有几条清晰的扩展路径。一是把统计模块做成实时大屏,接入投放点称重设备后,后端通过MQTT接收重量事件,自动闭环投放记录;二是给盲识别添加自然语言交互,用户可以直接问"过期药品扔哪个桶",由大模型接口生成答复,这比固定答案列表体验好得多;三是把积分系统接入真实的兑换供应链,前端商城对接商品库存与发货流程,形成完整的商业闭环。
最后说点个人体会。这套系统最核心的价值不在代码量,而在你能否把"智慧"和"资源循环利用"这两个抽象概念变成可运行、可验证、可解释的产品逻辑。千万不要为了炫技堆技术,识别模块调用API也好、部署用Docker也好,都要服务于"让分类更准确、让回收更顺畅"这条主线。我实际做完的最大收获是:一个毕设项目的答辩成败,80%取决于需求拆解是否清晰,剩下才看代码实现。把业务闭环想明白,代码怎么写都不回跑偏,评委问什么你都能从容答上来。