
每年的毕业设计季我都会收到一堆私信问得最多的不是“怎么实现”而是“做什么题目才稳妥又有亮点”。这里说的“稳妥”不是指那种数据库增删改查的普通管理系统而是指在技术达标、工作量丰满、演示效果好这三点上都要占得住。如果你正在为选题发愁校园“光盘行动”这个方向我建议你认真看看。它把当下高校食堂浪费现象作为切入点用 Spring Boot 搭后端、Vue 做前端再叠加上 AI 图像识别来判断餐盘是否光盘最后用社交排行榜调动参与积极性。一套组合下来技术栈新老结合、模块清晰、选题还自带社会责任色彩非常适合拿来作为毕业设计。我接下来会把这个项目的核心拆开讲从整体设计、AI 识别实现、排行榜玩法、到实际部署和踩坑记录尽量把那些课程设计里不会教你的细节都补上。无论你是刚学完 SSM、只会写增删改查还是已经能独立做前后端分离项目这篇内容都能帮你把思路捋顺并且给你一份能直接落地的参考方案。1. 项目整体设计与核心思路1.1 这个平台到底要解决什么问题先别急着写代码我们得把需求捋清楚。校园食堂每天产生的餐厨垃圾是非常可观的但真正落实到管理层面很少有人能给出一个量化的、可感知的方案。传统的“光盘行动”往往靠宣传标语大家看看就过去了没有后续反馈也没有参与感。这个平台要解决的核心问题有两个一是怎么让“光盘”这个行为被快速、客观地记录二是怎么让记录下来的行为产生持续吸引力。前者靠 AI 识别技术后者靠社交排行榜。你把这两个点想透了整个项目的技术选型和模块划分就都有了依据。这里还要补充一个隐藏需求作为一个面向校园场景的管理系统它不应该只服务某一个角色。学生需要上传餐盘照片、查看自己的积分和排名食堂管理员需要审核记录、统计浪费数据、查看热门菜品系统管理员可能需要管理用户和公告。这些角色对应的权限和界面都要在设计前期就预留出来不要等代码写了一半再回头加那样会很痛苦。1.2 技术选型为什么是 Spring Boot Vue很多人在选题时会纠结用 Django 还是用 Spring Boot用 React 还是 Vue。我个人的建议是除非你 Django 已经写得非常熟练否则 Spring Boot 更加稳妥。原因有三个。第一校园场景下的管理系统核心还是业务数据的流转Spring Boot 在这方面的生态非常成熟分页、事务、权限、文件上传都有现成方案遇到问题随便一搜就能找到答案。第二你做的是一个前后端分离的项目后端只提供接口Spring Boot 的 RESTful API 开发体验足够顺滑。第三毕业设计答辩时评委老师对 Spring Boot 的接受度普遍很高你讲解起来也更有底气。前端选择 Vue 而不是 React主要考虑的是上手门槛。Vue 的模板语法更直观状态管理有 Pinia路由配置也简单配合 Element Plus 做后台管理界面几行代码就能出不错的效果。如果做的是移动端 H5用 Vant 组件库也很快。前后端分离的结构也意味着你可以并行开发后端不用等前端页面做完就能通过工具调试接口。1.3 功能模块的整体拆解先画一个整体地图免得后面走偏。整个平台可以分成四个核心端后端服务Spring Boot 负责用户认证、照片上传与记录管理、AI 识别调度、排行榜计算、数据统计。AI 识别模块可以放在项目中独立的一个服务里也可以作为 Python 写的独立服务通过 HTTP 接口调用后面我会展开讲。管理后台Vue Element Plus给管理员用的主要处理审核、用户管理、食堂菜品数据统计。学生端Vue 或其他移动端适配方案用于上传照片、查看个人积分、浏览排行榜、参与互动。四个端之间通过接口通信识别模块是独立的后端调用它前端不直接接触 AI 模型。这个分层的好处是如果以后模型升级或者想把识别能力复用给小程序端都不会影响主流程。2. AI 识别“光盘”背后的实现方案2.1 图像识别还是图像分类这是整个项目里最容易被大家搞复杂的地方。有人一看到“AI 识别”就直接想到用 YOLO 做目标检测要识别餐盘、识别食物、识别剩菜越做越复杂最后训练出来的模型效果还很差。实际上对于“光盘行动”这个场景你要判断的只有一件事餐盘里还有没有明显剩菜。这本质上是一个图像分类问题而不是目标检测问题。你用“光盘”和“非光盘”两类数据训练分类模型完全够用。当然如果想让系统更精细一点比如识别出“肉类剩余”和“蔬菜剩余”那才需要考虑目标检测但这对于毕业设计来说属于加分项而不是必选项时间不够完全可以不做。我建议把主路径做好再考虑扩展。2.2 业内常用的识别流程图片上传到结果回显一个比较稳妥的流程是这样的用户拍下餐盘照片上传到 Spring Boot 后端后端把图片保存到本地目录或对象存储然后把图片路径传给 AI 服务AI 服务完成推理返回一个“是否光盘”的结果以及置信度后端拿到结果后生成一条记录并把积分加给用户。整个流程里有两个关键点要注意一个是图片的存储。很多人做项目时直接把图片以 Base64 字符串传给后端再解码保存这样做接口传输大、效率低调试也不方便。更好的方式是前端用 multipart/form-data 表单上传后端用 MultipartFile 接收保存后返回图片的 URL 给前端预览。另一个是 AI 推理的同步与异步。如果识别服务响应很快同步调用没问题。但如果模型比较大推理耗时超过 2 秒同步调用会让用户一直等着体验很差。最优做法是后端先把记录状态置为“识别中”随后通过异步调用 AI 服务识别完成后回调更新状态。学生端刷新后就能看到识别结果这种体验比转圈等待要好得多。2.3 模型训练数据从哪里来训练数据是 AI 识别项目里最现实的坎。公开数据集里的餐盘图片大多是西餐场景跟国内食堂的餐盘差距不小所以最好使用自采数据。自采数据的一个可行方案是去学校食堂拍。可以先和食堂负责人打个招呼说明是学校内的活动平台一般都会支持。拍摄时注意覆盖不同角度、不同光线、不同菜品类型至少每个类别准备 500 到 1000 张。如果个人拍摄能力有限可以利用网络图片补一部分但要记得筛选掉明显不符合国内食堂场景的图片。数据标注相对简单。因为是二分类任务不需要打框只需要按目录整理即可例如两个文件夹ok 和 not_ok分别放光盘和非光盘。这里有个技巧与其从零训练一个很大的模型不用充分训练的小模型不如直接用迁移学习的方式在预训练模型基础上微调这样收敛快数据量要求也不高。我实际用过的一个方案是基于 MobileNetV3 或者 EfficientNet 轻量级模型用 PyTorch 训练只改最后一层全连接层为二分类输出。训练轮次大约 10 到 15 轮在 1000 张左右的数据集上就能达到 90% 以上的准确率。对于毕设演示来说这个效果已经完全够用了。2.4 为什么不能忽略置信度阈值分类模型输出的不是一个简单的类别而是每个类别的概率。比如“光盘”的概率是 0.82“非光盘”的概率是 0.18这时如果阈值设置为 0.5系统就会判断为光盘。但真实场景里用户拍摄的照片可能模糊、光线不足或者拍摄角度很刁钻。我建议你在系统中留一个可配置的置信度阈值。也就是说如果概率低于某个值比如 0.6就标记为“待人工审核”。这不只是在技术上加了一个保险实际效果上也能减少错误识别带来的争议。食堂阿姨都说“这也算光盘”会上架的模型经不起真实用户考验。阈值这个参数可以在管理后台设置方便调整。不同食堂的餐盘颜色、灯光条件不一样你的模型在一个学校训练得很好换个学校可能不灵阈值就是用来兜底的。3. 前后端联调与社交排行榜的设计3.1 后端接口设计别把排行榜做成死数据设计接口时我习惯先把用户故事画出来。用户上传照片需要有一个上传接口用户查看自己的历史记录需要有一个记录列表接口用户查看排行榜需要有一个排行接口管理员审核需要有一个待审核列表接口。这里我不展开写所有接口重点说两个容易出彩的设计。第一个是上传接口的返回格式。不要只返回“成功或失败”最好一次性把识别结果、积分变化、当前总积分一起返回。前端拿到这些数据可以直接弹窗提示“本次光盘成功积分 2”体验会好很多。第二个是排行榜的查询参数。你可以设计为支持按“日排行”“周排行”“总排行”三种维度切换。怎么实现呢如果只查总排行一条 SQL 就够了。但日排行和周排行需要时间过滤条件。我的实现是用 Redis 缓存活跃用户的积分每天定时把昨日数据写入数据库再生成日报表。周排行同理每周计算一次。这样做的好处是学生频繁刷新排行榜时不会每次都打到数据库的聚合查询上性能压力小很多。3.2 数据库表设计怎么保存“识别结果”数据库设计是整个项目的基石我见过太多人把表设计得乱七八糟。这里给出一个最少但合理的表结构参考用户表用户ID、微信/学号、昵称、头像、总积分、今日积分创建时间。识别记录表记录ID、用户ID、图片URL、识别结果0/1、置信度、积分变动、状态待审核/通过/驳回、创建时间。排行榜快照表快照ID、排名类型日/周/总、排名数据JSON或用户ID分段存储、生成时间。公告表公告ID、标题、内容、发布时间。需要注意的一个坑是“今日积分”这个字段。你可以设计成冗余字段在用户表里存一份然后用定时任务在每天零点清零。但如果并发量高多次更新用户同一个字段会有性能问题。替代方案是不存今日积分实时从记录表里聚合但这样排行榜查询会变慢。折中的做法是用 Redis 的 Hash 结构存今日积分零点后自动过期数据库只保存累计积分这样查询和更新的压力都小。3.3 排行榜算法不是简单按总积分排序如果排行榜只是按“总积分降序”排序那这个功能就太单薄了。毕业答辩时评委问“你的算法有什么特色”你总不能说是 order by score desc 吧我建议引入“行为权重”和“时间衰减”两个概念。用户在活动初期参与和中期坚持应该有不同的积分逻辑。例如每天首次光盘 2 分连续打卡第 7 天额外 5 分邀请好友注册并完成一次光盘 3 分。这些规则虽然简单但会让你项目里的“积分”体系变得有血有肉。排行榜本身可以引入热度计算比如用 Gibbs 采样模型分析参与行为或者更简单一点使用指数衰减公式rank_score 总积分 / (天数衰减系数)让长期坚持用户排在前面而不是一次性刷分用户。具体参数可以根据活动周期调整。答辩时你说清楚了“为什么要用衰减函数”这就是一个亮点。不过要小心不要为了复杂度强行引入不必要的算法。毕业设计评分看重的是你能否把需求和实现讲清楚一个小而精的排行加权策略比一个你不懂原理的复杂公式要好得多。3.4 前端页面与交互如何留住用户前端页面做得好不好看直接影响老师的第一印象。但比“好看”更重要的是“让用户知道下一步干嘛”。学生端首页建议直接放两个大按钮“拍照识别”和“查看榜单”。识别完成后用一个有仪式感的动效展示“光盘成功”再加一个进度条展示连续打卡天数。排行榜页面可以用 Tabs 切换日榜、周榜、总榜前三名做突出显示。这里有个小细节当前登录用户的排名要单独在顶部固定展示不要让学生翻好几页才看到自己在哪。这一点很加分。管理后台的界面不需要追求花哨重点是数据可视化。用 ECharts 画出本周每日光盘次数、每周浪费趋势、食堂热门菜品剩余次数这些图表会让你的毕设答辩瞬间提升一个档次。4. 实操过程与踩坑记录4.1 从零搭起前后端项目骨架实践的第一步不是写代码而是把项目骨架搭好。我推荐的方式是后端创建一个 Spring Boot 项目带上 spring-boot-starter-web、mybatis-plus、mysql-connector、lombok、spring-boot-starter-validation 这些基础依赖。前端创建一个 Vue 项目这里强调一下创建时直接用 Vite 而不是 Vue CLIVite 启动速度快对新版本 Node 的兼容性也更好。项目目录建议采用 Maven 标准结构controller、service、mapper、entity、config 分包清晰。前端目录按 views、components、router、store、api 分成几块这样项目结构一目了然。答辩时老师问你模块划分你能说出理由就是加分。数据库表在建表时字段注释一定要写清楚。尤其像 status、type 这种字段用 TINYINT 定义后必须写清楚 0 和 1 分别代表什么否则过两天你自己都会忘记。最好在项目文档里附上建表 SQL 和字段说明。4.2 AI 识别调用时最容易踩的坑AI 服务写好后第一个大坑就是 Python 环境和 Spring Boot 的环境怎么互通。常见做法是启动一个 Flask 服务把训练好的模型加载到内存中提供 /predict 接口入参是图片路径或图片字节流返回 JSON 结果。Spring Boot 端通过 RestTemplate 或 HTTP 客户端发请求过去两边都是 HTTP 协议互不干扰。这个方案本身简单但有几个实际问题Flask 服务启动时要加载模型加载成功前不能接收请求。需要在启动日志里输出模型加载完成的标识否则前端会莫名 500。图片路径问题Spring Boot 保存的本地路径Python 服务所在的服务器不一定访问得到。如果是本地开发就无所谓部署到服务器上就要改配置文件把路径统一成绝对路径或使用对象存储。并发问题Flask 默认开发服务器不能支撑高并发。如果学生同时上传照片Python 服务可能会阻塞。解决办法是用 gunicorn 搭多进程或者把模型推理时加一个线程锁控制同时只有 2 个推理请求。另一个实操细节是图片压缩。手机拍照出来的照片动辄 3MB 以上直接传给模型推理会拖慢速度。在 Spring Boot 后端接收图片后可以先压缩到 800x800 像素以内再传给 AI 服务既不损失识别精度又能显著加快推理时间。4.3 排行榜实时更新的性能坑排行榜的坑主要集中在“突发热度”上。原本一天只有几百次识别记录排行榜查询完全没问题。但某天食堂有个活动一小时涌进来上千条记录数据库聚合查询就开始变慢页面转圈。这里我实践后的解法是分化缓存。核心逻辑是今日排行用 Redis 的 ZSet 存储成员是用户ID分数是今日积分。每次给用户加分时同时更新 ZSet。排行榜接口只查询 Redis不查数据库。总榜和昨日的日榜则由定时任务在凌晨从数据库计算好写入一张结果表。定时任务跑完后把结果缓存起来前端查询时直接读缓存。这种写法的一个好处是排行榜看起来是实时的但数据库压力非常小。即便当天多了很多请求也只需要更新 Redis 中的几个 key。4.4 上传图片的安全限制怎么防“刷盘”校园场景里的用户大多是学生整体素质不错但“刷盘”行为依然不可小觑。有人会把同一张照片反复上传有人会把别人的光盘照片拿来用甚至有人用 PS 改图片去骗积分。如果不加限制排行榜就失去了意义。处理这个问题至少需要三层防护第一层是对同一用户短时间内的上传频率做限制比如 10 秒内只能提交一次接口层面用拦截器实现。第二层是“图片去重”可以给图片计算一个 MD5 或感知哈希值如果与某用户最近一次上传的图片哈希一致就提示“图片已被使用”。第三层是管理员人工审核置信度低于阈值的图片进入待审核队列管理员可以看图片决定是否通过。也许你会觉得人工审核很麻烦但实际上真正的“待审核”记录不会太多大部分用户的照片识别结果置信度都挺高管理员只要每天花 10 分钟处理新增记录就够了。5. 常见问题与排查技巧实录5.1 一张表看清高频问题和解决思路我把实际开发中遇到过的、项目中最容易出问题的点整理成了一张速查表方便你边做边对照。问题现象可能原因解决思路前端上传图片后返回 413Nginx 默认上传大小限制在 Nginx 配置中增大 client_max_body_sizeAI 服务识别结果一直超时Flask 开发服务器并发能力差换成 gunicorn 并配置多 worker排行榜数据和累计积分对不上当日多次修改用户积分导致并发覆盖使用 Redis 事务或数据库行锁更新图片上传后前端预览看不到后端保存路径和静态资源映射不一致Spring Boot 配置 resource handler上传照片被识别为“非光盘”的比例很高食堂灯光偏黄图像色彩失真训练数据里加入不同色温图片学生反复上传同一张照片刷积分缺少去重机制增加图片感知哈希去重字段排行榜加载很慢每次实时查询数据库聚合改为 Redis ZSet 定时任务缓存Redis 中的数据突然全部失效没有设置持久化或内存淘汰策略开启 AOF 或 RDB 持久化5.2 我实际踩过的几个坑和补救经验第一个坑是数据库事务范围过小。我在写“加分”逻辑时早期只把“插入识别记录”当作一个事务处理积分更新操作放在另一个方法里。结果有一次服务器在记录保存后、积分更新前意外重启导致用户刷了记录却没加分。后来我把“保存记录 更新积分 更新排行榜”放在同一个事务方法里问题就消失了。这里也提醒你识别成功这个动作应当具有原子性可以封装成一个独立事务方法。第二个坑是假阳性太高。第一次训练模型时我拍摄的照片全部是在食堂明亮的窗口光线条件下拍的结果模型在宿舍昏暗光线下经常把“有剩菜”识别成“光盘”。后来我加入随机亮度扰动、随机旋转等数据增强策略才把准确率拉回来。对于毕设这个体量数据增强比堆数据更有效。第三个坑是前端图片压缩。开始我为了效率把图片压缩到 256x256 再传识别速度非常快但图片框选区域中有剩菜时压缩后根本看不清细节准确率直线下降。后来调整为压缩到最长边 800px、质量压缩到 0.85准确率和速度达到了一个比较平衡的状态。第四个坑是排行榜的并发重复增加。有同学在写今日积分时用的是先读 Redis再加一再写回。连续两个请求同时发生时会丢失中间的加分导致积分变少。后来换成 INCRBY 原子操作再没出现过这个问题。这个点你部署时尤其要注意。5.3 答辩时如何把项目讲出高级感技术做完了最后还要能讲出来。很多同学答辩时喜欢从头到尾念 PPT讲完需求讲技术但老师听完最大的感受是“好像做了很多又好像什么都没做”。我的建议是准备一条主线先指出校园浪费真实现状再说明系统如何通过“AI 识别降低记录门槛、排行榜激励机制带动参与”解决这个问题。每个功能模块都围绕这条主线展开。技术亮点不需要全讲挑三个最有分量的一个轻量级识别模型的设计与调优过程一个排行榜缓存方案一个事务与防刷设计。每个技术点讲清楚“为什么这么做、不这么做会怎样”比罗列所有技术名词效果好得多。特别提醒一下AI 部分不要只说是“用了 PyTorch”要能说出你用的模型结构、训练的轮次、数据集规模、最终准确率以及为什么用置信度阈值。哪怕准确率只有 88%你完整交代了过程老师也会认可。6. 写在项目之后的话项目做完并不代表结束它还可以有非常多可以延展的方向。比如把识别服务从 Flask 换成一个服务化框架让模型热更新比如给前端加一个小程序版本学生不用下载 App 就能参与比如在排行榜中加入学院和班级维度让集体与集体之间也能形成良性竞争再比如把积累的浪费数据做成可视化的趋势大屏放在食堂门口展示。我始终觉得毕业设计是大学阶段最后一次可以完全按自己想法做一件事的机会不一定要把技术堆得多高但要把问题想明白把方案做完整。如果我上面写的这些经验能让你少踩两个坑在答辩时多一份从容那就足够了。