直接说结论吧:垃圾分类系统这个毕设题,属于那种“看着不难,做着琐碎,但容易出彩”的类型。做得浅的只做个增删改查,做得深的能挂算法、能上大屏、能讲故事。这篇就把我当时做完全套的完整思路、数据库设计、识别逻辑、积分机制、部署排错经验全部分享出来,准备做这个题目的,照着往下走。
1. 项目怎么定位:明确边界才能做得完
1.1 先搞懂这个系统到底解决什么问题
多数人拿到“智能垃圾分类系统”这个题目,第一反应是“识别生活垃圾属于哪一类”。但毕设不能只做一个识别功能,必须是一个完整的管理系统,对吧?所以这个题目的本质是:一个以垃圾分类数据为核心、带积分激励、带管理后台、带统计展示的Web应用。
从现实需求出发,社区或学校投放分类垃圾桶后,靠人工监督成本太高,居民的垃圾分类准确率也参差不齐。一个线上系统可以让居民查分类、扫码或手动登记投放记录、攒积分换奖品,管理者能在后台看到每个点位/楼栋的投放数据,这就有话可说了。答辩时你可以说“本系统响应国家垃圾分类政策,以信息化手段提升居民参与度”——这是题目的政策性价值。
1.2 技术选型别贪多:适合演示才是第一原则
技术栈务必要“成熟+稳+能答得上来”。我当时最终敲定的是这套,你直接抄作业都行:
| 层级 | 技术方案 | 选择理由 |
|---|---|---|
| 后端框架 | Spring Boot 2.7.x | 版本成熟,资料最多,报错一搜就有答案 |
| ORM | MyBatis-Plus | 省掉大量重复SQL,分页查询一行搞定 |
| 数据库 | MySQL 5.7 / 8.0 | 免费、通用,答辩环境最容易部署 |
| 前端方案 | Thymeleaf + Bootstrap 5 | 前后端不分离,部署只有一个jar包,省心 |
| 图表展示 | ECharts | 引入一个JS文件即可完成各类统计图表 |
| 权限控制 | Spring Security + JWT | 能讲出“安全认证”的完整链路 |
| 其他 | Lombok、Hutool、Redis(可选) | 减少样板代码,提升开发效率 |
注意:如果你对Vue很熟,也可以做前后端分离,但毕设答辩环境不确定性大,前后端分离意味着要部署两个服务,还要处理跨域,多一层风险。我的建议是:非必要不分离,先保证能跑起来。
1.3 功能模块边界:四类角色,四个端
整个系统的角色划分决定了代码结构的清晰度:
- 居民(普通用户):注册登录、搜索垃圾类别、扫码或手动登记投放记录、查看积分明细、兑换商品、查看个人统计。
- 管理员:后台登录、用户管理、垃圾品类管理、垃圾名词库维护、投放记录审核(如有审核场景)、公告管理、数据统计。
- 保洁/审核人员(可选角色):若要做更真实的场景,可增加审核员角色,用于复核有争议的投放记录。这个角色能显著增加系统复杂度展示度,但做起来耗时,建议作为扩展功能。
你不需要把每个功能都做成大模块。优先级应该是:
- 第一梯队:用户管理 + 登录注册 + 垃圾查询 + 投放登记 + 积分收入
- 第二梯队:管理员后台的品类管理 + 记录管理 + 简单统计
- 第三梯队:积分兑换 + 公告 + 数据大屏 + 分享推广
把前三梯队做完,文档和答辩素材基本就够丰富了。第四梯队如果时间够再陆续加上,做完查询类功能后有余力再考虑做识别。
2. 数据库怎么设计:表结构才是一个系统的灵魂
2.1 核心表关系锁定
我设计时用到了以下核心表,每一张都有明确作用,在文档和答辩中的占比很高:
用户表 user 垃圾类别表 category 垃圾名词库表 waste_item(简称:垃圾词条表) 投放记录表 record 积分明细表 points_log 积分商品表 goods 兑换订单表 exchange_order 公告表 notice它们之间的关系,一句话说清楚:用户产生投放记录,投放记录关联垃圾词条,词条归属类别,类别决定积分权重。用户还可以用积分去兑换商品,兑换产生订单。
2.2 关键表的字段设计(直接可用的版本)
user(用户表)
CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '用户名', `password` varchar(255) NOT NULL COMMENT '加密后的密码', `nickname` varchar(50) DEFAULT NULL COMMENT '昵称', `phone` varchar(20) DEFAULT NULL COMMENT '手机号', `avatar` varchar(255) DEFAULT NULL COMMENT '头像地址', `points` int(11) DEFAULT 0 COMMENT '当前积分', `role` tinyint(4) DEFAULT 2 COMMENT '角色:1管理员 2居民', `status` tinyint(4) DEFAULT 1 COMMENT '状态:1正常 0禁用', `create_time` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;waste_item(垃圾词条表)是识别功能的核心资产,字段不多,但数据量决定了“智能感”:
CREATE TABLE `waste_item` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(100) NOT NULL COMMENT '垃圾名称,如:矿泉水瓶', `category_id` bigint(20) NOT NULL COMMENT '关联的垃圾类别', `keywords` varchar(255) DEFAULT NULL COMMENT '同义词/别名,逗号分隔', `note` varchar(255) DEFAULT NULL COMMENT '投放提示或说明', `create_time` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;category(垃圾类别表)
CREATE TABLE `category` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL COMMENT '类别名:可回收物/有害垃圾/厨余垃圾/其他垃圾', `code` varchar(20) DEFAULT NULL COMMENT '类别编码,如recyclable', `description` varchar(255) DEFAULT NULL COMMENT '类别描述', `icon` varchar(255) DEFAULT NULL COMMENT '图标地址', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;record(投放记录表)
CREATE TABLE `record` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) NOT NULL, `waste_item_id` bigint(20) DEFAULT NULL COMMENT '识别的垃圾词条', `category_id` bigint(20) NOT NULL COMMENT '最终归入的类别', `waste_name` varchar(100) DEFAULT NULL COMMENT '登记的垃圾名称', `point` int(11) DEFAULT 0 COMMENT '本次获得的积分', `source` tinyint(4) DEFAULT 1 COMMENT '来源:1搜索登记 2扫码投放 3手动录入', `status` tinyint(4) DEFAULT 1 COMMENT '状态:1正常 2异常/争议', `create_time` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;2.3 设计时的三个关键决策
第一个决策:积分放user表还是单独算?
我选择了在user表里冗余一个points字段,同时用points_log记录每一笔流水。这样可以保证查询“我的积分”极快(直接查user表),同时积分明细完整可追溯,任何积分变动都能找到原因。冗余字段没问题,只要保证在记录流水时同步更新user表即可。
第二个决策:垃圾词条数据准备多少条?
这个直接决定了“智能”的体验。我花了一天半时间整理词条库,最终录入了500+条垃圾词条,覆盖了日常能想到的绝大多数垃圾:塑料瓶、易拉罐、废纸箱、电池、过期药品、剩菜剩饭、榴莲壳、椰子壳、化妆品、旧衣服、灯泡、烟头……每条都关联到了对应类别。有这一句词条库,演示时用户搜什么基本都有结果,否则你当着答辩老师的面搜出来一个“未收录”,体验会差很多。
第三个决策:时间字段用什么类型?
用datetime,不要用timestamp。虽然两者效果接近,但datetime在MySQL 5.7和8.0的兼容性表现更稳定,做时间范围统计时也更直观。顺手给create_time加索引,因为所有统计功能都绕不开时间维度。
3. 垃圾分类识别功能:怎么实现“智能”
3.1 说清楚“智能”的层次
答辩时如果有人问你“这个智能体现在哪里”,你得能分层回答:
- 第一层:关键词匹配识别。用户输入“矿泉水瓶”,系统去词条表里匹配,返回“可回收物”,这是基础层。
- 第二层:同义词/别名扩展。用户输入“塑料水瓶子”,系统通过keywords字段里的同义词库,也能匹配到“矿泉水瓶”,这就是“智能”的直观体现。
- 第三层:模糊匹配。用户输入不完全准确,比如“茶叶沫”和“茶叶渣”,可以通过MySQL的LIKE查询或分词逻辑找到最接近的词条。这块不需要上NLP,核心思路是用数据库和字符串匹配把体验做到位。
我的实现逻辑是这样的:
public CategoryVO matchCategory(String wasteName) { // 1.优先精确匹配 WasteItem item = wasteItemMapper.selectOne( new LambdaQueryWrapper<WasteItem>() .eq(WasteItem::getName, wasteName) .last("limit 1")); if (item != null) { return buildVO(item.getCategoryId()); } // 2.再用同义词匹配(keywords字段逗号分隔) List<WasteItem> itemList = wasteItemMapper.selectList(null); for (WasteItem wi : itemList) { if (wi.getKeywords() != null) { String[] kws = wi.getKeywords().split(",|,"); for (String kw : kws) { if (kw.trim().equals(wasteName)) { return buildVO(wi.getCategoryId()); } } } } // 3.最后用模糊匹配兜底 List<WasteItem> likeList = wasteItemMapper.selectList( new LambdaQueryWrapper<WasteItem>() .like(WasteItem::getName, wasteName) .or() .like(WasteItem::getKeywords, wasteName) .last("limit 5")); if (likeList != null && likeList.size() > 0) { return buildVO(likeList.get(0).getCategoryId()); } return null; }注意:有人喜欢搞复杂的规则引擎或者直接引入机器学习库,比如训练一个文本分类模型。千万不要!毕设答辩现场没有时间让你调模型,简单的数据库匹配逻辑配上足够的词条数据,演示效果完全不输。如果你想在“智能”上加分,可以增加一个“拍照识别”的扩展模块作为演示亮点,但主流程仍然以词条匹配为主。
3.2 提示词的“投递引导”功能
搜索“玻璃瓶”后,识别出来是“可回收物”,这只是一个结果。为了让系统看起来“真有用”,我还给每个类别配了投放提示。比如:
- 可回收物:“请将瓶内液体倒空,压扁后投放”
- 有害垃圾:“请连同包装一起投放,避免破损”
这些提示存在category表的description字段里,查询结果页直接展示,一分钟就能加上,但用户体验和“专业感”就会提升一个档次。
我把这个功能叫做“查询后的下一步引导”,它让用户的时间花得有价值:本来只是查个分类,系统还告诉你具体怎么投放才正确,这是加分场景。
3.3 识别率不高时怎么补救
如果用户输入的词条在库里没有匹配到,系统不会直接告诉“我没收录”,而是给出一个“未识别到分类,请选择以下相近选项”的列表,让用户手动选择最接近的垃圾词条。这个设计等于给识别失败加了个降级通道,很实用,答辩时也能说明你考虑过边界情况。
手动选择后,后端会把用户的输入自动记到一个待收录词条表,管理员后台能看到。管理员审核后把新词条加入库中,系统就“越用越聪明”。这一点在听答辩的老师那里是很有分量的设计点。
4. 积分机制与可视化:让系统显得高级
4.1 积分算法与防刷设计
积分规则不能拍脑袋写死,要能讲出设计依据。我采用的规则是:
| 垃圾类别 | 单次积分 | 设计理由 |
|---|---|---|
| 可回收物 | 10分 | 资源回收价值高,正向激励 |
| 有害垃圾 | 15分 | 引导用户单独投放,权重最高 |
| 厨余垃圾 | 5分 | 高频垃圾,但不宜过高 |
| 其他垃圾 | 2分 | 兜底类别,保持参与感 |
一天内的积分上限设置为100分,主要防止恶意刷分。上限的实现方式回溯到当天记录表里查询:
// 计算用户今日累计积分 LocalDateTime start = LocalDateTime.now().withHour(0).withMinute(0).withSecond(0); LocalDateTime end = LocalDateTime.now().withHour(23).withMinute(59).withSecond(59); Integer todayPoints = recordMapper.selectTodayPoints(userId, start, end); if (todayPoints + point > 100) { throw new BusinessException("今日积分已达上限"); }这个设计的好处是:规则调整只需要改一个常量或配置项,不会牵扯其他逻辑。答辩时如果老师问“怎么防刷”,你直接把这个代码和上限说明展示出来,比空口讲“有防刷”有效得多。
4.2 积分兑换模块怎么“小而美”
积分兑换不是核心功能,但它是“积分有价值”的证明。我的做法很简单:
- 商品表记录所需积分、库存、图片
- 用户兑换时校验积分是否足够、库存是否充足
- 兑换后扣减积分,生成订单记录,扣减库存
这里有几个小细节要注意:
- 用事务:扣积分和扣库存必须在一个事务里,否则会数据不一致。
- 订单编号生成规则:时间戳 + 用户ID + 随机数,保证唯一。
- 兑换按钮的防重复提交:前端点击后立即置灰,后端也可以做幂等校验。
这块功能代码量不大,但涉及“事务、并发、业务校验”,文档里可以写整整一章。
4.3 可视化大屏:声音不大,效果拉满
ECharts负责两块:社区投放总量趋势图和各类垃圾占比饼图。数据接口给后端遍历记录表做聚合即可,前端直接调用接口拿JSON渲染。
我的统计SQL示例:
SELECT category_id, COUNT(*) AS cnt, SUM(point) AS total_points FROM record WHERE create_time >= #{startTime} GROUP BY category_id后端聚合后,返回ECharts能直接使用的格式:
{ "trend": [{"date":"2024-04-01","count":23}, ...], "categoryDist": [{"name":"可回收物","value":35}, ...] }可视化还有一个很重要的优化点:如果数据量不够,图表会显得很空。我先自己造了1000+条模拟投放数据,时间跨度为最近14天,这样答辩演示时图表曲线非常自然。模拟数据要写在文档里说明,而不是只留着给演示用。
这里再强调一句:图表不是越多越好,趋势图、占比图、排名榜三张图足够。多了反而会让系统显得花哨而不实用。
5. 实操过程记录与避坑经验
5.1 项目搭建与初始化:从空目录到能跑通
初始化顺序是:新建Spring Boot项目(勾选Web、MySQL、Mybatis-Plus依赖)→ 配置application.yml → 建库建表 → 启动项目验证空壳能跑 → 逐一实现模块。
一个小习惯:每一个模块完成后,马上提交一次代码并本地启动验证,不要攒到最后一起测。不然错误定位成本会高到让人崩溃。我用的是每次功能开发完成后,都会在浏览器里走一遍关键路径再去做下一个功能,比如做完登录就测登录,做完查询就测查询,绝不堆到晚上统一测。
5.2 词条数据的准备技巧
真实数据录入很枯燥,但这是演示效果好坏的底线。我当时把词条分为几类来收集:
- 日常厨房类:剩菜、蛋壳、骨头、菜帮菜叶
- 客厅类:塑料瓶、易拉罐、玻璃杯、纸箱、电池、遥控器
- 卫浴类:牙刷、毛巾、洗发水瓶、卸妆棉
- 有害类:废灯泡、过期药品、油漆桶、电池、温度计
- 其他类:烟头、灰土、创可贴、餐巾纸
每条数据尽量把同义词也写上。比如“塑料瓶”的keywords可以写“矿泉水瓶,饮料瓶,可乐瓶,瓶子”。有了同义词,用户的自然输入基本都能命中。
5.3 远程调试与环境统一
这段必须单独说。远程调试或部署时最常见的问题是本地环境和服务器环境不一致。JDK版本、Maven仓库、数据库版本都可能不一样。我踩过的坑是:本地JDK用的17,服务器是JDK 11,Spring Boot 2.7默认兼容没问题,但代码里用了某个高版本语法就会直接启动失败。
我的建议:把JDK版本固定在JDK 8或JDK 11,用Spring Boot 2.x版本,这是毕设项目最稳的组合。远程调试时需要把数据库初始化SQL脚本和配置文件一并打包发过去,并且在配置里禁用Redis等外部依赖,不然环境有问题会拖很久。
另一个坑是时区和编码。MySQL连接串务必加上:
?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai不加的话,中文乱码、数据库时间差8小时的问题会接踵而至,而且排查起来非常折磨人。这个配置是最容易被忽略的,强烈建议每次新建项目都先写进去。
5.4 答辩演示的准备工作
演示不是打开系统随便点,一定要“彩排过三条主链路”:
- 用户注册 → 登录 → 搜索“塑料瓶” → 看到“可回收物”和投放提示 → 登记投放 → 积分增加
- 进入“个人中心” → 查看积分明细 → 兑换商品 → 积分扣减
- 管理员登录 → 查看用户列表 → 查看投放记录统计 → 查看ECharts图表
三条链路演练两遍,基本能把系统所有核心功能都呈现在老师面前。另外准备15张左右截图放入PPT,每组截图旁边配一两句“设计意图”,让老师知道截图里有什么亮点。
6. 常见问题与排查技巧实录
我在开发和调试过程中积累了不少典型问题和解决办法,整理成一个速查表,希望能帮你在遇到同样的问题时少走弯路:
| 现象 | 最可能原因 | 解决思路 |
|---|---|---|
| 项目启动后访问页面报404 | Controller路径写错或未被扫描到 | 检查启动类所在包结构,Controller必须放在启动类子包下 |
| 中文乱码 | 编码配置缺失 | 确认数据库连接串加characterEncoding=utf8,检查页面charset设置 |
| 时间差了8小时 | 时区未指定 | 连接串加serverTimezone=Asia/Shanghai |
| 登录后跳转不生效 | 拦截器拦截了静态资源 | 放行/login、/css、/js、/images等路径 |
| 图片上传后访问404 | 静态资源映射未配置 | 配置ResourceHandler映射本地上传目录到 /upload/** |
| MyBatis-Plus分页无效 | 缺少分页插件配置 | 必须注册PaginationInnerInterceptor |
| 积分兑换同时点了两次 | 缺少防重复提交 | 使用前端按钮置灰 + 后端事务幂等校验 |
| 部署后端口被占用 | 服务器上其他服务占用8080 | 修改application.yml的server.port为不常用端口 |
| ECharts图表不显示 | 图表数据格式不对或容器高度为0 | 检查JSON格式,图表div必须设置宽度和高度 |
其中有两个“看似简单但解决时间最长”的问题,我展开说:
问题一:数据库连接池启动报错,明明密码是对的
当时花了大半天发现是配置文件缩进写错,yml格式对空格极其严格,YAML里的缩进错了根本不会报具体错误,而是报一个“Failed to configure a DataSource”。排查这种问题的最快方式是IDEA里看YAML配色,属性名是蓝色,值如果是绿色且没高亮,就说明缩进有问题。
问题二:线上环境图表加载不出来
本地一切正常,部署到服务器后ECharts饼图空白。排查下来有两个原因:一是包名带中文路径导致JS资源加载失败,二是ECharts的CDN链接在服务器网络环境下无法访问。解决办法是:把ECharts的JS文件下载到项目的静态资源目录中,本地引用,不要依赖外部CDN。凡是外部CDN的资源,部署环境网络一变就全废,务必把公共库文件都下载到本地。
7. 这套系统还能怎么扩展
到这里,基础项目已经完整且质量有保障了。如果你想在中期检查或者优秀毕设评选时再往上走一层,我建议顺着这几个方向扩展,每个方向都是低成本高性价比:
方向一:增加开放信息/资讯模块
垃圾分类相关的环保资讯、公告、政策宣传,可以作为系统的内容补充。在首页放一个资讯列表,用户可查看详情,管理员可发布资讯。这个模块本质上是文章管理,代码量不大,却能丰富整个系统。
方向二:对接微信小程序
把查询和投放记录功能做成小程序端,后端接口复用现有Spring Boot代码,只需新增一套微信小程序的展示页面。这个扩展能讲“移动端+后端复用”的架构故事,是个不错的亮点。
方向三:增加社区排行榜
在首页或小程序端展示“本周积分排名Top10”,加入用户排名、团队排名的概念。排行榜能有效提升激励机制的话题感,演示时也很有互动效果。
方向四:引入消息通知
比如积分变动通知、兑换审核结果通知、预约上门回收通知等。实现方案可以用WebSocket做实时推送,也可以简单用数据库待通知列表,用户下次登录时展示。
这几个方向不需要全部做,挑一个最贴合你答辩故事线的就行。怕的是“功能过多的系统”,每个功能都稀烂,老师们反而会觉得把控不住范围。
最后,在做这个系统的过程中,我最大的一个心得是:毕设项目不怕简单,怕的是“底盘不稳”。与其堆砌花哨功能,不如把用户、词条、记录、积分这4张核心表的关系理清楚,把主流程跑通,把界面做整洁,把数据准备好,把演示练熟练。你把这四点做到位,这个系统在你答辩时就已经能立住了。如果学有余力,再去加识别优化或小程序端,那是锦上添花的事,很可能直接决定你的成绩上限。