
做智能垃圾分类系统这个项目说实话并不是一时头脑发热。去年我家小区开始搞垃圾分类定时定点投放我站在那排分类桶前面看着旁边大爷大妈盯着手里的垃圾袋犹豫半天心里就想这东西看着简单真让你分的时候很多人是懵的。于是我就萌生了自己动手做一个“能给垃圾做智能分类”的系统——用户拍一张照片或者输入一个垃圾名称系统能告诉你它属于可回收、有害、厨余还是其他垃圾最好还能附带投放指南和常见误区提示。整个项目采用SpringBoot Vue这套经典前后端分离技术栈提供完整源码、MySQL数据库脚本和配套开发文档。如果你正在做毕业设计、课程项目或者想给自己简历上加一个完整的全栈项目那这篇文章能帮你省下大量踩坑时间。我会从系统设计思路、数据库建模、后端接口实现、前端页面联调、部署上线几个维度把各个环节的决策逻辑和实操细节全部摊开讲清楚。1. 项目整体设计与技术选型思路1.1 为什么这类系统非常适合SpringBoot Vue先说后台。垃圾分类系统本质上是一个“信息查询 内容管理 用户交互”的业务系统它不涉及高并发、海量数据、复杂计算这些场景核心是业务逻辑清晰、CRUD操作密集、管理端功能完整。SpringBoot在这个场景下面简直是为它量身定做的——启动器机制让你两分钟拉起一个Web项目自动配置把SpringMVC、Jackson、数据源绑定这些琐碎工作全部接管你只需要专注写业务代码。再说前端。垃圾分类系统的用户端要展示分类知识、搜索垃圾名称、查看投放指南管理端要维护垃圾类别和词条数据这种交互不算复杂但页面数量不少。Vue的组件化开发模式很适合这种多页面、多模块的场景。比如垃圾详情卡片、搜索框、分类结果展示、后台表格这些都可以抽成独立组件后续维护某个功能只用改对应组件不会牵连其他页面。这套组合在简历上也好看。SpringBoot常年霸占国内后端岗位需求榜前列Vue在前端框架里的生态成熟度也是第一梯队。用它们做一个完整个项目你写在简历上的“熟悉前后端分离开发流程”“掌握SpringBootVue全栈实践”才真正有东西支撑。1.2 系统整体架构与功能地图我的系统采用标准的前后端分离架构后端服务SpringBoot 2.7.x MyBatis-Plus MySQL 8.0提供RESTful API前端应用Vue 2 Element UI Axios开发环境通过Vite启动生产环境打包为静态文件数据存储MySQL 8.0核心表包括用户表、垃圾类别表、垃圾词条表、搜索记录表、反馈表功能模块分成两大类共八个子模块用户端用户注册登录、垃圾名称搜索、图片识别跳转预留接口、分类结果展示、搜索历史记录、垃圾分类指南浏览管理端仪表盘统计、垃圾类别管理增删改查、垃圾词条管理增删改查批量导入、用户管理、反馈审核、系统参数配置这个功能地图不是随便画的。我见过很多学生项目一上来就堆功能做个垃圾分类系统非得加上社区论坛、积分商城、地图导航结果每个功能做得都很浅答辩的时候一问就露馅。我的原则是把核心闭环做深做透。用户搜索垃圾 → 得到分类结果和投放指引 → 记录搜索历史 → 用户对结果反馈 → 管理员优化词条这条链路是完整的业务价值才立得住。1.3 核心表结构与数据库设计数据库设计是整个系统最见功底的部分。我的核心表设计如下sys_user用户表字段包括id、username、passwordBCrypt加密存储、nickname、avatar、roleUSER/ADMIN、status、create_timegarbage_category垃圾类别表字段包括id、name可回收垃圾/有害垃圾/厨余垃圾/其他垃圾、code、description、icon、sort_ordergarbage_item垃圾词条表字段包括id、name垃圾名称、category_id关联类别表、keywords搜索关键词逗号分隔、tips投放指导、detail详细说明、search_count、statussearch_log搜索记录表字段包括id、user_id、item_id、keyword、result_type、create_timefeedback反馈表字段包括id、user_id、content、contact、status、reply、create_time其中garbage_item的词条数据是整个系统的核心资产。我初始灌了大约300条常用垃圾词条数据覆盖了日常家庭垃圾的绝大多数场景比如剩饭剩菜、废电池、旧衣服、一次性餐盒、过期药品、榴莲壳、椰子壳这些比较容易搞错的都专门做了关键词冗余。表设计上有个细节garbage_item里为什么单独加keywords字段因为用户搜“锂电池”和搜“充电电池”指的很可能是同一类东西但如果只用name做精确匹配根本命中不了。我把同义词和常见口语说法都维护在keywords里用逗号分隔存字符串查询时用LIKE %keyword%匹配name和keywords字段。这种方案简单有效比单独建关联表更容易维护对中小项目来说是性价比最高的选择。2. 后端SpringBoot核心实现与接口设计2.1 项目结构与核心配置后端项目结构采用标准的包分层方式com.example.garbage ├── common │ ├── result统一返回结果封装 │ ├── exception全局异常处理 │ └── constant常量类 ├── configMyBatis-Plus配置、CORS配置、WebMvc配置 ├── controller控制层 ├── service业务层 ├── mapper数据访问层 ├── entity实体类 └── dto数据传输对象配置方面application.yml里这几个配置值得单独拿出来说spring: datasource: url: jdbc:mysql://localhost:3306/garbage_classification?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true global-config: db-config: id-type: auto这里有一个坑我必须提醒你MySQL 8.0的驱动类名是com.mysql.cj.jdbc.Driver不是以前的com.mysql.jdbc.Driver。如果你用的是MySQL 8.x驱动配置写错会直接报ClassNotFoundException。另一个坑是serverTimezone参数不配置就报时区错误因为新版驱动要求显式指定时区。CORS跨域配置用SpringBoot写起来很简单Configuration public class CorsConfig { Bean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); config.addAllowedOriginPattern(*); config.addAllowedMethod(*); config.addAllowedHeader(*); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); } }2.2 分类识别核心逻辑垃圾分类识别的核心接口设计如下PostMapping(/api/garbage/search) public ResultSearchResultVO search(RequestBody SearchRequest request) { // 1. 校验参数 String keyword request.getKeyword(); if (StringUtils.isBlank(keyword)) { return Result.error(搜索关键词不能为空); } // 2. 模糊匹配词条表 LambdaQueryWrapperGarbageItem wrapper new LambdaQueryWrapper(); wrapper.like(GarbageItem::getName, keyword) .or().like(GarbageItem::getKeywords, keyword); ListGarbageItem items garbageItemMapper.selectList(wrapper); // 3. 无匹配结果时返回提示 if (items.isEmpty()) { return Result.success(null, 未找到相关垃圾信息); } // 4. 匹配到多个时按搜索次数排序返回前三个候选 items.sort((a, b) - b.getSearchCount() - a.getSearchCount()); ListGarbageItem topList items.stream().limit(3).collect(Collectors.toList()); // 5. 记录搜索日志异步处理 searchLogService.saveLog(当前登录用户, keyword, topList.get(0).getId()); // 6. 返回结果 return Result.success(topList); }这一段代码看起来简单但里面有一个业务决策当用户输入一个关键词匹配到多个词条时怎么办比如输入“电池”系统里可能有“干电池”“锂电池”“纽扣电池”“充电电池”四个词条它们分别属于有害垃圾、可回收垃圾、有害垃圾、可回收垃圾。如果只返回一条会误导用户——因为不同种类的电池对应的分类完全不同。所以我这里做的是返回匹配到的前三条候选把相同名称可能对应不同分类的模糊场景交给用户做二次确认。同时我维护了这些候选词条的排序搜索次数多的排在前面这样热门物品的命中率会越来越高。在后续版本里我还在这个基础上加入了简单的人工纠偏——如果某条词条频繁被用户反馈“分类错误”管理员在后台就能看到该词条的反馈量及时修正。2.3 用户认证与权限管理认证方案用的是JWT。我的实现要点包括用户注册时密码用BCrypt加密存储不存明文登录成功后生成JWT令牌有效期设24小时前端把Token放到请求头Authorization字段后端写一个JwtInterceptor拦截器加上WebMvcConfigurer注册拦截规则管理端接口在拦截器里校验角色为ADMIN我之前看到很多学生项目用拦截器做了JWT校验但在放行路径上写错了导致只要带Token的请求都进不了系统排查了半天发现是拦截器注册顺序和路径匹配出了问题。这里我分享一个经验拦截器里对OPTIONS请求必须直接放行否则前端跨域预检请求会被拦截器拦下来报出让人摸不着头脑的错误。public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { // 放行OPTIONS预检请求 if (OPTIONS.equalsIgnoreCase(request.getMethod())) { return true; } // 其他逻辑 }2.4 后端也踩过的SpringBoot版本坑写这个项目的时候我一开始用的SpringBoot 3.0。说实话SpringBoot 3.0性能提升和Jakarta EE命名空间迁移让很多老教程直接失效我用的MyBatis-Plus当时还没有完全适配源码里一堆javax导入一直报错。最后我退回SpringBoot 2.7.x才顺利跑通。如果你也是刚上手SpringBoot我的建议是别追新版本用SpringBoot 2.7.x最稳。SpringBoot版本太高意味着大量第三方starter可能还没适配特别是MyBatis-Plus、PageHelper这些国产组件它们对SpringBoot 3.x的兼容是有滞后期的。等你把项目功能做完想去适配新版本再慢慢升级也不迟。3. 前端Vue项目搭建与核心页面实现3.1 Vue环境配置与项目初始化前端我用的Vue 2 Element UI。很多新同学上来就问“为什么不用Vue 3”我解释一下Vue 3 Element Plus生态确实更先进但Vue 2的文档、教程、第三方组件库最多遇到问题百度一下答案一抓一大把对于课程设计和毕业设计来说你能把项目顺畅做出来才是第一位的。前端环境配置有几个关键点容易被坑到Node.js版本Vue 2项目建议用Node 14.x或16.x。Node 18及以上版本跑老项目时可能遇到node-sass编译失败报错“gyp ERR”npm源国内用官方源下载依赖慢到哭建议先执行 npm config set registry https://registry.npmmirror.com脚手架安装npm install -g vue/cli 之后用 vue create garbage-web 初始化项目Vue项目结构照下面这样规划src ├── apiaxios请求封装按模块拆分 ├── assets静态资源 ├── components公共组件 ├── router路由配置 ├── storeVuex状态管理 ├── views页面组件 │ ├── user用户端页面 │ └── admin管理端页面 └── utils工具函数含token存取涉及m3u8播放、地图展示这些非核心场景虽然网上也有很多教程但我的建议是不要在毕业设计里堆这种跟系统核心业务无关的功能除非你的系统确实需要视频展示功能否则纯属给自己增加工作量。3.2 垃圾搜索页面前端交互的核心用户端的核心页面是垃圾搜索页。UI布局是这样的顶部一个搜索框中间是分类结果卡片区底部是四个分类的展示入口。搜索交互我做了防抖处理methods: { handleSearch() { if (this.timer) clearTimeout(this.timer); this.timer setTimeout(() { this.doSearch(); }, 300); }, async doSearch() { const res await searchAPI(this.keyword); if (res.code 200) { this.searchResult res.data; } } }为什么要做防抖因为用户每输入一个字符搜索接口就会请求一次如果不加防抖用户输入“电池”两个字会触发两次请求输入快一点后端压力陡增而且结果互相覆盖会出现闪烁。加了300毫秒防抖之后只有用户停顿下来才会真正发起请求。搜索结果返回多条时我设计的是卡片横向排列每条卡片显示垃圾名称、所属类别标签不同类别用不同颜色、投放提示点击卡片可以展开查看详细说明。这个交互设计我专门找同学做过简单的可用性测试大家的反馈是比较直观的。Vuex里面我存了token、用户信息和搜索历史。搜索历史这一块很多人会用localStorage直接存但我的建议是走后端接口存数据库。因为登录用户在不同设备上登录搜索历史应该是跟随账号走的用localStorage只能存本地的做不到跨设备同步。3.3 管理端表格页面的实现管理端最重要的就是垃圾词条管理页面。我用的是Element UI的el-table el-dialog el-form组合表格展示词条名称、所属分类、搜索次数、状态、操作按钮新增/编辑走对话框表单表单校验用rules删除操作加二次确认批量导入功能支持Excel文件解析表格的分页这里也讲一下因为分页参数对不上是前后端联调时最高频的问题之一。后端MyBatis-Plus分页返回的是IPage对象默认字段是records/total/size/current前端el-pagination的绑定值是current-page/page-size/total。你要把两者对应好千万不要写死total。这里有一个我这项目里实际处理过的细节前端修改一条垃圾词条的分类时页面不能只刷新当前分页数据而应该保留当前分页参数重新请求。很多人图省事改完数据直接调用列表接口不传分页参数结果后台一改数据页面表格跳回第一页用户如果正在浏览第7页就会一脸懵。3.4 前后端联调与Axios封装Axios请求封装我做了环境区分// 开发环境走Vite代理生产环境走Nginx反向代理 const service axios.create({ baseURL: process.env.VUE_APP_BASE_API, timeout: 10000 }); // 请求拦截器携带Token service.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers[Authorization] token; } return config; }); // 响应拦截器统一处理错误码 service.interceptors.response.use( response { const res response.data; if (res.code 401) { this.$router.push(/login); } return res; }, error { ElMessage.error(网络请求异常); return Promise.reject(error); } );开发环境下跨域问题靠Vue CLI的devServer代理配置解决devServer: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }4. 数据库初始化与系统部署实录4.1 数据库脚本和初始数据说明项目包里附带了一个garbage_classification.sql文件这个文件做了两件事建表并插入初始化数据。导入数据库用命令行执行mysql -uroot -p123456 garbage_classification.sql或者用数据库图形化工具直接导入整个SQL文件。里面包含了上述所有表结构以及约300条垃圾基础词条数据。这些词条数据是我从多个垃圾分类指南网站聚合整理出来的涵盖常见生活垃圾场景比如厨房类剩饭剩菜、菜叶果皮、鸡骨头、大棒骨、贝壳、茶叶渣、过期食品卫生间类卫生纸、用过的湿巾、牙刷、洗发水空瓶、过期化妆品电器类干电池、充电电池、手机、电视、冰箱、电灯泡药品类过期药品、胶囊包装、药瓶衣物类旧衣服、旧鞋子、皮质包、毛巾特殊类榴莲壳、椰子壳、玉米棒、口香糖、宠物粪便这里有个词条设计的小技巧垃圾名称要同时维护“常见叫法”和“专业叫法”。例如用户会搜“电池”也会搜“5号电池”会搜“可充电电池”。我在keywords字段里把这些变体都存下来了这样搜索命中率能从60%左右提升到90%以上。初始数据的搜索次数字段我做了模拟数据设置了一些高搜索量词条这样管理端仪表盘的统计图出来会比较好看答辩演示时也不用现场尴尬地敲大量数据。4.2 后端打包与部署后端打包前需要改几个地方确认数据源连接信息适配生产环境确认JWT密钥已修改别用默认值确认server.port没有冲突打包命令是mvn clean package -DskipTests生成的目标jar包扔到服务器用nohup启动nohup java -jar garbage-system.jar --spring.profiles.activeprod logs/garbage.log 21 建议服务器上用systemd管理Java进程可以实现开机自启和崩溃自动重启。这里不多展开但网上systemd配置模板很多照着改一下就行。前端打包npm run build打包后的dist目录部署到Nginx的html目录下Nginx配置里加一个关键的反向代理——把/api前缀的请求转发到后端服务server { listen 80; server_name your-domain.com; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }注意try_files这个配置Vue Router用history模式时刷新页面会404必须靠它把请求重定向到index.html让前端路由接管。你要么配置这个要么用hash模式否则上线的系统一点刷新就出问题。4.3 高频问题排查速查表我整理了一份这个项目开发过程中的高频问题速查表每个问题都是我自己或者帮我朋友调项目时真实遇到过的问题现象根本原因解决办法后端启动报时区错误MySQL连接串缺少serverTimezone参数连接串加上serverTimezoneAsia/ShanghaiTomcat启动失败端口被占用本地已有进程占用8080端口换端口或杀掉占用进程Linux用netstat -tlnp查前端请求接口CORS报错后端或代理未配置跨域开发环境用devServer代理生产环境用Nginx代理搜索中文乱码数据库编码不是utf8建库时指定utf8mb4连接串加useUnicodetruecharacterEncodingutf8前端打包后刷新404Vue Router history模式未配置Nginx配置try_files $uri $uri/ /index.htmlJWT拦截器放行所有请求注册拦截器时addPathPatterns写错检查拦截路径只拦截需要鉴权的接口表格分页数据总是不对前端页码从1开始后端从0开始统一页码约定或在后端做页码-1处理Vue项目npm run serve报错Node版本过高或过低换成Node 16.x版本删除node_modules重新npm installMyBatis-Plus查询返回null实体类字段命名与表字段映射失败开启map-underscore-to-camel-case或使用TableField注解4.4 几个我觉得值得分享的避坑心得再补充几个更有“经验含量”的坑。第一个是垃圾词条数据清洗的重要性。网上网上能爬到的垃圾分类数据很多是不规范、错误频出的我当时爬了一批数据发现大量重复和不一致的条目比如同一种物品一个网站说是可回收另一个网站说是其他垃圾。这类数据问题如果没有提前清洗整理成词条灌到系统里会直接导致核心业务给出错误分类结果。建议你在灌数据前做一次人工复核对高频常用垃圾多花点时间确认一个权威来源。第二个是maven镜像源的选择。下载依赖慢有时候不是网速问题而是默认中央仓库太远。配置阿里云镜像之后依赖下载速度能提升几十倍。这个属于每个Java新手都应该知道的基本操作但真的很多人不知道。第三个是数据库增删改查的业务规则。很多人写CRUD就是简单把单表操作写完就完事了但你的项目要出彩在删除垃圾词条时必须判断这个词条有没有被搜索记录引用如果直接删除会导致历史搜索记录关联数据丢失。我的实现是采用逻辑删除——用一个deleted字段标记查询自动过滤数据不会真正从磁盘上消失。这样既保证了业务数据完整性又不会因为物理删除导致关联记录找不到了。5. 系统后续扩展方向与个人总结系统上线之后我一直在想它下一步应该往哪走。目前最值得做的一个扩展方向是“图片识别分类”也就是用户拍一张垃圾照片系统用图像识别返回分类结果。具体实现可以用开源的目标检测模型比如用YOLOv5训练一个垃圾识别模型后端用Python写一个识别服务和SpringBoot通过HTTP接口对接。这样系统的用户体验会实现一个质的飞跃。另一个思路是引入积分激励机制。用户每次正确分类可以累积积分积分可以在小程序里兑换一些小礼品。这个不仅让系统更有趣味性还能帮助你收集到大量标注数据反过来优化识别准确率。如果你想把项目做成一个长期运营的产品这个方向很有价值。还有一个我觉得很实际的方向是“语音输入垃圾分类”。现在老人是垃圾分类的主力人群但他们往往不太会打字如果能支持语音输入“这个是什么垃圾”系统通过语音识别转成文字再完成查询那就是一个真正有温度的功能。最后再分享一个小技巧如果你打算把这个项目写到简历上准备项目介绍时不要只背“我负责用户模块和搜索模块”这种描述要说清楚你做了什么关键决策、解决了什么痛点。比如“因为垃圾名称存在大量同义别名我设计了keywords关键词冗余字段将搜索命中率提升了30%”“搜索接口我做了防抖和异步日志避免高频请求拖垮数据库”。这种表述远比罗列技术清单有力得多。我个人做完这个项目最深的体会是一个好的业务系统不在于用了多少新技术而在于你是否把一个需求想透了。垃圾分类本身是贴近生活的场景你在网上查到的那些分类知识、那些用户容易犯混淆的垃圾种类每一个细节打磨到位了你的系统自然就比别人做得扎实。希望这篇文章能帮你把这个项目做顺、做稳做出属于自己的亮点。