ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Spring Boot智能垃圾分类系统实战:从设计到部署全解析

2026/10/3 4:03:51 拓冰建站 浏览量
Spring Boot智能垃圾分类系统实战:从设计到部署全解析 这两年Java课程设计和毕业设计选题资源回收、垃圾分类、环境监测这一类确实是热门方向。前段时间帮几个学生把关项目架构不下五个人选了基于Spring Boot的智能垃圾分类系统。说实话一开始我也觉得这题目有点做滥了但深入聊下来发现这个选题能火是有道理的——它的业务闭环非常完整用户端能识别垃圾、记录投放、攒积分兑换管理端能维护数据、看报表、管用户几乎把Java后端该练的技术栈都串了起来。这篇文章我就基于实际搭这个项目的全过程展开从需求分析到数据库设计从识别逻辑到接口实现再到部署阶段的坑全部按真实开发顺序讲。如果你正在做课设、找毕设方向或者刚学完Spring Boot想找一个能完整跑起来的练手项目这篇可以直接参考。1. 为什么垃圾分类系统是Java课设/毕设的万金油选题1.1 业务闭环完整功能边界清晰一个项目好不好做第一看业务边界。图书管理系统太单薄商城系统又太复杂——支付、物流、库存、优惠券随便拉出来一个都是深坑课设周期内根本做不透。垃圾分类系统恰好卡在中间这个舒适区里。这个系统的核心链路很直白用户输入垃圾名称系统返回分类用户去投放点投放系统记录投放行为并发放积分积分攒够了去兑换商品管理员在后台审核和维护数据。一条线走下来用户侧和管理侧都有事做而且每一环都能看到数据流转。拿积分这个环节举例它关联了投放记录、用户积分、积分明细、商品库存四个表业务逻辑足够解释得清楚又不至于复杂到失控。对比一下其它常见选题就明白了。论坛系统要处理版块权限、帖子审核、评论盖楼需求一多就容易无限发散点餐系统要接桌台状态、订单流转、后厨出餐各种状态机的组合能把人绕晕。垃圾分类系统则天然收敛在一个百科打卡积分的模型里哪怕需求写得再花哨核心还是那几个实体几张表。1.2 技术覆盖面广难度梯度可伸缩这个题目还有个好处它的技术难度是可以按能力裁剪的。基础版只需要做到垃圾名称查询分类一个模糊查询接口加一张物品表就完事。中等版本加上用户体系、投放记录、积分累计和后台管理CRUD的基本功、联表查询、分组统计都能练到。进阶版本再叠加接口鉴权、Redis缓存、微信小程序端对接甚至可以对垃圾图片做基于规则的特征判断那展示出来的就是一个完整度相当高的作品了。我给学生做规划时一般会分三档难度档位核心功能对应技术点基础版垃圾名称查询、分类展示Spring Boot CRUD、MyBatis-Plus条件构造器标准版用户登录、投放记录、积分体系、后台管理JWT鉴权、多表联查、分页查询、分组统计进阶版图片识别、热词缓存、数据报表、小程序端Redis缓存、文件上传、ECharts图表、uni-app对接在这个架构下基础代码是同一套只是根据时间和能力决定做厚做薄。这正好是答辩时最能体现工作量差异的地方——同一个题目有人只做出来一个查询页面有人做出来一个能讲故事的产品模型差距一下就拉开了。1.3 演示效果直观答辩讲起来不虚很多课设项目的问题是看不到实物效果。权限管理系统、接口后端这类项目评委坐在下面看你打开Postman戳几个接口情绪很难调动起来。但垃圾分类系统不是演示的时候掏出手机或者打开浏览器页面输入剩饭两个字系统立刻返回厨余垃圾再输入过期药品返回有害垃圾这种即时反馈的效果是非常直观的。更重要的是这个场景每个人都能理解不存在知识门槛。评委不需要你解释什么是RBAC权限模型、什么是工作流引擎垃圾怎么分类他自己日常也在扔。技术答辩的时候你可以顺着业务提问往下走如果用户输入了模糊词怎么办如果有人恶意刷积分怎么办这些追问都有料可答也就不会出现在台上冷场的局面。2. 技术选型Spring Boot生态里的中配稳定方案2.1 Spring Boot版本怎么选2.7.x是课设的稳定牌版本选择这个事很多新手上来就随便新建一个项目默认拉到什么版本就用什么版本结果后面踩的坑比写的代码还多。垃圾分类系统的技术栈里Spring Boot版本是第一道选择题。目前主流就两条线Spring Boot 2.7.x和Spring Boot 3.x。3.x要求JDK 17起步默认使用Jakarta EE命名空间很多老教程和现成代码段直接就没法用了。而2.7.x是2.x系列的最后一个版本既支持JDK 8也支持JDK 11绝大多数学校的机房环境、网上能找到的教程、毕业设计范文都跑在这条线上。我用表格把关键差异列一下选型时直接对照看对比项Spring Boot 2.7.xSpring Boot 3.xJDK要求JDK 8 / 11JDK 17javax vs jakartajavaxjakartaMyBatis-Plus支持成熟稳定需要boot3专用starter教程/资料丰富度极高中等新特性无亮点但够用安全性能提升对课程设计、毕业设计这类场景我强烈建议选2.7.x。没有什么非用不可的新特性稳定压倒一切。我实际开发中也遇到过学生用3.x搞了一周最后因为一个注解包名对不上卡住的例子换成2.7.x十分钟就解决了。这不是说3.x不好而是你要评估自己的时间成本和资料获取成本。2.2 数据访问层为什么是MyBatis-Plus而不是JPA数据访问层的选型经常在MyBatis-Plus和Spring Data JPA之间纠结。我的意见很明确这个项目用MyBatis-Plus。JPA确实够省事实体类一标注表关系就自动建但它的坑在于自动这两个字。对课设来说你需要写联表查询的时候JPA的派生方法名能绕晕你——方法名叫findByCategoryIdAndCreateTimeBetweenOrderByCreateTimeDesc看一会就知道怎么回事但写起来真费劲。MyBatis-Plus则保留了你对SQL的控制权同时把单表CRUD的样板代码省到了极致。垃圾分类系统里至少有八成操作是单表CRUD比如物品表新增一条垃圾数据、用户表修改积分、投放记录分页查询这些用MyBatis-Plus的BaseMapper接口自带的insert、updateById、selectPage直接搞定。剩下两成复杂查询比如按分类统计投放数量、查询用户最近积分流水写XML或LambdaQueryWrapper都能轻松做掉。依赖引入很简单dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency注意如果是Spring Boot 3.x要把artifactId换成mybatis-plus-spring-boot3-starter这个细节后面踩坑部分还会展开讲。2.3 辅助组件怎么配Redis、JWT、接口文档主体框架定了辅助组件也要配齐这些都是后端项目的标准动作。Redis在这个系统里有两个用途一是缓存高频查询的垃圾分类结果比如矿泉水瓶这种每天被查几百次的数据没必要每次都打数据库二是做积分操作时的并发控制不过课设规模下一般用不到缓存做锁简单应付就行。Redis的引入不会增加多少代码量但能在答辩时多一个技术亮点我觉得值得加。JWT做鉴权是前后端分离项目的标配。管理员登录、用户登录都签发token后续请求在请求头带上Authorization: Bearer token拦截器里校验。比起传统的Session方案JWT无状态、好扩展的特点正好配小程序端和后台管理端两个客户端并行使用的情况。接口文档用knife4j它是Swagger的增强版界面更符合国内的审美习惯。别觉得文档是可有可无的东西项目做完要交付、要演示接口一多没有文档自己都记不住参数。knife4j引入后启动项目访问doc.html就能看到所有接口定义配合swagger-annotations注解生成文档的成本极低。2.4 前端形态和部署思路这个系统的前端一般分两块用户端和管理端。管理端用Vue ElementUI的多一些用户端如果追求演示效果可以做成微信小程序或者H5。这里要泼一盆冷水如果你的主要目标是课设/毕设过关别再花三周去打磨小程序的美观度。后端能力才是项目核心前端能正常调用接口、展示数据就够了。我见过太多学生把时间耗在调CSS布局上后端接口一塌糊涂最后答辩被问倒的场景。用户端做一个简单的H5页面或者直接用Vue单页应用里做两个角色路由完全足够。部署方面课程设计阶段的服务器配置普遍不高2核4G或者云服务器低配版都够跑。建议直接用mvn package打成jar包然后java -jar跑起来。数据库用MySQL 8缓存用Redis都是最常见的组合。不要在这个阶段折腾Docker K8s那些东西除非你本来就会。3. 需求拆解与数据库设计数据流才是这个系统的灵魂3.1 用户侧核心诉求识别、投放、攒积分从用户视角出发这个系统大概要解决四个问题。第一我不知道手里的垃圾算什么分类所以需要输入垃圾名称获取分类结果第二投放完垃圾之后我希望系统能记录我的环保行为第三有了行为记录就希望能累积积分积分能兑换一些实用商品比如垃圾袋、环保布袋之类第四我偶尔想主动查一查某种垃圾的正确投放方式。这些需求落到功能上就对应着垃圾搜索/识别接口、投放登记接口、积分查询接口、分类百科浏览。用户在我的页面能看到自己的积分余额和投放历史这个信息结构可以画出一个比较清晰的用户端功能树。整体来看用户侧的功能不超过六个页面但涉及到用户表、物品表、投放记录表、积分流水表、商品表、兑换记录表六张核心表的联动。3.2 管理侧核心诉求数据维护、记录审核、统计报表管理侧的需求相对更后台一些。管理员需要维护垃圾物品数据库因为垃圾种类是无穷无尽的新品类不断出现得能随时增删改查。投放记录要能查看和筛选如果用户投放的重量或积分有异常管理员要能人工修正。积分商品需要上下架管理兑换订单要处理。最重要的一块是统计报表。这个系统天然适合做可视化按垃圾类别统计投放占比画出饼图按时间维度统计每天的投放次数画折线图按垃圾分类正确率给用户画像。这些报表不只是好看它们让系统从能用变成有管理价值。3.3 核心表结构清单与关键字段说明这里直接给出表设计这是整个系统的地基。参考我实际项目的建表方案表名用途关键字段t_user普通用户id, nickname, avatar, phone, total_points, statust_admin_user管理员id, username, password, role, last_login_timet_category垃圾类别id, name, description, color_codet_garbage_item垃圾物品id, name, category_id, keywords, is_correctt_recognition_log识别记录id, user_id, input_content, category_id, create_timet_drop_record投放记录id, user_id, item_id, category_id, points, create_timet_point_record积分流水id, user_id, change_value, change_type, source_id, create_timet_point_goods积分商品id, name, cover_url, points_required, stock, statust_exchange_record兑换记录id, user_id, goods_id, points_cost, status, create_time以t_garbage_item为例很多人会忽略keywords字段的重要性。比如用户输入塑料瓶能查到矿泉水瓶是因为这条物品记录的keywords里存了矿泉水瓶,塑料瓶,水瓶,饮料瓶这些别名查询的时候对keywords做LIKE匹配就能覆盖很多输入习惯。category_id关联类别表让垃圾物品和四色分类保持对应关系。3.4 设计理由冗余字段、状态字段与索引规划表设计里有两个容易被忽略的地方这里单独提一下。第一个是积分流水表为什么要单独建而不是只在用户表里存一个total_points。如果只存一个总数用户想知道我这个月积分怎么攒的就完全无从查起。单独的t_point_record表把每次积分变更的来源记录下来投放给积分、兑换扣积分、管理员手动调整积分全部有迹可循。这就是典型的记录流水冗余汇总模式汇总值直接读用户表明细值查流水表各有各的用处。第二个是几乎所有核心表都需要status状态字段。比如兑换记录有待发货/已发货/已取消三种状态积分商品有上架/下架两种状态。这个设计非常必要因为真实业务里很少直接删数据宁可标记状态也不做物理删除。课设阶段很多同学意识不到这一点习惯性用DELETE结果后面想做下架功能发现没有字段支撑只能推倒重来。索引方面t_drop_record的(user_id, create_time)组合索引必须建因为用户查询自己的投放历史是最常见场景。t_recognition_log按input_content精确查询的频率不高普通索引就够了不需要上全文索引。4. 垃圾分类识别模块不依赖AI模型的规则引擎实现4.1 识别需求的技术定位为什么要避开深度学习很多人在设计识别模块时第一反应是上图像识别、深度学习模型。我的建议是除非你有现成的数据集和调好的模型否则千万别碰深度学习这条路。道理很简单一是数据集不好搞垃圾分类图片数据集质量参差不齐要自己标注又要时间二是OCR识别垃圾名称在课设场景下不解决核心问题——你拍一张矿泉水瓶的照片识别出矿泉水瓶这五个字最后还是得走关键词分类这条路三是最关键的答辩评委问起来为什么这个模型识别准确率90%数据怎么标注的如果答不上来反而扣分。规则引擎的每个判断逻辑你都能解释清楚这才是课设该有的可解释性。所以我的定位是用户输入文字可以是手动输入、语音转文字或者简单OCR系统对文本做分类判断返回四色分类结果和相关建议。这套方案开发成本低、演示稳定、逻辑清楚完全够用。4.2 关键词映射与特征标签最可靠的分类方式识别模块的核心不是算法而是设计好t_garbage_item表里的关键词映射关系。四色分类的基础规则要预设例如可回收物的典型关键词纸箱、易拉罐、矿泉水瓶、旧衣服有害垃圾典型关键词电池、过期药品、废灯管、油漆桶厨余垃圾典型关键词剩饭、菜叶、果皮、茶渣其他垃圾典型关键词烟蒂、陶瓷碎片、卫生纸。实际开发时我会在t_category表里给每个类别加一个default_keywords字段作为兜底的候选池。同时t_garbage_item表的keywords字段存储更具体的物品别名。识别时先查物品表精确匹配匹配不到再查类别表的默认关键词。这样既支持矿泉水瓶这种具体物品也支持塑胶这种模糊词。具体实现上最简单的逻辑就是遍历表里的关键词集合计算输入文本和每个关键词的相关度。相关度计算不止是String.contains还要考虑长度比重比如输入瓶字关键词矿泉水瓶包含它但瓶单独没太大意义所以要给长关键词更高权重。这部分逻辑写成代码很直观我用一个简化版的打分思路public ListGarbageItem matchItems(String input) { ListGarbageItem allItems garbageItemMapper.selectList(null); return allItems.stream() .filter(item - matchScore(item.getKeywords(), input) 0) .sorted((a, b) - Double.compare(matchScore(b.getKeywords(), input), matchScore(a.getKeywords(), input))) .limit(5) .collect(Collectors.toList()); } private double matchScore(String keywords, String input) { String[] parts keywords.split(,); double maxScore 0; for (String part : parts) { if (part.contains(input) || input.contains(part)) { double lenScore (double) Math.min(part.length(), input.length()) / Math.max(part.length(), input.length()); maxScore Math.max(maxScore, lenScore); } } return maxScore; }这个打分的思路是如果关键词和输入互相包含就以较短长度除以较长长度得到一个0到1之间的系数。输入矿泉水瓶和关键词矿泉水瓶完全一致得分1.0输入水瓶和关键词矿泉水瓶虽然包含关系成立但长度差异大得分只有0.5。这个系数在后面做兜底筛选时很好用。4.3 模糊匹配兜底Jaccard相似度实现现实使用中用户输入经常是半瓶矿泉水吃了两口的苹果这种带修饰语的句子直接做包含匹配往往命中不了。这一步就需要相似度兜底。我用的是基于字符集合的Jaccard相似度原理不复杂把输入字符串和物品名称拆成字符集合计算交集大小除以并集大小。两个字都相同的矿泉水瓶和矿泉水瓶盖相似度很高而苹果和香蕉几乎没有字符重叠相似度接近0。代码实现如下public double jaccardSimilarity(String a, String b) { SetCharacter setA a.chars() .mapToObj(c - (char) c).collect(Collectors.toSet()); SetCharacter setB b.chars() .mapToObj(c - (char) c).collect(Collectors.toSet()); SetCharacter union new HashSet(setA); union.addAll(setB); if (union.isEmpty()) return 0; int intersection 0; for (Character c : setA) { if (setB.contains(c)) intersection; } return (double) intersection / union.size(); }使用时先跑第一轮关键词精确匹配如果最高分低于0.8就对物品表中的名称字段统一做一次Jaccard相似度计算取相似度最高且超过0.6的结果作为候选。这里阈值不能设太高因为塑料瓶和塑料瓶盖字符重叠比例很高但语义上并不完全相同0.6到0.7之间比较合适。这个逻辑虽然朴素但实测效果在课设场景下足够稳定。我也用过一些现成的文本相似度库比如HanLP的编辑距离效果更好但引入了不必要的依赖。Jaccard实现简单逻辑透明答辩时三句话说清楚我觉得这才是课设项目该有的过犹不及。4.4 识别接口的缓存与降级策略识别接口是用户请求量最大的接口每次查询都要比对几十条甚至上百条垃圾数据虽然规模不大但性能上有个很隐蔽的问题所有垃圾物品数据每次都要全量加载到内存里做遍历。优化方式是用Redis做两级缓存第一级缓存输入内容→分类结果的映射设置30分钟过期第二级缓存全量垃圾物品的数据快照设置一小时过期。这样同一热词在30分钟内重复查询接口可以直接返回缓存值数据库压力几乎为零。代码上用一个切面或者简单的Cacheable注解就能搞定。降级策略则是考虑极端情况。如果Redis服务挂了识别接口不能跟着挂所以每次走缓存之前要加异常捕获一旦Redis连接失败就回退到直接查数据库的逻辑。我写过一个版本是直接用try-catch包住Redis操作出异常就放行到数据库查询。后来想想这个设计其实挺正确的——课设场景下缓存是用来提效的不是用来保命的核心业务流程必须能脱离缓存独立运行。5. 核心接口与JWT鉴权能直接对接小程序的那套设计5.1 统一返回体与异常规范前后端联调少吵架的关键接口设计没有统一返回结构前后端联调就是灾难现场。有的接口返回{code:0, data:{}}有的直接返回裸对象前端要针对每个接口单独处理异常光对齐字段就能吵一天。我在这个项目里认准了一个标准返回体所有接口统一遵守Data public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(success); result.setData(data); return result; } public static T ResultT error(Integer code, String message) { ResultT result new Result(); result.setCode(code); result.setMessage(message); return result; } }配合全局异常处理器业务里抛出的任何异常都能自动包装成标准返回体不用在每个接口手写try-catch。比如用户未登录抛出code401参数不合法返回code400前端只要判断code是否为200就行大大降低了联调成本。5.2 JWT鉴权接入流程与拦截器配置JWT的核心是用户登录成功后在服务端用密钥签发一个token里面携带用户id和角色信息客户端保存token并在后续请求中携带。服务端通过拦截器校验token的合法性判断是否是有效登录用户。相比Session方案它不需要在服务端存储会话状态天然适合多个客户端并行使用的场景。这个系统的用户和管理员是两类人登录接口分别是/api/user/login和/api/admin/login签发的token里通过role字段区分身份。拦截器需要区分哪些路径需要校验、哪些路径放行比如垃圾识别接口可以让未登录用户使用但投放记录和积分接口必须登录后才能访问。这个配置用拦截器注册的方式实现Configuration public class WebConfig implements WebMvcConfigurer { Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(jwtInterceptor) .addPathPatterns(/api/**) .excludePathPatterns(/api/user/login, /api/admin/login, /api/garbage/recognize); } }拦截器内部校验token的具体逻辑核心是解析token串、校验签名、提取用户id和角色放到Request上下文里供后续接口使用。中间有个很容易踩的坑是密码加密管理员密码千万不能明文存库用BCryptPasswordEncoder做哈希存储登录时用matches校验。这个很多教程没强调但做后端这是底线。5.3 积分加减的并发安全问题积分操作看起来就是个简单更新但这个简单操作的并发问题真的能藏一整晚。设想一个场景用户连续投放两次垃圾前端同时发起两个请求如果都用先查当前积分再加新积分再更新回去的流程两个请求都读到了同一个初始值后更新的那个就把前面加的分覆盖掉了用户白扔了垃圾。正确的做法是数据库层面的原子操作把查询和累加合成一条SQL。MyBatis-Plus支持直接传入setSql做原生SQL片段更新// 直接对数据库做原子累加不需要先查询再更新 userMapper.update(null, new LambdaUpdateWrapperUser() .setSql(total_points total_points points) .eq(User::getId, userId));这样无论并发多少个请求数据库都能保证最终值等于初始值加上所有累加值不会丢失更新。这个点虽然小但是体现对并发问题的理解答辩时聊起来是很加分的设计细节。5.4 管理端统计报表的数据聚合写法统计报表无非是分组聚合加时间筛选。按垃圾类别统计投放量对应SQL是GROUP BY category_id COUNT(*)按天统计投放趋势对应SQL是DATE_FORMAT(create_time, %Y-%m-%d) GROUP BY。用MyBatis-Plus的QueryWrapper没有直接写SQL舒服我一般直接写XML方法或注解SQL。select idcountByCategory resultTypemap SELECT category_id AS categoryId, COUNT(*) AS cnt FROM t_drop_record WHERE create_time BETWEEN #{start} AND #{end} GROUP BY category_id /select前端拿到这个ListMapString, Object直接渲染饼图。有一点要注意的是不要把聚合结果只限制在投放记录表上识别记录表也可以做统计——比如用户识别错误的垃圾有哪些这能从数据层面暴露用户的分类知识盲区管理员针对高频错误可以在系统里推送分类指南。这些报表做出来系统的智能逼格就提升一个档次了。6. 从开发到部署那些差点让我砸键盘的坑6.1 跨域配置和JWT拦截器的执行顺序问题前后端分离项目最常见的坑就是跨域。前端在8080端口后端在9090端口前端发请求时浏览器会先发一个OPTIONS预检请求验证服务器允不允许跨域。如果你的拦截器把这个OPTIONS请求也拦截下来并返回401浏览器就会判定跨域失败前端控制台报一串看不懂的CORS错误。这个问题的根源是拦截器在处理请求时根本没有区分预检请求和真实业务请求。解决办法也很简单在JWT拦截器的preHandle方法开头加一行判断if (OPTIONS.equalsIgnoreCase(request.getMethod())) { return true; }把预检请求直接放行后面的CORS配置由Spring的WebMvcConfigurer接上。我这里再补充一个细节同时配置了跨域和拦截器时推荐跨域配置用allowedOriginPatterns而不是allowedOrigins因为后者在携带凭证时对具体来源有限制*写法有时候会失效。6.2 MyBatis-Plus分页插件失效的典型原因用MyBatis-Plus做分页查询的人应该都遇到过这个情况明明写了selectPage结果方法返回的记录数是对的但SQL却是全表查完内存分页数据一大页面就卡死。这就是分页插件没有生效。MyBatis-Plus的分页插件是要手动注册的默认配置里没这个拦截器。需要写一个配置类把PaginationInnerInterceptor注入容器Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor( new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }另一个案例是Spring Boot 3.x下用了旧的mybatis-plus-boot-starter依赖导致分页拦截器虽然注册了但没被正常加载。换成mybatis-plus-spring-boot3-starter之后立刻好了。排查这类问题最简单的方式是打开SQL日志mybatis-plus.configuration.log-implorg.apache.ibatis.logging.stdout.StdOutImpl看控制台输出的SQL里有没有LIMIT关键字没有就是插件没在干活。6.3 垃圾名称的脏数据与唯一索引设计这个系统的核心数据是垃圾物品表一旦数据质量差识别结果就会闹笑话。常见问题有三个同一物品录入多次比如矿泉水瓶和矿泉水瓶 尾部多个空格就变成两条记录大小写混用iPhone和iphone被当成不同垃圾名称不规范有人录空的塑料瓶有人录塑料瓶。我的处理办法是双管齐下。建表时给name字段加唯一索引防止完全重复的数据进入。录入和导入数据时做一次规范清洗统一trim()去掉首尾空格英文转小写去重后再插入。识别模块加载数据时也做缓存预热初始化时把物品表中的别名都加载进内存避免每次请求都打数据库。唯一索引插入冲突时MyBatis-Plus抛出DuplicateKeyException全局异常处理器要把它捕获并转成友好的提示该垃圾名称已存在请勿重复添加而不是把一堆堆栈信息抛给前端。这个细节在管理端录入垃圾数据时体验提升非常明显。6.4 演示环境部署与数据预置的建议最后说部署和数据预置这部分对课设答辩的成败影响被严重低估了。很多同学代码写完部署到服务器上一看垃圾物品表空空的识别什么都说查不到演示效果直接从100分跌到30分。部署时的建议是数据库用MySQL 8缓存用Redis两个服务用Docker Compose一次拉起应用打成jar包用nohup java -jar后台运行。这里有个生产环境不要用的东西——Spring Boot自带的spring-boot-devtools它会导致类加载器行为异常还会自动重启应用部署到服务器上如果忘了排除可能频繁重启。数据预置要做两件事一是准备一份基础的垃圾分类数据SQL脚本至少包含100条常见垃圾物品覆盖四色分类这样演示时随手输入可乐瓶废电池剩菜都能出结果二是准备一个演示账号预置几百积分的流水方便直接演示积分兑换流程。我最后一次带队做这个项目的时候光数据预置就花了一个晚上把各类别下的常见物品都手敲进去了后来演示全程没翻车这个时间花得值。整套项目走下来从需求访谈、表结构设计、识别逻辑到接口联调和部署其实最耗时的不是写代码本身而是那些看起来是细节但决定成败的部分——比如垃圾别名是否丰富、演示数据是否完整、并发加分是否安全。如果你正在做或者准备做这个系统我建议你特别留意这三件事张表设计的时候就把别名和状态字段规划好别等代码写了一半再改表识别模块一定要先积累一批真实垃圾名称去测试不要等到部署环境才第一次见到半瓶矿泉水这种输入部署前把演示数据脚本准备好这比优化任何一行SQL都更能提升答辩效果。这套源码和配套的SQL初始化脚本文末可以联系我获取直接私信留邮箱就行。